| defmodule Plausible.Stats.Dashboard.QueryParser do |
| @moduledoc """ |
| Parses a dashboard query string into `%ParsedQueryParams{}`. Note that |
| `metrics` and `dimensions` do not exist at this step yet, and are expected |
| to be filled in by each specific report. |
| """ |
|
|
| alias Plausible.Stats.{ParsedQueryParams, QueryInclude, ApiQueryParser, QueryError} |
|
|
| @valid_comparison_shorthands %{ |
| "previous_period" => :previous_period, |
| "year_over_year" => :year_over_year |
| } |
|
|
| @valid_comparison_shorthand_keys Map.keys(@valid_comparison_shorthands) |
|
|
| def parse(params, opts \\ []) do |
| with {:ok, input_date_range} <- parse_input_date_range(params), |
| {:ok, relative_date} <- parse_relative_date(params), |
| {:ok, dimensions} <- ApiQueryParser.parse_dimensions(params["dimensions"]), |
| {:ok, filters} <- ApiQueryParser.parse_filters(params["filters"]), |
| {:ok, metrics} <- parse_metrics(params), |
| {:ok, order_by} <- ApiQueryParser.parse_order_by(params["order_by"]), |
| {:ok, pagination} <- ApiQueryParser.parse_pagination(params["pagination"]), |
| {:ok, include} <- parse_include(params) do |
| {:ok, |
| ParsedQueryParams.new!(%{ |
| input_date_range: input_date_range, |
| relative_date: relative_date, |
| dimensions: dimensions, |
| filters: filters, |
| metrics: metrics, |
| order_by: order_by, |
| pagination: pagination, |
| include: include, |
| skip_goal_existence_check: true, |
| now: Keyword.get(opts, :now) |
| })} |
| end |
| end |
|
|
| defp parse_input_date_range(%{"date_range" => date_range}) do |
| case date_range do |
| "realtime" -> {:ok, :realtime} |
| "realtime_30m" -> {:ok, :realtime_30m} |
| date_range -> ApiQueryParser.parse_input_date_range(date_range) |
| end |
| end |
|
|
| defp parse_input_date_range(_) do |
| {:error, |
| %QueryError{code: :invalid_date_range, message: "Required 'date_range' parameter missing"}} |
| end |
|
|
| defp parse_relative_date(%{"relative_date" => date}) when is_binary(date) do |
| case Date.from_iso8601(date) do |
| {:ok, date} -> |
| {:ok, date} |
|
|
| _ -> |
| {:error, |
| %QueryError{code: :invalid_relative_date, message: "Failed to convert '#{date}' to date"}} |
| end |
| end |
|
|
| defp parse_relative_date(_), do: {:ok, nil} |
|
|
| defp parse_metrics(%{"metrics" => [_ | _] = metrics}) do |
| ApiQueryParser.parse_metrics(metrics) |
| end |
|
|
| defp parse_metrics(_), |
| do: |
| {:error, |
| %QueryError{ |
| code: :invalid_metrics, |
| message: "Expected at least one valid metric in 'metrics'" |
| }} |
|
|
| defp parse_include(params) do |
| with {:ok, compare} <- parse_include_compare(params["include"]) do |
| {:ok, |
| %QueryInclude{ |
| imports: params["include"]["imports"] == true, |
| imports_meta: params["include"]["imports_meta"] == true, |
| compare: compare, |
| compare_match_day_of_week: params["include"]["compare_match_day_of_week"] == true, |
| time_labels: params["include"]["time_labels"] == true, |
| partial_time_labels: params["include"]["partial_time_labels"] == true, |
| present_index: params["include"]["present_index"] == true, |
| empty_metrics: params["include"]["empty_metrics"] == true, |
| trim_relative_date_range: true, |
| drop_unavailable_time_on_page: true, |
| drop_unavailable_revenue_metrics: true |
| }} |
| end |
| end |
|
|
| defp parse_include_compare(%{"compare" => compare}) |
| when compare in @valid_comparison_shorthand_keys do |
| {:ok, @valid_comparison_shorthands[compare]} |
| end |
|
|
| defp parse_include_compare(%{"compare" => [from, to] = compare}) |
| when is_binary(from) and is_binary(to) do |
| case ApiQueryParser.parse_date_strings(from, to) do |
| {:ok, compare} -> |
| {:ok, compare} |
|
|
| {:error, _} -> |
| {:error, |
| %QueryError{ |
| code: :invalid_include, |
| message: "Invalid include.compare '#{inspect(compare)}'" |
| }} |
| end |
| end |
|
|
| defp parse_include_compare(%{"compare" => compare}) when not is_nil(compare) do |
| {:error, |
| %QueryError{code: :invalid_include, message: "Invalid include.compare '#{inspect(compare)}'"}} |
| end |
|
|
| defp parse_include_compare(_) do |
| {:ok, nil} |
| end |
| end |
|
|