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
12 changes: 8 additions & 4 deletions frontend/snapshots.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6745,13 +6745,17 @@ snapshots:
scenes-app-engineering-analytics-delivery-pipeline--without-deploys--light:
hash: v1.k794b7964.5eaee687a224ef9cd7d3707391948732cacb11769bdd619f315a101abcb85de1.ymQC-4XF77SeJ0AN5SrtlyYKs8Ve29MBfz4J8FGLWtg
scenes-app-engineering-analytics-health--health--dark:
hash: v1.k794b7964.f360c920cb938b520b6be36feb1c1f09e17bf75c9fc1867062c9196d3095648c.p0RcxBr1mTWBq9zZRmCKht04bJMXAB0Gw1gOtEvctL0
hash: v1.k794b7964.b44984309f95ae7f109b15a9fae848c4b4946590788e71a8e4387710dfb936d6.OoB3EM50krQphJ342Rw0f3OstTYHV2NjyjS7JCFrzuk
scenes-app-engineering-analytics-health--health--light:
hash: v1.k794b7964.c8f5e1bd6dd76487d604f6047196430fcdb1089c50b9f39596ca80757c59740e.a-xn5Ef0rSu9fYwDJVgIuLzafToROwBE8jWojJaEOmw
hash: v1.k794b7964.35c7ca733e40d8700368971cab0385c5c0e9299b7e2bdcdc959c4021d1e5dd9a.JY2t4yraYrMoI7dKNjGbOrOZoTLFYIrNqoyj9HTnxWs
scenes-app-engineering-analytics-health--health-with-limited-deployment-coverage--dark:
hash: v1.k794b7964.91d4d18da5f4bf6784bed887fd0a7e2ff10c278fd1c6ae2330911541aaeb9f1e.t7zP7abNW6GN2rtotpz5VGWkzKBbmaSqSqwLuoKbJ1I
scenes-app-engineering-analytics-health--health-with-limited-deployment-coverage--light:
hash: v1.k794b7964.cea50b9a0c73e0868c0b4b59dbc0e0c8301fb8c08fb33bab1d2bfdfaa6c58143.4KlzrYv4MgqmhfXlrKavy5FXTDxsFa1A_T275D4jDIU
scenes-app-engineering-analytics-health--health-without-attributed-pull-requests--dark:
hash: v1.k794b7964.ade08d76e0e86e94837e2336d129092631dc8be77dff7c9b0049c6011b482b84.MhzKqJFl7699ZEH8SnnqEgzPTlKZ1ztmWsRR2_L0ZEc
hash: v1.k794b7964.712465012bc894bfbf77066f2b3287bbb28c684d8992bfb6e6824e04a4ff3d0f.AIi4PIwjarrVtkm9Rn9pSyAWRL7IITCCaAywR7a3p9A
scenes-app-engineering-analytics-health--health-without-attributed-pull-requests--light:
hash: v1.k794b7964.f2d943eccccec7f01157f2c7dc4806db7ccb408be9f33b854dba0e19db54fab1.Wo_17c5n-muEgIRJEYEARPH8Li6c2H3kS2Ai6CVyaHo
hash: v1.k794b7964.916a98ec25fe104a20e7364b41baad00b75c67456d07a457c96cf31cbefcd330.vm_ef_S4gyq4TggAyQQOsNQ2BUqZyNK1nYiDkdWF8UU
scenes-app-engineering-analytics-health--health-without-deploy-data--dark:
hash: v1.k794b7964.19d11f17e9a84ed24f41001e49e34211992af3fd06e22d3d8e4956ea54451692.bHLFcWqNX7duZvoYdWlAWdK6Hbf3hNcKuEofZN8Jeg4
scenes-app-engineering-analytics-health--health-without-deploy-data--light:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,17 @@ import { ReactNode } from 'react'

import { LemonCard, LemonSkeleton, Tooltip } from '@posthog/lemon-ui'

