Skip to content
Draft
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
21 changes: 7 additions & 14 deletions lib/sanbase_web/graphql/absinthe_before_send.ex
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ defmodule SanbaseWeb.Graphql.AbsintheBeforeSend do
# do_not_cache_query flag in the process dictionary
case do_not_cache? or error_queries != [] do
true -> :ok
# The pre_override_queries are the getMetric and getSignal query names
# before they got renamed to getMetric|<metric> and getSignal|<signal>
# The pre_override_queries are the getMetric query names
# before they got renamed to getMetric|<metric>
false -> maybe_cache_result(query_metadata.queries, blueprint)
end
end
Expand Down Expand Up @@ -312,9 +312,6 @@ defmodule SanbaseWeb.Graphql.AbsintheBeforeSend do
defp get_query_and_selector({:get_metric, _alias, metric, selector, version}),
do: {"getMetric|#{metric}", selector, version}

defp get_query_and_selector({:get_signal, _alias, signal, selector}),
do: {"getSignal|#{signal}", selector, nil}

defp get_query_and_selector(query), do: {query, nil, nil}

defp remote_ip(blueprint) do
Expand Down Expand Up @@ -408,31 +405,27 @@ defmodule SanbaseWeb.Graphql.AbsintheBeforeSend do
%Absinthe.Blueprint{} = blueprint
)
when is_map(alias_to_query_name_map) do
# The TransformResolution middleware is used in getMetric and getSignal APIs
# to enrich them so we can export also the name of the metric/signal that has been queried.
# The TransformResolution middleware is used in getMetric API
# to enrich it so we can export also the name of the metric that has been queried.
# This allows us to better see what exactly has been called -- instead of just seeing
# `getMetric`, we enrich it with some of the arguments that have been passed to it -- meric name and selector.
# `getMetric`, we enrich it with some of the arguments that have been passed to it -- metric name and selector.
# Additionally, this middleware also records the GraphQL alias provided by the user,
# which is later used to resolve aliases to query names.

# Get a map where the values are one of:
# - {:get_metric, alias, metric, selector, version} |
# - {:get_signal, alias, signal, selector} |
# These will replace the `alias: getMetric` seen in the queries list in order
# to enrich them with the metric/signal that has been queried by the user
# to enrich them with the metric that has been queried by the user
#
# and the keys are the alias itself.
alias_to_get_query_tuple_map =
Map.get(blueprint.execution.context, :__get_query_name_arg__, [])
|> Map.new(fn
{:get_metric, alias, _metric, _selector, _version} = tuple ->
{Sanbase.Utils.Inflect.camelize(alias, :lower), tuple}

{:get_signal, alias, _signal, _selector} = tuple ->
{Sanbase.Utils.Inflect.camelize(alias, :lower), tuple}
end)

# Rename aliases to the query name itself, or in case of getMetric and getSignal -- the whole tuple.
# Rename aliases to the query name itself, or in case of getMetric -- the whole tuple.
# The tuple is used in export_api_call_data/1 to construct the query name (like getMetric|price_usd) and
# the selector that has been provided (like {"slugs": ["bitcoin", "ethereum"]})
rename_mapper = fn list ->
Expand Down
13 changes: 1 addition & 12 deletions lib/sanbase_web/graphql/middlewares/transform_resolution.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule SanbaseWeb.Graphql.Middlewares.TransformResolution do
@moduledoc """
Update the :__get_query_name_arg__ in the context in case the query is
get_metric, get_signal or get_anomly.
get_metric.
"""

@behaviour Absinthe.Middleware
Expand All @@ -28,17 +28,6 @@ defmodule SanbaseWeb.Graphql.Middlewares.TransformResolution do
%{resolution | context: Map.update(context, :__get_query_name_arg__, [elem], &[elem | &1])}
end

defp do_call(:get_signal, alias, %Resolution{context: context} = resolution) do
%{arguments: %{signal: signal}} = resolution
selectors = get_selectors(resolution)
# In case of no alias, use the query name as alias. It is guaranteed that if there are two
# of the same queries in a document, at least one of them will have an alias,
# otherwise we get name collision.
elem = {:get_signal, alias || "getSignal", signal, selectors}

