From 5b115ce9472fa8f98a5a0811a6932466ac2822e0 Mon Sep 17 00:00:00 2001 From: Juraj Majerik Date: Sat, 14 Dec 2024 08:10:10 +0100 Subject: [PATCH] wip --- .../ExperimentView/ExperimentView.tsx | 2 + .../experiments/ExperimentView/Results.tsx | 5 +- .../DeltaChart.tsx} | 485 ++++++++---------- .../experiments/MetricsView/MetricsView.tsx | 178 +++++++ .../MetricsView/NoResultEmptyState.tsx | 99 ++++ .../scenes/experiments/MetricsView/const.tsx | 20 + .../scenes/experiments/experimentLogic.tsx | 3 +- frontend/src/scenes/experiments/utils.test.ts | 2 +- 8 files changed, 511 insertions(+), 283 deletions(-) rename frontend/src/scenes/experiments/{ExperimentView/DeltaViz.tsx => MetricsView/DeltaChart.tsx} (62%) create mode 100644 frontend/src/scenes/experiments/MetricsView/MetricsView.tsx create mode 100644 frontend/src/scenes/experiments/MetricsView/NoResultEmptyState.tsx create mode 100644 frontend/src/scenes/experiments/MetricsView/const.tsx diff --git a/frontend/src/scenes/experiments/ExperimentView/ExperimentView.tsx b/frontend/src/scenes/experiments/ExperimentView/ExperimentView.tsx index 8225391583fc9..5815f459de9d7 100644 --- a/frontend/src/scenes/experiments/ExperimentView/ExperimentView.tsx +++ b/frontend/src/scenes/experiments/ExperimentView/ExperimentView.tsx @@ -7,6 +7,7 @@ import { WebExperimentImplementationDetails } from 'scenes/experiments/WebExperi import { ExperimentImplementationDetails } from '../ExperimentImplementationDetails' import { experimentLogic } from '../experimentLogic' +import { MetricsView } from '../MetricsView/MetricsView' import { ExperimentLoadingAnimation, LoadingState, @@ -49,6 +50,7 @@ const ResultsTab = (): JSX.Element => { )} )} + ) diff --git a/frontend/src/scenes/experiments/ExperimentView/Results.tsx b/frontend/src/scenes/experiments/ExperimentView/Results.tsx index 065273fd87e56..0afe6482ae6bb 100644 --- a/frontend/src/scenes/experiments/ExperimentView/Results.tsx +++ b/frontend/src/scenes/experiments/ExperimentView/Results.tsx @@ -1,20 +1,17 @@ import '../Experiment.scss' import { useValues } from 'kea' -import { FEATURE_FLAGS } from 'lib/constants' import { experimentLogic } from '../experimentLogic' import { ResultsHeader, ResultsQuery } from './components' -import { DeltaViz } from './DeltaViz' import { SummaryTable } from './SummaryTable' export function Results(): JSX.Element { - const { experimentResults, featureFlags } = useValues(experimentLogic) + const { experimentResults } = useValues(experimentLogic) return (
- {featureFlags[FEATURE_FLAGS.EXPERIMENTS_MULTIPLE_METRICS] && }
diff --git a/frontend/src/scenes/experiments/ExperimentView/DeltaViz.tsx b/frontend/src/scenes/experiments/MetricsView/DeltaChart.tsx similarity index 62% rename from frontend/src/scenes/experiments/ExperimentView/DeltaViz.tsx rename to frontend/src/scenes/experiments/MetricsView/DeltaChart.tsx index 94be61a664753..173942b073b9c 100644 --- a/frontend/src/scenes/experiments/ExperimentView/DeltaViz.tsx +++ b/frontend/src/scenes/experiments/MetricsView/DeltaChart.tsx @@ -1,69 +1,24 @@ -import { useValues, useActions } from 'kea' -import { useEffect, useRef, useState } from 'react' - -import { InsightType } from '~/types' - -import { experimentLogic, getDefaultFilters, getDefaultFunnelsMetric } from '../experimentLogic' -import { VariantTag } from './components' +import { IconActivity, IconPencil } from '@posthog/icons' import { LemonButton, LemonTag } from '@posthog/lemon-ui' -import { IconArchive, IconCheck, IconPencil, IconPlus, IconX, IconHourglass } from '@posthog/icons' -import { FEATURE_FLAGS } from 'lib/constants' - -const MAX_PRIMARY_METRICS = 10 - -const BAR_HEIGHT = 8 -const BAR_PADDING = 10 -const TICK_PANEL_HEIGHT = 20 -const VIEW_BOX_WIDTH = 800 -const HORIZONTAL_PADDING = 20 -const CONVERSION_RATE_RECT_WIDTH = 2 -const TICK_FONT_SIZE = 9 - -const COLORS = { - BOUNDARY_LINES: '#d0d0d0', - ZERO_LINE: '#666666', - BAR_NEGATIVE: '#F44435', - BAR_BEST: '#4DAF4F', - BAR_DEFAULT: '#d9d9d9', - BAR_CONTROL: 'rgba(217, 217, 217, 0.4)', - BAR_MIDDLE_POINT: 'black', - BAR_MIDDLE_POINT_CONTROL: 'rgba(0, 0, 0, 0.4)', -} - -// Helper function to find nice round numbers for ticks -export function getNiceTickValues(maxAbsValue: number): number[] { - // Round up maxAbsValue to ensure we cover all values - maxAbsValue = Math.ceil(maxAbsValue * 10) / 10 - - const magnitude = Math.floor(Math.log10(maxAbsValue)) - const power = Math.pow(10, magnitude) - - let baseUnit - const normalizedMax = maxAbsValue / power - if (normalizedMax <= 1) { - baseUnit = 0.2 * power - } else if (normalizedMax <= 2) { - baseUnit = 0.5 * power - } else if (normalizedMax <= 5) { - baseUnit = 1 * power - } else { - baseUnit = 2 * power - } - - // Calculate how many baseUnits we need to exceed maxAbsValue - const unitsNeeded = Math.ceil(maxAbsValue / baseUnit) - - // Determine appropriate number of decimal places based on magnitude - const decimalPlaces = Math.max(0, -magnitude + 1) +import { useActions, useValues } from 'kea' +import { humanFriendlyNumber } from 'lib/utils' +import { useEffect, useRef, useState } from 'react' - const ticks: number[] = [] - for (let i = -unitsNeeded; i <= unitsNeeded; i++) { - // Round each tick value to avoid floating point precision issues - const tickValue = Number((baseUnit * i).toFixed(decimalPlaces)) - ticks.push(tickValue) - } - return ticks -} +import { InsightType, TrendExperimentVariant } from '~/types' + +import { experimentLogic } from '../experimentLogic' +import { VariantTag } from '../ExperimentView/components' +import { + BAR_HEIGHT, + BAR_PADDING, + COLORS, + CONVERSION_RATE_RECT_WIDTH, + HORIZONTAL_PADDING, + TICK_FONT_SIZE, + TICK_PANEL_HEIGHT, + VIEW_BOX_WIDTH, +} from './const' +import { NoResultEmptyState } from './NoResultEmptyState' function formatTickValue(value: number): string { if (value === 0) { @@ -87,107 +42,7 @@ function formatTickValue(value: number): string { return `${(value * 100).toFixed(decimals)}%` } -export function DeltaViz(): JSX.Element { - const { experiment, getMetricType, metricResults, primaryMetricsResultErrors, credibleIntervalForVariant } = - useValues(experimentLogic) - const { setExperiment, openPrimaryMetricModal } = useActions(experimentLogic) - - const variants = experiment.parameters.feature_flag_variants - const metrics = experiment.metrics || [] - - // Calculate the maximum absolute value across ALL metrics - const maxAbsValue = Math.max( - ...metrics.flatMap((_, metricIndex) => { - const result = metricResults?.[metricIndex] - return variants.flatMap((variant) => { - const interval = credibleIntervalForVariant(result, variant.key, getMetricType(metricIndex)) - return interval ? [Math.abs(interval[0] / 100), Math.abs(interval[1] / 100)] : [] - }) - }) - ) - - // Add padding to the range - const padding = Math.max(maxAbsValue * 0.05, 0.02) - const chartBound = maxAbsValue + padding - - // Calculate tick values once for all charts - const commonTickValues = getNiceTickValues(chartBound) - - return ( -
-
-
-
-

