Skip to content

Commit 03bdabc

Browse files
authored
feat(Funnels): Make detailed results table for Funnels sortable (#30298)
1 parent 8cd6168 commit 03bdabc

17 files changed

+54
-2
lines changed
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading

frontend/src/scenes/insights/views/Funnels/FunnelStepsTable.tsx

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { LemonRow } from 'lib/lemon-ui/LemonRow'
77
import { LemonTable, LemonTableColumn, LemonTableColumnGroup } from 'lib/lemon-ui/LemonTable'
88
import { Lettermark, LettermarkColor } from 'lib/lemon-ui/Lettermark'
99
import { humanFriendlyDuration, humanFriendlyNumber, percentage } from 'lib/utils'
10+
import { compare as compareFn } from 'natural-orderby'
1011
import { funnelDataLogic } from 'scenes/funnels/funnelDataLogic'
1112
import { funnelPersonsModalLogic } from 'scenes/funnels/funnelPersonsModalLogic'
1213
import { getVisibilityKey } from 'scenes/funnels/funnelUtils'
@@ -75,6 +76,31 @@ export function FunnelStepsTable(): JSX.Element | null {
7576
/>
7677
),
7778
dataIndex: 'breakdown_value',
79+
sorter: (a: FlattenedFunnelStepByBreakdown, b: FlattenedFunnelStepByBreakdown) => {
80+
// Unwrap breakdown values to compare them properly
81+
const valueA = a.breakdown_value?.length == 1 ? a.breakdown_value[0] : a.breakdown_value
82+
const valueB = b.breakdown_value?.length == 1 ? b.breakdown_value[0] : b.breakdown_value
83+
84+
// For numeric values, use numeric comparison
85+
if (typeof valueA === 'number' && typeof valueB === 'number') {
86+
return valueA - valueB
87+
}
88+
89+
// For string values, use string comparison
90+
const labelA = formatBreakdownLabel(
91+
valueA,
92+
breakdownFilter,
93+
allCohorts.results,
94+
formatPropertyValueForDisplay
95+
)
96+
const labelB = formatBreakdownLabel(
97+
valueB,
98+
breakdownFilter,
99+
allCohorts.results,
100+
formatPropertyValueForDisplay
101+
)
102+
return compareFn()(labelA, labelB)
103+
},
78104
render: function RenderBreakdownValue(
79105
_: void,
80106
breakdown: FlattenedFunnelStepByBreakdown
@@ -127,9 +153,12 @@ export function FunnelStepsTable(): JSX.Element | null {
127153
conversion
128154
</>
129155
),
156+
key: 'total_conversion',
130157
render: (_: void, breakdown: FlattenedFunnelStepByBreakdown) =>
131158
percentage(breakdown?.conversionRates?.total ?? 0, 2, true),
132159
align: 'right',
160+
sorter: (a: FlattenedFunnelStepByBreakdown, b: FlattenedFunnelStepByBreakdown) =>
161+
(a?.conversionRates?.total ?? 0) - (b?.conversionRates?.total ?? 0),
133162
},
134163
],
135164
},
@@ -146,6 +175,7 @@ export function FunnelStepsTable(): JSX.Element | null {
146175
children: [
147176
{
148177
title: stepIndex === 0 ? 'Entered' : 'Converted',
178+
key: `step_${stepIndex}_conversion`,
149179
render: function RenderCompleted(
150180
_: void,
151181
breakdown: FlattenedFunnelStepByBreakdown
@@ -166,14 +196,16 @@ export function FunnelStepsTable(): JSX.Element | null {
166196
))
167197
)
168198
},
169-
170199
align: 'right',
200+
sorter: (a: FlattenedFunnelStepByBreakdown, b: FlattenedFunnelStepByBreakdown) =>
201+
(a.steps?.[stepIndex]?.count ?? 0) - (b.steps?.[stepIndex]?.count ?? 0),
171202
},
172203
...(stepIndex === 0
173204
? []
174205
: [
175206
{
176-
title: 'Dropped off',
207+
title: 'Dropped off',
208+
key: `step_${stepIndex}_dropoff`,
177209
render: function RenderDropped(
178210
_: void,
179211
breakdown: FlattenedFunnelStepByBreakdown
@@ -199,6 +231,9 @@ export function FunnelStepsTable(): JSX.Element | null {
199231
)
200232
},
201233
align: 'right',
234+
sorter: (a: FlattenedFunnelStepByBreakdown, b: FlattenedFunnelStepByBreakdown) =>
235+
(a.steps?.[stepIndex]?.droppedOffFromPrevious ?? 0) -
236+
(b.steps?.[stepIndex]?.droppedOffFromPrevious ?? 0),
202237
},
203238
]),
204239
{
@@ -209,6 +244,7 @@ export function FunnelStepsTable(): JSX.Element | null {
209244
so&nbsp;far
210245
</>
211246
),
247+
key: `step_${stepIndex}_conversion_so_far`,
212248
render: function RenderConversionSoFar(
213249
_: void,
214250
breakdown: FlattenedFunnelStepByBreakdown
@@ -228,6 +264,9 @@ export function FunnelStepsTable(): JSX.Element | null {
228264
)
229265
},
230266
align: 'right',
267+
sorter: (a: FlattenedFunnelStepByBreakdown, b: FlattenedFunnelStepByBreakdown) =>
268+
(a.steps?.[step.order]?.conversionRates.total ?? 0) -
269+
(b.steps?.[step.order]?.conversionRates.total ?? 0),
231270
},
232271
...(stepIndex === 0
233272
? []
@@ -240,6 +279,7 @@ export function FunnelStepsTable(): JSX.Element | null {
240279
from&nbsp;previous
241280
</>
242281
),
282+
key: `step_${stepIndex}_conversion_from_prev`,
243283
render: function RenderConversionFromPrevious(
244284
_: void,
245285
breakdown: FlattenedFunnelStepByBreakdown
@@ -268,6 +308,9 @@ export function FunnelStepsTable(): JSX.Element | null {
268308
)
269309
},
270310
align: 'right',
311+
sorter: (a: FlattenedFunnelStepByBreakdown, b: FlattenedFunnelStepByBreakdown) =>
312+
(a.steps?.[step.order]?.conversionRates.fromPrevious ?? 0) -
313+
(b.steps?.[step.order]?.conversionRates.fromPrevious ?? 0),
271314
},
272315
{
273316
title: (
@@ -277,6 +320,7 @@ export function FunnelStepsTable(): JSX.Element | null {
277320
time
278321
</>
279322
),
323+
key: `step_${stepIndex}_median_time`,
280324
render: (_: void, breakdown: FlattenedFunnelStepByBreakdown) =>
281325
breakdown.steps?.[step.order]?.median_conversion_time != undefined
282326
? humanFriendlyDuration(breakdown.steps[step.order].median_conversion_time, {
@@ -286,6 +330,9 @@ export function FunnelStepsTable(): JSX.Element | null {
286330
align: 'right',
287331
width: 0,
288332
className: 'whitespace-nowrap',
333+
sorter: (a: FlattenedFunnelStepByBreakdown, b: FlattenedFunnelStepByBreakdown) =>
334+
(a.steps?.[step.order]?.median_conversion_time ?? 0) -
335+
(b.steps?.[step.order]?.median_conversion_time ?? 0),
289336
},
290337
{
291338
title: (
@@ -295,6 +342,7 @@ export function FunnelStepsTable(): JSX.Element | null {
295342
time
296343
</>
297344
),
345+
key: `step_${stepIndex}_average_time`,
298346
render: (_: void, breakdown: FlattenedFunnelStepByBreakdown) =>
299347
breakdown.steps?.[step.order]?.average_conversion_time != undefined
300348
? humanFriendlyDuration(breakdown.steps[step.order].average_conversion_time, {
@@ -304,6 +352,9 @@ export function FunnelStepsTable(): JSX.Element | null {
304352
align: 'right',
305353
width: 0,
306354
className: 'whitespace-nowrap',
355+
sorter: (a: FlattenedFunnelStepByBreakdown, b: FlattenedFunnelStepByBreakdown) =>
356+
(a.steps?.[step.order]?.average_conversion_time ?? 0) -
357+
(b.steps?.[step.order]?.average_conversion_time ?? 0),
307358
},
308359
]),
309360
] as LemonTableColumn<FlattenedFunnelStepByBreakdown, keyof FlattenedFunnelStepByBreakdown>[],
@@ -319,6 +370,7 @@ export function FunnelStepsTable(): JSX.Element | null {
319370
rowStatus={(record) => (record.significant ? 'highlighted' : null)}
320371
rowRibbonColor={getFunnelsColor}
321372
firstColumnSticky
373+
useURLForSorting
322374
/>
323375
)
324376
}

0 commit comments

Comments
 (0)