Skip to content
1 change: 1 addition & 0 deletions frontend/src/lib/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ export const FEATURE_FLAGS = {
REPLAY_FILTERS_REDESIGN: 'replay-filters-redesign', // owner: @ksvat #team-replay
REPLAY_NEW_DETECTED_URL_COLLECTIONS: 'replay-new-detected-url-collections', // owner: @ksvat #team-replay multivariate
EXPERIMENTS_USE_NEW_CREATE_FORM: 'experiments-use-new-create-form', // owner: @rodrigoi #team-experiments
EXPERIMENTS_NEW_CALCULATOR: 'experiments-new-calculator', // owner: @jurajmajerik #team-experiments
} as const
export type FeatureFlagLookupKey = keyof typeof FEATURE_FLAGS
export type FeatureFlagKey = (typeof FEATURE_FLAGS)[keyof typeof FEATURE_FLAGS]
Expand Down
152 changes: 81 additions & 71 deletions frontend/src/scenes/experiments/ExperimentView/ExperimentDuration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { IconArrowRight, IconCalculator, IconPencil } from '@posthog/icons'
import { LemonButton } from '@posthog/lemon-ui'

import { TZLabel } from 'lib/components/TZLabel'
import { FEATURE_FLAGS } from 'lib/constants'
import { dayjs } from 'lib/dayjs'
import { LemonCalendarSelect } from 'lib/lemon-ui/LemonCalendar/LemonCalendarSelect'
import { LemonProgress } from 'lib/lemon-ui/LemonProgress'
Expand Down Expand Up @@ -78,10 +79,12 @@ const DateButton = ({ date, type, onChange }: DateButtonProps): JSX.Element => {
}