Primary metrics

-
-
- -
-
-
- } - type="secondary" - size="small" - onClick={() => { - const newMetrics = [...experiment.metrics, getDefaultFunnelsMetric()] - setExperiment({ - metrics: newMetrics, - }) - openPrimaryMetricModal(newMetrics.length - 1) - }} - disabledReason={ - metrics.length >= MAX_PRIMARY_METRICS - ? `You can only add up to ${MAX_PRIMARY_METRICS} primary metrics.` - : undefined - } - > - Add metric - -
-
-
-
-
-
- {metrics.map((metric, metricIndex) => { - const result = metricResults?.[metricIndex] - const isFirstMetric = metricIndex === 0 - - return ( -
- -
- ) - })} -
-
-
- ) -} - -function Chart({ +export function DeltaChart({ result, error, variants, @@ -208,7 +63,14 @@ function Chart({ tickValues: number[] chartBound: number }): JSX.Element { - const { credibleIntervalForVariant, conversionRateForVariant, experimentId } = useValues(experimentLogic) + const { + credibleIntervalForVariant, + conversionRateForVariant, + experimentId, + countDataForVariant, + exposureCountDataForVariant, + metricResultsLoading, + } = useValues(experimentLogic) const { openPrimaryMetricModal } = useActions(experimentLogic) const [tooltipData, setTooltipData] = useState<{ x: number; y: number; variant: string } | null>(null) const [emptyStateTooltipVisible, setEmptyStateTooltipVisible] = useState(true) @@ -392,15 +254,63 @@ function Chart({ const interval = credibleIntervalForVariant(result, variant.key, metricType) const [lower, upper] = interval ? [interval[0] / 100, interval[1] / 100] : [0, 0] - const variantRate = conversionRateForVariant(result, variant.key) - const controlRate = conversionRateForVariant(result, 'control') - const delta = variantRate && controlRate ? (variantRate - controlRate) / controlRate : 0 + let delta: number + if (metricType === InsightType.TRENDS) { + const controlVariant = result.variants.find( + (v: TrendExperimentVariant) => v.key === 'control' + ) as TrendExperimentVariant + + const variantData = result.variants.find( + (v: TrendExperimentVariant) => v.key === variant.key + ) as TrendExperimentVariant + + if ( + !variantData?.count || + !variantData?.absolute_exposure || + !controlVariant?.count || + !controlVariant?.absolute_exposure + ) { + delta = 0 + } else { + const controlMean = controlVariant.count / controlVariant.absolute_exposure + const variantMean = variantData.count / variantData.absolute_exposure + delta = (variantMean - controlMean) / controlMean + } + } else { + // Original funnel logic + const variantRate = conversionRateForVariant(result, variant.key) + const controlRate = conversionRateForVariant(result, 'control') + delta = variantRate && controlRate ? (variantRate - controlRate) / controlRate : 0 + } // Find the highest delta among all variants const maxDelta = Math.max( ...variants.map((v) => { + if (metricType === InsightType.TRENDS) { + const controlVariant = result.variants.find( + (cv: TrendExperimentVariant) => cv.key === 'control' + ) as TrendExperimentVariant + const variantData = result.variants.find( + (vd: TrendExperimentVariant) => vd.key === v.key + ) as TrendExperimentVariant + + if ( + !variantData?.count || + !variantData?.absolute_exposure || + !controlVariant?.count || + !controlVariant?.absolute_exposure + ) { + return 0 + } + + const controlMean = controlVariant.count / controlVariant.absolute_exposure + const variantMean = variantData.count / variantData.absolute_exposure + return (variantMean - controlMean) / controlMean + } + const vRate = conversionRateForVariant(result, v.key) - return vRate && controlRate ? (vRate - controlRate) / controlRate : 0 + const cRate = conversionRateForVariant(result, 'control') + return vRate && cRate ? (vRate - cRate) / cRate : 0 }) ) @@ -463,8 +373,7 @@ function Chart({ ) })} - ) : ( - // Empty state + ) : metricResultsLoading ? ( setEmptyStateTooltipVisible(false)} + > +
+ Results loading... +
+ + + ) : ( + + { + const rect = e.currentTarget.getBoundingClientRect() + setTooltipPosition({ + x: rect.left + rect.width / 2, + y: rect.top, + }) + setEmptyStateTooltipVisible(true) + }} + onMouseLeave={() => setEmptyStateTooltipVisible(false)} >
Results not yet available - + {error?.statusCode === 400 ? ( + + + + {(() => { + try { + const detail = JSON.parse(error.detail) + return Object.values(detail).filter((v) => v === false).length + } catch { + return '0' + } + })()} + + /4 + + ) : ( + + Error + + )}
@@ -519,12 +477,51 @@ function Chart({ >
-
- Conversion rate: - - {conversionRateForVariant(result, tooltipData.variant)?.toFixed(2)}% - -
+ {metricType === InsightType.TRENDS ? ( + <> +
+ Count: + + {(() => { + const count = countDataForVariant(result, tooltipData.variant) + return count !== null ? humanFriendlyNumber(count) : '—' + })()} + +
+
+ Exposure: + + {(() => { + const exposure = exposureCountDataForVariant( + result, + tooltipData.variant + ) + return exposure !== null ? humanFriendlyNumber(exposure) : '—' + })()} + +
+
+ Mean: + + {(() => { + const variant = result.variants.find( + (v: TrendExperimentVariant) => v.key === tooltipData.variant + ) + return variant?.count && variant?.absolute_exposure + ? (variant.count / variant.absolute_exposure).toFixed(2) + : '—' + })()} + +
+ + ) : ( +
+ Conversion rate: + + {conversionRateForVariant(result, tooltipData.variant)?.toFixed(2)}% + +
+ )}
Delta: @@ -532,6 +529,37 @@ function Chart({ Baseline ) : ( (() => { + if (metricType === InsightType.TRENDS) { + const controlVariant = result.variants.find( + (v: TrendExperimentVariant) => v.key === 'control' + ) + const variant = result.variants.find( + (v: TrendExperimentVariant) => v.key === tooltipData.variant + ) + + if ( + !variant?.count || + !variant?.absolute_exposure || + !controlVariant?.count || + !controlVariant?.absolute_exposure + ) { + return '—' + } + + const controlMean = + controlVariant.count / controlVariant.absolute_exposure + const variantMean = variant.count / variant.absolute_exposure + const delta = (variantMean - controlMean) / controlMean + return delta ? ( + 0 ? 'text-success' : 'text-danger'}> + {`${delta > 0 ? '+' : ''}${(delta * 100).toFixed(2)}%`} + + ) : ( + '—' + ) + } + + // Original funnel logic const variantRate = conversionRateForVariant(result, tooltipData.variant) const controlRate = conversionRateForVariant(result, 'control') const delta = @@ -591,107 +619,10 @@ function Chart({ minWidth: '200px', }} > - +
)}
) } - -export function NoResultsEmptyState({ error }: { error: any }): JSX.Element { - if (!error) { - return <> - } - - type ErrorCode = 'no-events' | 'no-flag-info' | 'no-control-variant' | 'no-test-variant' - - const { statusCode } = error - - function ChecklistItem({ errorCode, value }: { errorCode: ErrorCode; value: boolean }): JSX.Element { - const failureText = { - 'no-events': 'Metric events not received', - 'no-flag-info': 'Feature flag information not present on the events', - 'no-control-variant': 'Events with the control variant not received', - 'no-test-variant': 'Events with at least one test variant not received', - } - - const successText = { - 'no-events': 'Experiment events have been received', - 'no-flag-info': 'Feature flag information is present on the events', - 'no-control-variant': 'Events with the control variant received', - 'no-test-variant': 'Events with at least one test variant received', - } - - return ( -
- {value === false ? ( - - - {successText[errorCode]} - - ) : ( - - - {failureText[errorCode]} - - )} -
- ) - } - - // Validation errors return 400 and are rendered as a checklist - if (statusCode === 400) { - let parsedDetail: Record - try { - parsedDetail = JSON.parse(error.detail) - } catch (error) { - return ( -
-
- Experiment results could not be calculated -
-
{error}
-
- ) - } - - const checklistItems = [] - for (const [errorCode, value] of Object.entries(parsedDetail)) { - checklistItems.push() - } - - return
{checklistItems}
- } - - if (statusCode === 504) { - return ( -
-
-
- -

Experiment results timed out

-
- This may occur when the experiment has a large amount of data or is particularly complex. We - are actively working on fixing this. In the meantime, please try refreshing the experiment - to retrieve the results. -
-
-
-
- ) - } - - // Other unexpected errors - return ( -
-
-
- -

Experiment results could not be calculated

-
{error.detail}
-
-
-
- ) -} diff --git a/frontend/src/scenes/experiments/MetricsView/MetricsView.tsx b/frontend/src/scenes/experiments/MetricsView/MetricsView.tsx new file mode 100644 index 0000000000000..3c671987a2640 --- /dev/null +++ b/frontend/src/scenes/experiments/MetricsView/MetricsView.tsx @@ -0,0 +1,178 @@ +import { IconPlus } from '@posthog/icons' +import { LemonButton } from '@posthog/lemon-ui' +import { useActions, useValues } from 'kea' +import { IconAreaChart } from 'lib/lemon-ui/icons' + +import { experimentLogic, getDefaultFunnelsMetric } from '../experimentLogic' +import { MAX_PRIMARY_METRICS } from './const' +import { DeltaChart } from './DeltaChart' + +// Helper function to find nice round numbers for ticks +export function getNiceTickValues(maxAbsValue: number): number[] { + // Round up maxAbsValue to ensure we cover all values + maxAbsValue = Math.ceil(maxAbsValue * 10) / 10 + + const magnitude = Math.floor(Math.log10(maxAbsValue)) + const power = Math.pow(10, magnitude) + + let baseUnit + const normalizedMax = maxAbsValue / power + if (normalizedMax <= 1) { + baseUnit = 0.2 * power + } else if (normalizedMax <= 2) { + baseUnit = 0.5 * power + } else if (normalizedMax <= 5) { + baseUnit = 1 * power + } else { + baseUnit = 2 * power + } + + // Calculate how many baseUnits we need to exceed maxAbsValue + const unitsNeeded = Math.ceil(maxAbsValue / baseUnit) + + // Determine appropriate number of decimal places based on magnitude + const decimalPlaces = Math.max(0, -magnitude + 1) + + const ticks: number[] = [] + for (let i = -unitsNeeded; i <= unitsNeeded; i++) { + // Round each tick value to avoid floating point precision issues + const tickValue = Number((baseUnit * i).toFixed(decimalPlaces)) + ticks.push(tickValue) + } + return ticks +} + +function AddMetric({ + metrics, + setExperiment, + openPrimaryMetricModal, +}: { + metrics: any[] + setExperiment: (payload: { metrics: any[] }) => void + openPrimaryMetricModal: (index: number) => void +}): JSX.Element { + return ( + } + type="secondary" + size="small" + onClick={() => { + const newMetrics = [...metrics, getDefaultFunnelsMetric()] + setExperiment({ + metrics: newMetrics, + }) + openPrimaryMetricModal(newMetrics.length - 1) + }} + disabledReason={ + metrics.length >= MAX_PRIMARY_METRICS + ? `You can only add up to ${MAX_PRIMARY_METRICS} primary metrics.` + : undefined + } + > + Add metric + + ) +} + +export function MetricsView(): JSX.Element { + const { experiment, getMetricType, metricResults, primaryMetricsResultErrors, credibleIntervalForVariant } = + useValues(experimentLogic) + const { setExperiment, openPrimaryMetricModal } = useActions(experimentLogic) + + const variants = experiment.parameters.feature_flag_variants + const metrics = experiment.metrics || [] + + // Calculate the maximum absolute value across ALL metrics + const maxAbsValue = Math.max( + ...metrics.flatMap((_, metricIndex) => { + const result = metricResults?.[metricIndex] + if (!result) { + return [] + } + return variants.flatMap((variant) => { + const interval = credibleIntervalForVariant(result, variant.key, getMetricType(metricIndex)) + return interval ? [Math.abs(interval[0] / 100), Math.abs(interval[1] / 100)] : [] + }) + }) + ) + + const padding = Math.max(maxAbsValue * 0.05, 0.02) + const chartBound = maxAbsValue + padding + + const commonTickValues = getNiceTickValues(chartBound) + + return ( +
+
+
+
+

Primary metrics

+
+
+ +
+
+
+ +
+
+
+
+ {metrics.length > 0 ? ( +
+
+ {metrics.map((metric, metricIndex) => { + const result = metricResults?.[metricIndex] + const isFirstMetric = metricIndex === 0 + + return ( +
+ +
+ ) + })} +
+
+ ) : ( +
+
+ +
+ Add up to {MAX_PRIMARY_METRICS} primary metrics to monitor side effects of your experiment. +
+ +
+
+ )} +
+ ) +} diff --git a/frontend/src/scenes/experiments/MetricsView/NoResultEmptyState.tsx b/frontend/src/scenes/experiments/MetricsView/NoResultEmptyState.tsx new file mode 100644 index 0000000000000..6ba235036713d --- /dev/null +++ b/frontend/src/scenes/experiments/MetricsView/NoResultEmptyState.tsx @@ -0,0 +1,99 @@ +import { IconArchive } from '@posthog/icons' +import { IconCheck, IconX } from '@posthog/icons' + +export function NoResultEmptyState({ error }: { error: any }): JSX.Element { + if (!error) { + return <> + } + + type ErrorCode = 'no-events' | 'no-flag-info' | 'no-control-variant' | 'no-test-variant' + + const { statusCode } = error + + function ChecklistItem({ errorCode, value }: { errorCode: ErrorCode; value: boolean }): JSX.Element { + const failureText = { + 'no-events': 'Metric events not received', + 'no-flag-info': 'Feature flag information not present on the events', + 'no-control-variant': 'Events with the control variant not received', + 'no-test-variant': 'Events with at least one test variant not received', + } + + const successText = { + 'no-events': 'Experiment events have been received', + 'no-flag-info': 'Feature flag information is present on the events', + 'no-control-variant': 'Events with the control variant received', + 'no-test-variant': 'Events with at least one test variant received', + } + + return ( +
+ {value === false ? ( + + + {successText[errorCode]} + + ) : ( + + + {failureText[errorCode]} + + )} +
+ ) + } + + // Validation errors return 400 and are rendered as a checklist + if (statusCode === 400) { + let parsedDetail: Record + try { + parsedDetail = JSON.parse(error.detail) + } catch (error) { + return ( +
+
+ Experiment results could not be calculated +
+
{error}
+
+ ) + } + + const checklistItems = [] + for (const [errorCode, value] of Object.entries(parsedDetail)) { + checklistItems.push() + } + + return
{checklistItems}
+ } + + if (statusCode === 504) { + return ( +
+
+
+ +

Experiment results timed out

+
+ This may occur when the experiment has a large amount of data or is particularly complex. We + are actively working on fixing this. In the meantime, please try refreshing the experiment + to retrieve the results. +
+
+
+
+ ) + } + + // Other unexpected errors + return ( +
+
+
+ +

