Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(experiments): holdout groups UI #25772

Merged
merged 25 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
af17ad1
init
jurajmajerik Oct 23, 2024
c003b75
wip
jurajmajerik Oct 23, 2024
90626b9
wip
jurajmajerik Oct 23, 2024
3fd49d1
wip
jurajmajerik Oct 23, 2024
27b0e90
Update UI snapshots for `chromium` (1)
github-actions[bot] Oct 23, 2024
5a5850d
Update UI snapshots for `chromium` (2)
github-actions[bot] Oct 23, 2024
ccbe969
Update UI snapshots for `chromium` (1)
github-actions[bot] Oct 23, 2024
8828df8
wip
jurajmajerik Oct 24, 2024
3ce742e
Merge branch 'experiment-holdouts-ui' of https://github.com/PostHog/p…
jurajmajerik Oct 24, 2024
e6b7b88
Merge branch 'master' of https://github.com/PostHog/posthog into expe…
jurajmajerik Oct 25, 2024
fc5d0ce
wip
jurajmajerik Oct 25, 2024
adeba07
Update UI snapshots for `chromium` (1)
github-actions[bot] Oct 25, 2024
67b14a1
Update UI snapshots for `chromium` (1)
github-actions[bot] Oct 25, 2024
336b1b6
wip
jurajmajerik Oct 25, 2024
1850c15
Merge branch 'experiment-holdouts-ui' of https://github.com/PostHog/p…
jurajmajerik Oct 25, 2024
3b37e8b
wip
jurajmajerik Oct 25, 2024
94508b7
Update UI snapshots for `chromium` (1)
github-actions[bot] Oct 25, 2024
020d24f
Merge branch 'master' of https://github.com/PostHog/posthog into expe…
jurajmajerik Oct 25, 2024
e5bf641
address feedback
jurajmajerik Oct 25, 2024
81d2600
Update query snapshots
github-actions[bot] Oct 25, 2024
23f406c
Update query snapshots
github-actions[bot] Oct 25, 2024
145915f
Merge branch 'master' into experiment-holdouts-ui
jurajmajerik Oct 25, 2024
8b0ca4a
Update query snapshots
github-actions[bot] Oct 25, 2024
a2802a9
Update UI snapshots for `chromium` (1)
github-actions[bot] Oct 25, 2024
fb86847
Update UI snapshots for `chromium` (1)
github-actions[bot] Oct 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ee/clickhouse/queries/experiments/funnel_experiment_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
calculate_credible_intervals,
calculate_probabilities,
)
from posthog.models.experiment import ExperimentHoldout
from posthog.models.feature_flag import FeatureFlag
from posthog.models.filters.filter import Filter
from posthog.models.team import Team
Expand Down Expand Up @@ -54,10 +55,13 @@ def __init__(
feature_flag: FeatureFlag,
experiment_start_date: datetime,
experiment_end_date: Optional[datetime] = None,
holdout: Optional[ExperimentHoldout] = None,
funnel_class: type[ClickhouseFunnel] = ClickhouseFunnel,
):
breakdown_key = f"$feature/{feature_flag.key}"
self.variants = [variant["key"] for variant in feature_flag.variants]
if holdout:
self.variants.append(f"holdout-{holdout.id}")

# our filters assume that the given time ranges are in the project timezone.
# while start and end date are in UTC.
Expand Down
4 changes: 4 additions & 0 deletions ee/clickhouse/queries/experiments/trend_experiment_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
calculate_credible_intervals,
calculate_probabilities,
)
from posthog.models.experiment import ExperimentHoldout
from posthog.models.feature_flag import FeatureFlag
from posthog.models.filters.filter import Filter
from posthog.models.team import Team
Expand Down Expand Up @@ -81,9 +82,12 @@ def __init__(
experiment_end_date: Optional[datetime] = None,
trend_class: type[Trends] = Trends,
custom_exposure_filter: Optional[Filter] = None,
holdout: Optional[ExperimentHoldout] = None,
):
breakdown_key = f"$feature/{feature_flag.key}"
self.variants = [variant["key"] for variant in feature_flag.variants]
if holdout:
self.variants.append(f"holdout-{holdout.id}")

