From 8f6224b5de53dea8ebeb4385bc14a047c192a8f2 Mon Sep 17 00:00:00 2001 From: Vini Brasil Date: Wed, 14 Jun 2023 10:23:11 +0100 Subject: [PATCH] Revenue tracking: goal conversions and tracker script (#3019) * Add revenue average and total to Goal Conversions * Add revenue option to tracker script * Simplify revenue tracker script --- .../dashboard/stats/behaviours/conversions.js | 67 ++++++++----------- lib/plausible_web/plugs/tracker.ex | 3 +- tracker/compile.js | 2 +- tracker/src/plausible.js | 5 ++ tracker/test/fixtures/revenue.html | 18 +++++ tracker/test/revenue.spec.js | 13 ++++ 6 files changed, 68 insertions(+), 40 deletions(-) create mode 100644 tracker/test/fixtures/revenue.html create mode 100644 tracker/test/revenue.spec.js diff --git a/assets/js/dashboard/stats/behaviours/conversions.js b/assets/js/dashboard/stats/behaviours/conversions.js index f7ec52c16..6d001420e 100644 --- a/assets/js/dashboard/stats/behaviours/conversions.js +++ b/assets/js/dashboard/stats/behaviours/conversions.js @@ -10,36 +10,27 @@ import * as url from '../../util/url' import { escapeFilterValue } from '../../util/filters' import LazyLoader from '../../components/lazy-loader' -const MOBILE_UPPER_WIDTH = 767 -const DEFAULT_WIDTH = 1080 +function Money({ formatted }) { + if (formatted) { + return {formatted.short} + } else { + return "-" + } +} export default class Conversions extends React.Component { constructor(props) { super(props) this.htmlNode = React.createRef() - this.state = { - loading: true, - viewport: DEFAULT_WIDTH, - } + this.state = { loading: true, } this.onVisible = this.onVisible.bind(this) this.fetchConversions = this.fetchConversions.bind(this) - this.handleResize = this.handleResize.bind(this); - } - - componentDidMount() { - window.addEventListener('resize', this.handleResize, false); - this.handleResize(); } componentWillUnmount() { - window.removeEventListener('resize', this.handleResize, false); document.removeEventListener('tick', this.fetchConversions) } - handleResize() { - this.setState({ viewport: window.innerWidth }); - } - onVisible() { this.fetchConversions() if (this.props.query.period === 'realtime') { @@ -55,60 +46,60 @@ export default class Conversions extends React.Component { } } - getBarMaxWidth() { - const { viewport } = this.state; - return viewport > MOBILE_UPPER_WIDTH ? "16rem" : "10rem"; - } - fetchConversions() { api.get(`/api/stats/${encodeURIComponent(this.props.site.domain)}/conversions`, this.props.query) .then((res) => this.setState({ loading: false, goals: res, prevHeight: null })) } - renderGoal(goal) { - const { viewport } = this.state; + renderGoal(goal, renderRevenueColumn) { const renderProps = this.props.query.filters['goal'] == goal.name && goal.prop_names return (
- - {goal.name} - + + + {goal.name} + +
{numberFormatter(goal.unique_conversions)} - {viewport > MOBILE_UPPER_WIDTH && {numberFormatter(goal.total_conversions)}} + {numberFormatter(goal.total_conversions)} {goal.conversion_rate}% + {renderRevenueColumn && } + {renderRevenueColumn && }
- {renderProps && } + { renderProps && !goal.total_revenue && }
) } renderInner() { - const { viewport } = this.state; if (this.state.loading) { return
} else if (this.state.goals) { + const hasRevenue = this.state.goals.some((goal) => goal.total_revenue) + return (
Goal
Uniques - {viewport > MOBILE_UPPER_WIDTH && Total} + Total CR + {hasRevenue && Revenue} + {hasRevenue && Average}
- {this.state.goals.map(this.renderGoal.bind(this))} + { this.state.goals.map((goal) => this.renderGoal.bind(this)(goal, hasRevenue) ) }
) diff --git a/lib/plausible_web/plugs/tracker.ex b/lib/plausible_web/plugs/tracker.ex index 4c5352cd4..cd9a89b4e 100644 --- a/lib/plausible_web/plugs/tracker.ex +++ b/lib/plausible_web/plugs/tracker.ex @@ -11,7 +11,8 @@ defmodule PlausibleWeb.Tracker do "manual", "file-downloads", "pageview-props", - "tagged-events" + "tagged-events", + "revenue" ] # Generates Power Set of all variants diff --git a/tracker/compile.js b/tracker/compile.js index 31ff49711..5896759cf 100644 --- a/tracker/compile.js +++ b/tracker/compile.js @@ -26,7 +26,7 @@ function compilefile(input, output, templateVars = {}) { } } -const base_variants = ["hash", "outbound-links", "exclusions", "compat", "local", "manual", "file-downloads", "pageview-props", "tagged-events"] +const base_variants = ["hash", "outbound-links", "exclusions", "compat", "local", "manual", "file-downloads", "pageview-props", "tagged-events", "revenue"] const variants = [...g.clone.powerSet(base_variants)].filter(a => a.length > 0).map(a => a.sort()); compilefile(relPath('src/plausible.js'), relPath('../priv/tracker/js/plausible.js')) diff --git a/tracker/src/plausible.js b/tracker/src/plausible.js index b89da3bdf..68c85a840 100644 --- a/tracker/src/plausible.js +++ b/tracker/src/plausible.js @@ -81,6 +81,11 @@ if (options && options.props) { payload.p = options.props } + {{#if revenue}} + if (options && options.revenue) { + payload.$ = options.revenue + } + {{/if}} {{#if pageview_props}} var propAttributes = scriptEl.getAttributeNames().filter(function (name) { diff --git a/tracker/test/fixtures/revenue.html b/tracker/test/fixtures/revenue.html new file mode 100644 index 000000000..a36ad43e2 --- /dev/null +++ b/tracker/test/fixtures/revenue.html @@ -0,0 +1,18 @@ + + + + + + + + Plausible Playwright tests + + + + + + + + diff --git a/tracker/test/revenue.spec.js b/tracker/test/revenue.spec.js new file mode 100644 index 000000000..d47b55238 --- /dev/null +++ b/tracker/test/revenue.spec.js @@ -0,0 +1,13 @@ +const { mockRequest, expectCustomEvent } = require('./support/test-utils'); +const { expect, test } = require('@playwright/test'); + +test.describe('with revenue script extension', () => { + test('sends revenue currency and amount', async ({ page }) => { + const plausibleRequestMock = mockRequest(page, '/api/event') + await page.goto('/revenue.html'); + await page.click('#purchase') + + const plausibleRequest = await plausibleRequestMock + expect(plausibleRequest.postDataJSON()["$"]).toEqual({amount: 15.99, currency: "USD"}) + }); +});