import { cn } from 'lib/utils/css-classes'

import { percent } from '../lib/format'
import { DeltaBadge, percentChange, pointChange } from './MetricTile'
import { DeltaBadge, percentChange, pointChange, type TileBenchmark } from './MetricTile'

const BENCHMARK_EDGE_CLASS: Record<TileBenchmark['band'], string> = {
elite: 'border-l-success',
high: 'border-l-purple',
medium: 'border-l-warning',
low: 'border-l-danger',
}

function MagnitudeBar({
fraction,
Expand Down Expand Up @@ -100,6 +109,7 @@ export function WindowComparisonCard({
deltaUnit,
deltaPrecision,
tooltip,
benchmark,
marker,
markerPrevious,
markerLabel,
Expand All @@ -121,6 +131,7 @@ export function WindowComparisonCard({
deltaPrecision?: number
/** Definition or methodology, shown on title hover. */
tooltip?: ReactNode
benchmark?: TileBenchmark | null
/** A companion figure (e.g. p90) pinned as a tick on each magnitude bar, on the same scale as
* the value. Ignored in `share` mode, which has no scale to pin against. */
marker?: number | null
Expand All @@ -129,14 +140,27 @@ export function WindowComparisonCard({
loading?: boolean
emptyText: string
}): JSX.Element {
const max = Math.max(value ?? 0, previousValue ?? 0, marker ?? 0, markerPrevious ?? 0)
const max = Math.max(...[value, previousValue, marker, markerPrevious].map((number) => number ?? 0))
const delta = deltaUnit === 'pt' ? pointChange(value, previousValue) : percentChange(value, previousValue)
const tooltipContent = benchmark ? (
<div className="flex flex-col gap-1">
{tooltip}
<div>
DORA band: {benchmark.label.toLowerCase()}. {benchmark.tooltip}
</div>
</div>
) : (
tooltip
)

return (
<LemonCard hoverEffect={false} className="flex flex-col p-4">
<LemonCard
hoverEffect={false}
className={cn('flex flex-col p-4', benchmark && `border-l-4 ${BENCHMARK_EDGE_CLASS[benchmark.band]}`)}
>
<h3 className="mb-1 text-xs font-semibold text-secondary">
{tooltip ? (
<Tooltip title={tooltip}>
{tooltipContent ? (
<Tooltip title={tooltipContent}>
<span className="cursor-default">{title}</span>
</Tooltip>
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useValues } from 'kea'

import { Section } from '../components/Section'
import { WindowComparisonCard } from '../components/WindowComparisonCard'
import { changeFailureBenchmark, deploymentFrequencyBenchmark, restoreTimeBenchmark } from '../lib/doraBenchmark'
import { compactAgeLabel } from '../lib/format'
import { doraLogic } from './doraLogic'

Expand All @@ -17,6 +18,7 @@ export function DoraDeploymentHealth(): JSX.Element {
title="Deployment frequency"
tooltip="Successful deployments per day in the selected environment scope. Each regional deployment counts separately."
value={dora?.deployments_per_day}
benchmark={deploymentFrequencyBenchmark(dora?.deployments_per_day)}
previousValue={dora?.deployments_per_day_prev}
formatValue={(value) => `${value.toFixed(1)}/day`}
emptyText="No successful deployments in this window."
Expand All @@ -26,6 +28,7 @@ export function DoraDeploymentHealth(): JSX.Element {
title="Failed deployment share"
tooltip="Deployments with a failure or error status divided by deployments that reached an outcome. A change failure proxy: successful deploys that broke production are not counted because no incident data is linked."
value={dora?.failed_deployment_share}
benchmark={changeFailureBenchmark(dora?.failed_deployment_share)}
previousValue={dora?.failed_deployment_share_prev}
formatValue={(value) => `${(value * 100).toFixed(1)}%`}
deltaUnit="pt"
Expand All @@ -37,6 +40,7 @@ export function DoraDeploymentHealth(): JSX.Element {
title="Failed deploy to next success"
tooltip="Median wait from a deployment's first failure status to the next successful deployment in the same environment. A time to restore proxy: recovery without a deploy is invisible, and unrecovered failures are excluded."
value={dora?.median_failed_deploy_to_next_success_seconds}
benchmark={restoreTimeBenchmark(dora?.median_failed_deploy_to_next_success_seconds)}
previousValue={dora?.median_failed_deploy_to_next_success_seconds_prev}
formatValue={compactAgeLabel}
goodWhenDown
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { LemonButton, LemonSkeleton } from '@posthog/lemon-ui'
import { LeadTimeBoxPlot } from '../components/LeadTimeBoxPlot'
import { compactAgeLabel } from '../lib/format'
import { doraLogic } from './doraLogic'
import { DoraUnattributedNotice } from './DoraUnattributedNotice'

export function DoraLeadTimeDistributionContent(): JSX.Element {
const {
Expand Down Expand Up @@ -94,7 +93,6 @@ export function DoraLeadTimeDistributionContent(): JSX.Element {
: 'Show open to merge and merge to deploy'}
</LemonButton>
</div>
Comment thread
pauldambra marked this conversation as resolved.
<DoraUnattributedNotice />
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,7 @@ export function DoraLeadTimeDistributions(): JSX.Element {
/>
}
>
<Section
id="merge-to-deploy"
title="Lead time distributions"
note={`Box per bucket: whisker ${excludeOutliers ? 'p5 to p95' : 'min to max'}, box p25 to p75, line at the median, dot at the mean. Buckets key on deploy time.`}
busy={doraLoading && !!dora}
>
<Section id="merge-to-deploy" title="Lead time distributions" busy={doraLoading && !!dora}>
<DoraLeadTimeDistributionContent />
</Section>
</ScopePanel>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { useActions, useValues } from 'kea'

import { LemonSelect } from '@posthog/lemon-ui'
import { LemonBanner, LemonSelect } from '@posthog/lemon-ui'

import { ScopePanel } from '../components/ScopePanel'
import { DoraLeadTimeDistributions } from './DoraLeadTimeDistributions'
import { DoraLeadTimeSummary } from './DoraLeadTimeSummary'
import { doraLogic } from './doraLogic'

export function DoraLeadTimeSection(): JSX.Element {
const { dora, doraLoading, githubTeam, githubTeamOptions } = useValues(doraLogic)
const { dora, doraLoading, githubTeam, githubTeamOptions, showUnattributedWarning } = useValues(doraLogic)
const { setGithubTeam } = useActions(doraLogic)

return (
Expand All @@ -27,6 +27,15 @@ export function DoraLeadTimeSection(): JSX.Element {
/>
}
>
{showUnattributedWarning && (
<div data-attr="engineering-analytics-dora-unattributed">
<LemonBanner type="warning">
More than 10% of PRs merged in this window have no successful deployment attributed in the
selected environments. Lead-time results exclude unmatched PRs. Check the source sync or allow
more time for deployments.
</LemonBanner>
</div>
)}
<DoraLeadTimeSummary />
<DoraLeadTimeDistributions />
</ScopePanel>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useValues } from 'kea'

import { Section } from '../components/Section'
import { WindowComparisonCard } from '../components/WindowComparisonCard'
import { leadTimeBenchmark } from '../lib/doraBenchmark'
import { compactAgeLabel } from '../lib/format'
import { doraLogic } from './doraLogic'

Expand All @@ -15,6 +16,7 @@ export function DoraLeadTimeSummary(): JSX.Element {
title="Open to deploy"
tooltip={`Median from a PR's open to the first successful deployment containing it in any selected environment, over ${dora?.deployed_pr_count ?? 0} deployed PRs (bots and drafts excluded). Each PR counts once. The box plots split it into open to merge and merge to deploy.`}
value={dora?.median_open_to_deploy_seconds}
benchmark={leadTimeBenchmark(dora?.median_open_to_deploy_seconds)}
previousValue={dora?.median_open_to_deploy_seconds_prev}
formatValue={compactAgeLabel}
goodWhenDown
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,20 @@ export const Health: Story = {
parameters: { pageUrl: urls.engineeringAnalyticsHealth() },
}

export const HealthWithLimitedDeploymentCoverage: Story = {
...Health,
decorators: [
mswDecorator({
get: {
'api/projects/:team_id/engineering_analytics/dora/': {
...DORA,
unattributed_merged_pr_share: 0.25,
} satisfies DoraOverviewApi,
},
}),
],
}

export const HealthWithoutAttributedPullRequests: Story = {
render: () => <App />,
parameters: {
Expand All @@ -202,6 +216,7 @@ export const HealthWithoutAttributedPullRequests: Story = {
...DORA,
deployed_pr_count: 0,
deployed_pr_count_prev: 0,
unattributed_merged_pr_share: 1,
median_merge_to_deploy_seconds: null,
median_merge_to_deploy_seconds_prev: null,
median_open_to_deploy_seconds: null,
Expand Down
14 changes: 14 additions & 0 deletions products/engineering_analytics/frontend/scenes/doraLogic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,20 @@ describe('doraLogic', () => {
expect(logic.values.environmentOptions.map(({ key }) => key)).toEqual(['dev', 'prod-us', 'prod-eu'])
})

it.each([
[null, false],
[0, false],
[0.1, false],
[0.1001, true],
[0.25, true],
[1, true],
])('warns only above 10%% unattributed merged PRs (%s)', async (share, showWarning) => {
await expectLogic(logic).toDispatchActions(['loadDoraSuccess'])
logic.actions.loadDoraSuccess({ ...DORA, unattributed_merged_pr_share: share })

expect(logic.values.showUnattributedWarning).toBe(showWarning)
})

it('sends the visible selections as repeated parameters and restores production when cleared', async () => {
await expectLogic(logic).toDispatchActions(['loadDoraSuccess'])
await expectLogic(logic, () => logic.actions.setEnvironments(['prod-eu', 'dev'])).toDispatchActions([
Expand Down
6 changes: 6 additions & 0 deletions products/engineering_analytics/frontend/scenes/doraLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export interface doraLogicValues {
openToMergeBuckets: BoxPlotBucket[]
selectedEnvironments: string[]
showAllLeadTimeStages: boolean
showUnattributedWarning: boolean
}

// Generated by kea-typegen. Update if you're an agent, ignore if you're human.
Expand Down Expand Up @@ -121,6 +122,7 @@ export interface doraLogicActions {
// Generated by kea-typegen. Update if you're an agent, ignore if you're human.
export interface doraLogicMeta {
__keaTypeGenInternalSelectorTypes: {
showUnattributedWarning: (dora: DoraOverviewApi | null) => boolean
selectedEnvironments: (dora: DoraOverviewApi | null, doraLoading: boolean, environments: string[]) => string[]
boxPlotBuckets: (dora: DoraOverviewApi | null) => BoxPlotBucket[]
openToMergeBuckets: (dora: DoraOverviewApi | null) => BoxPlotBucket[]
Expand Down Expand Up @@ -244,6 +246,10 @@ export const doraLogic = kea<doraLogicType>([
})),

selectors({
showUnattributedWarning: [
(s) => [s.dora],
(dora: DoraOverviewApi | null): boolean => (dora?.unattributed_merged_pr_share ?? 0) > 0.1,
],
selectedEnvironments: [
(s) => [s.dora, s.doraLoading, s.environments],
(dora: DoraOverviewApi | null, doraLoading: boolean, environments: string[]): string[] => {
Expand Down
Loading