diff --git a/assets/js/dashboard/stats/modals/countries.js b/assets/js/dashboard/stats/modals/countries.js index ff5cb9e8a..e04b68e08 100644 --- a/assets/js/dashboard/stats/modals/countries.js +++ b/assets/js/dashboard/stats/modals/countries.js @@ -22,7 +22,15 @@ class CountriesModal extends React.Component { } label() { - return this.state.query.period === 'realtime' ? 'Current visitors' : 'Visitors' + if (this.state.query.period === 'realtime') { + return 'Current visitors' + } + + if (this.showConversionRate()) { + return 'Conversions' + } + + return 'Visitors' } showConversionRate() { @@ -48,8 +56,9 @@ class CountriesModal extends React.Component { {countryFullName} + {this.showConversionRate() && {country.total_visitors} } - {numberFormatter(country.count)} ({country.percentage}%) + {numberFormatter(country.count)} {!this.showConversionRate() && ({country.percentage}%)} {this.showConversionRate() && {country.conversion_rate}% } @@ -79,6 +88,7 @@ class CountriesModal extends React.Component { > Country + {this.showConversionRate() && Total visitors} + {this.showConversionRate() && {numberFormatter(page.total_visitors)}} {numberFormatter(page.count)} - {numberFormatter(page.entries)} - {this.showVisitDuration() && {durationFormatter(page.visit_duration)}} + {this.showExtra() && {numberFormatter(page.entries)}} + {this.showExtra() && {durationFormatter(page.visit_duration)}} {this.showConversionRate() && {numberFormatter(page.conversion_rate)}%} ) @@ -115,26 +128,11 @@ class EntryPagesModal extends React.Component { align="left" >Page url - Unique Entrances - - Total Entrances - - Visit Duration - - {this.showConversionRate() && CR - } + {this.showConversionRate() && Total Visitors } + {this.label()} + {this.showExtra() && Total Entrances } + {this.showExtra() && Visit Duration } + {this.showConversionRate() && CR } diff --git a/assets/js/dashboard/stats/modals/exit-pages.js b/assets/js/dashboard/stats/modals/exit-pages.js index f88c24008..9dff30751 100644 --- a/assets/js/dashboard/stats/modals/exit-pages.js +++ b/assets/js/dashboard/stats/modals/exit-pages.js @@ -46,6 +46,22 @@ class ExitPagesModal extends React.Component { return !!this.state.query.filters.goal } + showExtra() { + return this.state.query.period !== 'realtime' && !this.showConversionRate() + } + + label() { + if (this.state.query.period === 'realtime') { + return 'Current visitors' + } + + if (this.showConversionRate()) { + return 'Conversions' + } + + return 'Visitors' + } + renderPage(page) { const query = new URLSearchParams(window.location.search) query.set('exit_page', page.name) @@ -55,9 +71,10 @@ class ExitPagesModal extends React.Component { {page.name} + {this.showConversionRate() && {numberFormatter(page.total_visitors)}} {numberFormatter(page.count)} - {numberFormatter(page.exits)} - {this.formatPercentage(page.exit_rate)} + {this.showExtra() && {numberFormatter(page.exits)}} + {this.showExtra() && {this.formatPercentage(page.exit_rate)}} {this.showConversionRate() && {numberFormatter(page.conversion_rate)}%} ) @@ -89,9 +106,10 @@ class ExitPagesModal extends React.Component { Page url - Unique Exits - Total Exits - Exit Rate + {this.showConversionRate() && Total Visitors } + {this.label()} + {this.showExtra() && Total Exits} + {this.showExtra() && Exit Rate} {this.showConversionRate() && CR} diff --git a/assets/js/dashboard/stats/modals/pages.js b/assets/js/dashboard/stats/modals/pages.js index df61b5998..17e945121 100644 --- a/assets/js/dashboard/stats/modals/pages.js +++ b/assets/js/dashboard/stats/modals/pages.js @@ -66,6 +66,7 @@ class PagesModal extends React.Component { {page.name} + {this.showConversionRate() && {page.total_visitors} } {numberFormatter(page.count)} {this.showPageviews() && {numberFormatter(page.pageviews)} } {this.showExtra() && {this.formatBounceRate(page)} } @@ -76,7 +77,15 @@ class PagesModal extends React.Component { } label() { - return this.state.query.period === 'realtime' ? 'Current visitors' : 'Visitors' + if (this.state.query.period === 'realtime') { + return 'Current visitors' + } + + if (this.showConversionRate()) { + return 'Conversions' + } + + return 'Visitors' } renderLoading() { @@ -105,6 +114,7 @@ class PagesModal extends React.Component { Page url + {this.showConversionRate() && Total visitors} { this.label() } {this.showPageviews() && Pageviews} {this.showExtra() && Bounce rate} diff --git a/assets/js/dashboard/stats/modals/referrer-drilldown.js b/assets/js/dashboard/stats/modals/referrer-drilldown.js index 20e62f660..aff3c236d 100644 --- a/assets/js/dashboard/stats/modals/referrer-drilldown.js +++ b/assets/js/dashboard/stats/modals/referrer-drilldown.js @@ -27,6 +27,22 @@ class ReferrerDrilldownModal extends React.Component { return this.state.query.period !== 'realtime' && !this.state.query.filters.goal } + showConversionRate() { + return !!this.state.query.filters.goal + } + + label() { + if (this.state.query.period === 'realtime') { + return 'Current visitors' + } + + if (this.showConversionRate()) { + return 'Conversions' + } + + return 'Visitors' + } + formatBounceRate(ref) { if (typeof(ref.bounce_rate) === 'number') { return ref.bounce_rate + '%' @@ -107,9 +123,11 @@ class ReferrerDrilldownModal extends React.Component { { referrer.tweets.map(this.renderTweet) } + {this.showConversionRate() && {numberFormatter(referrer.total_visitors)} } {numberFormatter(referrer.count)} {this.showExtra() && {this.formatBounceRate(referrer)} } {this.showExtra() && {this.formatDuration(referrer)} } + {this.showConversionRate() && {referrer.conversion_rate}% } ) } else { @@ -118,9 +136,11 @@ class ReferrerDrilldownModal extends React.Component { { this.renderReferrerName(referrer) } + {this.showConversionRate() && {numberFormatter(referrer.total_visitors)} } {numberFormatter(referrer.count)} {this.showExtra() && {this.formatBounceRate(referrer)} } {this.showExtra() && {this.formatDuration(referrer)} } + {this.showConversionRate() && {referrer.conversion_rate}% } ) } @@ -153,9 +173,11 @@ class ReferrerDrilldownModal extends React.Component { Referrer - Visitors + {this.showConversionRate() && Total visitors} + {this.label()} {this.showExtra() && Bounce rate} {this.showExtra() && Visit duration} + {this.showConversionRate() && CR} diff --git a/assets/js/dashboard/stats/modals/sources.js b/assets/js/dashboard/stats/modals/sources.js index 308e349a5..944d1f331 100644 --- a/assets/js/dashboard/stats/modals/sources.js +++ b/assets/js/dashboard/stats/modals/sources.js @@ -97,6 +97,7 @@ class SourcesModal extends React.Component { /> { source.name } + {this.showConversionRate() && {numberFormatter(source.total_visitors)} } {numberFormatter(source.count)} {this.showExtra() && {this.formatBounceRate(source)} } {this.showExtra() && {this.formatDuration(source)} } @@ -106,7 +107,15 @@ class SourcesModal extends React.Component { } label() { - return this.state.query.period === 'realtime' ? 'Current visitors' : 'Visitors' + if (this.state.query.period === 'realtime') { + return 'Current visitors' + } + + if (this.showConversionRate()) { + return 'Conversions' + } + + return 'Visitors' } renderLoading() { @@ -139,6 +148,7 @@ class SourcesModal extends React.Component { Source + {this.showConversionRate() && Total visitors} {this.label()} {this.showExtra() && Bounce rate} {this.showExtra() && Visit duration} diff --git a/assets/js/dashboard/stats/pages/entry-pages.js b/assets/js/dashboard/stats/pages/entry-pages.js index 5f26edf94..13414c29c 100644 --- a/assets/js/dashboard/stats/pages/entry-pages.js +++ b/assets/js/dashboard/stats/pages/entry-pages.js @@ -38,6 +38,18 @@ export default class EntryPages extends React.Component { .then((res) => this.setState({loading: false, pages: res})) } + label() { + if (this.props.query.period === 'realtime') { + return 'Current visitors' + } + + if (this.showConversionRate()) { + return 'Conversions' + } + + return 'Unique Entrances' + } + renderPage(page) { const externalLink = url.externalLinkForPage(this.props.site.domain, page.name) const maxWidthDeduction = this.showConversionRate() ? "10rem" : "5rem" @@ -80,7 +92,7 @@ export default class EntryPages extends React.Component {
Page url
- Unique Entrances + {this.label()} {this.showConversionRate() && CR}
diff --git a/assets/js/dashboard/stats/pages/exit-pages.js b/assets/js/dashboard/stats/pages/exit-pages.js index f93434cbf..ba8970a59 100644 --- a/assets/js/dashboard/stats/pages/exit-pages.js +++ b/assets/js/dashboard/stats/pages/exit-pages.js @@ -33,6 +33,18 @@ export default class ExitPages extends React.Component { return !!this.props.query.filters.goal } + label() { + if (this.props.query.period === 'realtime') { + return 'Current visitors' + } + + if (this.showConversionRate()) { + return 'Conversions' + } + + return 'Unique Exits' + } + fetchPages() { api.get(`/api/stats/${encodeURIComponent(this.props.site.domain)}/exit-pages`, this.props.query) .then((res) => this.setState({loading: false, pages: res})) @@ -80,7 +92,7 @@ export default class ExitPages extends React.Component {
Page url
- Unique Exits + {this.label()} {this.showConversionRate() && CR}
diff --git a/assets/js/dashboard/stats/pages/pages.js b/assets/js/dashboard/stats/pages/pages.js index c83913fac..d9c153516 100644 --- a/assets/js/dashboard/stats/pages/pages.js +++ b/assets/js/dashboard/stats/pages/pages.js @@ -43,6 +43,10 @@ export default class Visits extends React.Component { return 'Current visitors' } + if (this.showConversionRate()) { + return 'Conversions' + } + return 'Visitors' } diff --git a/assets/js/dashboard/stats/sources/referrer-list.js b/assets/js/dashboard/stats/sources/referrer-list.js index 93b8d98c6..23e60014f 100644 --- a/assets/js/dashboard/stats/sources/referrer-list.js +++ b/assets/js/dashboard/stats/sources/referrer-list.js @@ -41,6 +41,10 @@ export default class Referrers extends React.Component { return this.props.query.period === 'realtime' } + showConversionRate() { + return !!this.props.query.filters.goal + } + fetchReferrers() { if (this.props.query.filters.source) { api.get(`/api/stats/${encodeURIComponent(this.props.site.domain)}/referrers/${encodeURIComponent(this.props.query.filters.source)}`, this.props.query, {show_noref: this.showNoRef()}) @@ -67,6 +71,7 @@ export default class Referrers extends React.Component { } renderReferrer(referrer) { + const maxWidthDeduction = this.showConversionRate() ? "10rem" : "5rem" const query = new URLSearchParams(window.location.search) query.set('referrer', referrer.name) @@ -76,7 +81,7 @@ export default class Referrers extends React.Component { count={referrer.count} all={this.state.referrers} bg="bg-blue-50 dark:bg-gray-500 dark:bg-opacity-15" - maxWidthDeduction="4rem" + maxWidthDeduction={maxWidthDeduction} > @@ -95,12 +100,21 @@ export default class Referrers extends React.Component { {numberFormatter(referrer.count)} + {this.showConversionRate() && {referrer.conversion_rate}%} ) } label() { - return this.props.query.period === 'realtime' ? 'Current visitors' : 'Visitors' + if (this.props.query.period === 'realtime') { + return 'Current visitors' + } + + if (this.showConversionRate()) { + return 'Conversions' + } + + return 'Visitors' } renderList() { @@ -108,10 +122,14 @@ export default class Referrers extends React.Component { return (
Referrer - { this.label() } + +
+ {this.label()} + {this.showConversionRate() && CR} +
diff --git a/assets/js/dashboard/stats/sources/source-list.js b/assets/js/dashboard/stats/sources/source-list.js index d75a88ec9..a8a02e827 100644 --- a/assets/js/dashboard/stats/sources/source-list.js +++ b/assets/js/dashboard/stats/sources/source-list.js @@ -77,7 +77,15 @@ class AllSources extends React.Component { } label() { - return this.props.query.period === 'realtime' ? 'Current visitors' : 'Visitors' + if (this.props.query.period === 'realtime') { + return 'Current visitors' + } + + if (this.showConversionRate()) { + return 'Conversions' + } + + return 'Visitors' } renderList() { @@ -198,7 +206,15 @@ class UTMSources extends React.Component { } label() { - return this.props.query.period === 'realtime' ? 'Current visitors' : 'Visitors' + if (this.props.query.period === 'realtime') { + return 'Current visitors' + } + + if (this.showConversionRate()) { + return 'Conversions' + } + + return 'Visitors' } renderList() { diff --git a/lib/plausible/stats/base.ex b/lib/plausible/stats/base.ex index c4c7316e7..284b73f3d 100644 --- a/lib/plausible/stats/base.ex +++ b/lib/plausible/stats/base.ex @@ -311,6 +311,7 @@ defmodule Plausible.Stats.Base do end defp db_prop_val("referrer_source", @no_ref), do: "" + defp db_prop_val("referrer", @no_ref), 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: "" diff --git a/lib/plausible_web/controllers/api/stats_controller.ex b/lib/plausible_web/controllers/api/stats_controller.ex index 3345f18d2..b058aa4ae 100644 --- a/lib/plausible_web/controllers/api/stats_controller.ex +++ b/lib/plausible_web/controllers/api/stats_controller.ex @@ -321,6 +321,7 @@ defmodule PlausibleWeb.Api.StatsController do referrers = Stats.breakdown(site, query, "visit:referrer", metrics, pagination) + |> maybe_add_cr(site, query, pagination, "referrer", "visit:referrer") |> transform_keys(%{"referrer" => "name", "visitors" => "count"}) %{"visitors" => %{"value" => total_visitors}} = Stats.aggregate(site, query, ["visitors"]) @@ -608,6 +609,7 @@ defmodule PlausibleWeb.Api.StatsController do without_goal = Enum.find(list_without_goals, fn s -> s[key_name] === item[key_name] end) item + |> Map.put(:total_visitors, without_goal["visitors"]) |> Map.put(:conversion_rate, calculate_cr(without_goal["visitors"], item["visitors"])) end) 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 31d4b1569..1aca02344 100644 --- a/test/plausible_web/controllers/api/stats_controller/browsers_test.exs +++ b/test/plausible_web/controllers/api/stats_controller/browsers_test.exs @@ -32,7 +32,13 @@ defmodule PlausibleWeb.Api.StatsController.BrowsersTest do conn = get(conn, "/api/stats/#{site.domain}/browsers?period=day&filters=#{filters}") assert json_response(conn, 200) == [ - %{"name" => "Chrome", "count" => 1, "percentage" => 100, "conversion_rate" => 50.0} + %{ + "name" => "Chrome", + "total_visitors" => 2, + "count" => 1, + "percentage" => 100, + "conversion_rate" => 50.0 + } ] end end diff --git a/test/plausible_web/controllers/api/stats_controller/countries_test.exs b/test/plausible_web/controllers/api/stats_controller/countries_test.exs index 361621f06..be345032d 100644 --- a/test/plausible_web/controllers/api/stats_controller/countries_test.exs +++ b/test/plausible_web/controllers/api/stats_controller/countries_test.exs @@ -59,12 +59,14 @@ defmodule PlausibleWeb.Api.StatsController.CountriesTest do assert json_response(conn, 200) == [ %{ "name" => "GBR", + "total_visitors" => 1, "count" => 1, "percentage" => 50, "conversion_rate" => 100.0 }, %{ "name" => "EST", + "total_visitors" => 2, "count" => 1, "percentage" => 50, "conversion_rate" => 50.0 diff --git a/test/plausible_web/controllers/api/stats_controller/operating_systems_test.exs b/test/plausible_web/controllers/api/stats_controller/operating_systems_test.exs index 676c88020..8962b9e3e 100644 --- a/test/plausible_web/controllers/api/stats_controller/operating_systems_test.exs +++ b/test/plausible_web/controllers/api/stats_controller/operating_systems_test.exs @@ -33,7 +33,13 @@ defmodule PlausibleWeb.Api.StatsController.OperatingSystemsTest do get(conn, "/api/stats/#{site.domain}/operating-systems?period=day&filters=#{filters}") assert json_response(conn, 200) == [ - %{"name" => "Mac", "count" => 1, "percentage" => 100, "conversion_rate" => 50.0} + %{ + "name" => "Mac", + "total_visitors" => 2, + "count" => 1, + "percentage" => 100, + "conversion_rate" => 50.0 + } ] end end diff --git a/test/plausible_web/controllers/api/stats_controller/pages_test.exs b/test/plausible_web/controllers/api/stats_controller/pages_test.exs index e930827dc..468d1fbd1 100644 --- a/test/plausible_web/controllers/api/stats_controller/pages_test.exs +++ b/test/plausible_web/controllers/api/stats_controller/pages_test.exs @@ -95,7 +95,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do conn = get(conn, "/api/stats/#{site.domain}/pages?period=day&filters=#{filters}") assert json_response(conn, 200) == [ - %{"count" => 1, "name" => "/", "conversion_rate" => 33.3} + %{"total_visitors" => 3, "count" => 1, "name" => "/", "conversion_rate" => 33.3} ] end end @@ -195,6 +195,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do assert json_response(conn, 200) == [ %{ + "total_visitors" => 1, "count" => 1, "entries" => 1, "name" => "/page2", @@ -202,6 +203,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do "conversion_rate" => 100.0 }, %{ + "total_visitors" => 2, "count" => 1, "entries" => 1, "name" => "/page1", @@ -289,6 +291,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do %{ "name" => "/exit1", "count" => 1, + "total_visitors" => 1, "exits" => 1, "exit_rate" => 50, "conversion_rate" => 100.0 @@ -296,6 +299,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do %{ "name" => "/exit2", "count" => 1, + "total_visitors" => 1, "exits" => 1, "exit_rate" => 100, "conversion_rate" => 100.0 diff --git a/test/plausible_web/controllers/api/stats_controller/screen_sizes_test.exs b/test/plausible_web/controllers/api/stats_controller/screen_sizes_test.exs index f446595ce..22f15419e 100644 --- a/test/plausible_web/controllers/api/stats_controller/screen_sizes_test.exs +++ b/test/plausible_web/controllers/api/stats_controller/screen_sizes_test.exs @@ -34,6 +34,7 @@ defmodule PlausibleWeb.Api.StatsController.ScreenSizesTest do assert json_response(conn, 200) == [ %{ "name" => "Desktop", + "total_visitors" => 2, "count" => 1, "percentage" => 100, "conversion_rate" => 50.0 diff --git a/test/plausible_web/controllers/api/stats_controller/sources_test.exs b/test/plausible_web/controllers/api/stats_controller/sources_test.exs index ab82603f9..6b8b947d3 100644 --- a/test/plausible_web/controllers/api/stats_controller/sources_test.exs +++ b/test/plausible_web/controllers/api/stats_controller/sources_test.exs @@ -310,7 +310,12 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do ) assert json_response(conn, 200) == [ - %{"name" => "Twitter", "count" => 1, "conversion_rate" => 50.0} + %{ + "name" => "Twitter", + "total_visitors" => 2, + "count" => 1, + "conversion_rate" => 50.0 + } ] end @@ -341,7 +346,12 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do ) assert json_response(conn, 200) == [ - %{"name" => "Twitter", "count" => 1, "conversion_rate" => 50.0} + %{ + "name" => "Twitter", + "total_visitors" => 2, + "count" => 1, + "conversion_rate" => 50.0 + } ] end end @@ -487,7 +497,12 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do assert json_response(conn, 200) == %{ "total_visitors" => 1, "referrers" => [ - %{"name" => "10words.com", "count" => 1} + %{ + "name" => "10words.com", + "total_visitors" => 2, + "conversion_rate" => 50.0, + "count" => 1 + } ] } end @@ -523,7 +538,12 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do assert json_response(conn, 200) == %{ "total_visitors" => 1, "referrers" => [ - %{"name" => "10words.com", "count" => 1} + %{ + "name" => "10words.com", + "total_visitors" => 2, + "conversion_rate" => 50.0, + "count" => 1 + } ] } end