Skip to content

Commit 2b1a42f

Browse files
committed
Add option to unpack percentage column and hide metrics if all null
1 parent ccd77c9 commit 2b1a42f

7 files changed

Lines changed: 90 additions & 17 deletions

File tree

assets/js/dashboard/stats/behaviours/conversions.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export default function Conversions({
7272
alwaysOnFilters={reportConfig.alwaysOnFilters}
7373
DimensionElement={DimensionElement}
7474
onDataReady={afterFetchData}
75+
hideMetricsIfAllNull={['total_revenue', 'average_revenue']}
7576
/>
7677
)
7778
}

assets/js/dashboard/stats/behaviours/props.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export default function Properties({
8686
alwaysOnFilters={reportConfig.alwaysOnFilters}
8787
DimensionElement={DimensionElement}
8888
onDataReady={afterFetchData}
89+
bundlePercentageWithVisitors={false}
8990
/>
9091
</div>
9192
)

assets/js/dashboard/stats/metrics.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,6 @@ export const getBreakdownMetricLabel = (
7676
dimensions: string[]
7777
}
7878
): string => {
79-
if (dimensions[0]?.startsWith('event:props:')) {
80-
return getCustomPropsBreakdownMetricLabel(metric)
81-
}
82-
8379
switch (dimensions[0]) {
8480
case 'visit:entry_page':
8581
return getEntryPagesBreakdownMetricLabel(metric, {
@@ -93,11 +89,15 @@ export const getBreakdownMetricLabel = (
9389
})
9490
case 'event:goal':
9591
return getConversionsBreakdownMetricLabel(metric)
96-
default:
92+
default: {
93+
if (dimensions[0]?.startsWith('event:props:')) {
94+
return getCustomPropsBreakdownMetricLabel(metric)
95+
}
9796
return getDefaultBreakdownMetricLabel(metric, {
9897
hasConversionGoalFilter,
9998
isRealtime
10099
})
100+
}
101101
}
102102
}
103103

@@ -107,6 +107,8 @@ const getCustomPropsBreakdownMetricLabel = (metric: Metric): string => {
107107
return 'Visitors'
108108
case 'events':
109109
return 'Events'
110+
case 'percentage':
111+
return '%'
110112
default:
111113
return getDefaultBreakdownMetricLabel(metric, {
112114
hasConversionGoalFilter: false,

assets/js/dashboard/stats/modals/conversions.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ function ConversionsModal() {
5050
alwaysOnFilters={reportConfig.alwaysOnFilters}
5151
defaultOrderBy={[['visitors', 'desc']]}
5252
DimensionElement={GoalsDimensionCell}
53+
hideMetricsIfAllNull={['total_revenue', 'average_revenue']}
5354
/>
5455
</Modal>
5556
)

assets/js/dashboard/stats/modals/details-breakdown.tsx

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,18 @@ type DetailsBreakdownProps = SharedBreakdownReportProps & {
6060
searchEnabled?: boolean
6161
onDataReady?: (data: PaginatedData) => void
6262
DimensionElement: (props: DimensionCellProps) => ReactNode
63+
/**
64+
* When true (default), `percentage` is shown inline inside the Visitors
65+
* cell rather than as its own column. Set to false for reports that want
66+
* percentage as a separate breakdown column (e.g. custom properties).
67+
*/
68+
bundlePercentageWithVisitors?: boolean
69+
/**
70+
* Metrics that should be dropped from the rendered columns when every row
71+
* (across all loaded pages) has null for that metric. Used by the goals
72+
* modal to hide revenue columns when the current rows have no revenue data.
73+
*/
74+
hideMetricsIfAllNull?: Metric[]
6375
}
6476

6577
const getMetricCellWidthClass = (
@@ -90,7 +102,9 @@ export function DetailsBreakdown({
90102
defaultOrderBy = [] as MetricOrderBy,
91103
DimensionElement,
92104
searchEnabled = true,
93-
onDataReady
105+
onDataReady,
106+
bundlePercentageWithVisitors = true,
107+
hideMetricsIfAllNull
94108
}: DetailsBreakdownProps) {
95109
const site = useSiteContext()
96110
const { dashboardState } = useDashboardStateContext()
@@ -159,14 +173,30 @@ export function DetailsBreakdown({
159173
[dashboardState, dimensions]
160174
)
161175

176+
const columnsHiddenForAllNull = useMemo((): Set<Metric> => {
177+
const hidden = new Set<Metric>()
178+
if (!hideMetricsIfAllNull || !apiState.data?.pages?.length || !query) {
179+
return hidden
180+
}
181+
for (const metric of hideMetricsIfAllNull) {
182+
const idx = query.metrics.indexOf(metric)
183+
if (idx === -1) continue
184+
const allNull = apiState.data.pages.every((page) =>
185+
page.results.every((row) => row.metrics[idx] == null)
186+
)
187+
if (allNull) hidden.add(metric)
188+
}
189+
return hidden
190+
}, [apiState.data, query, hideMetricsIfAllNull])
191+
162192
const columns: ColumnConfiguration<QueryResultRow>[] | null = useMemo(() => {
163193
if (!query) return null
164194

165195
const filterDimension = query.dimensions[0] as NonTimeDimension
166196

167197
const hasPercentage = query.metrics.includes('percentage')
168198
const isVisitorsWithPercentageCell = (m: Metric) =>
169-
hasPercentage && m === 'visitors'
199+
bundlePercentageWithVisitors && hasPercentage && m === 'visitors'
170200

171201
return [
172202
{
@@ -183,8 +213,10 @@ export function DetailsBreakdown({
183213
align: 'left'
184214
},
185215
...query.metrics
186-
// Percentage is not its own column — shown inline in the visitors cell
187-
.filter((metric) => metric !== 'percentage')
216+
.filter((metric) => {
217+
if (columnsHiddenForAllNull.has(metric)) return false
218+
return !(bundlePercentageWithVisitors && metric === 'percentage')
219+
})
188220
.map(
189221
(metric): ColumnConfiguration<QueryResultRow> => ({
190222
key: metric,
@@ -236,7 +268,9 @@ export function DetailsBreakdown({
236268
meta,
237269
orderByDictionary,
238270
toggleSortByMetric,
239-
metricLabelFor
271+
metricLabelFor,
272+
bundlePercentageWithVisitors,
273+
columnsHiddenForAllNull
240274
])
241275

242276
const tableData = apiState.data

assets/js/dashboard/stats/modals/props.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ function PropsModal() {
7171
alwaysOnFilters={reportConfig.alwaysOnFilters}
7272
defaultOrderBy={[['visitors', 'desc']]}
7373
DimensionElement={DimensionElementForProp}
74+
bundlePercentageWithVisitors={false}
7475
/>
7576
</Modal>
7677
)

assets/js/dashboard/stats/reports/index-breakdown.tsx

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,18 @@ type IndexBreakdownProps = SharedBreakdownReportProps & {
5353
metricColumnWidth?: string
5454
DimensionElement: (props: DimensionCellWithBarProps) => ReactNode
5555
onDataReady?: (data: QueryApiResponse) => void
56+
/**
57+
* When true (default), `percentage` is shown inline inside the Visitors
58+
* cell rather than as its own column. Set to false for reports that want
59+
* percentage as a separate breakdown column (e.g. custom properties).
60+
*/
61+
bundlePercentageWithVisitors?: boolean
62+
/**
63+
* Metrics that should be dropped from the rendered columns when every row's
64+
* value for that metric is null. Used by the goals report to hide revenue
65+
* columns when the current rows have no revenue data.
66+
*/
67+
hideMetricsIfAllNull?: Metric[]
5668
}
5769

5870
export function IndexBreakdown({
@@ -62,7 +74,9 @@ export function IndexBreakdown({
6274
dimensionLabel,
6375
alwaysOnFilters,
6476
onDataReady,
65-
metricColumnWidth = DEFAULT_METRIC_COLUMN_WIDTH
77+
metricColumnWidth = DEFAULT_METRIC_COLUMN_WIDTH,
78+
bundlePercentageWithVisitors = true,
79+
hideMetricsIfAllNull
6680
}: IndexBreakdownProps) {
6781
const site = useSiteContext()
6882
const { dashboardState } = useDashboardStateContext()
@@ -123,19 +137,36 @@ export function IndexBreakdown({
123137
[dashboardState, dimensions]
124138
)
125139

140+
const columnsHiddenForAllNull = useMemo((): Set<Metric> => {
141+
const hidden = new Set<Metric>()
142+
if (!hideMetricsIfAllNull || !apiState.data || !query) return hidden
143+
for (const metric of hideMetricsIfAllNull) {
144+
const idx = query.metrics.indexOf(metric)
145+
if (idx === -1) continue
146+
const allNull = apiState.data.results.every(
147+
(row) => row.metrics[idx] == null
148+
)
149+
if (allNull) hidden.add(metric)
150+
}
151+
return hidden
152+
}, [apiState.data, query, hideMetricsIfAllNull])
153+
126154
const columns = useMemo((): ColumnConfiguration<QueryResultRow>[] | null => {
127155
if (!query || barMetricIndex === null || barMaxValue === null) return null
128156

129-
// Only render columns for metrics the API actually returned. Also,
130-
// percentage is not its own column —- it's shown inline in the
131-
// visitors cell instead.
132-
const filteredMetrics = query.metrics.filter((m) => m !== 'percentage')
157+
// Only render columns for metrics the API actually returned. When
158+
// bundlePercentageWithVisitors is on (default), `percentage` is shown
159+
// inline in the Visitors cell rather than as its own column.
160+
const filteredMetrics = query.metrics.filter((m) => {
161+
if (columnsHiddenForAllNull.has(m)) return false
162+
return !(bundlePercentageWithVisitors && m === 'percentage')
163+
})
133164

134165
const filterDimension = query.dimensions[0] as NonTimeDimension
135166

136167
const hasPercentage = query.metrics.includes('percentage')
137168
const isVisitorsWithPercentageCell = (m: Metric) =>
138-
hasPercentage && m === 'visitors'
169+
bundlePercentageWithVisitors && hasPercentage && m === 'visitors'
139170

140171
return [
141172
{
@@ -191,7 +222,9 @@ export function IndexBreakdown({
191222
metricLabelFor,
192223
barMaxValue,
193224
query,
194-
metricColumnWidth
225+
metricColumnWidth,
226+
bundlePercentageWithVisitors,
227+
columnsHiddenForAllNull
195228
])
196229

197230
return (

0 commit comments

Comments
 (0)