%{resolution | context: Map.update(context, :__get_query_name_arg__, [elem], &[elem | &1])}
end

defp do_call(_query_field, _alias, %Resolution{} = resolution) do
resolution
end
Expand Down
106 changes: 18 additions & 88 deletions lib/sanbase_web/graphql/resolvers/signal_resolver.ex
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
defmodule SanbaseWeb.Graphql.Resolvers.SignalResolver do
import Sanbase.Utils.Transform, only: [maybe_apply_function: 2]

import SanbaseWeb.Graphql.Helpers.{Utils, CalibrateInterval}
import Absinthe.Resolution.Helpers, only: [on_load: 2]
import Sanbase.Project.Selector, only: [args_to_selector: 1, args_to_raw_selector: 1]

import Sanbase.Utils.ErrorHandling,
only: [handle_graphql_error: 3, maybe_handle_graphql_error: 2]
import Sanbase.Project.Selector, only: [args_to_selector: 1]

alias Sanbase.Signal
alias SanbaseWeb.Graphql.SanbaseDataloader
alias Sanbase.Billing.Plan.Restrictions

@datapoints 300

def project(%{slug: slug}, _args, %{context: %{loader: loader}}) do
loader
|> Dataloader.load(SanbaseDataloader, :project_by_slug, slug)
Expand All @@ -22,15 +16,8 @@ defmodule SanbaseWeb.Graphql.Resolvers.SignalResolver do
end)
end

def get_signal(_root, %{signal: signal}, _resolution) do
case Signal.has_signal?(signal) do
true -> {:ok, %{signal: signal}}
{:error, error} -> {:error, error}
end
end

def get_raw_signals(_root, %{from: from, to: to} = args, resolution) do
signals = Map.get(args, :signals, :all)
def get_anomalies(_root, %{from: from, to: to} = args, resolution) do
anomalies = Map.get(args, :anomalies, available_anomalies())

selector =
case Map.has_key?(args, :selector) do
Expand All @@ -42,72 +29,9 @@ defmodule SanbaseWeb.Graphql.Resolvers.SignalResolver do
selector
end

Signal.raw_data(signals, selector, from, to)
Signal.raw_data(anomalies, selector, from, to)
|> maybe_apply_function(&overwrite_not_accessible_signals(&1, resolution))
end

def get_available_signals(_root, _args, _resolution), do: {:ok, Signal.available_signals()}

def get_available_slugs(_root, _args, %{source: %{signal: signal}}),
do: Signal.available_slugs(signal)

def get_metadata(_root, _args, resolution) do
%{source: %{signal: signal}} = resolution

case Signal.metadata(signal) do
{:ok, metadata} ->
restrictions = resolution_to_signal_restrictions(resolution)
{:ok, Map.merge(restrictions, metadata)}

{:error, error} ->
{:error, handle_graphql_error("metadata", %{signal: signal}, error)}
end
end

def available_since(_root, args, %{source: %{signal: signal}}) do
with {:ok, selector} <- args_to_selector(args),
{:ok, first_datetime} <- Signal.first_datetime(signal, selector) do
{:ok, first_datetime}
end
|> maybe_handle_graphql_error(fn error ->
handle_graphql_error(
"Available Since",
%{signal: signal, selector: args_to_raw_selector(args)},
error
)
end)
end

def timeseries_data(
_root,
%{from: from, to: to, interval: interval} = args,
%{source: %{signal: signal}}
) do
with {:ok, selector} <- args_to_selector(args),
{:ok, opts} = selector_args_to_opts(args),
{:ok, from, to, interval} <-
calibrate(Signal, signal, selector, from, to, interval, 86_400, @datapoints),
{:ok, result} <- Signal.timeseries_data(signal, selector, from, to, interval, opts) do
{:ok, result |> Enum.reject(&is_nil/1)}
else
{:error, error} ->
{:error, handle_graphql_error(signal, args_to_raw_selector(args), error)}
end
end

