Skip to content

Commit 28f4e31

Browse files
authored
feat(experiments): New running time calculator (1/2) (#41862)
1 parent 0b217aa commit 28f4e31

File tree

10 files changed

+984
-80
lines changed

10 files changed

+984
-80
lines changed

frontend/src/lib/constants.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@ export const FEATURE_FLAGS = {
340340
REPLAY_FILTERS_REDESIGN: 'replay-filters-redesign', // owner: @ksvat #team-replay
341341
REPLAY_NEW_DETECTED_URL_COLLECTIONS: 'replay-new-detected-url-collections', // owner: @ksvat #team-replay multivariate
342342
EXPERIMENTS_USE_NEW_CREATE_FORM: 'experiments-use-new-create-form', // owner: @rodrigoi #team-experiments
343+
EXPERIMENTS_NEW_CALCULATOR: 'experiments-new-calculator', // owner: @jurajmajerik #team-experiments
343344
AGENT_MODES: 'phai-agent-modes', // owner: @skoob13 #team-posthog-ai
344345
APP_SHORTCUTS: 'app-shortcuts', // owner: @adamleithp #team-platform-ux
345346
} as const

frontend/src/scenes/experiments/ExperimentView/ExperimentDuration.tsx

Lines changed: 81 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { IconArrowRight, IconCalculator, IconPencil } from '@posthog/icons'
55
import { LemonButton } from '@posthog/lemon-ui'
66

77
import { TZLabel } from 'lib/components/TZLabel'
8+
import { FEATURE_FLAGS } from 'lib/constants'
89
import { dayjs } from 'lib/dayjs'
910
import { LemonCalendarSelect } from 'lib/lemon-ui/LemonCalendar/LemonCalendarSelect'
1011
import { LemonProgress } from 'lib/lemon-ui/LemonProgress'
@@ -78,10 +79,12 @@ const DateButton = ({ date, type, onChange }: DateButtonProps): JSX.Element => {
7879
}
7980

8081
export const ExperimentDuration = (): JSX.Element => {
81-
const { experiment, actualRunningTime } = useValues(experimentLogic)
82+
const { experiment, actualRunningTime, featureFlags } = useValues(experimentLogic)
8283
const { changeExperimentStartDate, changeExperimentEndDate } = useActions(experimentLogic)
8384
const { openCalculateRunningTimeModal } = useActions(modalsLogic)
8485

86+
const useNewCalculator = featureFlags[FEATURE_FLAGS.EXPERIMENTS_NEW_CALCULATOR] === 'test'
87+
8588
const { start_date, end_date } = experiment
8689
const [progressPopoverVisible, setProgressPopoverVisible] = useState(false)
8790
const hideTimeoutRef = useRef<NodeJS.Timeout | null>(null)
@@ -114,85 +117,92 @@ export const ExperimentDuration = (): JSX.Element => {
114117
<IconArrowRight className="text-base" />
115118
<DateButton date={end_date} type="end" onChange={changeExperimentEndDate} />
116119
</div>
117-
<Popover
118-
visible={progressPopoverVisible}
119-
onMouseEnterInside={showPopover}
120-
onMouseLeaveInside={hidePopover}
121-
overlay={
122-
<div className="p-2">
123-
{!recommendedSampleSize || !recommendedRunningTime ? (
124-
<div className="flex justify-center items-center h-full">
125-
<div className="text-center">
126-
<IconCalculator className="text-3xl mb-2 text-tertiary" />
127-
<div className="text-md font-semibold leading-tight mb-3">
128-
No running time yet
120+
{!useNewCalculator && (
121+
<Popover
122+
visible={progressPopoverVisible}
123+
onMouseEnterInside={showPopover}
124+
onMouseLeaveInside={hidePopover}
125+
overlay={
126+
<div className="p-2">
127+
{!recommendedSampleSize || !recommendedRunningTime ? (
128+
<div className="flex justify-center items-center h-full">
129+
<div className="text-center">
130+
<IconCalculator className="text-3xl mb-2 text-tertiary" />
131+
<div className="text-md font-semibold leading-tight mb-3">
132+
No running time yet
133+
</div>
134+
<div className="flex justify-center">
135+
<LemonButton
136+
icon={<IconPencil fontSize="12" />}
137+
size="xsmall"
138+
className="flex items-center gap-2"
139+
type="secondary"
140+
onClick={() => openCalculateRunningTimeModal()}
141+
>
142+
Calculate running time
143+
</LemonButton>
144+
</div>
129145
</div>
130-
<div className="flex justify-center">
146+
</div>
147+
) : (
148+
<>
149+
<LemonProgress
150+
className="w-full border"
151+
bgColor="var(--color-bg-table)"
152+
size="medium"
153+
percent={(actualRunningTime / recommendedRunningTime) * 100}
154+
/>
155+
<div className="text-center mt-2 mb-4 text-xs text-muted">
156+
{actualRunningTime} of {humanFriendlyNumber(recommendedRunningTime, 0)} days
157+
completed ({Math.round((actualRunningTime / recommendedRunningTime) * 100)}
158+
%)
159+
</div>
160+
161+
<div className="space-y-3">
162+
<div>
163+
<div className="card-secondary mb-1">Recommended sample size</div>
164+
<div className="text-sm font-semibold">
165+
{humanFriendlyNumber(recommendedSampleSize, 0)} users
166+
</div>
167+
</div>
168+
<div>
169+
<div className="card-secondary mb-1">Estimated running time</div>
170+
<div className="text-sm font-semibold">
171+
{humanFriendlyNumber(recommendedRunningTime, 0)} days
172+
</div>
173+
</div>
174+
<div>
175+
<div className="card-secondary mb-1">Minimum detectable effect</div>
176+
<div className="text-sm font-semibold">{minimumDetectableEffect}%</div>
177+
</div>
131178
<LemonButton
132-
icon={<IconPencil fontSize="12" />}
133179
size="xsmall"
134-
className="flex items-center gap-2"
180+
className="flex items-center gap-2 mt-4"
135181
type="secondary"
136182
onClick={() => openCalculateRunningTimeModal()}
137183
>
138-
Calculate running time
184+
Recalculate
139185
</LemonButton>
140186
</div>
141-
</div>
142-
</div>
143-
) : (
144-
<>
145-
<LemonProgress
146-
className="w-full border"
147-
bgColor="var(--color-bg-table)"
148-
size="medium"
149-
percent={(actualRunningTime / recommendedRunningTime) * 100}
150-
/>
151-
<div className="text-center mt-2 mb-4 text-xs text-muted">
152-
{actualRunningTime} of {humanFriendlyNumber(recommendedRunningTime, 0)} days
153-
completed ({Math.round((actualRunningTime / recommendedRunningTime) * 100)}%)
154-
</div>
155-
156-
<div className="space-y-3">
157-
<div>
158-
<div className="card-secondary mb-1">Recommended sample size</div>
159-
<div className="text-sm font-semibold">
160-
{humanFriendlyNumber(recommendedSampleSize, 0)} users
161-
</div>
162-
</div>
163-
<div>
164-
<div className="card-secondary mb-1">Estimated running time</div>
165-
<div className="text-sm font-semibold">
166-
{humanFriendlyNumber(recommendedRunningTime, 0)} days
167-
</div>
168-
</div>
169-
<div>
170-
<div className="card-secondary mb-1">Minimum detectable effect</div>
171-
<div className="text-sm font-semibold">{minimumDetectableEffect}%</div>
172-
</div>
173-
<LemonButton
174-
size="xsmall"
175-
className="flex items-center gap-2 mt-4"
176-
type="secondary"
177-
onClick={() => openCalculateRunningTimeModal()}
178-
>
179-
Recalculate
180-
</LemonButton>
181-
</div>
182-
</>
183-
)}
187+
</>
188+
)}
189+
</div>
190+
}
191+
>
192+
<div
193+
onMouseEnter={showPopover}
194+
onMouseLeave={hidePopover}
195+
style={{ color: 'var(--brand-blue)' }}
196+
>
197+
<LemonProgressCircle
198+
progress={
199+
recommendedRunningTime ? Math.min(actualRunningTime / recommendedRunningTime, 1) : 0
200+
}
201+
size={22}
202+
/>
184203
</div>
185-
}
186-
>
187-
<div onMouseEnter={showPopover} onMouseLeave={hidePopover} style={{ color: 'var(--brand-blue)' }}>
188-
<LemonProgressCircle
189-
progress={
190-
recommendedRunningTime ? Math.min(actualRunningTime / recommendedRunningTime, 1) : 0
191-
}
192-
size={22}
193-
/>
194-
</div>
195-
</Popover>
204+
</Popover>
205+
)}
196206
</div>
197207
</div>
198208
)

frontend/src/scenes/experiments/ExperimentView/ExperimentView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ export function ExperimentView({ tabId }: Pick<ExperimentSceneLogicProps, 'tabId
266266
<LoadingState />
267267
) : (
268268
<>
269-
{usesNewQueryRunner ? <Info /> : <LegacyExperimentInfo />}
269+
{usesNewQueryRunner ? <Info tabId={tabId} /> : <LegacyExperimentInfo />}
270270
{usesNewQueryRunner ? <ExperimentHeader /> : <LegacyExperimentHeader />}
271271
<LemonTabs
272272
activeKey={activeTabKey}

frontend/src/scenes/experiments/ExperimentView/Info.tsx

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { IconGear, IconPencil, IconRefresh, IconWarning } from '@posthog/icons'
66
import { LemonButton, LemonModal, Link, ProfilePicture, Tooltip } from '@posthog/lemon-ui'
77

88
import { CopyToClipboardInline } from 'lib/components/CopyToClipboard'
9+
import { FEATURE_FLAGS } from 'lib/constants'
910
import { dayjs } from 'lib/dayjs'
1011
import { usePeriodicRerender } from 'lib/hooks/usePeriodicRerender'
1112
import { LemonTextArea } from 'lib/lemon-ui/LemonTextArea/LemonTextArea'
@@ -18,9 +19,11 @@ import { ExperimentStatsMethod, ProgressStatus } from '~/types'
1819

1920
import { CONCLUSION_DISPLAY_CONFIG } from '../constants'
2021
import { experimentLogic } from '../experimentLogic'
22+
import type { ExperimentSceneLogicProps } from '../experimentSceneLogic'
2123
import { getExperimentStatus } from '../experimentsLogic'
2224
import { modalsLogic } from '../modalsLogic'
2325
import { ExperimentDuration } from './ExperimentDuration'
26+
import { RunningTimeNew } from './RunningTimeNew'
2427
import { StatsMethodModal } from './StatsMethodModal'
2528
import { StatusTag } from './components'
2629

@@ -65,7 +68,7 @@ export const ExperimentLastRefresh = ({
6568
)
6669
}
6770

68-
export function Info(): JSX.Element {
71+
export function Info({ tabId }: Pick<ExperimentSceneLogicProps, 'tabId'>): JSX.Element {
6972
const {
7073
experiment,
7174
legacyPrimaryMetricsResults,
@@ -77,12 +80,19 @@ export function Info(): JSX.Element {
7780
statsMethod,
7881
usesNewQueryRunner,
7982
isExperimentDraft,
83+
featureFlags,
8084
} = useValues(experimentLogic)
8185
const { updateExperiment, refreshExperimentResults } = useActions(experimentLogic)
82-
const { openEditConclusionModal, openDescriptionModal, closeDescriptionModal, openStatsEngineModal } =
83-
useActions(modalsLogic)
86+
const {
87+
openEditConclusionModal,
88+
openDescriptionModal,
89+
closeDescriptionModal,
90+
openStatsEngineModal,
91+
openRunningTimeConfigModal,
92+
} = useActions(modalsLogic)
8493
const { isDescriptionModalOpen } = useValues(modalsLogic)
8594

95+
const useNewCalculator = featureFlags[FEATURE_FLAGS.EXPERIMENTS_NEW_CALCULATOR] === 'test'
8696
const [tempDescription, setTempDescription] = useState(experiment.description || '')
8797

8898
useEffect(() => {
@@ -235,11 +245,20 @@ export function Info(): JSX.Element {
235245
<div className="flex flex-col overflow-hidden items-start min-[1200px]:items-end">
236246
<div className="inline-flex deprecated-space-x-8">
237247
{experiment.start_date && (
238-
<ExperimentLastRefresh
239-
isRefreshing={primaryMetricsResultsLoading || secondaryMetricsResultsLoading}
240-
lastRefresh={lastRefresh}
241-
onClick={() => refreshExperimentResults(true)}
242-
/>
248+
<>
249+
{useNewCalculator && tabId && (
250+
<RunningTimeNew
251+
experiment={experiment}
252+
tabId={tabId}
253+
onClick={openRunningTimeConfigModal}
254+
/>
255+
)}
256+
<ExperimentLastRefresh
257+
isRefreshing={primaryMetricsResultsLoading || secondaryMetricsResultsLoading}
258+
lastRefresh={lastRefresh}
259+
onClick={() => refreshExperimentResults(true)}
260+
/>
261+
</>
243262
)}
244263
<div className="flex flex-col">
245264
<Label intent="menu">Created by</Label>

0 commit comments

Comments
 (0)