# our filters assume that the given time ranges are in the project timezone.
# while start and end date are in UTC.
Expand Down
2 changes: 2 additions & 0 deletions ee/clickhouse/views/experiments.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ def _calculate_experiment_results(experiment: Experiment, refresh: bool = False)
experiment.feature_flag,
experiment.start_date,
experiment.end_date,
holdout=experiment.holdout,
custom_exposure_filter=exposure_filter,
).get_results()
else:
Expand All @@ -60,6 +61,7 @@ def _calculate_experiment_results(experiment: Experiment, refresh: bool = False)
experiment.feature_flag,
experiment.start_date,
experiment.end_date,
holdout=experiment.holdout,
).get_results()

return _experiment_results_cached(
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions frontend/src/lib/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ export const FEATURE_FLAGS = {
LEGACY_ACTION_WEBHOOKS: 'legacy-action-webhooks', // owner: @mariusandra #team-cdp
SESSION_REPLAY_URL_TRIGGER: 'session-replay-url-trigger', // owner: @richard-better #team-replay
REPLAY_TEMPLATES: 'replay-templates', // owner: @raquelmsmith #team-replay
EXPERIMENTS_HOLDOUTS: 'experiments-holdouts', // owner: @jurajmajerik #team-experiments
MESSAGING: 'messaging', // owner @mariusandra #team-cdp
} as const
export type FeatureFlagKey = (typeof FEATURE_FLAGS)[keyof typeof FEATURE_FLAGS]
Expand Down
40 changes: 38 additions & 2 deletions frontend/src/scenes/experiments/ExperimentForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import { LemonDivider, LemonInput, LemonTextArea, Tooltip } from '@posthog/lemon
import { BindLogic, useActions, useValues } from 'kea'
import { Form, Group } from 'kea-forms'
import { ExperimentVariantNumber } from 'lib/components/SeriesGlyph'
import { MAX_EXPERIMENT_VARIANTS } from 'lib/constants'
import { FEATURE_FLAGS, MAX_EXPERIMENT_VARIANTS } from 'lib/constants'
import { IconChevronLeft } from 'lib/lemon-ui/icons'
import { LemonButton } from 'lib/lemon-ui/LemonButton'
import { LemonField } from 'lib/lemon-ui/LemonField'
import { LemonRadio } from 'lib/lemon-ui/LemonRadio'
import { LemonSelect } from 'lib/lemon-ui/LemonSelect'
import { capitalizeFirstLetter } from 'lib/utils'
import { useEffect } from 'react'
import { insightDataLogic } from 'scenes/insights/insightDataLogic'
Expand All @@ -23,7 +24,7 @@ import { experimentLogic } from './experimentLogic'
import { ExperimentInsightCreator } from './MetricSelector'

const StepInfo = (): JSX.Element => {
const { experiment } = useValues(experimentLogic)
const { experiment, featureFlags } = useValues(experimentLogic)
const { addExperimentGroup, removeExperimentGroup, moveToNextFormStep } = useActions(experimentLogic)

return (
Expand Down Expand Up @@ -134,6 +135,14 @@ const StepInfo = (): JSX.Element => {
</div>
</div>
</div>
{featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOLDOUTS] && (
<div>
<h3>Holdout group</h3>
<div className="text-xs text-muted">Exclude a stable group of users from the experiment.</div>
<LemonDivider />
<HoldoutSelector />
</div>
)}
</div>
<LemonButton
className="mt-2"
Expand Down Expand Up @@ -276,6 +285,33 @@ const StepGoal = (): JSX.Element => {
)
}

export const HoldoutSelector = (): JSX.Element => {
const { experiment, holdouts } = useValues(experimentLogic)
const { setExperiment } = useActions(experimentLogic)

const holdoutOptions = holdouts.map((holdout) => ({
value: holdout.id,
label: holdout.name,
}))
holdoutOptions.unshift({ value: null, label: 'No holdout' })

return (
<div className="mt-4 mb-8">
<LemonSelect
options={holdoutOptions}
value={experiment.holdout_id || null}
onChange={(value) => {
setExperiment({
...experiment,
holdout_id: value,
})
}}
data-attr="experiment-holdout-selector"
/>
</div>
)
}

export function ExperimentForm(): JSX.Element {
const { currentFormStep, props } = useValues(experimentLogic)
const { setCurrentFormStep } = useActions(experimentLogic)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import '../Experiment.scss'

import { LemonDivider } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { FEATURE_FLAGS } from 'lib/constants'
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'

import { ExperimentImplementationDetails } from '../ExperimentImplementationDetails'
import { experimentLogic } from '../experimentLogic'
Expand All @@ -15,6 +17,7 @@ import {
import { DataCollection } from './DataCollection'
import { DistributionTable } from './DistributionTable'
import { ExperimentExposureModal, ExperimentGoalModal, Goal } from './Goal'
import { HoldoutSelector } from './HoldoutSelector'
import { Info } from './Info'
import { Overview } from './Overview'
import { ReleaseConditionsTable } from './ReleaseConditionsTable'
Expand All @@ -24,6 +27,7 @@ import { SecondaryMetricsTable } from './SecondaryMetricsTable'
export function ExperimentView(): JSX.Element {
const { experiment, experimentLoading, experimentResultsLoading, experimentId, experimentResults } =
useValues(experimentLogic)
const { featureFlags } = useValues(featureFlagLogic)

const { updateExperimentSecondaryMetrics } = useActions(experimentLogic)

Expand All @@ -47,6 +51,7 @@ export function ExperimentView(): JSX.Element {
<div className="xl:flex">
<div className="w-1/2 pr-2">
<Goal />
{featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOLDOUTS] && <HoldoutSelector />}
</div>

<div className="w-1/2 xl:pl-2 mt-8 xl:mt-0">
Expand All @@ -60,6 +65,7 @@ export function ExperimentView(): JSX.Element {
<div className="xl:flex">
<div className="w-1/2 pr-2">
<Goal />
{featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOLDOUTS] && <HoldoutSelector />}
</div>

<div className="w-1/2 xl:pl-2 mt-8 xl:mt-0">
Expand Down
47 changes: 47 additions & 0 deletions frontend/src/scenes/experiments/ExperimentView/HoldoutSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { IconInfo } from '@posthog/icons'
import { LemonSelect, Tooltip } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'

import { experimentLogic } from '../experimentLogic'

export function HoldoutSelector(): JSX.Element {
const { experiment, holdouts, isExperimentRunning } = useValues(experimentLogic)
const { setExperiment, updateExperiment } = useActions(experimentLogic)

const holdoutOptions = holdouts.map((holdout) => ({
value: holdout.id,
label: holdout.name,
}))
holdoutOptions.unshift({ value: null, label: 'No holdout' })

return (
<div className="mt-3">
<div className="inline-flex space-x-1">
<h4 className="font-semibold mb-0">Holdout group</h4>
<Tooltip title="Exclude a stable group of users from the experiment. This cannot be changed once the experiment is launched.">
<IconInfo className="text-muted-alt text-base" />
</Tooltip>
</div>
<div className="mt-1">
<LemonSelect
disabledReason={
isExperimentRunning &&
!experiment.end_date &&
'The holdout group cannot be changed once the experiment is launched.'
}
size="xsmall"
options={holdoutOptions}
value={experiment.holdout_id || null}
onChange={(value) => {
setExperiment({
...experiment,
holdout_id: value,
})
updateExperiment({ holdout_id: value })
}}
data-attr="experiment-holdout-selector"
/>
</div>
</div>
)
}
19 changes: 17 additions & 2 deletions frontend/src/scenes/experiments/ExperimentView/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,25 @@ export function VariantTag({
experimentId: number | 'new'
variantKey: string
}): JSX.Element {
const { experimentResults, getIndexForVariant } = useValues(experimentLogic({ experimentId }))
const { experiment, experimentResults, getIndexForVariant } = useValues(experimentLogic({ experimentId }))

if (experiment.holdout && variantKey === `holdout-${experiment.holdout_id}`) {
return (
<span className="flex items-center space-x-2">
<div
className="w-2 h-2 rounded-full mr-0.5"
// eslint-disable-next-line react/forbid-dom-props
style={{
backgroundColor: getExperimentInsightColour(getIndexForVariant(experimentResults, variantKey)),
}}
/>
<LemonTag type="option">{experiment.holdout.name}</LemonTag>
</span>
)
}

return (
<span className="flex items-center space-x-1">
<span className="flex items-center space-x-2">
<div
className="w-2 h-2 rounded-full mr-0.5"
// eslint-disable-next-line react/forbid-dom-props
Expand Down
Loading
Loading