@sentry/junior-dashboard 0.264.0 → 0.266.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 (213) 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 +145 -136
  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 +5 -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/ActorAvatar.tsx +10 -22
  14. package/src/client/components/BottomSheet.tsx +12 -2
  15. package/src/client/components/ConnectionBanner.tsx +4 -3
  16. package/src/client/components/CopyButton.tsx +82 -0
  17. package/src/client/components/DetailsPanel.tsx +133 -0
  18. package/src/client/components/Drawer.tsx +15 -24
  19. package/src/client/components/EmptyTelemetry.tsx +5 -3
  20. package/src/client/components/FilterBar.tsx +16 -31
  21. package/src/client/components/ImageAttachment.tsx +49 -63
  22. package/src/client/components/InlineError.tsx +8 -4
  23. package/src/client/components/Metric.tsx +33 -23
  24. package/src/client/components/Notice.tsx +19 -26
  25. package/src/client/components/PageContentSkeleton.tsx +1 -1
  26. package/src/client/components/Pagination.tsx +27 -27
  27. package/src/client/components/ParticipantAvatarStack.tsx +43 -39
  28. package/src/client/components/ProfileMenu.tsx +27 -93
  29. package/src/client/components/README.md +103 -78
  30. package/src/client/components/SearchInput.tsx +51 -41
  31. package/src/client/components/SegmentedNav.tsx +1 -1
  32. package/src/client/components/ShimmerText.tsx +2 -6
  33. package/src/client/components/ThemeSwitcher.tsx +19 -15
  34. package/src/client/components/VersionDriftBanner.tsx +9 -7
  35. package/src/client/components/charts/ActivityChart.tsx +173 -288
  36. package/src/client/components/charts/CacheWrites.tsx +59 -37
  37. package/src/client/components/charts/InputCacheChart.tsx +137 -131
  38. package/src/client/components/charts/README.md +4 -4
  39. package/src/client/components/charts/SystemMetricCharts.tsx +68 -95
  40. package/src/client/components/charts/WorkspaceUsageChart.tsx +42 -62
  41. package/src/client/components/controls/DirectorySortSelect.tsx +3 -3
  42. package/src/client/components/controls/TimeRangeSelector.tsx +10 -15
  43. package/src/client/components/layout/DashboardHeader.tsx +44 -90
  44. package/src/client/components/layout/SecondaryNavigation.tsx +2 -1
  45. package/src/client/components/layout/VisualViewportShell.tsx +2 -2
  46. package/src/client/components/ui/accordion.tsx +77 -0
  47. package/src/client/components/ui/alert-dialog.tsx +89 -0
  48. package/src/client/components/ui/alert.tsx +64 -0
  49. package/src/client/components/ui/avatar.tsx +53 -0
  50. package/src/client/components/ui/button.tsx +86 -0
  51. package/src/client/components/ui/chart.tsx +74 -0
  52. package/src/client/components/ui/checkbox.tsx +26 -0
  53. package/src/client/components/ui/collapsible.tsx +22 -0
  54. package/src/client/components/ui/dialog.tsx +66 -0
  55. package/src/client/components/ui/empty.tsx +32 -0
  56. package/src/client/components/ui/field.tsx +49 -0
  57. package/src/client/components/ui/input-group.tsx +96 -0
  58. package/src/client/components/ui/input.tsx +35 -0
  59. package/src/client/components/ui/native-select.tsx +57 -0
  60. package/src/client/components/ui/pagination.tsx +60 -0
  61. package/src/client/components/ui/popover.tsx +67 -0
  62. package/src/client/components/ui/radio-group.tsx +32 -0
  63. package/src/client/components/ui/sheet.tsx +13 -3
  64. package/src/client/components/ui/skeleton.tsx +19 -0
  65. package/src/client/components/ui/spinner.tsx +21 -0
  66. package/src/client/components/ui/table.tsx +101 -0
  67. package/src/client/components/ui/tabs.tsx +75 -0
  68. package/src/client/components/ui/textarea.tsx +17 -0
  69. package/src/client/components/ui/toast.tsx +215 -0
  70. package/src/client/components/ui/toggle-group.tsx +36 -0
  71. package/src/client/components/ui/toggle.tsx +37 -0
  72. package/src/client/components/ui/tooltip.tsx +168 -0
  73. package/src/client/components/ui/trace.tsx +96 -0
  74. package/src/client/conversations/ChatLayout.tsx +7 -3
  75. package/src/client/conversations/ConversationBrief.tsx +17 -6
  76. package/src/client/conversations/ConversationChildAgents.tsx +5 -5
  77. package/src/client/conversations/ConversationComposer.tsx +26 -12
  78. package/src/client/conversations/ConversationDetails.tsx +147 -62
  79. package/src/client/conversations/ConversationEventDetails.tsx +141 -0
  80. package/src/client/conversations/ConversationEventLog.tsx +150 -129
  81. package/src/client/conversations/ConversationHeader.tsx +13 -7
  82. package/src/client/conversations/ConversationHeaderActions.tsx +97 -93
  83. package/src/client/conversations/ConversationHomeList.tsx +1 -1
  84. package/src/client/conversations/ConversationLinkedWork.tsx +118 -0
  85. package/src/client/conversations/ConversationMeta.tsx +55 -88
  86. package/src/client/conversations/ConversationPage.tsx +220 -101
  87. package/src/client/conversations/ConversationSidebar.tsx +49 -29
  88. package/src/client/conversations/ConversationUsageBreakdown.tsx +108 -65
  89. package/src/client/conversations/ConversationWatches.tsx +173 -0
  90. package/src/client/conversations/ConversationWorkspace.tsx +12 -22
  91. package/src/client/conversations/ConversationWorkspaceLoading.tsx +1 -1
  92. package/src/client/conversations/DetailsList.tsx +139 -0
  93. package/src/client/conversations/EventDetails.tsx +42 -8
  94. package/src/client/conversations/EventUsage.tsx +99 -74
  95. package/src/client/conversations/ForkConversationButton.tsx +27 -16
  96. package/src/client/conversations/ObjectCard.tsx +20 -8
  97. package/src/client/conversations/PendingMailboxStack.tsx +52 -38
  98. package/src/client/conversations/README.md +89 -13
  99. package/src/client/conversations/ShareConversationButton.tsx +28 -17
  100. package/src/client/conversations/SubagentTranscriptDrawer.tsx +21 -15
  101. package/src/client/conversations/ToolFrame.tsx +36 -52
  102. package/src/client/conversations/TranscriptActivityGroup.tsx +13 -4
  103. package/src/client/conversations/TranscriptAutomationTriggerView.tsx +5 -4
  104. package/src/client/conversations/TranscriptContextEventView.tsx +25 -11
  105. package/src/client/conversations/TranscriptEventView.tsx +35 -16
  106. package/src/client/conversations/TranscriptHeadingRow.tsx +7 -7
  107. package/src/client/conversations/TranscriptLoading.tsx +1 -1
  108. package/src/client/conversations/TranscriptMarkdown.tsx +25 -20
  109. package/src/client/conversations/TranscriptMessageContextView.tsx +35 -18
  110. package/src/client/conversations/TranscriptMessageView.tsx +6 -1
  111. package/src/client/conversations/TranscriptReasoningView.tsx +54 -68
  112. package/src/client/conversations/TranscriptStructuredEventView.tsx +25 -15
  113. package/src/client/conversations/TranscriptSummary.tsx +34 -2
  114. package/src/client/conversations/TranscriptTimestamp.tsx +9 -7
  115. package/src/client/conversations/TranscriptToolView.tsx +1 -1
  116. package/src/client/conversations/TranscriptTurnContextView.tsx +144 -65
  117. package/src/client/conversations/TranscriptView.tsx +63 -27
  118. package/src/client/conversations/eventLog.ts +58 -0
  119. package/src/client/conversations/eventTime.ts +60 -0
  120. package/src/client/conversations/queries.ts +29 -0
  121. package/src/client/conversations/transcriptBottomPinning.ts +34 -0
  122. package/src/client/conversations/transcriptMentions.tsx +18 -5
  123. package/src/client/format.ts +5 -0
  124. package/src/client/pages/ConnectionsPage.tsx +2 -2
  125. package/src/client/pages/PersonalTokensPage.tsx +4 -4
  126. package/src/client/pages/SettingsPage.tsx +9 -10
  127. package/src/client/pages/admin/ServiceAccountsSection.tsx +2 -2
  128. package/src/client/pages/automations/AutomationCostChart.tsx +50 -71
  129. package/src/client/pages/automations/AutomationDeleteDialog.tsx +87 -0
  130. package/src/client/pages/automations/AutomationDetails.tsx +193 -0
  131. package/src/client/pages/automations/AutomationEditPage.tsx +7 -8
  132. package/src/client/pages/automations/AutomationEditor.tsx +41 -29
  133. package/src/client/pages/automations/AutomationEditorLoading.tsx +1 -1
  134. package/src/client/pages/automations/AutomationEventFields.tsx +202 -119
  135. package/src/client/pages/automations/AutomationExecutionChart.tsx +51 -64
  136. package/src/client/pages/automations/AutomationExecutionStatusChart.tsx +47 -70
  137. package/src/client/pages/automations/AutomationExecutionsPage.tsx +150 -126
  138. package/src/client/pages/automations/AutomationFilters.tsx +189 -74
  139. package/src/client/pages/automations/AutomationLifecycle.tsx +109 -78
  140. package/src/client/pages/automations/AutomationOutcomeFields.tsx +26 -24
  141. package/src/client/pages/automations/AutomationRecentRuns.tsx +143 -0
  142. package/src/client/pages/automations/AutomationRow.tsx +12 -138
  143. package/src/client/pages/automations/AutomationRunDetails.tsx +307 -0
  144. package/src/client/pages/automations/AutomationRunsPage.tsx +119 -94
  145. package/src/client/pages/automations/AutomationScheduleFields.tsx +51 -51
  146. package/src/client/pages/automations/AutomationStatus.tsx +201 -0
  147. package/src/client/pages/automations/AutomationVersionsPage.tsx +65 -28
  148. package/src/client/pages/automations/AutomationsPage.tsx +80 -68
  149. package/src/client/pages/automations/AutomationsPageLayout.tsx +46 -14
  150. package/src/client/pages/automations/README.md +57 -0
  151. package/src/client/pages/automations/TriggerFilters.tsx +22 -0
  152. package/src/client/pages/automations/automationFormat.ts +37 -23
  153. package/src/client/pages/code/CodeActivityChart.tsx +59 -101
  154. package/src/client/pages/code/CodePage.tsx +52 -55
  155. package/src/client/pages/code/RepositoryPage.tsx +6 -8
  156. package/src/client/pages/locations/LocationActivityChart.tsx +53 -70
  157. package/src/client/pages/locations/LocationDirectoryActivityChart.tsx +60 -80
  158. package/src/client/pages/memory/MemoryCostChart.tsx +79 -103
  159. package/src/client/pages/memory/MemoryDetails.tsx +218 -0
  160. package/src/client/pages/memory/MemoryPage.tsx +168 -112
  161. package/src/client/pages/memory/MemoryPageLayout.tsx +33 -9
  162. package/src/client/pages/memory/MemoryTimeline.tsx +46 -69
  163. package/src/client/pages/memory/memoryRecord.ts +1 -1
  164. package/src/client/pages/people/ContributionGrid.tsx +23 -16
  165. package/src/client/pages/people/PeopleActivityChart.tsx +68 -107
  166. package/src/client/pages/people/ProfileCodeChanges.tsx +5 -5
  167. package/src/client/pages/system/BaseSandboxCard.tsx +1 -1
  168. package/src/client/pages/system/ConversationActivityChart.tsx +49 -67
  169. package/src/client/pages/system/GuardianActivity.tsx +1 -2
  170. package/src/client/pages/system/PluginBarChart.tsx +106 -87
  171. package/src/client/pages/system/PluginReportRecordSet.tsx +25 -24
  172. package/src/client/pages/system/README.md +23 -0
  173. package/src/client/pages/system/SystemNavigation.tsx +5 -1
  174. package/src/client/pages/system/WorkspaceDeleteDialog.tsx +111 -0
  175. package/src/client/pages/system/WorkspaceDetails.tsx +200 -43
  176. package/src/client/pages/system/WorkspaceEditor.tsx +59 -61
  177. package/src/client/pages/system/WorkspaceFormPage.tsx +23 -47
  178. package/src/client/pages/system/WorkspaceList.tsx +20 -30
  179. package/src/client/pages/system/WorkspacesPage.tsx +117 -119
  180. package/src/client/pages/system/sandboxSize.ts +8 -0
  181. package/src/client/pages/system/workspaceQueries.ts +53 -0
  182. package/src/client/pages/user/PluginUserPage.tsx +51 -2
  183. package/src/client/pages/user/pluginUserPageData.ts +1 -6
  184. package/src/client/styles.ts +4 -0
  185. package/src/client.tsx +7 -1
  186. package/src/mock-reporting/automation-collection.ts +4 -2
  187. package/src/mock-reporting/fixtures.ts +23 -7
  188. package/src/mock-reporting/routes.ts +12 -0
  189. package/src/mock-reporting/watches.ts +94 -0
  190. package/src/shell.ts +4 -9
  191. package/src/tailwind.css +0 -10
  192. package/src/client/components/Button.tsx +0 -118
  193. package/src/client/components/Field.tsx +0 -53
  194. package/src/client/components/SegmentedTabs.tsx +0 -119
  195. package/src/client/components/Select.tsx +0 -52
  196. package/src/client/components/Skeleton.tsx +0 -17
  197. package/src/client/components/TextInput.tsx +0 -58
  198. package/src/client/components/Tooltip.tsx +0 -216
  199. package/src/client/components/charts/useChartLayout.ts +0 -27
  200. package/src/client/components/ui/sheet.LICENSE +0 -21
  201. package/src/client/conversations/ConversationDetailsFrame.tsx +0 -119
  202. package/src/client/conversations/CopyMarkdownButton.tsx +0 -65
  203. package/src/client/pages/automations/AutomationDetailsDrawer.tsx +0 -219
  204. package/src/client/pages/dev/ChartFixtures.tsx +0 -325
  205. package/src/client/pages/dev/ComponentsPage.tsx +0 -899
  206. package/src/client/pages/dev/ConversationFixture.tsx +0 -231
  207. package/src/client/pages/dev/ObjectFixtures.tsx +0 -138
  208. package/src/client/pages/dev/SegmentedFixtures.tsx +0 -59
  209. package/src/client/pages/dev/SelectFixtures.tsx +0 -62
  210. package/src/client/pages/dev/TranscriptActivityFixtures.tsx +0 -206
  211. package/src/client/pages/memory/MemoryDetailsDrawer.tsx +0 -167
  212. package/src/client/pages/system/SandboxSizeSummary.tsx +0 -47
  213. package/src/client/pages/system/SnapshotSummary.tsx +0 -78
