Skip to content

Commit

Permalink
Add conversion_rate to sources api and source table (plausible#1299)
Browse files Browse the repository at this point in the history
* Add conversion_rate to sources api and source table

* Remove percentageFormatter

* Update source tests to include conversionat rate

* Add CR to detals modal

* Correct formatting with linter

* Add change log

* Add CR to Pages, Device and Countries panels
  • Loading branch information
ro-savage authored and oliver-kriska committed Oct 1, 2021
1 parent 08d970a commit c27f6bc
Show file tree
Hide file tree
Showing 20 changed files with 382 additions and 45 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ All notable changes to this project will be documented in this file.
- Added `CLICKHOUSE_FLUSH_INTERVAL_MS` and `CLICKHOUSE_MAX_BUFFER_SIZE` configuration parameters plausible/analytics#1073
- Ability to invite users to sites with different roles plausible/analytics#1122
- Option to configure a custom name for the script file
- Add Conversion Rate to Top Sources, Top Pages Devices, Countries when filtered by a goal plausible/analytics#1299

### Fixed
- Fix weekly report time range plausible/analytics#951
Expand Down
20 changes: 14 additions & 6 deletions assets/js/dashboard/stats/devices/browsers.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default class Browsers extends React.Component {
this.fetchBrowsers()
}
}

onVisible() {
this.fetchBrowsers()
if (this.props.timer) this.props.timer.onTick(this.fetchBrowsers.bind(this))
Expand All @@ -37,6 +37,10 @@ export default class Browsers extends React.Component {
}
}

showConversionRate() {
return !!this.props.query.filters.goal
}

label() {
return this.props.query.period === 'realtime' ? 'Current visitors' : 'Visitors'
}
Expand All @@ -58,21 +62,22 @@ export default class Browsers extends React.Component {
} else {
query.set('browser', browser.name)
}
const maxWidthDeduction = this.showConversionRate() ? "10rem" : "5rem"

return (
<div className="flex items-center justify-between my-1 text-sm" key={browser.name}>
<Bar
count={browser.count}
all={this.state.browsers}
bg="bg-green-50 dark:bg-gray-500 dark:bg-opacity-15"
maxWidthDeduction="6rem"
maxWidthDeduction={maxWidthDeduction}
>
{this.renderBrowserContent(browser, query)}
</Bar>
<span className="font-medium dark:text-gray-200">
{numberFormatter(browser.count)}
<span className="inline-block w-8 text-xs text-right">({browser.percentage}%)</span>
<span className="font-medium dark:text-gray-200 text-right w-20">
{numberFormatter(browser.count)} <span className="inline-block w-8 text-xs"> ({browser.percentage}%)</span>
</span>
{this.showConversionRate() && <span className="font-medium dark:text-gray-200 w-20 text-right">{numberFormatter(browser.conversion_rate)}%</span>}
</div>
)
}
Expand All @@ -85,7 +90,10 @@ export default class Browsers extends React.Component {
<React.Fragment>
<div className="flex items-center justify-between mt-3 mb-2 text-xs font-bold tracking-wide text-gray-500 dark:text-gray-400">
<span>{ key }</span>
<span>{ this.label() }</span>
<div className="text-right">
<span className="inline-block w-20">{ this.label() }</span>
{this.showConversionRate() && <span className="inline-block w-20">CR</span>}
</div>
</div>
{ this.state.browsers && this.state.browsers.map(this.renderBrowser.bind(this)) }
</React.Fragment>
Expand Down
28 changes: 17 additions & 11 deletions assets/js/dashboard/stats/devices/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ class ScreenSizes extends React.Component {
if (this.props.timer) this.props.timer.onTick(this.fetchScreenSizes.bind(this))
}


fetchScreenSizes() {
api.get(
`/api/stats/${encodeURIComponent(this.props.site.domain)}/screen-sizes`,
Expand All @@ -66,21 +65,27 @@ class ScreenSizes extends React.Component {
.then((res) => this.setState({loading: false, sizes: res}))
}

showConversionRate() {
return !!this.props.query.filters.goal
}


label() {
return this.props.query.period === 'realtime' ? 'Current visitors' : 'Visitors'
}

renderScreenSize(size) {
const query = new URLSearchParams(window.location.search)
query.set('screen', size.name)
const maxWidthDeduction = this.showConversionRate() ? "10rem" : "5rem"

return (
<div className="flex items-center justify-between my-1 text-sm" key={size.name}>
<Bar
count={size.count}
all={this.state.sizes}
bg="bg-green-50 dark:bg-gray-500 dark:bg-opacity-15"
maxWidthDeduction="6rem"
maxWidthDeduction={maxWidthDeduction}
>
<span
tooltip={EXPLANATION[size.name]}
Expand All @@ -91,12 +96,10 @@ class ScreenSizes extends React.Component {
</Link>
</span>
</Bar>
<span
className="font-medium dark:text-gray-200"
>
{numberFormatter(size.count)}
<span className="inline-block w-8 text-xs text-right">({size.percentage}%)</span>
<span className="font-medium dark:text-gray-200 text-right w-20">
{numberFormatter(size.count)} <span className="inline-block w-8 text-xs text-right">({size.percentage}%)</span>
</span>
{this.showConversionRate() && <span className="font-medium dark:text-gray-200 w-20 text-right">{numberFormatter(size.conversion_rate)}%</span>}
</div>
)
}
Expand All @@ -109,7 +112,10 @@ class ScreenSizes extends React.Component {
className="flex items-center justify-between mt-3 mb-2 text-xs font-bold tracking-wide text-gray-500"
>
<span>Screen size</span>
<span>{ this.label() }</span>
<div className="text-right">
<span className="inline-block w-20">{ this.label() }</span>
{this.showConversionRate() && <span className="inline-block w-20">CR</span>}
</div>
</div>
{ this.state.sizes && this.state.sizes.map(this.renderScreenSize.bind(this)) }
</React.Fragment>
Expand Down Expand Up @@ -146,14 +152,14 @@ export default class Devices extends React.Component {
}
}


setMode(mode) {
return () => {
storage.setItem(this.tabKey, mode)
this.setState({mode})
}
}

renderContent() {
switch (this.state.mode) {
case 'browser':
Expand Down Expand Up @@ -190,7 +196,7 @@ export default class Devices extends React.Component {
</li>
)
}

return (
<li
className="cursor-pointer hover:text-indigo-600"
Expand Down
15 changes: 12 additions & 3 deletions assets/js/dashboard/stats/devices/operating-systems.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,18 @@ export default class OperatingSystems extends React.Component {
}
}

showConversionRate() {
return !!this.props.query.filters.goal
}

renderOperatingSystem(os) {
const query = new URLSearchParams(window.location.search)
if (this.props.query.filters.os) {
query.set('os_version', os.name)
} else {
query.set('os', os.name)
}
const maxWidthDeduction = this.showConversionRate() ? "10rem" : "5rem"

return (
<div
Expand All @@ -53,15 +58,16 @@ export default class OperatingSystems extends React.Component {
count={os.count}
all={this.state.operatingSystems}
bg="bg-green-50 dark:gray-500 dark:bg-opacity-15"
maxWidthDeduction="6rem"
maxWidthDeduction={maxWidthDeduction}
>
<span className="flex px-2 py-1.5 dark:text-gray-300 relative z-9 break-all">
<Link className="md:truncate block hover:underline" to={{search: query.toString()}}>
{os.name}
</Link>
</span>
</Bar>
<span className="font-medium dark:text-gray-200">{numberFormatter(os.count)} <span className="inline-block w-8 text-xs text-right">({os.percentage}%)</span></span>
<span className="font-medium dark:text-gray-200 text-right w-20">{numberFormatter(os.count)} <span className="inline-block w-8 text-xs text-right">({os.percentage}%)</span></span>
{this.showConversionRate() && <span className="font-medium dark:text-gray-200 w-20 text-right">{numberFormatter(os.conversion_rate)}%</span>}
</div>
)
}
Expand All @@ -78,7 +84,10 @@ export default class OperatingSystems extends React.Component {
<React.Fragment>
<div className="flex items-center justify-between mt-3 mb-2 text-xs font-bold tracking-wide text-gray-500 dark:text-gray-400">
<span>{ key }</span>
<span>{ this.label() }</span>
<div className="text-right">
<span className="inline-block w-20">{ this.label() }</span>
{this.showConversionRate() && <span className="inline-block w-20">CR</span>}
</div>
</div>
{ this.state.operatingSystems && this.state.operatingSystems.map(this.renderOperatingSystem.bind(this)) }
</React.Fragment>
Expand Down
8 changes: 7 additions & 1 deletion assets/js/dashboard/stats/modals/countries.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ class CountriesModal extends React.Component {
return this.state.query.period === 'realtime' ? 'Current visitors' : 'Visitors'
}

showConversionRate() {
return !!this.state.query.filters.goal
}

renderCountry(country) {
const query = new URLSearchParams(window.location.search)
query.set('country', country.name)
Expand All @@ -47,6 +51,7 @@ class CountriesModal extends React.Component {
<td className="p-2 w-32 font-medium" align="right">
{numberFormatter(country.count)} <span className="inline-block text-xs w-8 text-right">({country.percentage}%)</span>
</td>
{this.showConversionRate() && <td className="p-2 w-32 font-medium" align="right">{country.conversion_rate}%</td> }
</tr>
)
}
Expand All @@ -57,7 +62,7 @@ class CountriesModal extends React.Component {
<div className="loading mt-32 mx-auto"><div></div></div>
)
}

if (this.state.countries) {
return (
<>
Expand All @@ -81,6 +86,7 @@ class CountriesModal extends React.Component {
>
{this.label()}
</th>
{this.showConversionRate() && <th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="right">CR</th>}
</tr>
</thead>
<tbody>
Expand Down
10 changes: 10 additions & 0 deletions assets/js/dashboard/stats/modals/entry-pages.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ class EntryPagesModal extends React.Component {
return '-';
}

showConversionRate() {
return !!this.state.query.filters.goal
}

renderPage(page) {
const query = new URLSearchParams(window.location.search)
query.set('entry_page', page.name)
Expand All @@ -76,6 +80,7 @@ class EntryPagesModal extends React.Component {
<td className="p-2 w-32 font-medium" align="right">{numberFormatter(page.count)}</td>
<td className="p-2 w-32 font-medium" align="right">{numberFormatter(page.entries)}</td>
{this.showVisitDuration() && <td className="p-2 w-32 font-medium" align="right">{durationFormatter(page.visit_duration)}</td>}
{this.showConversionRate() && <td className="p-2 w-32 font-medium" align="right">{numberFormatter(page.conversion_rate)}%</td>}
</tr>
)
}
Expand Down Expand Up @@ -125,6 +130,11 @@ class EntryPagesModal extends React.Component {
align="right"
>Visit Duration
</th>
{this.showConversionRate() && <th
className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400"
align="right"
>CR
</th>}
</tr>
</thead>
<tbody>
Expand Down
6 changes: 6 additions & 0 deletions assets/js/dashboard/stats/modals/exit-pages.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ class ExitPagesModal extends React.Component {
}
}

showConversionRate() {
return !!this.state.query.filters.goal
}

renderPage(page) {
const query = new URLSearchParams(window.location.search)
query.set('exit_page', page.name)
Expand All @@ -54,6 +58,7 @@ class ExitPagesModal extends React.Component {
<td className="p-2 w-32 font-medium" align="right">{numberFormatter(page.count)}</td>
<td className="p-2 w-32 font-medium" align="right">{numberFormatter(page.exits)}</td>
<td className="p-2 w-32 font-medium" align="right">{this.formatPercentage(page.exit_rate)}</td>
{this.showConversionRate() && <td className="p-2 w-32 font-medium" align="right">{numberFormatter(page.conversion_rate)}%</td>}
</tr>
)
}
Expand Down Expand Up @@ -87,6 +92,7 @@ class ExitPagesModal extends React.Component {
<th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="right">Unique Exits</th>
<th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="right">Total Exits</th>
<th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="right">Exit Rate</th>
{this.showConversionRate() && <th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="right">CR</th>}
</tr>
</thead>
<tbody>
Expand Down
6 changes: 6 additions & 0 deletions assets/js/dashboard/stats/modals/pages.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ class PagesModal extends React.Component {
return this.state.query.period !== 'realtime' && !(filters.goal || filters.source || filters.referrer)
}

showConversionRate() {
return !!this.state.query.filters.goal
}

formatBounceRate(page) {
if (typeof(page.bounce_rate) === 'number') {
return page.bounce_rate + '%'
Expand All @@ -66,6 +70,7 @@ class PagesModal extends React.Component {
{this.showPageviews() && <td className="p-2 w-32 font-medium" align="right">{numberFormatter(page.pageviews)}</td> }
{this.showExtra() && <td className="p-2 w-32 font-medium" align="right">{this.formatBounceRate(page)}</td> }
{this.showExtra() && <td className="p-2 w-32 font-medium" align="right">{timeOnPage}</td> }
{this.showConversionRate() && <td className="p-2 w-32 font-medium" align="right">{page.conversion_rate}%</td> }
</tr>
)
}
Expand Down Expand Up @@ -104,6 +109,7 @@ class PagesModal extends React.Component {
{this.showPageviews() && <th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="right">Pageviews</th>}
{this.showExtra() && <th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="right">Bounce rate</th>}
{this.showExtra() && <th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="right">Time on Page</th>}
{this.showConversionRate() && <th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="right">CR</th>}
</tr>
</thead>
<tbody>
Expand Down
8 changes: 8 additions & 0 deletions assets/js/dashboard/stats/modals/sources.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ class SourcesModal extends React.Component {
return this.state.query.period !== 'realtime' && !this.state.query.filters.goal
}

showConversionRate() {
return !!this.state.query.filters.goal
}

loadMore() {
this.setState({loading: true, page: this.state.page + 1}, this.loadSources.bind(this))
}
Expand Down Expand Up @@ -82,6 +86,8 @@ class SourcesModal extends React.Component {
if (filter === 'utm_sources') query.set('utm_source', source.name)
if (filter === 'utm_campaigns') query.set('utm_campaign', source.name)

console.log(source)

return (
<tr className="text-sm dark:text-gray-200" key={source.name}>
<td className="p-2">
Expand All @@ -94,6 +100,7 @@ class SourcesModal extends React.Component {
<td className="p-2 w-32 font-medium" align="right">{numberFormatter(source.count)}</td>
{this.showExtra() && <td className="p-2 w-32 font-medium" align="right">{this.formatBounceRate(source)}</td> }
{this.showExtra() && <td className="p-2 w-32 font-medium" align="right">{this.formatDuration(source)}</td> }
{this.showConversionRate() && <td className="p-2 w-32 font-medium" align="right">{source.conversion_rate}%</td> }
</tr>
)
}
Expand Down Expand Up @@ -135,6 +142,7 @@ class SourcesModal extends React.Component {
<th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="right">{this.label()}</th>
{this.showExtra() && <th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="right">Bounce rate</th>}
{this.showExtra() && <th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="right">Visit duration</th>}
{this.showConversionRate() && <th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="right">CR</th>}
</tr>
</thead>
<tbody>
Expand Down
Loading

0 comments on commit c27f6bc

Please sign in to comment.