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): add delta timeseries chart UI #27960

Merged
merged 6 commits into from
Jan 28, 2025
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
1 change: 1 addition & 0 deletions frontend/src/lib/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ export const FEATURE_FLAGS = {
WEB_REVENUE_TRACKING: 'web-revenue-tracking', // owner: @robbie-c #team-web-analytics
LLM_OBSERVABILITY: 'llm-observability', // owner: #team-ai-product-manager
ONBOARDING_SESSION_REPLAY_SEPERATE_STEP: 'onboarding-session-replay-separate-step', // owner: @joshsny #team-growth
EXPERIMENT_INTERVAL_TIMESERIES: 'experiments-interval-timeseries', // owner: @jurajmajerik #team-experiments
} as const
export type FeatureFlagKey = (typeof FEATURE_FLAGS)[keyof typeof FEATURE_FLAGS]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { MetricModal } from '../Metrics/MetricModal'
import { MetricSourceModal } from '../Metrics/MetricSourceModal'
import { SharedMetricModal } from '../Metrics/SharedMetricModal'
import { MetricsView } from '../MetricsView/MetricsView'
import { VariantDeltaTimeseries } from '../MetricsView/VariantDeltaTimeseries'
import { ExperimentLoadingAnimation, ExploreButton, LoadingState, PageHeaderCustom, ResultsQuery } from './components'
import { CumulativeExposuresChart } from './CumulativeExposuresChart'
import { DataCollection } from './DataCollection'
Expand Down Expand Up @@ -127,6 +128,8 @@ export function ExperimentView(): JSX.Element {

<DistributionModal experimentId={experimentId} />
<ReleaseConditionsModal experimentId={experimentId} />

<VariantDeltaTimeseries />
</>
)}
</div>
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/scenes/experiments/MetricsView/DeltaChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { LemonBanner, LemonButton, LemonModal, LemonTag, LemonTagType, Tooltip } from '@posthog/lemon-ui'
import clsx from 'clsx'
import { useActions, useValues } from 'kea'
import { FEATURE_FLAGS } from 'lib/constants'
import { LemonProgress } from 'lib/lemon-ui/LemonProgress'
import { humanFriendlyNumber } from 'lib/utils'
import { useEffect, useRef, useState } from 'react'
Expand Down Expand Up @@ -104,6 +105,7 @@ export function DeltaChart({
countDataForVariant,
exposureCountDataForVariant,
metricResultsLoading,
featureFlags,
} = useValues(experimentLogic)

const { experiment } = useValues(experimentLogic)
Expand All @@ -112,6 +114,7 @@ export function DeltaChart({
openSecondaryMetricModal,
openPrimarySharedMetricModal,
openSecondarySharedMetricModal,
openVariantDeltaTimeseriesModal,
} = useActions(experimentLogic)
const [tooltipData, setTooltipData] = useState<{ x: number; y: number; variant: string } | null>(null)
const [emptyStateTooltipVisible, setEmptyStateTooltipVisible] = useState(true)
Expand Down Expand Up @@ -401,6 +404,16 @@ export function DeltaChart({
})
}}
onMouseLeave={() => setTooltipData(null)}
onClick={() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not intuitive to me that hovering produces one UI and clicking produces another UI. Not a blocker, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, can adjust later 👍

if (featureFlags[FEATURE_FLAGS.EXPERIMENT_INTERVAL_TIMESERIES]) {
openVariantDeltaTimeseriesModal()
}
}}
className={
featureFlags[FEATURE_FLAGS.EXPERIMENT_INTERVAL_TIMESERIES]
? 'cursor-pointer'
: ''
}
>
{/* Add variant name using VariantTag */}
<foreignObject
Expand Down
151 changes: 151 additions & 0 deletions frontend/src/scenes/experiments/MetricsView/VariantDeltaTimeseries.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { Chart, ChartConfiguration } from 'chart.js/auto'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to have a storybook for this (at some point in the future) that includes the various permutations of data (zero days, one day, 100 days)

import { useActions, useValues } from 'kea'
import { LemonButton } from 'lib/lemon-ui/LemonButton'
import { LemonModal } from 'lib/lemon-ui/LemonModal'
import { useEffect } from 'react'

import { experimentLogic } from '../experimentLogic'

