@sentry/junior-dashboard 0.266.0 → 0.268.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 (96) hide show
  1. package/dist/app.js +85 -12
  2. package/dist/client.js +94 -64
  3. package/dist/index.js +85 -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 +55 -53
  9. package/src/client/components/DetailsPanel.tsx +12 -13
  10. package/src/client/components/Drawer.tsx +46 -23
  11. package/src/client/components/ImageAttachment.tsx +1 -1
  12. package/src/client/components/ParticipantAvatarStack.tsx +6 -2
  13. package/src/client/components/README.md +15 -2
  14. package/src/client/components/UserInputForm.tsx +283 -0
  15. package/src/client/components/layout/DashboardHeader.tsx +13 -0
  16. package/src/client/components/ui/alert.tsx +1 -1
  17. package/src/client/components/ui/button-group.tsx +59 -0
  18. package/src/client/components/ui/button.tsx +11 -1
  19. package/src/client/components/ui/dialog.tsx +29 -35
  20. package/src/client/components/ui/drawer.tsx +140 -0
  21. package/src/client/components/ui/empty.tsx +30 -1
  22. package/src/client/components/ui/separator.tsx +22 -0
  23. package/src/client/components/ui/useSheet.ts +5 -1
  24. package/src/client/conversations/ComposerImages.tsx +66 -14
  25. package/src/client/conversations/ConversationComposer.tsx +84 -12
  26. package/src/client/conversations/ConversationDetails.tsx +3 -27
  27. package/src/client/conversations/ConversationEventDetails.tsx +26 -10
  28. package/src/client/conversations/ConversationHomeList.tsx +6 -0
  29. package/src/client/conversations/ConversationListStatusIcon.tsx +15 -1
  30. package/src/client/conversations/ConversationPage.tsx +160 -2
  31. package/src/client/conversations/ConversationSidebar.tsx +6 -0
  32. package/src/client/conversations/ConversationTranscript.tsx +48 -16
  33. package/src/client/conversations/ConversationWorkspace.tsx +4 -0
  34. package/src/client/conversations/DetailsList.tsx +33 -0
  35. package/src/client/conversations/EventDetails.tsx +46 -19
  36. package/src/client/conversations/ForkConversationButton.tsx +79 -73
  37. package/src/client/conversations/PendingMailboxStack.tsx +310 -112
  38. package/src/client/conversations/README.md +30 -13
  39. package/src/client/conversations/SubagentTranscriptDrawer.tsx +19 -22
  40. package/src/client/conversations/TranscriptActivityGroup.tsx +27 -19
  41. package/src/client/conversations/TranscriptAttachment.tsx +39 -4
  42. package/src/client/conversations/TranscriptAutomationTriggerView.tsx +14 -19
  43. package/src/client/conversations/TranscriptFailureView.tsx +10 -13
  44. package/src/client/conversations/TranscriptMarkdown.tsx +232 -436
  45. package/src/client/conversations/TranscriptMessageContextView.tsx +12 -1
  46. package/src/client/conversations/TranscriptMessageView.tsx +10 -1
  47. package/src/client/conversations/TranscriptSummary.tsx +5 -31
  48. package/src/client/conversations/TranscriptToolView.tsx +13 -1
  49. package/src/client/conversations/TranscriptTurnContextView.tsx +174 -161
  50. package/src/client/conversations/TranscriptView.tsx +7 -3
  51. package/src/client/conversations/UserInputRequestCard.tsx +39 -0
  52. package/src/client/conversations/conversationOutbox.ts +46 -0
  53. package/src/client/conversations/eventTime.ts +13 -1
  54. package/src/client/conversations/eventTranscript.ts +12 -5
  55. package/src/client/conversations/queries.ts +127 -26
  56. package/src/client/conversations/transcriptMarkdownLinks.ts +5 -367
  57. package/src/client/conversations/turnContext.ts +15 -0
  58. package/src/client/conversations/userInputRequest.ts +56 -0
  59. package/src/client/format.ts +31 -8
  60. package/src/client/pages/SettingsPage.tsx +103 -3
  61. package/src/client/pages/automations/{AutomationLifecycle.tsx → AutomationActions.tsx} +60 -69
  62. package/src/client/pages/automations/AutomationDraftChanges.tsx +171 -0
  63. package/src/client/pages/automations/AutomationEditor.tsx +182 -156
  64. package/src/client/pages/automations/AutomationEventFields.tsx +119 -45
  65. package/src/client/pages/automations/AutomationExecutionStatusChart.tsx +1 -1
  66. package/src/client/pages/automations/AutomationFormSection.tsx +20 -2
  67. package/src/client/pages/automations/AutomationOutcomeFields.tsx +1 -1
  68. package/src/client/pages/automations/AutomationOverview.tsx +238 -0
  69. package/src/client/pages/automations/AutomationPage.tsx +343 -0
  70. package/src/client/pages/automations/AutomationRecentRuns.tsx +3 -2
  71. package/src/client/pages/automations/AutomationRow.tsx +11 -13
  72. package/src/client/pages/automations/AutomationRunDetails.tsx +4 -2
  73. package/src/client/pages/automations/{AutomationExecutionsPage.tsx → AutomationRunsTab.tsx} +94 -103
  74. package/src/client/pages/automations/AutomationStatus.tsx +164 -108
  75. package/src/client/pages/automations/AutomationVersionsTab.tsx +518 -0
  76. package/src/client/pages/automations/AutomationsPage.tsx +10 -98
  77. package/src/client/pages/automations/AutomationsPageLayout.tsx +5 -23
  78. package/src/client/pages/automations/README.md +56 -46
  79. package/src/client/pages/code/RepositorySections.tsx +1 -0
  80. package/src/client/pages/code/RepositoryTabs.tsx +3 -0
  81. package/src/client/primaryNav.ts +15 -2
  82. package/src/client/review-desk/ConversationReviewBar.tsx +62 -0
  83. package/src/client/review-desk/ReviewDeskPage.tsx +277 -0
  84. package/src/client/review-desk/ReviewItemActions.tsx +165 -0
  85. package/src/client/review-desk/ReviewItemCard.tsx +47 -0
  86. package/src/client/review-desk/ReviewItemDetail.tsx +115 -0
  87. package/src/client/review-desk/queries.ts +164 -0
  88. package/src/client/review-desk/reviewItemFormat.ts +61 -0
  89. package/src/client/types.ts +3 -0
  90. package/src/mock-reporting/fixtures.ts +54 -2
  91. package/src/mock-reporting/routes.ts +20 -0
  92. package/src/shell.ts +3 -0
  93. package/src/client/conversations/transcriptStyles.ts +0 -10
  94. package/src/client/pages/automations/AutomationDetails.tsx +0 -193
  95. package/src/client/pages/automations/AutomationEditPage.tsx +0 -176
  96. package/src/client/pages/automations/AutomationVersionsPage.tsx +0 -390
