@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
@@ -12,23 +12,30 @@ import {
12
12
  TooltipTrigger,
13
13
  } from "../../components/ui/tooltip";
14
14
  import { Link } from "react-router";
15
- import { CircleHelp, Pencil, Settings2, Trash2 } from "lucide-react";
15
+ import { CircleHelp, Pencil, Trash2 } from "lucide-react";
16
16
  import {
17
17
  automationEditSchema,
18
18
  type AutomationSummary,
19
19
  } from "@sentry/junior/api/schema";
20
- import { Button } from "../../components/ui/button";
20
+ import { Button, buttonVariants } from "../../components/ui/button";
21
21
  import { FormNotice } from "../../components/FormNotice";
22
22
  import { DashboardApiError, fetchDashboardJson, post } from "../../http";
23
23
 
24
- /** Keep revision-checked lifecycle actions available in the fixed details footer. */
25
- export function AutomationLifecycle({
24
+ /**
25
+ * Header actions for one Automation: Edit, then creator-only Pause or Resume
26
+ * and Delete. Public readers can edit settings, but only the creator controls
27
+ * lifecycle. Lifecycle actions re-read the revision before they apply.
28
+ */
29
+ export function AutomationActions({
26
30
  automation,
27
31
  editPath,
32
+ editVariant,
28
33
  onDelete,
29
34
  }: {
30
35
  automation: AutomationSummary;
31
36
  editPath: string;
37
+ /** Use "default" when a notice on the page owns the main action. */
38
+ editVariant: "default" | "primary";
32
39
  onDelete(source: HTMLButtonElement): void;
33
40
  }) {
34
41
  const toast = useToastManager();
@@ -36,8 +43,6 @@ export function AutomationLifecycle({
36
43
  const completed = automation.status === "completed";
37
44
  const paused = automation.status === "paused";
38
45
  const blocked = automation.status === "blocked";
39
- // Public readers can edit settings, but only the creator controls lifecycle.
40
- const canEdit = !completed;
41
46
  const canPause = automation.ownedByViewer && !completed;
42
47
  const action = paused || blocked ? "resume" : "pause";
43
48
  const mutation = useMutation({
@@ -50,7 +55,7 @@ export function AutomationLifecycle({
50
55
  // Do not apply the opposite action if another surface changed lifecycle.
51
56
  if (current.status !== automation.status) {
52
57
  throw new Error(
53
- "The Automation changed. Close and reopen its details before trying again.",
58
+ "The Automation changed. Reload the page before trying again.",
54
59
  );
55
60
  }
56
61
  return post(automationEditSchema, `${path}/lifecycle`, {
@@ -80,57 +85,24 @@ export function AutomationLifecycle({
80
85
  },
81
86
  });
82
87
  return (
83
- <footer
84
- aria-label="Automation actions"
85
- className="grid gap-3 border-t border-dashboard-border bg-dashboard-bg-elevated p-3 pb-[max(0.75rem,env(safe-area-inset-bottom))]"
86
- >
87
- <div className="flex items-center gap-2">
88
- <div className="mr-auto flex items-center gap-1">
89
- <Tooltip>
90
- <TooltipTrigger
91
- render={
92
- <Link
93
- aria-label={canEdit ? "Edit automation" : "View settings"}
94
- className="grid size-11 place-items-center rounded-md text-dashboard-text-muted no-underline hover:bg-dashboard-fill-strong hover:text-dashboard-text focus-visible:outline-2 focus-visible:outline-dashboard-focus"
95
- to={editPath}
96
- >
97
- {canEdit ? (
98
- <Pencil aria-hidden="true" size={16} />
99
- ) : (
100
- <Settings2 aria-hidden="true" size={16} />
101
- )}
102
- </Link>
103
- }
104
- />
105
- <TooltipContent size="compact">
106
- {canEdit ? "Edit automation" : "View settings"}
107
- </TooltipContent>
108
- </Tooltip>
109
- {automation.ownedByViewer ? (
110
- <Tooltip>
111
- <TooltipTrigger
112
- render={
113
- <Button
114
- focusableWhenDisabled
115
- aria-label="Delete automation"
116
- className="min-h-11 min-w-11"
117
- size="icon"
118
- variant="danger"
119
- onClick={(event) => onDelete(event.currentTarget)}
120
- disabled={mutation.isPending}
121
- >
122
- <Trash2 aria-hidden="true" size={16} />
123
- </Button>
124
- }
125
- />
126
- <TooltipContent size="compact">Delete automation</TooltipContent>
127
- </Tooltip>
128
- ) : null}
129
- </div>
88
+ <div className="grid min-w-0 justify-items-stretch gap-3 md:justify-items-end">
89
+ <div
90
+ aria-label="Automation actions"
91
+ className="flex flex-wrap items-center gap-2"
92
+ role="group"
93
+ >
94
+ {completed ? null : (
95
+ <Link
96
+ className={buttonVariants({ variant: editVariant })}
97
+ to={editPath}
98
+ >
99
+ <Pencil aria-hidden="true" size={14} />
100
+ Edit automation
101
+ </Link>
102
+ )}
130
103
  {canPause ? (
131
104
  <>
132
105
  <Button
133
- className="min-h-11"
134
106
  disabled={mutation.isPending}
135
107
  onClick={() => mutation.mutate()}
136
108
  >
@@ -142,17 +114,15 @@ export function AutomationLifecycle({
142
114
  </Button>
143
115
  <Popover>
144
116
  <PopoverTrigger
145
- render={
146
- <Button
147
- aria-label="About pausing"
148
- className="min-h-11 min-w-11"
149
- size="icon"
150
- />
151
- }
117
+ render={<Button aria-label="About pausing" size="icon" />}
152
118
  >
153
119
  <CircleHelp aria-hidden="true" size={16} />
154
120
  </PopoverTrigger>
155
- <PopoverContent side="top" align="end" aria-label="About pausing">
121
+ <PopoverContent
122
+ side="bottom"
123
+ align="end"
124
+ aria-label="About pausing"
125
+ >
156
126
  <PopoverDescription>
157
127
  Pausing stops new runs. Work already queued or running may
158
128
  finish. Resuming skips missed runs and waits for the next
@@ -165,14 +135,35 @@ export function AutomationLifecycle({
165
135
  </Popover>
166
136
  </>
167
137
  ) : null}
138
+ {automation.ownedByViewer ? (
139
+ <Tooltip>
140
+ <TooltipTrigger
141
+ render={
142
+ <Button
143
+ focusableWhenDisabled
144
+ aria-label="Delete automation"
145
+ size="icon"
146
+ variant="danger"
147
+ onClick={(event) => onDelete(event.currentTarget)}
148
+ disabled={mutation.isPending}
149
+ >
150
+ <Trash2 aria-hidden="true" size={16} />
151
+ </Button>
152
+ }
153
+ />
154
+ <TooltipContent size="compact">Delete automation</TooltipContent>
155
+ </Tooltip>
156
+ ) : null}
168
157
  </div>
169
158
  {mutation.error ? (
170
- <FormNotice tone="error" title="Could not update this automation.">
171
- {mutation.error instanceof DashboardApiError
172
- ? (mutation.error.apiError ?? "Try again.")
173
- : mutation.error.message}
174
- </FormNotice>
159
+ <div className="max-w-sm">
160
+ <FormNotice tone="error" title="Could not update this automation.">
161
+ {mutation.error instanceof DashboardApiError
162
+ ? (mutation.error.apiError ?? "Try again.")
163
+ : mutation.error.message}
164
+ </FormNotice>
165
+ </div>
175
166
  ) : null}
176
- </footer>
167
+ </div>
177
168
  );
178
169
  }
@@ -0,0 +1,171 @@
1
+ import type {
2
+ AutomationEdit,
3
+ AutomationSummary,
4
+ } from "@sentry/junior/api/schema";
5
+ import { Button } from "../../components/ui/button";
6
+ import { Card } from "../../components/layout/Card";
7
+ import { cn } from "../../styles";
8
+ import { automationOutcomeLabel } from "./automationOutcomes";
9
+ import {
10
+ createAutomationDraft,
11
+ scheduleDraft,
12
+ type AutomationDraft,
13
+ type AutomationScheduleDraft,
14
+ } from "./automationDraft";
15
+
16
+ /** In form order, so the list reads top to bottom like the form. */
17
+ const CHANGE_LABELS: Record<keyof AutomationDraft, string> = {
18
+ title: "Title",
19
+ instruction: "Instruction",
20
+ schedule: "Schedule",
21
+ trigger: "Trigger",
22
+ outcomes: "Where results go",
23
+ credentialMode: "Credentials",
24
+ };
25
+
26
+ /**
27
+ * List unsaved fields with the saved value, the new value, and an Undo.
28
+ * It sits beside the form on wide screens and below it on small screens.
29
+ */
30
+ export function AutomationDraftChanges(props: {
31
+ changes: Partial<AutomationDraft>;
32
+ disabled: boolean;
33
+ onUndo(key: keyof AutomationDraft): void;
34
+ original: AutomationEdit;
35
+ summary: AutomationSummary;
36
+ }) {
37
+ const keys = (Object.keys(CHANGE_LABELS) as (keyof AutomationDraft)[]).filter(
38
+ (key) => props.changes[key] !== undefined,
39
+ );
40
+ const saved = savedDraft(props.original);
41
+ const label = (draft: Partial<AutomationDraft>, key: keyof AutomationDraft) =>
42
+ draftValueLabel(draft, key, props.summary);
43
+ return (
44
+ <aside
45
+ aria-label="Your changes"
46
+ className={cn(
47
+ "min-w-0 lg:sticky lg:top-4 lg:self-start",
48
+ keys.length === 0 && "max-lg:hidden",
49
+ )}
50
+ >
51
+ <Card padding="sm">
52
+ <h2 className="m-0 mb-3 font-sans text-xs font-medium text-dashboard-text-muted">
53
+ Your changes
54
+ </h2>
55
+ {keys.length === 0 ? (
56
+ <p className="m-0 text-sm text-dashboard-text-muted">
57
+ No changes yet. Edit a section to see it here.
58
+ </p>
59
+ ) : (
60
+ <ul className="m-0 grid list-none gap-3 p-0">
61
+ {keys.map((key) => (
62
+ <li className="grid min-w-0 gap-1 text-sm" key={key}>
63
+ <div className="flex items-center justify-between gap-3">
64
+ <span className="font-medium">{CHANGE_LABELS[key]}</span>
65
+ <Button
66
+ aria-label={`Undo ${CHANGE_LABELS[key].toLowerCase()} change`}
67
+ disabled={props.disabled}
68
+ onClick={() => props.onUndo(key)}
69
+ >
70
+ Undo
71
+ </Button>
72
+ </div>
73
+ <p className="m-0 line-clamp-3 break-words whitespace-pre-wrap text-xs text-dashboard-text-muted line-through decoration-dashboard-text-muted/50">
74
+ <span className="sr-only">Saved: </span>
75
+ {label(saved, key)}
76
+ </p>
77
+ <p className="m-0 line-clamp-3 break-words whitespace-pre-wrap text-xs">
78
+ <span className="sr-only">New: </span>
79
+ {label(props.changes, key)}
80
+ </p>
81
+ </li>
82
+ ))}
83
+ </ul>
84
+ )}
85
+ <p className="mt-3 mb-0 text-xs leading-relaxed text-dashboard-text-muted">
86
+ Saving creates a new version. It does not run this automation.
87
+ </p>
88
+ </Card>
89
+ </aside>
90
+ );
91
+ }
92
+
93
+ /** The saved values in draft form, including the saved schedule. */
94
+ function savedDraft(original: AutomationEdit): AutomationDraft {
95
+ const draft = createAutomationDraft(original);
96
+ return original.kind === "scheduled"
97
+ ? { ...draft, schedule: scheduleDraft(original) }
98
+ : draft;
99
+ }
100
+
101
+ function draftValueLabel(
102
+ draft: Partial<AutomationDraft>,
103
+ key: keyof AutomationDraft,
104
+ summary: AutomationSummary,
105
+ ): string {
106
+ switch (key) {
107
+ case "title":
108
+ return draft.title || "First line of the instruction";
109
+ case "instruction":
110
+ return draft.instruction || "Empty";
111
+ case "credentialMode":
112
+ return draft.credentialMode === "creator"
113
+ ? summary.ownedByViewer
114
+ ? "Your connected accounts"
115
+ : `${summary.createdBy}'s connected accounts`
116
+ : "System credentials only";
117
+ case "outcomes":
118
+ return draft.outcomes?.length
119
+ ? draft.outcomes
120
+ .map((outcome) =>
121
+ automationOutcomeLabel(outcome, summary.destination),
122
+ )
123
+ .join(", ")
124
+ : "No success message";
125
+ case "schedule":
126
+ return draft.schedule ? scheduleDraftLabel(draft.schedule) : "Not set";
127
+ case "trigger": {
128
+ const trigger = draft.trigger;
129
+ if (!trigger) return "Not set";
130
+ const resource = trigger.identifier
131
+ ? `${trigger.label} · ${trigger.identifier}`
132
+ : `Any ${trigger.label}`;
133
+ const filters = Object.keys(trigger.match ?? {}).length;
134
+ return [
135
+ resource,
136
+ trigger.events.join(", "),
137
+ filters ? `${filters} ${filters === 1 ? "filter" : "filters"}` : "",
138
+ ]
139
+ .filter(Boolean)
140
+ .join(" · ");
141
+ }
142
+ }
143
+ }
144
+
145
+ const FREQUENCY_UNITS = {
146
+ daily: "day",
147
+ weekly: "week",
148
+ monthly: "month",
149
+ yearly: "year",
150
+ } as const;
151
+
152
+ /** Describe a schedule draft. Saved and new values use the same words. */
153
+ function scheduleDraftLabel(schedule: AutomationScheduleDraft): string {
154
+ if (schedule.kind === "one_off") {
155
+ return `Once on ${schedule.timing.date} at ${schedule.timing.time} (${schedule.timezone})`;
156
+ }
157
+ const interval = schedule.interval ?? 1;
158
+ const unit = FREQUENCY_UNITS[schedule.frequency];
159
+ const cadence = interval === 1 ? unit : `${interval} ${unit}s`;
160
+ const detail =
161
+ schedule.frequency === "weekly" && schedule.weekdays?.length
162
+ ? ` on ${schedule.weekdays.map((day) => day.slice(0, 1).toUpperCase() + day.slice(1, 3)).join(", ")}`
163
+ : schedule.frequency === "monthly" && schedule.dayOfMonth
164
+ ? ` on day ${schedule.dayOfMonth}`
165
+ : schedule.frequency === "yearly" &&
166
+ schedule.month &&
167
+ schedule.dayOfMonth
168
+ ? ` on ${String(schedule.month).padStart(2, "0")}-${String(schedule.dayOfMonth).padStart(2, "0")}`
169
+ : "";
170
+ return `Every ${cadence}${detail} at ${schedule.time} (${schedule.timezone})`;
171
+ }