diff --git a/lib/sanbase/clickhouse/founders/founders.ex b/lib/sanbase/clickhouse/founders/founders.ex index c54bded73..63481b7c5 100644 --- a/lib/sanbase/clickhouse/founders/founders.ex +++ b/lib/sanbase/clickhouse/founders/founders.ex @@ -1,13 +1,13 @@ defmodule Sanbase.Clickhouse.Founders do - def get_founders() do - query = get_founders_query() + def get_founders(slug \\ nil) do + query = get_founders_query(slug) - Sanbase.ClickhouseRepo.query_transform(query, fn [name, slug] -> - %{name: name, slug: slug} + Sanbase.ClickhouseRepo.query_transform(query, fn [name, project_slug] -> + %{name: name, slug: project_slug} end) end - defp get_founders_query() do + defp get_founders_query(nil) do sql = """ SELECT name, slug FROM founder_metadata @@ -15,4 +15,14 @@ defmodule Sanbase.Clickhouse.Founders do Sanbase.Clickhouse.Query.new(sql, %{}) end + + defp get_founders_query(slug) do + sql = """ + SELECT name, slug + FROM founder_metadata + WHERE slug = {{slug}} + """ + + Sanbase.Clickhouse.Query.new(sql, %{slug: slug}) + end end diff --git a/lib/sanbase/social_data/metric_adapter.ex b/lib/sanbase/social_data/metric_adapter.ex index f8a5fbb82..5452a5ee6 100644 --- a/lib/sanbase/social_data/metric_adapter.ex +++ b/lib/sanbase/social_data/metric_adapter.ex @@ -162,47 +162,28 @@ defmodule Sanbase.SocialData.MetricAdapter do end @impl Sanbase.Metric.Behaviour - def aggregated_timeseries_data(metric, selector, from, to, opts) - when metric in @social_volume_timeseries_metrics or - metric in @community_messages_count_timeseries_metrics do + def aggregated_timeseries_data(metric, selector, from, to, opts) do slug = Map.get(selector, :slug) case is_nil(slug) or is_binary(slug) do - true -> - case timeseries_data(metric, selector, from, to, "1h", opts) do - {:ok, result} -> - value = Enum.reduce(result, 0, &(&1.value + &2)) - {:ok, %{value: value}} - - {:error, error} -> - {:error, error} - end - false -> {:error, "Aggregated timeseries data is not supported for lists of slugs."} - end - end - def aggregated_timeseries_data(metric, selector, from, to, opts) - when metric in @social_dominance_timeseries_metrics do - slug = Map.get(selector, :slug) - - case is_nil(slug) or is_binary(slug) do true -> case timeseries_data(metric, selector, from, to, "1h", opts) do {:ok, result} -> + {:ok, metadata} = metadata(metric) + aggregation = Keyword.get(opts, :aggregation) || metadata.default_aggregation + value = - Enum.map(result, & &1.value) - |> Sanbase.Math.mean() + Sanbase.MathAggregation.compute(result, aggregation, & &1.value) + |> Sanbase.Math.round_float() {:ok, %{value: value}} {:error, error} -> {:error, error} end - - false -> - {:error, "Aggregated timeseries data is not supported for lists of slugs."} end end diff --git a/lib/sanbase/utils/math_aggregation.ex b/lib/sanbase/utils/math_aggregation.ex new file mode 100644 index 000000000..e809fa036 --- /dev/null +++ b/lib/sanbase/utils/math_aggregation.ex @@ -0,0 +1,20 @@ +defmodule Sanbase.MathAggregation do + def compute(list, aggregation, fun \\ & &1) + def compute(list, :max, fun), do: Enum.map(list, fun) |> Enum.max() + def compute(list, :min, fun), do: Enum.map(list, fun) |> Enum.min() + def compute(list, :avg, fun), do: Enum.map(list, fun) |> Sanbase.Math.mean() + def compute(list, :median, fun), do: Enum.map(list, fun) |> Sanbase.Math.median() + def compute(list, :count, _fun), do: Enum.count(list) + def compute(list, :sum, fun), do: Enum.map(list, fun) |> Enum.sum() + def compute(list, :first, fun), do: Enum.map(list, fun) |> List.first() + def compute(list, :last, fun), do: Enum.map(list, fun) |> List.last() + + def compute(list, :ohlc, fun) do + %{ + open: compute(list, :first, fun), + high: compute(list, :max, fun), + low: compute(list, :min, fun), + close: compute(list, :last, fun) + } + end +end diff --git a/lib/sanbase_web/graphql/resolvers/metric/metric_resolver.ex b/lib/sanbase_web/graphql/resolvers/metric/metric_resolver.ex index 0cfe9c68f..8adbf4fe9 100644 --- a/lib/sanbase_web/graphql/resolvers/metric/metric_resolver.ex +++ b/lib/sanbase_web/graphql/resolvers/metric/metric_resolver.ex @@ -107,11 +107,13 @@ defmodule SanbaseWeb.Graphql.Resolvers.MetricResolver do end end - def get_available_founders(_root, _args, %{source: %{metric: metric}}) do + def get_available_founders(_root, args, %{source: %{metric: metric}}) do with {:ok, selectors} <- Metric.available_selectors(metric) do case :founders in selectors do true -> - with {:ok, data} <- Sanbase.Clickhouse.Founders.get_founders() do + slug = Map.get(args, :slug, nil) + + with {:ok, data} <- Sanbase.Clickhouse.Founders.get_founders(slug) do slugs = Enum.map(data, & &1.slug) projects = Sanbase.Project.List.by_slugs(slugs) slug_to_project_map = Map.new(projects, &{&1.slug, &1}) diff --git a/lib/sanbase_web/graphql/schema/types/metric_types.ex b/lib/sanbase_web/graphql/schema/types/metric_types.ex index 13f8650a4..4662af129 100644 --- a/lib/sanbase_web/graphql/schema/types/metric_types.ex +++ b/lib/sanbase_web/graphql/schema/types/metric_types.ex @@ -417,6 +417,10 @@ defmodule SanbaseWeb.Graphql.MetricTypes do end field :available_founders, list_of(:founder) do + @desc ~s""" + Filter the founders for which slug should be returned + """ + arg(:slug, :string, default_value: nil) cache_resolve(&MetricResolver.get_available_founders/3) end 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 a7c67241c..812a9de06 100644 --- a/test/sanbase_web/graphql/metric/api_metric_metadata_test.exs +++ b/test/sanbase_web/graphql/metric/api_metric_metadata_test.exs @@ -44,8 +44,9 @@ defmodule SanbaseWeb.Graphql.ApiMetricMetadataTest do |> json_response(200) |> get_in(["data", "getMetric", "metadata", "availableFounders"]) - assert %{"name" => "Vitalik Buterin", "project" => %{"name" => "Ethereum"}} in result + assert length(result) == 2 assert %{"name" => "Satoshi Nakamoto", "project" => %{"name" => "Bitcoin"}} in result + assert %{"name" => "Vitalik Buterin", "project" => %{"name" => "Ethereum"}} in result end end) @@ -59,6 +60,47 @@ defmodule SanbaseWeb.Graphql.ApiMetricMetadataTest do assert result == [] end + test "returns data for availableFounders with slug filter", %{conn: conn} do + metric_with_founders = + Metric.available_metrics() + |> Enum.filter(fn m -> + {:ok, selectors} = Metric.available_selectors(m) + + :founders in selectors + end) + |> hd() + + insert(:project, %{name: "Ethereum", ticker: "ETH", slug: "ethereum"}) + insert(:project, %{name: "Bitcoin", ticker: "BTC", slug: "bitcoin"}) + + rows = [ + ["Satoshi Nakamoto", "bitcoin"] + ] + + query = + """ + { + getMetric(metric: "#{metric_with_founders}"){ + metadata{ + availableFounders(slug: "bitcoin"){ name project{ name } } + } + } + } + """ + + Sanbase.Mock.prepare_mock2(&Sanbase.ClickhouseRepo.query/2, {:ok, %{rows: rows}}) + |> Sanbase.Mock.run_with_mocks(fn -> + result = + conn + |> post("/graphql", query_skeleton(query)) + |> json_response(200) + |> get_in(["data", "getMetric", "metadata", "availableFounders"]) + + assert length(result) == 1 + assert %{"name" => "Satoshi Nakamoto", "project" => %{"name" => "Bitcoin"}} in result + end) + end + test "returns data for all available metric", %{conn: conn} do metrics = Metric.available_metrics() |> Enum.shuffle()