diff --git a/lib/sanbase_web/graphql/absinthe_before_send.ex b/lib/sanbase_web/graphql/absinthe_before_send.ex index 52e9ceef9a..605fe8b424 100644 --- a/lib/sanbase_web/graphql/absinthe_before_send.ex +++ b/lib/sanbase_web/graphql/absinthe_before_send.ex @@ -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| and getSignal| + # The pre_override_queries are the getMetric query names + # before they got renamed to getMetric| false -> maybe_cache_result(query_metadata.queries, blueprint) end end @@ -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 @@ -408,18 +405,17 @@ 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 = @@ -427,12 +423,9 @@ defmodule SanbaseWeb.Graphql.AbsintheBeforeSend do |> 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 -> diff --git a/lib/sanbase_web/graphql/middlewares/transform_resolution.ex b/lib/sanbase_web/graphql/middlewares/transform_resolution.ex index 5388efd7cb..2bc127bd79 100644 --- a/lib/sanbase_web/graphql/middlewares/transform_resolution.ex +++ b/lib/sanbase_web/graphql/middlewares/transform_resolution.ex @@ -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 @@ -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 diff --git a/lib/sanbase_web/graphql/resolvers/signal_resolver.ex b/lib/sanbase_web/graphql/resolvers/signal_resolver.ex index 76c15feac4..1b706c50bf 100644 --- a/lib/sanbase_web/graphql/resolvers/signal_resolver.ex +++ b/lib/sanbase_web/graphql/resolvers/signal_resolver.ex @@ -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) @@ -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 @@ -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 @@ -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} -> @@ -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 diff --git a/lib/sanbase_web/graphql/schema/queries/signal_queries.ex b/lib/sanbase_web/graphql/schema/queries/signal_queries.ex index 272bd75bcf..69fab865d3 100644 --- a/lib/sanbase_web/graphql/schema/queries/signal_queries.ex +++ b/lib/sanbase_web/graphql/schema/queries/signal_queries.ex @@ -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 diff --git a/lib/sanbase_web/graphql/schema/types/project_types.ex b/lib/sanbase_web/graphql/schema/types/project_types.ex index 083b111526..f26240aae6 100644 --- a/lib/sanbase_web/graphql/schema/types/project_types.ex +++ b/lib/sanbase_web/graphql/schema/types/project_types.ex @@ -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: ""){ - 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 diff --git a/lib/sanbase_web/graphql/schema/types/signal_types.ex b/lib/sanbase_web/graphql/schema/types/signal_types.ex index 6928e4fcc3..cc6bd74294 100644 --- a/lib/sanbase_web/graphql/schema/types/signal_types.ex +++ b/lib/sanbase_web/graphql/schema/types/signal_types.ex @@ -1,19 +1,11 @@ defmodule SanbaseWeb.Graphql.SignalTypes do use Absinthe.Schema.Notation - import SanbaseWeb.Graphql.Cache, only: [cache_resolve: 1, cache_resolve: 2] + import SanbaseWeb.Graphql.Cache, only: [cache_resolve: 2] - alias SanbaseWeb.Graphql.Complexity - alias SanbaseWeb.Graphql.Middlewares.AccessControl alias SanbaseWeb.Graphql.Resolvers.SignalResolver - alias Sanbase.Signal - input_object :signal_selector_input_object do - field(:slug, :string) - field(:slugs, list_of(:string)) - end - - input_object :signal_target_selector_input_object do + input_object :anomaly_target_selector_input_object do field(:slug, :string) field(:slugs, list_of(:string)) field(:market_segments, list_of(:string)) @@ -22,122 +14,18 @@ defmodule SanbaseWeb.Graphql.SignalTypes do field(:watchlist_slug, :string) end - object :raw_signal do - field(:signal, non_null(:string)) + object :anomaly do + field(:anomaly, non_null(:string)) field(:is_hidden, non_null(:boolean)) field(:datetime, :datetime) field(:slug, :string) field(:value, :float) field(:metadata, :json) - # The signals can be computed for assets that are no longer linked to + # The anomalies can be computed for assets that are no longer linked to # an existing project. In this case this field can be nil. field :project, :project do cache_resolve(&SignalResolver.project/3) end end - - object :signal_data do - field(:datetime, non_null(:datetime)) - field(:value, :float) - field(:metadata, list_of(:json)) - end - - object :signal_metadata do - @desc ~s""" - The name of the signal the metadata is about - """ - field(:signal, non_null(:string)) - - @desc ~s""" - List of slugs which can be provided to the `timeseriesData` field to fetch - the signal. - """ - field :available_slugs, list_of(:string) do - cache_resolve(&SignalResolver.get_available_slugs/3, ttl: 600) - end - - @desc ~s""" - The minimal granularity for which the data is available. - """ - field(:min_interval, :string) - - @desc ~s""" - When the interval provided in the query is bigger than `min_interval` and - contains two or more data points, the data must be aggregated into a single - data point. The default aggregation that is applied is this `default_aggregation`. - The default aggregation can be changed by the `aggregation` parameter of - the `timeseriesData` field. Available aggregations are: - [ - #{Signal.available_aggregations() |> Enum.map(&Atom.to_string/1) |> Enum.map(&String.upcase/1) |> Enum.join(",")} - ] - """ - field(:default_aggregation, :aggregation) - - @desc ~s""" - The supported aggregations for this signal. For more information about - aggregations see the documentation for `defaultAggregation` - """ - field(:available_aggregations, list_of(:aggregation)) - field(:data_type, :signal_data_type) - field(:is_accessible, :boolean) - field(:is_restricted, :boolean) - field(:restricted_from, :datetime) - field(:restricted_to, :datetime) - end - - object :signal do - @desc ~s""" - Return a list of 'datetime' and 'value' for a given anomaly, slug - and time period. - """ - field :timeseries_data, list_of(:signal_data) do - arg(:slug, :string) - arg(:selector, :signal_selector_input_object) - arg(:from, non_null(:datetime)) - arg(:to, non_null(:datetime)) - arg(:interval, :interval, default_value: "1d") - arg(:aggregation, :aggregation, default_value: nil) - - complexity(&Complexity.from_to_interval/3) - middleware(AccessControl, %{allow_realtime_data: true, allow_historical_data: true}) - - cache_resolve(&SignalResolver.timeseries_data/3) - end - - @desc ~s""" - A derivative of the `timeseriesData` - read its full descriptio if not - familiar with it. - - `aggregatedTimeseriesData` returns a single float value instead of list - of datetimes and values. The single values is computed by aggregating all - of the values in the specified from-to range with the `aggregation` aggregation. - """ - field :aggregated_timeseries_data, :float do - arg(:slug, :string) - arg(:selector, :signal_selector_input_object) - arg(:from, non_null(:datetime)) - arg(:to, non_null(:datetime)) - arg(:aggregation, :aggregation, default_value: nil) - - complexity(&Complexity.from_to_interval/3) - middleware(AccessControl) - - cache_resolve(&SignalResolver.aggregated_timeseries_data/3) - end - - field :available_since, :datetime do - arg(:slug, :string) - arg(:selector, :signal_selector_input_object) - cache_resolve(&SignalResolver.available_since/3) - end - - field :metadata, :signal_metadata do - cache_resolve(&SignalResolver.get_metadata/3) - end - end - - enum :signal_data_type do - value(:timeseries) - end end diff --git a/test/sanbase/billing/query_access_level_test.exs b/test/sanbase/billing/query_access_level_test.exs index 24353b19b7..f4ef6499bb 100644 --- a/test/sanbase/billing/query_access_level_test.exs +++ b/test/sanbase/billing/query_access_level_test.exs @@ -75,7 +75,7 @@ defmodule Sanbase.Billing.QueryAccessLevelTest do :get_available_blockchains, :get_available_metrics, :get_available_metrics_for_selector, - :get_available_signals, + :get_anomalies, :get_blockchain_address_labels, :get_cached_dashboard_queries_executions, :get_chart_configuration_shared_access_token, @@ -109,12 +109,10 @@ defmodule Sanbase.Billing.QueryAccessLevelTest do :get_primary_user, :get_questionnaire, :get_questionnaire_user_answers, - :get_raw_signals, :get_reports, :get_reports_by_tags, :get_secondary_users, :get_sheets_templates, - :get_signal, :get_subscription_with_payment_intent, :get_telegram_deep_link, :get_trigger_by_id, diff --git a/test/sanbase_web/graphql/billing/timeframe_access_restrictions/api_product_access_test.exs b/test/sanbase_web/graphql/billing/timeframe_access_restrictions/api_product_access_test.exs index 6f586c5faa..75f32cb000 100644 --- a/test/sanbase_web/graphql/billing/timeframe_access_restrictions/api_product_access_test.exs +++ b/test/sanbase_web/graphql/billing/timeframe_access_restrictions/api_product_access_test.exs @@ -9,7 +9,6 @@ defmodule Sanbase.Billing.ApiProductAccessTest do alias Sanbase.Accounts.Apikey alias Sanbase.Price alias Sanbase.Metric - alias Sanbase.Signal alias Sanbase.Clickhouse.TopHolders @product "SANAPI" @@ -17,8 +16,7 @@ defmodule Sanbase.Billing.ApiProductAccessTest do setup_all_with_mocks([ {Price, [], [timeseries_data: fn _, _, _, _ -> price_resp() end]}, {Metric, [:passthrough], [timeseries_data: fn _, _, _, _, _, _ -> metric_resp() end]}, - {TopHolders, [], [top_holders: fn _, _, _, _ -> top_holders_resp() end]}, - {Signal, [:passthrough], [timeseries_data: fn _, _, _, _, _, _ -> signal_resp() end]} + {TopHolders, [], [top_holders: fn _, _, _, _ -> top_holders_resp() end]} ]) do [] end @@ -61,16 +59,6 @@ defmodule Sanbase.Billing.ApiProductAccessTest do assert result != nil end - test "can access FREE signals for all time", context do - {from, to} = from_to(2500, 0) - signal = get_free_timeseries_element(context.next_integer.(), @product, :signal) - slug = context.project.slug - query = signal_query(signal, slug, from, to) - result = execute_query(context.conn, query, "getSignal") - assert_called(Signal.timeseries_data(signal, :_, from, to, :_, :_)) - assert result != nil - end - test "cannot access RESTRICTED metrics for over 1 year", context do {from, to} = from_to(1 * 365 + 1, 32) metric = v2_restricted_metric_for_plan(context.next_integer.(), @product, "FREE") @@ -183,16 +171,6 @@ defmodule Sanbase.Billing.ApiProductAccessTest do assert result != nil end - test "can access FREE signals for all time", context do - {from, to} = from_to(2500, 0) - signal = get_free_timeseries_element(context.next_integer.(), @product, :signal) - slug = context.project.slug - query = signal_query(signal, slug, from, to) - result = execute_query(context.conn, query, "getSignal") - assert_called(Signal.timeseries_data(signal, :_, from, to, :_, :_)) - assert result != nil - end - test "can access RESTRICTED metrics for less than 1 years", context do {from, to} = from_to(1 * 365 - 1, 1 * 365 - 2) @@ -354,26 +332,6 @@ defmodule Sanbase.Billing.ApiProductAccessTest do """ end - # test "can't access signal with min plan PRO", context do - # {from, to} = from_to(2 * 365 - 1, 2 * 365 - 2) - # signal = restricted_signal_for_plan(context.next_integer.(), @product, "PRO") - # slug = context.project.slug - # query = signal_query(signal, slug, from, to) - # error_message = execute_query_with_error(context.conn, query, "getSignal") - - # refute called(Signal.timeseries_data(signal, :_, from, to, :_, :_)) - - # assert error_message == - # """ - # The signal #{signal} is not accessible with the currently used - # Sanapi Basic subscription. Please upgrade to Sanapi Pro subscription. - - # If you have a subscription for one product but attempt to fetch data using - # another product, this error will still be shown. The data on Sanbase cannot - # be fetched with a Sanapi subscription and vice versa. - # """ - # end - test "some metrics can be accessed only with free timeframe", context do {from, to} = from_to(89, 2) metric = "active_deposits" @@ -430,16 +388,6 @@ defmodule Sanbase.Billing.ApiProductAccessTest do assert result != nil end - test "can access FREE signals for all time", context do - {from, to} = from_to(2500, 0) - signal = get_free_timeseries_element(context.next_integer.(), @product, :signal) - slug = context.project.slug - query = signal_query(signal, slug, from, to) - result = execute_query(context.conn, query, "getSignal") - assert_called(Signal.timeseries_data(signal, :_, from, to, :_, :_)) - assert result != nil - end - test "can access RESTRICTED metrics for less than 7 years", context do {from, to} = from_to(7 * 365 - 1, 7 * 365 - 2) metric = v2_restricted_metric_for_plan(context.next_integer.(), @product, "PRO") @@ -589,16 +537,6 @@ defmodule Sanbase.Billing.ApiProductAccessTest do assert result != nil end - test "can access FREE signals for all time", context do - {from, to} = from_to(2500, 0) - signal = get_free_timeseries_element(context.next_integer.(), @product, :signal) - slug = context.project.slug - query = signal_query(signal, slug, from, to) - result = execute_query(context.conn, query, "getSignal") - assert_called(Signal.timeseries_data(signal, :_, from, to, :_, :_)) - assert result != nil - end - test "can access RESTRICTED metrics for all time & realtime", context do {from, to} = from_to(2500, 0) metric = v2_restricted_metric_for_plan(context.next_integer.(), @product, "CUSTOM") @@ -992,23 +930,6 @@ defmodule Sanbase.Billing.ApiProductAccessTest do """ end - defp signal_query(signal, slug, from, to) do - """ - { - getSignal(signal: "#{signal}") { - timeseriesData( - slug: "#{slug}" - from: "#{from}" - to: "#{to}" - interval: "30d"){ - datetime - value - } - } - } - """ - end - defp restricted_access_query(slug, from, to) do """ { diff --git a/test/sanbase_web/graphql/billing/timeframe_access_restrictions/sanbase_product_access_test.exs b/test/sanbase_web/graphql/billing/timeframe_access_restrictions/sanbase_product_access_test.exs index 53b230e45e..34800b085b 100644 --- a/test/sanbase_web/graphql/billing/timeframe_access_restrictions/sanbase_product_access_test.exs +++ b/test/sanbase_web/graphql/billing/timeframe_access_restrictions/sanbase_product_access_test.exs @@ -7,7 +7,6 @@ defmodule Sanbase.Billing.SanbaseProductAccessTest do import Sanbase.TestHelpers alias Sanbase.Metric - alias Sanbase.Signal alias Sanbase.Clickhouse.TopHolders @triggers_free_limit_count 3 @@ -19,7 +18,6 @@ defmodule Sanbase.Billing.SanbaseProductAccessTest do setup_all_with_mocks([ {Sanbase.Price, [:passthrough], [timeseries_data: fn _, _, _, _ -> price_resp() end]}, {Sanbase.Metric, [:passthrough], [timeseries_data: fn _, _, _, _, _, _ -> metric_resp() end]}, - {Sanbase.Signal, [:passthrough], [timeseries_data: fn _, _, _, _, _, _ -> signal_resp() end]}, {TopHolders, [], [top_holders: fn _, _, _, _ -> top_holders_resp() end]}, {Sanbase.Alert.UserTrigger, [:passthrough], [triggers_count_for: fn _ -> @triggers_free_limit_count end]} @@ -61,19 +59,6 @@ defmodule Sanbase.Billing.SanbaseProductAccessTest do assert result != nil end - test "can access FREE signals for all time", context do - {from, to} = from_to(2500, 0) - slug = context.project.slug - - signal = get_free_timeseries_element(context.next_integer.(), @product, :signal) - query = signal_query(signal, slug, from, to) - - result = execute_query(context.conn, query, "getSignal") - - assert_called(Signal.timeseries_data(signal, :_, from, to, :_, :_)) - assert result != nil - end - test "cannot access RESTRICTED metrics for over 2 years", context do {from, to} = from_to(2 * 365 + 1, 31) slug = context.project.slug @@ -204,19 +189,6 @@ defmodule Sanbase.Billing.SanbaseProductAccessTest do assert result != nil end - test "can access FREE signals for all time", context do - {from, to} = from_to(2500, 0) - slug = context.project.slug - - signal = get_free_timeseries_element(context.next_integer.(), @product, :signal) - query = signal_query(signal, slug, from, to) - - result = execute_query(context.conn, query, "getSignal") - - assert_called(Signal.timeseries_data(signal, :_, from, to, :_, :_)) - assert result != nil - end - test "can access RESTRICTED metrics for all time", context do {from, to} = from_to(4000, 10) slug = context.project.slug @@ -567,23 +539,6 @@ defmodule Sanbase.Billing.SanbaseProductAccessTest do """ end - defp signal_query(signal, slug, from, to) do - """ - { - getSignal(signal: "#{signal}") { - timeseriesData( - slug: "#{slug}" - from: "#{from}" - to: "#{to}" - interval: "30d"){ - datetime - value - } - } - } - """ - end - defp restricted_access_query(slug, from, to) do """ { diff --git a/test/sanbase_web/graphql/signal/api_anomaly_raw_data_test.exs b/test/sanbase_web/graphql/signal/api_anomaly_raw_data_test.exs new file mode 100644 index 0000000000..ada3b575f4 --- /dev/null +++ b/test/sanbase_web/graphql/signal/api_anomaly_raw_data_test.exs @@ -0,0 +1,172 @@ +defmodule SanbaseWeb.Graphql.Clickhouse.ApiAnomalyRawDataTest 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, + from: ~U[2019-01-01 00:00:00Z], + to: ~U[2019-01-02 00:00:00Z] + ] + end + + test "returns anomalies without anomaly filtering", context do + %{conn: conn, from: from, to: to} = context + + rows = [ + [ + ~U[2019-01-01 00:00:00Z] |> DateTime.to_unix(), + "anomaly_mvrv_usd", + "bitcoin", + 2.4, + ~s|{"side": "high"}| + ] + ] + + Sanbase.Mock.prepare_mock2(&Sanbase.ClickhouseRepo.query/3, {:ok, %{rows: rows}}) + |> Sanbase.Mock.run_with_mocks(fn -> + result = + get_anomalies(conn, :all, :all, from, to) + |> get_in(["data", "getAnomalies"]) + + assert result == [ + %{ + "datetime" => "2019-01-01T00:00:00Z", + "metadata" => %{"side" => "high"}, + "value" => 2.4, + "anomaly" => "anomaly_mvrv_usd", + "slug" => "bitcoin", + "isHidden" => false + } + ] + end) + end + + test "returns anomalies with anomaly filtering", context do + %{conn: conn, from: from, to: to} = context + + rows = [ + [ + ~U[2019-01-01 00:00:00Z] |> DateTime.to_unix(), + "anomaly_mvrv_usd", + "bitcoin", + 2.4, + ~s|{"side": "high"}| + ] + ] + + Sanbase.Mock.prepare_mock2(&Sanbase.ClickhouseRepo.query/3, {:ok, %{rows: rows}}) + |> Sanbase.Mock.run_with_mocks(fn -> + result = + get_anomalies(conn, ["anomaly_mvrv_usd"], :all, from, to) + |> get_in(["data", "getAnomalies"]) + + assert result == [ + %{ + "datetime" => "2019-01-01T00:00:00Z", + "metadata" => %{"side" => "high"}, + "value" => 2.4, + "anomaly" => "anomaly_mvrv_usd", + "slug" => "bitcoin", + "isHidden" => false + } + ] + end) + end + + test "returns anomalies with selector filtering", context do + %{conn: conn, from: from, to: to} = context + + insert(:random_erc20_project, slug: "bitcoin") + + rows = [ + [ + ~U[2019-01-01 00:00:00Z] |> DateTime.to_unix(), + "anomaly_mvrv_usd", + "bitcoin", + 2.4, + ~s|{"side": "high"}| + ] + ] + + Sanbase.Mock.prepare_mock2(&Sanbase.ClickhouseRepo.query/3, {:ok, %{rows: rows}}) + |> Sanbase.Mock.run_with_mocks(fn -> + result = + get_anomalies(conn, :all, ["bitcoin"], from, to) + |> get_in(["data", "getAnomalies"]) + + assert result == [ + %{ + "datetime" => "2019-01-01T00:00:00Z", + "metadata" => %{"side" => "high"}, + "value" => 2.4, + "anomaly" => "anomaly_mvrv_usd", + "slug" => "bitcoin", + "isHidden" => false + } + ] + end) + end + + defp get_anomalies(conn, anomalies, slugs, from, to) do + query = get_anomalies_query(anomalies, slugs, from, to) + + conn + |> post("/graphql", query_skeleton(query, "getAnomalies")) + |> json_response(200) + end + + defp get_anomalies_query(:all, :all, from, to) do + """ + { + getAnomalies(from: "#{from}", to: "#{to}"){ + datetime + anomaly + slug + value + metadata + isHidden + } + } + """ + end + + defp get_anomalies_query(anomalies, :all, from, to) do + anomalies = Enum.map(anomalies, &~s/"#{&1}"/) |> Enum.join(",") + + """ + { + getAnomalies(anomalies: [#{anomalies}], from: "#{from}", to: "#{to}"){ + datetime + anomaly + slug + value + metadata + isHidden + } + } + """ + end + + defp get_anomalies_query(:all, slugs, from, to) do + slugs_str = Enum.map(slugs, &~s/"#{&1}"/) |> Enum.join(",") + + """ + { + getAnomalies(from: "#{from}", to: "#{to}", selector: {slugs: [#{slugs_str}]}){ + datetime + anomaly + slug + value + metadata + isHidden + } + } + """ + end +end diff --git a/test/sanbase_web/graphql/signal/api_signal_metadata_test.exs b/test/sanbase_web/graphql/signal/api_signal_metadata_test.exs deleted file mode 100644 index 8344ba3652..0000000000 --- a/test/sanbase_web/graphql/signal/api_signal_metadata_test.exs +++ /dev/null @@ -1,69 +0,0 @@ -defmodule SanbaseWeb.Graphql.Clickhouse.ApiSignalMetadataTest do - use SanbaseWeb.ConnCase, async: true - - import Sanbase.Factory, only: [rand_str: 0] - import SanbaseWeb.Graphql.TestHelpers - - alias Sanbase.Signal - - test "returns data for all available signal", %{conn: conn} do - signals = Signal.available_signals() - aggregations = Signal.available_aggregations() - - aggregations = - aggregations |> Enum.map(fn aggr -> aggr |> Atom.to_string() |> String.upcase() end) - - for signal <- signals do - %{"data" => %{"getSignal" => %{"metadata" => metadata}}} = get_signal_metadata(conn, signal) - - assert metadata["signal"] == signal - - assert match?( - %{"signal" => _, "defaultAggregation" => _, "minInterval" => _, "dataType" => _}, - metadata - ) - - assert metadata["defaultAggregation"] in aggregations - assert metadata["minInterval"] in ["5m"] - assert metadata["dataType"] in ["TIMESERIES"] - assert length(metadata["availableAggregations"]) > 0 - end - end - - test "returns error for unavailable signal", %{conn: conn} do - rand_signals = Enum.map(1..100, fn _ -> rand_str() end) - rand_signals = rand_signals -- Signal.available_signals() - - # Do not mock the `histogram_data` function because it's the one that rejects - for signal <- rand_signals do - %{ - "errors" => [ - %{"message" => error_message} - ] - } = get_signal_metadata(conn, signal) - - assert error_message == - "The signal '#{signal}' is not supported, is deprecated or is mistyped." - end - end - - defp get_signal_metadata(conn, signal) do - query = """ - { - getSignal(signal: "#{signal}"){ - metadata{ - minInterval - defaultAggregation - availableAggregations - dataType - signal - } - } - } - """ - - conn - |> post("/graphql", query_skeleton(query)) - |> json_response(200) - end -end diff --git a/test/sanbase_web/graphql/signal/api_signal_raw_data_test.exs b/test/sanbase_web/graphql/signal/api_signal_raw_data_test.exs deleted file mode 100644 index 84d200eed2..0000000000 --- a/test/sanbase_web/graphql/signal/api_signal_raw_data_test.exs +++ /dev/null @@ -1,359 +0,0 @@ -defmodule SanbaseWeb.Graphql.Clickhouse.ApiSignalRawDataTest do - use SanbaseWeb.ConnCase, async: false - - import Sanbase.Factory - import SanbaseWeb.Graphql.TestHelpers - - setup do - %{user: user} = insert(:subscription_pro_sanbase, user: insert(:user)) - free_user = insert(:user) - - conn = setup_jwt_auth(build_conn(), user) - free_conn = setup_jwt_auth(build_conn(), free_user) - - [ - conn: conn, - free_conn: free_conn, - from: ~U[2019-01-01 00:00:00Z], - to: ~U[2019-01-02 00:00:00Z] - ] - end - - test "signal without signals filtering", context do - %{conn: conn, from: from, to: to} = context - - # TODO: Update with different signals when they are added to the JSON file - rows = [ - [ - ~U[2019-01-01 00:00:00Z] |> DateTime.to_unix(), - "project_in_trends", - "bitcoin", - 217.437388, - ~s|{"rank": 6, "word": "bitcoin", "project": "BTC_bitcoin", "total_score": 217.43738810221353}| - ], - [ - ~U[2019-01-02 00:00:00Z] |> DateTime.to_unix(), - "project_in_trends", - "bitcoin", - 150.535170, - ~s|{"rank": 9, "word": "bitcoin", "project": "BTC_bitcoin", "total_score": 150.5351702372233}| - ] - ] - - Sanbase.Mock.prepare_mock2(&Sanbase.ClickhouseRepo.query/3, {:ok, %{rows: rows}}) - |> Sanbase.Mock.run_with_mocks(fn -> - result = - get_raw_signals(conn, :all, :all, from, to) - |> get_in(["data", "getRawSignals"]) - - assert result == [ - %{ - "datetime" => "2019-01-01T00:00:00Z", - "metadata" => %{ - "rank" => 6, - "word" => "bitcoin", - "project" => "BTC_bitcoin", - "total_score" => 217.43738810221353 - }, - "value" => 217.437388, - "signal" => "anomaly_project_in_trending_words", - "slug" => "bitcoin", - "isHidden" => false - }, - %{ - "datetime" => "2019-01-02T00:00:00Z", - "metadata" => %{ - "rank" => 9, - "word" => "bitcoin", - "project" => "BTC_bitcoin", - "total_score" => 150.5351702372233 - }, - "value" => 150.535170, - "signal" => "anomaly_project_in_trending_words", - "slug" => "bitcoin", - "isHidden" => false - } - ] - end) - end - - test "signal with signals filtering", context do - %{conn: conn, from: from, to: to} = context - - rows = [ - [ - ~U[2019-01-01 00:00:00Z] |> DateTime.to_unix(), - "project_in_trends", - "pepe", - 572.494344, - ~s|{"rank": 1, "word": "pepe", "project": "PEPE_pepe", "total_score": 572.4943440755209}| - ], - [ - ~U[2019-01-02 00:00:00Z] |> DateTime.to_unix(), - "project_in_trends", - "pepe", - 260.986816, - ~s|{"rank": 9, "word": "pepe", "project": "PEPE_pepe", "total_score": 260.98681640625}| - ] - ] - - Sanbase.Mock.prepare_mock2(&Sanbase.ClickhouseRepo.query/3, {:ok, %{rows: rows}}) - |> Sanbase.Mock.run_with_mocks(fn -> - result = - get_raw_signals( - conn, - ["anomaly_project_in_trending_words", "anomaly_total_liquidations"], - :all, - from, - to - ) - |> get_in(["data", "getRawSignals"]) - - assert result == [ - %{ - "datetime" => "2019-01-01T00:00:00Z", - "metadata" => %{ - "rank" => 1, - "word" => "pepe", - "project" => "PEPE_pepe", - "total_score" => 572.4943440755209 - }, - "value" => 572.494344, - "signal" => "anomaly_project_in_trending_words", - "slug" => "pepe", - "isHidden" => false - }, - %{ - "datetime" => "2019-01-02T00:00:00Z", - "metadata" => %{ - "rank" => 9, - "word" => "pepe", - "project" => "PEPE_pepe", - "total_score" => 260.98681640625 - }, - "value" => 260.986816, - "signal" => "anomaly_project_in_trending_words", - "slug" => "pepe", - "isHidden" => false - } - ] - end) - end - - test "signal with selector filtering", context do - %{conn: conn, from: from, to: to} = context - - # When the slugs in the selector are validated they must exist - insert(:random_erc20_project, slug: "multi-collateral-dai") - insert(:random_erc20_project, slug: "not-dai") - - rows = [ - [ - ~U[2019-01-01 00:00:00Z] |> DateTime.to_unix(), - "project_in_trends", - "multi-collateral-dai", - 572.494344, - ~s|{"rank": 1, "word": "dai", "project": "DAI_multi-collateral-dai", "total_score": 572.4943440755209}| - ], - [ - ~U[2019-01-02 00:00:00Z] |> DateTime.to_unix(), - "project_in_trends", - "not-dai", - 169.057078, - ~s|{"rank": 10, "word": "not-dai", "project": "NOTDAI_not-dai", "total_score": 169.05707804361978}| - ] - ] - - Sanbase.Mock.prepare_mock2(&Sanbase.ClickhouseRepo.query/3, {:ok, %{rows: rows}}) - |> Sanbase.Mock.run_with_mocks(fn -> - result = - get_raw_signals(conn, :all, ["multi-collateral-dai"], from, to) - |> get_in(["data", "getRawSignals"]) - - assert result == [ - %{ - "datetime" => "2019-01-01T00:00:00Z", - "metadata" => %{ - "rank" => 1, - "word" => "dai", - "project" => "DAI_multi-collateral-dai", - "total_score" => 572.4943440755209 - }, - "value" => 572.494344, - "signal" => "anomaly_project_in_trending_words", - "slug" => "multi-collateral-dai", - "isHidden" => false - } - ] - end) - end - - test "restricted signal shown for fro user", context do - %{free_conn: free_conn, from: from, to: to} = context - insert(:random_erc20_project, slug: "multi-collateral-dai") - - rows = [ - [ - ~U[2019-01-01 00:00:00Z] |> DateTime.to_unix(), - "total_liquidations", - "multi-collateral-dai", - 21_029, - ~s|{"txHash": "0xecdeb8435aff6e18e08177bb94d52b2da6dd15b95aee7f442021911a7c9861e6", "address": "0x183c9077fb7b74f02d3badda6c85a19c92b1f648"}| - ], - [ - ~U[2019-01-02 00:00:00Z] |> DateTime.to_unix(), - "total_liquidations", - "multi-collateral-dai", - 12_308_120, - ~s|{"txHash": "0x0bb27622fa4fcdf39344251e9b0776467eaa5d9dbf0f025d254f55093848f2bd", "address": "0x61c808d82a3ac53231750dadc13c777b59310bd9"}| - ] - ] - - Sanbase.Mock.prepare_mock2(&Sanbase.ClickhouseRepo.query/3, {:ok, %{rows: rows}}) - |> Sanbase.Mock.run_with_mocks(fn -> - result = - get_raw_signals(free_conn, :all, :all, from, to) - |> get_in(["data", "getRawSignals"]) - - assert result == [ - %{ - "datetime" => "2019-01-01T00:00:00Z", - "isHidden" => false, - "metadata" => %{ - "address" => "0x183c9077fb7b74f02d3badda6c85a19c92b1f648", - "txHash" => - "0xecdeb8435aff6e18e08177bb94d52b2da6dd15b95aee7f442021911a7c9861e6" - }, - "signal" => "anomaly_total_liquidations", - "slug" => "multi-collateral-dai", - "value" => 21_029.0 - }, - %{ - "datetime" => "2019-01-02T00:00:00Z", - "metadata" => %{ - "address" => "0x61c808d82a3ac53231750dadc13c777b59310bd9", - "txHash" => - "0x0bb27622fa4fcdf39344251e9b0776467eaa5d9dbf0f025d254f55093848f2bd" - }, - "value" => 12_308_120.0, - "signal" => "anomaly_total_liquidations", - "slug" => "multi-collateral-dai", - "isHidden" => false - } - ] - end) - end - - test "restricted signal not shown for free user", context do - %{conn: conn, from: from, to: to} = context - insert(:random_erc20_project, slug: "multi-collateral-dai") - - rows = [ - [ - ~U[2019-01-01 00:00:00Z] |> DateTime.to_unix(), - "total_liquidations", - "multi-collateral-dai", - 21_029, - ~s|{"txHash": "0xecdeb8435aff6e18e08177bb94d52b2da6dd15b95aee7f442021911a7c9861e6", "address": "0x183c9077fb7b74f02d3badda6c85a19c92b1f648"}| - ], - [ - ~U[2019-01-02 00:00:00Z] |> DateTime.to_unix(), - "total_liquidations", - "multi-collateral-dai", - 12_308_120, - ~s|{"txHash": "0x0bb27622fa4fcdf39344251e9b0776467eaa5d9dbf0f025d254f55093848f2bd", "address": "0x61c808d82a3ac53231750dadc13c777b59310bd9"}| - ] - ] - - Sanbase.Mock.prepare_mock2(&Sanbase.ClickhouseRepo.query/3, {:ok, %{rows: rows}}) - |> Sanbase.Mock.run_with_mocks(fn -> - result = - get_raw_signals(conn, :all, :all, from, to) - |> get_in(["data", "getRawSignals"]) - - assert result == [ - %{ - "datetime" => "2019-01-01T00:00:00Z", - "isHidden" => false, - "metadata" => %{ - "address" => "0x183c9077fb7b74f02d3badda6c85a19c92b1f648", - "txHash" => - "0xecdeb8435aff6e18e08177bb94d52b2da6dd15b95aee7f442021911a7c9861e6" - }, - "signal" => "anomaly_total_liquidations", - "slug" => "multi-collateral-dai", - "value" => 21_029.0 - }, - %{ - "datetime" => "2019-01-02T00:00:00Z", - "metadata" => %{ - "address" => "0x61c808d82a3ac53231750dadc13c777b59310bd9", - "txHash" => - "0x0bb27622fa4fcdf39344251e9b0776467eaa5d9dbf0f025d254f55093848f2bd" - }, - "value" => 12_308_120.0, - "signal" => "anomaly_total_liquidations", - "slug" => "multi-collateral-dai", - "isHidden" => false - } - ] - end) - end - - # Private functions - - defp get_raw_signals(conn, signals, slugs, from, to) do - query = get_raw_signals_query(signals, slugs, from, to) - - conn - |> post("/graphql", query_skeleton(query, "getSignal")) - |> json_response(200) - end - - defp get_raw_signals_query(:all, :all, from, to) do - """ - { - getRawSignals(from: "#{from}", to: "#{to}"){ - datetime - signal - slug - value - metadata - isHidden - } - } - """ - end - - defp get_raw_signals_query([_ | _] = signals, :all, from, to) do - """ - { - getRawSignals(signals: #{string_list_to_string(signals)}, from: "#{from}", to: "#{to}"){ - datetime - signal - slug - value - metadata - isHidden - } - } - """ - end - - defp get_raw_signals_query(:all, slugs, from, to) do - slugs_str = Enum.map(slugs, &~s/"#{&1}"/) |> Enum.join(",") - - """ - { - getRawSignals(from: "#{from}", to: "#{to}", selector: {slugs: [#{slugs_str}]}){ - datetime - signal - slug - value - metadata - isHidden - } - } - """ - end -end diff --git a/test/sanbase_web/graphql/signal/api_signal_timeseries_data_test.exs b/test/sanbase_web/graphql/signal/api_signal_timeseries_data_test.exs deleted file mode 100644 index 92ca0504d4..0000000000 --- a/test/sanbase_web/graphql/signal/api_signal_timeseries_data_test.exs +++ /dev/null @@ -1,204 +0,0 @@ -defmodule SanbaseWeb.Graphql.Clickhouse.ApiSignalTimeseriesDataTest do - use SanbaseWeb.ConnCase, async: false - - import Sanbase.Factory - import SanbaseWeb.Graphql.TestHelpers - - alias Sanbase.Signal - - setup do - %{user: user} = insert(:subscription_pro_sanbase, user: insert(:user)) - conn = setup_jwt_auth(build_conn(), user) - - insert(:random_project, slug: "ethereum") - - [ - conn: conn, - slug: "ethereum", - from: ~U[2019-01-01 00:00:00Z], - to: ~U[2019-01-02 00:00:00Z], - interval: "1d" - ] - end - - test "returns data for an available signal", context do - %{conn: conn, slug: slug, from: from, to: to, interval: interval} = context - aggregation = :avg - [signal | _] = Signal.available_signals() - - rows = [ - [ - ~U[2019-01-01 00:00:00Z] |> DateTime.to_unix(), - 2, - [ - "{\"txHash\": \"0xecdeb8435aff6e18e08177bb94d52b2da6dd15b95aee7f442021911a7c9861e6\", \"address\": \"0x183c9077fb7b74f02d3badda6c85a19c92b1f648\"}", - "{\"txHash\": \"0x8e8eae8adeb2fae2b21387d7bea7f4287e425cfe9efc1728966eceed4feb7d4e\", \"address\": \"0x65b0bf8ee4947edd2a500d74e50a3d757dc79de0\"}" - ] - ], - [ - ~U[2019-01-02 00:00:00Z] |> DateTime.to_unix(), - 1, - [ - "{\"txHash\": \"0x0bb27622fa4fcdf39344251e9b0776467eaa5d9dbf0f025d254f55093848f2bd\", \"address\": \"0x61c808d82a3ac53231750dadc13c777b59310bd9\"}" - ] - ] - ] - - Sanbase.Mock.prepare_mock2(&Sanbase.ClickhouseRepo.query/3, {:ok, %{rows: rows}}) - |> Sanbase.Mock.run_with_mocks(fn -> - result = - get_timeseries_signal(conn, signal, slug, from, to, interval, aggregation) - |> extract_timeseries_data() - - assert result == [ - %{ - "value" => 2, - "datetime" => "2019-01-01T00:00:00Z", - "metadata" => [ - %{ - "address" => "0x183c9077fb7b74f02d3badda6c85a19c92b1f648", - "txHash" => - "0xecdeb8435aff6e18e08177bb94d52b2da6dd15b95aee7f442021911a7c9861e6" - }, - %{ - "address" => "0x65b0bf8ee4947edd2a500d74e50a3d757dc79de0", - "txHash" => - "0x8e8eae8adeb2fae2b21387d7bea7f4287e425cfe9efc1728966eceed4feb7d4e" - } - ] - }, - %{ - "value" => 1, - "datetime" => "2019-01-02T00:00:00Z", - "metadata" => [ - %{ - "address" => "0x61c808d82a3ac53231750dadc13c777b59310bd9", - "txHash" => - "0x0bb27622fa4fcdf39344251e9b0776467eaa5d9dbf0f025d254f55093848f2bd" - } - ] - } - ] - end) - end - - test "returns data for all available signals", context do - %{conn: conn, slug: slug, from: from, to: to, interval: interval} = context - aggregation = :avg - signals = Signal.available_signals() - - Sanbase.Mock.prepare_mock2( - &Signal.timeseries_data/6, - {:ok, - [ - %{value: 100.0, datetime: ~U[2019-01-01 00:00:00Z], metadata: []}, - %{value: 200.0, datetime: ~U[2019-01-02 00:00:00Z], metadata: []} - ]} - ) - |> Sanbase.Mock.run_with_mocks(fn -> - result = - for signal <- signals do - get_timeseries_signal(conn, signal, slug, from, to, interval, aggregation) - |> extract_timeseries_data() - end - - # Assert that all results are lists where we have a map with values - assert Enum.all?(result, &match?([%{"datetime" => _, "value" => _} | _], &1)) - end) - end - - test "returns data for all available aggregations", context do - %{conn: conn, slug: slug, from: from, to: to, interval: interval} = context - # nil means aggregation is not passed, we should not explicitly pass it - signal = Signal.available_signals() |> Enum.random() - {:ok, %{available_aggregations: aggregations}} = Signal.metadata(signal) - - Sanbase.Mock.prepare_mock2( - &Signal.timeseries_data/6, - {:ok, - [ - %{value: 100.0, datetime: ~U[2019-01-01 00:00:00Z], metadata: []}, - %{value: 200.0, datetime: ~U[2019-01-02 00:00:00Z], metadata: []} - ]} - ) - |> Sanbase.Mock.run_with_mocks(fn -> - result = - for aggregation <- aggregations do - get_timeseries_signal(conn, signal, slug, from, to, interval, aggregation) - |> extract_timeseries_data() - end - - # Assert that all results are lists where we have a map with values - assert Enum.all?(result, &match?([%{"datetime" => _, "value" => _} | _], &1)) - end) - end - - test "returns error for unavailable aggregations", context do - %{conn: conn, slug: slug, from: from, to: to, interval: interval} = context - aggregations = Signal.available_aggregations() - rand_aggregations = Enum.map(1..10, fn _ -> rand_str() |> String.to_atom() end) - rand_aggregations = rand_aggregations -- aggregations - [signal | _] = Signal.available_signals() - - # Do not mock the `get` function. It will reject the query if the execution - # reaches it. Currently the execution is halted even earlier because the - # aggregation is an enum with available values - result = - for aggregation <- rand_aggregations do - get_timeseries_signal(conn, signal, slug, from, to, interval, aggregation) - end - - # Assert that all results are lists where we have a map with values - assert Enum.all?(result, &match?(%{"errors" => _}, &1)) - end - - test "returns error for unavailable signals", context do - %{conn: conn, slug: slug, from: from, to: to, interval: interval} = context - aggregation = :avg - rand_signals = Enum.map(1..100, fn _ -> rand_str() end) - rand_signals = rand_signals -- Signal.available_signals() - - # Do not mock the `timeseries_data` function because it's the one that rejects - for signal <- rand_signals do - %{"errors" => [%{"message" => error_message}]} = - get_timeseries_signal(conn, signal, slug, from, to, interval, aggregation) - - assert error_message == - "The signal '#{signal}' is not supported, is deprecated or is mistyped." - end - end - - # Private functions - - defp get_timeseries_signal(conn, signal, slug, from, to, interval, aggregation) do - query = get_timeseries_query(signal, slug, from, to, interval, aggregation) - - conn - |> post("/graphql", query_skeleton(query, "getSignal")) - |> json_response(200) - end - - defp extract_timeseries_data(result) do - %{"data" => %{"getSignal" => %{"timeseriesData" => timeseries_data}}} = result - timeseries_data - end - - defp get_timeseries_query(signal, slug, from, to, interval, aggregation) do - """ - { - getSignal(signal: "#{signal}"){ - timeseriesData( - slug: "#{slug}", - from: "#{from}", - to: "#{to}", - interval: "#{interval}", - aggregation: #{Atom.to_string(aggregation) |> String.upcase()}){ - datetime - value - metadata - } - } - } - """ - end -end diff --git a/test/support/graphql_test_helpers.ex b/test/support/graphql_test_helpers.ex index 24bbb9d9dc..a669aafb16 100644 --- a/test/support/graphql_test_helpers.ex +++ b/test/support/graphql_test_helpers.ex @@ -3,7 +3,7 @@ defmodule SanbaseWeb.Graphql.TestHelpers do import Phoenix.ConnTest import Sanbase.Factory - alias Sanbase.{Metric, Signal} + alias Sanbase.Metric alias Sanbase.Billing.Plan.AccessChecker # The default endpoint for testing @@ -28,16 +28,8 @@ defmodule SanbaseWeb.Graphql.TestHelpers do |> Enum.map(&elem(&1, 0)) end - def restricted_signal_for_plan(position, product, plan_name) do - Signal.restricted_signals() - |> Enum.filter(&AccessChecker.plan_has_access?(plan_name, product, {:signal, &1})) - |> Stream.cycle() - |> Enum.at(position) - end - - def get_free_timeseries_element(position, product, argument) - when argument in [:metric, :signal] do - free_timeseries_elements(product, argument) + def get_free_timeseries_element(position, product, :metric) do + free_timeseries_elements(product, :metric) |> Enum.to_list() |> Stream.cycle() |> Enum.at(position) @@ -56,19 +48,6 @@ defmodule SanbaseWeb.Graphql.TestHelpers do |> MapSet.intersection(MapSet.new(Metric.available_timeseries_metrics())) end - defp free_timeseries_elements(product, :signal) do - Signal.min_plan_map() - |> Enum.filter(fn - {_, "FREE"} -> true - {_, %{^product => "FREE"}} -> true - _ -> false - end) - |> Enum.map(fn {signal, _} -> signal end) - |> MapSet.new() - |> MapSet.intersection(MapSet.new(Signal.free_signals())) - |> MapSet.intersection(MapSet.new(Signal.available_timeseries_signals())) - end - def from_to(from_days_shift, to_days_shift) do from = Timex.shift(DateTime.utc_now(), days: -from_days_shift) |> DateTime.truncate(:second) to = Timex.shift(DateTime.utc_now(), days: -to_days_shift) |> DateTime.truncate(:second)