@sentry/junior-dashboard 0.265.0 → 0.267.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.
Files changed (104) hide show
  1. package/dist/app.js +49 -12
  2. package/dist/client.js +94 -63
  3. package/dist/index.js +49 -12
  4. package/dist/tailwind.css +1 -1
  5. package/package.json +11 -5
  6. package/src/app.ts +0 -2
  7. package/src/auth.ts +4 -7
  8. package/src/client/App.tsx +35 -53
  9. package/src/client/components/ActorAvatar.tsx +10 -22
  10. package/src/client/components/ConnectionBanner.tsx +4 -3
  11. package/src/client/components/CopyButton.tsx +3 -4
  12. package/src/client/components/DetailsPanel.tsx +12 -13
  13. package/src/client/components/Drawer.tsx +46 -23
  14. package/src/client/components/EmptyTelemetry.tsx +5 -3
  15. package/src/client/components/ImageAttachment.tsx +49 -63
  16. package/src/client/components/InlineError.tsx +8 -4
  17. package/src/client/components/Notice.tsx +19 -26
  18. package/src/client/components/Pagination.tsx +26 -26
  19. package/src/client/components/ParticipantAvatarStack.tsx +13 -10
  20. package/src/client/components/README.md +28 -3
  21. package/src/client/components/SearchInput.tsx +51 -41
  22. package/src/client/components/VersionDriftBanner.tsx +9 -7
  23. package/src/client/components/charts/CacheWrites.tsx +52 -43
  24. package/src/client/components/ui/alert.tsx +64 -0
  25. package/src/client/components/ui/avatar.tsx +53 -0
  26. package/src/client/components/ui/button-group.tsx +59 -0
  27. package/src/client/components/ui/button.tsx +2 -1
  28. package/src/client/components/ui/dialog.tsx +60 -0
  29. package/src/client/components/ui/drawer.tsx +140 -0
  30. package/src/client/components/ui/empty.tsx +61 -0
  31. package/src/client/components/ui/input-group.tsx +96 -0
  32. package/src/client/components/ui/pagination.tsx +60 -0
  33. package/src/client/components/ui/separator.tsx +22 -0
  34. package/src/client/components/ui/spinner.tsx +21 -0
  35. package/src/client/components/ui/table.tsx +22 -1
  36. package/src/client/components/ui/tooltip.tsx +1 -1
  37. package/src/client/components/ui/useSheet.ts +5 -1
  38. package/src/client/conversations/ComposerImages.tsx +66 -14
  39. package/src/client/conversations/ConversationChildAgents.tsx +5 -5
  40. package/src/client/conversations/ConversationComposer.tsx +84 -12
  41. package/src/client/conversations/ConversationDetails.tsx +3 -27
  42. package/src/client/conversations/ConversationEventDetails.tsx +26 -10
  43. package/src/client/conversations/ConversationHeader.tsx +5 -4
  44. package/src/client/conversations/ConversationHeaderActions.tsx +7 -11
  45. package/src/client/conversations/ConversationPage.tsx +156 -2
  46. package/src/client/conversations/ConversationSidebar.tsx +5 -4
  47. package/src/client/conversations/ConversationTranscript.tsx +48 -16
  48. package/src/client/conversations/ConversationUsageBreakdown.tsx +108 -65
  49. package/src/client/conversations/ConversationWatches.tsx +67 -59
  50. package/src/client/conversations/DetailsList.tsx +77 -29
  51. package/src/client/conversations/EventDetails.tsx +46 -19
  52. package/src/client/conversations/EventUsage.tsx +99 -74
  53. package/src/client/conversations/ForkConversationButton.tsx +79 -73
  54. package/src/client/conversations/PendingMailboxStack.tsx +312 -119
  55. package/src/client/conversations/README.md +30 -10
  56. package/src/client/conversations/ShareConversationButton.tsx +3 -4
  57. package/src/client/conversations/SubagentTranscriptDrawer.tsx +19 -22
  58. package/src/client/conversations/TranscriptActivityGroup.tsx +22 -19
  59. package/src/client/conversations/TranscriptAttachment.tsx +39 -4
  60. package/src/client/conversations/TranscriptAutomationTriggerView.tsx +14 -19
  61. package/src/client/conversations/TranscriptEventView.tsx +35 -16
  62. package/src/client/conversations/TranscriptFailureView.tsx +10 -13
  63. package/src/client/conversations/TranscriptMarkdown.tsx +232 -436
  64. package/src/client/conversations/TranscriptMessageContextView.tsx +31 -20
  65. package/src/client/conversations/TranscriptMessageView.tsx +7 -1
  66. package/src/client/conversations/TranscriptStructuredEventView.tsx +25 -15
  67. package/src/client/conversations/TranscriptSummary.tsx +13 -7
  68. package/src/client/conversations/TranscriptTurnContextView.tsx +78 -56
  69. package/src/client/conversations/TranscriptView.tsx +7 -3
  70. package/src/client/conversations/UserInputRequestCard.tsx +244 -0
  71. package/src/client/conversations/conversationOutbox.ts +46 -0
  72. package/src/client/conversations/eventLog.ts +1 -0
  73. package/src/client/conversations/eventTime.ts +13 -1
  74. package/src/client/conversations/eventTranscript.ts +12 -5
  75. package/src/client/conversations/queries.ts +127 -26
  76. package/src/client/conversations/transcriptMarkdownLinks.ts +5 -367
  77. package/src/client/conversations/transcriptMentions.tsx +18 -5
  78. package/src/client/conversations/userInputRequest.ts +56 -0
  79. package/src/client/format.ts +10 -3
  80. package/src/client/pages/automations/{AutomationLifecycle.tsx → AutomationActions.tsx} +60 -69
  81. package/src/client/pages/automations/AutomationDraftChanges.tsx +171 -0
  82. package/src/client/pages/automations/AutomationEditor.tsx +182 -156
  83. package/src/client/pages/automations/AutomationEventFields.tsx +119 -45
  84. package/src/client/pages/automations/AutomationExecutionStatusChart.tsx +1 -1
  85. package/src/client/pages/automations/AutomationFormSection.tsx +20 -2
  86. package/src/client/pages/automations/AutomationOutcomeFields.tsx +1 -1
  87. package/src/client/pages/automations/AutomationOverview.tsx +238 -0
  88. package/src/client/pages/automations/AutomationPage.tsx +343 -0
  89. package/src/client/pages/automations/AutomationRecentRuns.tsx +3 -2
  90. package/src/client/pages/automations/AutomationRow.tsx +11 -13
  91. package/src/client/pages/automations/AutomationRunDetails.tsx +4 -2
  92. package/src/client/pages/automations/{AutomationExecutionsPage.tsx → AutomationRunsTab.tsx} +94 -103
  93. package/src/client/pages/automations/AutomationStatus.tsx +164 -108
  94. package/src/client/pages/automations/AutomationVersionsTab.tsx +518 -0
  95. package/src/client/pages/automations/AutomationsPage.tsx +10 -98
  96. package/src/client/pages/automations/AutomationsPageLayout.tsx +5 -23
  97. package/src/client/pages/automations/README.md +56 -46
  98. package/src/client/types.ts +3 -0
  99. package/src/mock-reporting/fixtures.ts +44 -2
  100. package/src/mock-reporting/watches.ts +1 -1
  101. package/src/client/conversations/transcriptStyles.ts +0 -10
  102. package/src/client/pages/automations/AutomationDetails.tsx +0 -193
  103. package/src/client/pages/automations/AutomationEditPage.tsx +0 -176
  104. package/src/client/pages/automations/AutomationVersionsPage.tsx +0 -390
