Skip to content

Commit

Permalink
feat(experiments): new secondary metrics view (#27123)
Browse files Browse the repository at this point in the history
  • Loading branch information
jurajmajerik authored Dec 23, 2024
1 parent b22f2f7 commit ca73fa2
Show file tree
Hide file tree
Showing 13 changed files with 342 additions and 364 deletions.
21 changes: 17 additions & 4 deletions frontend/src/scenes/experiments/ExperimentView/ExperimentView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { WebExperimentImplementationDetails } from 'scenes/experiments/WebExperi
import { ExperimentImplementationDetails } from '../ExperimentImplementationDetails'
import { experimentLogic } from '../experimentLogic'
import { PrimaryMetricModal } from '../Metrics/PrimaryMetricModal'
import { SecondaryMetricModal } from '../Metrics/SecondaryMetricModal'
import { MetricsView } from '../MetricsView/MetricsView'
import {
ExperimentLoadingAnimation,
Expand Down Expand Up @@ -52,7 +53,11 @@ const ResultsTab = (): JSX.Element => {
)}
</>
)}
<SecondaryMetricsTable experimentId={experiment.id} />
{featureFlags[FEATURE_FLAGS.EXPERIMENTS_MULTIPLE_METRICS] ? (
<MetricsView isSecondary={true} />
) : (
<SecondaryMetricsTable experimentId={experiment.id} />
)}
</div>
)
}
Expand All @@ -70,8 +75,15 @@ const VariantsTab = (): JSX.Element => {
}