Experiment results could not be calculated

+
{error.detail}
+
+
+
+ ) +} diff --git a/frontend/src/scenes/experiments/MetricsView/const.tsx b/frontend/src/scenes/experiments/MetricsView/const.tsx new file mode 100644 index 0000000000000..1a7262e08ff81 --- /dev/null +++ b/frontend/src/scenes/experiments/MetricsView/const.tsx @@ -0,0 +1,20 @@ +export const MAX_PRIMARY_METRICS = 10 + +export const BAR_HEIGHT = 8 +export const BAR_PADDING = 10 +export const TICK_PANEL_HEIGHT = 20 +export const VIEW_BOX_WIDTH = 800 +export const HORIZONTAL_PADDING = 20 +export const CONVERSION_RATE_RECT_WIDTH = 2 +export const TICK_FONT_SIZE = 9 + +export const COLORS = { + BOUNDARY_LINES: '#d0d0d0', + ZERO_LINE: '#666666', + BAR_NEGATIVE: '#F44435', + BAR_BEST: '#4DAF4F', + BAR_DEFAULT: '#d9d9d9', + BAR_CONTROL: 'rgba(217, 217, 217, 0.4)', + BAR_MIDDLE_POINT: 'black', + BAR_MIDDLE_POINT_CONTROL: 'rgba(0, 0, 0, 0.4)', +} diff --git a/frontend/src/scenes/experiments/experimentLogic.tsx b/frontend/src/scenes/experiments/experimentLogic.tsx index c8d1266bdb44f..51c99a91fc965 100644 --- a/frontend/src/scenes/experiments/experimentLogic.tsx +++ b/frontend/src/scenes/experiments/experimentLogic.tsx @@ -648,6 +648,7 @@ export const experimentLogic = kea([ minimum_detectable_effect: minimumDetectableEffect, }, }) + actions.closePrimaryMetricModal() }, updateExperimentCollectionGoal: async () => { const { recommendedRunningTime, recommendedSampleSize, minimumDetectableEffect } = values @@ -677,7 +678,7 @@ export const experimentLogic = kea([ } }, closePrimaryMetricModal: () => { - // actions.loadExperiment() + actions.loadExperiment() }, resetRunningExperiment: async () => { actions.updateExperiment({ start_date: null, end_date: null, archived: false }) diff --git a/frontend/src/scenes/experiments/utils.test.ts b/frontend/src/scenes/experiments/utils.test.ts index 22d03cad8829a..906841aaec363 100644 --- a/frontend/src/scenes/experiments/utils.test.ts +++ b/frontend/src/scenes/experiments/utils.test.ts @@ -1,6 +1,6 @@ import { EntityType, FeatureFlagFilters, InsightType } from '~/types' -import { getNiceTickValues } from './ExperimentView/DeltaViz' +import { getNiceTickValues } from './MetricsView/MetricsView' import { getMinimumDetectableEffect, transformFiltersForWinningVariant } from './utils' describe('utils', () => {