export const ExperimentDuration = (): JSX.Element => {
const { experiment, actualRunningTime } = useValues(experimentLogic)
const { experiment, actualRunningTime, featureFlags } = useValues(experimentLogic)
const { changeExperimentStartDate, changeExperimentEndDate } = useActions(experimentLogic)
const { openCalculateRunningTimeModal } = useActions(modalsLogic)

const useNewCalculator = featureFlags[FEATURE_FLAGS.EXPERIMENTS_NEW_CALCULATOR] === 'test'

const { start_date, end_date } = experiment
const [progressPopoverVisible, setProgressPopoverVisible] = useState(false)
const hideTimeoutRef = useRef<NodeJS.Timeout | null>(null)
Expand Down Expand Up @@ -114,85 +117,92 @@ export const ExperimentDuration = (): JSX.Element => {
<IconArrowRight className="text-base" />
<DateButton date={end_date} type="end" onChange={changeExperimentEndDate} />
</div>
<Popover
visible={progressPopoverVisible}
onMouseEnterInside={showPopover}
onMouseLeaveInside={hidePopover}
overlay={
<div className="p-2">
{!recommendedSampleSize || !recommendedRunningTime ? (
<div className="flex justify-center items-center h-full">
<div className="text-center">
<IconCalculator className="text-3xl mb-2 text-tertiary" />
<div className="text-md font-semibold leading-tight mb-3">
No running time yet
{!useNewCalculator && (
<Popover
visible={progressPopoverVisible}
onMouseEnterInside={showPopover}
onMouseLeaveInside={hidePopover}
overlay={
<div className="p-2">
{!recommendedSampleSize || !recommendedRunningTime ? (
<div className="flex justify-center items-center h-full">
<div className="text-center">
<IconCalculator className="text-3xl mb-2 text-tertiary" />
<div className="text-md font-semibold leading-tight mb-3">
No running time yet
</div>
<div className="flex justify-center">
<LemonButton
icon={<IconPencil fontSize="12" />}
size="xsmall"
className="flex items-center gap-2"
type="secondary"
onClick={() => openCalculateRunningTimeModal()}
>
Calculate running time
</LemonButton>
</div>
</div>
<div className="flex justify-center">
</div>
) : (
<>
<LemonProgress
className="w-full border"
bgColor="var(--color-bg-table)"
size="medium"
percent={(actualRunningTime / recommendedRunningTime) * 100}
/>
<div className="text-center mt-2 mb-4 text-xs text-muted">
{actualRunningTime} of {humanFriendlyNumber(recommendedRunningTime, 0)} days
completed ({Math.round((actualRunningTime / recommendedRunningTime) * 100)}
%)
</div>

<div className="space-y-3">
<div>
<div className="card-secondary mb-1">Recommended sample size</div>
<div className="text-sm font-semibold">
{humanFriendlyNumber(recommendedSampleSize, 0)} users
</div>
</div>
<div>
<div className="card-secondary mb-1">Estimated running time</div>
<div className="text-sm font-semibold">
{humanFriendlyNumber(recommendedRunningTime, 0)} days
</div>
</div>
<div>
<div className="card-secondary mb-1">Minimum detectable effect</div>
<div className="text-sm font-semibold">{minimumDetectableEffect}%</div>
</div>
<LemonButton
icon={<IconPencil fontSize="12" />}
size="xsmall"
className="flex items-center gap-2"
className="flex items-center gap-2 mt-4"
type="secondary"
onClick={() => openCalculateRunningTimeModal()}
>
Calculate running time
Recalculate
</LemonButton>
</div>
</div>
</div>
) : (
<>
<LemonProgress
className="w-full border"
bgColor="var(--color-bg-table)"
size="medium"
percent={(actualRunningTime / recommendedRunningTime) * 100}
/>
<div className="text-center mt-2 mb-4 text-xs text-muted">
{actualRunningTime} of {humanFriendlyNumber(recommendedRunningTime, 0)} days
completed ({Math.round((actualRunningTime / recommendedRunningTime) * 100)}%)
</div>

<div className="space-y-3">
<div>
<div className="card-secondary mb-1">Recommended sample size</div>
<div className="text-sm font-semibold">
{humanFriendlyNumber(recommendedSampleSize, 0)} users
</div>
</div>
<div>
<div className="card-secondary mb-1">Estimated running time</div>
<div className="text-sm font-semibold">
{humanFriendlyNumber(recommendedRunningTime, 0)} days
</div>
</div>
<div>
<div className="card-secondary mb-1">Minimum detectable effect</div>
<div className="text-sm font-semibold">{minimumDetectableEffect}%</div>
</div>
<LemonButton
size="xsmall"
className="flex items-center gap-2 mt-4"
type="secondary"
onClick={() => openCalculateRunningTimeModal()}
>
Recalculate
</LemonButton>
</div>
</>
)}
</>
)}
</div>
}
>
<div
onMouseEnter={showPopover}
onMouseLeave={hidePopover}
style={{ color: 'var(--brand-blue)' }}
>
<LemonProgressCircle
progress={
recommendedRunningTime ? Math.min(actualRunningTime / recommendedRunningTime, 1) : 0
}
size={22}
/>
</div>
}
>
<div onMouseEnter={showPopover} onMouseLeave={hidePopover} style={{ color: 'var(--brand-blue)' }}>
<LemonProgressCircle
progress={
recommendedRunningTime ? Math.min(actualRunningTime / recommendedRunningTime, 1) : 0
}
size={22}
/>
</div>
</Popover>
</Popover>
)}
</div>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ export function ExperimentView({ tabId }: Pick<ExperimentSceneLogicProps, 'tabId
<LoadingState />
) : (
<>
{usesNewQueryRunner ? <Info /> : <LegacyExperimentInfo />}
{usesNewQueryRunner ? <Info tabId={tabId} /> : <LegacyExperimentInfo />}
{usesNewQueryRunner ? <ExperimentHeader /> : <LegacyExperimentHeader />}
<LemonTabs
activeKey={activeTabKey}
Expand Down
35 changes: 27 additions & 8 deletions frontend/src/scenes/experiments/ExperimentView/Info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { IconGear, IconPencil, IconRefresh, IconWarning } from '@posthog/icons'
import { LemonButton, LemonModal, Link, ProfilePicture, Tooltip } from '@posthog/lemon-ui'

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

import { CONCLUSION_DISPLAY_CONFIG } from '../constants'
import { experimentLogic } from '../experimentLogic'
import type { ExperimentSceneLogicProps } from '../experimentSceneLogic'
import { getExperimentStatus } from '../experimentsLogic'
import { modalsLogic } from '../modalsLogic'
import { ExperimentDuration } from './ExperimentDuration'
import { RunningTimeNew } from './RunningTimeNew'
import { StatsMethodModal } from './StatsMethodModal'
import { StatusTag } from './components'

Expand Down Expand Up @@ -65,7 +68,7 @@ export const ExperimentLastRefresh = ({
)
}

export function Info(): JSX.Element {
export function Info({ tabId }: Pick<ExperimentSceneLogicProps, 'tabId'>): JSX.Element {
const {
experiment,
legacyPrimaryMetricsResults,
Expand All @@ -77,12 +80,19 @@ export function Info(): JSX.Element {
statsMethod,
usesNewQueryRunner,
isExperimentDraft,
featureFlags,
} = useValues(experimentLogic)
const { updateExperiment, refreshExperimentResults } = useActions(experimentLogic)
const { openEditConclusionModal, openDescriptionModal, closeDescriptionModal, openStatsEngineModal } =
useActions(modalsLogic)
const {
openEditConclusionModal,
openDescriptionModal,
closeDescriptionModal,
openStatsEngineModal,
openRunningTimeConfigModal,
} = useActions(modalsLogic)
const { isDescriptionModalOpen } = useValues(modalsLogic)

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

useEffect(() => {
Expand Down Expand Up @@ -235,11 +245,20 @@ export function Info(): JSX.Element {
<div className="flex flex-col overflow-hidden items-start min-[1200px]:items-end">
<div className="inline-flex deprecated-space-x-8">
{experiment.start_date && (
<ExperimentLastRefresh
isRefreshing={primaryMetricsResultsLoading || secondaryMetricsResultsLoading}
lastRefresh={lastRefresh}
onClick={() => refreshExperimentResults(true)}
/>
<>
{useNewCalculator && tabId && (
<RunningTimeNew
experiment={experiment}
tabId={tabId}
onClick={openRunningTimeConfigModal}
/>
)}
<ExperimentLastRefresh
isRefreshing={primaryMetricsResultsLoading || secondaryMetricsResultsLoading}
lastRefresh={lastRefresh}
onClick={() => refreshExperimentResults(true)}
/>
</>
)}
<div className="flex flex-col">
<Label intent="menu">Created by</Label>
Expand Down
Loading