| defmodule Plausible.Stats.QueryComparisonsTest do |
| use Plausible.DataCase |
| alias Plausible.Stats |
| alias Plausible.Stats.{ParsedQueryParams, QueryBuilder, QueryInclude} |
|
|
| setup [:create_user, :create_site, :create_site_import] |
|
|
| test "aggregates a single metric", %{site: site} do |
| populate_stats(site, [ |
| build(:pageview, timestamp: ~N[2021-01-01 00:00:00]), |
| build(:pageview, timestamp: ~N[2021-01-01 00:25:00]), |
| build(:pageview, timestamp: ~N[2021-01-02 00:00:00]), |
| build(:pageview, timestamp: ~N[2021-01-07 00:00:00]) |
| ]) |
|
|
| assert {:ok, query} = |
| QueryBuilder.build(site, %ParsedQueryParams{ |
| metrics: [:pageviews], |
| input_date_range: {:date_range, ~D[2021-01-07], ~D[2021-01-13]}, |
| include: %QueryInclude{compare: :previous_period} |
| }) |
|
|
| assert %Stats.QueryResult{results: results} = Stats.query(site, query) |
|
|
| assert results == [ |
| %{ |
| dimensions: [], |
| metrics: [1], |
| comparison: %{change: [-67], dimensions: [], metrics: [3]} |
| } |
| ] |
| end |
|
|
| test "timeseries comparison", %{site: site} do |
| populate_stats(site, [ |
| build(:pageview, timestamp: ~N[2021-01-01 00:00:00]), |
| build(:pageview, timestamp: ~N[2021-01-01 00:25:00]), |
| build(:pageview, timestamp: ~N[2021-01-06 00:00:00]), |
| build(:pageview, timestamp: ~N[2021-01-07 00:00:00]), |
| build(:pageview, timestamp: ~N[2021-01-08 00:00:00]) |
| ]) |
|
|
| assert {:ok, query} = |
| QueryBuilder.build(site, %ParsedQueryParams{ |
| metrics: [:pageviews], |
| dimensions: ["time:day"], |
| input_date_range: {:date_range, ~D[2021-01-07], ~D[2021-01-13]}, |
| include: %QueryInclude{compare: :previous_period} |
| }) |
|
|
| assert %Stats.QueryResult{results: results, comparison_results: comparison_results} = |
| Stats.query(site, query) |
|
|
| assert results == [ |
| %{dimensions: ["2021-01-07"], metrics: [1]}, |
| %{dimensions: ["2021-01-08"], metrics: [1]} |
| ] |
|
|
| assert comparison_results == [ |
| %{dimensions: ["2021-01-01"], metrics: [2], change: [-50]}, |
| %{dimensions: ["2021-01-06"], metrics: [1], change: nil} |
| ] |
| end |
|
|
| test "timeseries last 28d period compares the same period with and without match_day_of_week=true", |
| %{ |
| site: site |
| } do |
| now = ~U[2021-06-10 12:00:00Z] |
| today = DateTime.to_date(now) |
|
|
| {:ok, query1} = |
| QueryBuilder.build(site, %ParsedQueryParams{ |
| metrics: [:visitors], |
| dimensions: ["time"], |
| input_date_range: {:last_n_days, 28}, |
| include: %QueryInclude{time_labels: true, compare: :previous_period}, |
| now: now |
| }) |
|
|
| query2 = Stats.Query.set_include(query1, :compare_match_day_of_week, true) |
|
|
| assert %Stats.QueryResult{meta: meta1} = Stats.query(site, query1) |
| assert %Stats.QueryResult{meta: meta2} = Stats.query(site, query2) |
|
|
| assert meta1[:time_labels] == meta2[:time_labels] |
| assert meta1[:comparison_time_labels] == meta2[:comparison_time_labels] |
|
|
| expected_first_date = today |> Date.shift(day: -28) |> Date.to_iso8601() |
| expected_last_date = today |> Date.shift(day: -1) |> Date.to_iso8601() |
| expected_comparison_first_date = today |> Date.shift(day: -56) |> Date.to_iso8601() |
| expected_comparison_last_date = today |> Date.shift(day: -29) |> Date.to_iso8601() |
|
|
| actual_first_date = List.first(meta1[:time_labels]) |
| actual_comparison_first_date = List.first(meta1[:comparison_time_labels]) |
| actual_last_date = List.last(meta1[:time_labels]) |
| actual_comparison_last_date = List.last(meta1[:comparison_time_labels]) |
|
|
| assert actual_first_date == expected_first_date |
| assert actual_last_date == expected_last_date |
| assert actual_comparison_first_date == expected_comparison_first_date |
| assert actual_comparison_last_date == expected_comparison_last_date |
| end |
|
|
| test "timeseries last 91d period in year_over_year comparison", %{ |
| site: site |
| } do |
| populate_stats(site, [ |
| build(:pageview, timestamp: ~N[2021-04-01 00:00:00]), |
| build(:pageview, timestamp: ~N[2021-04-01 00:00:00]), |
| build(:pageview, timestamp: ~N[2021-04-05 00:00:00]), |
| build(:pageview, timestamp: ~N[2021-04-05 00:00:00]), |
| build(:pageview, timestamp: ~N[2021-06-30 00:00:00]), |
| build(:pageview, timestamp: ~N[2022-04-01 00:00:00]), |
| build(:pageview, timestamp: ~N[2022-04-05 00:00:00]), |
| build(:pageview, timestamp: ~N[2022-06-30 00:00:00]), |
| build(:pageview, timestamp: ~N[2022-07-01 00:00:00]) |
| ]) |
|
|
| assert {:ok, query} = |
| QueryBuilder.build(site, %ParsedQueryParams{ |
| metrics: [:visitors], |
| dimensions: ["time:day"], |
| input_date_range: {:last_n_days, 91}, |
| include: %QueryInclude{time_labels: true, compare: :year_over_year}, |
| now: ~U[2022-07-01 14:00:00Z] |
| }) |
|
|
| assert %Stats.QueryResult{ |
| results: results, |
| comparison_results: comparison_results, |
| meta: meta |
| } = Stats.query(site, query) |
|
|
| time_labels = meta[:time_labels] |
|
|
| assert "2022-04-01" = List.first(time_labels) |
| assert "2022-04-05" = Enum.at(time_labels, 4) |
| assert "2022-06-30" = List.last(time_labels) |
|
|
| assert results == [ |
| %{dimensions: ["2022-04-01"], metrics: [1]}, |
| %{dimensions: ["2022-04-05"], metrics: [1]}, |
| %{dimensions: ["2022-06-30"], metrics: [1]} |
| ] |
|
|
| assert comparison_results == [ |
| %{dimensions: ["2021-04-01"], metrics: [2], change: [-50]}, |
| %{dimensions: ["2021-04-05"], metrics: [2], change: [-50]}, |
| %{dimensions: ["2021-06-30"], metrics: [1], change: [0]} |
| ] |
| end |
|
|
| test "dimensional comparison with low limit", %{site: site} do |
| populate_stats(site, [ |
| build(:pageview, browser: "Firefox", timestamp: ~N[2021-01-01 00:00:00]), |
| build(:pageview, browser: "Firefox", timestamp: ~N[2021-01-01 00:00:00]), |
| build(:pageview, browser: "Firefox", timestamp: ~N[2021-01-01 00:00:00]), |
| build(:pageview, browser: "Firefox", timestamp: ~N[2021-01-01 00:00:00]), |
| build(:pageview, browser: "Safari", timestamp: ~N[2021-01-01 00:00:00]), |
| build(:pageview, browser: "Safari", timestamp: ~N[2021-01-01 00:00:00]), |
| build(:pageview, browser: "Safari", timestamp: ~N[2021-01-01 00:00:00]), |
| build(:pageview, browser: "Chrome", timestamp: ~N[2021-01-01 00:00:00]), |
| build(:pageview, browser: "Chrome", timestamp: ~N[2021-01-07 00:00:00]), |
| build(:pageview, browser: "Chrome", timestamp: ~N[2021-01-07 00:00:00]), |
| build(:pageview, browser: "Chrome", timestamp: ~N[2021-01-07 00:00:00]), |
| build(:pageview, browser: "Firefox", timestamp: ~N[2021-01-07 00:00:00]), |
| build(:pageview, browser: "Firefox", timestamp: ~N[2021-01-07 00:00:00]), |
| build(:pageview, browser: "Safari", timestamp: ~N[2021-01-08 00:00:00]) |
| ]) |
|
|
| {:ok, page_one_query} = |
| QueryBuilder.build(site, %ParsedQueryParams{ |
| metrics: [:visitors, :percentage], |
| input_date_range: {:date_range, ~D[2021-01-07], ~D[2021-01-13]}, |
| dimensions: ["visit:browser"], |
| include: %QueryInclude{ |
| compare: :previous_period |
| }, |
| pagination: %{limit: 2, offset: 0} |
| }) |
|
|
| assert %Stats.QueryResult{results: page_one_results} = Stats.query(site, page_one_query) |
|
|
| assert page_one_results == [ |
| %{ |
| dimensions: ["Chrome"], |
| metrics: [3, 50.0], |
| comparison: %{ |
| dimensions: ["Chrome"], |
| metrics: [1, 12.5], |
| change: [200, 300] |
| } |
| }, |
| %{ |
| dimensions: ["Firefox"], |
| metrics: [2, 33.33], |
| comparison: %{ |
| dimensions: ["Firefox"], |
| metrics: [4, 50.0], |
| change: [-50, -33] |
| } |
| } |
| ] |
|
|
| page_two_query = Stats.Query.set(page_one_query, pagination: %{limit: 2, offset: 2}) |
|
|
| assert %Stats.QueryResult{results: page_two_results} = Stats.query(site, page_two_query) |
|
|
| assert page_two_results == [ |
| %{ |
| dimensions: ["Safari"], |
| metrics: [1, 16.67], |
| comparison: %{ |
| dimensions: ["Safari"], |
| metrics: [3, 37.5], |
| change: [-67, -56] |
| } |
| } |
| ] |
| end |
|
|
| test "dimensional comparison with imported data", %{ |
| site: site, |
| site_import: site_import |
| } do |
| populate_stats(site, site_import.id, [ |
| build(:pageview, browser: "Chrome", timestamp: ~N[2021-01-01 00:00:00]), |
| build(:pageview, browser: "Chrome", timestamp: ~N[2021-01-07 00:00:00]), |
| build(:pageview, browser: "Chrome", timestamp: ~N[2021-01-07 00:00:00]), |
| build(:pageview, browser: "Firefox", timestamp: ~N[2021-01-07 00:00:00]), |
| build(:imported_browsers, |
| date: ~D[2021-01-01], |
| browser: "Firefox", |
| browser_version: "121", |
| visitors: 50 |
| ), |
| build(:imported_browsers, |
| date: ~D[2021-01-01], |
| browser: "Chrome", |
| browser_version: "99", |
| visitors: 39 |
| ), |
| build(:imported_browsers, |
| date: ~D[2021-01-01], |
| browser: "Safari", |
| browser_version: "99", |
| visitors: 10 |
| ), |
| build(:imported_visitors, date: ~D[2021-01-01], visitors: 99) |
| ]) |
|
|
| assert {:ok, query} = |
| QueryBuilder.build(site, %ParsedQueryParams{ |
| metrics: [:visitors, :percentage], |
| input_date_range: {:date_range, ~D[2021-01-07], ~D[2021-01-13]}, |
| dimensions: ["visit:browser"], |
| include: %QueryInclude{ |
| imports: true, |
| compare: :previous_period |
| }, |
| pagination: %{limit: 2, offset: 0} |
| }) |
|
|
| assert %Stats.QueryResult{results: results} = Stats.query(site, query) |
|
|
| assert results == [ |
| %{ |
| dimensions: ["Chrome"], |
| metrics: [2, 66.67], |
| comparison: %{ |
| dimensions: ["Chrome"], |
| metrics: [40, 40.0], |
| change: [-95, 67] |
| } |
| }, |
| %{ |
| dimensions: ["Firefox"], |
| metrics: [1, 33.33], |
| comparison: %{ |
| dimensions: ["Firefox"], |
| metrics: [50, 50.0], |
| change: [-98, -33] |
| } |
| } |
| ] |
| end |
|
|
| describe "custom comparison range" do |
| test "can use date range for custom comparison", %{site: site} do |
| populate_stats(site, [ |
| build(:pageview, timestamp: ~N[2021-01-01 00:00:00]), |
| build(:pageview, timestamp: ~N[2021-01-01 00:25:00]), |
| build(:pageview, timestamp: ~N[2021-01-07 00:00:00]) |
| ]) |
|
|
| assert {:ok, query} = |
| QueryBuilder.build(site, %ParsedQueryParams{ |
| metrics: [:pageviews], |
| input_date_range: {:date_range, ~D[2021-01-07], ~D[2021-01-13]}, |
| include: %QueryInclude{compare: {:date_range, ~D[2021-01-01], ~D[2021-01-06]}} |
| }) |
|
|
| assert %Stats.QueryResult{results: results} = Stats.query(site, query) |
|
|
| assert results == [ |
| %{ |
| dimensions: [], |
| metrics: [1], |
| comparison: %{change: [-50], dimensions: [], metrics: [2]} |
| } |
| ] |
| end |
|
|
| test "can use datetime range for custom comparison", %{site: site} do |
| populate_stats(site, [ |
| build(:pageview, timestamp: ~N[2021-01-01 01:00:00]), |
| build(:pageview, user_id: 1, timestamp: ~N[2021-01-01 05:25:00]), |
| build(:pageview, user_id: 1, timestamp: ~N[2021-01-01 05:26:00]), |
| build(:pageview, timestamp: ~N[2021-01-02 04:00:00]), |
| build(:pageview, timestamp: ~N[2021-01-03 02:00:00]) |
| ]) |
|
|
| assert {:ok, query} = |
| QueryBuilder.build(site, %ParsedQueryParams{ |
| metrics: [:visitors, :pageviews], |
| input_date_range: |
| {:datetime_range, ~U[2021-01-02 03:00:00Z], ~U[2021-01-03 02:59:59Z]}, |
| include: %QueryInclude{ |
| compare: {:datetime_range, ~U[2021-01-01 03:00:00Z], ~U[2021-01-02 02:59:59Z]} |
| } |
| }) |
|
|
| assert %Stats.QueryResult{results: results} = Stats.query(site, query) |
|
|
| assert results == [ |
| %{ |
| dimensions: [], |
| metrics: [2, 2], |
| comparison: %{change: [100, 0], dimensions: [], metrics: [1, 2]} |
| } |
| ] |
| end |
|
|
| test "custom datetime range comparison handles timezones correctly", %{user: user} do |
| weird_tz_site = new_site(owner: user, timezone: "America/Havana") |
|
|
| populate_stats(weird_tz_site, [ |
| |
| build(:pageview, timestamp: ~N[2021-01-01 08:00:00]), |
| |
| build(:pageview, timestamp: ~N[2021-01-01 10:25:00]), |
| |
| build(:pageview, timestamp: ~N[2021-01-02 09:00:00]), |
| |
| build(:pageview, timestamp: ~N[2021-01-03 08:00:00]) |
| ]) |
|
|
| assert {:ok, query} = |
| QueryBuilder.build(weird_tz_site, %ParsedQueryParams{ |
| metrics: [:pageviews], |
| input_date_range: |
| {:datetime_range, ~U[2021-01-02 03:00:00Z], ~U[2021-01-03 02:59:59Z]}, |
| include: %QueryInclude{ |
| compare: {:datetime_range, ~U[2021-01-01 03:00:00Z], ~U[2021-01-02 02:59:59Z]} |
| } |
| }) |
|
|
| assert %Stats.QueryResult{results: results} = Stats.query(weird_tz_site, query) |
|
|
| assert results == [ |
| %{ |
| dimensions: [], |
| metrics: [1], |
| comparison: %{change: [-50], dimensions: [], metrics: [2]} |
| } |
| ] |
| end |
| end |
| end |
|
|