File size: 4,256 Bytes
8da2481
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
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