@@ -43,12 +43,17 @@ export function MentionPeopleProvider(props: {
43
43
 
44
44
  /** Render one line of display text with the people the API resolved for it. */
45
45
  export function MentionText(props: {
46
+ /**
47
+ * Link people to their page. Set false inside a button, which cannot hold
48
+ * a link.
49
+ */
50
+ links?: boolean;
46
51
  mentions: readonly ActorIdentity[];
47
52
  text: string;
48
53
  }) {
49
54
  return (
50
55
  <MentionPeopleProvider mentions={props.mentions}>
51
- {renderMentionText(props.text, "mention-text")}
56
+ {renderMentionText(props.text, "mention-text", props.links)}
52
57
  </MentionPeopleProvider>
53
58
  );
54
59
  }
@@ -57,7 +62,11 @@ export function MentionText(props: {
57
62
  * Render text with Slack `<@U123>` and `<#C123|name>` tokens as readable
58
63
  * mentions. It reads people from the nearest `MentionPeopleProvider`.
59
64
  */
60
- export function renderMentionText(text: string, keyBase: string): ReactNode[] {
65
+ export function renderMentionText(
66
+ text: string,
67
+ keyBase: string,
68
+ links = true,
69
+ ): ReactNode[] {
61
70
  const nodes: ReactNode[] = [];
62
71
  let cursor = 0;
63
72
  for (const mention of findMentions(text)) {
@@ -70,7 +79,11 @@ export function renderMentionText(text: string, keyBase: string): ReactNode[] {
70
79
  );
71
80
  }
72
81
  nodes.push(
73
- <MentionView key={`${keyBase}-m-${mention.start}`} mention={mention} />,
82
+ <MentionView
83
+ key={`${keyBase}-m-${mention.start}`}
84
+ links={links}
85
+ mention={mention}
86
+ />,
74
87
  );
75
88
  cursor = mention.end;
76
89
  }
@@ -95,13 +108,13 @@ function findMentions(text: string): TranscriptMention[] {
95
108
  }));
96
109
  }
97
110
 
98
- function MentionView(props: { mention: TranscriptMention }) {
111
+ function MentionView(props: { links: boolean; mention: TranscriptMention }) {
99
112
  const people = useContext(TranscriptMentionPeople);
100
113
  const { id, label, sigil } = props.mention;
101
114
  const person = sigil === "@" ? people.get(id) : undefined;
102
115
  const name = person?.fullName ?? person?.slackUserName ?? label ?? id;
103
116
  const text = <HighlightText text={`${sigil}${name.replace(/^[@#]/, "")}`} />;
104
- if (person?.email) {
117
+ if (props.links && person?.email) {
105
118
  return (
106
119
  <Link
107
120
  // Previews sit in cards that disable pointer events on their text.
@@ -791,6 +791,11 @@ export function locationPath(locationId: string): string {
791
791
  return `/system/locations/${encodeURIComponent(locationId)}`;
792
792
  }
793
793
 
794
+ /** Build the Workspace list route that opens one Workspace's details. */
795
+ export function workspacePath(workspaceId: string): string {
796
+ return `/system/workspaces/${encodeURIComponent(workspaceId)}`;
797
+ }
798
+
794
799
  /** Build the canonical automation detail route for an automation id. */
795
800
  export function automationPath(automationId: string): string {
796
801
  return `/automations/${encodeURIComponent(automationId)}`;
@@ -11,7 +11,7 @@ import { del, fetchDashboardJson, post } from "../http";
11
11
  import { dashboardContainerClass } from "../styles";
12
12
  import { getDashboardAgentName } from "../agentName";
13
13
  import { useDocumentTitle } from "../documentTitle";
14
- import { Button } from "../components/Button";
14
+ import { Button } from "../components/ui/button";
15
15
  import { InlineError } from "../components/InlineError";
16
16
  import { Card } from "../components/layout/Card";
17
17
 
@@ -168,7 +168,7 @@ export function ConnectionsPage() {
168
168
  connectMutation.reset();
169
169
  disconnectMutation.mutate(connection);
170
170
  }}
171
- tone="danger"
171
+ variant="danger"
172
172
  >
173
173
  {pendingProvider === connection.provider
174
174
  ? "Disconnecting…"
@@ -10,10 +10,10 @@ import { deleteDashboardResource, fetchDashboardJson, post } from "../http";
10
10
  import { dashboardContainerClass } from "../styles";
11
11
  import { getDashboardAgentName } from "../agentName";
12
12
  import { useDocumentTitle } from "../documentTitle";
13
- import { Button } from "../components/Button";
13
+ import { Button } from "../components/ui/button";
14
+ import { Input } from "../components/ui/input";
14
15
  import { InlineError } from "../components/InlineError";
15
16
  import { Card } from "../components/layout/Card";
16
- import { TextInput } from "../components/TextInput";
17
17
 
18
18
  const personalTokensQueryKey = ["dashboard", "personal-tokens"] as const;
19
19
 
@@ -129,7 +129,7 @@ export function PersonalTokensPage() {
129
129
  </div>
130
130
  ) : (
131
131
  <div className="mt-5 flex gap-2">
132
- <TextInput
132
+ <Input
133
133
  aria-label="Token name"
134
134
  className="min-w-0 flex-1"
135
135
  maxLength={80}
@@ -189,7 +189,7 @@ export function PersonalTokensPage() {
189
189
  revokeTokenMutation.mutate(token);
190
190
  }}
191
191
  size="icon"
192
- tone="danger"
192
+ variant="danger"
193
193
  type="button"
194
194
  >
195
195
  <Trash2 size={16} />
@@ -5,11 +5,11 @@ import {
5
5
  dashboardIdentitySchema,
6
6
  type DashboardIdentity,
7
7
  } from "../../api/schema";
8
- import { Button } from "../components/Button";
9
- import { Field } from "../components/Field";
8
+ import { Button } from "../components/ui/button";
9
+ import { Input } from "../components/ui/input";
10
+ import { Field, FieldDescription, FieldLabel } from "../components/ui/field";
10
11
  import { InlineError } from "../components/InlineError";
11
12
  import { Card } from "../components/layout/Card";
12
- import { TextInput } from "../components/TextInput";
13
13
  import { useDocumentTitle } from "../documentTitle";
14
14
  import { patch } from "../http";
15
15
  import { dashboardContainerClass } from "../styles";
@@ -64,13 +64,9 @@ export function SettingsPage({ identity }: SettingsPageProps) {
64
64
  <form className="mt-6" onSubmit={submit}>
65
65
  <Card className="mb-0" padding="md" variant="raised">
66
66
  <h2 className="m-0 text-lg font-bold">Profile</h2>
67
- <Field
68
- className="mt-5"
69
- help="Your display name is shown with your conversations and activity."
70
- htmlFor="display-name"
71
- label="Display name"
72
- >
73
- <TextInput
67
+ <Field className="mt-5">
68
+ <FieldLabel htmlFor="display-name">Display name</FieldLabel>
69
+ <Input
74
70
  autoComplete="name"
75
71
  id="display-name"
76
72
  maxLength={80}
@@ -82,6 +78,9 @@ export function SettingsPage({ identity }: SettingsPageProps) {
82
78
  }}
83
79
  value={displayName}
84
80
  />
81
+ <FieldDescription id="display-name-help">
82
+ Your display name is shown with your conversations and activity.
83
+ </FieldDescription>
85
84
  </Field>
86
85
  <div className="mt-5 flex items-center gap-3">
87
86
  <Button disabled={!canSave} type="submit">
@@ -13,7 +13,7 @@ import {
13
13
  type serviceAccountSchema,
14
14
  } from "@sentry/junior/api/schema";
15
15
  import { getDashboardAgentName } from "../../agentName";
16
- import { Button } from "../../components/Button";
16
+ import { Button } from "../../components/ui/button";
17
17
  import { InlineError } from "../../components/InlineError";
18
18
  import { StatusChip } from "../../components/StatusChip";
19
19
  import { Card } from "../../components/layout/Card";
@@ -192,7 +192,7 @@ export function ServiceAccountsSection(props: {
192
192
  connectMutation.reset();
193
193
  disconnectMutation.mutate(account);
194
194
  }}
195
- tone="danger"
195
+ variant="danger"
196
196
  >
197
197
  Disconnect
198
198
  </Button>
@@ -4,21 +4,27 @@ 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 { formatCostSummary } from "../../format";
20
20
 
21
- const COST_COLOR = "var(--color-cyan-300)";
21
+ const chartConfig = {
22
+ costUsd: { color: "var(--color-cyan-300)", label: "Spend" },
23
+ };
24
+
25
+ function formatSpend(value: number): string {
26
+ return formatCostSummary({ total: value });
27
+ }
22
28
 
23
29
  /** Render linked conversation spend for completed automation executions. */
24
30
  export function AutomationCostChart(props: {
@@ -28,11 +34,6 @@ export function AutomationCostChart(props: {
28
34
  }) {
29
35
  const bucketUnit = props.bucketUnit ?? "day";
30
36
  const days = props.days;
31
- // Cost charts need a wider left gutter for currency tick labels.
32
- const layout = createActivityChartLayout(200, { left: 112 });
33
- const step =
34
- days.length > 0 ? layout.plotWidth / days.length : layout.plotWidth;
35
- const barWidth = Math.max(2, Math.min(13, step * 0.68));
36
37
  const totals = days.map((day) => day.costUsd);
37
38
  const total = totals.reduce((sum, value) => sum + value, 0);
38
39
  const maximum = Math.max(0.01, ...totals);
@@ -51,66 +52,44 @@ export function AutomationCostChart(props: {
51
52
  </div>
52
53
 
53
54
  <div className="relative mt-5 overflow-hidden">
54
- <ChartSvg
55
- aria-label={`Automation execution spend 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
60
- format={(value) => formatCostSummary({ total: value })}
61
- layout={layout}
62
- maximum={maximum}
63
- />
64
- {days.map((day, dayIndex) => {
65
- const x = layout.left + dayIndex * step + (step - barWidth) / 2;
66
- const value = totals[dayIndex] ?? 0;
67
- const height = (value / maximum) * layout.plotHeight;
68
- return (
69
- <ActivityChartTooltip
70
- key={day.date}
71
- content={
72
- <ActivityTooltipRows
73
- rows={[["spend", formatCostSummary({ total: value })]]}
74
- />
75
- }
76
- date={day.date}
77
- summary={`${formatCostSummary({ total: value })}`}
78
- >
79
- <g tabIndex={0}>
80
- <rect
81
- fill={COST_COLOR}
82
- height={height}
83
- opacity={value > 0 ? 0.82 : 0.1}
84
- rx="1"
85
- width={barWidth}
86
- x={x}
87
- y={layout.top + layout.plotHeight - height}
88
- />
89
- <rect
90
- fill="transparent"
91
- height={layout.plotHeight}
92
- width={Math.max(barWidth, 8)}
93
- x={x - (Math.max(barWidth, 8) - barWidth) / 2}
94
- y={layout.top}
95
- />
96
- </g>
97
- </ActivityChartTooltip>
98
- );
99
- })}
100
- <ActivityChartAverageLine
101
- unit={timeRangeBucketAverageUnit(bucketUnit)}
102
- average={average}
103
- format={(value) => formatCostSummary({ total: value })}
104
- layout={layout}
105
- maximum={maximum}
106
- stroke="var(--color-slate-200)"
107
- />
108
- <ActivityChartDateLabels
109
- dates={days.map((day) => day.date)}
110
- layout={layout}
111
- xPosition={(index) => layout.left + index * step + step / 2}
112
- />
113
- </ChartSvg>
59
+ <BarChart
60
+ aria-label={`Automation execution spend 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 format={formatSpend} maximum={maximum} />
67
+ <ActivityChartXAxis keys={days.map((day) => day.date)} />
68
+ <ChartTooltip
69
+ content={
70
+ <ActivityChartTooltipContent
71
+ rows={(day: AutomationExecutionDay) => [
72
+ ["spend", formatSpend(day.costUsd)],
73
+ ]}
74
+ />
75
+ }
76
+ />
77
+ <Bar
78
+ dataKey="costUsd"
79
+ fill="var(--color-costUsd)"
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={formatSpend}
89
+ stroke="var(--color-slate-200)"
90
+ />
91
+ </BarChart>
92
+ </ChartContainer>
114
93
  {!hasCost ? (
115
94
  <div className="pointer-events-none absolute inset-0 grid place-items-center pt-12 font-sans text-xs text-dashboard-text-muted">
116
95
  No linked spend in this period.
@@ -0,0 +1,87 @@
1
+ import { useState } from "react";
2
+ import { useMutation, useQueryClient } from "@tanstack/react-query";
3
+ import type { AutomationSummary } from "@sentry/junior/api/schema";
4
+ import {
5
+ AlertDialog,
6
+ AlertDialogContent,
7
+ AlertDialogTitle,
8
+ AlertDialogDescription,
9
+ AlertDialogFooter,
10
+ AlertDialogCancel,
11
+ AlertDialogAction,
12
+ } from "../../components/ui/alert-dialog";
13
+ import { FormNotice } from "../../components/FormNotice";
14
+ import { useToastManager } from "../../components/ui/toast";
15
+ import { deleteDashboardResource } from "../../http";
16
+
17
+ /** Confirm deletion by name and keep a failed request available for retry. */
18
+ export function AutomationDeleteDialog(props: {
19
+ automation: AutomationSummary;
20
+ onClose(): void;
21
+ onDeleted(): void;
22
+ returnFocus(): HTMLElement | false;
23
+ }) {
24
+ const [open, setOpen] = useState(true);
25
+ const queryClient = useQueryClient();
26
+ const toast = useToastManager();
27
+ const deletion = useMutation({
28
+ mutationFn: () =>
29
+ deleteDashboardResource(
30
+ `/api/automations/${encodeURIComponent(props.automation.id)}`,
31
+ ),
32
+ onSuccess: async () => {
33
+ await queryClient.invalidateQueries({
34
+ queryKey: ["dashboard", "automations"],
35
+ });
36
+ props.onDeleted();
37
+ toast.add({
38
+ title: "Automation deleted",
39
+ description: props.automation.title,
40
+ type: "success",
41
+ });
42
+ setOpen(false);
43
+ },
44
+ });
45
+ return (
46
+ <AlertDialog
47
+ open={open}
48
+ onOpenChange={(next, details) => {
49
+ if (deletion.isPending) details.cancel();
50
+ else setOpen(next);
51
+ }}
52
+ onOpenChangeComplete={(next) => {
53
+ if (!next) props.onClose();
54
+ }}
55
+ >
56
+ <AlertDialogContent finalFocus={props.returnFocus}>
57
+ <AlertDialogTitle>Delete automation?</AlertDialogTitle>
58
+ <AlertDialogDescription>
59
+ Delete{" "}
60
+ <strong className="font-medium text-dashboard-text">
61
+ {props.automation.title}
62
+ </strong>
63
+ ? This removes the Automation and stops future triggers. Work already
64
+ queued or running may finish.
65
+ </AlertDialogDescription>
66
+ {deletion.error ? (
67
+ <FormNotice
68
+ tone="error"
69
+ title="The automation could not be deleted. Try again."
70
+ />
71
+ ) : null}
72
+ <AlertDialogFooter>
73
+ <AlertDialogCancel disabled={deletion.isPending}>
74
+ Cancel
75
+ </AlertDialogCancel>
76
+ <AlertDialogAction
77
+ variant="danger"
78
+ disabled={deletion.isPending}
79
+ onClick={() => deletion.mutate()}
80
+ >
81
+ {deletion.isPending ? "Deleting…" : "Delete automation"}
82
+ </AlertDialogAction>
83
+ </AlertDialogFooter>
84
+ </AlertDialogContent>
85
+ </AlertDialog>
86
+ );
87
+ }
@@ -0,0 +1,193 @@
1
+ import type { AutomationSummary } from "@sentry/junior/api/schema";
2
+ import type { ReactNode } from "react";
3
+ import { Link, useLocation } from "react-router";
4
+
5
+ import {
6
+ Accordion,
7
+ AccordionContent,
8
+ AccordionItem,
9
+ AccordionTrigger,
10
+ } from "../../components/ui/accordion";
11
+ import { Button } from "../../components/ui/button";
12
+ import { DetailsPanel } from "../../components/DetailsPanel";
13
+ import { InlineError } from "../../components/InlineError";
14
+ import { PageContentSkeleton } from "../../components/PageContentSkeleton";
15
+ import {
16
+ timeRangeLabel,
17
+ type TimeRangeDays,
18
+ } from "../../components/controls/TimeRangeSelector";
19
+ import { TranscriptText } from "../../conversations/TranscriptText";
20
+ import { peoplePath } from "../../format";
21
+ import { dashboardInlineLinkClass } from "../../styles";
22
+ import { TriggerFilters } from "./TriggerFilters";
23
+ import { AutomationLifecycle } from "./AutomationLifecycle";
24
+ import { AutomationRecentRuns } from "./AutomationRecentRuns";
25
+ import { AutomationStatus } from "./AutomationStatus";
26
+ import {
27
+ automationScheduleLabel,
28
+ formatAutomationRunTime,
29
+ } from "./automationFormat";
30
+ import { automationOutcomeLabel } from "./automationOutcomes";
31
+
32
+ /** Keep Automation context scrollable above persistent, permission-aware actions. */
33
+ export function AutomationDetails(props: {
34
+ onClose(): void;
35
+ onDelete(source: HTMLButtonElement): void;
36
+ onRetry(): void;
37
+ error: boolean;
38
+ range: TimeRangeDays;
39
+ automation: AutomationSummary | undefined;
40
+ }) {
41
+ const location = useLocation();
42
+ const { automation } = props;
43
+ return (
44
+ <DetailsPanel
45
+ label="Automation details"
46
+ sheetTitle={automation?.title}
47
+ onClose={props.onClose}
48
+ title={
49
+ <h2 className="m-0 break-words font-display text-base font-medium text-dashboard-text">
50
+ {automation?.title ?? "Automation details"}
51
+ </h2>
52
+ }
53
+ >
54
+ <div className="grid h-full min-h-0 grid-rows-[minmax(0,1fr)_auto]">
55
+ <div
56
+ aria-label="Automation content"
57
+ role="region"
58
+ tabIndex={0}
59
+ className="min-h-0 overflow-y-auto overscroll-contain p-4 outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-dashboard-focus [overflow-wrap:anywhere]"
60
+ >
61
+ {automation ? (
62
+ <AutomationDetailsContent
63
+ automation={automation}
64
+ range={props.range}
65
+ />
66
+ ) : null}
67
+ {props.error ? (
68
+ <InlineError>
69
+ Automation details could not be loaded. It may be unavailable or
70
+ you may not have access.{" "}
71
+ <Button onClick={props.onRetry}>Try again</Button>
72
+ </InlineError>
73
+ ) : !automation ? (
74
+ <PageContentSkeleton
75
+ label="Loading automation details"
76
+ variant="panel"
77
+ />
78
+ ) : null}
79
+ </div>
80
+ {automation ? (
81
+ <AutomationLifecycle
82
+ automation={automation}
83
+ onDelete={props.onDelete}
84
+ editPath={`/automations/${encodeURIComponent(automation.id)}/edit${location.search}`}
85
+ />
86
+ ) : null}
87
+ </div>
88
+ </DetailsPanel>
89
+ );
90
+ }
91
+
92
+ function AutomationDetailsContent({
93
+ automation,
94
+ range,
95
+ }: {
96
+ automation: AutomationSummary;
97
+ range: TimeRangeDays;
98
+ }) {
99
+ const location = useLocation();
100
+ return (
101
+ <div className="grid min-w-0 gap-5">
102
+ <AutomationStatus automation={automation} />
103
+ <section aria-label="Instructions" className="text-sm leading-relaxed">
104
+ <TranscriptText
105
+ mentions={automation.mentions ?? []}
106
+ text={automation.instruction}
107
+ />
108
+ </section>
109
+ <dl className="m-0 grid gap-3 text-xs leading-relaxed">
110
+ <AutomationFact label="Trigger">
111
+ {automation.kind === "scheduled" ? (
112
+ <p className="m-0">{automationScheduleLabel(automation)}</p>
113
+ ) : (
114
+ <>
115
+ <p className="m-0 break-words">{automation.resource}</p>
116
+ <p className="m-0 mt-1 text-dashboard-text-muted">
117
+ {automation.trigger.events.join(", ")}
118
+ </p>
119
+ <TriggerFilters filters={automation.trigger.filters} />
120
+ </>
121
+ )}
122
+ </AutomationFact>
123
+ <AutomationFact label="On success">
124
+ {automation.outcomes.length === 0 ? (
125
+ <span className="text-dashboard-text-muted">No message</span>
126
+ ) : (
127
+ <ol className="m-0 grid list-none gap-2 p-0">
128
+ {automation.outcomes.map((outcome, index) => (
129
+ <li key={index}>
130
+ Message{" "}
131
+ {automationOutcomeLabel(outcome, automation.destination)}
132
+ </li>
133
+ ))}
134
+ </ol>
135
+ )}
136
+ </AutomationFact>
137
+ </dl>
138
+ <AutomationRecentRuns automation={automation} />
139
+ <Accordion className="border-t border-dashboard-border pt-1">
140
+ <AccordionItem value="about">
141
+ <AccordionTrigger className="min-h-11 text-xs text-dashboard-text-muted">
142
+ About this Automation
143
+ </AccordionTrigger>
144
+ <AccordionContent>
145
+ <dl className="m-0 grid gap-3 text-xs leading-relaxed">
146
+ <AutomationFact label="Created by">
147
+ {automation.createdByEmail ? (
148
+ <Link
149
+ className={dashboardInlineLinkClass}
150
+ to={peoplePath(automation.createdByEmail)}
151
+ >
152
+ {automation.ownedByViewer ? "you" : automation.createdBy}
153
+ </Link>
154
+ ) : automation.ownedByViewer ? (
155
+ "you"
156
+ ) : (
157
+ automation.createdBy
158
+ )}
159
+ </AutomationFact>
160
+ <AutomationFact label="Created">
161
+ {formatAutomationRunTime(automation.createdAt)}
162
+ </AutomationFact>
163
+ <AutomationFact label="Accounts">
164
+ {automation.credentialMode === "creator"
165
+ ? `Uses accounts connected by ${automation.ownedByViewer ? "you" : automation.createdBy}.`
166
+ : "Junior’s accounts only"}
167
+ </AutomationFact>
168
+ <AutomationFact label="Runs">
169
+ {automation.runs[range]} runs / {timeRangeLabel(range)} ·{" "}
170
+ {automation.totalRuns} total
171
+ </AutomationFact>
172
+ </dl>
173
+ <Link
174
+ className={`${dashboardInlineLinkClass} mt-3 inline-block py-2 text-xs`}
175
+ to={`/automations/${encodeURIComponent(automation.id)}/versions${location.search}`}
176
+ >
177
+ Version history
178
+ </Link>
179
+ </AccordionContent>
180
+ </AccordionItem>
181
+ </Accordion>
182
+ </div>
183
+ );
184
+ }
185
+
186
+ function AutomationFact(props: { label: string; children: ReactNode }) {
187
+ return (
188
+ <div className="grid min-w-0 grid-cols-[5.25rem_minmax(0,1fr)] gap-3">
189
+ <dt className="text-dashboard-text-muted">{props.label}</dt>
190
+ <dd className="m-0 min-w-0">{props.children}</dd>
191
+ </div>
192
+ );
193
+ }
@@ -8,10 +8,11 @@ import {
8
8
  import { useAutomationData } from "../../api";
9
9
  import { useDocumentTitle } from "../../documentTitle";
10
10
  import { fetchDashboardJson } from "../../http";
11
- import { Button } from "../../components/Button";
11
+ import { Button } from "../../components/ui/button";
12
12
  import { FormNotice } from "../../components/FormNotice";
13
13
  import { StatusChip } from "../../components/StatusChip";
14
14
  import { AutomationsNavigation } from "./AutomationsPageLayout";
15
+ import { TriggerFilters } from "./TriggerFilters";
15
16
  import { AutomationEditor } from "./AutomationEditor";
16
17
  import { AutomationEditorLoading } from "./AutomationEditorLoading";
17
18
  import { AutomationFormSection } from "./AutomationFormSection";
@@ -49,7 +50,7 @@ export function AutomationEditPage(props: { enabled: boolean }) {
49
50
  value?.kind === "event"
50
51
  ? value.status !== "active"
51
52
  ? value.status
52
- : value.triggerAvailable
53
+ : value.trigger.problems.length === 0
53
54
  ? "active"
54
55
  : "unavailable"
55
56
  : value?.status;
@@ -144,12 +145,10 @@ function AutomationReadOnly({ automation }: { automation: AutomationSummary }) {
144
145
  ) : (
145
146
  <div className="grid gap-3 text-sm">
146
147
  <p className="m-0 break-words">{automation.resource}</p>
147
- <p className="m-0 break-words">{automation.events.join(", ")}</p>
148
- {automation.match ? (
149
- <pre className="whitespace-pre-wrap break-words text-xs">
150
- {JSON.stringify(automation.match, null, 2)}
151
- </pre>
152
- ) : null}
148
+ <p className="m-0 break-words">
149
+ {automation.trigger.events.join(", ")}
150
+ </p>
151
+ <TriggerFilters filters={automation.trigger.filters} />
153
152
  </div>
154
153
  )}
155
154
  </AutomationFormSection>