Skip to content

Commit cfb77d0

Browse files
authored
Merge branch 'master' into validate-empty-filter-list
2 parents d802ba0 + ad92741 commit cfb77d0

6 files changed

Lines changed: 91 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ All notable changes to this project will be documented in this file.
2929
- Fixed Stats API timeseries returning time buckets falling outside the queried range
3030
- Fixed issue with all non-interactive events being counted as interactive
3131
- Fixed countries map countries staying highlighted on Chrome
32+
- Fixed comparison tooltip in the top pages report missing date labels
3233

3334
## v3.2.0 - 2026-01-16
3435

assets/js/dashboard/stats/graph/graph-util.js

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,38 @@ function plottable(dataArray) {
2525
})
2626
}
2727

28-
const buildComparisonDataset = function (comparisonPlot) {
28+
const buildComparisonDataset = function (comparisonPlot, presentIndex) {
2929
if (!comparisonPlot) return []
3030

31+
const data = presentIndex
32+
? comparisonPlot.slice(0, presentIndex)
33+
: comparisonPlot
34+
3135
return [
3236
{
33-
data: plottable(comparisonPlot),
37+
data: plottable(data),
3438
borderColor: 'rgba(99, 102, 241, 0.3)',
3539
pointBackgroundColor: 'rgba(99, 102, 241, 0.2)',
3640
pointHoverBackgroundColor: 'rgba(99, 102, 241, 0.5)',
3741
yAxisID: 'yComparison'
3842
}
3943
]
4044
}
45+
46+
const buildDashedComparisonDataset = function (comparisonPlot, presentIndex) {
47+
if (!comparisonPlot || !presentIndex) return []
48+
const dashedPart = comparisonPlot.slice(presentIndex - 1, presentIndex + 1)
49+
const dashedPlot = new Array(presentIndex - 1).concat(dashedPart)
50+
return [
51+
{
52+
data: plottable(dashedPlot),
53+
borderDash: [3, 3],
54+
borderColor: 'rgba(99, 102, 241, 0.3)',
55+
pointHoverBackgroundColor: 'rgba(99, 102, 241, 0.5)',
56+
yAxisID: 'yComparison'
57+
}
58+
]
59+
}
4160
const buildDashedDataset = function (plot, presentIndex) {
4261
if (!presentIndex) return []
4362
const dashedPart = plot.slice(presentIndex - 1, presentIndex + 1)
@@ -90,7 +109,8 @@ export const buildDataSet = (
90109
const dataset = [
91110
...buildMainPlotDataset(plot, present_index),
92111
...buildDashedDataset(plot, present_index),
93-
...buildComparisonDataset(comparisonPlot)
112+
...buildComparisonDataset(comparisonPlot, present_index),
113+
...buildDashedComparisonDataset(comparisonPlot, present_index)
94114
]
95115

96116
return dataset.map((item) => Object.assign(item, defaultOptions))

lib/plausible/stats/table_decider.ex

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ defmodule Plausible.Stats.TableDecider do
77
use Plausible
88

99
import Enum, only: [empty?: 1]
10-
import Plausible.Stats.Filters, only: [dimensions_used_in_filters: 1]
10+
11+
import Plausible.Stats.Filters,
12+
only: [dimensions_used_in_filters: 1, filtering_on_dimension?: 2]
1113

1214
alias Plausible.Stats.{Query, QueryError}
1315

@@ -146,7 +148,11 @@ defmodule Plausible.Stats.TableDecider do
146148
# :TRICKY: For time:minute dimension we prefer sessions over events as there
147149
# might be minutes where no events occurred but the session was active.
148150
defp metric_partitioner(query, metric) when metric in [:visitors, :visits] do
149-
if "time:minute" in query.dimensions, do: :session, else: :either
151+
if "time:minute" in query.dimensions and not filtering_on_dimension?(query, "event:goal") do
152+
:session
153+
else
154+
:either
155+
end
150156
end
151157

152158
defp metric_partitioner(_, :conversion_rate), do: :either

lib/plausible_web/controllers/api/stats_controller.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -723,7 +723,7 @@ defmodule PlausibleWeb.Api.StatsController do
723723
else
724724
json(conn, %{
725725
results: pages,
726-
meta: Map.merge(meta, Stats.Breakdown.formatted_date_ranges(query)),
726+
meta: Map.new(meta.values) |> Map.merge(Stats.Breakdown.formatted_date_ranges(query)),
727727
skip_imported_reason: meta[:imports_skip_reason]
728728
})
729729
end

test/plausible/stats/query/query_test.exs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,4 +317,55 @@ defmodule Plausible.Stats.QueryTest do
317317
]
318318
end
319319
end
320+
321+
describe "group_conversion_rate with time:minute and time:hour dimensions" do
322+
test "unique conversions are not smeared when querying visitors without conversion_rate",
323+
%{site: site} do
324+
insert(:goal, site: site, event_name: "Signup")
325+
326+
populate_stats(site, [
327+
build(:pageview, user_id: 1, timestamp: ~N[2021-01-01 00:00:00]),
328+
build(:event, name: "Signup", user_id: 1, timestamp: ~N[2021-01-01 00:05:00]),
329+
build(:pageview, user_id: 2, timestamp: ~N[2021-01-01 00:00:00])
330+
])
331+
332+
{:ok, query} =
333+
QueryBuilder.build(site, %ParsedQueryParams{
334+
metrics: [:visitors],
335+
input_date_range: {:datetime_range, ~U[2021-01-01 00:00:00Z], ~U[2021-01-01 00:10:00Z]},
336+
dimensions: ["time:minute"],
337+
filters: [[:is, "event:goal", ["Signup"]]],
338+
skip_goal_existence_check: true
339+
})
340+
341+
%Stats.QueryResult{results: results} = Stats.query(site, query)
342+
343+
assert [%{dimensions: ["2021-01-01 00:05:00"], metrics: [1]}] = results
344+
end
345+
346+
test "unique conversions are not smeared across all session minutes via timeSlots",
347+
%{site: site} do
348+
insert(:goal, site: site, event_name: "Signup")
349+
350+
populate_stats(site, [
351+
build(:pageview, user_id: 1, timestamp: ~N[2021-01-01 00:00:00]),
352+
build(:event, name: "Signup", user_id: 1, timestamp: ~N[2021-01-01 00:05:00]),
353+
build(:pageview, user_id: 2, timestamp: ~N[2021-01-01 00:00:00]),
354+
build(:pageview, user_id: 3, timestamp: ~N[2021-01-01 00:05:00])
355+
])
356+
357+
{:ok, query} =
358+
QueryBuilder.build(site, %ParsedQueryParams{
359+
metrics: [:visitors, :group_conversion_rate],
360+
input_date_range: {:datetime_range, ~U[2021-01-01 00:00:00Z], ~U[2021-01-01 00:10:00Z]},
361+
dimensions: ["time:minute"],
362+
filters: [[:is, "event:goal", ["Signup"]]],
363+
skip_goal_existence_check: true
364+
})
365+
366+
%Stats.QueryResult{results: results} = Stats.query(site, query)
367+
368+
assert [%{dimensions: ["2021-01-01 00:05:00"], metrics: [1, 50.0]}] = results
369+
end
370+
end
320371
end

test/plausible_web/controllers/api/stats_controller/pages_test.exs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,8 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
657657
"percentage" => 100.0
658658
}
659659
]
660+
661+
assert json_response(conn, 200)["meta"] == %{"date_range_label" => "1 Jan 2021"}
660662
end
661663

662664
test "returns top pages with :not_member filter on custom pageview props including (none) value",
@@ -2212,6 +2214,11 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
22122214
}
22132215
}
22142216
]
2217+
2218+
assert json_response(conn, 200)["meta"] == %{
2219+
"date_range_label" => "2 Jan 2021",
2220+
"comparison_date_range_label" => "1 Jan 2021"
2221+
}
22152222
end
22162223

22172224
on_ee do

0 commit comments

Comments
 (0)