diff --git a/assets/js/dashboard/stats/modals/sources.js b/assets/js/dashboard/stats/modals/sources.js
index 334483a31..fd47b2bd6 100644
--- a/assets/js/dashboard/stats/modals/sources.js
+++ b/assets/js/dashboard/stats/modals/sources.js
@@ -10,7 +10,9 @@ const TITLES = {
sources: 'Top Sources',
utm_mediums: 'Top UTM mediums',
utm_sources: 'Top UTM sources',
- utm_campaigns: 'Top UTM campaigns'
+ utm_campaigns: 'Top UTM campaigns',
+ utm_contents: 'Top UTM contents',
+ utm_terms: 'Top UTM Terms'
}
class SourcesModal extends React.Component {
@@ -84,6 +86,8 @@ class SourcesModal extends React.Component {
if (filter === 'utm_mediums') query.set('utm_medium', source.name)
if (filter === 'utm_sources') query.set('utm_source', source.name)
if (filter === 'utm_campaigns') query.set('utm_campaign', source.name)
+ if (filter === 'utm_contents') query.set('utm_content', source.name)
+ if (filter === 'utm_terms') query.set('utm_term', source.name)
console.log(source)
diff --git a/assets/js/dashboard/stats/sources/source-list.js b/assets/js/dashboard/stats/sources/source-list.js
index c19bf2fa8..3a16f125f 100644
--- a/assets/js/dashboard/stats/sources/source-list.js
+++ b/assets/js/dashboard/stats/sources/source-list.js
@@ -138,9 +138,11 @@ class AllSources extends React.Component {
}
const UTM_TAGS = {
- utm_medium: {label: 'UTM Medium', endpoint: 'utm_mediums'},
- utm_source: {label: 'UTM Source', endpoint: 'utm_sources'},
- utm_campaign: {label: 'UTM Campaign', endpoint: 'utm_campaigns'},
+ utm_medium: {label: 'UTM Medium', shortLabel: 'UTM Medium', endpoint: 'utm_mediums'},
+ utm_source: {label: 'UTM Source', shortLabel: 'UTM Source', endpoint: 'utm_sources'},
+ utm_campaign: {label: 'UTM Campaign', shortLabel: 'UTM Campai', endpoint: 'utm_campaigns'},
+ utm_content: {label: 'UTM Content', shortLabel: 'UTM Conten', endpoint: 'utm_contents'},
+ utm_term: {label: 'UTM Term', shortLabel: 'UTM Term', endpoint: 'utm_terms'},
}
class UTMSources extends React.Component {
@@ -266,6 +268,11 @@ class UTMSources extends React.Component {
}
}
+import { Fragment } from 'react'
+import { Menu, Transition } from '@headlessui/react'
+import { ChevronDownIcon } from '@heroicons/react/solid'
+import classNames from 'classnames'
+
export default class SourceList extends React.Component {
constructor(props) {
super(props)
@@ -284,15 +291,57 @@ export default class SourceList extends React.Component {
}
renderTabs() {
- const activeClass = 'inline-block h-5 text-indigo-700 dark:text-indigo-500 font-bold active-prop-heading'
- const defaultClass = 'hover:text-indigo-600 cursor-pointer'
+ const activeClass = 'inline-block h-5 text-indigo-700 dark:text-indigo-500 font-bold active-prop-heading truncate text-left'
+ const defaultClass = 'hover:text-indigo-600 cursor-pointer truncate text-left'
+ const dropdownOptions = ['utm_medium', 'utm_source', 'utm_campaign']
+ let buttonText = UTM_TAGS[this.state.tab] ? UTM_TAGS[this.state.tab].label : 'UTM Params'
+
return (
-
- - All
- - Medium
- - Source
- - Campaign
-
+
+
All
+
+
+
)
}
@@ -305,6 +354,10 @@ export default class SourceList extends React.Component {
return
} else if (this.state.tab === 'utm_campaign') {
return
+ } else if (this.state.tab === 'utm_content') {
+ return
+ } else if (this.state.tab === 'utm_term') {
+ return
}
}
}
diff --git a/lib/plausible/event/clickhouse_schema.ex b/lib/plausible/event/clickhouse_schema.ex
index 8b2ae8f30..f5d5fe678 100644
--- a/lib/plausible/event/clickhouse_schema.ex
+++ b/lib/plausible/event/clickhouse_schema.ex
@@ -17,6 +17,8 @@ defmodule Plausible.ClickhouseEvent do
field :utm_medium, :string, default: ""
field :utm_source, :string, default: ""
field :utm_campaign, :string, default: ""
+ field :utm_content, :string, default: ""
+ field :utm_term, :string, default: ""
field :country_code, :string, default: ""
field :subdivision1_code, :string, default: ""
@@ -53,6 +55,8 @@ defmodule Plausible.ClickhouseEvent do
:utm_medium,
:utm_source,
:utm_campaign,
+ :utm_content,
+ :utm_term,
:country_code,
:subdivision1_code,
:subdivision2_code,
diff --git a/lib/plausible/session/clickhouse_schema.ex b/lib/plausible/session/clickhouse_schema.ex
index 727d5e562..ccf653049 100644
--- a/lib/plausible/session/clickhouse_schema.ex
+++ b/lib/plausible/session/clickhouse_schema.ex
@@ -21,6 +21,8 @@ defmodule Plausible.ClickhouseSession do
field :utm_medium, :string
field :utm_source, :string
field :utm_campaign, :string
+ field :utm_content, :string
+ field :utm_term, :string
field :referrer, :string
field :referrer_source, :string
@@ -60,6 +62,8 @@ defmodule Plausible.ClickhouseSession do
:utm_medium,
:utm_source,
:utm_campaign,
+ :utm_content,
+ :utm_term,
:country_code,
:country_geoname_id,
:subdivision1_code,
diff --git a/lib/plausible/session/store.ex b/lib/plausible/session/store.ex
index ceab3cc1b..aa9daff06 100644
--- a/lib/plausible/session/store.ex
+++ b/lib/plausible/session/store.ex
@@ -113,6 +113,8 @@ defmodule Plausible.Session.Store do
utm_medium: event.utm_medium,
utm_source: event.utm_source,
utm_campaign: event.utm_campaign,
+ utm_content: event.utm_content,
+ utm_term: event.utm_term,
country_code: event.country_code,
subdivision1_code: event.subdivision1_code,
subdivision2_code: event.subdivision2_code,
diff --git a/lib/plausible/stats/base.ex b/lib/plausible/stats/base.ex
index e2ea1b1d8..4bb34a9c4 100644
--- a/lib/plausible/stats/base.ex
+++ b/lib/plausible/stats/base.ex
@@ -314,6 +314,8 @@ defmodule Plausible.Stats.Base do
defp db_prop_val(:utm_medium, @no_ref), do: ""
defp db_prop_val(:utm_source, @no_ref), do: ""
defp db_prop_val(:utm_campaign, @no_ref), do: ""
+ defp db_prop_val(:utm_content, @no_ref), do: ""
+ defp db_prop_val(:utm_term, @no_ref), do: ""
defp db_prop_val(_, val), do: val
defp utc_boundaries(%Query{period: "realtime"}, _timezone) do
diff --git a/lib/plausible/stats/breakdown.ex b/lib/plausible/stats/breakdown.ex
index a0040053a..72eb1e651 100644
--- a/lib/plausible/stats/breakdown.ex
+++ b/lib/plausible/stats/breakdown.ex
@@ -138,7 +138,9 @@ defmodule Plausible.Stats.Breakdown do
"visit:source",
"visit:utm_medium",
"visit:utm_source",
- "visit:utm_campaign"
+ "visit:utm_campaign",
+ "visit:utm_content",
+ "visit:utm_term"
] do
query = Query.treat_page_filter_as_entry_page(query)
breakdown_sessions(site, query, property, metrics, pagination)
@@ -412,6 +414,26 @@ defmodule Plausible.Stats.Breakdown do
)
end
+ defp do_group_by(q, "visit:utm_content") do
+ from(
+ s in q,
+ group_by: s.utm_content,
+ select_merge: %{
+ "utm_content" => fragment("if(empty(?), ?, ?)", s.utm_content, @no_ref, s.utm_content)
+ }
+ )
+ end
+
+ defp do_group_by(q, "visit:utm_term") do
+ from(
+ s in q,
+ group_by: s.utm_term,
+ select_merge: %{
+ "utm_term" => fragment("if(empty(?), ?, ?)", s.utm_term, @no_ref, s.utm_term)
+ }
+ )
+ end
+
defp do_group_by(q, "visit:device") do
from(
s in q,
diff --git a/lib/plausible/stats/clickhouse.ex b/lib/plausible/stats/clickhouse.ex
index 698e716da..5fbb89255 100644
--- a/lib/plausible/stats/clickhouse.ex
+++ b/lib/plausible/stats/clickhouse.ex
@@ -248,6 +248,22 @@ defmodule Plausible.Stats.Clickhouse do
q
end
+ q =
+ if query.filters["utm_content"] do
+ utm_content = query.filters["utm_content"]
+ from(s in q, where: s.utm_content == ^utm_content)
+ else
+ q
+ end
+
+ q =
+ if query.filters["utm_term"] do
+ utm_term = query.filters["utm_term"]
+ from(s in q, where: s.utm_term == ^utm_term)
+ else
+ q
+ end
+
q = include_path_filter_entry(q, query.filters["entry_page"])
q = include_path_filter_exit(q, query.filters["exit_page"])
@@ -342,6 +358,22 @@ defmodule Plausible.Stats.Clickhouse do
q
end
+ q =
+ if query.filters["utm_content"] do
+ utm_content = query.filters["utm_content"]
+ from(e in q, where: e.utm_content == ^utm_content)
+ else
+ q
+ end
+
+ q =
+ if query.filters["utm_term"] do
+ utm_term = query.filters["utm_term"]
+ from(e in q, where: e.utm_term == ^utm_term)
+ else
+ q
+ end
+
q =
if query.filters["referrer"] do
ref = query.filters["referrer"]
diff --git a/lib/plausible/stats/filter_suggestions.ex b/lib/plausible/stats/filter_suggestions.ex
index def8f305e..569a3ceee 100644
--- a/lib/plausible/stats/filter_suggestions.ex
+++ b/lib/plausible/stats/filter_suggestions.ex
@@ -200,6 +200,18 @@ defmodule Plausible.Stats.FilterSuggestions do
where: fragment("? ilike ?", e.utm_campaign, ^filter_query)
)
+ "utm_content" ->
+ from(e in q,
+ select: {e.utm_content},
+ where: fragment("? ilike ?", e.utm_content, ^filter_query)
+ )
+
+ "utm_term" ->
+ from(e in q,
+ select: {e.utm_term},
+ where: fragment("? ilike ?", e.utm_term, ^filter_query)
+ )
+
"referrer" ->
from(e in q,
select: {e.referrer},
diff --git a/lib/plausible/stats/filters.ex b/lib/plausible/stats/filters.ex
index bdfde2f83..2091fe88d 100644
--- a/lib/plausible/stats/filters.ex
+++ b/lib/plausible/stats/filters.ex
@@ -5,6 +5,8 @@ defmodule Plausible.Stats.Filters do
"utm_medium",
"utm_source",
"utm_campaign",
+ "utm_content",
+ "utm_term",
"screen",
"device",
"browser",
diff --git a/lib/plausible_web/controllers/api/external_controller.ex b/lib/plausible_web/controllers/api/external_controller.ex
index b111b938f..35b823f28 100644
--- a/lib/plausible_web/controllers/api/external_controller.ex
+++ b/lib/plausible_web/controllers/api/external_controller.ex
@@ -104,6 +104,8 @@ defmodule PlausibleWeb.Api.ExternalController do
utm_medium: query["utm_medium"],
utm_source: query["utm_source"],
utm_campaign: query["utm_campaign"],
+ utm_content: query["utm_content"],
+ utm_term: query["utm_term"],
country_code: location_details[:country_code],
country_geoname_id: location_details[:country_geoname_id],
subdivision1_code: location_details[:subdivision1_code],
diff --git a/lib/plausible_web/controllers/api/stats_controller.ex b/lib/plausible_web/controllers/api/stats_controller.ex
index 0675b2419..62e38798f 100644
--- a/lib/plausible_web/controllers/api/stats_controller.ex
+++ b/lib/plausible_web/controllers/api/stats_controller.ex
@@ -287,6 +287,44 @@ defmodule PlausibleWeb.Api.StatsController do
end
end
+ def utm_contents(conn, params) do
+ site = conn.assigns[:site]
+
+ query =
+ Query.from(site.timezone, params)
+ |> Filters.add_prefix()
+ |> maybe_hide_noref("visit:utm_content", params)
+
+ pagination = parse_pagination(params)
+ metrics = ["visitors", "bounce_rate", "visit_duration"]
+
+ res =
+ Stats.breakdown(site, query, "visit:utm_content", metrics, pagination)
+ |> maybe_add_cr(site, query, pagination, "utm_content", "visit:utm_content")
+ |> transform_keys(%{"utm_content" => "name", "visitors" => "count"})
+
+ json(conn, res)
+ end
+
+ def utm_terms(conn, params) do
+ site = conn.assigns[:site]
+
+ query =
+ Query.from(site.timezone, params)
+ |> Filters.add_prefix()
+ |> maybe_hide_noref("visit:utm_term", params)
+
+ pagination = parse_pagination(params)
+ metrics = ["visitors", "bounce_rate", "visit_duration"]
+
+ res =
+ Stats.breakdown(site, query, "visit:utm_term", metrics, pagination)
+ |> maybe_add_cr(site, query, pagination, "utm_term", "visit:utm_term")
+ |> transform_keys(%{"utm_term" => "name", "visitors" => "count"})
+
+ json(conn, res)
+ end
+
def utm_sources(conn, params) do
site = conn.assigns[:site]
diff --git a/lib/plausible_web/router.ex b/lib/plausible_web/router.ex
index abd1a3032..df3d6d43a 100644
--- a/lib/plausible_web/router.ex
+++ b/lib/plausible_web/router.ex
@@ -56,6 +56,8 @@ defmodule PlausibleWeb.Router do
get "/:domain/utm_mediums", StatsController, :utm_mediums
get "/:domain/utm_sources", StatsController, :utm_sources
get "/:domain/utm_campaigns", StatsController, :utm_campaigns
+ get "/:domain/utm_contents", StatsController, :utm_contents
+ get "/:domain/utm_terms", StatsController, :utm_terms
get "/:domain/referrers/:referrer", StatsController, :referrer_drilldown
get "/:domain/pages", StatsController, :pages
get "/:domain/entry-pages", StatsController, :entry_pages
diff --git a/lib/plausible_web/templates/auth/user_settings.html.eex b/lib/plausible_web/templates/auth/user_settings.html.eex
index 8c4e94e40..a8020f035 100644
--- a/lib/plausible_web/templates/auth/user_settings.html.eex
+++ b/lib/plausible_web/templates/auth/user_settings.html.eex
@@ -273,7 +273,7 @@
Deleting your account removes all sites and stats you've collected
<%= if @subscription && @subscription.status == "active" do %>
- Delete my account
+ Delete my account
Your account cannot be deleted because you have an active subscription. If you want to delete your account, please cancel your subscription first.
<% else %>
<%= link("Delete my account", to: "/me", class: "inline-block mt-4 px-4 py-2 border border-gray-300 dark:border-gray-500 text-sm leading-5 font-medium rounded-md text-red-700 bg-white dark:bg-gray-800 hover:text-red-500 dark:hover:text-red-400 focus:outline-none focus:border-blue-300 focus:ring active:text-red-800 active:bg-gray-50 transition ease-in-out duration-150", method: "delete", data: [confirm: "Deleting your account will also delete all the sites and data that you own. This action cannot be reversed. Are you sure?"]) %>
diff --git a/priv/clickhouse_repo/migrations/20211017093035_add_utm_content_and_term.exs b/priv/clickhouse_repo/migrations/20211017093035_add_utm_content_and_term.exs
new file mode 100644
index 000000000..bd7f07158
--- /dev/null
+++ b/priv/clickhouse_repo/migrations/20211017093035_add_utm_content_and_term.exs
@@ -0,0 +1,15 @@
+defmodule Plausible.ClickhouseRepo.Migrations.AddUtmContentAndTerm do
+ use Ecto.Migration
+
+ def change do
+ alter table(:events) do
+ add :utm_content, :string
+ add :utm_term, :string
+ end
+
+ alter table(:sessions) do
+ add :utm_content, :string
+ add :utm_term, :string
+ end
+ end
+end
diff --git a/test/plausible/session/store_test.exs b/test/plausible/session/store_test.exs
index 33f3220d5..aa39cb2c5 100644
--- a/test/plausible/session/store_test.exs
+++ b/test/plausible/session/store_test.exs
@@ -21,6 +21,8 @@ defmodule Plausible.Session.StoreTest do
utm_medium: "medium",
utm_source: "source",
utm_campaign: "campaign",
+ utm_content: "content",
+ utm_term: "term",
browser: "browser",
browser_version: "55",
country_code: "EE",
@@ -47,6 +49,8 @@ defmodule Plausible.Session.StoreTest do
assert session.utm_medium == event.utm_medium
assert session.utm_source == event.utm_source
assert session.utm_campaign == event.utm_campaign
+ assert session.utm_content == event.utm_content
+ assert session.utm_term == event.utm_term
assert session.country_code == event.country_code
assert session.screen_size == event.screen_size
assert session.operating_system == event.operating_system
diff --git a/test/support/factory.ex b/test/support/factory.ex
index b51cb0a6f..754668f83 100644
--- a/test/support/factory.ex
+++ b/test/support/factory.ex
@@ -48,6 +48,8 @@ defmodule Plausible.Factory do
utm_medium: "",
utm_source: "",
utm_campaign: "",
+ utm_content: "",
+ utm_term: "",
entry_page: "/",
pageviews: 1,
events: 1,
@@ -88,6 +90,8 @@ defmodule Plausible.Factory do
utm_medium: "",
utm_source: "",
utm_campaign: "",
+ utm_content: "",
+ utm_term: "",
browser: "",
browser_version: "",
country_code: "",