@sentry/junior-dashboard 0.264.0 → 0.265.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/LICENSE +201 -0
- package/README.md +2 -5
- package/dist/app.js +119 -35
- package/dist/client.js +145 -136
- package/dist/index.d.ts +0 -1
- package/dist/index.js +119 -35
- package/dist/tailwind.css +1 -1
- package/package.json +5 -4
- package/src/api/schema.ts +0 -1
- package/src/app.ts +5 -20
- package/src/client/App.tsx +29 -31
- package/src/client/api.ts +7 -0
- package/src/client/components/BottomSheet.tsx +12 -2
- package/src/client/components/CopyButton.tsx +83 -0
- package/src/client/components/DetailsPanel.tsx +133 -0
- package/src/client/components/Drawer.tsx +15 -24
- package/src/client/components/FilterBar.tsx +16 -31
- package/src/client/components/Metric.tsx +33 -23
- package/src/client/components/PageContentSkeleton.tsx +1 -1
- package/src/client/components/Pagination.tsx +1 -1
- package/src/client/components/ParticipantAvatarStack.tsx +40 -36
- package/src/client/components/ProfileMenu.tsx +27 -93
- package/src/client/components/README.md +86 -77
- package/src/client/components/SegmentedNav.tsx +1 -1
- package/src/client/components/ShimmerText.tsx +2 -6
- package/src/client/components/ThemeSwitcher.tsx +19 -15
- package/src/client/components/charts/ActivityChart.tsx +173 -288
- package/src/client/components/charts/CacheWrites.tsx +63 -50
- package/src/client/components/charts/InputCacheChart.tsx +137 -131
- package/src/client/components/charts/README.md +4 -4
- package/src/client/components/charts/SystemMetricCharts.tsx +68 -95
- package/src/client/components/charts/WorkspaceUsageChart.tsx +42 -62
- package/src/client/components/controls/DirectorySortSelect.tsx +3 -3
- package/src/client/components/controls/TimeRangeSelector.tsx +10 -15
- package/src/client/components/layout/DashboardHeader.tsx +44 -90
- package/src/client/components/layout/SecondaryNavigation.tsx +2 -1
- package/src/client/components/layout/VisualViewportShell.tsx +2 -2
- package/src/client/components/ui/accordion.tsx +77 -0
- package/src/client/components/ui/alert-dialog.tsx +89 -0
- package/src/client/components/ui/button.tsx +85 -0
- package/src/client/components/ui/chart.tsx +74 -0
- package/src/client/components/ui/checkbox.tsx +26 -0
- package/src/client/components/ui/collapsible.tsx +22 -0
- package/src/client/components/ui/field.tsx +49 -0
- package/src/client/components/ui/input.tsx +35 -0
- package/src/client/components/ui/native-select.tsx +57 -0
- package/src/client/components/ui/popover.tsx +67 -0
- package/src/client/components/ui/radio-group.tsx +32 -0
- package/src/client/components/ui/sheet.tsx +13 -3
- package/src/client/components/ui/skeleton.tsx +19 -0
- package/src/client/components/ui/table.tsx +80 -0
- package/src/client/components/ui/tabs.tsx +75 -0
- package/src/client/components/ui/textarea.tsx +17 -0
- package/src/client/components/ui/toast.tsx +215 -0
- package/src/client/components/ui/toggle-group.tsx +36 -0
- package/src/client/components/ui/toggle.tsx +37 -0
- package/src/client/components/ui/tooltip.tsx +168 -0
- package/src/client/components/ui/trace.tsx +96 -0
- package/src/client/conversations/ChatLayout.tsx +7 -3
- package/src/client/conversations/ConversationBrief.tsx +17 -6
- package/src/client/conversations/ConversationComposer.tsx +26 -12
- package/src/client/conversations/ConversationDetails.tsx +147 -62
- package/src/client/conversations/ConversationEventDetails.tsx +141 -0
- package/src/client/conversations/ConversationEventLog.tsx +150 -129
- package/src/client/conversations/ConversationHeader.tsx +8 -3
- package/src/client/conversations/ConversationHeaderActions.tsx +90 -82
- package/src/client/conversations/ConversationHomeList.tsx +1 -1
- package/src/client/conversations/ConversationLinkedWork.tsx +118 -0
- package/src/client/conversations/ConversationMeta.tsx +55 -88
- package/src/client/conversations/ConversationPage.tsx +220 -101
- package/src/client/conversations/ConversationSidebar.tsx +48 -29
- package/src/client/conversations/ConversationWatches.tsx +165 -0
- package/src/client/conversations/ConversationWorkspace.tsx +12 -22
- package/src/client/conversations/ConversationWorkspaceLoading.tsx +1 -1
- package/src/client/conversations/DetailsList.tsx +124 -0
- package/src/client/conversations/EventDetails.tsx +42 -8
- package/src/client/conversations/ForkConversationButton.tsx +27 -16
- package/src/client/conversations/ObjectCard.tsx +20 -8
- package/src/client/conversations/PendingMailboxStack.tsx +52 -33
- package/src/client/conversations/README.md +86 -13
- package/src/client/conversations/ShareConversationButton.tsx +25 -13
- package/src/client/conversations/SubagentTranscriptDrawer.tsx +21 -15
- package/src/client/conversations/ToolFrame.tsx +36 -52
- package/src/client/conversations/TranscriptActivityGroup.tsx +13 -4
- package/src/client/conversations/TranscriptAutomationTriggerView.tsx +5 -4
- package/src/client/conversations/TranscriptContextEventView.tsx +25 -11
- package/src/client/conversations/TranscriptHeadingRow.tsx +7 -7
- package/src/client/conversations/TranscriptLoading.tsx +1 -1
- package/src/client/conversations/TranscriptMarkdown.tsx +25 -20
- package/src/client/conversations/TranscriptMessageContextView.tsx +8 -2
- package/src/client/conversations/TranscriptMessageView.tsx +6 -1
- package/src/client/conversations/TranscriptReasoningView.tsx +54 -68
- package/src/client/conversations/TranscriptTimestamp.tsx +9 -7
- package/src/client/conversations/TranscriptToolView.tsx +1 -1
- package/src/client/conversations/TranscriptTurnContextView.tsx +99 -27
- package/src/client/conversations/TranscriptView.tsx +63 -27
- package/src/client/conversations/eventLog.ts +57 -0
- package/src/client/conversations/eventTime.ts +60 -0
- package/src/client/conversations/queries.ts +29 -0
- package/src/client/conversations/transcriptBottomPinning.ts +34 -0
- package/src/client/format.ts +5 -0
- package/src/client/pages/ConnectionsPage.tsx +2 -2
- package/src/client/pages/PersonalTokensPage.tsx +4 -4
- package/src/client/pages/SettingsPage.tsx +9 -10
- package/src/client/pages/admin/ServiceAccountsSection.tsx +2 -2
- package/src/client/pages/automations/AutomationCostChart.tsx +50 -71
- package/src/client/pages/automations/AutomationDeleteDialog.tsx +87 -0
- package/src/client/pages/automations/AutomationDetails.tsx +193 -0
- package/src/client/pages/automations/AutomationEditPage.tsx +7 -8
- package/src/client/pages/automations/AutomationEditor.tsx +41 -29
- package/src/client/pages/automations/AutomationEditorLoading.tsx +1 -1
- package/src/client/pages/automations/AutomationEventFields.tsx +202 -119
- package/src/client/pages/automations/AutomationExecutionChart.tsx +51 -64
- package/src/client/pages/automations/AutomationExecutionStatusChart.tsx +47 -70
- package/src/client/pages/automations/AutomationExecutionsPage.tsx +150 -126
- package/src/client/pages/automations/AutomationFilters.tsx +189 -74
- package/src/client/pages/automations/AutomationLifecycle.tsx +109 -78
- package/src/client/pages/automations/AutomationOutcomeFields.tsx +26 -24
- package/src/client/pages/automations/AutomationRecentRuns.tsx +143 -0
- package/src/client/pages/automations/AutomationRow.tsx +12 -138
- package/src/client/pages/automations/AutomationRunDetails.tsx +307 -0
- package/src/client/pages/automations/AutomationRunsPage.tsx +119 -94
- package/src/client/pages/automations/AutomationScheduleFields.tsx +51 -51
- package/src/client/pages/automations/AutomationStatus.tsx +201 -0
- package/src/client/pages/automations/AutomationVersionsPage.tsx +65 -28
- package/src/client/pages/automations/AutomationsPage.tsx +80 -68
- package/src/client/pages/automations/AutomationsPageLayout.tsx +46 -14
- package/src/client/pages/automations/README.md +57 -0
- package/src/client/pages/automations/TriggerFilters.tsx +22 -0
- package/src/client/pages/automations/automationFormat.ts +37 -23
- package/src/client/pages/code/CodeActivityChart.tsx +59 -101
- package/src/client/pages/code/CodePage.tsx +52 -55
- package/src/client/pages/code/RepositoryPage.tsx +6 -8
- package/src/client/pages/locations/LocationActivityChart.tsx +53 -70
- package/src/client/pages/locations/LocationDirectoryActivityChart.tsx +60 -80
- package/src/client/pages/memory/MemoryCostChart.tsx +79 -103
- package/src/client/pages/memory/MemoryDetails.tsx +218 -0
- package/src/client/pages/memory/MemoryPage.tsx +168 -112
- package/src/client/pages/memory/MemoryPageLayout.tsx +33 -9
- package/src/client/pages/memory/MemoryTimeline.tsx +46 -69
- package/src/client/pages/memory/memoryRecord.ts +1 -1
- package/src/client/pages/people/ContributionGrid.tsx +23 -16
- package/src/client/pages/people/PeopleActivityChart.tsx +68 -107
- package/src/client/pages/people/ProfileCodeChanges.tsx +5 -5
- package/src/client/pages/system/BaseSandboxCard.tsx +1 -1
- package/src/client/pages/system/ConversationActivityChart.tsx +49 -67
- package/src/client/pages/system/GuardianActivity.tsx +1 -2
- package/src/client/pages/system/PluginBarChart.tsx +106 -87
- package/src/client/pages/system/PluginReportRecordSet.tsx +25 -24
- package/src/client/pages/system/README.md +23 -0
- package/src/client/pages/system/SystemNavigation.tsx +5 -1
- package/src/client/pages/system/WorkspaceDeleteDialog.tsx +111 -0
- package/src/client/pages/system/WorkspaceDetails.tsx +200 -43
- package/src/client/pages/system/WorkspaceEditor.tsx +59 -61
- package/src/client/pages/system/WorkspaceFormPage.tsx +23 -47
- package/src/client/pages/system/WorkspaceList.tsx +20 -30
- package/src/client/pages/system/WorkspacesPage.tsx +117 -119
- package/src/client/pages/system/sandboxSize.ts +8 -0
- package/src/client/pages/system/workspaceQueries.ts +53 -0
- package/src/client/pages/user/PluginUserPage.tsx +51 -2
- package/src/client/pages/user/pluginUserPageData.ts +1 -6
- package/src/client/styles.ts +4 -0
- package/src/client.tsx +7 -1
- package/src/mock-reporting/automation-collection.ts +4 -2
- package/src/mock-reporting/fixtures.ts +23 -7
- package/src/mock-reporting/routes.ts +12 -0
- package/src/mock-reporting/watches.ts +94 -0
- package/src/shell.ts +4 -9
- package/src/tailwind.css +0 -10
- package/src/client/components/Button.tsx +0 -118
- package/src/client/components/Field.tsx +0 -53
- package/src/client/components/SegmentedTabs.tsx +0 -119
- package/src/client/components/Select.tsx +0 -52
- package/src/client/components/Skeleton.tsx +0 -17
- package/src/client/components/TextInput.tsx +0 -58
- package/src/client/components/Tooltip.tsx +0 -216
- package/src/client/components/charts/useChartLayout.ts +0 -27
- package/src/client/components/ui/sheet.LICENSE +0 -21
- package/src/client/conversations/ConversationDetailsFrame.tsx +0 -119
- package/src/client/conversations/CopyMarkdownButton.tsx +0 -65
- package/src/client/pages/automations/AutomationDetailsDrawer.tsx +0 -219
- package/src/client/pages/dev/ChartFixtures.tsx +0 -325
- package/src/client/pages/dev/ComponentsPage.tsx +0 -899
- package/src/client/pages/dev/ConversationFixture.tsx +0 -231
- package/src/client/pages/dev/ObjectFixtures.tsx +0 -138
- package/src/client/pages/dev/SegmentedFixtures.tsx +0 -59
- package/src/client/pages/dev/SelectFixtures.tsx +0 -62
- package/src/client/pages/dev/TranscriptActivityFixtures.tsx +0 -206
- package/src/client/pages/memory/MemoryDetailsDrawer.tsx +0 -167
- package/src/client/pages/system/SandboxSizeSummary.tsx +0 -47
- package/src/client/pages/system/SnapshotSummary.tsx +0 -78
|
@@ -8,10 +8,11 @@ import {
|
|
|
8
8
|
import { useAutomationData } from "../../api";
|
|
9
9
|
import { useDocumentTitle } from "../../documentTitle";
|
|
10
10
|
import { fetchDashboardJson } from "../../http";
|
|
11
|
-
import { Button } from "../../components/
|
|
11
|
+
import { Button } from "../../components/ui/button";
|
|
12
12
|
import { FormNotice } from "../../components/FormNotice";
|
|
13
13
|
import { StatusChip } from "../../components/StatusChip";
|
|
14
14
|
import { AutomationsNavigation } from "./AutomationsPageLayout";
|
|
15
|
+
import { TriggerFilters } from "./TriggerFilters";
|
|
15
16
|
import { AutomationEditor } from "./AutomationEditor";
|
|
16
17
|
import { AutomationEditorLoading } from "./AutomationEditorLoading";
|
|
17
18
|
import { AutomationFormSection } from "./AutomationFormSection";
|
|
@@ -49,7 +50,7 @@ export function AutomationEditPage(props: { enabled: boolean }) {
|
|
|
49
50
|
value?.kind === "event"
|
|
50
51
|
? value.status !== "active"
|
|
51
52
|
? value.status
|
|
52
|
-
: value.
|
|
53
|
+
: value.trigger.problems.length === 0
|
|
53
54
|
? "active"
|
|
54
55
|
: "unavailable"
|
|
55
56
|
: value?.status;
|
|
@@ -144,12 +145,10 @@ function AutomationReadOnly({ automation }: { automation: AutomationSummary }) {
|
|
|
144
145
|
) : (
|
|
145
146
|
<div className="grid gap-3 text-sm">
|
|
146
147
|
<p className="m-0 break-words">{automation.resource}</p>
|
|
147
|
-
<p className="m-0 break-words">
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
</pre>
|
|
152
|
-
) : null}
|
|
148
|
+
<p className="m-0 break-words">
|
|
149
|
+
{automation.trigger.events.join(", ")}
|
|
150
|
+
</p>
|
|
151
|
+
<TriggerFilters filters={automation.trigger.filters} />
|
|
153
152
|
</div>
|
|
154
153
|
)}
|
|
155
154
|
</AutomationFormSection>
|
|
@@ -8,10 +8,13 @@ import {
|
|
|
8
8
|
type AutomationEdit,
|
|
9
9
|
type AutomationSummary,
|
|
10
10
|
} from "@sentry/junior/api/schema";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
11
|
+
import { Button } from "../../components/ui/button";
|
|
12
|
+
import { Input } from "../../components/ui/input";
|
|
13
|
+
import { Textarea } from "../../components/ui/textarea";
|
|
14
|
+
import { Field, FieldError, FieldLabel } from "../../components/ui/field";
|
|
15
|
+
import { useToastManager } from "../../components/ui/toast";
|
|
14
16
|
import { FormNotice } from "../../components/FormNotice";
|
|
17
|
+
import { RadioGroup, RadioGroupItem } from "../../components/ui/radio-group";
|
|
15
18
|
import { DashboardApiError, fetchDashboardJson, patch } from "../../http";
|
|
16
19
|
import { AutomationFormSection } from "./AutomationFormSection";
|
|
17
20
|
import { AutomationScheduleFields } from "./AutomationScheduleFields";
|
|
@@ -33,6 +36,7 @@ export function AutomationEditor(props: {
|
|
|
33
36
|
summary: AutomationSummary;
|
|
34
37
|
returnPath: string;
|
|
35
38
|
}) {
|
|
39
|
+
const toast = useToastManager();
|
|
36
40
|
const [original, setOriginal] = useState(props.automation);
|
|
37
41
|
const [draft, setDraft] = useState(() =>
|
|
38
42
|
createAutomationDraft(props.automation),
|
|
@@ -80,7 +84,12 @@ export function AutomationEditor(props: {
|
|
|
80
84
|
await queryClient.invalidateQueries({
|
|
81
85
|
queryKey: ["dashboard", "automations"],
|
|
82
86
|
});
|
|
83
|
-
|
|
87
|
+
toast.add({
|
|
88
|
+
title: "Automation saved",
|
|
89
|
+
description: "Changes apply to future work.",
|
|
90
|
+
type: "success",
|
|
91
|
+
});
|
|
92
|
+
navigate(props.returnPath);
|
|
84
93
|
},
|
|
85
94
|
onError: (error) => {
|
|
86
95
|
setFields(error instanceof DashboardApiError ? (error.fields ?? {}) : {});
|
|
@@ -246,8 +255,9 @@ export function AutomationEditor(props: {
|
|
|
246
255
|
title="What to do"
|
|
247
256
|
detail="Give Junior a clear instruction and a name you can find later."
|
|
248
257
|
>
|
|
249
|
-
<Field
|
|
250
|
-
<
|
|
258
|
+
<Field>
|
|
259
|
+
<FieldLabel htmlFor="title">Title</FieldLabel>
|
|
260
|
+
<Input
|
|
251
261
|
size="comfortable"
|
|
252
262
|
id="title"
|
|
253
263
|
value={draft.title}
|
|
@@ -255,24 +265,24 @@ export function AutomationEditor(props: {
|
|
|
255
265
|
onChange={(e) => change({ title: e.target.value })}
|
|
256
266
|
{...inputState("title")}
|
|
257
267
|
/>
|
|
268
|
+
<FieldError id="title-error">{fieldError("title")}</FieldError>
|
|
258
269
|
</Field>
|
|
259
|
-
<Field
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
error={fieldError("instruction")}
|
|
263
|
-
>
|
|
264
|
-
<TextArea
|
|
265
|
-
prose
|
|
270
|
+
<Field>
|
|
271
|
+
<FieldLabel htmlFor="instruction">Instruction</FieldLabel>
|
|
272
|
+
<Textarea
|
|
266
273
|
id="instruction"
|
|
267
274
|
rows={8}
|
|
268
275
|
value={draft.instruction}
|
|
269
276
|
onChange={(e) => change({ instruction: e.target.value })}
|
|
270
277
|
{...inputState("instruction")}
|
|
271
|
-
className="resize-y text-base sm:text-sm"
|
|
278
|
+
className="resize-y text-base leading-relaxed sm:text-sm"
|
|
272
279
|
/>
|
|
273
280
|
<p className="m-0 text-right text-xs text-dashboard-text-muted">
|
|
274
281
|
{draft.instruction.length.toLocaleString()} / 4,000
|
|
275
282
|
</p>
|
|
283
|
+
<FieldError id="instruction-error">
|
|
284
|
+
{fieldError("instruction")}
|
|
285
|
+
</FieldError>
|
|
276
286
|
</Field>
|
|
277
287
|
</AutomationFormSection>
|
|
278
288
|
<AutomationFormSection
|
|
@@ -304,6 +314,7 @@ export function AutomationEditor(props: {
|
|
|
304
314
|
<AutomationEventFields
|
|
305
315
|
value={draft.trigger ?? original.trigger}
|
|
306
316
|
original={original.trigger}
|
|
317
|
+
disabled={save.isPending}
|
|
307
318
|
onChange={(trigger) => change({ trigger })}
|
|
308
319
|
/>
|
|
309
320
|
)}
|
|
@@ -323,6 +334,7 @@ export function AutomationEditor(props: {
|
|
|
323
334
|
value={draft.outcomes}
|
|
324
335
|
original={original.outcomes}
|
|
325
336
|
destination={props.summary.destination}
|
|
337
|
+
disabled={save.isPending}
|
|
326
338
|
onChange={(outcomes) => change({ outcomes })}
|
|
327
339
|
/>
|
|
328
340
|
) : (
|
|
@@ -341,8 +353,15 @@ export function AutomationEditor(props: {
|
|
|
341
353
|
title="Credentials"
|
|
342
354
|
detail="Choose which connected accounts Junior can use."
|
|
343
355
|
>
|
|
344
|
-
<
|
|
345
|
-
|
|
356
|
+
<RadioGroup
|
|
357
|
+
aria-label="Credential mode"
|
|
358
|
+
disabled={save.isPending}
|
|
359
|
+
className="divide-y divide-dashboard-border-subtle rounded-md border border-dashboard-border-emphasis"
|
|
360
|
+
onValueChange={(credentialMode: "creator" | "system") =>
|
|
361
|
+
change({ credentialMode })
|
|
362
|
+
}
|
|
363
|
+
value={dropsCreatorCredentials ? "system" : draft.credentialMode}
|
|
364
|
+
>
|
|
346
365
|
{(
|
|
347
366
|
[
|
|
348
367
|
[
|
|
@@ -365,24 +384,17 @@ export function AutomationEditor(props: {
|
|
|
365
384
|
).map(([value, label, detail]) => (
|
|
366
385
|
<label
|
|
367
386
|
key={value}
|
|
368
|
-
className="flex cursor-pointer items-start gap-3 px-4 py-4 has-checked:bg-dashboard-fill-soft has-disabled:cursor-not-allowed has-disabled:opacity-60"
|
|
387
|
+
className="flex cursor-pointer items-start gap-3 px-4 py-4 has-data-checked:bg-dashboard-fill-soft has-data-disabled:cursor-not-allowed has-data-disabled:opacity-60"
|
|
369
388
|
>
|
|
370
|
-
<
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
className="mt-0.5 size-4 shrink-0 accent-dashboard-focus"
|
|
374
|
-
checked={
|
|
375
|
-
dropsCreatorCredentials
|
|
376
|
-
? value === "system"
|
|
377
|
-
: draft.credentialMode === value
|
|
378
|
-
}
|
|
389
|
+
<RadioGroupItem
|
|
390
|
+
className="mt-0.5"
|
|
391
|
+
value={value}
|
|
379
392
|
disabled={
|
|
380
393
|
value === "creator" &&
|
|
381
394
|
!owned &&
|
|
382
395
|
(original.credentialMode !== "creator" ||
|
|
383
396
|
dropsCreatorCredentials)
|
|
384
397
|
}
|
|
385
|
-
onChange={() => change({ credentialMode: value })}
|
|
386
398
|
/>
|
|
387
399
|
<span>
|
|
388
400
|
<span className="block text-sm font-medium">{label}</span>
|
|
@@ -392,7 +404,7 @@ export function AutomationEditor(props: {
|
|
|
392
404
|
</span>
|
|
393
405
|
</label>
|
|
394
406
|
))}
|
|
395
|
-
</
|
|
407
|
+
</RadioGroup>
|
|
396
408
|
{dropsCreatorCredentials ? (
|
|
397
409
|
<FormNotice title="Saving switches this automation to system credentials.">
|
|
398
410
|
Only {creator} can let a changed instruction
|
|
@@ -431,7 +443,7 @@ export function AutomationEditor(props: {
|
|
|
431
443
|
</Link>
|
|
432
444
|
<Button
|
|
433
445
|
type="submit"
|
|
434
|
-
|
|
446
|
+
variant="primary"
|
|
435
447
|
className="min-h-11 sm:min-h-9"
|
|
436
448
|
disabled={!dirty || save.isPending || conflict}
|
|
437
449
|
>
|
|
@@ -1,13 +1,37 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
1
2
|
import { useQuery } from "@tanstack/react-query";
|
|
3
|
+
import {
|
|
4
|
+
describeEventTrigger,
|
|
5
|
+
eventMatchFilterText,
|
|
6
|
+
eventTriggerProblems,
|
|
7
|
+
eventTypeText,
|
|
8
|
+
eventsWithoutMatchField,
|
|
9
|
+
} from "@sentry/junior-plugin-api";
|
|
2
10
|
import {
|
|
3
11
|
automationEventCatalogSchema,
|
|
4
12
|
type AutomationEdit,
|
|
5
13
|
} from "@sentry/junior/api/schema";
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
14
|
+
import {
|
|
15
|
+
AlertDialog,
|
|
16
|
+
AlertDialogAction,
|
|
17
|
+
AlertDialogCancel,
|
|
18
|
+
AlertDialogContent,
|
|
19
|
+
AlertDialogDescription,
|
|
20
|
+
AlertDialogFooter,
|
|
21
|
+
AlertDialogTitle,
|
|
22
|
+
} from "../../components/ui/alert-dialog";
|
|
23
|
+
import { Button } from "../../components/ui/button";
|
|
24
|
+
import { Input } from "../../components/ui/input";
|
|
25
|
+
import {
|
|
26
|
+
Field,
|
|
27
|
+
FieldDescription,
|
|
28
|
+
FieldError,
|
|
29
|
+
FieldLabel,
|
|
30
|
+
} from "../../components/ui/field";
|
|
31
|
+
import { NativeSelect } from "../../components/ui/native-select";
|
|
10
32
|
import { FormNotice } from "../../components/FormNotice";
|
|
33
|
+
import { TriggerFilters } from "./TriggerFilters";
|
|
34
|
+
import { Checkbox } from "../../components/ui/checkbox";
|
|
11
35
|
import { fetchDashboardJson } from "../../http";
|
|
12
36
|
|
|
13
37
|
type Trigger = Extract<AutomationEdit, { kind: "event" }>["trigger"];
|
|
@@ -16,8 +40,11 @@ type Trigger = Extract<AutomationEdit, { kind: "event" }>["trigger"];
|
|
|
16
40
|
export function AutomationEventFields(props: {
|
|
17
41
|
value: Trigger;
|
|
18
42
|
original: Trigger;
|
|
43
|
+
/** Base UI checkboxes do not inherit a native fieldset's disabled state. */
|
|
44
|
+
disabled?: boolean;
|
|
19
45
|
onChange(value: Trigger): void;
|
|
20
46
|
}) {
|
|
47
|
+
const [replacement, setReplacement] = useState<Trigger>();
|
|
21
48
|
const catalog = useQuery({
|
|
22
49
|
queryKey: ["dashboard", "automation-event-catalog"],
|
|
23
50
|
queryFn: ({ signal }) =>
|
|
@@ -28,25 +55,25 @@ export function AutomationEventFields(props: {
|
|
|
28
55
|
),
|
|
29
56
|
retry: false,
|
|
30
57
|
});
|
|
31
|
-
const
|
|
32
|
-
(item) =>
|
|
33
|
-
|
|
34
|
-
|
|
58
|
+
const sourceResourceTypes =
|
|
59
|
+
catalog.data?.filter((item) => item.namespace === props.value.namespace) ??
|
|
60
|
+
[];
|
|
61
|
+
const resource = sourceResourceTypes.find(
|
|
62
|
+
(item) => item.type === props.value.resourceType,
|
|
35
63
|
);
|
|
64
|
+
const problems = catalog.data
|
|
65
|
+
? eventTriggerProblems(sourceResourceTypes, props.value)
|
|
66
|
+
: [];
|
|
67
|
+
// A filter that a selected event does not set is fixable here. Other
|
|
68
|
+
// problems mean the catalog changed after the trigger was saved.
|
|
36
69
|
const unsupported =
|
|
37
|
-
!resource ||
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return (Array.isArray(value) ? value : [value]).some(
|
|
45
|
-
(item) =>
|
|
46
|
-
typeof item !== field.kind ||
|
|
47
|
-
(field.enum && !field.enum.includes(String(item))),
|
|
48
|
-
);
|
|
49
|
-
});
|
|
70
|
+
!resource || problems.some((problem) => problem.reason !== "field_not_set");
|
|
71
|
+
const matchFields = Object.entries(resource?.matchFields ?? {});
|
|
72
|
+
const shownMatchFields = matchFields.filter(
|
|
73
|
+
([key, field]) =>
|
|
74
|
+
props.value.match?.[key] !== undefined ||
|
|
75
|
+
eventsWithoutMatchField(field, props.value.events).length === 0,
|
|
76
|
+
);
|
|
50
77
|
const setMatch = (
|
|
51
78
|
key: string,
|
|
52
79
|
value: NonNullable<Trigger["match"]>[string] | undefined,
|
|
@@ -70,16 +97,17 @@ export function AutomationEventFields(props: {
|
|
|
70
97
|
<FormNotice title="Saved trigger · Kept unchanged">
|
|
71
98
|
Some saved values are not in the current catalog. You can edit other
|
|
72
99
|
fields, or select a new resource type to replace the trigger.
|
|
73
|
-
<
|
|
74
|
-
{
|
|
75
|
-
|
|
100
|
+
<SavedTrigger
|
|
101
|
+
resourceTypes={sourceResourceTypes}
|
|
102
|
+
trigger={props.value}
|
|
103
|
+
/>
|
|
76
104
|
</FormNotice>
|
|
77
105
|
) : null}
|
|
78
|
-
<Field
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
<
|
|
106
|
+
<Field>
|
|
107
|
+
<FieldLabel htmlFor="trigger-resource">
|
|
108
|
+
{unsupported ? "Replace trigger" : "Resource type"}
|
|
109
|
+
</FieldLabel>
|
|
110
|
+
<NativeSelect
|
|
83
111
|
id="trigger-resource"
|
|
84
112
|
value={
|
|
85
113
|
unsupported
|
|
@@ -91,13 +119,7 @@ export function AutomationEventFields(props: {
|
|
|
91
119
|
(item) => `${item.namespace}:${item.type}` === e.target.value,
|
|
92
120
|
);
|
|
93
121
|
if (!next) return;
|
|
94
|
-
|
|
95
|
-
!window.confirm(
|
|
96
|
-
"Replace the saved trigger? Its resource, events, and conditions will be cleared.",
|
|
97
|
-
)
|
|
98
|
-
)
|
|
99
|
-
return;
|
|
100
|
-
props.onChange({
|
|
122
|
+
setReplacement({
|
|
101
123
|
namespace: next.namespace,
|
|
102
124
|
resourceType: next.type,
|
|
103
125
|
identifier: "",
|
|
@@ -117,12 +139,38 @@ export function AutomationEventFields(props: {
|
|
|
117
139
|
{item.namespace} · {item.type.replaceAll("_", " ")}
|
|
118
140
|
</option>
|
|
119
141
|
))}
|
|
120
|
-
</
|
|
142
|
+
</NativeSelect>
|
|
121
143
|
</Field>
|
|
144
|
+
<AlertDialog
|
|
145
|
+
onOpenChange={(open) => {
|
|
146
|
+
if (!open) setReplacement(undefined);
|
|
147
|
+
}}
|
|
148
|
+
open={replacement !== undefined}
|
|
149
|
+
>
|
|
150
|
+
<AlertDialogContent>
|
|
151
|
+
<AlertDialogTitle>Replace the saved trigger?</AlertDialogTitle>
|
|
152
|
+
<AlertDialogDescription>
|
|
153
|
+
Its resource, events, and filters will be cleared.
|
|
154
|
+
</AlertDialogDescription>
|
|
155
|
+
<AlertDialogFooter>
|
|
156
|
+
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
157
|
+
<AlertDialogAction
|
|
158
|
+
onClick={() => {
|
|
159
|
+
if (replacement) props.onChange(replacement);
|
|
160
|
+
setReplacement(undefined);
|
|
161
|
+
}}
|
|
162
|
+
variant="primary"
|
|
163
|
+
>
|
|
164
|
+
Replace trigger
|
|
165
|
+
</AlertDialogAction>
|
|
166
|
+
</AlertDialogFooter>
|
|
167
|
+
</AlertDialogContent>
|
|
168
|
+
</AlertDialog>
|
|
122
169
|
{!unsupported && resource ? (
|
|
123
170
|
<>
|
|
124
|
-
<Field
|
|
125
|
-
<
|
|
171
|
+
<Field>
|
|
172
|
+
<FieldLabel htmlFor="trigger-label">Resource name</FieldLabel>
|
|
173
|
+
<Input
|
|
126
174
|
size="comfortable"
|
|
127
175
|
id="trigger-label"
|
|
128
176
|
value={props.value.label}
|
|
@@ -131,12 +179,11 @@ export function AutomationEventFields(props: {
|
|
|
131
179
|
}
|
|
132
180
|
/>
|
|
133
181
|
</Field>
|
|
134
|
-
<Field
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
<TextInput
|
|
182
|
+
<Field>
|
|
183
|
+
<FieldLabel htmlFor="trigger-identifier">
|
|
184
|
+
Resource identifier
|
|
185
|
+
</FieldLabel>
|
|
186
|
+
<Input
|
|
140
187
|
size="comfortable"
|
|
141
188
|
id="trigger-identifier"
|
|
142
189
|
value={props.value.identifier}
|
|
@@ -144,6 +191,9 @@ export function AutomationEventFields(props: {
|
|
|
144
191
|
props.onChange({ ...props.value, identifier: e.target.value })
|
|
145
192
|
}
|
|
146
193
|
/>
|
|
194
|
+
<FieldDescription id="trigger-identifier-help">
|
|
195
|
+
Use the exact provider resource identifier, not its display name.
|
|
196
|
+
</FieldDescription>
|
|
147
197
|
</Field>
|
|
148
198
|
<fieldset>
|
|
149
199
|
<legend className="mb-2 text-sm font-semibold">
|
|
@@ -155,11 +205,11 @@ export function AutomationEventFields(props: {
|
|
|
155
205
|
key={event}
|
|
156
206
|
className="flex min-h-11 cursor-pointer items-start gap-3 px-3 py-3 text-sm"
|
|
157
207
|
>
|
|
158
|
-
<
|
|
159
|
-
|
|
160
|
-
className="mt-0.5 size-4 shrink-0 accent-dashboard-focus"
|
|
208
|
+
<Checkbox
|
|
209
|
+
className="mt-0.5"
|
|
161
210
|
checked={props.value.events.includes(event)}
|
|
162
|
-
|
|
211
|
+
disabled={props.disabled}
|
|
212
|
+
onCheckedChange={() =>
|
|
163
213
|
props.onChange({
|
|
164
214
|
...props.value,
|
|
165
215
|
events: props.value.events.includes(event)
|
|
@@ -169,84 +219,89 @@ export function AutomationEventFields(props: {
|
|
|
169
219
|
}
|
|
170
220
|
/>
|
|
171
221
|
<span className="min-w-0 break-words">
|
|
172
|
-
{event
|
|
173
|
-
{resource.guidance?.[event] ? (
|
|
174
|
-
<span className="mt-1 block text-xs leading-relaxed text-dashboard-text-muted">
|
|
175
|
-
{resource.guidance[event]}
|
|
176
|
-
</span>
|
|
177
|
-
) : null}
|
|
222
|
+
{eventTypeText(event)}
|
|
178
223
|
</span>
|
|
179
224
|
</label>
|
|
180
225
|
))}
|
|
181
226
|
</div>
|
|
182
227
|
</fieldset>
|
|
183
|
-
{
|
|
228
|
+
{matchFields.length ? (
|
|
184
229
|
<fieldset className="grid gap-4">
|
|
185
|
-
<legend className="mb-3 text-sm font-semibold">
|
|
230
|
+
<legend className="mb-3 text-sm font-semibold">Filters</legend>
|
|
186
231
|
<p className="m-0 text-xs leading-relaxed text-dashboard-text-muted">
|
|
187
|
-
|
|
188
|
-
value.
|
|
232
|
+
Every filter must match. Leave a filter blank to match any
|
|
233
|
+
value.
|
|
234
|
+
{shownMatchFields.length < matchFields.length
|
|
235
|
+
? " Filters that a selected event does not set are hidden."
|
|
236
|
+
: null}
|
|
189
237
|
</p>
|
|
190
|
-
{
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
238
|
+
{shownMatchFields.map(([key, field]) => {
|
|
239
|
+
const value = props.value.match?.[key];
|
|
240
|
+
const problem = problems.find(
|
|
241
|
+
(candidate) => candidate.field === key,
|
|
242
|
+
);
|
|
243
|
+
return (
|
|
244
|
+
<Field key={key}>
|
|
245
|
+
<FieldLabel htmlFor={`match-${key}`}>{key}</FieldLabel>
|
|
246
|
+
{Array.isArray(value) ? (
|
|
247
|
+
<div className="text-sm">
|
|
248
|
+
<span className="break-all">
|
|
249
|
+
{eventMatchFilterText(key, field, value)}
|
|
250
|
+
</span>{" "}
|
|
251
|
+
<Button onClick={() => setMatch(key, undefined)}>
|
|
252
|
+
Remove filter
|
|
253
|
+
</Button>
|
|
254
|
+
</div>
|
|
255
|
+
) : field.kind === "boolean" || field.enum ? (
|
|
256
|
+
<NativeSelect
|
|
257
|
+
id={`match-${key}`}
|
|
258
|
+
value={value === undefined ? "" : String(value)}
|
|
259
|
+
onChange={(e) =>
|
|
260
|
+
setMatch(
|
|
261
|
+
key,
|
|
262
|
+
e.target.value === ""
|
|
263
|
+
? undefined
|
|
264
|
+
: field.kind === "boolean"
|
|
265
|
+
? e.target.value === "true"
|
|
266
|
+
: e.target.value,
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
>
|
|
270
|
+
<option value="">Any value</option>
|
|
271
|
+
{(field.enum ?? ["true", "false"]).map((v) => (
|
|
272
|
+
<option key={v}>{v}</option>
|
|
273
|
+
))}
|
|
274
|
+
</NativeSelect>
|
|
275
|
+
) : (
|
|
276
|
+
<Input
|
|
277
|
+
size="comfortable"
|
|
278
|
+
id={`match-${key}`}
|
|
279
|
+
type={field.kind === "number" ? "number" : "text"}
|
|
280
|
+
step="any"
|
|
281
|
+
value={value === undefined ? "" : String(value)}
|
|
282
|
+
onChange={(e) =>
|
|
283
|
+
setMatch(
|
|
284
|
+
key,
|
|
285
|
+
e.target.value === ""
|
|
286
|
+
? undefined
|
|
287
|
+
: field.kind === "number"
|
|
288
|
+
? e.target.valueAsNumber
|
|
289
|
+
: e.target.value,
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
/>
|
|
293
|
+
)}
|
|
294
|
+
{field.description ? (
|
|
295
|
+
<FieldDescription id={`match-${key}-help`}>
|
|
296
|
+
{field.description}
|
|
297
|
+
</FieldDescription>
|
|
298
|
+
) : null}
|
|
299
|
+
{problem ? (
|
|
300
|
+
<FieldError>{problem.message}</FieldError>
|
|
301
|
+
) : null}
|
|
302
|
+
</Field>
|
|
303
|
+
);
|
|
304
|
+
})}
|
|
250
305
|
</fieldset>
|
|
251
306
|
) : null}
|
|
252
307
|
</>
|
|
@@ -262,3 +317,31 @@ export function AutomationEventFields(props: {
|
|
|
262
317
|
</>
|
|
263
318
|
);
|
|
264
319
|
}
|
|
320
|
+
|
|
321
|
+
/** Show the saved trigger and its problems in the same words as the details. */
|
|
322
|
+
function SavedTrigger(props: {
|
|
323
|
+
resourceTypes: Parameters<typeof describeEventTrigger>[0];
|
|
324
|
+
trigger: Trigger;
|
|
325
|
+
}) {
|
|
326
|
+
const description = describeEventTrigger(props.resourceTypes, props.trigger);
|
|
327
|
+
return (
|
|
328
|
+
<>
|
|
329
|
+
<ul className="my-2 list-disc pl-5">
|
|
330
|
+
{description.problems.map((problem) => (
|
|
331
|
+
<li key={problem}>{problem}</li>
|
|
332
|
+
))}
|
|
333
|
+
</ul>
|
|
334
|
+
<div className="text-xs">
|
|
335
|
+
<p className="m-0 break-words">
|
|
336
|
+
{props.trigger.namespace} ·{" "}
|
|
337
|
+
{props.trigger.resourceType.replaceAll("_", " ")} ·{" "}
|
|
338
|
+
{props.trigger.identifier}
|
|
339
|
+
</p>
|
|
340
|
+
<p className="m-0 mt-1 break-words text-dashboard-text-muted">
|
|
341
|
+
{description.events.join(", ")}
|
|
342
|
+
</p>
|
|
343
|
+
<TriggerFilters filters={description.filters} />
|
|
344
|
+
</div>
|
|
345
|
+
</>
|
|
346
|
+
);
|
|
347
|
+
}
|