@sentry/junior-dashboard 0.249.0 → 0.250.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/app.js +1 -1
- package/dist/client.js +39 -38
- package/dist/index.js +1 -1
- package/package.json +3 -3
- package/src/client/App.tsx +18 -0
- package/src/client/components/ProfileMenu.tsx +12 -0
- package/src/client/pages/ConnectionsPage.tsx +207 -0
- package/src/client/pages/dev/ChartFixtures.tsx +265 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentry/junior-dashboard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.250.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"react-router": "^7.18.2",
|
|
35
35
|
"shiki": "4.1.0",
|
|
36
36
|
"zod": "^4.5.4",
|
|
37
|
-
"@sentry/junior
|
|
38
|
-
"@sentry/junior": "0.
|
|
37
|
+
"@sentry/junior": "0.250.0",
|
|
38
|
+
"@sentry/junior-plugin-api": "0.250.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@oxlint/plugins": "1.79.0",
|
package/src/client/App.tsx
CHANGED
|
@@ -38,6 +38,7 @@ import { CodePage } from "./pages/code/CodePage";
|
|
|
38
38
|
import { LocationDetailPage } from "./pages/locations/LocationDetailPage";
|
|
39
39
|
import { LocationsPage } from "./pages/locations/LocationsPage";
|
|
40
40
|
import { PeoplePage } from "./pages/people/PeoplePage";
|
|
41
|
+
import { ConnectionsPage } from "./pages/ConnectionsPage";
|
|
41
42
|
import { PersonalTokensPage } from "./pages/PersonalTokensPage";
|
|
42
43
|
import { PersonProfilePage } from "./pages/people/PersonProfilePage";
|
|
43
44
|
import { SettingsPage } from "./pages/SettingsPage";
|
|
@@ -562,6 +563,23 @@ export function DashboardShell() {
|
|
|
562
563
|
}
|
|
563
564
|
path="/settings/api-tokens"
|
|
564
565
|
/>
|
|
566
|
+
<Route
|
|
567
|
+
element={
|
|
568
|
+
loading ? (
|
|
569
|
+
<PageRouteLoading
|
|
570
|
+
description="Connect and disconnect your accounts."
|
|
571
|
+
label="Loading connected accounts"
|
|
572
|
+
title="Connected accounts"
|
|
573
|
+
variant="list"
|
|
574
|
+
/>
|
|
575
|
+
) : loggedIn ? (
|
|
576
|
+
<ConnectionsPage />
|
|
577
|
+
) : (
|
|
578
|
+
<Navigate replace to="/" />
|
|
579
|
+
)
|
|
580
|
+
}
|
|
581
|
+
path="/settings/connections"
|
|
582
|
+
/>
|
|
565
583
|
<Route
|
|
566
584
|
element={
|
|
567
585
|
loading || userPagesQuery.isPending ? (
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
ChevronDown,
|
|
4
4
|
KeyRound,
|
|
5
5
|
LogOut,
|
|
6
|
+
Plug,
|
|
6
7
|
Settings,
|
|
7
8
|
ShieldCheck,
|
|
8
9
|
UserRound,
|
|
@@ -194,6 +195,14 @@ export function ProfileMenu({
|
|
|
194
195
|
<KeyRound aria-hidden="true" size={16} strokeWidth={2} />
|
|
195
196
|
API tokens
|
|
196
197
|
</Link>
|
|
198
|
+
<Link
|
|
199
|
+
className={profileLinkClass}
|
|
200
|
+
onClick={close}
|
|
201
|
+
to="/settings/connections"
|
|
202
|
+
>
|
|
203
|
+
<Plug aria-hidden="true" size={16} strokeWidth={2} />
|
|
204
|
+
Connected accounts
|
|
205
|
+
</Link>
|
|
197
206
|
{identity.user.isAdmin ? (
|
|
198
207
|
<Link className={profileLinkClass} onClick={close} to="/admin">
|
|
199
208
|
<ShieldCheck aria-hidden="true" size={16} strokeWidth={2} />
|
|
@@ -266,6 +275,9 @@ export function ProfileMenu({
|
|
|
266
275
|
<Link className={sheetItemClass} to="/settings/api-tokens">
|
|
267
276
|
API tokens
|
|
268
277
|
</Link>
|
|
278
|
+
<Link className={sheetItemClass} to="/settings/connections">
|
|
279
|
+
Connected accounts
|
|
280
|
+
</Link>
|
|
269
281
|
{identity.user.isAdmin ? (
|
|
270
282
|
<Link className={sheetItemClass} to="/admin">
|
|
271
283
|
Admin
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
2
|
+
import { Plug } from "lucide-react";
|
|
3
|
+
import type { z } from "zod";
|
|
4
|
+
import {
|
|
5
|
+
connectionListSchema,
|
|
6
|
+
disconnectConnectionResponseSchema,
|
|
7
|
+
startConnectionResponseSchema,
|
|
8
|
+
type connectionSchema,
|
|
9
|
+
} from "@sentry/junior/api/schema";
|
|
10
|
+
import { del, fetchDashboardJson, post } from "../http";
|
|
11
|
+
import { dashboardContainerClass } from "../styles";
|
|
12
|
+
import { getDashboardAgentName } from "../agentName";
|
|
13
|
+
import { useDocumentTitle } from "../documentTitle";
|
|
14
|
+
import { Button } from "../components/Button";
|
|
15
|
+
import { InlineError } from "../components/InlineError";
|
|
16
|
+
import { Card } from "../components/layout/Card";
|
|
17
|
+
|
|
18
|
+
type Connection = z.infer<typeof connectionSchema>;
|
|
19
|
+
|
|
20
|
+
const connectionsQueryKey = ["dashboard", "connections"] as const;
|
|
21
|
+
|
|
22
|
+
function connectionPath(provider: string): string {
|
|
23
|
+
return `/api/connections/${encodeURIComponent(provider)}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Connect and disconnect the viewer's plugin accounts. */
|
|
27
|
+
export function ConnectionsPage() {
|
|
28
|
+
useDocumentTitle("Connected accounts");
|
|
29
|
+
const queryClient = useQueryClient();
|
|
30
|
+
const connectionsQuery = useQuery({
|
|
31
|
+
queryKey: connectionsQueryKey,
|
|
32
|
+
queryFn: ({ signal }) =>
|
|
33
|
+
fetchDashboardJson(connectionListSchema, "/api/connections", signal),
|
|
34
|
+
// OAuth finishes in another tab. Refresh when the viewer returns.
|
|
35
|
+
refetchOnWindowFocus: true,
|
|
36
|
+
retry: false,
|
|
37
|
+
});
|
|
38
|
+
const connectMutation = useMutation({
|
|
39
|
+
mutationFn: (input: { provider: string; popup: Window | null }) =>
|
|
40
|
+
post(startConnectionResponseSchema, connectionPath(input.provider), {}),
|
|
41
|
+
onSuccess: ({ authorizationUrl }, { popup }) => {
|
|
42
|
+
// A blocked popup returns null, so use this tab. A tab the viewer
|
|
43
|
+
// closed means they cancelled; keep them on the dashboard.
|
|
44
|
+
if (!popup) {
|
|
45
|
+
window.location.assign(authorizationUrl);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (popup.closed) return;
|
|
49
|
+
popup.opener = null;
|
|
50
|
+
popup.location.href = authorizationUrl;
|
|
51
|
+
},
|
|
52
|
+
onError: (_error, { popup }) => {
|
|
53
|
+
popup?.close();
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
const disconnectMutation = useMutation({
|
|
57
|
+
mutationFn: (connection: Connection) =>
|
|
58
|
+
del(
|
|
59
|
+
disconnectConnectionResponseSchema,
|
|
60
|
+
connectionPath(connection.provider),
|
|
61
|
+
),
|
|
62
|
+
onSuccess: (_result, connection) => {
|
|
63
|
+
queryClient.setQueryData<{ connections: Connection[] }>(
|
|
64
|
+
connectionsQueryKey,
|
|
65
|
+
(current) =>
|
|
66
|
+
current && {
|
|
67
|
+
connections: current.connections.map((item) =>
|
|
68
|
+
item.provider === connection.provider
|
|
69
|
+
? {
|
|
70
|
+
provider: item.provider,
|
|
71
|
+
displayName: item.displayName,
|
|
72
|
+
description: item.description,
|
|
73
|
+
connected: false,
|
|
74
|
+
}
|
|
75
|
+
: item,
|
|
76
|
+
),
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
const connections = connectionsQuery.data?.connections ?? [];
|
|
82
|
+
const busy = connectMutation.isPending || disconnectMutation.isPending;
|
|
83
|
+
const pendingProvider = connectMutation.isPending
|
|
84
|
+
? connectMutation.variables?.provider
|
|
85
|
+
: disconnectMutation.isPending
|
|
86
|
+
? disconnectMutation.variables?.provider
|
|
87
|
+
: undefined;
|
|
88
|
+
const listFailed = !connectionsQuery.data && connectionsQuery.isError;
|
|
89
|
+
const displayedError = connectMutation.isError
|
|
90
|
+
? "Could not start the connection. Try again."
|
|
91
|
+
: disconnectMutation.isError
|
|
92
|
+
? "Could not disconnect the account. Try again."
|
|
93
|
+
: listFailed
|
|
94
|
+
? "Could not load connected accounts. Try again."
|
|
95
|
+
: undefined;
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<div className={`${dashboardContainerClass} px-4 py-8 md:px-8`}>
|
|
99
|
+
<section className="mx-auto w-full max-w-3xl">
|
|
100
|
+
<h1 className="m-0 text-2xl font-bold">Connected accounts</h1>
|
|
101
|
+
<p className="mt-2 mb-0 max-w-2xl text-sm text-dashboard-text-muted">
|
|
102
|
+
Connect an account so {getDashboardAgentName()} can act as you in
|
|
103
|
+
conversations you start here.
|
|
104
|
+
</p>
|
|
105
|
+
|
|
106
|
+
<Card className="mb-0 mt-6" padding="md" variant="raised">
|
|
107
|
+
{displayedError ? (
|
|
108
|
+
<InlineError className={listFailed ? undefined : "mb-4"}>
|
|
109
|
+
{displayedError}
|
|
110
|
+
</InlineError>
|
|
111
|
+
) : null}
|
|
112
|
+
{listFailed ? null : (
|
|
113
|
+
<div className="space-y-2">
|
|
114
|
+
{connectionsQuery.isPending ? (
|
|
115
|
+
<p className="m-0 text-sm text-dashboard-text-muted">
|
|
116
|
+
Loading connected accounts…
|
|
117
|
+
</p>
|
|
118
|
+
) : connections.length === 0 ? (
|
|
119
|
+
<p className="m-0 text-sm text-dashboard-text-muted">
|
|
120
|
+
No installed plugins support account connections.
|
|
121
|
+
</p>
|
|
122
|
+
) : (
|
|
123
|
+
connections.map((connection) => (
|
|
124
|
+
<div
|
|
125
|
+
className="flex items-center gap-3 rounded-md border border-dashboard-border-strong p-3"
|
|
126
|
+
key={connection.provider}
|
|
127
|
+
>
|
|
128
|
+
<Plug
|
|
129
|
+
className="shrink-0 text-dashboard-accent"
|
|
130
|
+
size={16}
|
|
131
|
+
/>
|
|
132
|
+
<div className="min-w-0 flex-1">
|
|
133
|
+
<p className="m-0 truncate text-sm font-semibold">
|
|
134
|
+
{connection.displayName}
|
|
135
|
+
</p>
|
|
136
|
+
<p className="mt-1 mb-0 line-clamp-2 break-words text-xs text-dashboard-text-muted">
|
|
137
|
+
{connection.connected ? (
|
|
138
|
+
connection.account ? (
|
|
139
|
+
<>
|
|
140
|
+
Connected as{" "}
|
|
141
|
+
{connection.account.url ? (
|
|
142
|
+
<a
|
|
143
|
+
className="text-dashboard-text"
|
|
144
|
+
href={connection.account.url}
|
|
145
|
+
rel="noreferrer"
|
|
146
|
+
target="_blank"
|
|
147
|
+
>
|
|
148
|
+
{connection.account.label}
|
|
149
|
+
</a>
|
|
150
|
+
) : (
|
|
151
|
+
connection.account.label
|
|
152
|
+
)}
|
|
153
|
+
</>
|
|
154
|
+
) : (
|
|
155
|
+
"Connected"
|
|
156
|
+
)
|
|
157
|
+
) : (
|
|
158
|
+
connection.description
|
|
159
|
+
)}
|
|
160
|
+
</p>
|
|
161
|
+
</div>
|
|
162
|
+
{connection.connected ? (
|
|
163
|
+
<Button
|
|
164
|
+
aria-busy={pendingProvider === connection.provider}
|
|
165
|
+
aria-label={`Disconnect ${connection.displayName}`}
|
|
166
|
+
disabled={busy}
|
|
167
|
+
onClick={() => {
|
|
168
|
+
connectMutation.reset();
|
|
169
|
+
disconnectMutation.mutate(connection);
|
|
170
|
+
}}
|
|
171
|
+
tone="danger"
|
|
172
|
+
>
|
|
173
|
+
{pendingProvider === connection.provider
|
|
174
|
+
? "Disconnecting…"
|
|
175
|
+
: "Disconnect"}
|
|
176
|
+
</Button>
|
|
177
|
+
) : (
|
|
178
|
+
<Button
|
|
179
|
+
aria-busy={pendingProvider === connection.provider}
|
|
180
|
+
aria-label={`Connect ${connection.displayName}`}
|
|
181
|
+
disabled={busy}
|
|
182
|
+
onClick={() => {
|
|
183
|
+
disconnectMutation.reset();
|
|
184
|
+
// Open the tab during the click so popup blockers
|
|
185
|
+
// allow it, then point it at the provider.
|
|
186
|
+
const popup = window.open("about:blank", "_blank");
|
|
187
|
+
connectMutation.mutate({
|
|
188
|
+
provider: connection.provider,
|
|
189
|
+
popup,
|
|
190
|
+
});
|
|
191
|
+
}}
|
|
192
|
+
>
|
|
193
|
+
{pendingProvider === connection.provider
|
|
194
|
+
? "Connecting…"
|
|
195
|
+
: "Connect"}
|
|
196
|
+
</Button>
|
|
197
|
+
)}
|
|
198
|
+
</div>
|
|
199
|
+
))
|
|
200
|
+
)}
|
|
201
|
+
</div>
|
|
202
|
+
)}
|
|
203
|
+
</Card>
|
|
204
|
+
</section>
|
|
205
|
+
</div>
|
|
206
|
+
);
|
|
207
|
+
}
|
|
@@ -1,17 +1,62 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
1
2
|
import type {
|
|
3
|
+
AutomationExecutionDay,
|
|
4
|
+
AutomationExecutionStatusDay,
|
|
5
|
+
CodeActivityDay,
|
|
2
6
|
ConversationMetricDay,
|
|
7
|
+
DailyConversationActivity,
|
|
8
|
+
GuardianMetricDay,
|
|
3
9
|
LocationActivityDayReport,
|
|
4
10
|
PeopleActivityDayReport,
|
|
11
|
+
PluginOperationalReport,
|
|
5
12
|
} from "@sentry/junior/api/schema";
|
|
6
13
|
import { InputCacheChart } from "../../components/charts/InputCacheChart";
|
|
7
14
|
import { SystemMetricCharts } from "../../components/charts/SystemMetricCharts";
|
|
15
|
+
import { WorkspaceUsageChart } from "../../components/charts/WorkspaceUsageChart";
|
|
16
|
+
import { AutomationCostChart } from "../automations/AutomationCostChart";
|
|
17
|
+
import { AutomationExecutionChart } from "../automations/AutomationExecutionChart";
|
|
18
|
+
import { AutomationExecutionStatusChart } from "../automations/AutomationExecutionStatusChart";
|
|
19
|
+
import { CodeActivityChart } from "../code/CodeActivityChart";
|
|
20
|
+
import { LocationActivityChart } from "../locations/LocationActivityChart";
|
|
8
21
|
import { LocationDirectoryActivityChart } from "../locations/LocationDirectoryActivityChart";
|
|
22
|
+
import { MemoryCostChart } from "../memory/MemoryCostChart";
|
|
23
|
+
import { MemoryTimeline } from "../memory/MemoryTimeline";
|
|
24
|
+
import type { MemoryCostDay, MemoryDay } from "../memory/memoryDashboard";
|
|
9
25
|
import { ContributionGrid } from "../people/ContributionGrid";
|
|
10
26
|
import { PeopleActivityChart } from "../people/PeopleActivityChart";
|
|
11
27
|
import { ConversationActivityChart } from "../system/ConversationActivityChart";
|
|
28
|
+
import { GuardianActivity } from "../system/GuardianActivity";
|
|
29
|
+
import { PluginBarChart } from "../system/PluginBarChart";
|
|
12
30
|
import { Card } from "../../components/layout/Card";
|
|
13
31
|
import { CardHeader } from "../../components/layout/CardHeader";
|
|
14
32
|
|
|
33
|
+
type PluginWidget = NonNullable<PluginOperationalReport["widgets"]>[number];
|
|
34
|
+
|
|
35
|
+
// The gallery browser scenario saves one image for each id.
|
|
36
|
+
const CHART_FIXTURE_IDS = [
|
|
37
|
+
"conversation-activity",
|
|
38
|
+
"conversation-activity-hourly",
|
|
39
|
+
"input-cache",
|
|
40
|
+
"system-metrics",
|
|
41
|
+
"people-activity",
|
|
42
|
+
"location-directory-activity",
|
|
43
|
+
"location-activity",
|
|
44
|
+
"code-activity",
|
|
45
|
+
"code-activity-dense",
|
|
46
|
+
"code-activity-empty",
|
|
47
|
+
"workspace-usage",
|
|
48
|
+
"automation-executions",
|
|
49
|
+
"automation-execution-status",
|
|
50
|
+
"automation-cost",
|
|
51
|
+
"memory-timeline",
|
|
52
|
+
"memory-cost",
|
|
53
|
+
"guardian-activity",
|
|
54
|
+
"plugin-bar-chart",
|
|
55
|
+
"contribution-grid",
|
|
56
|
+
] as const;
|
|
57
|
+
|
|
58
|
+
type ChartFixtureId = (typeof CHART_FIXTURE_IDS)[number];
|
|
59
|
+
|
|
15
60
|
const METRIC_DAYS: ConversationMetricDay[] = fixtureDates(14).map(
|
|
16
61
|
(date, index) => ({
|
|
17
62
|
conversations: 4 + ((index * 5) % 17),
|
|
@@ -22,6 +67,16 @@ const METRIC_DAYS: ConversationMetricDay[] = fixtureDates(14).map(
|
|
|
22
67
|
}),
|
|
23
68
|
);
|
|
24
69
|
|
|
70
|
+
const HOURLY_METRIC_DAYS: ConversationMetricDay[] = fixtureHours(24).map(
|
|
71
|
+
(date, index) => ({
|
|
72
|
+
conversations: index % 5 === 0 ? 0 : 1 + ((index * 7) % 9),
|
|
73
|
+
costUsd: 0.05 + ((index * 3) % 7) * 0.04,
|
|
74
|
+
date,
|
|
75
|
+
durationMs: 30_000 + ((index * 11) % 7) * 9_000,
|
|
76
|
+
tokens: 2_000 + ((index * 13) % 11) * 400,
|
|
77
|
+
}),
|
|
78
|
+
);
|
|
79
|
+
|
|
25
80
|
const PEOPLE_DAYS: PeopleActivityDayReport[] = fixtureDates(30).map(
|
|
26
81
|
(date, index) => ({
|
|
27
82
|
activePeople: 4 + ((index * 7) % 13),
|
|
@@ -38,44 +93,233 @@ const LOCATION_DAYS: LocationActivityDayReport[] = fixtureDates(30).map(
|
|
|
38
93
|
}),
|
|
39
94
|
);
|
|
40
95
|
|
|
96
|
+
const LOCATION_DETAIL_DAYS: DailyConversationActivity[] = fixtureDates(90).map(
|
|
97
|
+
(date, index) => {
|
|
98
|
+
const conversations = index % 11 === 0 ? 0 : 2 + ((index * 7) % 15);
|
|
99
|
+
return {
|
|
100
|
+
active: Math.min(conversations, index % 4),
|
|
101
|
+
conversations,
|
|
102
|
+
date,
|
|
103
|
+
durationMs: conversations * 60_000,
|
|
104
|
+
failed: index % 13 === 0 ? 1 : 0,
|
|
105
|
+
};
|
|
106
|
+
},
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const CODE_DAYS: CodeActivityDay[] = codeDays(30);
|
|
110
|
+
|
|
111
|
+
const DENSE_CODE_DAYS: CodeActivityDay[] = codeDays(90);
|
|
112
|
+
|
|
113
|
+
const EMPTY_CODE_DAYS: CodeActivityDay[] = fixtureDates(30).map((date) => ({
|
|
114
|
+
closed: 0,
|
|
115
|
+
created: 0,
|
|
116
|
+
date,
|
|
117
|
+
merged: 0,
|
|
118
|
+
}));
|
|
119
|
+
|
|
120
|
+
const WORKSPACE_USAGE_DAYS = fixtureDates(30).map((date, index) => ({
|
|
121
|
+
count: index % 6 === 0 ? 0 : 1 + ((index * 5) % 9),
|
|
122
|
+
date,
|
|
123
|
+
}));
|
|
124
|
+
|
|
125
|
+
const AUTOMATION_DAYS: AutomationExecutionDay[] = fixtureDates(30).map(
|
|
126
|
+
(date, index) => ({
|
|
127
|
+
costUsd: index % 7 === 0 ? 0 : 0.04 + ((index * 5) % 9) * 0.11,
|
|
128
|
+
date,
|
|
129
|
+
event: (index * 3) % 5,
|
|
130
|
+
scheduled: index % 7 === 0 ? 0 : 1 + ((index * 7) % 6),
|
|
131
|
+
}),
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const AUTOMATION_STATUS_DAYS: AutomationExecutionStatusDay[] = fixtureDates(
|
|
135
|
+
30,
|
|
136
|
+
).map((date, index) => ({
|
|
137
|
+
blocked: index % 9 === 0 ? 1 : 0,
|
|
138
|
+
completed: index % 7 === 0 ? 0 : 1 + ((index * 5) % 4),
|
|
139
|
+
date,
|
|
140
|
+
failed: index % 5 === 0 ? 1 : 0,
|
|
141
|
+
}));
|
|
142
|
+
|
|
143
|
+
const MEMORY_DAYS: MemoryDay[] = fixtureDates(30).map((date, index) => ({
|
|
144
|
+
date,
|
|
145
|
+
personal: index % 8 === 0 ? 0 : (index * 3) % 7,
|
|
146
|
+
public: (index * 5) % 4,
|
|
147
|
+
}));
|
|
148
|
+
|
|
149
|
+
const MEMORY_EXTRACTION_DAYS: MemoryCostDay[] = fixtureDates(30).map(
|
|
150
|
+
(date, index) => ({
|
|
151
|
+
costUsd: index % 8 === 0 ? 0 : 0.02 + ((index * 7) % 9) * 0.015,
|
|
152
|
+
date,
|
|
153
|
+
events: index % 8 === 0 ? 0 : 1 + ((index * 3) % 6),
|
|
154
|
+
}),
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const MEMORY_RECALL_DAYS: MemoryCostDay[] = fixtureDates(30).map(
|
|
158
|
+
(date, index) => ({
|
|
159
|
+
costUsd: 0.01 + ((index * 5) % 7) * 0.008,
|
|
160
|
+
date,
|
|
161
|
+
events: 2 + ((index * 5) % 9),
|
|
162
|
+
}),
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const GUARDIAN_DAYS: GuardianMetricDay[] = fixtureDates(30).map(
|
|
166
|
+
(date, index) => {
|
|
167
|
+
const allow = 6 + ((index * 7) % 19);
|
|
168
|
+
const ask = (index * 3) % 5;
|
|
169
|
+
const deny = index % 6 === 0 ? 2 : 0;
|
|
170
|
+
return {
|
|
171
|
+
allow,
|
|
172
|
+
ask,
|
|
173
|
+
costUsd: (allow + ask + deny) * 0.0021,
|
|
174
|
+
date,
|
|
175
|
+
deny,
|
|
176
|
+
requests: allow + ask + deny,
|
|
177
|
+
};
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
const PLUGIN_WIDGET: PluginWidget = {
|
|
182
|
+
categories: fixtureDates(14).map((date, index) => ({
|
|
183
|
+
id: date,
|
|
184
|
+
label: date,
|
|
185
|
+
values: {
|
|
186
|
+
failed: index % 4 === 0 ? 2 : 0,
|
|
187
|
+
succeeded: 3 + ((index * 5) % 11),
|
|
188
|
+
},
|
|
189
|
+
})),
|
|
190
|
+
description: "Deliveries reported by the plugin.",
|
|
191
|
+
id: "deliveries",
|
|
192
|
+
series: [
|
|
193
|
+
{ key: "succeeded", label: "Succeeded", tone: "good" },
|
|
194
|
+
{ key: "failed", label: "Failed", tone: "danger" },
|
|
195
|
+
],
|
|
196
|
+
title: "Deliveries",
|
|
197
|
+
type: "bar_chart",
|
|
198
|
+
};
|
|
199
|
+
|
|
41
200
|
const CONTRIBUTION_DAYS = fixtureDates(70).map((date, index) => ({
|
|
42
201
|
conversations: index % 9 === 0 ? 0 : 1 + ((index * 7) % 18),
|
|
43
202
|
date,
|
|
44
203
|
durationMs: index % 9 === 0 ? 0 : 45_000 + ((index * 13) % 20) * 18_000,
|
|
45
204
|
}));
|
|
46
205
|
|
|
47
|
-
/** Render chart
|
|
206
|
+
/** Render every dashboard chart with fixed data for visual review. */
|
|
48
207
|
export function ChartFixtures() {
|
|
49
208
|
return (
|
|
50
209
|
<>
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
cacheCreationTokens:
|
|
59
|
-
index === 5 ? undefined : index === 9 ? 190_000 : 10_000,
|
|
60
|
-
}))}
|
|
61
|
-
/>
|
|
62
|
-
<SystemMetricCharts days={METRIC_DAYS} />
|
|
63
|
-
<PeopleActivityChart days={PEOPLE_DAYS} />
|
|
64
|
-
<LocationDirectoryActivityChart days={LOCATION_DAYS} />
|
|
65
|
-
<Card>
|
|
66
|
-
<CardHeader
|
|
67
|
-
description="Daily conversation intensity over ten weeks."
|
|
68
|
-
title="Contribution activity"
|
|
210
|
+
<ChartFixture id="conversation-activity">
|
|
211
|
+
<ConversationActivityChart days={METRIC_DAYS} />
|
|
212
|
+
</ChartFixture>
|
|
213
|
+
<ChartFixture id="conversation-activity-hourly">
|
|
214
|
+
<ConversationActivityChart
|
|
215
|
+
bucketUnit="hour"
|
|
216
|
+
days={HOURLY_METRIC_DAYS}
|
|
69
217
|
/>
|
|
70
|
-
|
|
71
|
-
|
|
218
|
+
</ChartFixture>
|
|
219
|
+
<ChartFixture id="input-cache">
|
|
220
|
+
<InputCacheChart
|
|
221
|
+
bucketUnit="day"
|
|
222
|
+
days={METRIC_DAYS.map((day, index) => ({
|
|
223
|
+
...day,
|
|
224
|
+
inputTokens: 2_000,
|
|
225
|
+
cachedInputTokens: index === 9 ? 8_000 : 180_000,
|
|
226
|
+
cacheCreationTokens:
|
|
227
|
+
index === 5 ? undefined : index === 9 ? 190_000 : 10_000,
|
|
228
|
+
}))}
|
|
229
|
+
/>
|
|
230
|
+
</ChartFixture>
|
|
231
|
+
<ChartFixture id="system-metrics">
|
|
232
|
+
<SystemMetricCharts days={METRIC_DAYS} />
|
|
233
|
+
</ChartFixture>
|
|
234
|
+
<ChartFixture id="people-activity">
|
|
235
|
+
<PeopleActivityChart days={PEOPLE_DAYS} />
|
|
236
|
+
</ChartFixture>
|
|
237
|
+
<ChartFixture id="location-directory-activity">
|
|
238
|
+
<LocationDirectoryActivityChart days={LOCATION_DAYS} />
|
|
239
|
+
</ChartFixture>
|
|
240
|
+
<ChartFixture id="location-activity">
|
|
241
|
+
<LocationActivityChart days={LOCATION_DETAIL_DAYS} />
|
|
242
|
+
</ChartFixture>
|
|
243
|
+
<ChartFixture id="code-activity">
|
|
244
|
+
<CodeActivityChart days={CODE_DAYS} range={30} />
|
|
245
|
+
</ChartFixture>
|
|
246
|
+
<ChartFixture id="code-activity-dense">
|
|
247
|
+
<CodeActivityChart days={DENSE_CODE_DAYS} range={90} />
|
|
248
|
+
</ChartFixture>
|
|
249
|
+
<ChartFixture id="code-activity-empty">
|
|
250
|
+
<CodeActivityChart days={EMPTY_CODE_DAYS} range={30} />
|
|
251
|
+
</ChartFixture>
|
|
252
|
+
<ChartFixture id="workspace-usage">
|
|
253
|
+
<WorkspaceUsageChart
|
|
254
|
+
days={WORKSPACE_USAGE_DAYS}
|
|
255
|
+
range={30}
|
|
256
|
+
workspaceName="junior"
|
|
257
|
+
/>
|
|
258
|
+
</ChartFixture>
|
|
259
|
+
<ChartFixture id="automation-executions">
|
|
260
|
+
<AutomationExecutionChart days={AUTOMATION_DAYS} range={30} />
|
|
261
|
+
</ChartFixture>
|
|
262
|
+
<ChartFixture id="automation-execution-status">
|
|
263
|
+
<AutomationExecutionStatusChart
|
|
264
|
+
days={AUTOMATION_STATUS_DAYS}
|
|
265
|
+
range={30}
|
|
266
|
+
/>
|
|
267
|
+
</ChartFixture>
|
|
268
|
+
<ChartFixture id="automation-cost">
|
|
269
|
+
<AutomationCostChart days={AUTOMATION_DAYS} range={30} />
|
|
270
|
+
</ChartFixture>
|
|
271
|
+
<ChartFixture id="memory-timeline">
|
|
272
|
+
<MemoryTimeline days={MEMORY_DAYS} range={30} />
|
|
273
|
+
</ChartFixture>
|
|
274
|
+
<ChartFixture id="memory-cost">
|
|
275
|
+
<MemoryCostChart
|
|
276
|
+
extractionDays={MEMORY_EXTRACTION_DAYS}
|
|
277
|
+
range={30}
|
|
278
|
+
recallDays={MEMORY_RECALL_DAYS}
|
|
279
|
+
/>
|
|
280
|
+
</ChartFixture>
|
|
281
|
+
<ChartFixture id="guardian-activity">
|
|
282
|
+
<GuardianActivity days={GUARDIAN_DAYS} />
|
|
283
|
+
</ChartFixture>
|
|
284
|
+
<ChartFixture id="plugin-bar-chart">
|
|
285
|
+
<PluginBarChart widget={PLUGIN_WIDGET} />
|
|
286
|
+
</ChartFixture>
|
|
287
|
+
<ChartFixture id="contribution-grid">
|
|
288
|
+
<Card>
|
|
289
|
+
<CardHeader
|
|
290
|
+
description="Daily conversation intensity over ten weeks."
|
|
291
|
+
title="Contribution activity"
|
|
292
|
+
/>
|
|
293
|
+
<ContributionGrid days={CONTRIBUTION_DAYS} />
|
|
294
|
+
</Card>
|
|
295
|
+
</ChartFixture>
|
|
72
296
|
</>
|
|
73
297
|
);
|
|
74
298
|
}
|
|
75
299
|
|
|
300
|
+
function ChartFixture(props: { children: ReactNode; id: ChartFixtureId }) {
|
|
301
|
+
return <div data-gallery-chart={props.id}>{props.children}</div>;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function codeDays(count: number): CodeActivityDay[] {
|
|
305
|
+
return fixtureDates(count).map((date, index) => ({
|
|
306
|
+
closed: index % 9 === 0 ? 1 : 0,
|
|
307
|
+
created: index % 4 === 0 ? 0 : 1 + ((index * 5) % 3),
|
|
308
|
+
date,
|
|
309
|
+
merged: index % 3 === 0 ? 1 + (index % 2) : 0,
|
|
310
|
+
}));
|
|
311
|
+
}
|
|
312
|
+
|
|
76
313
|
function fixtureDates(count: number): string[] {
|
|
77
314
|
const start = Date.UTC(2026, 4, 1);
|
|
78
315
|
return Array.from({ length: count }, (_, index) =>
|
|
79
316
|
new Date(start + index * 86_400_000).toISOString().slice(0, 10),
|
|
80
317
|
);
|
|
81
318
|
}
|
|
319
|
+
|
|
320
|
+
function fixtureHours(count: number): string[] {
|
|
321
|
+
const start = Date.UTC(2026, 4, 1);
|
|
322
|
+
return Array.from({ length: count }, (_, index) =>
|
|
323
|
+
new Date(start + index * 3_600_000).toISOString().slice(0, 13),
|
|
324
|
+
);
|
|
325
|
+
}
|