mirror of
https://github.com/plausible/analytics.git
synced 2024-09-11 18:07:33 +03:00
Put total conversions on the graph + goal-filtered CSV export improvements (#3929)
* Add validation for the events metric in main_graph * Test the already existing events metric support in main-graph * Put total conversions on the graph * extract main_graph_csv function (refactor only) * add total_conversions and conversion_rate to goal-filtered visitors.csv * update changelog
This commit is contained in:
parent
561dcd821e
commit
d6e1e8bebd
@ -2,6 +2,8 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
### Added
|
||||
- Add `total_conversions` and `conversion_rate` to `visitors.csv` in a goal-filtered CSV export
|
||||
- Ability to display total conversions (with a goal filter) on the main graph
|
||||
- Add `conversion_rate` to Stats API Timeseries and on the main graph
|
||||
- Add `time_on_page` metric into the Stats API
|
||||
- County Block List in Site Settings
|
||||
|
@ -10,6 +10,7 @@ export const METRIC_MAPPING = {
|
||||
'Total visits': 'visits',
|
||||
'Bounce rate': 'bounce_rate',
|
||||
'Unique conversions': 'conversions',
|
||||
'Total conversions': 'events',
|
||||
'Conversion rate': 'conversion_rate',
|
||||
'Average revenue': 'average_revenue',
|
||||
'Total revenue': 'total_revenue',
|
||||
@ -18,6 +19,7 @@ export const METRIC_MAPPING = {
|
||||
export const METRIC_LABELS = {
|
||||
'visitors': 'Visitors',
|
||||
'pageviews': 'Pageviews',
|
||||
'events': 'Total conversions',
|
||||
'views_per_visit': 'Views per Visit',
|
||||
'visits': 'Visits',
|
||||
'bounce_rate': 'Bounce Rate',
|
||||
@ -31,6 +33,7 @@ export const METRIC_LABELS = {
|
||||
export const METRIC_FORMATTER = {
|
||||
'visitors': numberFormatter,
|
||||
'pageviews': numberFormatter,
|
||||
'events': numberFormatter,
|
||||
'visits': numberFormatter,
|
||||
'views_per_visit': (number) => (number),
|
||||
'bounce_rate': (number) => (`${number}%`),
|
||||
|
@ -436,7 +436,7 @@ export default class VisitorGraph extends React.Component {
|
||||
const selectableMetrics = topStatData && topStatData.top_stats.map(({ name }) => METRIC_MAPPING[name]).filter(name => name)
|
||||
const canSelectSavedMetric = selectableMetrics && selectableMetrics.includes(savedMetric)
|
||||
|
||||
if (query.filters.goal && savedMetric !== 'conversion_rate') {
|
||||
if (query.filters.goal && !['conversion_rate', 'events'].includes(savedMetric)) {
|
||||
this.setState({ metric: 'conversions' })
|
||||
} else if (canSelectSavedMetric) {
|
||||
this.setState({ metric: savedMetric })
|
||||
|
@ -8,6 +8,7 @@ defmodule Plausible.Stats.Timeseries do
|
||||
|
||||
@typep metric ::
|
||||
:pageviews
|
||||
| :events
|
||||
| :visitors
|
||||
| :visits
|
||||
| :bounce_rate
|
||||
|
@ -1313,8 +1313,10 @@ defmodule PlausibleWeb.Api.StatsController do
|
||||
m -> Plausible.Stats.Metrics.from_string!(m)
|
||||
end
|
||||
|
||||
if metric == :conversion_rate and !query.filters["event:goal"] do
|
||||
{:error, "Metric `:conversion_rate` can only be queried with a goal filter"}
|
||||
requires_goal_filter? = metric in [:conversion_rate, :events]
|
||||
|
||||
if requires_goal_filter? and !query.filters["event:goal"] do
|
||||
{:error, "Metric `#{metric}` can only be queried with a goal filter"}
|
||||
else
|
||||
{:ok, metric}
|
||||
end
|
||||
|
@ -112,29 +112,6 @@ defmodule PlausibleWeb.StatsController do
|
||||
site = Plausible.Repo.preload(conn.assigns.site, :owner)
|
||||
query = Query.from(site, params)
|
||||
|
||||
metrics =
|
||||
if query.filters["event:goal"] do
|
||||
[:visitors]
|
||||
else
|
||||
[:visitors, :pageviews, :visits, :views_per_visit, :bounce_rate, :visit_duration]
|
||||
end
|
||||
|
||||
graph = Plausible.Stats.timeseries(site, query, metrics)
|
||||
columns = [:date | metrics]
|
||||
|
||||
column_headers =
|
||||
if query.filters["event:goal"] do
|
||||
[:date, :unique_conversions]
|
||||
else
|
||||
columns
|
||||
end
|
||||
|
||||
visitors =
|
||||
Enum.map(graph, fn row -> Enum.map(columns, &row[&1]) end)
|
||||
|> (fn data -> [column_headers | data] end).()
|
||||
|> CSV.encode()
|
||||
|> Enum.join()
|
||||
|
||||
filename =
|
||||
~c"Plausible export #{params["domain"]} #{Timex.format!(query.date_range.first, "{ISOdate} ")} to #{Timex.format!(query.date_range.last, "{ISOdate} ")}.zip"
|
||||
|
||||
@ -142,6 +119,7 @@ defmodule PlausibleWeb.StatsController do
|
||||
limited_params = Map.merge(params, %{"limit" => "100"})
|
||||
|
||||
csvs = %{
|
||||
~c"visitors.csv" => fn -> main_graph_csv(site, query) end,
|
||||
~c"sources.csv" => fn -> Api.StatsController.sources(conn, params) end,
|
||||
~c"utm_mediums.csv" => fn -> Api.StatsController.utm_mediums(conn, params) end,
|
||||
~c"utm_sources.csv" => fn -> Api.StatsController.utm_sources(conn, params) end,
|
||||
@ -175,8 +153,6 @@ defmodule PlausibleWeb.StatsController do
|
||||
|> Enum.zip(csv_values)
|
||||
|> Enum.reject(fn {_k, v} -> is_nil(v) end)
|
||||
|
||||
csvs = [{~c"visitors.csv", visitors} | csvs]
|
||||
|
||||
{:ok, {_, zip_content}} = :zip.create(filename, csvs, [:memory])
|
||||
|
||||
conn
|
||||
@ -191,6 +167,33 @@ defmodule PlausibleWeb.StatsController do
|
||||
end
|
||||
end
|
||||
|
||||
defp main_graph_csv(site, query) do
|
||||
{metrics, column_headers} = csv_graph_metrics(query)
|
||||
|
||||
map_bucket_to_row = fn bucket -> Enum.map([:date | metrics], &bucket[&1]) end
|
||||
prepend_column_headers = fn data -> [column_headers | data] end
|
||||
|
||||
Plausible.Stats.timeseries(site, query, metrics)
|
||||
|> Enum.map(map_bucket_to_row)
|
||||
|> prepend_column_headers.()
|
||||
|> CSV.encode()
|
||||
|> Enum.join()
|
||||
end
|
||||
|
||||
defp csv_graph_metrics(%Query{filters: %{"event:goal" => _}}) do
|
||||
metrics = [:visitors, :events, :conversion_rate]
|
||||
column_headers = [:date, :unique_conversions, :total_conversions, :conversion_rate]
|
||||
|
||||
{metrics, column_headers}
|
||||
end
|
||||
|
||||
defp csv_graph_metrics(_) do
|
||||
metrics = [:visitors, :pageviews, :visits, :views_per_visit, :bounce_rate, :visit_duration]
|
||||
column_headers = [:date | metrics]
|
||||
|
||||
{metrics, column_headers}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Authorizes and renders a shared link:
|
||||
1. Shared link with no password protection: needs to just make sure the shared link entry is still
|
||||
|
@ -1,32 +1,32 @@
|
||||
date,unique_conversions
|
||||
2021-09-20,0
|
||||
2021-09-21,0
|
||||
2021-09-22,0
|
||||
2021-09-23,0
|
||||
2021-09-24,0
|
||||
2021-09-25,0
|
||||
2021-09-26,0
|
||||
2021-09-27,0
|
||||
2021-09-28,0
|
||||
2021-09-29,0
|
||||
2021-09-30,0
|
||||
2021-10-01,0
|
||||
2021-10-02,0
|
||||
2021-10-03,0
|
||||
2021-10-04,0
|
||||
2021-10-05,0
|
||||
2021-10-06,0
|
||||
2021-10-07,0
|
||||
2021-10-08,0
|
||||
2021-10-09,0
|
||||
2021-10-10,0
|
||||
2021-10-11,0
|
||||
2021-10-12,0
|
||||
2021-10-13,0
|
||||
2021-10-14,0
|
||||
2021-10-15,0
|
||||
2021-10-16,0
|
||||
2021-10-17,0
|
||||
2021-10-18,0
|
||||
2021-10-19,1
|
||||
2021-10-20,0
|
||||
date,unique_conversions,total_conversions,conversion_rate
|
||||
2021-09-20,0,0,0.0
|
||||
2021-09-21,0,0,0.0
|
||||
2021-09-22,0,0,0.0
|
||||
2021-09-23,0,0,0.0
|
||||
2021-09-24,0,0,0.0
|
||||
2021-09-25,0,0,0.0
|
||||
2021-09-26,0,0,0.0
|
||||
2021-09-27,0,0,0.0
|
||||
2021-09-28,0,0,0.0
|
||||
2021-09-29,0,0,0.0
|
||||
2021-09-30,0,0,0.0
|
||||
2021-10-01,0,0,0.0
|
||||
2021-10-02,0,0,0.0
|
||||
2021-10-03,0,0,0.0
|
||||
2021-10-04,0,0,0.0
|
||||
2021-10-05,0,0,0.0
|
||||
2021-10-06,0,0,0.0
|
||||
2021-10-07,0,0,0.0
|
||||
2021-10-08,0,0,0.0
|
||||
2021-10-09,0,0,0.0
|
||||
2021-10-10,0,0,0.0
|
||||
2021-10-11,0,0,0.0
|
||||
2021-10-12,0,0,0.0
|
||||
2021-10-13,0,0,0.0
|
||||
2021-10-14,0,0,0.0
|
||||
2021-10-15,0,0,0.0
|
||||
2021-10-16,0,0,0.0
|
||||
2021-10-17,0,0,0.0
|
||||
2021-10-18,0,0,0.0
|
||||
2021-10-19,1,1,50.0
|
||||
2021-10-20,0,0,0.0
|
||||
|
|
@ -478,6 +478,83 @@ defmodule PlausibleWeb.Api.StatsController.MainGraphTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /api/stats/main-graph - events (total conversions) plot" do
|
||||
setup [:create_user, :log_in, :create_new_site]
|
||||
|
||||
test "returns 400 when the `events` metric is queried without a goal filter", %{
|
||||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/main-graph?period=month&date=2021-01-01&metric=events"
|
||||
)
|
||||
|
||||
assert %{"error" => error} = json_response(conn, 400)
|
||||
assert error =~ "`events` can only be queried with a goal filter"
|
||||
end
|
||||
|
||||
test "displays total conversions for a goal", %{conn: conn, site: site} do
|
||||
insert(:goal, site: site, event_name: "Signup")
|
||||
|
||||
populate_stats(site, [
|
||||
build(:event, name: "Different", timestamp: ~N[2021-01-01 00:00:00]),
|
||||
build(:event, name: "Signup", timestamp: ~N[2021-01-01 00:00:00]),
|
||||
build(:event, name: "Signup", timestamp: ~N[2021-01-01 00:00:00]),
|
||||
build(:pageview, timestamp: ~N[2021-01-31 00:00:00]),
|
||||
build(:event, name: "Signup", user_id: 123, timestamp: ~N[2021-01-31 00:00:00]),
|
||||
build(:event, name: "Signup", user_id: 123, timestamp: ~N[2021-01-31 00:00:00]),
|
||||
build(:event, name: "Signup", user_id: 123, timestamp: ~N[2021-01-31 00:00:00])
|
||||
])
|
||||
|
||||
filters = Jason.encode!(%{goal: "Signup"})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/main-graph?period=month&date=2021-01-01&metric=events&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert %{"plot" => plot} = json_response(conn, 200)
|
||||
assert Enum.count(plot) == 31
|
||||
|
||||
assert List.first(plot) == 2
|
||||
assert Enum.at(plot, 10) == 0.0
|
||||
assert List.last(plot) == 3
|
||||
end
|
||||
|
||||
test "displays total conversions per hour with previous day comparison plot", %{
|
||||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
insert(:goal, site: site, event_name: "Signup")
|
||||
|
||||
populate_stats(site, [
|
||||
build(:event, name: "Different", timestamp: ~N[2021-01-10 05:00:00]),
|
||||
build(:event, name: "Signup", timestamp: ~N[2021-01-10 05:00:00]),
|
||||
build(:event, name: "Signup", timestamp: ~N[2021-01-10 05:00:00]),
|
||||
build(:event, name: "Signup", timestamp: ~N[2021-01-10 19:00:00]),
|
||||
build(:pageview, timestamp: ~N[2021-01-10 19:00:00]),
|
||||
build(:event, name: "Signup", timestamp: ~N[2021-01-11 04:00:00]),
|
||||
build(:event, name: "Signup", timestamp: ~N[2021-01-11 05:00:00]),
|
||||
build(:event, name: "Signup", timestamp: ~N[2021-01-11 18:00:00])
|
||||
])
|
||||
|
||||
filters = Jason.encode!(%{goal: "Signup"})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/main-graph?period=day&date=2021-01-11&metric=events&filters=#{filters}&comparison=previous_period"
|
||||
)
|
||||
|
||||
assert %{"plot" => curr, "comparison_plot" => prev} = json_response(conn, 200)
|
||||
assert [0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0] = prev
|
||||
assert [0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0] = curr
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /api/stats/main-graph - bounce_rate plot" do
|
||||
setup [:create_user, :log_in, :create_new_site, :add_imported_data]
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user