@sentry/junior-dashboard 0.263.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.
Files changed (197) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +2 -5
  3. package/dist/app.js +119 -35
  4. package/dist/client.js +148 -135
  5. package/dist/index.d.ts +0 -1
  6. package/dist/index.js +119 -35
  7. package/dist/tailwind.css +1 -1
  8. package/package.json +9 -4
  9. package/src/api/schema.ts +0 -1
  10. package/src/app.ts +5 -20
  11. package/src/client/App.tsx +29 -31
  12. package/src/client/api.ts +7 -0
  13. package/src/client/components/BottomSheet.tsx +26 -18
  14. package/src/client/components/CopyButton.tsx +83 -0
  15. package/src/client/components/DetailsPanel.tsx +133 -0
  16. package/src/client/components/Drawer.tsx +34 -33
  17. package/src/client/components/FilterBar.tsx +16 -31
  18. package/src/client/components/Metric.tsx +33 -23
  19. package/src/client/components/PageContentSkeleton.tsx +1 -1
  20. package/src/client/components/Pagination.tsx +1 -1
  21. package/src/client/components/ParticipantAvatarStack.tsx +40 -36
  22. package/src/client/components/ProfileMenu.tsx +27 -93
  23. package/src/client/components/README.md +101 -64
  24. package/src/client/components/SegmentedNav.tsx +1 -1
  25. package/src/client/components/ShimmerText.tsx +2 -6
  26. package/src/client/components/ThemeSwitcher.tsx +19 -15
  27. package/src/client/components/charts/ActivityChart.tsx +173 -288
  28. package/src/client/components/charts/CacheWrites.tsx +63 -50
  29. package/src/client/components/charts/InputCacheChart.tsx +137 -131
  30. package/src/client/components/charts/README.md +4 -4
  31. package/src/client/components/charts/SystemMetricCharts.tsx +68 -95
  32. package/src/client/components/charts/WorkspaceUsageChart.tsx +42 -62
  33. package/src/client/components/controls/DirectorySortSelect.tsx +3 -3
  34. package/src/client/components/controls/TimeRangeSelector.tsx +10 -15
  35. package/src/client/components/layout/DashboardHeader.tsx +44 -90
  36. package/src/client/components/layout/SecondaryNavigation.tsx +2 -1
  37. package/src/client/components/layout/VisualViewportShell.tsx +2 -2
  38. package/src/client/components/ui/accordion.tsx +77 -0
  39. package/src/client/components/ui/alert-dialog.tsx +89 -0
  40. package/src/client/components/ui/button.tsx +85 -0
  41. package/src/client/components/ui/chart.tsx +74 -0
  42. package/src/client/components/ui/checkbox.tsx +26 -0
  43. package/src/client/components/ui/collapsible.tsx +22 -0
  44. package/src/client/components/ui/field.tsx +49 -0
  45. package/src/client/components/ui/input.tsx +35 -0
  46. package/src/client/components/ui/native-select.tsx +57 -0
  47. package/src/client/components/ui/popover.tsx +67 -0
  48. package/src/client/components/ui/radio-group.tsx +32 -0
  49. package/src/client/components/ui/sheet.tsx +151 -0
  50. package/src/client/components/ui/skeleton.tsx +19 -0
  51. package/src/client/components/ui/table.tsx +80 -0
  52. package/src/client/components/ui/tabs.tsx +75 -0
  53. package/src/client/components/ui/textarea.tsx +17 -0
  54. package/src/client/components/ui/toast.tsx +215 -0
  55. package/src/client/components/ui/toggle-group.tsx +36 -0
  56. package/src/client/components/ui/toggle.tsx +37 -0
  57. package/src/client/components/ui/tooltip.tsx +168 -0
  58. package/src/client/components/ui/trace.tsx +96 -0
  59. package/src/client/components/ui/useSheet.ts +74 -0
  60. package/src/client/conversations/ChatLayout.tsx +7 -3
  61. package/src/client/conversations/ConversationBrief.tsx +17 -6
  62. package/src/client/conversations/ConversationChildAgents.tsx +87 -58
  63. package/src/client/conversations/ConversationComposer.tsx +26 -12
  64. package/src/client/conversations/ConversationDetails.tsx +198 -95
  65. package/src/client/conversations/ConversationEventDetails.tsx +141 -0
  66. package/src/client/conversations/ConversationEventLog.tsx +150 -129
  67. package/src/client/conversations/ConversationHeader.tsx +8 -3
  68. package/src/client/conversations/ConversationHeaderActions.tsx +90 -82
  69. package/src/client/conversations/ConversationHomeList.tsx +1 -1
  70. package/src/client/conversations/ConversationLinkedWork.tsx +118 -0
  71. package/src/client/conversations/ConversationMeta.tsx +111 -92
  72. package/src/client/conversations/ConversationPage.tsx +241 -110
  73. package/src/client/conversations/ConversationSidebar.tsx +48 -29
  74. package/src/client/conversations/ConversationUsageBreakdown.tsx +16 -3
  75. package/src/client/conversations/ConversationWatches.tsx +165 -0
  76. package/src/client/conversations/ConversationWorkspace.tsx +12 -22
  77. package/src/client/conversations/ConversationWorkspaceLoading.tsx +1 -1
  78. package/src/client/conversations/DetailsList.tsx +124 -0
  79. package/src/client/conversations/EventDetails.tsx +42 -8
  80. package/src/client/conversations/ForkConversationButton.tsx +27 -16
  81. package/src/client/conversations/ObjectCard.tsx +20 -8
  82. package/src/client/conversations/PendingMailboxStack.tsx +52 -33
  83. package/src/client/conversations/README.md +93 -19
  84. package/src/client/conversations/ShareConversationButton.tsx +28 -3
  85. package/src/client/conversations/SubagentTranscriptDrawer.tsx +21 -15
  86. package/src/client/conversations/ToolFrame.tsx +36 -52
  87. package/src/client/conversations/TranscriptActivityGroup.tsx +13 -4
  88. package/src/client/conversations/TranscriptAutomationTriggerView.tsx +5 -4
  89. package/src/client/conversations/TranscriptContextEventView.tsx +25 -11
  90. package/src/client/conversations/TranscriptHeadingRow.tsx +7 -7
  91. package/src/client/conversations/TranscriptLoading.tsx +1 -1
  92. package/src/client/conversations/TranscriptMarkdown.tsx +25 -20
  93. package/src/client/conversations/TranscriptMessageContextView.tsx +8 -2
  94. package/src/client/conversations/TranscriptMessageView.tsx +6 -1
  95. package/src/client/conversations/TranscriptReasoningView.tsx +54 -68
  96. package/src/client/conversations/TranscriptSubagentView.tsx +3 -3
  97. package/src/client/conversations/TranscriptTimestamp.tsx +9 -7
  98. package/src/client/conversations/TranscriptToolView.tsx +1 -1
  99. package/src/client/conversations/TranscriptTurnContextView.tsx +99 -27
  100. package/src/client/conversations/TranscriptView.tsx +63 -27
  101. package/src/client/conversations/eventLog.ts +57 -0
  102. package/src/client/conversations/eventTime.ts +60 -0
  103. package/src/client/conversations/queries.ts +29 -0
  104. package/src/client/conversations/transcriptBottomPinning.ts +34 -0
  105. package/src/client/format.ts +5 -0
  106. package/src/client/lib/utils.ts +1 -0
  107. package/src/client/pages/ConnectionsPage.tsx +2 -2
  108. package/src/client/pages/PersonalTokensPage.tsx +4 -4
  109. package/src/client/pages/SettingsPage.tsx +9 -10
  110. package/src/client/pages/admin/ServiceAccountsSection.tsx +2 -2
  111. package/src/client/pages/automations/AutomationCostChart.tsx +50 -71
  112. package/src/client/pages/automations/AutomationDeleteDialog.tsx +87 -0
  113. package/src/client/pages/automations/AutomationDetails.tsx +193 -0
  114. package/src/client/pages/automations/AutomationEditPage.tsx +7 -8
  115. package/src/client/pages/automations/AutomationEditor.tsx +41 -29
  116. package/src/client/pages/automations/AutomationEditorLoading.tsx +1 -1
  117. package/src/client/pages/automations/AutomationEventFields.tsx +202 -119
  118. package/src/client/pages/automations/AutomationExecutionChart.tsx +51 -64
  119. package/src/client/pages/automations/AutomationExecutionStatusChart.tsx +47 -70
  120. package/src/client/pages/automations/AutomationExecutionsPage.tsx +150 -126
  121. package/src/client/pages/automations/AutomationFilters.tsx +189 -74
  122. package/src/client/pages/automations/AutomationLifecycle.tsx +109 -78
  123. package/src/client/pages/automations/AutomationOutcomeFields.tsx +26 -24
  124. package/src/client/pages/automations/AutomationRecentRuns.tsx +143 -0
  125. package/src/client/pages/automations/AutomationRow.tsx +12 -138
  126. package/src/client/pages/automations/AutomationRunDetails.tsx +307 -0
  127. package/src/client/pages/automations/AutomationRunsPage.tsx +119 -94
  128. package/src/client/pages/automations/AutomationScheduleFields.tsx +51 -51
  129. package/src/client/pages/automations/AutomationStatus.tsx +201 -0
  130. package/src/client/pages/automations/AutomationVersionsPage.tsx +65 -28
  131. package/src/client/pages/automations/AutomationsPage.tsx +80 -68
  132. package/src/client/pages/automations/AutomationsPageLayout.tsx +46 -14
  133. package/src/client/pages/automations/README.md +57 -0
  134. package/src/client/pages/automations/TriggerFilters.tsx +22 -0
  135. package/src/client/pages/automations/automationFormat.ts +37 -23
  136. package/src/client/pages/code/CodeActivityChart.tsx +59 -101
  137. package/src/client/pages/code/CodePage.tsx +52 -55
  138. package/src/client/pages/code/RepositoryPage.tsx +6 -8
  139. package/src/client/pages/locations/LocationActivityChart.tsx +53 -70
  140. package/src/client/pages/locations/LocationDirectoryActivityChart.tsx +60 -80
  141. package/src/client/pages/memory/MemoryCostChart.tsx +79 -103
  142. package/src/client/pages/memory/MemoryDetails.tsx +218 -0
  143. package/src/client/pages/memory/MemoryPage.tsx +168 -112
  144. package/src/client/pages/memory/MemoryPageLayout.tsx +33 -9
  145. package/src/client/pages/memory/MemoryTimeline.tsx +46 -69
  146. package/src/client/pages/memory/memoryRecord.ts +1 -1
  147. package/src/client/pages/people/ContributionGrid.tsx +23 -16
  148. package/src/client/pages/people/PeopleActivityChart.tsx +68 -107
  149. package/src/client/pages/people/ProfileCodeChanges.tsx +5 -5
  150. package/src/client/pages/system/BaseSandboxCard.tsx +1 -1
  151. package/src/client/pages/system/ConversationActivityChart.tsx +49 -67
  152. package/src/client/pages/system/GuardianActivity.tsx +1 -2
  153. package/src/client/pages/system/PluginBarChart.tsx +106 -87
  154. package/src/client/pages/system/PluginReportRecordSet.tsx +25 -24
  155. package/src/client/pages/system/README.md +23 -0
  156. package/src/client/pages/system/SystemNavigation.tsx +5 -1
  157. package/src/client/pages/system/WorkspaceDeleteDialog.tsx +111 -0
  158. package/src/client/pages/system/WorkspaceDetails.tsx +200 -43
  159. package/src/client/pages/system/WorkspaceEditor.tsx +59 -61
  160. package/src/client/pages/system/WorkspaceFormPage.tsx +23 -47
  161. package/src/client/pages/system/WorkspaceList.tsx +20 -30
  162. package/src/client/pages/system/WorkspacesPage.tsx +117 -119
  163. package/src/client/pages/system/sandboxSize.ts +8 -0
  164. package/src/client/pages/system/workspaceQueries.ts +53 -0
  165. package/src/client/pages/user/PluginUserPage.tsx +51 -2
  166. package/src/client/pages/user/pluginUserPageData.ts +1 -6
  167. package/src/client/styles.ts +4 -0
  168. package/src/client.tsx +7 -1
  169. package/src/mock-reporting/automation-collection.ts +4 -2
  170. package/src/mock-reporting/fixtures.ts +23 -7
  171. package/src/mock-reporting/routes.ts +12 -0
  172. package/src/mock-reporting/watches.ts +94 -0
  173. package/src/shell.ts +4 -9
  174. package/src/tailwind.css +77 -10
  175. package/src/client/components/Button.tsx +0 -118
  176. package/src/client/components/Field.tsx +0 -53
  177. package/src/client/components/SegmentedTabs.tsx +0 -119
  178. package/src/client/components/Select.tsx +0 -52
  179. package/src/client/components/Sheet.LICENSE +0 -21
  180. package/src/client/components/Sheet.tsx +0 -141
  181. package/src/client/components/Skeleton.tsx +0 -17
  182. package/src/client/components/TextInput.tsx +0 -58
  183. package/src/client/components/Tooltip.tsx +0 -216
  184. package/src/client/components/charts/useChartLayout.ts +0 -27
  185. package/src/client/conversations/ConversationDetailsFrame.tsx +0 -91
  186. package/src/client/conversations/CopyMarkdownButton.tsx +0 -65
  187. package/src/client/pages/automations/AutomationDetailsDrawer.tsx +0 -219
  188. package/src/client/pages/dev/ChartFixtures.tsx +0 -325
  189. package/src/client/pages/dev/ComponentsPage.tsx +0 -899
  190. package/src/client/pages/dev/ConversationFixture.tsx +0 -231
  191. package/src/client/pages/dev/ObjectFixtures.tsx +0 -138
  192. package/src/client/pages/dev/SegmentedFixtures.tsx +0 -59
  193. package/src/client/pages/dev/SelectFixtures.tsx +0 -62
  194. package/src/client/pages/dev/TranscriptActivityFixtures.tsx +0 -206
  195. package/src/client/pages/memory/MemoryDetailsDrawer.tsx +0 -167
  196. package/src/client/pages/system/SandboxSizeSummary.tsx +0 -47
  197. package/src/client/pages/system/SnapshotSummary.tsx +0 -78
