Skip to content

Commit

Permalink
feat(experiment): "Make decision" follow ups (#24422)
Browse files Browse the repository at this point in the history
Co-authored-by: Dylan Martin <[email protected]>
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Aug 19, 2024
1 parent 6bd8d4b commit 2b02e80
Show file tree
Hide file tree
Showing 6 changed files with 242 additions and 87 deletions.
10 changes: 10 additions & 0 deletions frontend/src/lib/utils/eventUsageLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@ export const eventUsageLogic = kea<eventUsageLogicType>([
newCohort,
}),
reportExperimentInsightLoadFailed: true,
reportExperimentVariantShipped: (experiment: Experiment) => ({ experiment }),
// Definition Popover
reportDataManagementDefinitionHovered: (type: TaxonomicFilterGroupType) => ({ type }),
reportDataManagementDefinitionClickView: (type: TaxonomicFilterGroupType) => ({ type }),
Expand Down Expand Up @@ -1067,6 +1068,15 @@ export const eventUsageLogic = kea<eventUsageLogicType>([
reportExperimentInsightLoadFailed: () => {
posthog.capture('experiment load insight failed')
},
reportExperimentVariantShipped: ({ experiment }) => {
posthog.capture('experiment variant shipped', {
name: experiment.name,
id: experiment.id,
filters: sanitizeFilterParams(experiment.filters),
parameters: experiment.parameters,
secondary_metrics_count: experiment.secondary_metrics.length,
})
},
reportPropertyGroupFilterAdded: () => {
posthog.capture('property group filter added')
},
Expand Down
124 changes: 63 additions & 61 deletions frontend/src/scenes/experiments/ExperimentView/components.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import '../Experiment.scss'

import { IconArchive, IconCheck, IconMagicWand, IconX } from '@posthog/icons'
import { IconArchive, IconCheck, IconInfo, IconMagicWand, IconX } from '@posthog/icons'
import {
LemonBanner,
LemonButton,
LemonCheckbox,
LemonDialog,
LemonDivider,
LemonModal,
Expand Down Expand Up @@ -335,7 +336,6 @@ export function PageHeaderCustom(): JSX.Element {
} = useValues(experimentLogic)
const {
launchExperiment,
resetRunningExperiment,
endExperiment,
archiveExperiment,
setEditExperiment,
Expand Down Expand Up @@ -404,36 +404,7 @@ export function PageHeaderCustom(): JSX.Element {
<LemonDivider vertical />
</>
)}
<ResetButton
experiment={experiment}
onConfirm={() => {
LemonDialog.open({
title: 'Reset this experiment?',
content: (
<div className="text-sm text-muted">
All collected data so far will be discarded and the experiment will go
back to draft mode.
{experiment.archived && (
<div className="mt-2">
Resetting will also unarchive the experiment.
</div>
)}
</div>
),
primaryButton: {
children: 'Reset',
type: 'primary',
onClick: () => resetRunningExperiment(),
size: 'small',
},
secondaryButton: {
children: 'Cancel',
type: 'tertiary',
size: 'small',
},
})
}}
/>
<ResetButton experimentId={experiment.id} />
{!experiment.end_date && (
<LemonButton
type="secondary"
Expand Down Expand Up @@ -519,11 +490,14 @@ export function PageHeaderCustom(): JSX.Element {
}

export function MakeDecisionModal({ experimentId }: { experimentId: Experiment['id'] }): JSX.Element {
const { experiment, sortedWinProbabilities, isMakeDecisionModalOpen } = useValues(experimentLogic({ experimentId }))
const { experiment, sortedWinProbabilities, isMakeDecisionModalOpen, isExperimentStopped } = useValues(
experimentLogic({ experimentId })
)
const { closeMakeDecisionModal, shipVariant } = useActions(experimentLogic({ experimentId }))
const { aggregationLabel } = useValues(groupsModel)

const [selectedVariantKey, setSelectedVariantKey] = useState<string | null>()
const [shouldStopExperiment, setShouldStopExperiment] = useState(true)
useEffect(() => setSelectedVariantKey(sortedWinProbabilities[0]?.key), [sortedWinProbabilities])

const aggregationTargetName =
Expand All @@ -542,34 +516,65 @@ export function MakeDecisionModal({ experimentId }: { experimentId: Experiment['
<LemonButton type="secondary" onClick={closeMakeDecisionModal}>
Cancel
</LemonButton>
<LemonButton onClick={() => shipVariant(selectedVariantKey)} type="primary">
<LemonButton
onClick={() => shipVariant({ selectedVariantKey, shouldStopExperiment })}
type="primary"
>
Ship variant
</LemonButton>
</div>
}
>
<div className="space-y-4">
<div className="space-y-6">
<div className="text-sm">
This action will roll out the selected variant to <b>100% of {aggregationTargetName}.</b>
</div>
<LemonSelect
data-attr="metrics-selector"
value={selectedVariantKey}
onChange={(variantKey) => setSelectedVariantKey(variantKey)}
options={sortedWinProbabilities.map(({ key }) => ({
value: key,
label: (
<div className="space-x-2 inline-flex">
<VariantTag experimentId={experimentId} variantKey={key} />
{key === sortedWinProbabilities[0]?.key && (
<LemonTag type="success">
<b className="uppercase">Winning</b>
</LemonTag>
)}
<div className="flex items-center">
<div className="w-1/2 pr-4">
<LemonSelect
className="w-full"
data-attr="metrics-selector"
value={selectedVariantKey}
onChange={(variantKey) => setSelectedVariantKey(variantKey)}
options={sortedWinProbabilities.map(({ key }) => ({
value: key,
label: (
<div className="space-x-2 inline-flex">
<VariantTag experimentId={experimentId} variantKey={key} />
{key === sortedWinProbabilities[0]?.key && (
<LemonTag type="success">
<b className="uppercase">Winning</b>
</LemonTag>
)}
</div>
),
}))}
/>
</div>
{!isExperimentStopped && (
<>
<LemonDivider className="my-0" vertical />
<div className="w-2/5 pl-4">
<LemonCheckbox
id="flag-enabled-checkbox"
label={
<div className="inline-flex items-center space-x-1">
<div className="">Stop experiment</div>
<Tooltip
title="This will end data collection. The experiment can be
restarted later if needed."
>
<IconInfo className="text-muted-alt text-base" />
</Tooltip>
</div>
}
onChange={() => setShouldStopExperiment(!shouldStopExperiment)}
checked={shouldStopExperiment}
/>
</div>
),
}))}
/>
</>
)}
</div>
<LemonBanner type="info" className="mb-4">
For more precise control over your release, adjust the rollout percentage and release conditions in
the{' '}
Expand Down Expand Up @@ -778,20 +783,17 @@ export function ActionBanner(): JSX.Element {
return <></>
}

export const ResetButton = ({
experiment,
onConfirm,
}: {
experiment: ExperimentType
onConfirm: () => void
}): JSX.Element => {
export const ResetButton = ({ experimentId }: { experimentId: number | 'new' }): JSX.Element => {
const { experiment } = useValues(experimentLogic({ experimentId }))
const { resetRunningExperiment } = useActions(experimentLogic)

const onClickReset = (): void => {
LemonDialog.open({
title: 'Reset this experiment?',
content: (
<>
<div className="text-sm text-muted">
All collected data so far will be discarded and the experiment will go back to draft mode.
All data collected so far will be discarded and the experiment will go back to draft mode.
</div>
{experiment.archived && (
<div className="text-sm text-muted">Resetting will also unarchive the experiment.</div>
Expand All @@ -801,7 +803,7 @@ export const ResetButton = ({
primaryButton: {
children: 'Confirm',
type: 'primary',
onClick: onConfirm,
onClick: resetRunningExperiment,
size: 'small',
},
secondaryButton: {
Expand Down
43 changes: 20 additions & 23 deletions frontend/src/scenes/experiments/experimentLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ import {
import { EXPERIMENT_EXPOSURE_INSIGHT_ID, EXPERIMENT_INSIGHT_ID } from './constants'
import type { experimentLogicType } from './experimentLogicType'
import { experimentsLogic } from './experimentsLogic'
import { getMinimumDetectableEffect } from './utils'
import { getMinimumDetectableEffect, transformFiltersForWinningVariant } from './utils'

const NEW_EXPERIMENT: Experiment = {
id: 'new',
Expand Down Expand Up @@ -125,6 +125,7 @@ export const experimentLogic = kea<experimentLogicType>([
'reportExperimentArchived',
'reportExperimentReset',
'reportExperimentExposureCohortCreated',
'reportExperimentVariantShipped',
],
insightDataLogic({ dashboardItemId: EXPERIMENT_INSIGHT_ID }),
['setQuery'],
Expand Down Expand Up @@ -703,10 +704,18 @@ export const experimentLogic = kea<experimentLogicType>([
})
}
},
shipVariantSuccess: () => {
shipVariantSuccess: ({ payload }) => {
lemonToast.success('The selected variant has been shipped')
actions.closeMakeDecisionModal()
if (payload.shouldStopExperiment) {
actions.endExperiment()
}
actions.loadExperiment()
actions.reportExperimentVariantShipped(values.experiment)
},
shipVariantFailure: ({ error }) => {
lemonToast.error(error)
actions.closeMakeDecisionModal()
},
})),
loaders(({ actions, props, values }) => ({
Expand Down Expand Up @@ -804,32 +813,20 @@ export const experimentLogic = kea<experimentLogicType>([
featureFlag: [
null as FeatureFlagType | null,
{
shipVariant: async (selectedVariant) => {
const currentFlagFilters = values.experiment.feature_flag?.filters

const newFilters = {
aggregation_group_type_index: currentFlagFilters?.aggregation_group_type_index || null,
payloads: currentFlagFilters?.payloads || {},
multivariate: {
variants: currentFlagFilters?.multivariate?.variants.map(({ key, name }) => ({
key,
rollout_percentage: key === selectedVariant ? 100 : 0,
...(name && { name }),
})),
},
groups: [
{ properties: [], rollout_percentage: 100 },
// Preserve existing groups so that users can roll back this action
// by deleting the newly added release condition
...(currentFlagFilters?.groups || []),
],
shipVariant: async ({ selectedVariantKey, shouldStopExperiment }) => {
if (!values.experiment.feature_flag) {
throw new Error('Experiment does not have a feature flag linked')
}

const savedFlag = await api.update(
const currentFlagFilters = values.experiment.feature_flag?.filters
const newFilters = transformFiltersForWinningVariant(currentFlagFilters, selectedVariantKey)

await api.update(
`api/projects/${values.currentTeamId}/feature_flags/${values.experiment.feature_flag?.id}`,
{ filters: newFilters }
)
return savedFlag

return shouldStopExperiment
},
},
],
Expand Down
Loading

0 comments on commit 2b02e80

Please sign in to comment.