const DELTA = [0.16, 0.17, 0.15, 0.16, 0.14, 0.15, 0.145, 0.15, 0.155, 0.148, 0.15, 0.147, 0.152, 0.15]
const UPPER_BOUND = [0.26, 0.27, 0.24, 0.24, 0.21, 0.21, 0.2, 0.2, 0.195, 0.183, 0.182, 0.177, 0.182, 0.18]
const LOWER_BOUND = [0.06, 0.07, 0.06, 0.08, 0.07, 0.09, 0.09, 0.1, 0.115, 0.113, 0.118, 0.117, 0.122, 0.12]

export const VariantDeltaTimeseries = (): JSX.Element => {
const { closeVariantDeltaTimeseriesModal } = useActions(experimentLogic)
const { isVariantDeltaTimeseriesModalOpen } = useValues(experimentLogic)

useEffect(() => {
if (isVariantDeltaTimeseriesModalOpen) {
setTimeout(() => {
const ctx = document.getElementById('variantDeltaChart') as HTMLCanvasElement
if (!ctx) {
console.error('Canvas element not found')
return
}

const existingChart = Chart.getChart(ctx)
if (existingChart) {
existingChart.destroy()
}

ctx.style.width = '100%'
ctx.style.height = '100%'

const data = {
labels: [
'Day 1',
'Day 2',
'Day 3',
'Day 4',
'Day 5',
'Day 6',
'Day 7',
'Day 8',
'Day 9',
'Day 10',
'Day 11',
'Day 12',
'Day 13',
'Day 14',
],
datasets: [
{
label: 'Upper Bound',
data: UPPER_BOUND,
borderColor: 'rgba(200, 200, 200, 1)',
fill: false,
tension: 0,
pointRadius: 0,
},
{
label: 'Lower Bound',
data: LOWER_BOUND,
borderColor: 'rgba(200, 200, 200, 1)',
fill: '-1',
backgroundColor: 'rgba(200, 200, 200, 0.2)',
tension: 0,
pointRadius: 0,
},
{
label: 'Delta',
data: DELTA,
borderColor: 'rgba(0, 100, 255, 1)',
borderWidth: 2,
fill: false,
tension: 0,
pointRadius: 0,
},
],
}

const config: ChartConfiguration = {
type: 'line',
data: data,
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
intersect: false,
mode: 'nearest',
axis: 'x',
},
scales: {
y: {
beginAtZero: true,
grid: {
display: false,
},
ticks: {
count: 6,
callback: (value) => `${(Number(value) * 100).toFixed(1)}%`,
},
},
},
plugins: {
legend: {
display: false,
},
tooltip: {
callbacks: {
labelPointStyle: function () {
return {
pointStyle: 'circle',
rotation: 0,
}
},
},
usePointStyle: true,
boxWidth: 16,
boxHeight: 1,
},
// @ts-expect-error Types of library are out of date
crosshair: false,
},
},
}

new Chart(ctx, config)
}, 0)
}
}, [isVariantDeltaTimeseriesModalOpen])

return (
<LemonModal
isOpen={isVariantDeltaTimeseriesModalOpen}
onClose={() => {
closeVariantDeltaTimeseriesModal()
}}
width={800}
title="Variant performance over time"
footer={
<LemonButton form="secondary-metric-modal-form" type="secondary" onClick={() => {}}>
Close
</LemonButton>
}
>
<div className="relative h-[400px]">
<canvas id="variantDeltaChart" />
</div>
</LemonModal>
)
}
9 changes: 9 additions & 0 deletions frontend/src/scenes/experiments/experimentLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,8 @@ export const experimentLogic = kea<experimentLogicType>([
closePrimarySharedMetricModal: true,
openSecondarySharedMetricModal: (sharedMetricId: SharedMetric['id'] | null) => ({ sharedMetricId }),
closeSecondarySharedMetricModal: true,
openVariantDeltaTimeseriesModal: true,
closeVariantDeltaTimeseriesModal: true,
addSharedMetricsToExperiment: (
sharedMetricIds: SharedMetric['id'][],
metadata: { type: 'primary' | 'secondary' }
Expand Down Expand Up @@ -567,6 +569,13 @@ export const experimentLogic = kea<experimentLogicType>([
closeSecondarySharedMetricModal: () => false,
},
],
isVariantDeltaTimeseriesModalOpen: [
false,
{
openVariantDeltaTimeseriesModal: () => true,
closeVariantDeltaTimeseriesModal: () => false,
},
],
isCreatingExperimentDashboard: [
false,
{
Expand Down
Loading