@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
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import type { AutomationSummary } from "@sentry/junior/api/schema";
|
|
2
|
+
import { Link } from "react-router";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
Accordion,
|
|
6
|
+
AccordionContent,
|
|
7
|
+
AccordionItem,
|
|
8
|
+
AccordionTrigger,
|
|
9
|
+
} from "../../components/ui/accordion";
|
|
10
|
+
import { FormNotice } from "../../components/FormNotice";
|
|
11
|
+
import { StatusChip } from "../../components/StatusChip";
|
|
12
|
+
import { conversationPath } from "../../conversations/conversationRoutes";
|
|
13
|
+
import { formatRelativeTime } from "../../format";
|
|
14
|
+
import { dashboardInlineLinkClass } from "../../styles";
|
|
15
|
+
import { formatAutomationRunTime } from "./automationFormat";
|
|
16
|
+
|
|
17
|
+
/** Separate the Automation's lifecycle, next trigger, and last execution result. */
|
|
18
|
+
export function AutomationStatus({
|
|
19
|
+
automation,
|
|
20
|
+
}: {
|
|
21
|
+
automation: AutomationSummary;
|
|
22
|
+
}) {
|
|
23
|
+
const paused = automation.status === "paused";
|
|
24
|
+
const blocked = automation.status === "blocked";
|
|
25
|
+
const completed = automation.status === "completed";
|
|
26
|
+
const triggerProblems =
|
|
27
|
+
automation.kind === "event" ? automation.trigger.problems : [];
|
|
28
|
+
const unavailable = triggerProblems.length > 0;
|
|
29
|
+
const failed =
|
|
30
|
+
automation.lastRunStatus === "failed" ||
|
|
31
|
+
automation.lastRunStatus === "blocked";
|
|
32
|
+
const needsFix = blocked || (paused && Boolean(automation.statusReason));
|
|
33
|
+
const showAttention = needsFix || unavailable || failed;
|
|
34
|
+
const lastResult = automation.lastRunStatus
|
|
35
|
+
? `Last run ${automation.lastRunStatus}`
|
|
36
|
+
: "Last run";
|
|
37
|
+
return (
|
|
38
|
+
<div className="grid gap-3">
|
|
39
|
+
<div className="grid gap-1.5">
|
|
40
|
+
<div aria-label="Automation state" aria-live="polite" role="status">
|
|
41
|
+
<StatusChip
|
|
42
|
+
tone={
|
|
43
|
+
paused || completed
|
|
44
|
+
? "neutral"
|
|
45
|
+
: blocked || unavailable
|
|
46
|
+
? "warning"
|
|
47
|
+
: "success"
|
|
48
|
+
}
|
|
49
|
+
>
|
|
50
|
+
{automation.status === "active" && unavailable
|
|
51
|
+
? "Trigger unavailable"
|
|
52
|
+
: automation.status}
|
|
53
|
+
</StatusChip>
|
|
54
|
+
</div>
|
|
55
|
+
<AutomationNextTrigger automation={automation} />
|
|
56
|
+
<p className="m-0 text-xs text-dashboard-text-muted">
|
|
57
|
+
{automation.lastRunAt ? (
|
|
58
|
+
<>
|
|
59
|
+
{lastResult} ·{" "}
|
|
60
|
+
<time
|
|
61
|
+
dateTime={automation.lastRunAt}
|
|
62
|
+
title={formatAutomationRunTime(automation.lastRunAt)}
|
|
63
|
+
>
|
|
64
|
+
{formatRelativeTime(automation.lastRunAt)}
|
|
65
|
+
</time>
|
|
66
|
+
</>
|
|
67
|
+
) : automation.lastRunStatus ? (
|
|
68
|
+
`${lastResult} · time not recorded`
|
|
69
|
+
) : automation.totalRuns === 0 ? (
|
|
70
|
+
"No runs yet"
|
|
71
|
+
) : (
|
|
72
|
+
"Last run time not recorded"
|
|
73
|
+
)}
|
|
74
|
+
</p>
|
|
75
|
+
</div>
|
|
76
|
+
{showAttention ? (
|
|
77
|
+
<FormNotice
|
|
78
|
+
title={
|
|
79
|
+
needsFix
|
|
80
|
+
? "Fix this before resuming"
|
|
81
|
+
: unavailable
|
|
82
|
+
? "Trigger unavailable"
|
|
83
|
+
: "Check the last run"
|
|
84
|
+
}
|
|
85
|
+
>
|
|
86
|
+
{needsFix ? (
|
|
87
|
+
<p className="m-0 break-words">
|
|
88
|
+
{automation.statusReason ??
|
|
89
|
+
"Check the last run to see what needs fixing."}{" "}
|
|
90
|
+
{!automation.ownedByViewer
|
|
91
|
+
? `Ask ${automation.createdBy} to resume after this is fixed.`
|
|
92
|
+
: null}
|
|
93
|
+
</p>
|
|
94
|
+
) : null}
|
|
95
|
+
{unavailable ? (
|
|
96
|
+
<>
|
|
97
|
+
<ul className="my-1 list-disc pl-5">
|
|
98
|
+
{triggerProblems.map((problem) => (
|
|
99
|
+
<li className="break-words" key={problem}>
|
|
100
|
+
{problem}
|
|
101
|
+
</li>
|
|
102
|
+
))}
|
|
103
|
+
</ul>
|
|
104
|
+
<p className="m-0">
|
|
105
|
+
Edit the trigger, or enable its plugin again.
|
|
106
|
+
</p>
|
|
107
|
+
</>
|
|
108
|
+
) : null}
|
|
109
|
+
{failed && !needsFix && !unavailable ? (
|
|
110
|
+
<p className="m-0">
|
|
111
|
+
{automation.status === "active"
|
|
112
|
+
? "Future runs are still on."
|
|
113
|
+
: paused
|
|
114
|
+
? "Review the last result before resuming."
|
|
115
|
+
: "Review the last result for details."}
|
|
116
|
+
</p>
|
|
117
|
+
) : null}
|
|
118
|
+
{automation.lastConversationId ? (
|
|
119
|
+
<Link
|
|
120
|
+
className={`${dashboardInlineLinkClass} mt-2 inline-block`}
|
|
121
|
+
to={conversationPath(automation.lastConversationId)}
|
|
122
|
+
>
|
|
123
|
+
Open last run
|
|
124
|
+
</Link>
|
|
125
|
+
) : automation.totalRuns > 0 ? (
|
|
126
|
+
<Link
|
|
127
|
+
className={`${dashboardInlineLinkClass} mt-2 inline-block`}
|
|
128
|
+
to={`/automations/${encodeURIComponent(automation.id)}/executions`}
|
|
129
|
+
>
|
|
130
|
+
View run history
|
|
131
|
+
</Link>
|
|
132
|
+
) : null}
|
|
133
|
+
</FormNotice>
|
|
134
|
+
) : null}
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function AutomationNextTrigger({
|
|
140
|
+
automation,
|
|
141
|
+
}: {
|
|
142
|
+
automation: AutomationSummary;
|
|
143
|
+
}) {
|
|
144
|
+
let description: string;
|
|
145
|
+
if (automation.status === "completed") description = "No future runs";
|
|
146
|
+
else if (automation.status === "paused")
|
|
147
|
+
description = "No new runs while paused";
|
|
148
|
+
else if (automation.status === "blocked")
|
|
149
|
+
description = "New runs are blocked until this is fixed";
|
|
150
|
+
else if (automation.kind === "event")
|
|
151
|
+
description =
|
|
152
|
+
automation.trigger.problems.length === 0
|
|
153
|
+
? "Waiting for a matching event"
|
|
154
|
+
: "Waiting for a trigger fix";
|
|
155
|
+
else if (automation.nextRunAt) {
|
|
156
|
+
const browserZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
157
|
+
const scheduleTime = formatAutomationRunTime(
|
|
158
|
+
automation.nextRunAt,
|
|
159
|
+
automation.timezone,
|
|
160
|
+
);
|
|
161
|
+
const localTime = formatAutomationRunTime(
|
|
162
|
+
automation.nextRunAt,
|
|
163
|
+
browserZone,
|
|
164
|
+
);
|
|
165
|
+
const timingLabel =
|
|
166
|
+
Date.parse(automation.nextRunAt) <= Date.now()
|
|
167
|
+
? "Run was due"
|
|
168
|
+
: "Next run";
|
|
169
|
+
return (
|
|
170
|
+
<Accordion>
|
|
171
|
+
<AccordionItem value="next-run">
|
|
172
|
+
<AccordionTrigger className="py-1 text-sm font-normal">
|
|
173
|
+
<span>
|
|
174
|
+
{timingLabel}{" "}
|
|
175
|
+
<time dateTime={automation.nextRunAt}>
|
|
176
|
+
{formatRelativeTime(automation.nextRunAt)}
|
|
177
|
+
</time>
|
|
178
|
+
</span>
|
|
179
|
+
</AccordionTrigger>
|
|
180
|
+
<AccordionContent>
|
|
181
|
+
<dl className="m-0 mt-1 grid gap-1 border-l border-dashboard-border pl-3 text-xs leading-relaxed">
|
|
182
|
+
<dt className="text-dashboard-text-muted">Schedule time</dt>
|
|
183
|
+
<dd className="m-0">
|
|
184
|
+
<time dateTime={automation.nextRunAt}>{scheduleTime}</time>
|
|
185
|
+
</dd>
|
|
186
|
+
{localTime !== scheduleTime ? (
|
|
187
|
+
<>
|
|
188
|
+
<dt className="mt-1 text-dashboard-text-muted">Your time</dt>
|
|
189
|
+
<dd className="m-0">
|
|
190
|
+
<time dateTime={automation.nextRunAt}>{localTime}</time>
|
|
191
|
+
</dd>
|
|
192
|
+
</>
|
|
193
|
+
) : null}
|
|
194
|
+
</dl>
|
|
195
|
+
</AccordionContent>
|
|
196
|
+
</AccordionItem>
|
|
197
|
+
</Accordion>
|
|
198
|
+
);
|
|
199
|
+
} else description = "Next run not scheduled";
|
|
200
|
+
return <p className="m-0 text-sm text-dashboard-text">{description}</p>;
|
|
201
|
+
}
|
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
/** Version history for one Automation. Making a version active saves it again. */
|
|
2
2
|
import { useState } from "react";
|
|
3
|
-
import { ArrowLeft } from "lucide-react";
|
|
3
|
+
import { ArrowLeft, ChevronRight } from "lucide-react";
|
|
4
4
|
import { Link, Navigate, useParams, useSearchParams } from "react-router";
|
|
5
5
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
6
6
|
import {
|
|
7
7
|
automationEditSchema,
|
|
8
8
|
type AutomationSummary,
|
|
9
|
-
type AutomationVersion,
|
|
10
9
|
type AutomationVersionList,
|
|
10
|
+
type ListedAutomationVersion,
|
|
11
11
|
} from "@sentry/junior/api/schema";
|
|
12
12
|
import { useAutomationData, useAutomationVersionsData } from "../../api";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
AlertDialog,
|
|
15
|
+
AlertDialogAction,
|
|
16
|
+
AlertDialogCancel,
|
|
17
|
+
AlertDialogContent,
|
|
18
|
+
AlertDialogDescription,
|
|
19
|
+
AlertDialogFooter,
|
|
20
|
+
AlertDialogTitle,
|
|
21
|
+
} from "../../components/ui/alert-dialog";
|
|
22
|
+
import { Button } from "../../components/ui/button";
|
|
23
|
+
import {
|
|
24
|
+
Collapsible,
|
|
25
|
+
CollapsibleContent,
|
|
26
|
+
CollapsibleTrigger,
|
|
27
|
+
} from "../../components/ui/collapsible";
|
|
28
|
+
import { dashboardDisclosureChevronClass } from "../../styles";
|
|
14
29
|
import { Detail, DetailList } from "../../components/DetailList";
|
|
15
30
|
import { FormNotice } from "../../components/FormNotice";
|
|
16
31
|
import { InlineError } from "../../components/InlineError";
|
|
@@ -23,6 +38,7 @@ import { DashboardApiError, fetchDashboardJson, post } from "../../http";
|
|
|
23
38
|
import { pathWithSearch } from "../../searchParams";
|
|
24
39
|
import { formatAutomationRunTime } from "./automationFormat";
|
|
25
40
|
import { automationOutcomeLabel } from "./automationOutcomes";
|
|
41
|
+
import { TriggerFilters } from "./TriggerFilters";
|
|
26
42
|
|
|
27
43
|
const DESCRIPTION = "Saved settings for this automation, newest first.";
|
|
28
44
|
|
|
@@ -90,6 +106,9 @@ function AutomationVersionsView(props: {
|
|
|
90
106
|
const { automation, data } = props;
|
|
91
107
|
const queryClient = useQueryClient();
|
|
92
108
|
const [activated, setActivated] = useState<number>();
|
|
109
|
+
// Keep the version after close so the closing dialog keeps its text.
|
|
110
|
+
const [confirming, setConfirming] = useState<number>();
|
|
111
|
+
const [confirmOpen, setConfirmOpen] = useState(false);
|
|
93
112
|
const canActivate = !(
|
|
94
113
|
automation.kind === "scheduled" && automation.status === "completed"
|
|
95
114
|
);
|
|
@@ -155,15 +174,8 @@ function AutomationVersionsView(props: {
|
|
|
155
174
|
activate.variables === version.version ? activate.error : null
|
|
156
175
|
}
|
|
157
176
|
onActivate={() => {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
: ` Only ${automation.createdBy} can turn on their connected accounts or change where results go.`;
|
|
161
|
-
if (
|
|
162
|
-
window.confirm(
|
|
163
|
-
`Make version ${version.version} active? Junior saves these settings as a new version. Future runs use them.${creatorOnly}`,
|
|
164
|
-
)
|
|
165
|
-
)
|
|
166
|
-
activate.mutate(version.version);
|
|
177
|
+
setConfirming(version.version);
|
|
178
|
+
setConfirmOpen(true);
|
|
167
179
|
}}
|
|
168
180
|
pending={
|
|
169
181
|
activate.isPending && activate.variables === version.version
|
|
@@ -176,6 +188,29 @@ function AutomationVersionsView(props: {
|
|
|
176
188
|
</ol>
|
|
177
189
|
</Card>
|
|
178
190
|
)}
|
|
191
|
+
<AlertDialog onOpenChange={setConfirmOpen} open={confirmOpen}>
|
|
192
|
+
<AlertDialogContent>
|
|
193
|
+
<AlertDialogTitle>Make version {confirming} active?</AlertDialogTitle>
|
|
194
|
+
<AlertDialogDescription>
|
|
195
|
+
Junior saves these settings as a new version. Future runs use them.
|
|
196
|
+
{automation.ownedByViewer
|
|
197
|
+
? null
|
|
198
|
+
: ` Only ${automation.createdBy} can turn on their connected accounts or change where results go.`}
|
|
199
|
+
</AlertDialogDescription>
|
|
200
|
+
<AlertDialogFooter>
|
|
201
|
+
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
202
|
+
<AlertDialogAction
|
|
203
|
+
onClick={() => {
|
|
204
|
+
if (confirming !== undefined) activate.mutate(confirming);
|
|
205
|
+
setConfirmOpen(false);
|
|
206
|
+
}}
|
|
207
|
+
variant="primary"
|
|
208
|
+
>
|
|
209
|
+
Make active
|
|
210
|
+
</AlertDialogAction>
|
|
211
|
+
</AlertDialogFooter>
|
|
212
|
+
</AlertDialogContent>
|
|
213
|
+
</AlertDialog>
|
|
179
214
|
{data.truncated ? (
|
|
180
215
|
<p className="m-0 text-center text-xs text-dashboard-text-muted">
|
|
181
216
|
Showing the 100 most recent versions.
|
|
@@ -192,9 +227,9 @@ function VersionRow(props: {
|
|
|
192
227
|
error: Error | null;
|
|
193
228
|
onActivate(): void;
|
|
194
229
|
pending: boolean;
|
|
195
|
-
previous:
|
|
230
|
+
previous: ListedAutomationVersion | undefined;
|
|
196
231
|
truncated: boolean;
|
|
197
|
-
version:
|
|
232
|
+
version: ListedAutomationVersion;
|
|
198
233
|
}) {
|
|
199
234
|
const { automation, version } = props;
|
|
200
235
|
const editor = version.editedBy
|
|
@@ -255,19 +290,25 @@ function VersionRow(props: {
|
|
|
255
290
|
: "Try again."}
|
|
256
291
|
</FormNotice>
|
|
257
292
|
) : null}
|
|
258
|
-
<
|
|
259
|
-
<
|
|
293
|
+
<Collapsible>
|
|
294
|
+
<CollapsibleTrigger className="group flex cursor-pointer items-center gap-1 py-1 text-xs text-dashboard-text-muted hover:text-dashboard-text focus-visible:outline focus-visible:outline-dashboard-focus">
|
|
295
|
+
<ChevronRight
|
|
296
|
+
aria-hidden="true"
|
|
297
|
+
className={dashboardDisclosureChevronClass}
|
|
298
|
+
/>
|
|
260
299
|
Show settings
|
|
261
|
-
</
|
|
262
|
-
<
|
|
263
|
-
|
|
300
|
+
</CollapsibleTrigger>
|
|
301
|
+
<CollapsibleContent hiddenUntilFound>
|
|
302
|
+
<VersionSettings automation={automation} version={version} />
|
|
303
|
+
</CollapsibleContent>
|
|
304
|
+
</Collapsible>
|
|
264
305
|
</li>
|
|
265
306
|
);
|
|
266
307
|
}
|
|
267
308
|
|
|
268
309
|
function VersionSettings(props: {
|
|
269
310
|
automation: AutomationSummary;
|
|
270
|
-
version:
|
|
311
|
+
version: ListedAutomationVersion;
|
|
271
312
|
}) {
|
|
272
313
|
const { automation, version } = props;
|
|
273
314
|
const { definition } = version;
|
|
@@ -298,12 +339,8 @@ function VersionSettings(props: {
|
|
|
298
339
|
{version.definition.trigger.label} ·{" "}
|
|
299
340
|
{version.definition.trigger.identifier}
|
|
300
341
|
<br />
|
|
301
|
-
{version.
|
|
302
|
-
{
|
|
303
|
-
<pre className="mt-2 mb-0 whitespace-pre-wrap break-words text-xs">
|
|
304
|
-
{JSON.stringify(version.definition.trigger.match, null, 2)}
|
|
305
|
-
</pre>
|
|
306
|
-
) : null}
|
|
342
|
+
{version.trigger.events.join(", ")}
|
|
343
|
+
<TriggerFilters filters={version.trigger.filters} />
|
|
307
344
|
</>
|
|
308
345
|
)}
|
|
309
346
|
</Detail>
|
|
@@ -332,8 +369,8 @@ function VersionSettings(props: {
|
|
|
332
369
|
|
|
333
370
|
/** Name the settings that differ from the previous version. */
|
|
334
371
|
function changedFields(
|
|
335
|
-
previous:
|
|
336
|
-
version:
|
|
372
|
+
previous: ListedAutomationVersion,
|
|
373
|
+
version: ListedAutomationVersion,
|
|
337
374
|
): string[] {
|
|
338
375
|
const before: Record<string, unknown> = previous.definition;
|
|
339
376
|
const after: Record<string, unknown> = version.definition;
|
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
import { useEffect } from "react";
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
2
|
import {
|
|
3
3
|
useLocation,
|
|
4
4
|
useNavigate,
|
|
5
5
|
useParams,
|
|
6
6
|
useSearchParams,
|
|
7
7
|
} from "react-router";
|
|
8
|
-
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
9
8
|
import {
|
|
10
9
|
automationListQuerySchema,
|
|
11
10
|
type AutomationSummary,
|
|
12
11
|
} from "@sentry/junior/api/schema";
|
|
13
12
|
import { Globe2, ListChecks, LockKeyhole, UserRound } from "lucide-react";
|
|
14
13
|
import { useAutomationsData, useAutomationData } from "../../api";
|
|
15
|
-
import { Button } from "../../components/
|
|
14
|
+
import { Button } from "../../components/ui/button";
|
|
16
15
|
import { AutomationFilters, AutomationSort } from "./AutomationFilters";
|
|
17
16
|
import { InlineError } from "../../components/InlineError";
|
|
18
17
|
import { PageContentSkeleton } from "../../components/PageContentSkeleton";
|
|
@@ -25,7 +24,6 @@ import {
|
|
|
25
24
|
import { Card } from "../../components/layout/Card";
|
|
26
25
|
import { PageHeader } from "../../components/layout/PageHeader";
|
|
27
26
|
import { StatCard } from "../../components/metrics/StatCard";
|
|
28
|
-
import { deleteDashboardResource } from "../../http";
|
|
29
27
|
import { automationPath } from "../../format";
|
|
30
28
|
import { useDocumentTitle } from "../../documentTitle";
|
|
31
29
|
import {
|
|
@@ -33,9 +31,12 @@ import {
|
|
|
33
31
|
useDebouncedSearchParam,
|
|
34
32
|
useSearchParamEnum,
|
|
35
33
|
} from "../../searchParams";
|
|
34
|
+
import { AutomationDeleteDialog } from "./AutomationDeleteDialog";
|
|
36
35
|
import { AutomationRow } from "./AutomationRow";
|
|
37
36
|
import { AutomationCostChart } from "./AutomationCostChart";
|
|
38
|
-
import {
|
|
37
|
+
import { AutomationDetails } from "./AutomationDetails";
|
|
38
|
+
import { PageLayout } from "../../components/layout/PageLayout";
|
|
39
|
+
import { cn } from "../../styles";
|
|
39
40
|
import { AutomationExecutionChart } from "./AutomationExecutionChart";
|
|
40
41
|
|
|
41
42
|
const AUTOMATION_RANGE_OPTIONS = ["1", "7", "30", "90"] as const;
|
|
@@ -54,7 +55,9 @@ export function AutomationsPage(props: {
|
|
|
54
55
|
enabled: boolean;
|
|
55
56
|
view: "list" | "overview";
|
|
56
57
|
}) {
|
|
57
|
-
const
|
|
58
|
+
const [deleteTarget, setDeleteTarget] = useState<AutomationSummary>();
|
|
59
|
+
const deleteSource = useRef<HTMLButtonElement | null>(null);
|
|
60
|
+
const listRef = useRef<HTMLDivElement>(null);
|
|
58
61
|
const navigate = useNavigate();
|
|
59
62
|
const location = useLocation();
|
|
60
63
|
const { automationId } = useParams();
|
|
@@ -93,17 +96,14 @@ export function AutomationsPage(props: {
|
|
|
93
96
|
props.view === "list" ? request.toString() : "",
|
|
94
97
|
);
|
|
95
98
|
const detail = useAutomationData(props.enabled, automationId);
|
|
96
|
-
const setFilter = (key: string, value: string) =>
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
},
|
|
105
|
-
{ replace: true },
|
|
106
|
-
);
|
|
99
|
+
const setFilter = (key: string, value: string) => {
|
|
100
|
+
// Consecutive control events can precede the router's next render.
|
|
101
|
+
const next = new URLSearchParams(window.location.search);
|
|
102
|
+
next.delete("page");
|
|
103
|
+
if (value) next.set(key, value);
|
|
104
|
+
else next.delete(key);
|
|
105
|
+
setSearchParams(next, { replace: true });
|
|
106
|
+
};
|
|
107
107
|
const setPage = (page: number) =>
|
|
108
108
|
setSearchParams((current) => {
|
|
109
109
|
const next = new URLSearchParams(current);
|
|
@@ -123,6 +123,15 @@ export function AutomationsPage(props: {
|
|
|
123
123
|
const totalPages = pageCount(visibleTaskCount, filters.pageSize);
|
|
124
124
|
const page = query.data?.page ?? filters.page;
|
|
125
125
|
const selectedTask = detail.data;
|
|
126
|
+
const closeDetails = () => {
|
|
127
|
+
const trigger = automationId
|
|
128
|
+
? listRef.current?.querySelector<HTMLButtonElement>(
|
|
129
|
+
`[data-automation-id="${CSS.escape(automationId)}"] button`,
|
|
130
|
+
)
|
|
131
|
+
: undefined;
|
|
132
|
+
navigate(tasksPath(listPath));
|
|
133
|
+
(trigger ?? listRef.current)?.focus();
|
|
134
|
+
};
|
|
126
135
|
// Overview names the tab through PageHeader; the list has a plain heading.
|
|
127
136
|
useDocumentTitle(
|
|
128
137
|
props.view === "list"
|
|
@@ -155,18 +164,6 @@ export function AutomationsPage(props: {
|
|
|
155
164
|
query.isPlaceholderData,
|
|
156
165
|
setSearchParams,
|
|
157
166
|
]);
|
|
158
|
-
const deletion = useMutation({
|
|
159
|
-
mutationFn: async (automation: AutomationSummary) => {
|
|
160
|
-
await deleteDashboardResource(
|
|
161
|
-
`/api/automations/${encodeURIComponent(automation.id)}`,
|
|
162
|
-
);
|
|
163
|
-
},
|
|
164
|
-
onSuccess: async () => {
|
|
165
|
-
await queryClient.invalidateQueries({
|
|
166
|
-
queryKey: ["dashboard", "automations"],
|
|
167
|
-
});
|
|
168
|
-
},
|
|
169
|
-
});
|
|
170
167
|
|
|
171
168
|
const loading = !query.data && !query.error;
|
|
172
169
|
const executionSeries = query.data?.executionDays?.length
|
|
@@ -185,13 +182,8 @@ export function AutomationsPage(props: {
|
|
|
185
182
|
: [];
|
|
186
183
|
const showExecutionCharts = executionSeries.length > 0;
|
|
187
184
|
|
|
188
|
-
|
|
185
|
+
const content = (
|
|
189
186
|
<>
|
|
190
|
-
{location.state?.automationSaved ? (
|
|
191
|
-
<p role="status" className="m-0 text-sm text-emerald-300">
|
|
192
|
-
Automation saved. Changes apply to future work.
|
|
193
|
-
</p>
|
|
194
|
-
) : null}
|
|
195
187
|
{props.view === "overview" ? (
|
|
196
188
|
<PageHeader
|
|
197
189
|
description="Scheduled and event-driven work created by users."
|
|
@@ -225,16 +217,6 @@ export function AutomationsPage(props: {
|
|
|
225
217
|
<Button onClick={() => void query.refetch()}>Try again</Button>
|
|
226
218
|
</InlineError>
|
|
227
219
|
) : null}
|
|
228
|
-
{automationId && detail.isPending ? (
|
|
229
|
-
<p role="status">Loading automation details…</p>
|
|
230
|
-
) : null}
|
|
231
|
-
{automationId && detail.error ? (
|
|
232
|
-
<InlineError>
|
|
233
|
-
Automation details could not be loaded. It may be unavailable or you
|
|
234
|
-
may not have access.{" "}
|
|
235
|
-
<Button onClick={() => void detail.refetch()}>Try again</Button>
|
|
236
|
-
</InlineError>
|
|
237
|
-
) : null}
|
|
238
220
|
{query.data && props.view === "overview" ? (
|
|
239
221
|
<>
|
|
240
222
|
<div className="grid grid-cols-2 gap-3 lg:grid-cols-4">
|
|
@@ -308,20 +290,7 @@ export function AutomationsPage(props: {
|
|
|
308
290
|
const key = `${automation.kind}:${automation.id}`;
|
|
309
291
|
return (
|
|
310
292
|
<AutomationRow
|
|
311
|
-
deleting={
|
|
312
|
-
deletion.isPending &&
|
|
313
|
-
deletion.variables?.id === automation.id
|
|
314
|
-
}
|
|
315
293
|
key={key}
|
|
316
|
-
onDelete={() => {
|
|
317
|
-
if (
|
|
318
|
-
window.confirm(
|
|
319
|
-
`Delete this ${automation.kind} automation?`,
|
|
320
|
-
)
|
|
321
|
-
) {
|
|
322
|
-
deletion.mutate(automation);
|
|
323
|
-
}
|
|
324
|
-
}}
|
|
325
294
|
onSelect={() =>
|
|
326
295
|
navigate(
|
|
327
296
|
automationId === automation.id
|
|
@@ -343,18 +312,61 @@ export function AutomationsPage(props: {
|
|
|
343
312
|
pageSize={filters.pageSize}
|
|
344
313
|
total={visibleTaskCount}
|
|
345
314
|
/>
|
|
346
|
-
{deletion.error ? (
|
|
347
|
-
<InlineError className="text-center">
|
|
348
|
-
The automation could not be deleted. Try again.
|
|
349
|
-
</InlineError>
|
|
350
|
-
) : null}
|
|
351
315
|
</>
|
|
352
316
|
) : null}
|
|
353
|
-
<AutomationDetailsDrawer
|
|
354
|
-
onClose={() => navigate(tasksPath(listPath))}
|
|
355
|
-
range={range}
|
|
356
|
-
automation={selectedTask}
|
|
357
|
-
/>
|
|
358
317
|
</>
|
|
359
318
|
);
|
|
319
|
+
if (props.view === "overview") return content;
|
|
320
|
+
|
|
321
|
+
return (
|
|
322
|
+
<div
|
|
323
|
+
className={cn(
|
|
324
|
+
"grid h-full min-h-0 min-w-0",
|
|
325
|
+
automationId && "xl:grid-cols-[minmax(0,1fr)_25rem]",
|
|
326
|
+
)}
|
|
327
|
+
>
|
|
328
|
+
<div
|
|
329
|
+
aria-label="Automations list"
|
|
330
|
+
className="min-h-0 overflow-y-auto overscroll-contain outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-dashboard-focus"
|
|
331
|
+
ref={listRef}
|
|
332
|
+
role="region"
|
|
333
|
+
tabIndex={0}
|
|
334
|
+
>
|
|
335
|
+
<PageLayout>{content}</PageLayout>
|
|
336
|
+
</div>
|
|
337
|
+
{automationId ? (
|
|
338
|
+
<AutomationDetails
|
|
339
|
+
key={automationId}
|
|
340
|
+
onClose={closeDetails}
|
|
341
|
+
onDelete={(source) => {
|
|
342
|
+
if (!selectedTask?.ownedByViewer) return;
|
|
343
|
+
deleteSource.current = source;
|
|
344
|
+
setDeleteTarget(selectedTask);
|
|
345
|
+
}}
|
|
346
|
+
onRetry={() => void detail.refetch()}
|
|
347
|
+
error={Boolean(detail.error)}
|
|
348
|
+
range={range}
|
|
349
|
+
automation={selectedTask}
|
|
350
|
+
/>
|
|
351
|
+
) : null}
|
|
352
|
+
{deleteTarget ? (
|
|
353
|
+
<AutomationDeleteDialog
|
|
354
|
+
automation={deleteTarget}
|
|
355
|
+
onClose={() => setDeleteTarget(undefined)}
|
|
356
|
+
onDeleted={() => {
|
|
357
|
+
if (automationId !== deleteTarget.id) return;
|
|
358
|
+
// The details unmount after the route change, which can finish
|
|
359
|
+
// after the dialog closes. Do not return focus to their button.
|
|
360
|
+
deleteSource.current = null;
|
|
361
|
+
navigate(tasksPath(listPath));
|
|
362
|
+
}}
|
|
363
|
+
returnFocus={() =>
|
|
364
|
+
deleteSource.current?.isConnected
|
|
365
|
+
? deleteSource.current
|
|
366
|
+
: (listRef.current ?? false)
|
|
367
|
+
}
|
|
368
|
+
/>
|
|
369
|
+
) : null}
|
|
370
|
+
</div>
|
|
371
|
+
);
|
|
360
372
|
}
|