From 853dbde0fa45560b27cc0238b63bd011959e2a00 Mon Sep 17 00:00:00 2001 From: daveminer Date: Thu, 16 Nov 2023 14:04:25 -0500 Subject: [PATCH] break ticker table out to LiveComponent --- lib/basket/alpaca/http/client.ex | 54 ------------------- lib/basket/http/alpaca.ex | 5 +- lib/basket/http/alpaca/impl.ex | 11 +++- lib/basket_web/components/search_input.ex | 4 +- lib/basket_web/components/ticker_bar_table.ex | 48 +++++++++++++++++ lib/basket_web/live/overview.ex | 19 +------ test/basket_web/live/overview_test.exs | 8 +-- 7 files changed, 69 insertions(+), 80 deletions(-) delete mode 100644 lib/basket/alpaca/http/client.ex create mode 100644 lib/basket_web/components/ticker_bar_table.ex diff --git a/lib/basket/alpaca/http/client.ex b/lib/basket/alpaca/http/client.ex deleted file mode 100644 index 40a31c7..0000000 --- a/lib/basket/alpaca/http/client.ex +++ /dev/null @@ -1,54 +0,0 @@ -defmodule Basket.Alpaca.HttpClient do - @moduledoc """ - HTTP client for Alpaca API. - """ - - use HTTPoison.Base - - require Logger - - @assets_resource "/v2/assets" - @latest_quotes_resource "/v2/stocks/bars/latest" - - def process_request_headers(headers) do - headers ++ [{"APCA-API-KEY-ID", api_key()}, {"APCA-API-SECRET-KEY", api_secret()}] - end - - @spec latest_quote(String.t()) :: {:error, any()} | {:ok, map()} - def latest_quote(ticker) do - case get("#{data_url()}#{@latest_quotes_resource}", [], - params: %{feed: "iex", symbols: ticker} - ) do - {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> - {:ok, body} - - {:error, error} -> - {:error, error} - end - end - - @spec list_assets() :: {:error, any()} | {:ok, list(map())} - def list_assets do - case get("#{market_url()}#{@assets_resource}", [], - params: %{status: "active", asset_class: "us_equity"} - ) do - {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> - {:ok, body} - - {:error, error} -> - {:error, error} - end - end - - def process_response_body(body) do - Jason.decode!(body) - end - - defp data_url, do: Application.fetch_env!(:basket, :alpaca)[:data_http_url] - - defp market_url, do: Application.fetch_env!(:basket, :alpaca)[:market_http_url] - - defp api_key, do: Application.fetch_env!(:basket, :alpaca)[:api_key] - - defp api_secret, do: Application.fetch_env!(:basket, :alpaca)[:api_secret] -end diff --git a/lib/basket/http/alpaca.ex b/lib/basket/http/alpaca.ex index cfa5d9b..505ecfc 100644 --- a/lib/basket/http/alpaca.ex +++ b/lib/basket/http/alpaca.ex @@ -1,9 +1,12 @@ defmodule Basket.Http.Alpaca do + @moduledoc """ + Interface for the Alpaca REST API. + """ @callback latest_quote(ticker :: String.t()) :: {:ok, map} | {:error, String.t()} @callback list_assets() :: {:ok, map} | {:error, String.t()} def latest_quote(ticker), do: impl().latest_quote(ticker) - def list_assets(), do: impl().list_assets() + def list_assets, do: impl().list_assets() defp impl, do: Application.get_env(:basket, :alpaca_http_client, Basket.Http.Alpaca.Impl) diff --git a/lib/basket/http/alpaca/impl.ex b/lib/basket/http/alpaca/impl.ex index db7923e..41a3ef7 100644 --- a/lib/basket/http/alpaca/impl.ex +++ b/lib/basket/http/alpaca/impl.ex @@ -1,4 +1,7 @@ defmodule Basket.Http.Alpaca.Impl do + @moduledoc """ + Implmentation of the Alpaca API HTTP client. + """ use HTTPoison.Base @behaviour Basket.Http.Alpaca @@ -8,7 +11,9 @@ defmodule Basket.Http.Alpaca.Impl do @impl Basket.Http.Alpaca def latest_quote(ticker) do - case get("#{data_url()}#{@latest_quotes_resource}", [], + case get( + "#{data_url()}#{@latest_quotes_resource}", + [], params: %{feed: "iex", symbols: ticker} ) do {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> @@ -21,7 +26,9 @@ defmodule Basket.Http.Alpaca.Impl do @impl Basket.Http.Alpaca def list_assets do - case get("#{market_url()}#{@assets_resource}", [], + case get( + "#{market_url()}#{@assets_resource}", + [], params: %{status: "active", asset_class: "us_equity"} ) do {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> diff --git a/lib/basket_web/components/search_input.ex b/lib/basket_web/components/search_input.ex index ac37a83..bb38dd2 100644 --- a/lib/basket_web/components/search_input.ex +++ b/lib/basket_web/components/search_input.ex @@ -42,8 +42,8 @@ defmodule BasketWeb.Components.SearchInput do {/for} <:actions> - <.button class="whitespace-nowrap"> - Add + <.button class="bg-green-600 whitespace-nowrap w-12"> + + diff --git a/lib/basket_web/components/ticker_bar_table.ex b/lib/basket_web/components/ticker_bar_table.ex new file mode 100644 index 0000000..6607180 --- /dev/null +++ b/lib/basket_web/components/ticker_bar_table.ex @@ -0,0 +1,48 @@ +defmodule BasketWeb.Components.TickerBarTable do + @moduledoc """ + Allows the user to search for and add a ticker to the table. Will make an HTTP call + if the ticker list is not populated, otherwise it will pull the list from the cache. + """ + + use Surface.LiveComponent + + import BasketWeb.CoreComponents + + prop rows, :list, default: [] + + attr :id, :string, required: true + attr :class, :string, default: nil + + def mount(socket) do + socket = assign(socket, basket: []) + + {:ok, socket} + end + + def render(assigns) do + ~F""" +
+ <.table id="ticker-list" rows={@rows}> + <:col :let={ticker} key="S" label="ticker">{value_from_ticker_bar(ticker["S"])} + <:col :let={ticker} key="o" label="open">{value_from_ticker_bar(ticker["o"])} + <:col :let={ticker} key="h" label="high">{value_from_ticker_bar(ticker["h"])} + <:col :let={ticker} key="l" label="low">{value_from_ticker_bar(ticker["l"])} + <:col :let={ticker} key="c" label="close">{value_from_ticker_bar(ticker["c"])} + <:col :let={ticker} key="v" label="volume">{value_from_ticker_bar(ticker["v"])} + <:col :let={ticker} key="t" label="timestamp">{value_from_ticker_bar(ticker["t"])} + <:col :let={ticker} label="remove"> + <.button + phx-click="ticker-remove" + phx-value-ticker={value_from_ticker_bar(ticker["S"])} + class="bg-red-600" + > + X + + " + +
+ """ + end + + defp value_from_ticker_bar(ticker_bar), do: elem(ticker_bar, 0) +end diff --git a/lib/basket_web/live/overview.ex b/lib/basket_web/live/overview.ex index 16b1d93..6879b86 100644 --- a/lib/basket_web/live/overview.ex +++ b/lib/basket_web/live/overview.ex @@ -4,12 +4,10 @@ defmodule BasketWeb.Overview do """ use Surface.LiveView - import BasketWeb.CoreComponents - require Logger alias Basket.{Http, Websocket} - alias BasketWeb.Components.{NavRow, SearchInput} + alias BasketWeb.Components.{NavRow, SearchInput, TickerBarTable} prop tickers, :list, default: [] @@ -117,20 +115,7 @@ defmodule BasketWeb.Overview do
<.live_component module={SearchInput} id="stock-search-input" tickers={@tickers} />
- <.table id="ticker-list" rows={@basket}> - <:col :let={ticker} key="S" label="ticker">{elem(ticker["S"], 0)} - <:col :let={ticker} key="o" label="open">{elem(ticker["o"], 0)} - <:col :let={ticker} key="h" label="high">{elem(ticker["h"], 0)} - <:col :let={ticker} key="l" label="low">{elem(ticker["l"], 0)} - <:col :let={ticker} key="c" label="close">{elem(ticker["c"], 0)} - <:col :let={ticker} key="v" label="volume">{elem(ticker["v"], 0)} - <:col :let={ticker} key="t" label="timestamp">{elem(ticker["t"], 0)} - <:col :let={ticker} label="remove"> - <.button phx-click="ticker-remove" phx-value-ticker={elem(ticker["S"], 0)}> - Remove - - " - + """ end diff --git a/test/basket_web/live/overview_test.exs b/test/basket_web/live/overview_test.exs index f710c97..c910f2b 100644 --- a/test/basket_web/live/overview_test.exs +++ b/test/basket_web/live/overview_test.exs @@ -25,7 +25,7 @@ defmodule BasketWeb.OverviewTest do "n" => 357, "o" => 187.11, "t" => "2023-11-15T20:59:00Z", - "v" => 43025, + "v" => 43_025, "vw" => 187.117416 } }, @@ -38,7 +38,7 @@ defmodule BasketWeb.OverviewTest do "n" => {358, ""}, "o" => {188.11, ""}, "t" => {"2023-11-15T20:59:00Z", ""}, - "v" => {43031, ""}, + "v" => {43_031, ""}, "vw" => {188.117416, ""} } ] @@ -260,7 +260,7 @@ defmodule BasketWeb.OverviewTest do "n" => {359, ""}, "o" => {189.11, "down"}, "t" => {"2023-11-15T21:59:00Z", ""}, - "v" => {43032, "down"}, + "v" => {43_032, "down"}, "vw" => {189.117416, "down"} } ] @@ -278,7 +278,7 @@ defmodule BasketWeb.OverviewTest do "n" => 359, "o" => 189.11, "t" => "2023-11-15T21:59:00Z", - "v" => 43032, + "v" => 43_032, "vw" => 189.117416 } },