Trim month, year, day periods to local now on main graph (#5698)

* Revert "Revert "Trim `month`, `year`, `day` periods to local now on main graph (#5668)" (#5684)"

This reverts commit 2d11681f25.

* Does not trim for comparisons

* Include the current hour in the trimmed time range
This commit is contained in:
Karl-Aksel Puulmann 2025-09-04 12:13:17 +03:00 committed by GitHub
parent 88fccb6972
commit 9af40a278d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 399 additions and 54 deletions

View File

@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file.
- Custom events can now be marked as non-interactive in events API and tracker script. Events marked as non-interactive are not counted towards bounce rate.
- Ability to leave team via Team Settings > Leave Team
- Stats APIv2 now supports `include.trim_relative_date_range`. This option allows trimming empty values after current time for `day`, `month` and `year` date_range values.
- Properties are now included in full site exports done via Site Settings > Imports & Exports
- Google Search Console integration settings: properties can be dynamically sought
@ -17,10 +18,9 @@ All notable changes to this project will be documented in this file.
### Changed
- A session is now marked as a bounce if it has less than 2 pageviews and no interactive custom events.
- All dropmenus on dashboard are navigable with Tab (used to be a mix between tab and arrow keys), and no two dropmenus can be open at once on the dashboard
- Special path-based events like "404" don't need `event.props.path` to be explicitly defined when tracking: it is set to be the same as `event.pathname` in event ingestion. If it is explicitly defined, it is not overridden for backwards compatibility.
- Main graph no longer shows empty values after current time for `day`, `month` and `year` periods.
### Fixed

View File

@ -193,6 +193,10 @@ export interface QueryApiSchema {
* If set, returns the total number of result rows rows before pagination under `meta.total_rows`
*/
total_rows?: boolean;
/**
* If set and using `day`, `month` or `year` date_ranges, the query will be trimmed to the current date
*/
trim_relative_date_range?: boolean;
comparisons?:
| {
mode: "previous_period" | "year_over_year";

View File

@ -14,6 +14,7 @@ defmodule Plausible.Stats.Filters.QueryParser do
imports_meta: false,
time_labels: false,
total_rows: false,
trim_relative_date_range: false,
comparisons: nil,
legacy_time_on_page_cutoff: nil
}
@ -34,7 +35,7 @@ defmodule Plausible.Stats.Filters.QueryParser do
end
with :ok <- JSONSchema.validate(schema_type, params),
{:ok, date} <- parse_date(site, Map.get(params, "date"), date),
{:ok, date, now} <- parse_date(site, Map.get(params, "date"), date, now),
{:ok, raw_time_range} <-
parse_time_range(site, Map.get(params, "date_range"), date, now),
utc_time_range = raw_time_range |> DateTimeRange.to_timezone("Etc/UTC"),
@ -51,6 +52,8 @@ defmodule Plausible.Stats.Filters.QueryParser do
{preloaded_goals, revenue_warning, revenue_currencies} <-
preload_goals_and_revenue(site, metrics, filters, dimensions),
query = %{
now: now,
input_date_range: Map.get(params, "date_range"),
metrics: metrics,
filters: filters,
utc_time_range: utc_time_range,
@ -197,15 +200,15 @@ defmodule Plausible.Stats.Filters.QueryParser do
{:ok, []}
end
defp parse_date(_site, date_string, _date) when is_binary(date_string) do
defp parse_date(site, date_string, _date, _now) when is_binary(date_string) do
case Date.from_iso8601(date_string) do
{:ok, date} -> {:ok, date}
{:ok, date} -> {:ok, date, DateTime.new!(date, ~T[00:00:00], site.timezone)}
_ -> {:error, "Invalid date '#{date_string}'."}
end
end
defp parse_date(_site, _date_string, date) do
{:ok, date}
defp parse_date(_site, _date_string, date, now) do
{:ok, date, now}
end
defp parse_time_range(_site, date_range, _date, now) when date_range in ["realtime", "30m"] do

View File

@ -19,7 +19,7 @@ defmodule Plausible.Stats.Legacy.QueryBuilder do
site_id: site.id,
site_native_stats_start_at: site.native_stats_start_at
)
|> put_period(site, params)
|> put_input_date_range(site, params)
|> put_timezone(site)
|> put_dimensions(params)
|> put_interval(params)
@ -66,7 +66,7 @@ defmodule Plausible.Stats.Legacy.QueryBuilder do
)
end
defp put_period(%Query{now: now} = query, _site, %{"period" => period})
defp put_input_date_range(%Query{now: now} = query, _site, %{"period" => period})
when period in ["realtime", "30m"] do
duration_minutes =
case period do
@ -80,19 +80,19 @@ defmodule Plausible.Stats.Legacy.QueryBuilder do
datetime_range =
DateTimeRange.new!(first_datetime, last_datetime) |> DateTimeRange.to_timezone("Etc/UTC")
struct!(query, period: period, utc_time_range: datetime_range)
struct!(query, input_date_range: period, utc_time_range: datetime_range)
end
defp put_period(query, site, %{"period" => "day"} = params) do
defp put_input_date_range(query, site, %{"period" => "day"} = params) do
date = parse_single_date(query, params)
datetime_range =
DateTimeRange.new!(date, date, site.timezone) |> DateTimeRange.to_timezone("Etc/UTC")
struct!(query, period: "day", utc_time_range: datetime_range)
struct!(query, input_date_range: "day", utc_time_range: datetime_range)
end
defp put_period(query, site, %{"period" => period} = params)
defp put_input_date_range(query, site, %{"period" => period} = params)
when period in ["7d", "28d", "30d", "91d"] do
{days, "d"} = Integer.parse(period)
@ -103,10 +103,10 @@ defmodule Plausible.Stats.Legacy.QueryBuilder do
DateTimeRange.new!(start_date, end_date, site.timezone)
|> DateTimeRange.to_timezone("Etc/UTC")
struct!(query, period: period, utc_time_range: datetime_range)
struct!(query, input_date_range: period, utc_time_range: datetime_range)
end
defp put_period(query, site, %{"period" => "month"} = params) do
defp put_input_date_range(query, site, %{"period" => "month"} = params) do
date = parse_single_date(query, params)
start_date = Date.beginning_of_month(date)
end_date = Date.end_of_month(date)
@ -115,10 +115,10 @@ defmodule Plausible.Stats.Legacy.QueryBuilder do
DateTimeRange.new!(start_date, end_date, site.timezone)
|> DateTimeRange.to_timezone("Etc/UTC")
struct!(query, period: "month", utc_time_range: datetime_range)
struct!(query, input_date_range: "month", utc_time_range: datetime_range)
end
defp put_period(query, site, %{"period" => "6mo"} = params) do
defp put_input_date_range(query, site, %{"period" => "6mo"} = params) do
end_date =
parse_single_date(query, params)
|> Date.shift(month: -1)
@ -132,10 +132,10 @@ defmodule Plausible.Stats.Legacy.QueryBuilder do
DateTimeRange.new!(start_date, end_date, site.timezone)
|> DateTimeRange.to_timezone("Etc/UTC")
struct!(query, period: "6mo", utc_time_range: datetime_range)
struct!(query, input_date_range: "6mo", utc_time_range: datetime_range)
end
defp put_period(query, site, %{"period" => "12mo"} = params) do
defp put_input_date_range(query, site, %{"period" => "12mo"} = params) do
end_date =
parse_single_date(query, params)
|> Date.shift(month: -1)
@ -149,10 +149,10 @@ defmodule Plausible.Stats.Legacy.QueryBuilder do
DateTimeRange.new!(start_date, end_date, site.timezone)
|> DateTimeRange.to_timezone("Etc/UTC")
struct!(query, period: "12mo", utc_time_range: datetime_range)
struct!(query, input_date_range: "12mo", utc_time_range: datetime_range)
end
defp put_period(query, site, %{"period" => "year"} = params) do
defp put_input_date_range(query, site, %{"period" => "year"} = params) do
end_date =
parse_single_date(query, params)
|> Timex.end_of_year()
@ -163,29 +163,33 @@ defmodule Plausible.Stats.Legacy.QueryBuilder do
DateTimeRange.new!(start_date, end_date, site.timezone)
|> DateTimeRange.to_timezone("Etc/UTC")
struct!(query, period: "year", utc_time_range: datetime_range)
struct!(query, input_date_range: "year", utc_time_range: datetime_range)
end
defp put_period(query, site, %{"period" => "all"}) do
defp put_input_date_range(query, site, %{"period" => "all"}) do
today = today(query)
start_date = Plausible.Sites.stats_start_date(site) || today
datetime_range =
DateTimeRange.new!(start_date, today, site.timezone) |> DateTimeRange.to_timezone("Etc/UTC")
struct!(query, period: "all", utc_time_range: datetime_range)
struct!(query, input_date_range: "all", utc_time_range: datetime_range)
end
defp put_period(query, site, %{"period" => "custom", "from" => from, "to" => to} = params) do
defp put_input_date_range(
query,
site,
%{"period" => "custom", "from" => from, "to" => to} = params
) do
new_params =
params
|> Map.drop(["from", "to"])
|> Map.put("date", Enum.join([from, to], ","))
put_period(query, site, new_params)
put_input_date_range(query, site, new_params)
end
defp put_period(query, site, %{"period" => "custom", "date" => date}) do
defp put_input_date_range(query, site, %{"period" => "custom", "date" => date}) do
[from, to] = String.split(date, ",")
from_date = Date.from_iso8601!(String.trim(from))
to_date = Date.from_iso8601!(String.trim(to))
@ -194,11 +198,11 @@ defmodule Plausible.Stats.Legacy.QueryBuilder do
DateTimeRange.new!(from_date, to_date, site.timezone)
|> DateTimeRange.to_timezone("Etc/UTC")
struct!(query, period: "custom", utc_time_range: datetime_range)
struct!(query, input_date_range: "custom", utc_time_range: datetime_range)
end
defp put_period(query, site, params) do
put_period(query, site, Map.merge(params, %{"period" => "30d"}))
defp put_input_date_range(query, site, params) do
put_input_date_range(query, site, Map.merge(params, %{"period" => "30d"}))
end
defp put_timezone(query, site) do
@ -283,13 +287,13 @@ defmodule Plausible.Stats.Legacy.QueryBuilder do
struct!(query, order_by: parse_order_by(params["order_by"]))
end
defp put_interval(%{:period => "all"} = query, params) do
defp put_interval(%{:input_date_range => "all"} = query, params) do
interval = Map.get(params, "interval", Interval.default_for_date_range(query.utc_time_range))
struct!(query, interval: interval)
end
defp put_interval(query, params) do
interval = Map.get(params, "interval", Interval.default_for_period(query.period))
interval = Map.get(params, "interval", Interval.default_for_period(query.input_date_range))
struct!(query, interval: interval)
end

View File

@ -4,7 +4,7 @@ defmodule Plausible.Stats.Query do
defstruct utc_time_range: nil,
comparison_utc_time_range: nil,
interval: nil,
period: nil,
input_date_range: nil,
dimensions: [],
filters: [],
sample_threshold: 20_000_000,
@ -39,7 +39,6 @@ defmodule Plausible.Stats.Query do
with {:ok, query_data} <- Filters.QueryParser.parse(site, schema_type, params) do
query =
%__MODULE__{
now: DateTime.utc_now(:second),
debug_metadata: debug_metadata,
site_id: site.id,
site_native_stats_start_at: site.native_stats_start_at
@ -157,7 +156,7 @@ defmodule Plausible.Stats.Query do
)
end
defp get_imports_in_range(_site, %__MODULE__{period: period})
defp get_imports_in_range(_site, %__MODULE__{input_date_range: period})
when period in ["realtime", "30m"] do
[]
end
@ -219,7 +218,6 @@ defmodule Plausible.Stats.Query do
Tracer.set_attributes([
{"plausible.query.interval", query.interval},
{"plausible.query.period", query.period},
{"plausible.query.dimensions", query.dimensions |> Enum.join(";")},
{"plausible.query.include_imported", query.include_imported},
{"plausible.query.filter_keys", filter_dimensions},

View File

@ -25,6 +25,7 @@ defmodule Plausible.Stats.QueryOptimizer do
3. Updating "time" dimension in order_by to the right granularity
4. Updates event:hostname filters to also apply on visit level for sane results.
5. Removes revenue metrics from dashboard queries if not requested, present or unavailable for the site.
6. Trims the date range to the current time if query.include.trim_relative_date_range is true.
"""
def optimize(query) do
@ -60,7 +61,8 @@ defmodule Plausible.Stats.QueryOptimizer do
&update_time_in_order_by/1,
&extend_hostname_filters_to_visit/1,
&remove_revenue_metrics_if_unavailable/1,
&set_time_on_page_data/1
&set_time_on_page_data/1,
&trim_relative_date_range/1
]
end
@ -231,4 +233,74 @@ defmodule Plausible.Stats.QueryOptimizer do
)
end
end
defp trim_relative_date_range(%Query{include: %{trim_relative_date_range: true}} = query) do
# This is here to trim future bucket labels on the main graph
if should_trim_date_range?(query) do
trimmed_range = trim_date_range_to_now(query)
%Query{query | utc_time_range: trimmed_range}
else
query
end
end
defp trim_relative_date_range(query), do: query
defp should_trim_date_range?(%Query{include: %{comparisons: comparison_opts}})
when is_map(comparison_opts),
do: false
defp should_trim_date_range?(%Query{input_date_range: "month"} = query) do
today = query.now |> DateTime.shift_zone!(query.timezone) |> DateTime.to_date()
date_range = Query.date_range(query)
current_month_start = Date.beginning_of_month(today)
current_month_end = Date.end_of_month(today)
date_range.first == current_month_start and date_range.last == current_month_end
end
defp should_trim_date_range?(%Query{input_date_range: "year"} = query) do
today = query.now |> DateTime.shift_zone!(query.timezone) |> DateTime.to_date()
date_range = Query.date_range(query)
current_year_start = Date.new!(today.year, 1, 1)
current_year_end = Date.new!(today.year, 12, 31)
date_range.first == current_year_start and date_range.last == current_year_end
end
defp should_trim_date_range?(%Query{input_date_range: "day"} = query) do
today = query.now |> DateTime.shift_zone!(query.timezone) |> DateTime.to_date()
date_range = Query.date_range(query)
date_range.first == today and date_range.last == today
end
defp should_trim_date_range?(_query), do: false
defp trim_date_range_to_now(query) do
if query.input_date_range == "day" do
time_range = query.utc_time_range |> DateTimeRange.to_timezone(query.timezone)
current_hour =
query.now
|> DateTime.shift_zone!(query.timezone)
|> Map.merge(%{minute: 59, second: 59, millisecond: 999})
time_range.first
|> DateTimeRange.new!(current_hour)
|> DateTimeRange.to_timezone("Etc/UTC")
else
date_range = Query.date_range(query)
today = query.now |> DateTime.shift_zone!(query.timezone) |> DateTime.to_date()
trimmed_to_date =
Enum.min([date_range.last, today], Date)
date_range.first
|> DateTimeRange.new!(trimmed_to_date, query.timezone)
|> DateTimeRange.to_timezone("Etc/UTC")
end
end
end

View File

@ -97,7 +97,7 @@ defmodule Plausible.Stats.Timeseries do
end)
end
defp transform_realtime_labels(results, %Query{period: "30m"}) do
defp transform_realtime_labels(results, %Query{input_date_range: "30m"}) do
Enum.with_index(results)
|> Enum.map(fn {entry, index} -> %{entry | date: -30 + index} end)
end

View File

@ -101,6 +101,7 @@ defmodule PlausibleWeb.Api.StatsController do
:ok <- validate_interval_granularity(site, params, dates),
params <- realtime_period_to_30m(params),
query = Query.from(site, params, debug_metadata(conn)),
query <- Query.set_include(query, :trim_relative_date_range, true),
{:ok, metric} <- parse_and_validate_graph_metric(params, query) do
{timeseries_result, comparison_result, _meta} = Stats.timeseries(site, query, [metric])
@ -281,10 +282,10 @@ defmodule PlausibleWeb.Api.StatsController do
toplevel_goal_filter?(query)
cond do
query.period == "30m" && goal_filter? ->
query.input_date_range == "30m" && goal_filter? ->
fetch_goal_realtime_top_stats(site, query)
query.period == "30m" ->
query.input_date_range == "30m" ->
fetch_realtime_top_stats(site, query)
goal_filter? ->
@ -580,7 +581,7 @@ defmodule PlausibleWeb.Api.StatsController do
Filters.filtering_on_dimension?(query, "event:page") ->
{:error, {:invalid_funnel_query, "pages"}}
query.period == "realtime" ->
query.input_date_range == "realtime" ->
{:error, {:invalid_funnel_query, "realtime period"}}
true ->

View File

@ -63,6 +63,11 @@
"default": false,
"description": "If set, returns the total number of result rows rows before pagination under `meta.total_rows`"
},
"trim_relative_date_range": {
"type": "boolean",
"default": false,
"description": "If set and using `day`, `month` or `year` date_ranges, the query will be trimmed to the current date"
},
"comparisons": {
"$comment": "only :internal",
"type": "object",

View File

@ -152,4 +152,195 @@ defmodule Plausible.Stats.QueryOptimizerTest do
]
end
end
describe "trim_relative_date_range" do
alias Plausible.Stats.Filters.QueryParser
test "trims current month period when flag is set" do
now = DateTime.new!(~D[2024-01-15], ~T[12:00:00], "UTC")
result =
perform(%{
utc_time_range: DateTimeRange.new!(~D[2024-01-01], ~D[2024-01-31], "UTC"),
input_date_range: "month",
now: now,
timezone: "UTC",
include: Map.put(QueryParser.default_include(), :trim_relative_date_range, true)
})
assert result.utc_time_range.first == ~U[2024-01-01 00:00:00Z]
assert result.utc_time_range.last == ~U[2024-01-15 23:59:59Z]
end
test "trims current year period when flag is set" do
now = DateTime.new!(~D[2024-03-15], ~T[12:00:00], "UTC")
result =
perform(%{
utc_time_range: DateTimeRange.new!(~D[2024-01-01], ~D[2024-12-31], "UTC"),
input_date_range: "year",
now: now,
timezone: "UTC",
include: Map.put(QueryParser.default_include(), :trim_relative_date_range, true)
})
assert result.utc_time_range.first == ~U[2024-01-01 00:00:00Z]
assert result.utc_time_range.last == ~U[2024-03-15 23:59:59Z]
end
test "trims current day period to current hour when flag is set" do
now = DateTime.new!(~D[2024-01-15], ~T[14:30:00], "UTC")
result =
perform(%{
utc_time_range: DateTimeRange.new!(~D[2024-01-15], ~D[2024-01-15], "UTC"),
input_date_range: "day",
now: now,
timezone: "UTC",
include: Map.put(QueryParser.default_include(), :trim_relative_date_range, true)
})
assert result.utc_time_range.first == ~U[2024-01-15 00:00:00Z]
assert result.utc_time_range.last == ~U[2024-01-15 14:59:59Z]
end
test "does not trim historical month periods even when flag is set" do
now = DateTime.new!(~D[2024-01-15], ~T[12:00:00], "UTC")
original_range = DateTimeRange.new!(~D[2023-06-01], ~D[2023-06-30], "UTC")
result =
perform(%{
utc_time_range: original_range,
input_date_range: "month",
now: now,
timezone: "UTC",
include: Map.put(QueryParser.default_include(), :trim_relative_date_range, true)
})
assert result.utc_time_range == original_range
end
test "does not trim historical year periods even when flag is set" do
now = DateTime.new!(~D[2024-03-15], ~T[12:00:00], "UTC")
original_range = DateTimeRange.new!(~D[2023-01-01], ~D[2023-12-31], "UTC")
result =
perform(%{
utc_time_range: original_range,
input_date_range: "year",
now: now,
timezone: "UTC",
include: Map.put(QueryParser.default_include(), :trim_relative_date_range, true)
})
assert result.utc_time_range == original_range
end
test "does not trim historical day periods even when flag is set" do
now = DateTime.new!(~D[2024-01-15], ~T[14:30:00], "UTC")
original_range = DateTimeRange.new!(~D[2024-01-10], ~D[2024-01-10], "UTC")
result =
perform(%{
utc_time_range: original_range,
input_date_range: "day",
now: now,
timezone: "UTC",
include: Map.put(QueryParser.default_include(), :trim_relative_date_range, true)
})
assert result.utc_time_range == original_range
end
test "does not trim when comparisons are set" do
now = DateTime.new!(~D[2024-01-15], ~T[12:00:00], "UTC")
original_range = DateTimeRange.new!(~D[2024-01-01], ~D[2024-01-31], "UTC")
result =
perform(%{
utc_time_range: original_range,
input_date_range: "day",
now: now,
timezone: "UTC",
include:
Map.merge(
QueryParser.default_include(),
%{comparisons: %{mode: "previous_period"}, trim_relative_date_range: true}
)
})
assert result.utc_time_range == original_range
end
test "does not trim when flag is false" do
now = DateTime.new!(~D[2024-01-15], ~T[12:00:00], "UTC")
original_range = DateTimeRange.new!(~D[2024-01-01], ~D[2024-01-31], "UTC")
result =
perform(%{
utc_time_range: original_range,
input_date_range: "month",
now: now,
timezone: "UTC",
include: Map.put(QueryParser.default_include(), :trim_relative_date_range, false)
})
assert result.utc_time_range == original_range
end
test "does not trim when flag is not set" do
now = DateTime.new!(~D[2024-01-15], ~T[12:00:00], "UTC")
original_range = DateTimeRange.new!(~D[2024-01-01], ~D[2024-01-31], "UTC")
result =
perform(%{
utc_time_range: original_range,
input_date_range: "month",
now: now,
timezone: "UTC",
include: QueryParser.default_include()
})
assert result.utc_time_range == original_range
end
test "does not trim non-current periods like custom date ranges" do
now = DateTime.new!(~D[2024-01-15], ~T[12:00:00], "UTC")
original_range = DateTimeRange.new!(~D[2024-01-10], ~D[2024-01-16], "UTC")
result =
perform(%{
utc_time_range: original_range,
input_date_range: "7d",
now: now,
timezone: "UTC",
include: Map.put(QueryParser.default_include(), :trim_relative_date_range, true)
})
assert result.utc_time_range == original_range
end
test "handles timezone correctly when trimming year periods" do
now = DateTime.new!(~D[2024-03-15], ~T[12:00:00], "UTC")
result =
perform(%{
utc_time_range:
DateTimeRange.new!(~D[2024-01-01], ~D[2024-12-31], "America/New_York")
|> DateTimeRange.to_timezone("Etc/UTC"),
input_date_range: "year",
now: now,
timezone: "America/New_York",
include: Map.put(QueryParser.default_include(), :trim_relative_date_range, true)
})
nyc_mar_15_end =
DateTimeRange.new!(~D[2024-03-15], ~D[2024-03-15], "America/New_York")
|> DateTimeRange.to_timezone("Etc/UTC")
|> Map.get(:last)
assert result.utc_time_range.last == nyc_mar_15_end
end
end
end

View File

@ -62,14 +62,24 @@ defmodule Plausible.Stats.Filters.QueryParserTest do
time_labels: false,
total_rows: false,
comparisons: nil,
legacy_time_on_page_cutoff: nil
legacy_time_on_page_cutoff: nil,
trim_relative_date_range: false
}
def check_success(params, site, expected_result, schema_type \\ :public) do
assert {:ok, result} = parse(site, schema_type, params, @now)
return_value = Map.take(result, [:preloaded_goals, :revenue_warning, :revenue_currencies])
result = Map.drop(result, [:preloaded_goals, :revenue_warning, :revenue_currencies])
result =
Map.drop(result, [
:now,
:input_date_range,
:preloaded_goals,
:revenue_warning,
:revenue_currencies
])
assert result == expected_result
return_value
@ -871,7 +881,8 @@ defmodule Plausible.Stats.Filters.QueryParserTest do
time_labels: true,
total_rows: true,
comparisons: nil,
legacy_time_on_page_cutoff: nil
legacy_time_on_page_cutoff: nil,
trim_relative_date_range: false
},
pagination: %{limit: 10_000, offset: 0}
})
@ -936,7 +947,8 @@ defmodule Plausible.Stats.Filters.QueryParserTest do
imports_meta: false,
time_labels: false,
total_rows: false,
legacy_time_on_page_cutoff: nil
legacy_time_on_page_cutoff: nil,
trim_relative_date_range: false
},
pagination: %{limit: 10_000, offset: 0}
},
@ -968,7 +980,8 @@ defmodule Plausible.Stats.Filters.QueryParserTest do
imports_meta: false,
time_labels: false,
total_rows: false,
legacy_time_on_page_cutoff: nil
legacy_time_on_page_cutoff: nil,
trim_relative_date_range: false
},
pagination: %{limit: 10_000, offset: 0}
},
@ -1003,7 +1016,8 @@ defmodule Plausible.Stats.Filters.QueryParserTest do
imports: false,
time_labels: false,
total_rows: false,
legacy_time_on_page_cutoff: nil
legacy_time_on_page_cutoff: nil,
trim_relative_date_range: false
},
pagination: %{limit: 10_000, offset: 0}
},

View File

@ -58,7 +58,7 @@ defmodule Plausible.Stats.QueryTest do
assert q.utc_time_range.first == ~U[2024-05-03 16:25:00Z]
assert q.utc_time_range.last == ~U[2024-05-03 16:30:05Z]
assert q.period == "realtime"
assert q.input_date_range == "realtime"
end
test "parses month format", %{site: site} do
@ -98,7 +98,7 @@ defmodule Plausible.Stats.QueryTest do
assert q.utc_time_range.first == ~U[2020-01-01 05:00:00Z]
assert q.utc_time_range.last == ~U[2024-05-04 03:59:59Z]
assert q.period == "all"
assert q.input_date_range == "all"
assert q.interval == "month"
end
@ -116,7 +116,7 @@ defmodule Plausible.Stats.QueryTest do
assert q.utc_time_range.first == ~U[2024-05-03 04:00:00Z]
assert q.utc_time_range.last == ~U[2024-05-04 03:59:59Z]
assert q.period == "all"
assert q.input_date_range == "all"
assert q.interval == "hour"
end
@ -126,7 +126,7 @@ defmodule Plausible.Stats.QueryTest do
assert q.utc_time_range.first == ~U[2024-05-03 04:00:00Z]
assert q.utc_time_range.last == ~U[2024-05-04 03:59:59Z]
assert q.period == "all"
assert q.input_date_range == "all"
assert q.interval == "hour"
end
@ -138,7 +138,7 @@ defmodule Plausible.Stats.QueryTest do
assert q.utc_time_range.first == ~U[2024-05-02 04:00:00Z]
assert q.utc_time_range.last == ~U[2024-05-04 03:59:59Z]
assert q.period == "all"
assert q.input_date_range == "all"
assert q.interval == "day"
end
@ -150,7 +150,7 @@ defmodule Plausible.Stats.QueryTest do
assert q.utc_time_range.first == ~U[2024-04-03 04:00:00Z]
assert q.utc_time_range.last == ~U[2024-05-04 03:59:59Z]
assert q.period == "all"
assert q.input_date_range == "all"
assert q.interval == "month"
end
@ -162,7 +162,7 @@ defmodule Plausible.Stats.QueryTest do
assert q.utc_time_range.first == ~U[2024-04-03 04:00:00Z]
assert q.utc_time_range.last == ~U[2024-05-04 03:59:59Z]
assert q.period == "all"
assert q.input_date_range == "all"
assert q.interval == "week"
end

View File

@ -169,6 +169,31 @@ defmodule PlausibleWeb.Api.ExternalStatsController.QueryComparisonsTest do
assert actual_comparison_last_date == expected_comparison_last_date
end
test "regression: does not trim when trim_relative_date_range is true", %{
conn: conn,
site: site
} do
conn =
post(conn, "/api/v2/query-internal-test", %{
"site_id" => site.domain,
"metrics" => ["visitors"],
"date_range" => "month",
"date" => "2021-01-15",
"dimensions" => ["time:day"],
"include" => %{
"trim_relative_date_range" => true,
"comparisons" => %{
"mode" => "previous_period"
}
}
})
assert json_response(conn, 200)["query"]["date_range"] == [
"2021-01-01T00:00:00+00:00",
"2021-01-31T23:59:59+00:00"
]
end
test "timeseries last 91d period in year_over_year comparison", %{
conn: conn,
site: site

View File

@ -1481,6 +1481,34 @@ defmodule PlausibleWeb.Api.ExternalStatsController.QueryTest do
]
end
test "shows month to date with time labels trimmed", %{conn: conn, site: site} do
populate_stats(site, [
build(:pageview, timestamp: ~N[2021-01-01 00:00:00]),
build(:pageview, timestamp: ~N[2021-01-15 00:00:00]),
build(:pageview, timestamp: ~N[2021-01-16 00:00:00])
])
conn =
post(conn, "/api/v2/query-internal-test", %{
"site_id" => site.domain,
"metrics" => ["visitors"],
"date_range" => "month",
"date" => "2021-01-15",
"dimensions" => ["time:day"],
"include" => %{"trim_relative_date_range" => true}
})
assert json_response(conn, 200)["results"] == [
%{"dimensions" => ["2021-01-01"], "metrics" => [1]},
%{"dimensions" => ["2021-01-15"], "metrics" => [1]}
]
assert json_response(conn, 200)["query"]["date_range"] == [
"2021-01-01T00:00:00+00:00",
"2021-01-15T23:59:59+00:00"
]
end
test "shows last 6 months of visitors", %{conn: conn, site: site} do
populate_stats(site, [
build(:pageview, timestamp: ~N[2020-08-13 00:00:00]),