Skip to content

Commit 44667ee

Browse files
kathiekiwiTrigger.dev RepoOps
authored andcommitted
feat(alerts): alerts page — list, detail, actions, contextual entry points
Alerts page: list, detail, actions, contextual entry points, and legacy replacement flow. Mono-RevId: 3d801a2f11f2ab62ab8f661c25bf31898b7f801d
1 parent 7a5a1f1 commit 44667ee

173 files changed

Lines changed: 24719 additions & 1043 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎apps/webapp/app/components/BlankStatePanels.tsx‎

Lines changed: 1 addition & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { DeploymentOnboardingFrame } from "./deployments/DeploymentOnboardingFrame";
22
import {
33
BeakerIcon,
4-
BellAlertIcon,
54
BookOpenIcon,
65
ChatBubbleLeftRightIcon,
76
CheckIcon,
@@ -25,7 +24,7 @@ import { useOrganization } from "~/hooks/useOrganizations";
2524
import { useProject } from "~/hooks/useProject";
2625
import { type MinimumEnvironment } from "~/presenters/SelectBestEnvironmentPresenter.server";
2726
import { type BranchableEnvironmentToken } from "~/utils/branchableEnvironment";
28-
import { NewBranchPanel } from "~/routes/resources.branches.create";
27+
import { NewBranchPanel } from "~/components/branches/NewBranchPanel";
2928
import { GitHubSettingsPanel } from "~/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.github";
3029
import { deployNowPath } from "~/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.deploy-now";
3130
import { VercelAtomicDeploymentNotice } from "~/components/deployments/VercelAtomicDeploymentNotice";
@@ -39,7 +38,6 @@ import {
3938
v3DeploymentPath,
4039
v3DeploymentsPath,
4140
v3EnvironmentPath,
42-
v3NewProjectAlertPath,
4341
} from "~/utils/pathBuilder";
4442
import { AskAgentButton } from "./dashboard-agent/AskAgentButton";
4543
import { CodeBlock } from "./code/CodeBlock";
@@ -436,79 +434,6 @@ export function DeploymentsNoneDev({ enhanced = false }: { enhanced?: boolean }
436434
);
437435
}
438436

