Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,8 @@ const ExperimentDetailHeader: FC<ExperimentDetailHeaderProps> = ({
const renderHypothesis = () => {
if (isEditingHypothesis) {
return (
<div className='mt-3' style={{ maxWidth: 640 }}>
<span className='fs-caption text-secondary fw-bold'>Hypothesis</span>
<div className='mt-3'>
<span className='fs-caption text-default fw-bold'>Hypothesis</span>
<div className='d-flex align-items-start gap-2 mt-1'>
<textarea
autoFocus
Expand Down Expand Up @@ -251,14 +251,9 @@ const ExperimentDetailHeader: FC<ExperimentDetailHeaderProps> = ({
}

return (
<div className='mt-3' style={{ maxWidth: 640 }}>
<span className='fs-caption text-secondary fw-bold'>Hypothesis</span>
<div className='d-flex align-items-start gap-1'>
<p className='text-secondary mb-0'>
{experiment.hypothesis || (
<span className='fst-italic'>No hypothesis</span>
)}
</p>
<div className='mt-3'>
<div className='d-flex align-items-center gap-1'>
<span className='fs-caption text-default fw-bold'>Hypothesis</span>
<Button
theme='text'
onClick={startEditingHypothesis}
Expand All @@ -267,6 +262,11 @@ const ExperimentDetailHeader: FC<ExperimentDetailHeaderProps> = ({
<Icon name='edit' width={14} />
</Button>
</div>
<p className='text-secondary mb-0'>
{experiment.hypothesis || (
<span className='fst-italic'>No hypothesis</span>
)}
</p>
</div>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ const ExperimentMetricScorecard: FC<ExperimentMetricScorecardProps> = ({
<>
{metricResult && (
<ExperimentResultsAxisChart
direction={metric.expected_direction}
identities={identities}
metricName={metric.metric_name}
metricResult={metricResult}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { FC, useMemo } from 'react'
import Icon from 'components/icons/Icon'
import { colorTextSuccess } from 'common/theme/tokens'
import { colorTextSuccess, colorTextWarning } from 'common/theme/tokens'
import { BayesianResultsSummary, Experiment } from 'common/types/responses'
import { deriveSummary } from './ExperimentSummaryScorecard'
import { deriveSummary } from './derive'
import VariantName from './VariantName'

type ExperimentRecommendationProps = {
Expand All @@ -21,6 +21,29 @@ const ExperimentRecommendation: FC<ExperimentRecommendationProps> = ({

if (!summary) return null

if (summary.controlWins) {
return (
<div className='alert alert-warning experiment-recommendation mb-3'>
<div className='d-flex align-items-center gap-2 mb-2'>
<Icon name='warning' width={20} fill={colorTextWarning} />
<span className='text-warning fw-semibold'>
Control is the winner
</span>
</div>
<div>
<VariantName name='Control' colour={summary.controlColour} /> has
shown the best performance, with {summary.chanceToBest} probability of
being the best arm. No variant has outperformed it.
</div>
<div className='mt-2'>
Consider rolling out{' '}
<VariantName name='Control' colour={summary.controlColour} /> to all
users.
</div>
</div>
)
}

Comment thread
coderabbitai[bot] marked this conversation as resolved.
return (
<div className='alert alert-success experiment-recommendation mb-3'>
<div className='d-flex align-items-center gap-2 mb-2'>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
import { FC, useMemo } from 'react'
import ColorSwatch from 'components/ColorSwatch'
import { colorTextDanger, colorTextSuccess } from 'common/theme/tokens'
import { BayesianMetricResult, ExpectedDirection } from 'common/types/responses'
import { BayesianMetricResult } from 'common/types/responses'
import {
AxisRange,
VariantIdentity,
buildTicks,
formatLiftPct,
isLiftFavourable,
getLiftColour,
valueToPercent,
} from './derive'

const getLiftColour = (lift: number, direction: ExpectedDirection): string =>
isLiftFavourable(lift, direction) ? colorTextSuccess : colorTextDanger

const TickLines: FC<{ ticks: number[]; range: AxisRange }> = ({
range,
ticks,
Expand All @@ -35,12 +31,10 @@ type ExperimentResultsAxisChartProps = {
identities: VariantIdentity[]
metricName: string
metricResult?: BayesianMetricResult
direction: ExpectedDirection
range: AxisRange
}

const ExperimentResultsAxisChart: FC<ExperimentResultsAxisChartProps> = ({
direction,
identities,
metricName,
metricResult,
Expand Down Expand Up @@ -93,7 +87,7 @@ const ExperimentResultsAxisChart: FC<ExperimentResultsAxisChartProps> = ({
)
}
if (!inf) return null
const colour = getLiftColour(inf.lift, direction)
const colour = getLiftColour(inf.lift)
const ciLeft = valueToPercent(inf.ci_low, range)
const ciRight = valueToPercent(inf.ci_high, range)
const dotPos = valueToPercent(inf.lift, range)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ import Icon from 'components/icons/Icon'
import Tooltip from 'components/Tooltip'
import {
colorIconSecondary,
colorTextDanger,
colorTextSecondary,
colorTextSuccess,
} from 'common/theme/tokens'
import {
BayesianMetricResult,
ExpectedDirection,
ExperimentMetric,
Inference,
MetricAggregation,
Expand All @@ -19,7 +17,7 @@ import {
import {
VariantIdentity,
formatLiftPct,
isLiftFavourable,
getLiftColour,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
liftToPercent,
} from './derive'

Expand All @@ -42,13 +40,9 @@ const renderMetricValue = (
return mean.toFixed(2)
}

const getLiftColour = (lift: number, direction: ExpectedDirection): string =>
isLiftFavourable(lift, direction) ? colorTextSuccess : colorTextDanger

const renderLift = (
identity: VariantIdentity,
inference: Inference | null,
direction: ExpectedDirection,
liftRange: number,
): ReactNode => {
if (identity.isControl) {
Expand All @@ -57,7 +51,7 @@ const renderLift = (
if (!inference) {
return <span className='text-secondary fs-caption'>Collecting data…</span>
}
const colour = getLiftColour(inference.lift, direction)
const colour = getLiftColour(inference.lift)
const left = liftToPercent(inference.ci_low, liftRange)
const right = liftToPercent(inference.ci_high, liftRange)
const dotPos = liftToPercent(inference.lift, liftRange)
Expand Down Expand Up @@ -201,14 +195,7 @@ const ExperimentResultsScorecardTable: FC<
</td>
<td>{stats ? stats.n.toLocaleString() : '—'}</td>
<td>{renderMetricValue(stats, metric.aggregation)}</td>
<td>
{renderLift(
v,
inference,
metric.expected_direction,
liftRange,
)}
</td>
<td>{renderLift(v, inference, liftRange)}</td>
<td>{renderCI(v, inference)}</td>
<td>
{renderWinProbability(v, inference, v.key === winnerKey)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
import { FC, useMemo } from 'react'
import InfoMessage from 'components/InfoMessage'
import { BayesianResultsSummary, Experiment } from 'common/types/responses'
import { getPrimaryMetric } from 'components/experiments/constants'
import {
formatLiftPct,
getMetricResult,
getVariantIdentities,
getWinningVariant,
isLiftFavourable,
} from './derive'
import { deriveSummary } from './derive'
import StatCard from './StatCard'

type ExperimentSummaryScorecardProps = {
Expand All @@ -17,44 +10,6 @@ type ExperimentSummaryScorecardProps = {
results?: BayesianResultsSummary
}

export type SummaryStats = {
winnerName: string
winnerColour: string
controlColour: string
chanceToBest: string
liftVsControl: string
liftFavourable: boolean
}

export const deriveSummary = (
experiment: Experiment,
results: BayesianResultsSummary,
): SummaryStats | null => {
const metric = getPrimaryMetric(experiment)
if (!metric) return null
const metricResult = getMetricResult(results, metric.metric)
if (!metricResult) return null

const identities = getVariantIdentities(experiment.feature)
const winner = getWinningVariant(metricResult, identities)
if (!winner) return null

const winnerIdentity = identities.find((v) => v.key === winner.key)
const controlIdentity = identities.find((v) => v.isControl)

return {
chanceToBest: `${Math.round(winner.chanceToWin * 100)}%`,
controlColour: controlIdentity?.colour ?? '',
liftFavourable: isLiftFavourable(
winner.inference.lift,
metric.expected_direction,
),
liftVsControl: formatLiftPct(winner.inference.lift),
winnerColour: winnerIdentity?.colour ?? '',
winnerName: winner.name,
}
}

const ExperimentSummaryScorecard: FC<ExperimentSummaryScorecardProps> = ({
experiment,
results,
Expand All @@ -66,6 +21,11 @@ const ExperimentSummaryScorecard: FC<ExperimentSummaryScorecardProps> = ({
)
const hasResults = !!results

let liftClassName: string | undefined
if (summary && !summary.controlWins) {
liftClassName = summary.liftFavourable ? 'text-success' : 'text-danger'
}

return (
<>
{!summary && hasResults && (
Expand All @@ -88,7 +48,11 @@ const ExperimentSummaryScorecard: FC<ExperimentSummaryScorecardProps> = ({
loading={!hasResults}
value={
summary?.winnerName ? (
<span className='text-success'>{summary.winnerName}</span>
<span
className={summary.controlWins ? undefined : 'text-success'}
>
{summary.winnerName}
</span>
) : undefined
}
/>
Expand All @@ -106,13 +70,7 @@ const ExperimentSummaryScorecard: FC<ExperimentSummaryScorecardProps> = ({
loading={!hasResults}
value={
summary?.liftVsControl ? (
<span
className={
summary.liftFavourable ? 'text-success' : 'text-danger'
}
>
{summary.liftVsControl}
</span>
<span className={liftClassName}>{summary.liftVsControl}</span>
) : undefined
}
/>
Expand Down
Loading
Loading