export function ExperimentView(): JSX.Element {
const { experimentLoading, metricResultsLoading, experimentId, metricResults, tabKey, featureFlags } =
useValues(experimentLogic)
const {
experimentLoading,
metricResultsLoading,
secondaryMetricResultsLoading,
experimentId,
metricResults,
tabKey,
featureFlags,
} = useValues(experimentLogic)

const { setTabKey } = useActions(experimentLogic)
const result = metricResults?.[0]
Expand All @@ -86,7 +98,7 @@ export function ExperimentView(): JSX.Element {
) : (
<>
<Info />
{metricResultsLoading ? (
{metricResultsLoading || secondaryMetricResultsLoading ? (
<ExperimentLoadingAnimation />
) : (
<>
Expand Down Expand Up @@ -131,6 +143,7 @@ export function ExperimentView(): JSX.Element {
</>
)}
<PrimaryMetricModal experimentId={experimentId} />
<SecondaryMetricModal experimentId={experimentId} />
<DistributionModal experimentId={experimentId} />
<ReleaseConditionsModal experimentId={experimentId} />
</>
Expand Down
19 changes: 8 additions & 11 deletions frontend/src/scenes/experiments/ExperimentView/Goal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export function MetricDisplayOld({ filters }: { filters?: FilterType }): JSX.Ele

export function ExposureMetric({ experimentId }: { experimentId: Experiment['id'] }): JSX.Element {
const { experiment, featureFlags } = useValues(experimentLogic({ experimentId }))
const { updateExperimentExposure, loadExperiment, setExperiment } = useActions(experimentLogic({ experimentId }))
const { updateExperimentGoal, loadExperiment, setExperiment } = useActions(experimentLogic({ experimentId }))
const [isModalOpen, setIsModalOpen] = useState(false)

const metricIdx = 0
Expand Down Expand Up @@ -213,16 +213,13 @@ export function ExposureMetric({ experimentId }: { experimentId: Experiment['id'
status="danger"
size="xsmall"
onClick={() => {
// :FLAG: CLEAN UP AFTER MIGRATION
if (featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOGQL]) {
setExperiment({
...experiment,
metrics: experiment.metrics.map((metric, idx) =>
idx === metricIdx ? { ...metric, exposure_query: undefined } : metric
),
})
}
updateExperimentExposure(null)
setExperiment({
...experiment,
metrics: experiment.metrics.map((metric, idx) =>
idx === metricIdx ? { ...metric, exposure_query: undefined } : metric
),
})
updateExperimentGoal()
}}
>
Reset
Expand Down
98 changes: 58 additions & 40 deletions frontend/src/scenes/experiments/ExperimentView/Overview.tsx
Original file line number Diff line number Diff line change
@@ -1,62 +1,80 @@
import { useValues } from 'kea'

import { CachedExperimentFunnelsQueryResponse, CachedExperimentTrendsQueryResponse } from '~/queries/schema'
import { ExperimentIdType } from '~/types'

import { experimentLogic } from '../experimentLogic'
import { VariantTag } from './components'

export function Overview(): JSX.Element {
const { experimentId, metricResults, getIndexForVariant, getHighestProbabilityVariant, areResultsSignificant } =
useValues(experimentLogic)
export function WinningVariantText({
result,
experimentId,
}: {
result: CachedExperimentFunnelsQueryResponse | CachedExperimentTrendsQueryResponse
experimentId: ExperimentIdType
}): JSX.Element {
const { getIndexForVariant, getHighestProbabilityVariant } = useValues(experimentLogic)

const result = metricResults?.[0]
if (!result) {
return <></>
}

function WinningVariantText(): JSX.Element {
const highestProbabilityVariant = getHighestProbabilityVariant(
result as CachedExperimentFunnelsQueryResponse | CachedExperimentTrendsQueryResponse
)
const index = getIndexForVariant(
result as CachedExperimentFunnelsQueryResponse | CachedExperimentTrendsQueryResponse,
highestProbabilityVariant || ''
)
if (highestProbabilityVariant && index !== null && result) {
const { probability } = result

return (
<div className="items-center inline-flex flex-wrap">
<VariantTag experimentId={experimentId} variantKey={highestProbabilityVariant} />
<span>&nbsp;is winning with a&nbsp;</span>
<span className="font-semibold text-success items-center">
{`${(probability[highestProbabilityVariant] * 100).toFixed(2)}% probability`}&nbsp;
</span>
<span>of being best.&nbsp;</span>
</div>
)
}
const highestProbabilityVariant = getHighestProbabilityVariant(result)
const index = getIndexForVariant(result, highestProbabilityVariant || '')
if (highestProbabilityVariant && index !== null && result) {
const { probability } = result

return <></>
}

function SignificanceText(): JSX.Element {
return (
<div className="flex-wrap">
<span>Your results are&nbsp;</span>
<span className="font-semibold">
{`${areResultsSignificant(0) ? 'significant' : 'not significant'}`}.
<div className="items-center inline-flex flex-wrap">
<VariantTag experimentId={experimentId} variantKey={highestProbabilityVariant} />
<span>&nbsp;is winning with a&nbsp;</span>
<span className="font-semibold text-success items-center">
{`${(probability[highestProbabilityVariant] * 100).toFixed(2)}% probability`}&nbsp;
</span>
<span>of being best.&nbsp;</span>
</div>
)
}

return <></>
}

export function SignificanceText({
metricIndex,
isSecondary = false,
}: {
metricIndex: number
isSecondary?: boolean
}): JSX.Element {
const { isPrimaryMetricSignificant, isSecondaryMetricSignificant } = useValues(experimentLogic)

return (
<div className="flex-wrap">
<span>Your results are&nbsp;</span>
<span className="font-semibold">
{`${
isSecondary
? isSecondaryMetricSignificant(metricIndex)
: isPrimaryMetricSignificant(metricIndex)
? 'significant'
: 'not significant'
}`}
.
</span>
</div>
)
}

export function Overview({ metricIndex = 0 }: { metricIndex?: number }): JSX.Element {
const { experimentId, metricResults } = useValues(experimentLogic)

const result = metricResults?.[metricIndex]
if (!result) {
return <></>
}

return (
<div>
<h2 className="font-semibold text-lg">Summary</h2>
<div className="items-center inline-flex flex-wrap">
<WinningVariantText />
<SignificanceText />
<WinningVariantText result={result} experimentId={experimentId} />
<SignificanceText metricIndex={metricIndex} />
</div>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IconInfo, IconPencil, IconPlus } from '@posthog/icons'
import { IconInfo, IconPencil } from '@posthog/icons'
import { LemonButton, LemonTable, LemonTableColumns, Tooltip } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { EntityFilterInfo } from 'lib/components/EntityFilterInfo'
Expand All @@ -8,15 +8,13 @@ import { useState } from 'react'

import { Experiment, InsightType } from '~/types'

import { experimentLogic, getDefaultFunnelsMetric, TabularSecondaryMetricResults } from '../experimentLogic'
import { experimentLogic, TabularSecondaryMetricResults } from '../experimentLogic'
import { SecondaryMetricChartModal } from '../Metrics/SecondaryMetricChartModal'
import { SecondaryMetricModal } from '../Metrics/SecondaryMetricModal'
import { MAX_SECONDARY_METRICS } from '../MetricsView/const'
import { AddSecondaryMetric } from '../MetricsView/MetricsView'
import { VariantTag } from './components'

const MAX_SECONDARY_METRICS = 10

export function SecondaryMetricsTable({ experimentId }: { experimentId: Experiment['id'] }): JSX.Element {
const [isEditModalOpen, setIsEditModalOpen] = useState(false)
const [isChartModalOpen, setIsChartModalOpen] = useState(false)
const [modalMetricIdx, setModalMetricIdx] = useState<number | null>(null)

Expand All @@ -34,18 +32,7 @@ export function SecondaryMetricsTable({ experimentId }: { experimentId: Experime
experimentMathAggregationForTrends,
getHighestProbabilityVariant,
} = useValues(experimentLogic({ experimentId }))
const { loadExperiment } = useActions(experimentLogic({ experimentId }))

const openEditModal = (idx: number): void => {
setModalMetricIdx(idx)
setIsEditModalOpen(true)
}

const closeEditModal = (): void => {
setIsEditModalOpen(false)
setModalMetricIdx(null)
loadExperiment()
}
const { openSecondaryMetricModal } = useActions(experimentLogic({ experimentId }))

const openChartModal = (idx: number): void => {
setModalMetricIdx(idx)
Expand Down Expand Up @@ -107,7 +94,7 @@ export function SecondaryMetricsTable({ experimentId }: { experimentId: Experime
type="secondary"
size="xsmall"
icon={<IconPencil />}
onClick={() => openEditModal(idx)}
onClick={() => openSecondaryMetricModal(idx)}
/>
</div>
</div>
Expand Down Expand Up @@ -266,11 +253,7 @@ export function SecondaryMetricsTable({ experimentId }: { experimentId: Experime
<div className="ml-auto">
{metrics && metrics.length > 0 && (
<div className="mb-2 mt-4 justify-end">
<AddSecondaryMetricButton
experimentId={experimentId}
metrics={metrics}
openEditModal={openEditModal}
/>
<AddSecondaryMetric />
</div>
)}
</div>
Expand All @@ -292,21 +275,11 @@ export function SecondaryMetricsTable({ experimentId }: { experimentId: Experime
Add up to {MAX_SECONDARY_METRICS} secondary metrics to monitor side effects of your
experiment.
</div>
<AddSecondaryMetricButton
experimentId={experimentId}
metrics={metrics}
openEditModal={openEditModal}
/>
<AddSecondaryMetric />
</div>
</div>
)}
</div>
<SecondaryMetricModal
metricIdx={modalMetricIdx ?? 0}
isOpen={isEditModalOpen}
onClose={closeEditModal}
experimentId={experimentId}
/>
<SecondaryMetricChartModal
experimentId={experimentId}
metricIdx={modalMetricIdx ?? 0}
Expand All @@ -316,37 +289,3 @@ export function SecondaryMetricsTable({ experimentId }: { experimentId: Experime
</>
)
}

const AddSecondaryMetricButton = ({
experimentId,
metrics,
openEditModal,
}: {
experimentId: Experiment['id']
metrics: any
openEditModal: (metricIdx: number) => void
}): JSX.Element => {
const { experiment } = useValues(experimentLogic({ experimentId }))
const { setExperiment } = useActions(experimentLogic({ experimentId }))
return (
<LemonButton
icon={<IconPlus />}
type="secondary"
size="xsmall"
onClick={() => {
const newMetricsSecondary = [...experiment.metrics_secondary, getDefaultFunnelsMetric()]
setExperiment({
metrics_secondary: newMetricsSecondary,
})
openEditModal(newMetricsSecondary.length - 1)
}}
disabledReason={
metrics.length >= MAX_SECONDARY_METRICS
? `You can only add up to ${MAX_SECONDARY_METRICS} secondary metrics.`
: undefined
}
>
Add metric
</LemonButton>
)
}
14 changes: 11 additions & 3 deletions frontend/src/scenes/experiments/ExperimentView/SummaryTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,30 @@ import {
import { experimentLogic } from '../experimentLogic'
import { VariantTag } from './components'

export function SummaryTable(): JSX.Element {
export function SummaryTable({
metricIndex = 0,
isSecondary = false,
}: {
metricIndex?: number
isSecondary?: boolean
}): JSX.Element {
const {
experimentId,
experiment,
metricResults,
secondaryMetricResults,
tabularExperimentResults,
getMetricType,
getSecondaryMetricType,
exposureCountDataForVariant,
conversionRateForVariant,
experimentMathAggregationForTrends,
countDataForVariant,
getHighestProbabilityVariant,
credibleIntervalForVariant,
} = useValues(experimentLogic)
const metricType = getMetricType(0)
const result = metricResults?.[0]
const metricType = isSecondary ? getSecondaryMetricType(metricIndex) : getMetricType(metricIndex)
const result = isSecondary ? secondaryMetricResults?.[metricIndex] : metricResults?.[metricIndex]
if (!result) {
return <></>
}
Expand Down
Loading

0 comments on commit ca73fa2

Please sign in to comment.