From 79f6756ba2eb83626dca16520dd5c3e1f2b16218 Mon Sep 17 00:00:00 2001 From: Uku Taht Date: Mon, 16 Aug 2021 10:39:13 +0300 Subject: [PATCH] Make is not filter work --- assets/js/dashboard/filters.js | 55 +++++++++++--------------------- lib/plausible/stats/aggregate.ex | 5 ++- lib/plausible/stats/base.ex | 29 ++++++++++++++--- lib/plausible/stats/filters.ex | 34 +++++++++++++------- 4 files changed, 69 insertions(+), 54 deletions(-) diff --git a/assets/js/dashboard/filters.js b/assets/js/dashboard/filters.js index 492dbecc1..738c87017 100644 --- a/assets/js/dashboard/filters.js +++ b/assets/js/dashboard/filters.js @@ -29,7 +29,17 @@ function clearAllFilters(history, query) { ); } -function filterText(key, value, query) { +function filterType(val) { + if (val.startsWith('!')) { + return ['is not', val.substr(1)] + } + + return ['is', val] +} + +function filterText(key, rawValue, query) { + const [type, value] = filterType(rawValue) + if (key === "goal") { return <>Completed goal {value} } @@ -38,51 +48,24 @@ function filterText(key, value, query) { const eventName = query.filters.goal ? query.filters.goal : 'event' return <>{eventName}.{metaKey} is {metaValue} } - if (key === "source") { - return <>Source: {value} - } - if (key === "utm_medium") { - return <>UTM medium: {value} - } - if (key === "utm_source") { - return <>UTM source: {value} - } - if (key === "utm_campaign") { - return <>UTM campaign: {value} - } - if (key === "referrer") { - return <>Referrer: {value} - } - if (key === "screen") { - return <>Screen size: {value} - } - if (key === "browser") { - return <>Browser: {value} - } if (key === "browser_version") { const browserName = query.filters.browser ? query.filters.browser : 'Browser' - return <>{browserName}.Version: {value} - } - if (key === "os") { - return <>Operating System: {value} + return <>{browserName}.Version {type} {value} } if (key === "os_version") { const osName = query.filters.os ? query.filters.os : 'OS' - return <>{osName}.Version: {value} + return <>{osName}.Version {type} {value} } if (key === "country") { const allCountries = Datamap.prototype.worldTopo.objects.world.geometries; const selectedCountry = allCountries.find((c) => c.id === value) || {properties: {name: value}}; - return <>Country: {selectedCountry.properties.name} + return <>Country {type} {selectedCountry.properties.name} } - if (key === "page") { - return <>Page: {value} - } - if (key === "entry_page") { - return <>Entry Page: {value} - } - if (key === "exit_page") { - return <>Exit Page: {value} + + const formattedFilter = formattedFilters[key] + + if (formattedFilter) { + return <>{formattedFilter} {type} {value} } throw new Error(`Unknown filter: ${key}`) diff --git a/lib/plausible/stats/aggregate.ex b/lib/plausible/stats/aggregate.ex index 724827b1f..23ff36f73 100644 --- a/lib/plausible/stats/aggregate.ex +++ b/lib/plausible/stats/aggregate.ex @@ -98,7 +98,10 @@ defmodule Plausible.Stats.Aggregate do {where_clause, where_arg} = case query.filters["event:page"] do {:is, page} -> - {"p=?", page} + {"p = ?", page} + + {:is_not, page} -> + {"p != ?", page} {:matches, expr} -> regex = page_regex(expr) diff --git a/lib/plausible/stats/base.ex b/lib/plausible/stats/base.ex index fae213a83..e4746537f 100644 --- a/lib/plausible/stats/base.ex +++ b/lib/plausible/stats/base.ex @@ -38,6 +38,9 @@ defmodule Plausible.Stats.Base do {:is, page} -> from(e in q, where: e.pathname == ^page) + {:is_not, page} -> + from(e in q, where: e.pathname != ^page) + {:matches, glob_expr} -> regex = page_regex(glob_expr) from(e in q, where: fragment("match(?, ?)", e.pathname, ^regex)) @@ -45,15 +48,19 @@ defmodule Plausible.Stats.Base do {:member, list} -> from(e in q, where: e.pathname in ^list) - _ -> + nil -> q + + _ -> + raise "Unknown filter type" end q = case query.filters["event:name"] do {:is, name} -> from(e in q, where: e.name == ^name) {:member, list} -> from(e in q, where: e.name in ^list) - _ -> q + nil -> q + _ -> raise "Unknown filter type" end q = @@ -64,8 +71,11 @@ defmodule Plausible.Stats.Base do {:is, :event, event} -> from(e in q, where: e.name == ^event) - _ -> + nil -> q + + _ -> + raise "Unknown goal type" end Enum.reduce(query.filters, q, fn {filter_key, filter_value}, query -> @@ -116,6 +126,9 @@ defmodule Plausible.Stats.Base do {:is, page} -> from(e in sessions_q, where: e.entry_page == ^page) + {:is_not, page} -> + from(e in sessions_q, where: e.entry_page != ^page) + {:matches, glob_expr} -> regex = page_regex(glob_expr) from(s in sessions_q, where: fragment("match(?, ?)", s.entry_page, ^regex)) @@ -123,8 +136,11 @@ defmodule Plausible.Stats.Base do {:member, list} -> from(e in sessions_q, where: e.entry_page in ^list) - _ -> + nil -> sessions_q + + _ -> + raise "Unknown filter type" end Enum.reduce(Filters.visit_props(), sessions_q, fn prop_name, sessions_q -> @@ -148,8 +164,11 @@ defmodule Plausible.Stats.Base do fragment_data = [{String.to_existing_atom(prop_name), {:in, list}}] from(s in sessions_q, where: fragment(^fragment_data)) - _ -> + nil -> sessions_q + + _ -> + raise "Unknown filter type" end end) end diff --git a/lib/plausible/stats/filters.ex b/lib/plausible/stats/filters.ex index 3dbce01ac..7569be821 100644 --- a/lib/plausible/stats/filters.ex +++ b/lib/plausible/stats/filters.ex @@ -28,21 +28,15 @@ defmodule Plausible.Stats.Filters do Enum.reduce(query.filters, %{}, fn {name, val}, new_filters -> cond do name == "country" -> - new_val = Plausible.Stats.CountryName.to_alpha2(val) - Map.put(new_filters, "visit:country", {:is, new_val}) - - name == "page" -> - if String.match?(val, ~r/\*/) do - Map.put(new_filters, "event:page", {:matches, val}) - else - Map.put(new_filters, "event:page", {:is, val}) - end + {filter_type, filter_val} = filter_value(name, val) + new_val = Plausible.Stats.CountryName.to_alpha2(filter_val) + Map.put(new_filters, "visit:country", {filter_type, new_val}) name in (@visit_props ++ ["goal"]) -> - Map.put(new_filters, "visit:" <> name, {:is, val}) + Map.put(new_filters, "visit:" <> name, filter_value(name, val)) name in @event_props -> - Map.put(new_filters, "event:" <> name, {:is, val}) + Map.put(new_filters, "event:" <> name, filter_value(name, val)) name == "props" -> Enum.reduce(val, new_filters, fn {prop_key, prop_val}, new_filters -> @@ -50,10 +44,26 @@ defmodule Plausible.Stats.Filters do end) true -> - Map.put(new_filters, name, {:is, val}) + raise "Unknown filter prop" end end) %Plausible.Stats.Query{query | filters: new_filters} end + + defp filter_value(key, "!" <> val) do + if String.contains?(key, "page") && String.match?(val, ~r/\*/) do + {:does_not_match, val} + else + {:is_not, val} + end + end + + defp filter_value(key, val) do + if String.contains?(key, "page") && String.match?(val, ~r/\*/) do + {:matches, val} + else + {:is, val} + end + end end