diff --git a/frontend/src/lib/components/LemonTable/LemonTable.scss b/frontend/src/lib/components/LemonTable/LemonTable.scss index e3b0ee9e228d6..bcdcfe97ff051 100644 --- a/frontend/src/lib/components/LemonTable/LemonTable.scss +++ b/frontend/src/lib/components/LemonTable/LemonTable.scss @@ -50,7 +50,6 @@ > th { font-weight: 700; text-align: left; - white-space: pre-wrap; } &.LemonTable__th--column-group { --row-base-height: 2.5rem; // Make group headers smaller for better hierarchy diff --git a/frontend/src/lib/components/LemonTable/LemonTable.tsx b/frontend/src/lib/components/LemonTable/LemonTable.tsx index 5111cd754066c..8fb19d57eaa85 100644 --- a/frontend/src/lib/components/LemonTable/LemonTable.tsx +++ b/frontend/src/lib/components/LemonTable/LemonTable.tsx @@ -33,7 +33,7 @@ export interface LemonTableProps> { /** Which column to use for the row key, as an alternative to the default row index mechanism. */ rowKey?: keyof T | ((record: T) => string | number) /** Class to append to each row. */ - rowClassName?: string | ((record: T) => string) + rowClassName?: string | ((record: T) => string | null) /** Color to mark each row with. */ rowRibbonColor?: string | ((record: T) => string | null) /** Status of each row. Defaults no status. */ diff --git a/frontend/src/lib/components/LemonTable/TableRow.tsx b/frontend/src/lib/components/LemonTable/TableRow.tsx index 41086c9f38fde..e5fe0d3b12cba 100644 --- a/frontend/src/lib/components/LemonTable/TableRow.tsx +++ b/frontend/src/lib/components/LemonTable/TableRow.tsx @@ -8,7 +8,7 @@ export interface TableRowProps> { record: T recordIndex: number rowKeyDetermined: string | number - rowClassNameDetermined: string | undefined + rowClassNameDetermined: string | null | undefined rowRibbonColorDetermined: string | null | undefined rowStatusDetermined: 'success' | 'warning' | 'danger' | 'highlighted' | undefined columnGroups: LemonTableColumnGroup[] diff --git a/frontend/src/lib/components/icons.tsx b/frontend/src/lib/components/icons.tsx index 6e7cd46a68b81..a712d1ae002b5 100644 --- a/frontend/src/lib/components/icons.tsx +++ b/frontend/src/lib/components/icons.tsx @@ -437,9 +437,9 @@ export function IconGroupedEvents(props: React.SVGProps): JSX.Ele } /** Material Design Assistant Photo icon. */ -export function IconFlag(): JSX.Element { +export function IconFlag(props: React.SVGProps): JSX.Element { return ( - + >({ (stepsInBreakdown[stepsInBreakdown.length - 1]?.count ?? 0) / (stepsInBreakdown[0]?.count ?? 1), }, - significant: stepsInBreakdown.some((step) => - step.significant ? Object.values(step.significant).some((val) => val) : false + significant: stepsInBreakdown.some( + (step) => step.significant?.total || step.significant?.fromPrevious ), }) }) diff --git a/frontend/src/scenes/insights/Insight.scss b/frontend/src/scenes/insights/Insight.scss index 2c9e8c96f795f..5a9908ede2bc2 100644 --- a/frontend/src/scenes/insights/Insight.scss +++ b/frontend/src/scenes/insights/Insight.scss @@ -286,3 +286,12 @@ $funnel_canvas_background: #fff; } } } + +.significance-highlight { + display: inline-flex; + background: var(--primary); + color: var(--bg-light); + .LemonRow__icon { + color: var(--bg-light); + } +} diff --git a/frontend/src/scenes/insights/InsightTabs/FunnelTab/FunnelStepsTable.tsx b/frontend/src/scenes/insights/InsightTabs/FunnelTab/FunnelStepsTable.tsx index 674550f191497..49e31165b99a1 100644 --- a/frontend/src/scenes/insights/InsightTabs/FunnelTab/FunnelStepsTable.tsx +++ b/frontend/src/scenes/insights/InsightTabs/FunnelTab/FunnelStepsTable.tsx @@ -6,7 +6,7 @@ import { LemonTable, LemonTableColumn, LemonTableColumnGroup } from 'lib/compone import { BreakdownKeyType, FlattenedFunnelStepByBreakdown } from '~/types' import { EntityFilterInfo } from 'lib/components/EntityFilterInfo' import { formatDisplayPercentage, getSeriesColor, getVisibilityIndex } from 'scenes/funnels/funnelUtils' -import { getActionFilterFromFunnelStep } from './funnelStepTableUtils' +import { getActionFilterFromFunnelStep, getSignificanceFromBreakdownStep } from './funnelStepTableUtils' import { formatBreakdownLabel } from 'scenes/insights/InsightsTable/InsightsTable' import { cohortsModel } from '~/models/cohortsModel' import { LemonCheckbox } from 'lib/components/LemonCheckbox' @@ -14,6 +14,7 @@ import { Lettermark, LettermarkColor } from 'lib/components/Lettermark/Lettermar import { LemonRow } from 'lib/components/LemonRow' import { humanFriendlyDuration } from 'lib/utils' import { ValueInspectorButton } from 'scenes/funnels/FunnelBarGraph' +import { IconFlag } from 'lib/components/icons' export function FunnelStepsTable(): JSX.Element | null { const { insightProps } = useValues(insightLogic) @@ -110,7 +111,7 @@ export function FunnelStepsTable(): JSX.Element | null { ), children: [ { - title: 'Completed', + title: stepIndex === 0 ? 'Entered' : 'Converted', render: function RenderCompleted( _: void, breakdown: FlattenedFunnelStepByBreakdown @@ -130,17 +131,11 @@ export function FunnelStepsTable(): JSX.Element | null { align: 'right', }, - { - title: 'Rate', - render: (_: void, breakdown: FlattenedFunnelStepByBreakdown) => - formatDisplayPercentage(breakdown.steps?.[stepIndex]?.conversionRates.fromPrevious ?? 0, true), - align: 'right', - }, ...(stepIndex === 0 ? [] : [ { - title: 'Dropped', + title: 'Dropped off', render: function RenderDropped( _: void, breakdown: FlattenedFunnelStepByBreakdown @@ -161,23 +156,72 @@ export function FunnelStepsTable(): JSX.Element | null { }, align: 'right', }, + ]), + { + title: 'Conversion so far', + render: function RenderRate( + _: void, + breakdown: FlattenedFunnelStepByBreakdown + ): JSX.Element | string { + const significance = getSignificanceFromBreakdownStep(breakdown, step.order) + return significance?.total ? ( + } + compact + > + {formatDisplayPercentage( + breakdown.steps?.[step.order]?.conversionRates.total ?? 0, + true + )} + + ) : ( + formatDisplayPercentage(breakdown.steps?.[step.order]?.conversionRates.total ?? 0, true) + ) + }, + align: 'right', + }, + ...(stepIndex === 0 + ? [] + : [ { - title: 'Rate', - render: (_: void, breakdown: FlattenedFunnelStepByBreakdown) => - formatDisplayPercentage( - 1 - (breakdown.steps?.[stepIndex]?.conversionRates.fromPrevious ?? 0), - true - ), + title: 'Conversion from previous', + render: function RenderRate( + _: void, + breakdown: FlattenedFunnelStepByBreakdown + ): JSX.Element | string { + const significance = getSignificanceFromBreakdownStep(breakdown, step.order) + // Only flag as significant here if not flagged already in "Conversion so far" + return !significance?.total && significance?.fromPrevious ? ( + } + compact + > + {formatDisplayPercentage( + breakdown.steps?.[step.order]?.conversionRates.fromPrevious ?? 0, + true + )} + + ) : ( + formatDisplayPercentage( + breakdown.steps?.[step.order]?.conversionRates.fromPrevious ?? 0, + true + ) + ) + }, align: 'right', }, { - title: 'Avg. time', + title: 'Avg. time', render: (_: void, breakdown: FlattenedFunnelStepByBreakdown) => breakdown.steps?.[step.order]?.average_conversion_time != undefined ? humanFriendlyDuration(breakdown.steps[step.order].average_conversion_time, 3) : '–', align: 'right', - className: 'nowrap', + width: 0, }, ]), ] as LemonTableColumn[], @@ -189,6 +233,8 @@ export function FunnelStepsTable(): JSX.Element | null { dataSource={flattenedBreakdowns} columns={columnsGrouped} loading={insightLoading} + rowKey="breakdownIndex" + rowStatus={(record) => (record.significant ? 'highlighted' : undefined)} rowRibbonColor={(series) => getSeriesColor( series?.breakdownIndex, @@ -197,7 +243,6 @@ export function FunnelStepsTable(): JSX.Element | null { flattenedBreakdowns.length ) } - rowKey="breakdownIndex" /> ) } diff --git a/frontend/src/styles/global.scss b/frontend/src/styles/global.scss index bc18713d65a49..dea8f4b1dd814 100644 --- a/frontend/src/styles/global.scss +++ b/frontend/src/styles/global.scss @@ -131,10 +131,6 @@ body strong { font-size: 8px; } -.nowrap { - white-space: nowrap !important; -} - .page-title-row { display: flex; flex-wrap: wrap;