diff --git a/frontend/src/scenes/experiments/ExperimentImplementationDetails.tsx b/frontend/src/scenes/experiments/ExperimentImplementationDetails.tsx index 526bd52595f4d..50d7c81373ef0 100644 --- a/frontend/src/scenes/experiments/ExperimentImplementationDetails.tsx +++ b/frontend/src/scenes/experiments/ExperimentImplementationDetails.tsx @@ -192,7 +192,7 @@ export function ExperimentImplementationDetails({ experiment }: ExperimentImplem } return ( -
+

Implementation

diff --git a/frontend/src/scenes/experiments/ExperimentView/ExperimentView.tsx b/frontend/src/scenes/experiments/ExperimentView/ExperimentView.tsx index 96d3dd3ff86e4..06c1ba1707b22 100644 --- a/frontend/src/scenes/experiments/ExperimentView/ExperimentView.tsx +++ b/frontend/src/scenes/experiments/ExperimentView/ExperimentView.tsx @@ -12,10 +12,12 @@ import { SavedMetricModal } from '../Metrics/SavedMetricModal' import { MetricsView } from '../MetricsView/MetricsView' import { ExperimentLoadingAnimation, + ExploreButton, LoadingState, NoResultsEmptyState, PageHeaderCustom, ResultsHeader, + ResultsQuery, } from './components' import { CumulativeExposuresChart } from './CumulativeExposuresChart' import { DataCollection } from './DataCollection' @@ -26,17 +28,58 @@ import { Overview } from './Overview' import { ReleaseConditionsModal, ReleaseConditionsTable } from './ReleaseConditionsTable' import { Results } from './Results' import { SecondaryMetricsTable } from './SecondaryMetricsTable' +import { SummaryTable } from './SummaryTable' -const ResultsTab = (): JSX.Element => { - const { experiment, metricResults, featureFlags } = useValues(experimentLogic) - const result = metricResults?.[0] - const hasResultsInsight = result && result.insight +const NewResultsTab = (): JSX.Element => { + const { experiment, metricResults } = useValues(experimentLogic) + const hasSomeResults = metricResults?.some((result) => result?.insight) + + const hasSinglePrimaryMetric = experiment.metrics.length === 1 return ( -
- {featureFlags[FEATURE_FLAGS.EXPERIMENTS_MULTIPLE_METRICS] ? ( - - ) : hasResultsInsight ? ( + <> + {!hasSomeResults && ( + <> + {experiment.type === 'web' ? ( + + ) : ( + + )} + + )} + {/* Show overview if there's only a single primary metric */} + {hasSinglePrimaryMetric && ( +
+ +
+ )} + + {/* Show detailed results if there's only a single primary metric */} + {hasSomeResults && hasSinglePrimaryMetric && ( +
+
+ +
+
+ +
+
+ +
+
+ )} + + + ) +} + +const OldResultsTab = (): JSX.Element => { + const { experiment, metricResults } = useValues(experimentLogic) + const hasSomeResults = metricResults?.some((result) => result?.insight) + + return ( + <> + {hasSomeResults ? ( ) : ( <> @@ -54,15 +97,16 @@ const ResultsTab = (): JSX.Element => { )} )} - {featureFlags[FEATURE_FLAGS.EXPERIMENTS_MULTIPLE_METRICS] ? ( - - ) : ( - - )} -
+ + ) } +const ResultsTab = (): JSX.Element => { + const { featureFlags } = useValues(experimentLogic) + return <>{featureFlags[FEATURE_FLAGS.EXPERIMENTS_MULTIPLE_METRICS] ? : } +} + const VariantsTab = (): JSX.Element => { return (
@@ -87,8 +131,8 @@ export function ExperimentView(): JSX.Element { } = useValues(experimentLogic) const { setTabKey } = useActions(experimentLogic) - const result = metricResults?.[0] - const hasResultsInsight = result && result.insight + // Instead, check if any result in the array has an insight + const hasSomeResults = metricResults?.some((result) => result?.insight) return ( <> @@ -103,8 +147,9 @@ export function ExperimentView(): JSX.Element { ) : ( <> - {hasResultsInsight && !featureFlags[FEATURE_FLAGS.EXPERIMENTS_MULTIPLE_METRICS] ? ( + {hasSomeResults && !featureFlags[FEATURE_FLAGS.EXPERIMENTS_MULTIPLE_METRICS] ? (
+

Summary

diff --git a/frontend/src/scenes/experiments/ExperimentView/Overview.tsx b/frontend/src/scenes/experiments/ExperimentView/Overview.tsx index 351e58e946646..a6765a64f3f9f 100644 --- a/frontend/src/scenes/experiments/ExperimentView/Overview.tsx +++ b/frontend/src/scenes/experiments/ExperimentView/Overview.tsx @@ -71,7 +71,6 @@ export function Overview({ metricIndex = 0 }: { metricIndex?: number }): JSX.Ele return (
-

Summary

diff --git a/frontend/src/scenes/experiments/ExperimentView/Results.tsx b/frontend/src/scenes/experiments/ExperimentView/Results.tsx index b0b291554ef90..b031ae5bdf594 100644 --- a/frontend/src/scenes/experiments/ExperimentView/Results.tsx +++ b/frontend/src/scenes/experiments/ExperimentView/Results.tsx @@ -15,7 +15,7 @@ export function Results(): JSX.Element {
- +
) } diff --git a/frontend/src/scenes/experiments/ExperimentView/components.tsx b/frontend/src/scenes/experiments/ExperimentView/components.tsx index cd252785b116c..b52df2d7207a8 100644 --- a/frontend/src/scenes/experiments/ExperimentView/components.tsx +++ b/frontend/src/scenes/experiments/ExperimentView/components.tsx @@ -20,35 +20,23 @@ import { FEATURE_FLAGS } from 'lib/constants' import { dayjs } from 'lib/dayjs' import { IconAreaChart } from 'lib/lemon-ui/icons' import { More } from 'lib/lemon-ui/LemonButton/More' -import { featureFlagLogic } from 'lib/logic/featureFlagLogic' import { useEffect, useState } from 'react' import { urls } from 'scenes/urls' import { groupsModel } from '~/models/groupsModel' -import { filtersToQueryNode } from '~/queries/nodes/InsightQuery/utils/filtersToQueryNode' -import { queryFromFilters } from '~/queries/nodes/InsightViz/utils' import { Query } from '~/queries/Query/Query' import { - CachedExperimentFunnelsQueryResponse, - CachedExperimentTrendsQueryResponse, ExperimentFunnelsQueryResponse, ExperimentTrendsQueryResponse, InsightQueryNode, InsightVizNode, NodeKind, } from '~/queries/schema' -import { - Experiment, - Experiment as ExperimentType, - ExperimentIdType, - ExperimentResults, - InsightShortId, - InsightType, -} from '~/types' +import { Experiment, Experiment as ExperimentType, ExperimentIdType, InsightShortId, InsightType } from '~/types' import { experimentLogic } from '../experimentLogic' import { getExperimentStatus, getExperimentStatusColor } from '../experimentsLogic' -import { getExperimentInsightColour, transformResultFilters } from '../utils' +import { getExperimentInsightColour } from '../utils' export function VariantTag({ experimentId, @@ -128,79 +116,39 @@ export function ResultsTag({ metricIndex = 0 }: { metricIndex?: number }): JSX.E } export function ResultsQuery({ - targetResults, + result, showTable, }: { - targetResults: ExperimentResults['result'] | ExperimentTrendsQueryResponse | ExperimentFunnelsQueryResponse | null + result: ExperimentTrendsQueryResponse | ExperimentFunnelsQueryResponse | null showTable: boolean }): JSX.Element { - const { featureFlags } = useValues(featureFlagLogic) - if (featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOGQL]) { - const newQueryResults = targetResults as unknown as - | CachedExperimentTrendsQueryResponse - | CachedExperimentFunnelsQueryResponse - - const query = - newQueryResults.kind === NodeKind.ExperimentTrendsQuery - ? newQueryResults.count_query - : newQueryResults.funnels_query - const fakeInsightId = Math.random().toString(36).substring(2, 15) - - return ( - - ) - } - - const oldQueryResults = targetResults as ExperimentResults['result'] - - if (!oldQueryResults?.filters) { + if (!result) { return <> } + const query = result.kind === NodeKind.ExperimentTrendsQuery ? result.count_query : result.funnels_query + const fakeInsightId = Math.random().toString(36).substring(2, 15) + return ( } @@ -234,7 +179,7 @@ export function ExploreButton({ return ( } to={urls.insightNew(undefined, undefined, query)} @@ -260,7 +205,7 @@ export function ResultsHeader(): JSX.Element {
-
{result && }
+
{result && }
) @@ -580,12 +525,17 @@ export function PageHeaderCustom(): JSX.Element { } export function ShipVariantModal({ experimentId }: { experimentId: Experiment['id'] }): JSX.Element { - const { experiment, sortedWinProbabilities, isShipVariantModalOpen } = useValues(experimentLogic({ experimentId })) + const { experiment, isShipVariantModalOpen } = useValues(experimentLogic({ experimentId })) const { closeShipVariantModal, shipVariant } = useActions(experimentLogic({ experimentId })) const { aggregationLabel } = useValues(groupsModel) const [selectedVariantKey, setSelectedVariantKey] = useState() - useEffect(() => setSelectedVariantKey(sortedWinProbabilities(0)[0]?.key), [sortedWinProbabilities(0)]) + useEffect(() => { + if (experiment.parameters?.feature_flag_variants?.length > 1) { + // First test variant selected by default + setSelectedVariantKey(experiment.parameters.feature_flag_variants[1].key) + } + }, [experiment]) const aggregationTargetName = experiment.filters.aggregation_group_type_index != null @@ -625,20 +575,19 @@ export function ShipVariantModal({ experimentId }: { experimentId: Experiment['i className="w-full" data-attr="metrics-selector" value={selectedVariantKey} - onChange={(variantKey) => setSelectedVariantKey(variantKey)} - options={sortedWinProbabilities(0).map(({ key }) => ({ - value: key, - label: ( -
- - {key === sortedWinProbabilities(0)[0]?.key && ( - - Winning - - )} -
- ), - }))} + onChange={(variantKey) => { + setSelectedVariantKey(variantKey) + }} + options={ + experiment.parameters?.feature_flag_variants?.map(({ key }) => ({ + value: key, + label: ( +
+ +
+ ), + })) || [] + } />
diff --git a/frontend/src/scenes/experiments/Metrics/SecondaryMetricChartModal.tsx b/frontend/src/scenes/experiments/Metrics/SecondaryMetricChartModal.tsx index ec540aa43c056..184137bb59be6 100644 --- a/frontend/src/scenes/experiments/Metrics/SecondaryMetricChartModal.tsx +++ b/frontend/src/scenes/experiments/Metrics/SecondaryMetricChartModal.tsx @@ -32,7 +32,7 @@ export function SecondaryMetricChartModal({ } > - + ) } diff --git a/frontend/src/scenes/experiments/MetricsView/DeltaChart.tsx b/frontend/src/scenes/experiments/MetricsView/DeltaChart.tsx index 820b7b38acfd3..88d24827fa63a 100644 --- a/frontend/src/scenes/experiments/MetricsView/DeltaChart.tsx +++ b/frontend/src/scenes/experiments/MetricsView/DeltaChart.tsx @@ -1,4 +1,12 @@ -import { IconActivity, IconGraph, IconMinus, IconPencil, IconTrending } from '@posthog/icons' +import { + IconActivity, + IconArrowRight, + IconFunnels, + IconGraph, + IconMinus, + IconPencil, + IconTrending, +} from '@posthog/icons' import { LemonBanner, LemonButton, LemonModal, LemonTag, LemonTagType, Tooltip } from '@posthog/lemon-ui' import { useActions, useValues } from 'kea' import { LemonProgress } from 'lib/lemon-ui/LemonProgress' @@ -35,6 +43,34 @@ function formatTickValue(value: number): string { return `${(value * 100).toFixed(decimals)}%` } +const getMetricTitle = (metric: any, metricType: InsightType): JSX.Element => { + if (metric.name) { + return {metric.name} + } + + if (metricType === InsightType.TRENDS && metric.count_query?.series?.[0]?.name) { + return {metric.count_query.series[0].name} + } + + if (metricType === InsightType.FUNNELS && metric.funnels_query?.series) { + const series = metric.funnels_query.series + if (series.length > 0) { + const firstStep = series[0]?.name + const lastStep = series[series.length - 1]?.name + + return ( + + + {firstStep} + + {lastStep} + + ) + } + } + + return Untitled metric +} export function DeltaChart({ isSecondary, @@ -183,9 +219,9 @@ export function DeltaChart({
-
- {metricIndex + 1}.{' '} - {metric.name || Untitled metric} +
+ {metricIndex + 1}. + {getMetricTitle(metric, metricType)}
-
- } - onClick={() => setIsModalOpen(true)} - > - Detailed results - -
+ {experiment.metrics.length > 1 && ( +
+ } + onClick={() => setIsModalOpen(true)} + > + Detailed results + +
+ )}
)}
@@ -274,34 +312,40 @@ export function DeltaChart({ {isFirstMetric &&
} {/* eslint-disable-next-line react/forbid-dom-props */}
- {variants.map((variant) => ( -
+ {result && + variants.map((variant) => (
-
- + > +
+
+ +
-
- ))} + ))}
{/* SVGs container */} @@ -537,7 +581,7 @@ export function DeltaChart({ ) : (
- +
@@ -792,7 +836,7 @@ export function DeltaChart({
- +
) diff --git a/frontend/src/scenes/experiments/MetricsView/MetricsView.tsx b/frontend/src/scenes/experiments/MetricsView/MetricsView.tsx index 9a362d9961974..2b5735778e1c3 100644 --- a/frontend/src/scenes/experiments/MetricsView/MetricsView.tsx +++ b/frontend/src/scenes/experiments/MetricsView/MetricsView.tsx @@ -1,5 +1,5 @@ import { IconInfo, IconPlus } from '@posthog/icons' -import { LemonButton, Tooltip } from '@posthog/lemon-ui' +import { LemonButton, LemonDivider, Tooltip } from '@posthog/lemon-ui' import { useActions, useValues } from 'kea' import { IconAreaChart } from 'lib/lemon-ui/icons' @@ -101,6 +101,7 @@ export function MetricsView({ isSecondary }: { isSecondary?: boolean }): JSX.Ele const variants = experiment.parameters.feature_flag_variants const results = isSecondary ? secondaryMetricResults : metricResults const errors = isSecondary ? secondaryMetricsResultErrors : primaryMetricsResultErrors + const hasSomeResults = results?.some((result) => result?.insight) let metrics = isSecondary ? experiment.metrics_secondary : experiment.metrics const savedMetrics = experiment.saved_metrics @@ -155,6 +156,52 @@ export function MetricsView({ isSecondary }: { isSecondary?: boolean }): JSX.Ele )} + {hasSomeResults && !isSecondary && ( + <> + + +

+ Each bar shows how a variant is performing compared to the control (the + gray bar) for this metric, using a{' '} + 95% credible interval. That means there's a 95% chance + the true difference for that variant falls within this range. The + vertical "0%" line is your baseline: +

+
    +
  • + To the right (green): The metric is higher (an + improvement). +
  • +
  • + To the left (red): The metric is lower (a + decrease). +
  • +
+

+ The width of the bar represents uncertainty. A{' '} + narrower bar means we're more confident in that result, + while a wider bar means it could shift either way. +

+

+ The control (baseline) is always shown in gray. Other bars will be green + or red—or even a mix—depending on whether the change is positive or + negative. +

+ How to read metrics +
+ } + > + How to read + + + )}
diff --git a/frontend/src/scenes/notebooks/Nodes/NotebookNodeExperiment.tsx b/frontend/src/scenes/notebooks/Nodes/NotebookNodeExperiment.tsx index c87e65370cd23..eb980c068129e 100644 --- a/frontend/src/scenes/notebooks/Nodes/NotebookNodeExperiment.tsx +++ b/frontend/src/scenes/notebooks/Nodes/NotebookNodeExperiment.tsx @@ -82,7 +82,7 @@ const Component = ({ attributes }: NotebookNodeProps
- +
)}