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

Add availableFounders #4499

Merged
merged 2 commits into from
Dec 13, 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/available_metrics/available_metrics.ex
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ defmodule Sanbase.AvailableMetrics do
metrics
|> Enum.map(fn metric ->
{:ok, m} = Sanbase.Metric.metadata(metric)
{:ok, available_selectors} = Sanbase.Metric.available_selectors(metric)

%{
metric: m.metric,
Expand All @@ -79,7 +80,7 @@ defmodule Sanbase.AvailableMetrics do
frequency_seconds: Sanbase.DateTimeUtils.str_to_sec(m.min_interval),
sanbase_access: "free",
sanapi_access: "free",
available_selectors: m.available_selectors,
available_selectors: available_selectors,
required_selectors: m.required_selectors,
access: Map.get(access_map, metric)
}
Expand Down
18 changes: 18 additions & 0 deletions lib/sanbase/clickhouse/founders/founders.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
defmodule Sanbase.Clickhouse.Founders do
def get_founders() do
query = get_founders_query()

Sanbase.ClickhouseRepo.query_transform(query, fn [name, slug] ->
%{name: name, slug: slug}
end)
end

defp get_founders_query() do
sql = """
SELECT name, slug
FROM founder_metadata
"""

Sanbase.Clickhouse.Query.new(sql, %{})
end
end
2 changes: 2 additions & 0 deletions lib/sanbase/metric/behaviour.ex
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ defmodule Sanbase.Metric.Behaviour do

@type available_label_fqns_result :: {:ok, list(String.t())} | {:error, String.t()}

@type available_selectors_result :: {:ok, list(atom())} | {:error, String.t()}

@type has_incomplete_data_result :: boolean()

@type complexity_weight_result :: number()
Expand Down
32 changes: 31 additions & 1 deletion lib/sanbase/metric/metric.ex
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,33 @@ defmodule Sanbase.Metric do
end
end

@doc ~s"""
Get the list of available selectors for a metric.
This is a separate function as it combines the available selectors from multiple
modules that implement the metric.
Most notably, social metrics from CH are precomptued only for slugs, while the
SocialData.MetricAdapter implements it for text, founders, etc.
"""
@spec available_selectors(metric) :: Type.available_selectors_result()
def available_selectors(metric) do
case get_module(metric, return_all_modules: true) do
nil ->
metric_not_available_error(metric)

module_or_modules ->
available_selectors =
module_or_modules
|> List.wrap()
|> Enum.flat_map(fn module ->
{:ok, metadata} = apply(module, :metadata, [metric])
metadata.available_selectors
end)
|> Enum.uniq()

{:ok, available_selectors}
end
end

@doc ~s"""
Get the first datetime for which a given metric is available for a given slug
"""
Expand Down Expand Up @@ -851,7 +878,10 @@ defmodule Sanbase.Metric do
end

true ->
metric_to_single_module(metric, opts)
case Keyword.get(opts, :return_all_modules, false) do
false -> metric_to_single_module(metric, opts)
true -> Map.get(Helper.metric_to_modules_map(), metric)
end
end
end

Expand Down
33 changes: 33 additions & 0 deletions lib/sanbase_web/graphql/resolvers/metric/metric_resolver.ex
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,29 @@ defmodule SanbaseWeb.Graphql.Resolvers.MetricResolver do
end
end

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
slugs = Enum.map(data, & &1.slug)
projects = Sanbase.Project.List.by_slugs(slugs)
slug_to_project_map = Map.new(projects, &{&1.slug, &1})

result =
Enum.map(data, fn map ->
Map.put(map, :project, slug_to_project_map[map.slug])
end)

{:ok, result}
end

false ->
{:ok, []}
end
end
end

def get_human_readable_name(_root, _args, %{source: %{metric: metric}}),
do: Metric.human_readable_name(metric)

Expand All @@ -131,6 +154,16 @@ defmodule SanbaseWeb.Graphql.Resolvers.MetricResolver do
end
end

def get_available_selectors(_root, _args, %{source: %{metric: metric}}) do
case Metric.available_selectors(metric) do
{:ok, selectors} ->
{:ok, selectors}

{:error, error} ->
{:error, handle_graphql_error("available_selectors", %{metric: metric}, error)}
end
end

def timeseries_data_complexity(_root, args, resolution) do
# Explicitly set `child_complexity` to 2 as this would be the
# value if both `datetime` and `value` fields are queried.
Expand Down
13 changes: 12 additions & 1 deletion lib/sanbase_web/graphql/schema/types/metric_types.ex
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ defmodule SanbaseWeb.Graphql.MetricTypes do
value(:table)
end

object :founder do
field(:name, non_null(:string))
field(:project, :project)
end

object :broken_data do
field(:from, non_null(:datetime))
field(:to, non_null(:datetime))
Expand Down Expand Up @@ -407,7 +412,13 @@ defmodule SanbaseWeb.Graphql.MetricTypes do
Every metric has `availableSelectors` in its metadata, showing exactly
which of the selectors can be used.
"""
field(:available_selectors, list_of(:selector_name))
field :available_selectors, list_of(:selector_name) do
resolve(&MetricResolver.get_available_selectors/3)
end

field :available_founders, list_of(:founder) do
cache_resolve(&MetricResolver.get_available_founders/3)
end

@desc ~s"""
The list of required selectors for the metric. It is used to show the list
Expand Down
55 changes: 54 additions & 1 deletion test/sanbase_web/graphql/metric/api_metric_metadata_test.exs
Original file line number Diff line number Diff line change
@@ -1,11 +1,64 @@
defmodule SanbaseWeb.Graphql.ApiMetricMetadataTest do
use SanbaseWeb.ConnCase, async: false

import Sanbase.Factory, only: [rand_str: 0]
import Sanbase.Factory
import SanbaseWeb.Graphql.TestHelpers

alias Sanbase.Metric

test "returns data for availableFounders", %{conn: conn} do
metrics_with_founders =
Metric.available_metrics()
|> Enum.filter(fn m ->
{:ok, selectors} = Metric.available_selectors(m)

:founders in selectors
end)

insert(:project, %{name: "Ethereum", ticker: "ETH", slug: "ethereum"})
insert(:project, %{name: "Bitcoin", ticker: "BTC", slug: "bitcoin"})

rows = [
["Vitalik Buterin", "ethereum"],
["Satoshi Nakamoto", "bitcoin"]
]

query = fn metric ->
"""
{
getMetric(metric: "#{metric}"){
metadata{
availableFounders{ name project{ name } }
}
}
}
"""
end

Sanbase.Mock.prepare_mock2(&Sanbase.ClickhouseRepo.query/2, {:ok, %{rows: rows}})
|> Sanbase.Mock.run_with_mocks(fn ->
for metric <- metrics_with_founders do
result =
conn
|> post("/graphql", query_skeleton(query.(metric)))
|> json_response(200)
|> get_in(["data", "getMetric", "metadata", "availableFounders"])

assert %{"name" => "Vitalik Buterin", "project" => %{"name" => "Ethereum"}} in result
assert %{"name" => "Satoshi Nakamoto", "project" => %{"name" => "Bitcoin"}} in result
end
end)

result =
conn
|> post("/graphql", query_skeleton(query.("price_usd")))
|> json_response(200)
|> get_in(["data", "getMetric", "metadata", "availableFounders"])

# No founders for metrics without founders in their selectors
assert result == []
end

test "returns data for all available metric", %{conn: conn} do
metrics = Metric.available_metrics() |> Enum.shuffle()

Expand Down
Loading