diff --git a/.gitignore b/.gitignore index a54345ec3..02be17d07 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,7 @@ plausible-report.xml *.iml *.log *.code-workspace +.vscode # Dializer /priv/plts/*.plt diff --git a/CHANGELOG.md b/CHANGELOG.md index ad7e43877..141640ffd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,6 @@ # Changelog All notable changes to this project will be documented in this file. -## Unreleased - ### Added - Add `referrers.csv` to CSV export - Add a new Properties section in the dashboard to break down by custom properties @@ -17,6 +15,7 @@ All notable changes to this project will be documented in this file. - Add site pinning to /sites view - Add support for JSON logger, via LOG_FORMAT=json environment variable - Add support for 2FA authentication +- Add 'browser_versions.csv' to CSV export ### Removed - Removed the nested custom event property breakdown UI when filtering by a goal in Goal Conversions diff --git a/assets/js/dashboard/stats/devices/index.js b/assets/js/dashboard/stats/devices/index.js index 4a407081a..916a4f876 100644 --- a/assets/js/dashboard/stats/devices/index.js +++ b/assets/js/dashboard/stats/devices/index.js @@ -12,7 +12,7 @@ function Browsers({ query, site }) { } function getFilterFor(listItem) { - return { browser: listItem['name']} + return { browser: listItem['name'] } } return ( @@ -35,7 +35,7 @@ function BrowserVersions({ query, site }) { if (query.filters.browser === '(not set)') { return {} } - return { browser_version: listItem['name']} + return { browser_version: listItem['name'] } } return ( @@ -56,7 +56,7 @@ function OperatingSystems({ query, site }) { } function getFilterFor(listItem) { - return { os: listItem['name']} + return { os: listItem['name'] } } return ( @@ -79,7 +79,7 @@ function OperatingSystemVersions({ query, site }) { if (query.filters.os === '(not set)') { return {} } - return { os_version: listItem['name']} + return { os_version: listItem['name'] } } return ( @@ -104,7 +104,7 @@ function ScreenSizes({ query, site }) { } function getFilterFor(listItem) { - return { screen: listItem['name']} + return { screen: listItem['name'] } } return ( diff --git a/lib/plausible/stats/breakdown.ex b/lib/plausible/stats/breakdown.ex index 1e6e84054..634334e00 100644 --- a/lib/plausible/stats/breakdown.ex +++ b/lib/plausible/stats/breakdown.ex @@ -649,8 +649,9 @@ defmodule Plausible.Stats.Breakdown do defp do_group_by(q, "visit:browser_version") do from( s in q, - group_by: s.browser_version, + group_by: [s.browser, s.browser_version], select_merge: %{ + browser: fragment("if(empty(?), ?, ?)", s.browser, @not_set, s.browser), browser_version: fragment("if(empty(?), ?, ?)", s.browser_version, @not_set, s.browser_version) }, diff --git a/lib/plausible_web/controllers/api/stats_controller.ex b/lib/plausible_web/controllers/api/stats_controller.ex index c419fbad7..bf2f6b231 100644 --- a/lib/plausible_web/controllers/api/stats_controller.ex +++ b/lib/plausible_web/controllers/api/stats_controller.ex @@ -1061,7 +1061,23 @@ defmodule PlausibleWeb.Api.StatsController do |> transform_keys(%{browser_version: :name}) |> add_percentages(site, query) - json(conn, versions) + if params["csv"] do + if Map.has_key?(query.filters, "event:goal") do + versions + |> transform_keys(%{ + name: :version, + browser: :name, + visitors: :conversions + }) + |> to_csv([:name, :version, :conversions, :conversion_rate]) + else + versions + |> transform_keys(%{name: :version, browser: :name}) + |> to_csv([:name, :version, :visitors]) + end + else + json(conn, versions) + end end def operating_systems(conn, params) do diff --git a/lib/plausible_web/controllers/stats_controller.ex b/lib/plausible_web/controllers/stats_controller.ex index ff149aa77..b6bee8b11 100644 --- a/lib/plausible_web/controllers/stats_controller.ex +++ b/lib/plausible_web/controllers/stats_controller.ex @@ -155,6 +155,7 @@ defmodule PlausibleWeb.StatsController do ~c"regions.csv" => fn -> Api.StatsController.regions(conn, params) end, ~c"cities.csv" => fn -> Api.StatsController.cities(conn, params) end, ~c"browsers.csv" => fn -> Api.StatsController.browsers(conn, params) end, + ~c"browser_versions.csv" => fn -> Api.StatsController.browser_versions(conn, params) end, ~c"operating_systems.csv" => fn -> Api.StatsController.operating_systems(conn, params) end, ~c"devices.csv" => fn -> Api.StatsController.screen_sizes(conn, params) end, ~c"conversions.csv" => fn -> Api.StatsController.conversions(conn, params) end, diff --git a/test/plausible_web/controllers/CSVs/30d-filter-goal/browser_versions.csv b/test/plausible_web/controllers/CSVs/30d-filter-goal/browser_versions.csv new file mode 100644 index 000000000..a81927435 --- /dev/null +++ b/test/plausible_web/controllers/CSVs/30d-filter-goal/browser_versions.csv @@ -0,0 +1,2 @@ +name,version,conversions,conversion_rate +(not set),(not set),1,50.0 diff --git a/test/plausible_web/controllers/CSVs/30d-filter-path/browser_versions.csv b/test/plausible_web/controllers/CSVs/30d-filter-path/browser_versions.csv new file mode 100644 index 000000000..96f76c8b0 --- /dev/null +++ b/test/plausible_web/controllers/CSVs/30d-filter-path/browser_versions.csv @@ -0,0 +1,2 @@ +name,version,visitors +(not set),(not set),1 diff --git a/test/plausible_web/controllers/CSVs/30d/browser_versions.csv b/test/plausible_web/controllers/CSVs/30d/browser_versions.csv new file mode 100644 index 000000000..6ea976358 --- /dev/null +++ b/test/plausible_web/controllers/CSVs/30d/browser_versions.csv @@ -0,0 +1,3 @@ +name,version,visitors +Firefox,120,2 +(not set),(not set),2 diff --git a/test/plausible_web/controllers/CSVs/30d/browsers.csv b/test/plausible_web/controllers/CSVs/30d/browsers.csv index 0065f7b3d..7c43ba903 100644 --- a/test/plausible_web/controllers/CSVs/30d/browsers.csv +++ b/test/plausible_web/controllers/CSVs/30d/browsers.csv @@ -1,3 +1,3 @@ name,visitors -ABrowserName,2 +Firefox,2 (not set),2 diff --git a/test/plausible_web/controllers/CSVs/6m/browser_versions.csv b/test/plausible_web/controllers/CSVs/6m/browser_versions.csv new file mode 100644 index 000000000..ec5d54b4c --- /dev/null +++ b/test/plausible_web/controllers/CSVs/6m/browser_versions.csv @@ -0,0 +1,4 @@ +name,version,visitors +Firefox,120,2 +(not set),(not set),2 +FirefoxNoVersion,(not set),1 diff --git a/test/plausible_web/controllers/CSVs/6m/browsers.csv b/test/plausible_web/controllers/CSVs/6m/browsers.csv index 10141774c..eba98919d 100644 --- a/test/plausible_web/controllers/CSVs/6m/browsers.csv +++ b/test/plausible_web/controllers/CSVs/6m/browsers.csv @@ -1,3 +1,4 @@ name,visitors -ABrowserName,3 +Firefox,2 (not set),2 +FirefoxNoVersion,1 diff --git a/test/plausible_web/controllers/api/external_stats_controller/breakdown_test.exs b/test/plausible_web/controllers/api/external_stats_controller/breakdown_test.exs index 9fc516fe2..3d9c9542e 100644 --- a/test/plausible_web/controllers/api/external_stats_controller/breakdown_test.exs +++ b/test/plausible_web/controllers/api/external_stats_controller/breakdown_test.exs @@ -517,8 +517,8 @@ defmodule PlausibleWeb.Api.ExternalStatsController.BreakdownTest do assert json_response(conn, 200) == %{ "results" => [ - %{"browser_version" => "56", "visitors" => 2}, - %{"browser_version" => "57", "visitors" => 1} + %{"browser_version" => "56", "visitors" => 2, "browser" => "(not set)"}, + %{"browser_version" => "57", "visitors" => 1, "browser" => "(not set)"} ] } end diff --git a/test/plausible_web/controllers/api/stats_controller/browsers_test.exs b/test/plausible_web/controllers/api/stats_controller/browsers_test.exs index 29c2c4d30..9e4a0ca14 100644 --- a/test/plausible_web/controllers/api/stats_controller/browsers_test.exs +++ b/test/plausible_web/controllers/api/stats_controller/browsers_test.exs @@ -188,8 +188,8 @@ defmodule PlausibleWeb.Api.StatsController.BrowsersTest do ) assert json_response(conn, 200) == [ - %{"name" => "78.0", "visitors" => 2, "percentage" => 66.7}, - %{"name" => "77.0", "visitors" => 1, "percentage" => 33.3} + %{"name" => "78.0", "visitors" => 2, "percentage" => 66.7, "browser" => "Chrome"}, + %{"name" => "77.0", "visitors" => 1, "percentage" => 33.3, "browser" => "Chrome"} ] end @@ -207,7 +207,12 @@ defmodule PlausibleWeb.Api.StatsController.BrowsersTest do ) assert json_response(conn, 200) == [ - %{"name" => "(not set)", "visitors" => 1, "percentage" => 100} + %{ + "name" => "(not set)", + "visitors" => 1, + "percentage" => 100, + "browser" => "(not set)" + } ] end end diff --git a/test/plausible_web/controllers/stats_controller_test.exs b/test/plausible_web/controllers/stats_controller_test.exs index 721749ae0..b33d87457 100644 --- a/test/plausible_web/controllers/stats_controller_test.exs +++ b/test/plausible_web/controllers/stats_controller_test.exs @@ -373,13 +373,15 @@ defmodule PlausibleWeb.StatsControllerTest do utm_term: "term", timestamp: Timex.shift(~N[2021-10-20 12:00:00], days: -1) |> NaiveDateTime.truncate(:second), - browser: "ABrowserName" + browser: "Firefox", + browser_version: "120" ), build(:pageview, timestamp: Timex.shift(~N[2021-10-20 12:00:00], months: -1) |> NaiveDateTime.truncate(:second), country_code: "EE", - browser: "ABrowserName" + browser: "Firefox", + browser_version: "120" ), build(:pageview, timestamp: @@ -387,7 +389,7 @@ defmodule PlausibleWeb.StatsControllerTest do utm_campaign: "ads", country_code: "EE", referrer_source: "Google", - browser: "ABrowserName" + browser: "FirefoxNoVersion" ), build(:event, timestamp: