Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dashboard: show comparison for breakdowns #4692

Open
wants to merge 57 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
5e5277a
Comparisons: Move code to LegacyQueryBuilder
macobo Oct 9, 2024
77aebf8
WIP: Return comparison results to frontend
macobo Oct 9, 2024
dc599f4
refactor: remove useless param
macobo Oct 9, 2024
6004c43
Different result format
macobo Oct 9, 2024
5ff03f9
Pass object to metric.renderValue
macobo Oct 9, 2024
8b13ff9
remove dead code
macobo Oct 9, 2024
0ddf6b8
Fixup response format
macobo Oct 9, 2024
2bede45
Comparison in a tooltip
macobo Oct 9, 2024
fad378d
Simple change arrow
macobo Oct 9, 2024
b2fdf48
Extract metric entry to ts
macobo Oct 10, 2024
664b807
popper attempt WIP
macobo Oct 10, 2024
85535d4
Slightly nicer content
macobo Oct 10, 2024
68a15d6
Solve warning
macobo Oct 15, 2024
95780aa
Unified changeArrow in app
macobo Oct 15, 2024
8ca9cb8
Remove needless spanning
macobo Oct 15, 2024
d4fc03b
Always set `graph_metric` in top stats.
macobo Oct 15, 2024
7fc479d
Remove dead code
macobo Oct 15, 2024
cf6b4a4
Move Money module under dashboard utils, keep in build
macobo Oct 15, 2024
b674a09
change <Metric /> definition to take in a `formatter` and store defau…
macobo Oct 15, 2024
effa6db
Use standard system for formatting numbers
macobo Oct 15, 2024
166e4f3
Arrows only in table
macobo Oct 15, 2024
b5753ff
remove dead import
macobo Oct 15, 2024
e2842aa
Inline renderValue
macobo Oct 15, 2024
4e67376
Render metric name in tooltip
macobo Oct 15, 2024
af1ea58
numberFormatter -> numberShortFormatter
macobo Oct 16, 2024
59fc3d0
numberShortFormatter update
macobo Oct 16, 2024
a12a47b
Separate long/short formatters
macobo Oct 16, 2024
f0825f8
Use long vs short formatters
macobo Oct 16, 2024
f8aa750
Put column name into tooltip
macobo Oct 16, 2024
9547522
Slightly improved label handling for percentages, conversion rate
macobo Oct 16, 2024
b0cd1b2
Improved boundary handling in tooltip.js
macobo Oct 16, 2024
9a79c38
Iterate tooltips, no tooltip for - revenue
macobo Oct 16, 2024
c24a1aa
Update top stats tests after graph_metric change
macobo Oct 16, 2024
1f49979
Change revenue metrics stats API return structure
macobo Oct 16, 2024
a418b79
useQueryContext in a component
macobo Oct 16, 2024
0f0ded2
graph_metric for current visitors to fix realtime view
macobo Oct 16, 2024
984a9d6
No tooltips if fully - row
macobo Oct 16, 2024
a62ef8e
renderValue as a proper function
macobo Oct 16, 2024
b89c87f
Simplify MetricEntry
macobo Oct 16, 2024
67709a7
Use common const
macobo Oct 16, 2024
a4dddc5
tooltip to typescript
macobo Oct 16, 2024
54682b2
More explicit return structure
macobo Oct 16, 2024
918313a
metric-entry -> metric-value
macobo Oct 16, 2024
bd1837b
Restore some files
macobo Oct 16, 2024
52a5ceb
ChangeArrow
macobo Oct 16, 2024
2716520
Restore MoreLink
macobo Oct 16, 2024
00cf801
Fix typing in MoreLink
macobo Oct 16, 2024
d9181df
<MetricValue />
macobo Oct 16, 2024
3bf280b
Tests for MetricValue and ChangeArrow
macobo Oct 16, 2024
849b25b
details modal fixups
macobo Oct 16, 2024
1f34a36
re-add space between arrow and percentage
macobo Oct 16, 2024
5305c48
Solve stylelint issues
macobo Oct 17, 2024
4df694f
Update test
macobo Oct 17, 2024
67ba1b3
Format
macobo Oct 17, 2024
0dadc44
Add flag `breakdown_comparisons_ui`
macobo Oct 17, 2024
6343953
reformat
macobo Oct 17, 2024
a8d62b6
Remove no change icon, better alignment
ukutaht Oct 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions assets/js/dashboard/components/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export type ColumnConfiguraton<T extends Record<string, unknown>> = {
/** Unique column ID, used for sorting purposes and to get the value of the cell using rowItem[key] */
key: keyof T
/** Column title */
label: ReactNode
label: string
/** If defined, the column is considered sortable. @see SortButton */
onSort?: () => void
sortDirection?: SortDirection
Expand All @@ -20,7 +20,7 @@ export type ColumnConfiguraton<T extends Record<string, unknown>> = {
/**
* Function used to transform the value found at item[key] for the cell. Superseded by renderItem if present. @example 1120 => "1.1k"
*/
renderValue?: (value: unknown) => ReactNode
renderValue?: (item: T) => ReactNode
/** Function used to create richer cells */
renderItem?: (item: T) => ReactNode
}
Expand Down Expand Up @@ -85,7 +85,7 @@ export const ItemRow = <T extends Record<string, string | number | ReactNode>>({
{renderItem
? renderItem(item)
: renderValue
? renderValue(item[key])
? renderValue(item)
: (item[key] ?? '')}
</TableCell>
))}
Expand Down
6 changes: 3 additions & 3 deletions assets/js/dashboard/extra/funnel.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import FlipMove from 'react-flip-move';
import Chart from 'chart.js/auto';
import FunnelTooltip from './funnel-tooltip';
import ChartDataLabels from 'chartjs-plugin-datalabels';
import numberFormatter from '../util/number-formatter';
import { numberShortFormatter } from '../util/number-formatter';
import Bar from '../stats/bar';

import RocketIcon from '../stats/modals/rocket-icon';
Expand Down Expand Up @@ -103,7 +103,7 @@ export default function Funnel({ funnelName, tabs }) {
const formatDataLabel = (visitors, ctx) => {
if (ctx.dataset.label === 'Visitors') {
const conversionRate = funnel.steps[ctx.dataIndex].conversion_rate
return `${conversionRate}% \n(${numberFormatter(visitors)} Visitors)`
return `${conversionRate}% \n(${numberShortFormatter(visitors)} Visitors)`
} else {
return null
}
Expand Down Expand Up @@ -330,7 +330,7 @@ export default function Funnel({ funnelName, tabs }) {
</Bar>

<span className="font-medium dark:text-gray-200 w-20 text-right" tooltip={step.visitors.toLocaleString()}>
{numberFormatter(step.visitors)}
{numberShortFormatter(step.visitors)}
</span>
</div>
</>
Expand Down
9 changes: 0 additions & 9 deletions assets/js/dashboard/extra/money.js

This file was deleted.

2 changes: 1 addition & 1 deletion assets/js/dashboard/site-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const siteContextDefaultValue = {
embedded: false,
background: undefined as string | undefined,
isDbip: false,
flags: {},
flags: {} as { breakdown_comparisons_ui?: boolean },
validIntervalsByPeriod: {} as Record<string, Array<string>>,
shared: false
}
Expand Down
5 changes: 3 additions & 2 deletions assets/js/dashboard/stats/graph/graph-tooltip.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { METRIC_FORMATTER, METRIC_LABELS } from './graph-util'
import dateFormatter from './date-formatter'
import { METRIC_LABELS } from './graph-util'
import { MetricFormatterShort } from '../reports/metric-formatter'

const renderBucketLabel = function(query, graphData, label, comparison = false) {
let isPeriodFull = graphData.full_intervals?.[label]
Expand Down Expand Up @@ -44,7 +45,7 @@ const buildTooltipData = function(query, graphData, metric, tooltipModel) {
const comparisonValue = comparisonData?.raw || 0
const comparisonDifference = label && comparisonLabel && calculatePercentageDifference(comparisonValue, value)

const metricFormatter = METRIC_FORMATTER[metric]
const metricFormatter = MetricFormatterShort[metric]
const formattedValue = metricFormatter(value)
const formattedComparisonValue = comparisonData && metricFormatter(comparisonValue)

Expand Down
15 changes: 0 additions & 15 deletions assets/js/dashboard/stats/graph/graph-util.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import numberFormatter, {durationFormatter} from '../../util/number-formatter'
import { getFiltersByKeyPrefix, hasGoalFilter } from '../../util/filters'
import { revenueAvailable } from '../../query'

Expand Down Expand Up @@ -36,20 +35,6 @@ export const METRIC_LABELS = {
'total_revenue': 'Total Revenue',
}

export const METRIC_FORMATTER = {
'visitors': numberFormatter,
'pageviews': numberFormatter,
'events': numberFormatter,
'visits': numberFormatter,
'views_per_visit': (number) => (number),
'bounce_rate': (number) => (`${number}%`),
'visit_duration': durationFormatter,
'conversions': numberFormatter,
'conversion_rate': (number) => (`${number}%`),
'total_revenue': numberFormatter,
'average_revenue': numberFormatter,
}

const buildComparisonDataset = function(comparisonPlot) {
if (!comparisonPlot) return []

Expand Down
5 changes: 3 additions & 2 deletions assets/js/dashboard/stats/graph/line-graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { useAppNavigate } from '../../navigation/use-app-navigate';
import { useQueryContext } from '../../query-context';
import Chart from 'chart.js/auto';
import GraphTooltip from './graph-tooltip'
import { buildDataSet, METRIC_LABELS, METRIC_FORMATTER } from './graph-util'
import { buildDataSet, METRIC_LABELS } from './graph-util'
import dateFormatter from './date-formatter';
import FadeIn from '../../fade-in';
import classNames from 'classnames';
import { hasGoalFilter } from '../../util/filters';
import { MetricFormatterShort } from '../reports/metric-formatter'

const calculateMaximumY = function(dataset) {
const yAxisValues = dataset
Expand Down Expand Up @@ -76,7 +77,7 @@ class LineGraph extends React.Component {
min: 0,
suggestedMax: calculateMaximumY(dataSet),
ticks: {
callback: METRIC_FORMATTER[metric],
callback: MetricFormatterShort[metric],
color: this.props.darkTheme ? 'rgb(243, 244, 246)' : undefined
},
grid: {
Expand Down
101 changes: 29 additions & 72 deletions assets/js/dashboard/stats/graph/top-stats.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ import React from 'react'
import { Tooltip } from '../../util/tooltip'
import { SecondsSinceLastLoad } from '../../util/seconds-since-last-load'
import classNames from 'classnames'
import numberFormatter, { durationFormatter } from '../../util/number-formatter'
import * as storage from '../../util/storage'
import { formatDateRange } from '../../util/date'
import { getGraphableMetrics } from './graph-util'
import { useQueryContext } from '../../query-context'
import { useSiteContext } from '../../site-context'
import { useLastLoadContext } from '../../last-load-context'
import { ChangeArrow } from '../reports/change-arrow'
import {
MetricFormatterShort,
MetricFormatterLong
} from '../reports/metric-formatter'

function Maybe({ condition, children }) {
if (condition) {
Expand All @@ -20,68 +24,14 @@ function Maybe({ condition, children }) {
}
}

function renderPercentageComparison(name, comparison, forceDarkBg = false) {
const formattedComparison = numberFormatter(Math.abs(comparison))

const defaultClassName = classNames({
'pl-2 text-xs dark:text-gray-100': !forceDarkBg,
'pl-2 text-xs text-gray-100': forceDarkBg
})

const noChangeClassName = classNames({
'pl-2 text-xs text-gray-700 dark:text-gray-300': !forceDarkBg,
'pl-2 text-xs text-gray-300': forceDarkBg
})

if (comparison > 0) {
const color = name === 'Bounce rate' ? 'text-red-400' : 'text-green-500'
return (
<span className={defaultClassName}>
<span className={color + ' font-bold'}>&uarr;</span>{' '}
{formattedComparison}%
</span>
)
} else if (comparison < 0) {
const color = name === 'Bounce rate' ? 'text-green-500' : 'text-red-400'
return (
<span className={defaultClassName}>
<span className={color + ' font-bold'}>&darr;</span>{' '}
{formattedComparison}%
</span>
)
} else if (comparison === 0) {
return <span className={noChangeClassName}>&#12336; 0%</span>
} else {
return null
}
}

function topStatNumberShort(name, value) {
if (['visit duration', 'time on page'].includes(name.toLowerCase())) {
return durationFormatter(value)
} else if (['bounce rate', 'conversion rate'].includes(name.toLowerCase())) {
return value + '%'
} else if (
['average revenue', 'total revenue'].includes(name.toLowerCase())
) {
return value?.short
} else {
return numberFormatter(value)
}
function topStatNumberShort(metric, value) {
const formatter = MetricFormatterShort[metric]
return formatter(value)
}

function topStatNumberLong(name, value) {
if (['visit duration', 'time on page'].includes(name.toLowerCase())) {
return durationFormatter(value)
} else if (['bounce rate', 'conversion rate'].includes(name.toLowerCase())) {
return value + '%'
} else if (
['average revenue', 'total revenue'].includes(name.toLowerCase())
) {
return value?.long
} else {
return (value || 0).toLocaleString()
}
function topStatNumberLong(metric, value) {
const formatter = MetricFormatterLong[metric]
return formatter(value)
}

export default function TopStats({ data, onMetricUpdate, tooltipBoundary }) {
Expand All @@ -97,17 +47,20 @@ export default function TopStats({ data, onMetricUpdate, tooltipBoundary }) {
<div>
{query.comparison && (
<div className="whitespace-nowrap">
{topStatNumberLong(stat.name, stat.value)} vs.{' '}
{topStatNumberLong(stat.name, stat.comparison_value)} {statName}
<span className="ml-2">
{renderPercentageComparison(stat.name, stat.change, true)}
</span>
{topStatNumberLong(stat.graph_metric, stat.value)} vs.{' '}
{topStatNumberLong(stat.graph_metric, stat.comparison_value)}{' '}
{statName}
<ChangeArrow
metric={stat.graph_metric}
change={stat.change}
className="pl-4 text-xs text-gray-100"
/>
</div>
)}

{!query.comparison && (
<div className="whitespace-nowrap">
{topStatNumberLong(stat.name, stat.value)} {statName}
{topStatNumberLong(stat.graph_metric, stat.value)} {statName}
</div>
)}

Expand All @@ -123,7 +76,7 @@ export default function TopStats({ data, onMetricUpdate, tooltipBoundary }) {

function canMetricBeGraphed(stat) {
const graphableMetrics = getGraphableMetrics(query, site)
return stat.graph_metric && graphableMetrics.includes(stat.graph_metric)
return graphableMetrics.includes(stat.graph_metric)
}

function maybeUpdateMetric(stat) {
Expand Down Expand Up @@ -200,10 +153,14 @@ export default function TopStats({ data, onMetricUpdate, tooltipBoundary }) {
className="font-bold text-xl dark:text-gray-100"
id={stat.graph_metric}
>
{topStatNumberShort(stat.name, stat.value)}
{topStatNumberShort(stat.graph_metric, stat.value)}
</p>
<Maybe condition={!query.comparison}>
{renderPercentageComparison(stat.name, stat.change)}
<Maybe condition={!query.comparison && stat.change != null}>
<ChangeArrow
metric={stat.graph_metric}
change={stat.change}
className="pl-2 text-xs dark:text-gray-100"
/>
</Maybe>
</span>
<Maybe condition={query.comparison}>
Expand All @@ -216,7 +173,7 @@ export default function TopStats({ data, onMetricUpdate, tooltipBoundary }) {
<Maybe condition={query.comparison}>
<div>
<p className="font-bold text-xl text-gray-500 dark:text-gray-400">
{topStatNumberShort(stat.name, stat.comparison_value)}
{topStatNumberShort(stat.graph_metric, stat.comparison_value)}
</p>
<p className="text-xs text-gray-500 dark:text-gray-400">
{formatDateRange(site, data.comparing_from, data.comparing_to)}
Expand Down
4 changes: 2 additions & 2 deletions assets/js/dashboard/stats/locations/map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import classNames from 'classnames'
import * as api from '../../api'
import { replaceFilterByPrefix, cleanLabels } from '../../util/filters'
import { useAppNavigate } from '../../navigation/use-app-navigate'
import numberFormatter from '../../util/number-formatter'
import { numberShortFormatter } from '../../util/number-formatter'
import * as topojson from 'topojson-client'
import { useQuery } from '@tanstack/react-query'
import { useSiteContext } from '../../site-context'
Expand Down Expand Up @@ -165,7 +165,7 @@ const WorldMap = ({
x={tooltip.x}
y={tooltip.y}
name={hoveredCountryData.name}
value={numberFormatter(hoveredCountryData.visitors)}
value={numberShortFormatter(hoveredCountryData.visitors)}
label={
labels[hoveredCountryData.visitors === 1 ? 'singular' : 'plural']
}
Expand Down
13 changes: 5 additions & 8 deletions assets/js/dashboard/stats/modals/google-keywords.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,9 @@ import Modal from './modal'
import { useQueryContext } from '../../query-context'
import { useSiteContext } from '../../site-context'
import { usePaginatedGetAPI } from '../../hooks/api-client'
import { createVisitors, Metric } from '../reports/metrics'
import {
createVisitors,
Metric,
renderNumberWithTooltip
} from '../reports/metrics'
import numberFormatter, {
numberShortFormatter,
percentageFormatter
} from '../../util/number-formatter'
import { apiPath } from '../../util/url'
Expand All @@ -33,21 +30,21 @@ const metrics = [
width: 'w-28',
key: 'impressions',
renderLabel: () => 'Impressions',
renderValue: renderNumberWithTooltip,
formatter: numberShortFormatter,
sortable: false
}),
new Metric({
width: 'w-16',
key: 'ctr',
renderLabel: () => 'CTR',
renderValue: percentageFormatter,
formatter: percentageFormatter,
sortable: false
}),
new Metric({
width: 'w-28',
key: 'position',
renderLabel: () => 'Position',
renderValue: numberFormatter,
formatter: numberShortFormatter,
sortable: false
})
]
Expand Down
Loading
Loading