From f24aff7a1ce30e420bfa44f0bb3541a82ffb4353 Mon Sep 17 00:00:00 2001 From: Santiago Botta Date: Tue, 12 Mar 2024 17:22:55 -0300 Subject: [PATCH 1/8] Adda function to fetch stream entry datetime --- lib/redis/stream.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/redis/stream.ex b/lib/redis/stream.ex index 4ecd851..6ba10c6 100644 --- a/lib/redis/stream.ex +++ b/lib/redis/stream.ex @@ -82,6 +82,9 @@ defmodule Redis.Stream.Entry do end) end + @spec get_datetime(t()) :: DateTime.t() + def get_datetime(%__MODULE__{datetime: datetime}), do: datetime + @spec parse_stream_entry_id(String.t()) :: DateTime.t() defp parse_stream_entry_id(entry_id) do entry_id From 2ac151c42309f4f20b16267dc17f539bc221d3d7 Mon Sep 17 00:00:00 2001 From: Santiago Botta Date: Tue, 12 Mar 2024 17:23:13 -0300 Subject: [PATCH 2/8] Fix filter_history_entries to properly sort elements --- lib/ex_finance/currencies.ex | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/ex_finance/currencies.ex b/lib/ex_finance/currencies.ex index 84bb00c..115fbb2 100644 --- a/lib/ex_finance/currencies.ex +++ b/lib/ex_finance/currencies.ex @@ -9,6 +9,8 @@ defmodule ExFinance.Currencies do alias ExFinance.Currencies.{Currency, Supplier} alias ExFinance.Repo + alias Redis.Stream + require Logger ## Events @@ -408,11 +410,19 @@ defmodule ExFinance.Currencies do ] defp filter_history_entries(entries) do entries - |> Enum.group_by(fn entry -> - "#{entry.datetime.year}-#{entry.datetime.month}-#{entry.datetime.day}" + |> Enum.group_by(&DateTime.to_date(Stream.Entry.get_datetime(&1))) + |> Enum.map(fn {_datetime, entries} -> + entries + |> Enum.sort_by( + &DateTime.to_date(Stream.Entry.get_datetime(&1)), + {:desc, Date} + ) + |> hd() end) - |> Enum.map(fn {_datetime, entries} -> hd(entries) end) - |> Enum.sort_by(& &1.datetime, :asc) + |> Enum.sort_by( + &DateTime.to_date(Stream.Entry.get_datetime(&1)), + {:asc, Date} + ) end @spec map_currency_history([Redis.Stream.Entry.t()]) :: [ From be9f4aaf04d27c8c103e4146d3b728232569850c Mon Sep 17 00:00:00 2001 From: Santiago Botta Date: Tue, 12 Mar 2024 17:55:14 -0300 Subject: [PATCH 3/8] Improvements in the Show view for dollar quotes --- .../live/public/currency_live/show.html.heex | 109 +++++++++--------- 1 file changed, 57 insertions(+), 52 deletions(-) diff --git a/lib/ex_finance_web/live/public/currency_live/show.html.heex b/lib/ex_finance_web/live/public/currency_live/show.html.heex index 4bbed79..7b39339 100644 --- a/lib/ex_finance_web/live/public/currency_live/show.html.heex +++ b/lib/ex_finance_web/live/public/currency_live/show.html.heex @@ -1,68 +1,73 @@ <.header> -
+
-
-

- <%= @currency.name %> -

- <%= if @currency.info_type == :reference do %> -

- <%= render_price(@currency.variation_price) %> -

- <% end %> - <%= if @currency.info_type == :market do %> -

- <%= render_price(@currency.buy_price) %> - <%= render_price( - @currency.sell_price - ) %> -

+
+
+

+ <%= @currency.name %> +

+ <%= if @currency.info_type == :reference do %> +

+ <%= render_price(@currency.variation_price) %> +

+ <% end %> + <%= if @currency.info_type == :market do %> +

+ <%= render_price(@currency.buy_price) %> - <%= render_price( + @currency.sell_price + ) %> +

+

+ Spread: + + <%= render_spread(@currency) %> + +

+ <% end %>

- Spread: - - <%= render_spread(@currency) %> - + <%= render_update_time(@currency) %>

- <% end %> -

- <%= render_update_time(@currency) %> -

- -
-
-
-

- <%= render_variation_percent(@currency) %> -

+ +
+
+
+

+ <%= render_variation_percent(@currency) %> +

