diff --git a/lib/sanbase/clickhouse/metric/metric_adapter.ex b/lib/sanbase/clickhouse/metric/metric_adapter.ex index 5bceb5573b..6f99b2f695 100644 --- a/lib/sanbase/clickhouse/metric/metric_adapter.ex +++ b/lib/sanbase/clickhouse/metric/metric_adapter.ex @@ -26,7 +26,8 @@ defmodule Sanbase.Clickhouse.MetricAdapter do defguard is_supported_selector(s) when is_map(s) and - (is_map_key(s, :slug) or is_map_key(s, :address) or + (is_map_key(s, :slug) or + is_map_key(s, :address) or is_map_key(s, :contract_address)) @impl Sanbase.Metric.Behaviour diff --git a/lib/sanbase/metric/metric.ex b/lib/sanbase/metric/metric.ex index 4f16968de4..fbb55b1fa4 100644 --- a/lib/sanbase/metric/metric.ex +++ b/lib/sanbase/metric/metric.ex @@ -26,7 +26,8 @@ defmodule Sanbase.Metric do @compile inline: [ execute_if_aggregation_valid: 3, - maybe_change_module: 4, + get_module: 1, + get_module: 2, combine_metrics_in_modules: 2 ] @@ -475,8 +476,6 @@ defmodule Sanbase.Metric do metric_not_available_error(metric, type: :timeseries) module when is_atom(module) -> - module = maybe_change_module(module, metric, %{}, opts) - case module.available_slugs(metric) do {:ok, slugs} -> supported_slugs = Sanbase.Project.List.projects_slugs() @@ -828,39 +827,13 @@ defmodule Sanbase.Metric do end end - defp maybe_change_module(module, metric, selector, opts) do - cond do - metric in Sanbase.SocialData.MetricAdapter.available_metrics() and - match?(%{text: _}, selector) -> - # When using slug, the social metrics are fetched from clickhouse - # But when text selector is used, the metric should be fetched from Elasticsearch - # as it cannot be precomputed due to the vast number of possible text arguments - Sanbase.SocialData.MetricAdapter - - metric in Sanbase.SocialData.MetricAdapter.available_metrics() and - match?(%{contract_address: _}, selector) -> - Sanbase.SocialData.MetricAdapter - - metric in Sanbase.PricePair.MetricAdapter.available_metrics() -> - source = Keyword.get(opts, :source) || Map.get(selector, :source) - - cond do - source == "cryptocompare" -> Sanbase.PricePair.MetricAdapter - metric == "price_eth" and source != "cryptocompare" -> Sanbase.Clickhouse.MetricAdapter - true -> module - end - - true -> - module - end - end - defp get_module(metric, opts \\ []) do selector = Keyword.get(opts, :selector, %{}) cond do metric in Sanbase.SocialData.MetricAdapter.available_metrics() and - (match?(%{text: _}, selector) or match?(%{contract_address: _}, selector)) -> + (match?(%{text: _}, selector) or match?(%{contract_address: _}, selector) or + match?(%{founders: _}, selector)) -> Sanbase.SocialData.MetricAdapter metric in Sanbase.Clickhouse.Github.MetricAdapter.available_metrics() and diff --git a/lib/sanbase/social_data/metric_adapter.ex b/lib/sanbase/social_data/metric_adapter.ex index 226ba99de5..f8a5fbb823 100644 --- a/lib/sanbase/social_data/metric_adapter.ex +++ b/lib/sanbase/social_data/metric_adapter.ex @@ -314,7 +314,7 @@ defmodule Sanbase.SocialData.MetricAdapter do "community_messages_count" <> _ -> [:slug] "social_active_users" -> [:source] "nft_social_volume" -> [:contract_address] - _ -> [:slug, :text] + _ -> [:slug, :text, :founders] end min_interval = diff --git a/lib/sanbase/social_data/sentiment.ex b/lib/sanbase/social_data/sentiment.ex index e7f39d5292..805b3ace58 100644 --- a/lib/sanbase/social_data/sentiment.ex +++ b/lib/sanbase/social_data/sentiment.ex @@ -36,13 +36,14 @@ defmodule Sanbase.SocialData.Sentiment do end defp sentiment_request(selector, from, to, interval, source, type) do - with {:ok, search_text} <- SocialHelper.social_metrics_selector_handler(selector) do + with {:ok, selector_name, selector_value} <- + SocialHelper.social_metrics_selector_handler(selector) do url = "#{metrics_hub_url()}/sentiment_#{type}" options = [ recv_timeout: @recv_timeout, params: [ - {"search_text", search_text}, + {selector_name, selector_value}, {"from_timestamp", from |> DateTime.truncate(:second) |> DateTime.to_iso8601()}, {"to_timestamp", to |> DateTime.truncate(:second) |> DateTime.to_iso8601()}, {"interval", interval}, diff --git a/lib/sanbase/social_data/social_helper.ex b/lib/sanbase/social_data/social_helper.ex index 45741289fc..d5e2f61a1b 100644 --- a/lib/sanbase/social_data/social_helper.ex +++ b/lib/sanbase/social_data/social_helper.ex @@ -34,10 +34,10 @@ defmodule Sanbase.SocialData.SocialHelper do |> case do %Project{social_volume_query: %{query: query, autogenerated_query: autogen_query}} when not is_nil(query) or not is_nil(autogen_query) -> - {:ok, query || autogen_query} + {:ok, "search_text", query || autogen_query} %Project{} = project -> - {:ok, SocialVolumeQuery.default_query(project)} + {:ok, "search_text", SocialVolumeQuery.default_query(project)} _ -> {:error, "Invalid slug"} @@ -45,7 +45,11 @@ defmodule Sanbase.SocialData.SocialHelper do end def social_metrics_selector_handler(%{text: search_text}) do - {:ok, search_text} + {:ok, "search_text", search_text} + end + + def social_metrics_selector_handler(%{founders: founders}) do + {:ok, "founders", Enum.join(founders, ",")} end def social_metrics_selector_handler(_args) do diff --git a/lib/sanbase/social_data/social_volume.ex b/lib/sanbase/social_data/social_volume.ex index 6f723f1bcb..bfe335a1f9 100644 --- a/lib/sanbase/social_data/social_volume.ex +++ b/lib/sanbase/social_data/social_volume.ex @@ -175,14 +175,15 @@ defmodule Sanbase.SocialData.SocialVolume do end defp social_volume_request(selector, from, to, interval, source, opts) do - with {:ok, search_text} <- SocialHelper.social_metrics_selector_handler(selector) do + with {:ok, selector_name, selector_value} when selector_name in ["search_text", "founders"] <- + SocialHelper.social_metrics_selector_handler(selector) do url = Path.join([metrics_hub_url(), opts_to_metric(opts)]) options = [ recv_timeout: @recv_timeout, params: [ - {"search_text", search_text}, + {selector_name, selector_value}, {"from_timestamp", from |> DateTime.truncate(:second) |> DateTime.to_iso8601()}, {"to_timestamp", to |> DateTime.truncate(:second) |> DateTime.to_iso8601()}, {"interval", interval}, diff --git a/lib/sanbase_web/graphql/schema/types/metric_types.ex b/lib/sanbase_web/graphql/schema/types/metric_types.ex index a1cf3c65b2..f4905215cb 100644 --- a/lib/sanbase_web/graphql/schema/types/metric_types.ex +++ b/lib/sanbase_web/graphql/schema/types/metric_types.ex @@ -43,6 +43,7 @@ defmodule SanbaseWeb.Graphql.MetricTypes do value(:watchlist_id) # social related value(:text) + value(:founders) value(:source) # label related value(:owner) @@ -86,6 +87,7 @@ defmodule SanbaseWeb.Graphql.MetricTypes do # social related field(:text, :string) field(:source, :string) + field(:founders, list_of(:string)) # dev activity related field(:organization, :string) field(:organizations, list_of(:string)) diff --git a/test/sanbase/social_data/social_helper_test.exs b/test/sanbase/social_data/social_helper_test.exs index a7bc4600ea..5f44e3bf4f 100644 --- a/test/sanbase/social_data/social_helper_test.exs +++ b/test/sanbase/social_data/social_helper_test.exs @@ -14,12 +14,17 @@ defmodule Sanbase.SocialHelperTest do Sanbase.Factory.insert(:project, %{ticker: "SAN", name: "Santiment", slug: "santiment"}) assert SocialHelper.social_metrics_selector_handler(%{slug: "santiment"}) == - {:ok, ~s/"san" OR "santiment"/} + {:ok, "search_text", ~s/"san" OR "santiment"/} end test "with text: success" do assert SocialHelper.social_metrics_selector_handler(%{text: "santiment"}) == - {:ok, "santiment"} + {:ok, "search_text", "santiment"} + end + + test "with founders: success" do + assert SocialHelper.social_metrics_selector_handler(%{founders: ["vitalik", "satoshi"]}) == + {:ok, "founders", "vitalik,satoshi"} end test "with false argument" do diff --git a/test/sanbase_web/graphql/metric/api_metric_metadata_test.exs b/test/sanbase_web/graphql/metric/api_metric_metadata_test.exs index d78d3168ef..111e5ed949 100644 --- a/test/sanbase_web/graphql/metric/api_metric_metadata_test.exs +++ b/test/sanbase_web/graphql/metric/api_metric_metadata_test.exs @@ -56,6 +56,7 @@ defmodule SanbaseWeb.Graphql.ApiMetricMetadataTest do "BLOCKCHAIN", "CONTRACT_ADDRESS", "ECOSYSTEM", + "FOUNDERS", "HOLDERS_COUNT", "LABEL_FQN", "LABEL_FQNS", diff --git a/test/sanbase_web/graphql/social_data/founders_socia_data_api_test.exs b/test/sanbase_web/graphql/social_data/founders_socia_data_api_test.exs new file mode 100644 index 0000000000..ae9cff9d93 --- /dev/null +++ b/test/sanbase_web/graphql/social_data/founders_socia_data_api_test.exs @@ -0,0 +1,76 @@ +defmodule SanbaseWeb.Graphql.FoundersSocialDataTest do + use SanbaseWeb.ConnCase, async: false + + import Sanbase.Factory + import SanbaseWeb.Graphql.TestHelpers + + setup do + %{user: user} = insert(:subscription_pro_sanbase, user: insert(:user)) + conn = setup_jwt_auth(build_conn(), user) + + %{conn: conn} + end + + test "successfully fetch social volume and sentiment for founders", context do + # This test does not include treatWordAsLuceneQuery: true, so the + # words are lowercased before being send and in the response + body = + %{ + "data" => %{ + "2024-09-28T00:00:00Z" => 373, + "2024-09-29T00:00:00Z" => 487, + "2024-09-30T00:00:00Z" => 323 + } + } + |> Jason.encode!() + + resp = %HTTPoison.Response{status_code: 200, body: body} + + Sanbase.Mock.prepare_mock(HTTPoison, :get, fn _url, _headers, options -> + search_texts = + Map.new(options[:params]) + |> Map.get("founders") + + # Assert that the words are lowercased before they are sent + assert search_texts == "vitalik,satoshi" + + {:ok, resp} + end) + |> Sanbase.Mock.run_with_mocks(fn -> + for metric <- ["social_volume_total", "sentiment_positive_total"] do + query = """ + { + getMetric(metric: "#{metric}"){ + timeseriesData( + selector: { founders: ["vitalik", "satoshi"] } + from: "2024-09-28T00:00:00Z" + to: "2024-09-30T00:00:00Z" + interval: "1d" + ){ + datetime + value + } + } + } + """ + + result = + context.conn + |> post("/graphql", query_skeleton(query)) + |> json_response(200) + + assert result == %{ + "data" => %{ + "getMetric" => %{ + "timeseriesData" => [ + %{"datetime" => "2024-09-28T00:00:00Z", "value" => 373.0}, + %{"datetime" => "2024-09-29T00:00:00Z", "value" => 487.0}, + %{"datetime" => "2024-09-30T00:00:00Z", "value" => 323.0} + ] + } + } + } + end + end) + end +end