@@ -7,6 +7,7 @@ import { LemonRow } from 'lib/lemon-ui/LemonRow'
7
7
import { LemonTable , LemonTableColumn , LemonTableColumnGroup } from 'lib/lemon-ui/LemonTable'
8
8
import { Lettermark , LettermarkColor } from 'lib/lemon-ui/Lettermark'
9
9
import { humanFriendlyDuration , humanFriendlyNumber , percentage } from 'lib/utils'
10
+ import { compare as compareFn } from 'natural-orderby'
10
11
import { funnelDataLogic } from 'scenes/funnels/funnelDataLogic'
11
12
import { funnelPersonsModalLogic } from 'scenes/funnels/funnelPersonsModalLogic'
12
13
import { getVisibilityKey } from 'scenes/funnels/funnelUtils'
@@ -75,6 +76,31 @@ export function FunnelStepsTable(): JSX.Element | null {
75
76
/>
76
77
) ,
77
78
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
+ } ,
78
104
render : function RenderBreakdownValue (
79
105
_ : void ,
80
106
breakdown : FlattenedFunnelStepByBreakdown
@@ -127,9 +153,12 @@ export function FunnelStepsTable(): JSX.Element | null {
127
153
conversion
128
154
</ >
129
155
) ,
156
+ key : 'total_conversion' ,
130
157
render : ( _ : void , breakdown : FlattenedFunnelStepByBreakdown ) =>
131
158
percentage ( breakdown ?. conversionRates ?. total ?? 0 , 2 , true ) ,
132
159
align : 'right' ,
160
+ sorter : ( a : FlattenedFunnelStepByBreakdown , b : FlattenedFunnelStepByBreakdown ) =>
161
+ ( a ?. conversionRates ?. total ?? 0 ) - ( b ?. conversionRates ?. total ?? 0 ) ,
133
162
} ,
134
163
] ,
135
164
} ,
@@ -146,6 +175,7 @@ export function FunnelStepsTable(): JSX.Element | null {
146
175
children : [
147
176
{
148
177
title : stepIndex === 0 ? 'Entered' : 'Converted' ,
178
+ key : `step_${ stepIndex } _conversion` ,
149
179
render : function RenderCompleted (
150
180
_ : void ,
151
181
breakdown : FlattenedFunnelStepByBreakdown
@@ -166,14 +196,16 @@ export function FunnelStepsTable(): JSX.Element | null {
166
196
) )
167
197
)
168
198
} ,
169
-
170
199
align : 'right' ,
200
+ sorter : ( a : FlattenedFunnelStepByBreakdown , b : FlattenedFunnelStepByBreakdown ) =>
201
+ ( a . steps ?. [ stepIndex ] ?. count ?? 0 ) - ( b . steps ?. [ stepIndex ] ?. count ?? 0 ) ,
171
202
} ,
172
203
...( stepIndex === 0
173
204
? [ ]
174
205
: [
175
206
{
176
- title : 'Dropped off' ,
207
+ title : 'Dropped off' ,
208
+ key : `step_${ stepIndex } _dropoff` ,
177
209
render : function RenderDropped (
178
210
_ : void ,
179
211
breakdown : FlattenedFunnelStepByBreakdown
@@ -199,6 +231,9 @@ export function FunnelStepsTable(): JSX.Element | null {
199
231
)
200
232
} ,
201
233
align : 'right' ,
234
+ sorter : ( a : FlattenedFunnelStepByBreakdown , b : FlattenedFunnelStepByBreakdown ) =>
235
+ ( a . steps ?. [ stepIndex ] ?. droppedOffFromPrevious ?? 0 ) -
236
+ ( b . steps ?. [ stepIndex ] ?. droppedOffFromPrevious ?? 0 ) ,
202
237
} ,
203
238
] ) ,
204
239
{
@@ -209,6 +244,7 @@ export function FunnelStepsTable(): JSX.Element | null {
209
244
so far
210
245
</ >
211
246
) ,
247
+ key : `step_${ stepIndex } _conversion_so_far` ,
212
248
render : function RenderConversionSoFar (
213
249
_ : void ,
214
250
breakdown : FlattenedFunnelStepByBreakdown
@@ -228,6 +264,9 @@ export function FunnelStepsTable(): JSX.Element | null {
228
264
)
229
265
} ,
230
266
align : 'right' ,
267
+ sorter : ( a : FlattenedFunnelStepByBreakdown , b : FlattenedFunnelStepByBreakdown ) =>
268
+ ( a . steps ?. [ step . order ] ?. conversionRates . total ?? 0 ) -
269
+ ( b . steps ?. [ step . order ] ?. conversionRates . total ?? 0 ) ,
231
270
} ,
232
271
...( stepIndex === 0
233
272
? [ ]
@@ -240,6 +279,7 @@ export function FunnelStepsTable(): JSX.Element | null {
240
279
from previous
241
280
</ >
242
281
) ,
282
+ key : `step_${ stepIndex } _conversion_from_prev` ,
243
283
render : function RenderConversionFromPrevious (
244
284
_ : void ,
245
285
breakdown : FlattenedFunnelStepByBreakdown
@@ -268,6 +308,9 @@ export function FunnelStepsTable(): JSX.Element | null {
268
308
)
269
309
} ,
270
310
align : 'right' ,
311
+ sorter : ( a : FlattenedFunnelStepByBreakdown , b : FlattenedFunnelStepByBreakdown ) =>
312
+ ( a . steps ?. [ step . order ] ?. conversionRates . fromPrevious ?? 0 ) -
313
+ ( b . steps ?. [ step . order ] ?. conversionRates . fromPrevious ?? 0 ) ,
271
314
} ,
272
315
{
273
316
title : (
@@ -277,6 +320,7 @@ export function FunnelStepsTable(): JSX.Element | null {
277
320
time
278
321
</ >
279
322
) ,
323
+ key : `step_${ stepIndex } _median_time` ,
280
324
render : ( _ : void , breakdown : FlattenedFunnelStepByBreakdown ) =>
281
325
breakdown . steps ?. [ step . order ] ?. median_conversion_time != undefined
282
326
? humanFriendlyDuration ( breakdown . steps [ step . order ] . median_conversion_time , {
@@ -286,6 +330,9 @@ export function FunnelStepsTable(): JSX.Element | null {
286
330
align : 'right' ,
287
331
width : 0 ,
288
332
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 ) ,
289
336
} ,
290
337
{
291
338
title : (
@@ -295,6 +342,7 @@ export function FunnelStepsTable(): JSX.Element | null {
295
342
time
296
343
</ >
297
344
) ,
345
+ key : `step_${ stepIndex } _average_time` ,
298
346
render : ( _ : void , breakdown : FlattenedFunnelStepByBreakdown ) =>
299
347
breakdown . steps ?. [ step . order ] ?. average_conversion_time != undefined
300
348
? humanFriendlyDuration ( breakdown . steps [ step . order ] . average_conversion_time , {
@@ -304,6 +352,9 @@ export function FunnelStepsTable(): JSX.Element | null {
304
352
align : 'right' ,
305
353
width : 0 ,
306
354
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 ) ,
307
358
} ,
308
359
] ) ,
309
360
] as LemonTableColumn < FlattenedFunnelStepByBreakdown , keyof FlattenedFunnelStepByBreakdown > [ ] ,
@@ -319,6 +370,7 @@ export function FunnelStepsTable(): JSX.Element | null {
319
370
rowStatus = { ( record ) => ( record . significant ? 'highlighted' : null ) }
320
371
rowRibbonColor = { getFunnelsColor }
321
372
firstColumnSticky
373
+ useURLForSorting
322
374
/>
323
375
)
324
376
}
0 commit comments