Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

founders selector in social metrics #4487

Merged
merged 3 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion lib/sanbase/clickhouse/metric/metric_adapter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 4 additions & 31 deletions lib/sanbase/metric/metric.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
]

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/sanbase/social_data/metric_adapter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
5 changes: 3 additions & 2 deletions lib/sanbase/social_data/sentiment.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down
10 changes: 7 additions & 3 deletions lib/sanbase/social_data/social_helper.ex
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,22 @@ 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"}
end
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
Expand Down
5 changes: 3 additions & 2 deletions lib/sanbase/social_data/social_volume.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down
2 changes: 2 additions & 0 deletions lib/sanbase_web/graphql/schema/types/metric_types.ex
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ defmodule SanbaseWeb.Graphql.MetricTypes do
value(:watchlist_id)
# social related
value(:text)
value(:founders)
value(:source)
# label related
value(:owner)
Expand Down Expand Up @@ -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))
Expand Down
9 changes: 7 additions & 2 deletions test/sanbase/social_data/social_helper_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ defmodule SanbaseWeb.Graphql.ApiMetricMetadataTest do
"BLOCKCHAIN",
"CONTRACT_ADDRESS",
"ECOSYSTEM",
"FOUNDERS",
"HOLDERS_COUNT",
"LABEL_FQN",
"LABEL_FQNS",
Expand Down
Original file line number Diff line number Diff line change
@@ -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