@sentry/junior-dashboard 0.234.0 → 0.236.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/dist/app.js +100 -18
- package/dist/client.js +47 -46
- package/dist/index.d.ts +3 -1
- package/dist/index.js +100 -18
- package/dist/tailwind.css +1 -1
- package/package.json +3 -3
- package/src/app.ts +7 -1
- package/src/assets/junior-avatar.png +0 -0
- package/src/assets/junior-favicon.png +0 -0
- package/src/assets.ts +1 -0
- package/src/client/App.tsx +35 -0
- package/src/client/api.ts +63 -0
- package/src/client/components/README.md +8 -4
- package/src/client/components/SegmentedNav.tsx +167 -0
- package/src/client/components/SegmentedTabs.tsx +24 -5
- package/src/client/components/layout/SecondaryNavigation.tsx +40 -83
- package/src/client/conversations/ConversationEventLog.tsx +45 -5
- package/src/client/conversations/ConversationForkLinks.tsx +51 -0
- package/src/client/conversations/ConversationHeader.tsx +12 -1
- package/src/client/conversations/ConversationPage.tsx +72 -2
- package/src/client/conversations/ForkConversationButton.tsx +125 -0
- package/src/client/conversations/TranscriptMessageView.tsx +28 -7
- package/src/client/conversations/TranscriptView.tsx +4 -0
- package/src/client/conversations/conversationRoutes.ts +10 -0
- package/src/client/conversations/queries.ts +45 -0
- package/src/client/pages/automations/AutomationDetailsDrawer.tsx +28 -27
- package/src/client/pages/automations/AutomationEditPage.tsx +23 -24
- package/src/client/pages/automations/AutomationEditor.tsx +66 -13
- package/src/client/pages/automations/AutomationFilters.tsx +22 -14
- package/src/client/pages/automations/AutomationLifecycle.tsx +6 -4
- package/src/client/pages/automations/AutomationOutcomeFields.tsx +18 -0
- package/src/client/pages/automations/AutomationRow.tsx +172 -187
- package/src/client/pages/automations/AutomationVersionsPage.tsx +356 -0
- package/src/client/pages/automations/AutomationsPage.tsx +40 -42
- package/src/client/pages/automations/AutomationsPageLayout.tsx +6 -5
- package/src/client/pages/automations/README.md +14 -3
- package/src/client/pages/automations/automationFormat.ts +43 -0
- package/src/client/pages/code/CodeChangeRow.tsx +41 -0
- package/src/client/pages/code/CodePage.tsx +18 -93
- package/src/client/pages/code/CodeSummaryCards.tsx +53 -0
- package/src/client/pages/code/RepositoryPage.tsx +227 -0
- package/src/client/pages/code/RepositorySections.tsx +108 -0
- package/src/client/pages/code/RepositoryTabs.tsx +146 -0
- package/src/client/pages/code/codeRepositoryRoutes.ts +18 -0
- package/src/client/pages/dev/ComponentsPage.tsx +5 -22
- package/src/client/pages/dev/SegmentedFixtures.tsx +58 -0
- package/src/client/pages/memory/MemoryPageLayout.tsx +12 -13
- package/src/client/pages/system/SystemNavigation.tsx +6 -2
- package/src/client/pages/system/SystemPageLayout.tsx +4 -6
- package/src/mock-reporting/code-repository.ts +77 -0
- package/src/mock-reporting/fixtures.ts +8 -2
- package/src/mock-reporting/routes.ts +25 -8
- package/src/shell.ts +21 -5
|
@@ -6,38 +6,24 @@ import {
|
|
|
6
6
|
Trash2,
|
|
7
7
|
Zap,
|
|
8
8
|
} from "lucide-react";
|
|
9
|
-
import { useEffect, useId, useRef, useState } from "react";
|
|
9
|
+
import { type ReactNode, useEffect, useId, useRef, useState } from "react";
|
|
10
10
|
import { Link, useLocation } from "react-router";
|
|
11
11
|
import { ActorAvatar } from "../../components/ActorAvatar";
|
|
12
12
|
import { SelectableRow } from "../../components/SelectableRow";
|
|
13
13
|
import { StatusChip } from "../../components/StatusChip";
|
|
14
|
-
import { formatRelativeTime,
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
export function AutomationListHeader() {
|
|
22
|
-
return (
|
|
23
|
-
<div
|
|
24
|
-
aria-hidden="true"
|
|
25
|
-
className={cn(
|
|
26
|
-
"hidden items-center gap-4 border-b border-dashboard-border-subtle px-4 py-2.5 text-xs text-dashboard-text-muted xl:grid",
|
|
27
|
-
desktopColumns,
|
|
28
|
-
)}
|
|
29
|
-
>
|
|
30
|
-
<span>Automation</span>
|
|
31
|
-
<span>Creator</span>
|
|
32
|
-
<span>Trigger</span>
|
|
33
|
-
<span>Destination</span>
|
|
34
|
-
<span>Last run</span>
|
|
35
|
-
<span className="sr-only">Actions</span>
|
|
36
|
-
</div>
|
|
37
|
-
);
|
|
38
|
-
}
|
|
14
|
+
import { formatRelativeTime, peoplePath } from "../../format";
|
|
15
|
+
import {
|
|
16
|
+
automationEventConditions,
|
|
17
|
+
automationEventNames,
|
|
18
|
+
automationScheduleLabel,
|
|
19
|
+
formatAutomationRunTime,
|
|
20
|
+
} from "./automationFormat";
|
|
39
21
|
|
|
40
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Show one automation as a compact card: what it is, where it posts, who owns
|
|
24
|
+
* it, and when it runs. Instructions, timezones, and event conditions stay in
|
|
25
|
+
* the details drawer.
|
|
26
|
+
*/
|
|
41
27
|
export function AutomationRow(props: {
|
|
42
28
|
deleting: boolean;
|
|
43
29
|
onDelete(): void;
|
|
@@ -47,6 +33,7 @@ export function AutomationRow(props: {
|
|
|
47
33
|
}) {
|
|
48
34
|
const { automation } = props;
|
|
49
35
|
const location = useLocation();
|
|
36
|
+
const status = automationStatus(automation);
|
|
50
37
|
const creator = (
|
|
51
38
|
<>
|
|
52
39
|
<ActorAvatar
|
|
@@ -58,197 +45,195 @@ export function AutomationRow(props: {
|
|
|
58
45
|
</>
|
|
59
46
|
);
|
|
60
47
|
return (
|
|
61
|
-
<article
|
|
48
|
+
<article
|
|
49
|
+
className="min-w-0 rounded-lg border border-dashboard-border-subtle bg-dashboard-fill-faint"
|
|
50
|
+
role="listitem"
|
|
51
|
+
>
|
|
62
52
|
<SelectableRow
|
|
63
|
-
className=
|
|
64
|
-
"relative grid grid-cols-[minmax(0,1fr)_minmax(0,1fr)] items-center gap-x-3 gap-y-2 px-4 py-3 xl:gap-x-4",
|
|
65
|
-
desktopColumns,
|
|
66
|
-
)}
|
|
53
|
+
className="flex items-start gap-3 rounded-lg px-4 py-3.5"
|
|
67
54
|
onSelect={props.onSelect}
|
|
68
55
|
selected={props.selected}
|
|
69
56
|
>
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
<span
|
|
78
|
-
className="block truncate font-display text-base font-medium text-dashboard-text"
|
|
79
|
-
title={automation.title}
|
|
80
|
-
>
|
|
81
|
-
{automation.title}
|
|
82
|
-
</span>
|
|
83
|
-
<span
|
|
84
|
-
className="mt-1 block truncate text-xs text-dashboard-text-muted"
|
|
85
|
-
title={automation.instruction}
|
|
57
|
+
<div className="min-w-0 flex-1">
|
|
58
|
+
<button
|
|
59
|
+
aria-expanded={props.selected}
|
|
60
|
+
aria-label={`View automation details: ${automation.title}`}
|
|
61
|
+
className="block w-full min-w-0 cursor-pointer rounded border-0 bg-transparent p-0 text-left focus-visible:outline focus-visible:outline-2 focus-visible:outline-dashboard-focus"
|
|
62
|
+
onClick={props.onSelect}
|
|
63
|
+
type="button"
|
|
86
64
|
>
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
65
|
+
<span className="flex min-w-0 items-center gap-2">
|
|
66
|
+
<span
|
|
67
|
+
className="truncate font-display text-base font-medium text-dashboard-text"
|
|
68
|
+
title={automation.title}
|
|
69
|
+
>
|
|
70
|
+
{automation.title}
|
|
71
|
+
</span>
|
|
72
|
+
{status ? (
|
|
73
|
+
<StatusChip
|
|
74
|
+
className="shrink-0"
|
|
75
|
+
size="compact"
|
|
76
|
+
tone={
|
|
77
|
+
status === "completed" || status === "paused"
|
|
78
|
+
? "neutral"
|
|
79
|
+
: "warning"
|
|
80
|
+
}
|
|
81
|
+
>
|
|
82
|
+
{status}
|
|
83
|
+
</StatusChip>
|
|
84
|
+
) : null}
|
|
105
85
|
</span>
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
</span>
|
|
128
|
-
<span
|
|
129
|
-
className="text-xs text-dashboard-text-muted xl:text-sm xl:text-dashboard-text"
|
|
130
|
-
title={
|
|
131
|
-
automation.lastRunAt
|
|
132
|
-
? formatTime(automation.lastRunAt, {
|
|
133
|
-
dateStyle: "medium",
|
|
134
|
-
timeStyle: "short",
|
|
135
|
-
})
|
|
136
|
-
: undefined
|
|
137
|
-
}
|
|
138
|
-
>
|
|
139
|
-
{automation.lastRunAt
|
|
140
|
-
? formatRelativeTime(automation.lastRunAt)
|
|
141
|
-
: "Never"}
|
|
142
|
-
</span>
|
|
143
|
-
{automation.lastRunStatus === "failed" ||
|
|
144
|
-
automation.lastRunStatus === "blocked" ? (
|
|
145
|
-
<Link
|
|
146
|
-
className="block text-xs text-amber-300 underline"
|
|
147
|
-
to={`/automations/${automation.kind}/${encodeURIComponent(automation.id)}/executions`}
|
|
148
|
-
>
|
|
149
|
-
Last run {automation.lastRunStatus}
|
|
150
|
-
</Link>
|
|
151
|
-
) : null}
|
|
86
|
+
<AutomationSummaryLine automation={automation} />
|
|
87
|
+
</button>
|
|
88
|
+
<div className="mt-2.5 flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 text-xs text-dashboard-text-muted">
|
|
89
|
+
{automation.createdByEmail ? (
|
|
90
|
+
<Link
|
|
91
|
+
className="inline-flex min-w-0 max-w-full items-center gap-2 rounded text-dashboard-text no-underline hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-dashboard-focus"
|
|
92
|
+
title={automation.createdBy}
|
|
93
|
+
to={peoplePath(automation.createdByEmail)}
|
|
94
|
+
>
|
|
95
|
+
{creator}
|
|
96
|
+
</Link>
|
|
97
|
+
) : (
|
|
98
|
+
<span
|
|
99
|
+
className="inline-flex min-w-0 max-w-full items-center gap-2 text-dashboard-text"
|
|
100
|
+
title={automation.createdBy}
|
|
101
|
+
>
|
|
102
|
+
{creator}
|
|
103
|
+
</span>
|
|
104
|
+
)}
|
|
105
|
+
<AutomationTiming automation={automation} />
|
|
106
|
+
</div>
|
|
152
107
|
</div>
|
|
153
|
-
|
|
154
|
-
|
|
108
|
+
{automation.ownedByViewer ? (
|
|
109
|
+
<div className="-mr-2 -mt-1.5 shrink-0">
|
|
155
110
|
<AutomationActions
|
|
156
111
|
editPath={`/automations/${automation.kind}/${encodeURIComponent(automation.id)}/edit${location.search}`}
|
|
157
112
|
deleting={props.deleting}
|
|
158
113
|
onDelete={props.onDelete}
|
|
159
114
|
title={automation.title}
|
|
160
115
|
/>
|
|
161
|
-
|
|
162
|
-
|
|
116
|
+
</div>
|
|
117
|
+
) : null}
|
|
163
118
|
</SelectableRow>
|
|
164
119
|
</article>
|
|
165
120
|
);
|
|
166
121
|
}
|
|
167
122
|
|
|
168
|
-
|
|
123
|
+
/** Return the one state worth flagging in the list, if the automation is not simply active. */
|
|
124
|
+
function automationStatus(automation: AutomationSummary): string | undefined {
|
|
125
|
+
if (automation.kind === "scheduled") {
|
|
126
|
+
return automation.status === "active" ? undefined : automation.status;
|
|
127
|
+
}
|
|
128
|
+
if (automation.status === "paused") return "paused";
|
|
129
|
+
return automation.triggerAvailable ? undefined : "Trigger unavailable";
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Combine the trigger and Destination into one muted line under the title. */
|
|
133
|
+
function AutomationSummaryLine({
|
|
134
|
+
automation,
|
|
135
|
+
}: {
|
|
136
|
+
automation: AutomationSummary;
|
|
137
|
+
}) {
|
|
169
138
|
const scheduled = automation.kind === "scheduled";
|
|
170
139
|
const Icon = scheduled ? CalendarClock : Zap;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
let status: string | undefined;
|
|
174
|
-
let conditions: string | undefined;
|
|
175
|
-
|
|
176
|
-
if (automation.kind === "scheduled") {
|
|
177
|
-
summary = automation.schedule.includes(automation.timezone)
|
|
140
|
+
const trigger =
|
|
141
|
+
automation.kind === "scheduled"
|
|
178
142
|
? automation.schedule
|
|
179
|
-
: `${automation.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
month: "short",
|
|
189
|
-
day: "numeric",
|
|
190
|
-
hour: "numeric",
|
|
191
|
-
minute: "2-digit",
|
|
192
|
-
timeZone: automation.timezone,
|
|
193
|
-
timeZoneName: "short",
|
|
194
|
-
});
|
|
195
|
-
detail = `Next ${nextRun}`;
|
|
196
|
-
}
|
|
197
|
-
} else {
|
|
198
|
-
summary = automation.resource;
|
|
199
|
-
detail = automation.events
|
|
200
|
-
.map((event) => event.replaceAll(/[._]/g, " "))
|
|
201
|
-
.join(", ");
|
|
202
|
-
status =
|
|
203
|
-
automation.status === "paused"
|
|
204
|
-
? "paused"
|
|
205
|
-
: automation.triggerAvailable
|
|
206
|
-
? undefined
|
|
207
|
-
: "Trigger unavailable";
|
|
208
|
-
conditions = Object.entries(automation.match ?? {})
|
|
209
|
-
.map(
|
|
210
|
-
([field, value]) =>
|
|
211
|
-
`${field}: ${Array.isArray(value) ? value.join(" or ") : String(value)}`,
|
|
212
|
-
)
|
|
213
|
-
.join(" · ");
|
|
214
|
-
}
|
|
143
|
+
: `${automation.resource} · ${automationEventNames(automation)}`;
|
|
144
|
+
const conditions =
|
|
145
|
+
automation.kind === "event" ? automationEventConditions(automation) : "";
|
|
146
|
+
const triggerDetail =
|
|
147
|
+
automation.kind === "scheduled"
|
|
148
|
+
? automationScheduleLabel(automation)
|
|
149
|
+
: conditions
|
|
150
|
+
? `${trigger} · ${conditions}`
|
|
151
|
+
: trigger;
|
|
215
152
|
return (
|
|
216
|
-
<
|
|
153
|
+
<span className="mt-1 flex min-w-0 items-center gap-1.5 text-xs text-dashboard-text-muted">
|
|
217
154
|
<Icon
|
|
218
155
|
aria-label={
|
|
219
156
|
scheduled
|
|
220
157
|
? "Scheduled automation"
|
|
221
158
|
: `${automation.source} event automation`
|
|
222
159
|
}
|
|
223
|
-
className="
|
|
160
|
+
className="shrink-0"
|
|
224
161
|
role="img"
|
|
225
|
-
size={
|
|
162
|
+
size={13}
|
|
226
163
|
/>
|
|
227
|
-
<
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
164
|
+
<span className="min-w-0 truncate" title={triggerDetail}>
|
|
165
|
+
{trigger}
|
|
166
|
+
</span>
|
|
167
|
+
<span aria-hidden="true" className="shrink-0 opacity-45">
|
|
168
|
+
·
|
|
169
|
+
</span>
|
|
170
|
+
{automation.destination.visibility === "private" ? (
|
|
171
|
+
<LockKeyhole
|
|
172
|
+
aria-label="Private destination"
|
|
173
|
+
className="shrink-0"
|
|
174
|
+
role="img"
|
|
175
|
+
size={12}
|
|
176
|
+
/>
|
|
177
|
+
) : null}
|
|
178
|
+
<span
|
|
179
|
+
className="min-w-0 max-w-[45%] truncate"
|
|
180
|
+
title={automation.destination.label}
|
|
181
|
+
>
|
|
182
|
+
{automation.destination.label}
|
|
183
|
+
</span>
|
|
184
|
+
</span>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Show next and last run as short relative times after the creator. */
|
|
189
|
+
function AutomationTiming({ automation }: { automation: AutomationSummary }) {
|
|
190
|
+
const nextRunAt =
|
|
191
|
+
automation.kind === "scheduled" && automation.status === "active"
|
|
192
|
+
? automation.nextRunAt
|
|
193
|
+
: undefined;
|
|
194
|
+
const lastRunTitle = automation.lastRunAt
|
|
195
|
+
? formatAutomationRunTime(automation.lastRunAt)
|
|
196
|
+
: undefined;
|
|
197
|
+
const lastRunFailed =
|
|
198
|
+
automation.lastRunStatus === "failed" ||
|
|
199
|
+
automation.lastRunStatus === "blocked";
|
|
200
|
+
return (
|
|
201
|
+
<>
|
|
202
|
+
{nextRunAt ? (
|
|
203
|
+
<MetaItem title={formatAutomationRunTime(nextRunAt)}>
|
|
204
|
+
Next run {formatRelativeTime(nextRunAt)}
|
|
205
|
+
</MetaItem>
|
|
206
|
+
) : null}
|
|
207
|
+
<MetaItem title={lastRunTitle}>
|
|
208
|
+
{lastRunFailed ? (
|
|
209
|
+
<Link
|
|
210
|
+
className="text-amber-300 underline"
|
|
211
|
+
to={`/automations/${automation.kind}/${encodeURIComponent(automation.id)}/executions`}
|
|
246
212
|
>
|
|
247
|
-
{
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
213
|
+
Last run {automation.lastRunStatus}
|
|
214
|
+
{automation.lastRunAt
|
|
215
|
+
? ` ${formatRelativeTime(automation.lastRunAt)}`
|
|
216
|
+
: null}
|
|
217
|
+
</Link>
|
|
218
|
+
) : automation.lastRunAt ? (
|
|
219
|
+
`Last run ${formatRelativeTime(automation.lastRunAt)}`
|
|
220
|
+
) : (
|
|
221
|
+
"Never run"
|
|
222
|
+
)}
|
|
223
|
+
</MetaItem>
|
|
224
|
+
</>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Keep each separator with the item after it so wrapped lines never end in a dot. */
|
|
229
|
+
function MetaItem(props: { children: ReactNode; title?: string }) {
|
|
230
|
+
return (
|
|
231
|
+
<span className="whitespace-nowrap" title={props.title}>
|
|
232
|
+
<span aria-hidden="true" className="mr-2 opacity-45">
|
|
233
|
+
·
|
|
234
|
+
</span>
|
|
235
|
+
{props.children}
|
|
236
|
+
</span>
|
|
252
237
|
);
|
|
253
238
|
}
|
|
254
239
|
|