Skip to content

Commit

Permalink
Fix queries with wildcard goals (#3015)
Browse files Browse the repository at this point in the history
* Fix queries with wildcard goals

* Changelog
  • Loading branch information
ukutaht committed Jun 14, 2023
1 parent 8f6224b commit 98055d2
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ All notable changes to this project will be documented in this file.
- Fix bug with [showing property breakdown with a prop filter](https://github.com/plausible/analytics/issues/1789)
- Fix bug when combining goal and prop filters plausible/analytics#2654
- Fix broken favicons when domain includes a slash
- Fix bug when using multiple [wildcard goal filters](https://github.com/plausible/analytics/pull/3015)

### Changed
- Treat page filter as entry page filter for `bounce_rate`
Expand Down
44 changes: 34 additions & 10 deletions lib/plausible/stats/base.ex
Original file line number Diff line number Diff line change
Expand Up @@ -75,20 +75,44 @@ defmodule Plausible.Stats.Base do
{:matches_member, clauses} ->
{events, pages} = split_goals(clauses, &page_regex/1)

from(e in q,
where:
fragment("multiMatchAny(?, ?)", e.pathname, ^pages) or
fragment("multiMatchAny(?, ?)", e.name, ^events)
)
event_clause =
if Enum.any?(events) do
dynamic([x], fragment("multiMatchAny(?, ?)", x.name, ^events))
else
dynamic([x], false)
end

page_clause =
if Enum.any?(pages) do
dynamic([x], fragment("multiMatchAny(?, ?)", x.pathname, ^pages))
else
dynamic([x], false)
end

where_clause = dynamic([], ^event_clause or ^page_clause)

from(e in q, where: ^where_clause)

{:not_matches_member, clauses} ->
{events, pages} = split_goals(clauses, &page_regex/1)

from(e in q,
where:
fragment("not(multiMatchAny(?, ?))", e.pathname, ^pages) and
fragment("not(multiMatchAny(?, ?))", e.name, ^events)
)
event_clause =
if Enum.any?(events) do
dynamic([x], fragment("multiMatchAny(?, ?)", x.name, ^events))
else
dynamic([x], false)
end

page_clause =
if Enum.any?(pages) do
dynamic([x], fragment("multiMatchAny(?, ?)", x.pathname, ^pages))
else
dynamic([x], false)
end

where_clause = dynamic([], not (^event_clause or ^page_clause))

from(e in q, where: ^where_clause)

{:not_member, clauses} ->
{events, pages} = split_goals(clauses)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,42 @@ defmodule PlausibleWeb.Api.StatsController.ConversionsTest do
]
end

test "can combine wildcard and no wildcard in matches_member", %{conn: conn, site: site} do
populate_stats(site, [
build(:pageview, pathname: "/blog/post-1"),
build(:pageview, pathname: "/blog/post-2"),
build(:pageview, pathname: "/billing/upgrade")
])

insert(:goal, %{site: site, page_path: "/blog/**"})
insert(:goal, %{site: site, page_path: "/billing/upgrade"})

filters = Jason.encode!(%{goal: "Visit /blog/**|Visit /billing/upgrade"})

conn =
get(
conn,
"/api/stats/#{site.domain}/conversions?period=day&filters=#{filters}"
)

assert json_response(conn, 200) == [
%{
"name" => "Visit /blog/**",
"unique_conversions" => 2,
"total_conversions" => 2,
"prop_names" => [],
"conversion_rate" => 66.7
},
%{
"name" => "Visit /billing/upgrade",
"unique_conversions" => 1,
"total_conversions" => 1,
"prop_names" => [],
"conversion_rate" => 33.3
}
]
end

test "can filter by matches_member filter type on goals", %{conn: conn, site: site} do
populate_stats(site, [
build(:pageview, pathname: "/"),
Expand Down Expand Up @@ -699,6 +735,37 @@ defmodule PlausibleWeb.Api.StatsController.ConversionsTest do
}
]
end

test "can combine wildcard and no wildcard in not_matches_member", %{conn: conn, site: site} do
populate_stats(site, [
build(:pageview, pathname: "/blog/post-1"),
build(:pageview, pathname: "/blog/post-2"),
build(:pageview, pathname: "/billing/upgrade"),
build(:pageview, pathname: "/register")
])

insert(:goal, %{site: site, page_path: "/blog/**"})
insert(:goal, %{site: site, page_path: "/billing/upgrade"})
insert(:goal, %{site: site, page_path: "/register"})

filters = Jason.encode!(%{goal: "!Visit /blog/**|Visit /billing/upgrade"})

conn =
get(
conn,
"/api/stats/#{site.domain}/conversions?period=day&filters=#{filters}"
)

assert json_response(conn, 200) == [
%{
"name" => "Visit /register",
"unique_conversions" => 1,
"total_conversions" => 1,
"prop_names" => [],
"conversion_rate" => 25
}
]
end
end

describe "GET /api/stats/:domain/conversions - with goal and prop=(none) filter" do
Expand Down

0 comments on commit 98055d2

Please sign in to comment.