@@ -28,6 +28,9 @@ import {
28
28
  personalSpendReportSchema,
29
29
  personParamsSchema,
30
30
  pluginOperationalReportFeedSchema,
31
+ reviewItemCountSchema,
32
+ reviewItemListSchema,
33
+ reviewItemWaitingConversationsSchema,
31
34
  automationExecutionListSchema,
32
35
  automationListSchema,
33
36
  automationParamsSchema,
@@ -294,6 +297,23 @@ export function createMockReportingApi(): Hono<{
294
297
  }
295
298
  return errorResponse("Attachment not found.", 404);
296
299
  });
300
+ // Mock reporting has no Review items. Answer the reads here so the
301
+ // dashboard does not query a database that has no review item tables.
302
+ app.get("/review-items", () =>
303
+ jsonResponse(reviewItemListSchema, { items: [] }),
304
+ );
305
+ app.get("/review-items/count", () =>
306
+ jsonResponse(reviewItemCountSchema, { mine: 0 }),
307
+ );
308
+ app.get("/review-items/waiting-conversations", () =>
309
+ jsonResponse(reviewItemWaitingConversationsSchema, { conversationIds: [] }),
310
+ );
311
+ app.get("/review-items/:id", () =>
312
+ errorResponse("Review item not found.", 404),
313
+ );
314
+ app.post("/review-items/:id/actions", () =>
315
+ errorResponse("Review item not found.", 404),
316
+ );
297
317
  app.get("/automations", (c) => {
298
318
  const report = readMockAutomationList();
299
319
  return jsonResponse(
package/src/shell.ts CHANGED
@@ -131,6 +131,9 @@ export function dashboardPagePaths(
131
131
  nested: true,
132
132
  path: basePath === "/" ? "/conversations" : `${basePath}/conversations`,
133
133
  },
134
+ {
135
+ path: basePath === "/" ? "/review" : `${basePath}/review`,
136
+ },
134
137
  {
135
138
  nested: true,
136
139
  path: basePath === "/" ? "/people" : `${basePath}/people`,
@@ -1,10 +0,0 @@
1
- /** Share the transcript empty/unavailable frame across top-level and segment views. */
2
- export function transcriptEmptyClass(
3
- tone: "default" | "error" = "default",
4
- ): string {
5
- const colors =
6
- tone === "error"
7
- ? "border-rose-300/25 bg-rose-300/[0.07] text-rose-100"
8
- : "border-dashboard-border bg-dashboard-fill-soft text-dashboard-text-muted";
9
- return `rounded-lg border p-5 text-sm leading-relaxed ${colors}`;
10
- }
@@ -1,193 +0,0 @@
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
- }
@@ -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
- }