@@ -1,176 +0,0 @@
1
- import { Link, useLocation, useParams } from "react-router";
2
- import { useQuery } from "@tanstack/react-query";
3
- import { ArrowLeft } from "lucide-react";
4
- import {
5
- automationEditSchema,
6
- type AutomationSummary,
7
- } from "@sentry/junior/api/schema";
8
- import { useAutomationData } from "../../api";
9
- import { useDocumentTitle } from "../../documentTitle";
10
- import { fetchDashboardJson } from "../../http";
11
- import { Button } from "../../components/ui/button";
12
- import { FormNotice } from "../../components/FormNotice";
13
- import { StatusChip } from "../../components/StatusChip";
14
- import { AutomationsNavigation } from "./AutomationsPageLayout";
15
- import { TriggerFilters } from "./TriggerFilters";
16
- import { AutomationEditor } from "./AutomationEditor";
17
- import { AutomationEditorLoading } from "./AutomationEditorLoading";
18
- import { AutomationFormSection } from "./AutomationFormSection";
19
- import { AutomationOutcomeList } from "./AutomationOutcomeFields";
20
-
21
- /** Load a linkable editor. Owners and public readers can edit until work completes. */
22
- export function AutomationEditPage(props: { enabled: boolean }) {
23
- const { automationId } = useParams();
24
- const location = useLocation();
25
- const summary = useAutomationData(props.enabled, automationId);
26
- const canEdit =
27
- summary.data &&
28
- !(summary.data.kind === "scheduled" && summary.data.status === "completed");
29
- const edit = useQuery({
30
- queryKey: ["dashboard", "automations", "edit", automationId],
31
- enabled: props.enabled && Boolean(canEdit),
32
- retry: false,
33
- queryFn: ({ signal }) =>
34
- fetchDashboardJson(
35
- automationEditSchema,
36
- `/api/automations/${encodeURIComponent(automationId!)}/edit`,
37
- signal,
38
- ),
39
- });
40
- const returnPath = `/automations/list${location.search}`;
41
- const value = summary.data;
42
- useDocumentTitle(
43
- value
44
- ? canEdit
45
- ? `${value.title} · Edit`
46
- : value.title
47
- : "Edit automation",
48
- );
49
- const status =
50
- value?.kind === "event"
51
- ? value.status !== "active"
52
- ? value.status
53
- : value.trigger.problems.length === 0
54
- ? "active"
55
- : "unavailable"
56
- : value?.status;
57
- const statusTone =
58
- status === "active"
59
- ? "success"
60
- : status === "blocked" || status === "unavailable"
61
- ? "warning"
62
- : "neutral";
63
- return (
64
- <AutomationsNavigation>
65
- <div className="mx-auto w-full min-w-0 max-w-5xl px-4 pt-7 sm:px-8 sm:pt-9">
66
- <Link
67
- to={returnPath}
68
- className="inline-flex min-h-8 items-center gap-2 text-sm text-dashboard-text-muted no-underline hover:text-dashboard-text"
69
- >
70
- <ArrowLeft aria-hidden size={15} />
71
- All automations
72
- </Link>
73
- <div className="mt-4 mb-8 flex flex-wrap items-start justify-between gap-4">
74
- <div className="min-w-0">
75
- <h1 className="m-0 break-words font-display text-3xl font-light tracking-tight">
76
- {value && !canEdit ? value.title : "Edit automation"}
77
- </h1>
78
- {value ? (
79
- <p className="mt-2 mb-0 text-sm text-dashboard-text-muted">
80
- Created by {value.ownedByViewer ? "you" : value.createdBy} ·{" "}
81
- {value.destination.label}
82
- </p>
83
- ) : null}
84
- </div>
85
- {value ? (
86
- <div className="flex items-center gap-4">
87
- <Link
88
- to={`/automations/${encodeURIComponent(value.id)}/versions${location.search}`}
89
- className="text-sm text-dashboard-text-muted underline underline-offset-2 hover:text-dashboard-text"
90
- >
91
- Version history
92
- </Link>
93
- <StatusChip tone={statusTone}>
94
- {status === "unavailable" ? "Trigger unavailable" : status}
95
- </StatusChip>
96
- </div>
97
- ) : null}
98
- </div>
99
- {(!value && summary.error) || (!edit.data && edit.error) ? (
100
- <FormNotice tone="error" title="Automation could not be loaded.">
101
- It may no longer be available, or you may not have access.{" "}
102
- <Button
103
- onClick={() => {
104
- void summary.refetch();
105
- if (canEdit) void edit.refetch();
106
- }}
107
- >
108
- Try again
109
- </Button>
110
- </FormNotice>
111
- ) : !value || (canEdit && !edit.data) ? (
112
- <AutomationEditorLoading />
113
- ) : canEdit && edit.data ? (
114
- <AutomationEditor
115
- key={automationId}
116
- automation={edit.data}
117
- summary={value}
118
- returnPath={returnPath}
119
- />
120
- ) : (
121
- <AutomationReadOnly automation={value} />
122
- )}
123
- </div>
124
- </AutomationsNavigation>
125
- );
126
- }
127
-
128
- function AutomationReadOnly({ automation }: { automation: AutomationSummary }) {
129
- return (
130
- <>
131
- <p className="mb-6 text-sm text-dashboard-text-muted">
132
- This automation has completed and cannot be edited. It will not run
133
- again.
134
- </p>
135
- <AutomationFormSection title="What to do" detail="The saved instruction.">
136
- <p className="m-0 whitespace-pre-wrap break-words text-sm leading-relaxed">
137
- {automation.instruction}
138
- </p>
139
- </AutomationFormSection>
140
- <AutomationFormSection title="When to run" detail="The saved trigger.">
141
- {automation.kind === "scheduled" ? (
142
- <p className="m-0 text-sm">
143
- {automation.schedule} · {automation.timezone}
144
- </p>
145
- ) : (
146
- <div className="grid gap-3 text-sm">
147
- <p className="m-0 break-words">{automation.resource}</p>
148
- <p className="m-0 break-words">
149
- {automation.trigger.events.join(", ")}
150
- </p>
151
- <TriggerFilters filters={automation.trigger.filters} />
152
- </div>
153
- )}
154
- </AutomationFormSection>
155
- <AutomationFormSection
156
- title="Where results go"
157
- detail="Messages after successful work."
158
- >
159
- <AutomationOutcomeList
160
- outcomes={automation.outcomes}
161
- destination={automation.destination}
162
- />
163
- </AutomationFormSection>
164
- <AutomationFormSection
165
- title="Credentials"
166
- detail="The accounts Junior can use."
167
- >
168
- <p className="m-0 text-sm">
169
- {automation.credentialMode === "creator"
170
- ? `Uses ${automation.ownedByViewer ? "your" : `${automation.createdBy}’s`} connected accounts.`
171
- : "System credentials only."}
172
- </p>
173
- </AutomationFormSection>
174
- </>
175
- );
176
- }
@@ -1,390 +0,0 @@
1
- /** Version history for one Automation. Making a version active saves it again. */
2
- import { useState } from "react";
3
- import { ArrowLeft, ChevronRight } from "lucide-react";
4
- import { Link, Navigate, useParams, useSearchParams } from "react-router";
5
- import { useMutation, useQueryClient } from "@tanstack/react-query";
6
- import {
7
- automationEditSchema,
8
- type AutomationSummary,
9
- type AutomationVersionList,
10
- type ListedAutomationVersion,
11
- } from "@sentry/junior/api/schema";
12
- import { useAutomationData, useAutomationVersionsData } from "../../api";
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";
29
- import { Detail, DetailList } from "../../components/DetailList";
30
- import { FormNotice } from "../../components/FormNotice";
31
- import { InlineError } from "../../components/InlineError";
32
- import { PageContentSkeleton } from "../../components/PageContentSkeleton";
33
- import { StatusChip } from "../../components/StatusChip";
34
- import { Card } from "../../components/layout/Card";
35
- import { PageHeader } from "../../components/layout/PageHeader";
36
- import { automationPath } from "../../format";
37
- import { DashboardApiError, fetchDashboardJson, post } from "../../http";
38
- import { pathWithSearch } from "../../searchParams";
39
- import { formatAutomationRunTime } from "./automationFormat";
40
- import { automationOutcomeLabel } from "./automationOutcomes";
41
- import { TriggerFilters } from "./TriggerFilters";
42
-
43
- const DESCRIPTION = "Saved settings for this automation, newest first.";
44
-
45
- /** Load one Automation's versions with its summary for labels and access. */
46
- export function AutomationVersionsPage(props: { enabled: boolean }) {
47
- const { automationId } = useParams();
48
- const [searchParams] = useSearchParams();
49
- const summary = useAutomationData(props.enabled, automationId);
50
- const versions = useAutomationVersionsData(props.enabled, automationId);
51
- if (!automationId) {
52
- return <Navigate replace to="/automations" />;
53
- }
54
- const backTo = pathWithSearch(automationPath(automationId), searchParams);
55
- const error = summary.error ?? versions.error;
56
- const notFound = error instanceof DashboardApiError && error.status === 404;
57
- if (error || notFound) {
58
- return (
59
- <>
60
- <PageHeader description={DESCRIPTION} title="Version history" />
61
- <Card padding="md">
62
- <InlineError>
63
- {notFound
64
- ? "This automation was not found or is not visible to you."
65
- : "Version history could not be loaded. Try again."}
66
- </InlineError>
67
- <BackLink to={backTo} />
68
- </Card>
69
- </>
70
- );
71
- }
72
- if (!summary.data || !versions.data) {
73
- return (
74
- <>
75
- <PageHeader description={DESCRIPTION} title="Version history" />
76
- <PageContentSkeleton label="Loading version history" variant="list" />
77
- </>
78
- );
79
- }
80
- return (
81
- <AutomationVersionsView
82
- automation={summary.data}
83
- backTo={backTo}
84
- data={versions.data}
85
- />
86
- );
87
- }
88
-
89
- function BackLink(props: { to: string }) {
90
- return (
91
- <Link
92
- className="mb-3 inline-flex items-center gap-2 font-sans text-xs text-dashboard-text-muted no-underline hover:text-dashboard-text"
93
- to={props.to}
94
- >
95
- <ArrowLeft aria-hidden="true" size={14} />
96
- Back to automation
97
- </Link>
98
- );
99
- }
100
-
101
- function AutomationVersionsView(props: {
102
- automation: AutomationSummary;
103
- backTo: string;
104
- data: AutomationVersionList;
105
- }) {
106
- const { automation, data } = props;
107
- const queryClient = useQueryClient();
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);
112
- const canActivate = !(
113
- automation.kind === "scheduled" && automation.status === "completed"
114
- );
115
- const path = `/api/automations/${encodeURIComponent(automation.id)}`;
116
- const activate = useMutation({
117
- mutationFn: async (version: number) => {
118
- // The user chose an exact saved version, so the latest revision is safe here.
119
- const current = await fetchDashboardJson(
120
- automationEditSchema,
121
- `${path}/edit`,
122
- );
123
- return post(
124
- automationEditSchema,
125
- `${path}/versions/${version}/activate`,
126
- { revision: current.revision },
127
- );
128
- },
129
- onMutate: () => setActivated(undefined),
130
- onSuccess: async (_, version) => {
131
- setActivated(version);
132
- await queryClient.invalidateQueries({
133
- queryKey: ["dashboard", "automations"],
134
- });
135
- },
136
- });
137
- return (
138
- <>
139
- <div>
140
- <BackLink to={props.backTo} />
141
- <PageHeader
142
- description={`${automation.kind} automation · ${automation.destination.label} · ${data.versions.length} ${data.versions.length === 1 ? "version" : "versions"}`}
143
- documentTitle={`${automation.title} · Version history`}
144
- title={automation.title}
145
- />
146
- </div>
147
- <p className="m-0 max-w-2xl text-sm leading-relaxed text-dashboard-text-muted">
148
- {canActivate
149
- ? "Making a version active saves its settings as a new version. Earlier versions stay in this list. It does not run the automation."
150
- : "This automation has completed. Its versions are read-only."}
151
- </p>
152
- {activated !== undefined ? (
153
- <p role="status" className="m-0 text-sm text-emerald-300">
154
- Version {activated} is active again. It was saved as a new version.
155
- </p>
156
- ) : null}
157
- {data.versions.length === 0 ? (
158
- <Card padding="md">
159
- <p className="m-0 text-sm text-dashboard-text-muted">
160
- No versions are saved yet. Junior saves a version each time the
161
- settings change.
162
- </p>
163
- </Card>
164
- ) : (
165
- <Card>
166
- <ol className="m-0 list-none divide-y divide-dashboard-border-subtle p-0">
167
- {data.versions.map((version, index) => (
168
- <VersionRow
169
- key={version.version}
170
- active={version.version === data.activeVersion}
171
- automation={automation}
172
- canActivate={canActivate}
173
- error={
174
- activate.variables === version.version ? activate.error : null
175
- }
176
- onActivate={() => {
177
- setConfirming(version.version);
178
- setConfirmOpen(true);
179
- }}
180
- pending={
181
- activate.isPending && activate.variables === version.version
182
- }
183
- previous={data.versions[index + 1]}
184
- truncated={data.truncated}
185
- version={version}
186
- />
187
- ))}
188
- </ol>
189
- </Card>
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>
214
- {data.truncated ? (
215
- <p className="m-0 text-center text-xs text-dashboard-text-muted">
216
- Showing the 100 most recent versions.
217
- </p>
218
- ) : null}
219
- </>
220
- );
221
- }
222
-
223
- function VersionRow(props: {
224
- active: boolean;
225
- automation: AutomationSummary;
226
- canActivate: boolean;
227
- error: Error | null;
228
- onActivate(): void;
229
- pending: boolean;
230
- previous: ListedAutomationVersion | undefined;
231
- truncated: boolean;
232
- version: ListedAutomationVersion;
233
- }) {
234
- const { automation, version } = props;
235
- const editor = version.editedBy
236
- ? (version.editedBy.fullName ??
237
- version.editedBy.userName ??
238
- version.editedBy.slackUserId)
239
- : "Editor unknown";
240
- const changes = props.previous
241
- ? changedFields(props.previous, version)
242
- : undefined;
243
- const changeText = changes
244
- ? changes.length
245
- ? `Changed ${changes.join(", ")}`
246
- : "No setting changes"
247
- : version.version === 1 || !props.truncated
248
- ? "First saved version"
249
- : undefined;
250
- const titleId = `automation-version-${version.version}`;
251
- return (
252
- <li aria-labelledby={titleId} className="grid gap-3 px-4 py-4 sm:px-5">
253
- <div className="flex flex-wrap items-start justify-between gap-3">
254
- <div className="min-w-0">
255
- <div className="flex flex-wrap items-center gap-3">
256
- <h3 id={titleId} className="m-0 text-sm font-medium">
257
- Version {version.version}
258
- </h3>
259
- {props.active ? (
260
- <StatusChip size="compact" tone="success">
261
- Active
262
- </StatusChip>
263
- ) : null}
264
- </div>
265
- <p className="mt-1 mb-0 text-xs text-dashboard-text-muted">
266
- {editor} · {formatAutomationRunTime(version.createdAt)}
267
- </p>
268
- {changeText ? (
269
- <p className="mt-1 mb-0 text-xs text-dashboard-text-muted">
270
- {changeText}
271
- </p>
272
- ) : null}
273
- </div>
274
- {props.canActivate && !props.active ? (
275
- <Button
276
- aria-label={`Make version ${version.version} active`}
277
- disabled={props.pending}
278
- onClick={props.onActivate}
279
- >
280
- {props.pending ? "Saving…" : "Make active"}
281
- </Button>
282
- ) : null}
283
- </div>
284
- {props.error ? (
285
- <FormNotice tone="error" title="This version could not be made active.">
286
- {props.error instanceof DashboardApiError
287
- ? props.error.code === "conflict"
288
- ? "The automation changed. Try again."
289
- : (props.error.apiError ?? "Try again.")
290
- : "Try again."}
291
- </FormNotice>
292
- ) : null}
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
- />
299
- Show settings
300
- </CollapsibleTrigger>
301
- <CollapsibleContent hiddenUntilFound>
302
- <VersionSettings automation={automation} version={version} />
303
- </CollapsibleContent>
304
- </Collapsible>
305
- </li>
306
- );
307
- }
308
-
309
- function VersionSettings(props: {
310
- automation: AutomationSummary;
311
- version: ListedAutomationVersion;
312
- }) {
313
- const { automation, version } = props;
314
- const { definition } = version;
315
- const destination =
316
- definition.destination.channelId === automation.destination.channelId &&
317
- definition.destination.teamId === automation.destination.teamId
318
- ? automation.destination.label
319
- : `Another destination · ${definition.destination.channelId}`;
320
- return (
321
- <DetailList className="mt-2">
322
- <Detail label="Title">
323
- {definition.title ?? "First line of the instruction"}
324
- </Detail>
325
- <Detail label="What to do" valueClassName="whitespace-pre-wrap">
326
- {definition.instruction}
327
- </Detail>
328
- <Detail label="When to run">
329
- {version.kind === "scheduled" ? (
330
- version.definition.schedule.description.includes(
331
- version.definition.schedule.timezone,
332
- ) ? (
333
- version.definition.schedule.description
334
- ) : (
335
- `${version.definition.schedule.description} (${version.definition.schedule.timezone})`
336
- )
337
- ) : (
338
- <>
339
- {version.definition.trigger.label} ·{" "}
340
- {version.definition.trigger.identifier}
341
- <br />
342
- {version.trigger.events.join(", ")}
343
- <TriggerFilters filters={version.trigger.filters} />
344
- </>
345
- )}
346
- </Detail>
347
- <Detail label="Destination">{destination}</Detail>
348
- <Detail label="On success">
349
- {definition.outcomes.length ? (
350
- <ol className="m-0 grid gap-1 p-0">
351
- {definition.outcomes.map((outcome, index) => (
352
- <li key={index}>
353
- {automationOutcomeLabel(outcome, automation.destination)}
354
- </li>
355
- ))}
356
- </ol>
357
- ) : (
358
- "No message on success"
359
- )}
360
- </Detail>
361
- <Detail label="Connected accounts">
362
- {definition.credentialMode === "creator"
363
- ? `Uses accounts connected by ${automation.ownedByViewer ? "you" : automation.createdBy}.`
364
- : "System credentials only"}
365
- </Detail>
366
- </DetailList>
367
- );
368
- }
369
-
370
- /** Name the settings that differ from the previous version. */
371
- function changedFields(
372
- previous: ListedAutomationVersion,
373
- version: ListedAutomationVersion,
374
- ): string[] {
375
- const before: Record<string, unknown> = previous.definition;
376
- const after: Record<string, unknown> = version.definition;
377
- return (
378
- [
379
- ["title", "title"],
380
- ["instruction", "instruction"],
381
- ["schedule", "schedule"],
382
- ["trigger", "trigger"],
383
- ["destination", "destination"],
384
- ["outcomes", "success messages"],
385
- ["credentialMode", "connected accounts"],
386
- ] as const
387
- ).flatMap(([key, label]) =>
388
- JSON.stringify(before[key]) === JSON.stringify(after[key]) ? [] : [label],
389
- );
390
- }