@@ -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 { Field } from "../../components/Field";
7
- import { Select } from "../../components/Select";
8
- import { TextInput } from "../../components/TextInput";
9
- import { Button } from "../../components/Button";
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 resource = catalog.data?.find(
32
- (item) =>
33
- item.namespace === props.value.namespace &&
34
- item.type === props.value.resourceType,
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
- props.value.events.some(
39
- (event) => !resource.supportedEvents.includes(event),
40
- ) ||
41
- Object.entries(props.value.match ?? {}).some(([key, value]) => {
42
- const field = resource.matchFields?.[key];
43
- if (!field) return true;
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
- <pre className="whitespace-pre-wrap break-all text-xs">
74
- {JSON.stringify(props.value, null, 2)}
75
- </pre>
100
+ <SavedTrigger
101
+ resourceTypes={sourceResourceTypes}
102
+ trigger={props.value}
103
+ />
76
104
  </FormNotice>
77
105
  ) : null}
78
- <Field
79
- label={unsupported ? "Replace trigger" : "Resource type"}
80
- htmlFor="trigger-resource"
81
- >
82
- <Select
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
- if (
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
- </Select>
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 label="Resource name" htmlFor="trigger-label">
125
- <TextInput
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
- label="Resource identifier"
136
- htmlFor="trigger-identifier"
137
- help="Use the exact provider resource identifier, not its display name."
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
- <input
159
- type="checkbox"
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
- onChange={() =>
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.replaceAll("_", " ").replaceAll(".", " · ")}
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
- {Object.keys(resource.matchFields ?? {}).length ? (
228
+ {matchFields.length ? (
184
229
  <fieldset className="grid gap-4">
185
- <legend className="mb-3 text-sm font-semibold">Only when</legend>
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
- All conditions must match. Leave a field blank to match any
188
- value. Existing lists match any one listed 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
- {Object.entries(resource.matchFields ?? {}).map(
191
- ([key, field]) => {
192
- const value = props.value.match?.[key];
193
- return (
194
- <Field
195
- key={key}
196
- label={key}
197
- htmlFor={`match-${key}`}
198
- help={field.description}
199
- >
200
- {Array.isArray(value) ? (
201
- <div className="text-sm">
202
- <span className="break-all">{value.join(", ")}</span>{" "}
203
- <Button onClick={() => setMatch(key, undefined)}>
204
- Remove condition
205
- </Button>
206
- </div>
207
- ) : field.kind === "boolean" || field.enum ? (
208
- <Select
209
- id={`match-${key}`}
210
- value={value === undefined ? "" : String(value)}
211
- onChange={(e) =>
212
- setMatch(
213
- key,
214
- e.target.value === ""
215
- ? undefined
216
- : field.kind === "boolean"
217
- ? e.target.value === "true"
218
- : e.target.value,
219
- )
220
- }
221
- >
222
- <option value="">Any value</option>
223
- {(field.enum ?? ["true", "false"]).map((v) => (
224
- <option key={v}>{v}</option>
225
- ))}
226
- </Select>
227
- ) : (
228
- <TextInput
229
- size="comfortable"
230
- id={`match-${key}`}
231
- type={field.kind === "number" ? "number" : "text"}
232
- step="any"
233
- value={value === undefined ? "" : String(value)}
234
- onChange={(e) =>
235
- setMatch(
236
- key,
237
- e.target.value === ""
238
- ? undefined
239
- : field.kind === "number"
240
- ? e.target.valueAsNumber
241
- : e.target.value,
242
- )
243
- }
244
- />
245
- )}
246
- </Field>
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
+ }
@@ -4,21 +4,23 @@ import {
4
4
  type TimeRangeDays,
5
5
  } from "../../components/controls/TimeRangeSelector";
6
6
  import type { AutomationExecutionDay } from "@sentry/junior/api/schema";
7
+ import { Bar, BarChart, CartesianGrid } from "recharts";
7
8
  import {
8
9
  ActivityChartAverageLine,
9
- ActivityChartDateLabels,
10
- ActivityChartGrid,
11
10
  activityChartAverage,
12
- ActivityChartTooltip,
13
- ActivityTooltipRows,
14
- ChartSvg,
15
- createActivityChartLayout,
11
+ activityChartMargin,
12
+ ActivityChartTooltipContent,
13
+ ActivityChartXAxis,
14
+ ActivityChartYAxis,
16
15
  } from "../../components/charts/ActivityChart";
16
+ import { ChartContainer, ChartTooltip } from "../../components/ui/chart";
17
17
 
18
18
  import { Card } from "../../components/layout/Card";
19
19
  import { formatActivityChartAverage } from "../../format";
20
20
 
21
- const EXECUTION_COLOR = "var(--color-amber-400)";
21
+ const chartConfig = {
22
+ total: { color: "var(--color-amber-400)", label: "Executions" },
23
+ };
22
24
 
23
25
  /** Render completed automation executions over a trailing window. */
24
26
  export function AutomationExecutionChart(props: {
@@ -29,12 +31,11 @@ export function AutomationExecutionChart(props: {
29
31
  }) {
30
32
  const bucketUnit = props.bucketUnit ?? "day";
31
33
 
32
- const days = props.days;
33
- const layout = createActivityChartLayout(200);
34
- const step =
35
- days.length > 0 ? layout.plotWidth / days.length : layout.plotWidth;
36
- const barWidth = Math.max(2, Math.min(13, step * 0.68));
37
- const totals = days.map((day) => day.scheduled + day.event);
34
+ const days = props.days.map((day) => ({
35
+ date: day.date,
36
+ total: day.scheduled + day.event,
37
+ }));
38
+ const totals = days.map((day) => day.total);
38
39
  const maximum = Math.max(1, ...totals);
39
40
  const average = activityChartAverage(totals);
40
41
  const hasExecutions = totals.some((total) => total > 0);
@@ -51,58 +52,44 @@ export function AutomationExecutionChart(props: {
51
52
  </div>
52
53
 
53
54
  <div className="relative mt-5 overflow-hidden">
54
- <ChartSvg
55
- aria-label={`Automation executions during the last ${props.range === 1 ? "24 hours" : `${props.range} days`}`}
56
- className="min-h-40"
57
- layout={layout}
55
+ <ChartContainer
56
+ className="aspect-auto h-50 w-full"
57
+ config={chartConfig}
58
58
  >
59
- <ActivityChartGrid layout={layout} maximum={maximum} />
60
- {days.map((day, dayIndex) => {
61
- const x = layout.left + dayIndex * step + (step - barWidth) / 2;
62
- const total = totals[dayIndex] ?? 0;
63
- const height = (total / maximum) * layout.plotHeight;
64
- return (
65
- <ActivityChartTooltip
66
- key={day.date}
67
- content={<ActivityTooltipRows rows={[["executions", total]]} />}
68
- date={day.date}
69
- summary={`${total} executions`}
70
- >
71
- <g tabIndex={0}>
72
- <rect
73
- fill={EXECUTION_COLOR}
74
- height={height}
75
- opacity={0.82}
76
- rx="1"
77
- width={barWidth}
78
- x={x}
79
- y={layout.top + layout.plotHeight - height}
80
- />
81
- <rect
82
- fill="transparent"
83
- height={layout.plotHeight}
84
- width={Math.max(barWidth, 8)}
85
- x={x - (Math.max(barWidth, 8) - barWidth) / 2}
86
- y={layout.top}
87
- />
88
- </g>
89
- </ActivityChartTooltip>
90
- );
91
- })}
92
- <ActivityChartAverageLine
93
- unit={timeRangeBucketAverageUnit(bucketUnit)}
94
- average={average}
95
- format={formatActivityChartAverage}
96
- layout={layout}
97
- maximum={maximum}
98
- stroke="var(--color-slate-200)"
99
- />
100
- <ActivityChartDateLabels
101
- dates={days.map((day) => day.date)}
102
- layout={layout}
103
- xPosition={(index) => layout.left + index * step + step / 2}
104
- />
105
- </ChartSvg>
59
+ <BarChart
60
+ aria-label={`Automation executions during the last ${props.range === 1 ? "24 hours" : `${props.range} days`}`}
61
+ data={days}
62
+ margin={activityChartMargin}
63
+ role="img"
64
+ >
65
+ <CartesianGrid strokeDasharray="3 5" vertical={false} />
66
+ <ActivityChartYAxis maximum={maximum} />
67
+ <ActivityChartXAxis keys={days.map((day) => day.date)} />
68
+ <ChartTooltip
69
+ content={
70
+ <ActivityChartTooltipContent
71
+ rows={(day: (typeof days)[number]) => [
72
+ ["executions", day.total],
73
+ ]}
74
+ />
75
+ }
76
+ />
77
+ <Bar
78
+ dataKey="total"
79
+ fill="var(--color-total)"
80
+ fillOpacity={0.82}
81
+ isAnimationActive={false}
82
+ maxBarSize={13}
83
+ radius={1}
84
+ />
85
+ <ActivityChartAverageLine
86
+ unit={timeRangeBucketAverageUnit(bucketUnit)}
87
+ average={average}
88
+ format={formatActivityChartAverage}
89
+ stroke="var(--color-slate-200)"
90
+ />
91
+ </BarChart>
92
+ </ChartContainer>
106
93
  {!hasExecutions ? (
107
94
  <div className="pointer-events-none absolute inset-0 grid place-items-center pt-12 font-sans text-xs text-dashboard-text-muted">
108
95
  No automation executions in this period.