+
+
+ <%= render_chart(@socket) %> +
<%!--
--%> -
- <%= render_chart(@socket) %> -
<.back navigate={~p"/currencies"}><%= gettext("Back to currencies") %> From d4101dbdb77f02ebbec4515b2ab4b00f6b93b8d8 Mon Sep 17 00:00:00 2001 From: Santiago Botta Date: Tue, 12 Mar 2024 19:36:00 -0300 Subject: [PATCH 4/8] Add support to fetch history by week and month --- lib/ex_finance/currencies.ex | 45 ++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/lib/ex_finance/currencies.ex b/lib/ex_finance/currencies.ex index 115fbb2..08367c5 100644 --- a/lib/ex_finance/currencies.ex +++ b/lib/ex_finance/currencies.ex @@ -13,6 +13,8 @@ defmodule ExFinance.Currencies do require Logger + @type interval :: :daily | :weekly | :monthly + ## Events @doc """ @@ -372,20 +374,20 @@ defmodule ExFinance.Currencies do """ @spec fetch_currency_history(String.t(), String.t()) :: {:ok, [Redis.Stream.Entry.t()]} | :error - def fetch_currency_history(supplier_name, type) do + def fetch_currency_history(supplier_name, type, interval \\ :daily) do stream_name = get_stream_name("currency-history_" <> supplier_name <> "_" <> type) - days_before_now = -20 + before_now = look_into_the_past(-20, interval) since = DateTime.utc_now() - |> DateTime.add(days_before_now, :day) + |> DateTime.add(before_now, :day) |> DateTime.to_unix(:millisecond) with {:ok, entries} <- Redis.Client.fetch_reverse_stream_since(stream_name, since), - filtered_entries <- filter_history_entries(entries), + filtered_entries <- filter_history_entries(entries, interval), history <- map_currency_history(filtered_entries) do {:ok, history} else @@ -405,12 +407,17 @@ defmodule ExFinance.Currencies do :error end - @spec filter_history_entries([Redis.Stream.Entry.t()]) :: [ + @spec look_into_the_past(integer(), atom()) :: integer() + defp look_into_the_past(days, :daily), do: days + defp look_into_the_past(days, :monthly), do: days * 30 + defp look_into_the_past(days, :weekly), do: days * 7 + + @spec filter_history_entries([Redis.Stream.Entry.t()], atom()) :: [ Redis.Stream.Entry.t() ] - defp filter_history_entries(entries) do + defp filter_history_entries(entries, interval) do entries - |> Enum.group_by(&DateTime.to_date(Stream.Entry.get_datetime(&1))) + |> group_history_by(interval) |> Enum.map(fn {_datetime, entries} -> entries |> Enum.sort_by( @@ -425,6 +432,30 @@ defmodule ExFinance.Currencies do ) end + @spec group_history_by([Redis.Stream.Entry.t()], interval()) :: map() + defp group_history_by(entries, :daily) do + entries + |> Enum.group_by(&DateTime.to_date(Stream.Entry.get_datetime(&1))) + end + + defp group_history_by(entries, :monthly) do + entries + |> Enum.group_by(fn %Stream.Entry{} = entry -> + Stream.Entry.get_datetime(entry) + |> DateTime.to_date() + |> Date.beginning_of_month() + end) + end + + defp group_history_by(entries, :weekly) do + entries + |> Enum.group_by(fn %Stream.Entry{} = entry -> + Stream.Entry.get_datetime(entry) + |> DateTime.to_date() + |> Date.beginning_of_week() + end) + end + @spec map_currency_history([Redis.Stream.Entry.t()]) :: [ {NaiveDateTime.t(), Currency.t()} ] From 035c775ef7670151c4e377ec574ee5174ece1eba Mon Sep 17 00:00:00 2001 From: Santiago Botta Date: Wed, 13 Mar 2024 00:49:48 -0300 Subject: [PATCH 5/8] Update translations --- priv/gettext/default.pot | 17 ++++++++++++++++- priv/gettext/en/LC_MESSAGES/default.po | 17 ++++++++++++++++- priv/gettext/es_AR/LC_MESSAGES/default.po | 17 ++++++++++++++++- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot index 10ae477..a7c97d7 100644 --- a/priv/gettext/default.pot +++ b/priv/gettext/default.pot @@ -80,7 +80,7 @@ msgid "Settings" msgstr "" #: lib/ex_finance_web/live/admin/currency_live/show.html.heex:25 -#: lib/ex_finance_web/live/public/currency_live/show.html.heex:68 +#: lib/ex_finance_web/live/public/currency_live/show.html.heex:85 #, elixir-autogen, elixir-format msgid "Back to currencies" msgstr "" @@ -161,3 +161,18 @@ msgstr "" #, elixir-autogen, elixir-format msgid "Enter a Cedear price to find out if the security value is below or above the fair price." msgstr "" + +#: lib/ex_finance_web/live/public/currency_live/show.html.heex:67 +#, elixir-autogen, elixir-format +msgid "Daily" +msgstr "" + +#: lib/ex_finance_web/live/public/currency_live/show.html.heex:73 +#, elixir-autogen, elixir-format +msgid "Monthly" +msgstr "" + +#: lib/ex_finance_web/live/public/currency_live/show.html.heex:70 +#, elixir-autogen, elixir-format +msgid "Weekly" +msgstr "" diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po index 1bf9b07..920f1e4 100644 --- a/priv/gettext/en/LC_MESSAGES/default.po +++ b/priv/gettext/en/LC_MESSAGES/default.po @@ -80,7 +80,7 @@ msgid "Settings" msgstr "" #: lib/ex_finance_web/live/admin/currency_live/show.html.heex:25 -#: lib/ex_finance_web/live/public/currency_live/show.html.heex:68 +#: lib/ex_finance_web/live/public/currency_live/show.html.heex:85 #, elixir-autogen, elixir-format msgid "Back to currencies" msgstr "" @@ -161,3 +161,18 @@ msgstr "" #, elixir-autogen, elixir-format, fuzzy msgid "Enter a Cedear price to find out if the security value is below or above the fair price." msgstr "" + +#: lib/ex_finance_web/live/public/currency_live/show.html.heex:67 +#, elixir-autogen, elixir-format +msgid "Daily" +msgstr "" + +#: lib/ex_finance_web/live/public/currency_live/show.html.heex:73 +#, elixir-autogen, elixir-format +msgid "Monthly" +msgstr "" + +#: lib/ex_finance_web/live/public/currency_live/show.html.heex:70 +#, elixir-autogen, elixir-format +msgid "Weekly" +msgstr "" diff --git a/priv/gettext/es_AR/LC_MESSAGES/default.po b/priv/gettext/es_AR/LC_MESSAGES/default.po index 301605d..b45af78 100644 --- a/priv/gettext/es_AR/LC_MESSAGES/default.po +++ b/priv/gettext/es_AR/LC_MESSAGES/default.po @@ -80,7 +80,7 @@ msgid "Settings" msgstr "Configuración" #: lib/ex_finance_web/live/admin/currency_live/show.html.heex:25 -#: lib/ex_finance_web/live/public/currency_live/show.html.heex:68 +#: lib/ex_finance_web/live/public/currency_live/show.html.heex:85 #, elixir-autogen, elixir-format msgid "Back to currencies" msgstr "Volver a cotizaciones" @@ -161,3 +161,18 @@ msgstr "Invitame un café en cafecito.app" #, elixir-autogen, elixir-format, fuzzy msgid "Enter a Cedear price to find out if the security value is below or above the fair price." msgstr "Ingresa un precio de Cedear para conocer si el valor del título se encuentra por debajo o encima del precio justo." + +#: lib/ex_finance_web/live/public/currency_live/show.html.heex:67 +#, elixir-autogen, elixir-format +msgid "Daily" +msgstr "Diario" + +#: lib/ex_finance_web/live/public/currency_live/show.html.heex:73 +#, elixir-autogen, elixir-format +msgid "Monthly" +msgstr "Mensual" + +#: lib/ex_finance_web/live/public/currency_live/show.html.heex:70 +#, elixir-autogen, elixir-format +msgid "Weekly" +msgstr "Semanal" From 66fc8adac2bde173d14a04a86d2f7d8580b2af88 Mon Sep 17 00:00:00 2001 From: Santiago Botta Date: Wed, 13 Mar 2024 00:54:12 -0300 Subject: [PATCH 6/8] Update the line chart to calculate suggested min and max --- assets/js/charts/line_chart.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/assets/js/charts/line_chart.js b/assets/js/charts/line_chart.js index e0a8eaa..d998104 100644 --- a/assets/js/charts/line_chart.js +++ b/assets/js/charts/line_chart.js @@ -45,12 +45,28 @@ export default class { this.chart = new Chart(ctx, config) } + resetDataset(label) { + const dataset = this._findDataset(label) + if (dataset) { + dataset.data = [] + } + this.chart.config.data.labels = [] + this.chart.update() + } + addPoint(data_label, label, value, backgroundColor, borderColor) { this.chart.config.data.labels.push(data_label) const dataset = this._findDataset(label) || this._createDataset( label, backgroundColor, borderColor ) dataset.data.push({x: Date.now(), y: value}) + + const numericYValues = dataset.data.map(point => parseFloat(point.y)) + const suggestedMin = Math.min(...numericYValues); + const suggestedMax = Math.max(...numericYValues); + this.chart.config.options.scales.y.suggestedMin = suggestedMin - 50 + this.chart.config.options.scales.y.suggestedMax = suggestedMax + 50 + this.chart.update() } From 7f5c38eac605416b9ced23764da01bb6c2ff5ffa Mon Sep 17 00:00:00 2001 From: Santiago Botta Date: Wed, 13 Mar 2024 00:54:26 -0300 Subject: [PATCH 7/8] Add an event handler to reset datasets --- assets/js/hooks/line_chart_hook.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/assets/js/hooks/line_chart_hook.js b/assets/js/hooks/line_chart_hook.js index 6b13b50..11b2de1 100644 --- a/assets/js/hooks/line_chart_hook.js +++ b/assets/js/hooks/line_chart_hook.js @@ -7,6 +7,10 @@ export default { mounted() { this.chart = new RealtimeLineChart(this.el) + this.handleEvent('reset-dataset', ({ label }) => { + this.chart.resetDataset(label) + }) + this.handleEvent('new-point', ({ background_color, border_color, From c09d82b4c490f37a464f32248b310d386f1198ec Mon Sep 17 00:00:00 2001 From: Santiago Botta Date: Wed, 13 Mar 2024 01:17:28 -0300 Subject: [PATCH 8/8] Add support to select intervals in currency quotes --- .../live/public/currency_live/show.ex | 38 ++++++++++++++----- .../live/public/currency_live/show.html.heex | 38 +++++++++++++++++++ 2 files changed, 66 insertions(+), 10 deletions(-) diff --git a/lib/ex_finance_web/live/public/currency_live/show.ex b/lib/ex_finance_web/live/public/currency_live/show.ex index 9bb8ace..77282fd 100644 --- a/lib/ex_finance_web/live/public/currency_live/show.ex +++ b/lib/ex_finance_web/live/public/currency_live/show.ex @@ -8,10 +8,22 @@ defmodule ExFinanceWeb.Public.CurrencyLive.Show do @impl true def mount(_params, _session, socket) do if connected?(socket) do - Process.send_after(self(), :update_chart, 500) + Process.send_after(self(), :update_chart, 50) end - {:ok, socket} + {:ok, + socket + |> assign_interval()} + end + + @impl true + def handle_event("interval_change", %{"interval" => interval}, socket) do + interval = String.to_existing_atom(interval) + Process.send_after(self(), :update_chart, 50) + + {:noreply, + socket + |> assign_interval(interval)} end @impl true @@ -22,7 +34,13 @@ defmodule ExFinanceWeb.Public.CurrencyLive.Show do supplier_name: supplier_name } <- socket.assigns.currency, {:ok, history} <- - Currencies.fetch_currency_history(supplier_name, type) do + Currencies.fetch_currency_history( + supplier_name, + type, + socket.assigns.interval + ) do + socket = push_event(socket, "reset-dataset", %{label: currency_name}) + socket = Enum.reduce(build_dataset(currency_name, history), socket, fn data, acc -> @@ -34,6 +52,7 @@ defmodule ExFinanceWeb.Public.CurrencyLive.Show do _error -> {:noreply, socket + |> push_event("reset-dataset", %{label: socket.assigns.currency.name}) |> push_event("new-point", %{ data_label: get_datetime_label(DateTime.utc_now()), label: socket.assigns.currency.name, @@ -167,13 +186,12 @@ defmodule ExFinanceWeb.Public.CurrencyLive.Show do defp render_chart(assigns) do ~H""" - + """ end + + @spec assign_interval(Phoenix.LiveView.Socket.t(), Currencies.interval()) :: + Phoenix.LiveView.Socket.t() + defp assign_interval(socket, interval \\ :daily), + do: assign(socket, :interval, interval) end diff --git a/lib/ex_finance_web/live/public/currency_live/show.html.heex b/lib/ex_finance_web/live/public/currency_live/show.html.heex index 7b39339..2584bff 100644 --- a/lib/ex_finance_web/live/public/currency_live/show.html.heex +++ b/lib/ex_finance_web/live/public/currency_live/show.html.heex @@ -62,6 +62,44 @@
+
+ + + +
<%= render_chart(@socket) %>