439-
export function AlertsNoneDev() {
440-
return (
441-
<div className="space-y-8">
442-
<InfoPanel
443-
icon={BellAlertIcon}
444-
iconClassName="text-alerts"
445-
title="Adding alerts"
446-
panelClassName="max-w-full"
447-
>
448-
<Paragraph spacing variant="small">
449-
You can get alerted when deployed runs fail.
450-
</Paragraph>
451-
<Paragraph spacing variant="small">
452-
We don't support alerts in the Development environment. Switch to a deployed environment
453-
to setup alerts.
454-
</Paragraph>
455-
<div className="flex gap-3">
456-
<LinkButton
457-
to={docsPath("troubleshooting-alerts")}
458-
variant="docs/medium"
459-
LeadingIcon={BookOpenIcon}
460-
className="inline-flex"
461-
>
462-
How to setup alerts
463-
</LinkButton>
464-
</div>
465-
</InfoPanel>
466-
<SwitcherPanel />
467-
</div>
468-
);
469-
}
470-
471-
export function AlertsNoneDeployed() {
472-
const organization = useOrganization();
473-
const project = useProject();
474-
const environment = useEnvironment();
475-
476-
return (
477-
<div className="space-y-8">
478-
<InfoPanel
479-
icon={BellAlertIcon}
480-
iconClassName="text-alerts"
481-
title="Adding alerts"
482-
panelClassName="max-w-full"
483-
>
484-
<Paragraph spacing variant="small">
485-
You can get alerted when deployed runs fail. We currently support sending Slack, Email,
486-
and webhooks.
487-
</Paragraph>
488-
489-
<div className="flex items-center justify-between gap-3">
490-
<LinkButton
491-
to={docsPath("troubleshooting-alerts")}
492-
variant="docs/medium"
493-
LeadingIcon={BookOpenIcon}
494-
className="inline-flex"
495-
>
496-
Alerts docs
497-
</LinkButton>
498-
<LinkButton
499-
to={v3NewProjectAlertPath(organization, project, environment)}
500-
variant="primary/medium"
501-
LeadingIcon={PlusIcon}
502-
shortcut={{ key: "n" }}
503-
>
504-
New alert
505-
</LinkButton>
506-
</div>
507-
</InfoPanel>
508-
</div>
509-
);
510-
}
511-
512437
export function NoWaitpointTokens() {
513438
return (
514439
<InfoPanel
@@ -662,26 +587,6 @@ export function BranchesNoBranches({
662587
);
663588
}
664589

665-
function SwitcherPanel({ title = "Switch to a deployed environment" }: { title?: string }) {
666-
const organization = useOrganization();
667-
const project = useProject();
668-
const environment = useEnvironment();
669-
670-
return (
671-
<div className="flex items-center rounded-md border border-grid-bright bg-background-bright p-3">
672-
<Paragraph variant="small" className="grow">
673-
{title}
674-
</Paragraph>
675-
<EnvironmentSelector
676-
organization={organization}
677-
project={project}
678-
environment={environment}
679-
className="w-auto grow-0 rounded-sm bg-grid-bright"
680-
/>
681-
</div>
682-
);
683-
}
684-
685590
export function BulkActionsNone() {
686591
const organization = useOrganization();
687592
const project = useProject();
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { buildActivityTimeAxis } from "~/components/primitives/charts/activityTimeAxis";
2+
import type { ChartBarReferenceArea } from "~/components/primitives/charts/ChartBar";
3+
import { Chart, type ChartConfig } from "~/components/primitives/charts/ChartCompound";
4+
import { cn } from "~/utils/cn";
5+
6+
export const EVENT_CHART_COLOR = "#6c5ce7";
7+
8+
export type ActivityPoint = { bucket: number } & Record<string, number | string | null | undefined>;
9+
10+
export type ActivitySeries = { key: string; label: string; color: string };
11+
12+
export type ActivityYAxisMode = "unit" | "count";
13+
14+
type ActivityYAxis = React.ComponentProps<typeof Chart.Bar>["yAxisProps"];
15+
16+
const Y_AXIS: Record<ActivityYAxisMode, ActivityYAxis> = {
17+
unit: { hide: true, domain: [0, 1] },
18+
count: {
19+
allowDecimals: false,
20+
domain: [0, (dataMax: number) => Math.max(1, Math.ceil(dataMax * 1.15))],
21+
},
22+
};
23+
24+
const ACTIVITY_BARS_CLASS =
25+
"min-h-0 w-full [&_.recharts-cartesian-grid_line]:[stroke-dasharray:3px_3px]";
26+
27+
export function raiseBucket(data: ActivityPoint[], raisedAt: Date): number | undefined {
28+
const at = raisedAt.getTime();
29+
return data.find(
30+
(row, index) =>
31+
row.bucket <= at && (data[index + 1] === undefined || data[index + 1]!.bucket > at)
32+
)?.bucket;
33+
}
34+
35+
export function ActivityBars({
36+
data,
37+
series,
38+
yAxis,
39+
valueFormatter,
40+
raisedAt,
41+
referenceArea,
42+
height,
43+
timeZone,
44+
}: {
45+
data: ActivityPoint[];
46+
series: ActivitySeries[];
47+
yAxis?: ActivityYAxisMode;
48+
valueFormatter?: (value: number) => string;
49+
raisedAt?: Date | null;
50+
referenceArea?: ChartBarReferenceArea | ChartBarReferenceArea[] | null;
51+
/** Fixed height; fills the parent when omitted. */
52+
height?: number;
53+
timeZone?: string;
54+
}) {
55+
const { tickFormatter, tooltipLabelFormatter } = buildActivityTimeAxis(
56+
data,
57+
undefined,
58+
undefined,
59+
timeZone
60+
);
61+
const config: ChartConfig = Object.fromEntries(
62+
series.map((s) => [s.key, { label: s.label, color: s.color }])
63+
);
64+
65+
const raiseTick = raisedAt ? raiseBucket(data, raisedAt) : undefined;
66+
// Chart.Bar has no vertical reference line, so the raise gets its own labelled tick.
67+
const ticks =
68+
raiseTick === undefined
69+
? undefined
70+
: [...new Set([data[0]!.bucket, raiseTick, data[data.length - 1]!.bucket])];
71+
72+
return (
73+
<div
74+
className={cn(ACTIVITY_BARS_CLASS, !height && "h-full")}
75+
style={height ? { height, minHeight: height } : undefined}
76+
>
77+
<Chart.Root
78+
config={config}
79+
data={data}
80+
dataKey="bucket"
81+
series={series.map((s) => s.key)}
82+
fillContainer
83+
>
84+
<Chart.Bar
85+
stackId="activity"
86+
barRadius={0}
87+
xAxisProps={{
88+
// A `ticks: undefined` key would still override Chart.Bar's auto ticks.
89+
...(ticks !== undefined ? { ticks } : {}),
90+
tickFormatter: (value: number) =>
91+
value === raiseTick ? `▲ raised ${tickFormatter(value)}` : tickFormatter(value),
92+
}}
93+
yAxisProps={yAxis ? Y_AXIS[yAxis] : undefined}
94+
referenceArea={referenceArea}
95+
tooltipLabelFormatter={tooltipLabelFormatter}
96+
tooltipValueFormatter={valueFormatter}
97+
/>
98+
</Chart.Root>
99+
</div>
100+
);
101+
}

0 commit comments

Comments
 (0)