def aggregated_timeseries_data(
_root,
%{from: from, to: to} = args,
%{source: %{signal: signal}}
) do
with {:ok, selector} <- args_to_selector(args),
{:ok, opts} = selector_args_to_opts(args),
{:ok, result} <- Signal.aggregated_timeseries_data(signal, selector, from, to, opts) do
{:ok, Map.values(result) |> List.first()}
end
|> maybe_handle_graphql_error(fn error ->
handle_graphql_error(signal, args_to_raw_selector(args), error)
end)
|> maybe_apply_function(&rename_signal_to_anomaly/1)
end

defp overwrite_not_accessible_signals(list, resolution) do
Expand All @@ -123,6 +47,19 @@ defmodule SanbaseWeb.Graphql.Resolvers.SignalResolver do
end)
end

defp available_anomalies() do
Signal.available_signals()
|> Enum.filter(&String.starts_with?(&1, "anomaly_"))
end

defp rename_signal_to_anomaly(list) do
Enum.map(list, fn signal ->
signal
|> Map.put(:anomaly, signal.signal)
|> Map.delete(:signal)
end)
end

defp should_hide_signal?(signal_map, restrictions_map) do
case Map.get(restrictions_map, signal_map.signal) do
%{is_accessible: false} ->
Expand Down Expand Up @@ -155,13 +92,6 @@ defmodule SanbaseWeb.Graphql.Resolvers.SignalResolver do
})
end

defp resolution_to_signal_restrictions(resolution) do
%{context: %{requested_product: requested_product, auth: %{plan: plan_name}}} = resolution
%{source: %{signal: signal}} = resolution

Restrictions.get({:signal, signal}, requested_product, requested_product, plan_name)
end

defp resolution_to_all_signals_restrictions(resolution) do
%{context: %{requested_product: requested_product, auth: %{plan: plan_name}}} = resolution

Expand Down
24 changes: 5 additions & 19 deletions lib/sanbase_web/graphql/schema/queries/signal_queries.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,20 @@ defmodule SanbaseWeb.Graphql.Schema.SignalQueries do
import SanbaseWeb.Graphql.Cache, only: [cache_resolve: 2]

alias SanbaseWeb.Graphql.Resolvers.SignalResolver
alias SanbaseWeb.Graphql.Middlewares.TransformResolution

object :signal_queries do
@desc ~s"""
Return data for a given metric.
Return anomaly events.
"""
field :get_signal, :signal do
field :get_anomalies, list_of(:anomaly) do
meta(access: :free)
arg(:signal, non_null(:string))

middleware(TransformResolution)
resolve(&SignalResolver.get_signal/3)
end

field :get_available_signals, list_of(:string) do
meta(access: :free)
cache_resolve(&SignalResolver.get_available_signals/3, ttl: 120)
end

field :get_raw_signals, list_of(:raw_signal) do
meta(access: :free)

arg(:selector, :signal_target_selector_input_object)
arg(:signals, list_of(:string))
arg(:selector, :anomaly_target_selector_input_object)
arg(:anomalies, list_of(:string))
arg(:from, non_null(:datetime))
arg(:to, non_null(:datetime))

cache_resolve(&SignalResolver.get_raw_signals/3, ttl: 30, max_ttl_offset: 30)
cache_resolve(&SignalResolver.get_anomalies/3, ttl: 30, max_ttl_offset: 30)
end
end
end
19 changes: 1 addition & 18 deletions lib/sanbase_web/graphql/schema/types/project_types.ex
Original file line number Diff line number Diff line change
Expand Up @@ -124,29 +124,12 @@ defmodule SanbaseWeb.Graphql.ProjectTypes do
"""
object :project do
@desc ~s"""
Returns a list of available signals. Every one of the signals in the list
can be passed as the `metric` argument of the `getMetric` query.

For example, any of the signals from the query:
Returns a list of available signals for this project.
```
{
projectBySlug(slug: "ethereum"){ availableSignals }
}
```
can be used like this:
```
{
getSignal(signal: "<signal>"){
timeseriesData(
slug: "ethereum"
from: "2019-01-01T00:00:00Z"
to: "2019-02-01T00:00:00Z"
interval: "1d"){
datetime
value
}
}
```
"""

field :available_signals, list_of(:string) do
Expand Down
Loading