Skip to content

Commit 7a11f5e

Browse files
RobertJoonaszoldar
andauthored
Refactor building the Query struct (#5893)
* rename Query.build -> Query.parse_and_build * rename two test files and move 4 %Query{} building functions into subfolder * rename StatsAPIFilterParser to LegacyStatsAPIFilterParser * rename Filters.QueryParser to QueryParser * turn QueryParserTest into QueryParseAndBuildTest * move query_parser.ex out of filters directory * separate build from parse * disable sample_threshold in the new intermediate build function, for now * remove now redundant test util functions * remove unused import * address todo from earlier * credo * Make module names in sync with paths in tests --------- Co-authored-by: Adrian Gruntkowski <[email protected]>
1 parent 6d5951f commit 7a11f5e

25 files changed

+3615
-3186
lines changed

extra/lib/plausible/stats/consolidated_view.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ defmodule Plausible.Stats.ConsolidatedView do
5656
|> DateTime.to_iso8601()
5757

5858
stats_query =
59-
Stats.Query.build!(view, :internal, %{
59+
Stats.Query.parse_and_build!(view, :internal, %{
6060
"site_id" => view.domain,
6161
"metrics" => ["visitors", "visits", "pageviews", "views_per_visit"],
6262
"include" => %{"comparisons" => %{"mode" => "custom", "date_range" => [c_from, c_to]}},
@@ -91,7 +91,7 @@ defmodule Plausible.Stats.ConsolidatedView do
9191

9292
defp query_24h_intervals(view, now) do
9393
graph_query =
94-
Stats.Query.build!(
94+
Stats.Query.parse_and_build!(
9595
view,
9696
:internal,
9797
%{

extra/lib/plausible_web/live/funnel_settings/form.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ defmodule PlausibleWeb.Live.FunnelSettings.Form do
349349
)
350350

351351
query =
352-
Plausible.Stats.Query.build!(
352+
Plausible.Stats.Query.parse_and_build!(
353353
site,
354354
:internal,
355355
%{

lib/plausible/segments/filters.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ defmodule Plausible.Segments.Filters do
33
This module contains functions that enable resolving segments in filters.
44
"""
55
alias Plausible.Segments
6-
alias Plausible.Stats.Filters
6+
alias Plausible.Stats.{Filters, QueryParser}
77

88
@max_segment_filters_count 10
99

@@ -48,7 +48,7 @@ defmodule Plausible.Segments.Filters do
4848
segments,
4949
%{},
5050
fn %Segments.Segment{id: id, segment_data: segment_data} ->
51-
case Filters.QueryParser.parse_filters(segment_data["filters"]) do
51+
case QueryParser.parse_filters(segment_data["filters"]) do
5252
{:ok, filters} -> {id, filters}
5353
_ -> {id, nil}
5454
end

lib/plausible/segments/segment.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ defmodule Plausible.Segments.Segment do
131131
"""
132132
def build_naive_query_from_segment_data(%Plausible.Site{} = site, filters),
133133
do:
134-
Plausible.Stats.Query.build(
134+
Plausible.Stats.Query.parse_and_build(
135135
site,
136136
:internal,
137137
%{

lib/plausible/stats/filters/filters.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ defmodule Plausible.Stats.Filters do
44
"""
55

66
alias Plausible.Stats.Query
7-
alias Plausible.Stats.Filters.QueryParser
8-
alias Plausible.Stats.Filters.StatsAPIFilterParser
7+
alias Plausible.Stats.QueryParser
8+
alias Plausible.Stats.Filters.LegacyStatsAPIFilterParser
99

1010
@visit_props [
1111
:source,
@@ -70,7 +70,7 @@ defmodule Plausible.Stats.Filters do
7070
case Jason.decode(filters) do
7171
{:ok, filters} when is_list(filters) -> parse(filters)
7272
{:ok, _} -> []
73-
{:error, err} -> StatsAPIFilterParser.parse_filter_expression(err.data)
73+
{:error, err} -> LegacyStatsAPIFilterParser.parse_filter_expression(err.data)
7474
end
7575
end
7676

lib/plausible/stats/filters/stats_api_filter_parser.ex renamed to lib/plausible/stats/filters/legacy_stats_api_filter_parser.ex

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
defmodule Plausible.Stats.Filters.StatsAPIFilterParser do
2-
@moduledoc false
1+
defmodule Plausible.Stats.Filters.LegacyStatsAPIFilterParser do
2+
@moduledoc """
3+
Parser for legacy filter format used in Stats API v1.
4+
"""
35

46
@non_escaped_pipe_regex ~r/(?<!\\)\|/
57

lib/plausible/stats/goal_suggestions.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ defmodule Plausible.Stats.GoalSuggestions do
4141
from_date = Date.shift(to_date, month: -6)
4242

4343
query =
44-
Plausible.Stats.Query.build!(
44+
Plausible.Stats.Query.parse_and_build!(
4545
site,
4646
:internal,
4747
%{

lib/plausible/stats/legacy/legacy_query_builder.ex

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ defmodule Plausible.Stats.Legacy.QueryBuilder do
88

99
use Plausible
1010

11-
alias Plausible.Stats.{Filters, Interval, Query, DateTimeRange}
11+
alias Plausible.Stats.{Filters, Interval, Query, QueryParser, QueryBuilder, DateTimeRange}
1212

1313
def from(site, params, debug_metadata, now \\ nil) do
1414
now = now || Plausible.Stats.Query.Test.get_fixed_now()
@@ -31,9 +31,9 @@ defmodule Plausible.Stats.Legacy.QueryBuilder do
3131
|> put_consolidated_site_ids(site)
3232
|> put_order_by(params)
3333
|> put_include(site, params)
34-
|> Query.put_comparison_utc_time_range()
34+
|> QueryBuilder.put_comparison_utc_time_range()
3535
|> Query.put_imported_opts(site)
36-
|> Query.set_time_on_page_data(site)
36+
|> QueryBuilder.set_time_on_page_data(site)
3737

3838
on_ee do
3939
query = Plausible.Stats.Sampling.put_threshold(query, site, params)
@@ -68,7 +68,7 @@ defmodule Plausible.Stats.Legacy.QueryBuilder do
6868

6969
defp preload_goals_and_revenue(query, site) do
7070
{preloaded_goals, revenue_warning, revenue_currencies} =
71-
Plausible.Stats.Filters.QueryParser.preload_goals_and_revenue(
71+
Plausible.Stats.QueryBuilder.preload_goals_and_revenue(
7272
site,
7373
query.metrics,
7474
query.filters,
@@ -269,36 +269,37 @@ defmodule Plausible.Stats.Legacy.QueryBuilder do
269269
[{:visitors, :asc}, {"visit:source", :desc}]
270270
"""
271271
def parse_order_by(order_by) do
272-
json_decode(order_by)
273-
|> unwrap([])
274-
|> Filters.QueryParser.parse_order_by()
275-
|> unwrap([])
272+
with true <- is_binary(order_by),
273+
{:ok, order_by} <- JSON.decode(order_by),
274+
{:ok, order_by} <- QueryParser.parse_order_by(order_by) do
275+
order_by
276+
else
277+
_ -> []
278+
end
276279
end
277280

278281
@doc """
279282
### Examples:
280283
iex> QueryBuilder.parse_include(%{}, nil)
281-
QueryParser.default_include()
284+
Plausible.Stats.ParsedQueryParams.default_include()
282285
283286
iex> QueryBuilder.parse_include(%{}, ~s({"total_rows": true}))
284-
Map.merge(QueryParser.default_include(), %{total_rows: true})
287+
Map.merge(Plausible.Stats.ParsedQueryParams.default_include(), %{total_rows: true})
285288
"""
286289
def parse_include(site, include) do
287-
json_decode(include)
288-
|> unwrap(%{})
289-
|> Filters.QueryParser.parse_include(site)
290-
|> unwrap(Filters.QueryParser.default_include())
291-
end
290+
include =
291+
with true <- is_binary(include),
292+
{:ok, include} <- JSON.decode(include),
293+
{:ok, include} <- QueryParser.parse_include(include, site) do
294+
include
295+
else
296+
_ -> %{}
297+
end
292298

293-
defp json_decode(string) when is_binary(string) do
294-
Jason.decode(string)
299+
Plausible.Stats.ParsedQueryParams.default_include()
300+
|> Map.merge(include)
295301
end
296302

297-
defp json_decode(_other), do: :error
298-
299-
defp unwrap({:ok, result}, _default), do: result
300-
defp unwrap(_, default), do: default
301-
302303
defp put_order_by(query, %{} = params) do
303304
struct!(query, order_by: parse_order_by(params["order_by"]))
304305
end
@@ -342,7 +343,7 @@ defmodule Plausible.Stats.Legacy.QueryBuilder do
342343

343344
def parse_comparison_params(site, %{"comparison" => "custom"} = params) do
344345
{:ok, date_range} =
345-
Filters.QueryParser.parse_date_range_pair(site, [
346+
QueryParser.parse_date_range_pair(site, [
346347
params["compare_from"],
347348
params["compare_to"]
348349
])
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
defmodule Plausible.Stats.ParsedQueryParams do
2+
@moduledoc false
3+
4+
defstruct [
5+
:now,
6+
:utc_time_range,
7+
:metrics,
8+
:filters,
9+
:dimensions,
10+
:order_by,
11+
:pagination,
12+
:include
13+
]
14+
15+
alias Plausible.Stats.DateTimeRange
16+
17+
@default_include %{
18+
imports: false,
19+
# `include.imports_meta` can be true even when `include.imports`
20+
# is false. Even if we don't want to include imported data, we
21+
# might still want to know whether imported data can be toggled
22+
# on/off on the dashboard.
23+
imports_meta: false,
24+
time_labels: false,
25+
total_rows: false,
26+
trim_relative_date_range: false,
27+
comparisons: nil,
28+
legacy_time_on_page_cutoff: nil
29+
}
30+
31+
def default_include(), do: @default_include
32+
33+
@default_pagination %{
34+
limit: 10_000,
35+
offset: 0
36+
}
37+
38+
def default_pagination(), do: @default_pagination
39+
40+
def new!(params) when is_map(params) do
41+
%DateTimeRange{} = utc_time_range = Map.fetch!(params, :utc_time_range)
42+
[_ | _] = metrics = Map.fetch!(params, :metrics)
43+
44+
%__MODULE__{
45+
now: params[:now],
46+
utc_time_range: utc_time_range,
47+
metrics: metrics,
48+
filters: params[:filters] || [],
49+
dimensions: params[:dimensions] || [],
50+
order_by: params[:order_by],
51+
pagination: Map.merge(@default_pagination, params[:pagination] || %{}),
52+
include: Map.merge(@default_include, params[:include] || %{})
53+
}
54+
end
55+
end

lib/plausible/stats/query.ex

Lines changed: 18 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ defmodule Plausible.Stats.Query do
1818
timezone: nil,
1919
legacy_breakdown: false,
2020
preloaded_goals: [],
21-
include: Plausible.Stats.Filters.QueryParser.default_include(),
21+
include: Plausible.Stats.ParsedQueryParams.default_include(),
2222
debug_metadata: %{},
2323
pagination: nil,
2424
# Revenue metric specific metadata
@@ -34,45 +34,40 @@ defmodule Plausible.Stats.Query do
3434
smear_session_metrics: false
3535

3636
require OpenTelemetry.Tracer, as: Tracer
37-
alias Plausible.Stats.{DateTimeRange, Filters, Imported, Legacy, Comparisons}
37+
38+
alias Plausible.Stats.{
39+
DateTimeRange,
40+
Imported,
41+
Legacy,
42+
Comparisons,
43+
QueryParser,
44+
ParsedQueryParams,
45+
QueryBuilder
46+
}
3847

3948
@type t :: %__MODULE__{}
4049

41-
def build(
50+
def parse_and_build(
4251
%Plausible.Site{domain: domain} = site,
4352
schema_type,
4453
%{"site_id" => domain} = params,
4554
debug_metadata \\ %{}
4655
) do
47-
with {:ok, query_data} <- Filters.QueryParser.parse(site, schema_type, params) do
48-
query =
49-
%__MODULE__{
50-
debug_metadata: debug_metadata,
51-
site_id: site.id,
52-
site_native_stats_start_at: site.native_stats_start_at
53-
}
54-
|> struct!(Map.to_list(query_data))
55-
|> set_time_on_page_data(site)
56-
|> put_comparison_utc_time_range()
57-
|> put_imported_opts(site)
58-
59-
on_ee do
60-
query = Plausible.Stats.Sampling.put_threshold(query, site, params)
61-
end
62-
63-
{:ok, query}
56+
with {:ok, %ParsedQueryParams{} = parsed_query_params} <-
57+
QueryParser.parse(site, schema_type, params) do
58+
QueryBuilder.build(site, parsed_query_params, params, debug_metadata)
6459
end
6560
end
6661

67-
def build!(site, schema_type, params, debug_metadata \\ %{}) do
68-
case build(site, schema_type, params, debug_metadata) do
62+
def parse_and_build!(site, schema_type, params, debug_metadata \\ %{}) do
63+
case parse_and_build(site, schema_type, params, debug_metadata) do
6964
{:ok, query} -> query
7065
{:error, reason} -> raise "Failed to build query: #{inspect(reason)}"
7166
end
7267
end
7368

7469
@doc """
75-
Builds query from old-style stats APIv1 params. New code should use `Query.build`.
70+
Builds query from old-style stats APIv1 params. New code should use `Query.parse_and_build`.
7671
"""
7772
def from(site, params, debug_metadata \\ %{}, now \\ nil) do
7873
Legacy.QueryBuilder.from(site, params, debug_metadata, now)
@@ -143,13 +138,6 @@ defmodule Plausible.Stats.Query do
143138
put_imported_opts(query, nil)
144139
end
145140

146-
def put_comparison_utc_time_range(%__MODULE__{include: %{comparisons: nil}} = query), do: query
147-
148-
def put_comparison_utc_time_range(%__MODULE__{include: %{comparisons: comparison_opts}} = query) do
149-
datetime_range = Comparisons.get_comparison_utc_time_range(query, comparison_opts)
150-
struct!(query, comparison_utc_time_range: datetime_range)
151-
end
152-
153141
def put_imported_opts(query, site) do
154142
requested? = query.include.imports
155143

@@ -190,15 +178,6 @@ defmodule Plausible.Stats.Query do
190178
in_comparison_range ++ in_range
191179
end
192180

193-
def set_time_on_page_data(query, site) do
194-
struct!(query,
195-
time_on_page_data: %{
196-
new_metric_visible: Plausible.Stats.TimeOnPage.new_time_on_page_visible?(site),
197-
cutoff_date: site.legacy_time_on_page_cutoff
198-
}
199-
)
200-
end
201-
202181
@spec get_skip_imported_reason(t()) ::
203182
nil | :no_imported_data | :out_of_range | :unsupported_query
204183
def get_skip_imported_reason(query) do

0 commit comments

Comments
 (0)