@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.
- package/LICENSE +201 -0
- package/README.md +2 -5
- package/dist/app.js +119 -35
- package/dist/client.js +145 -136
- package/dist/index.d.ts +0 -1
- package/dist/index.js +119 -35
- package/dist/tailwind.css +1 -1
- package/package.json +5 -4
- package/src/api/schema.ts +0 -1
- package/src/app.ts +5 -20
- package/src/client/App.tsx +29 -31
- package/src/client/api.ts +7 -0
- package/src/client/components/ActorAvatar.tsx +10 -22
- package/src/client/components/BottomSheet.tsx +12 -2
- package/src/client/components/ConnectionBanner.tsx +4 -3
- package/src/client/components/CopyButton.tsx +82 -0
- package/src/client/components/DetailsPanel.tsx +133 -0
- package/src/client/components/Drawer.tsx +15 -24
- package/src/client/components/EmptyTelemetry.tsx +5 -3
- package/src/client/components/FilterBar.tsx +16 -31
- package/src/client/components/ImageAttachment.tsx +49 -63
- package/src/client/components/InlineError.tsx +8 -4
- package/src/client/components/Metric.tsx +33 -23
- package/src/client/components/Notice.tsx +19 -26
- package/src/client/components/PageContentSkeleton.tsx +1 -1
- package/src/client/components/Pagination.tsx +27 -27
- package/src/client/components/ParticipantAvatarStack.tsx +43 -39
- package/src/client/components/ProfileMenu.tsx +27 -93
- package/src/client/components/README.md +103 -78
- package/src/client/components/SearchInput.tsx +51 -41
- package/src/client/components/SegmentedNav.tsx +1 -1
- package/src/client/components/ShimmerText.tsx +2 -6
- package/src/client/components/ThemeSwitcher.tsx +19 -15
- package/src/client/components/VersionDriftBanner.tsx +9 -7
- package/src/client/components/charts/ActivityChart.tsx +173 -288
- package/src/client/components/charts/CacheWrites.tsx +59 -37
- package/src/client/components/charts/InputCacheChart.tsx +137 -131
- package/src/client/components/charts/README.md +4 -4
- package/src/client/components/charts/SystemMetricCharts.tsx +68 -95
- package/src/client/components/charts/WorkspaceUsageChart.tsx +42 -62
- package/src/client/components/controls/DirectorySortSelect.tsx +3 -3
- package/src/client/components/controls/TimeRangeSelector.tsx +10 -15
- package/src/client/components/layout/DashboardHeader.tsx +44 -90
- package/src/client/components/layout/SecondaryNavigation.tsx +2 -1
- package/src/client/components/layout/VisualViewportShell.tsx +2 -2
- package/src/client/components/ui/accordion.tsx +77 -0
- package/src/client/components/ui/alert-dialog.tsx +89 -0
- package/src/client/components/ui/alert.tsx +64 -0
- package/src/client/components/ui/avatar.tsx +53 -0
- package/src/client/components/ui/button.tsx +86 -0
- package/src/client/components/ui/chart.tsx +74 -0
- package/src/client/components/ui/checkbox.tsx +26 -0
- package/src/client/components/ui/collapsible.tsx +22 -0
- package/src/client/components/ui/dialog.tsx +66 -0
- package/src/client/components/ui/empty.tsx +32 -0
- package/src/client/components/ui/field.tsx +49 -0
- package/src/client/components/ui/input-group.tsx +96 -0
- package/src/client/components/ui/input.tsx +35 -0
- package/src/client/components/ui/native-select.tsx +57 -0
- package/src/client/components/ui/pagination.tsx +60 -0
- package/src/client/components/ui/popover.tsx +67 -0
- package/src/client/components/ui/radio-group.tsx +32 -0
- package/src/client/components/ui/sheet.tsx +13 -3
- package/src/client/components/ui/skeleton.tsx +19 -0
- package/src/client/components/ui/spinner.tsx +21 -0
- package/src/client/components/ui/table.tsx +101 -0
- package/src/client/components/ui/tabs.tsx +75 -0
- package/src/client/components/ui/textarea.tsx +17 -0
- package/src/client/components/ui/toast.tsx +215 -0
- package/src/client/components/ui/toggle-group.tsx +36 -0
- package/src/client/components/ui/toggle.tsx +37 -0
- package/src/client/components/ui/tooltip.tsx +168 -0
- package/src/client/components/ui/trace.tsx +96 -0
- package/src/client/conversations/ChatLayout.tsx +7 -3
- package/src/client/conversations/ConversationBrief.tsx +17 -6
- package/src/client/conversations/ConversationChildAgents.tsx +5 -5
- package/src/client/conversations/ConversationComposer.tsx +26 -12
- package/src/client/conversations/ConversationDetails.tsx +147 -62
- package/src/client/conversations/ConversationEventDetails.tsx +141 -0
- package/src/client/conversations/ConversationEventLog.tsx +150 -129
- package/src/client/conversations/ConversationHeader.tsx +13 -7
- package/src/client/conversations/ConversationHeaderActions.tsx +97 -93
- package/src/client/conversations/ConversationHomeList.tsx +1 -1
- package/src/client/conversations/ConversationLinkedWork.tsx +118 -0
- package/src/client/conversations/ConversationMeta.tsx +55 -88
- package/src/client/conversations/ConversationPage.tsx +220 -101
- package/src/client/conversations/ConversationSidebar.tsx +49 -29
- package/src/client/conversations/ConversationUsageBreakdown.tsx +108 -65
- package/src/client/conversations/ConversationWatches.tsx +173 -0
- package/src/client/conversations/ConversationWorkspace.tsx +12 -22
- package/src/client/conversations/ConversationWorkspaceLoading.tsx +1 -1
- package/src/client/conversations/DetailsList.tsx +139 -0
- package/src/client/conversations/EventDetails.tsx +42 -8
- package/src/client/conversations/EventUsage.tsx +99 -74
- package/src/client/conversations/ForkConversationButton.tsx +27 -16
- package/src/client/conversations/ObjectCard.tsx +20 -8
- package/src/client/conversations/PendingMailboxStack.tsx +52 -38
- package/src/client/conversations/README.md +89 -13
- package/src/client/conversations/ShareConversationButton.tsx +28 -17
- package/src/client/conversations/SubagentTranscriptDrawer.tsx +21 -15
- package/src/client/conversations/ToolFrame.tsx +36 -52
- package/src/client/conversations/TranscriptActivityGroup.tsx +13 -4
- package/src/client/conversations/TranscriptAutomationTriggerView.tsx +5 -4
- package/src/client/conversations/TranscriptContextEventView.tsx +25 -11
- package/src/client/conversations/TranscriptEventView.tsx +35 -16
- package/src/client/conversations/TranscriptHeadingRow.tsx +7 -7
- package/src/client/conversations/TranscriptLoading.tsx +1 -1
- package/src/client/conversations/TranscriptMarkdown.tsx +25 -20
- package/src/client/conversations/TranscriptMessageContextView.tsx +35 -18
- package/src/client/conversations/TranscriptMessageView.tsx +6 -1
- package/src/client/conversations/TranscriptReasoningView.tsx +54 -68
- package/src/client/conversations/TranscriptStructuredEventView.tsx +25 -15
- package/src/client/conversations/TranscriptSummary.tsx +34 -2
- package/src/client/conversations/TranscriptTimestamp.tsx +9 -7
- package/src/client/conversations/TranscriptToolView.tsx +1 -1
- package/src/client/conversations/TranscriptTurnContextView.tsx +144 -65
- package/src/client/conversations/TranscriptView.tsx +63 -27
- package/src/client/conversations/eventLog.ts +58 -0
- package/src/client/conversations/eventTime.ts +60 -0
- package/src/client/conversations/queries.ts +29 -0
- package/src/client/conversations/transcriptBottomPinning.ts +34 -0
- package/src/client/conversations/transcriptMentions.tsx +18 -5
- package/src/client/format.ts +5 -0
- package/src/client/pages/ConnectionsPage.tsx +2 -2
- package/src/client/pages/PersonalTokensPage.tsx +4 -4
- package/src/client/pages/SettingsPage.tsx +9 -10
- package/src/client/pages/admin/ServiceAccountsSection.tsx +2 -2
- package/src/client/pages/automations/AutomationCostChart.tsx +50 -71
- package/src/client/pages/automations/AutomationDeleteDialog.tsx +87 -0
- package/src/client/pages/automations/AutomationDetails.tsx +193 -0
- package/src/client/pages/automations/AutomationEditPage.tsx +7 -8
- package/src/client/pages/automations/AutomationEditor.tsx +41 -29
- package/src/client/pages/automations/AutomationEditorLoading.tsx +1 -1
- package/src/client/pages/automations/AutomationEventFields.tsx +202 -119
- package/src/client/pages/automations/AutomationExecutionChart.tsx +51 -64
- package/src/client/pages/automations/AutomationExecutionStatusChart.tsx +47 -70
- package/src/client/pages/automations/AutomationExecutionsPage.tsx +150 -126
- package/src/client/pages/automations/AutomationFilters.tsx +189 -74
- package/src/client/pages/automations/AutomationLifecycle.tsx +109 -78
- package/src/client/pages/automations/AutomationOutcomeFields.tsx +26 -24
- package/src/client/pages/automations/AutomationRecentRuns.tsx +143 -0
- package/src/client/pages/automations/AutomationRow.tsx +12 -138
- package/src/client/pages/automations/AutomationRunDetails.tsx +307 -0
- package/src/client/pages/automations/AutomationRunsPage.tsx +119 -94
- package/src/client/pages/automations/AutomationScheduleFields.tsx +51 -51
- package/src/client/pages/automations/AutomationStatus.tsx +201 -0
- package/src/client/pages/automations/AutomationVersionsPage.tsx +65 -28
- package/src/client/pages/automations/AutomationsPage.tsx +80 -68
- package/src/client/pages/automations/AutomationsPageLayout.tsx +46 -14
- package/src/client/pages/automations/README.md +57 -0
- package/src/client/pages/automations/TriggerFilters.tsx +22 -0
- package/src/client/pages/automations/automationFormat.ts +37 -23
- package/src/client/pages/code/CodeActivityChart.tsx +59 -101
- package/src/client/pages/code/CodePage.tsx +52 -55
- package/src/client/pages/code/RepositoryPage.tsx +6 -8
- package/src/client/pages/locations/LocationActivityChart.tsx +53 -70
- package/src/client/pages/locations/LocationDirectoryActivityChart.tsx +60 -80
- package/src/client/pages/memory/MemoryCostChart.tsx +79 -103
- package/src/client/pages/memory/MemoryDetails.tsx +218 -0
- package/src/client/pages/memory/MemoryPage.tsx +168 -112
- package/src/client/pages/memory/MemoryPageLayout.tsx +33 -9
- package/src/client/pages/memory/MemoryTimeline.tsx +46 -69
- package/src/client/pages/memory/memoryRecord.ts +1 -1
- package/src/client/pages/people/ContributionGrid.tsx +23 -16
- package/src/client/pages/people/PeopleActivityChart.tsx +68 -107
- package/src/client/pages/people/ProfileCodeChanges.tsx +5 -5
- package/src/client/pages/system/BaseSandboxCard.tsx +1 -1
- package/src/client/pages/system/ConversationActivityChart.tsx +49 -67
- package/src/client/pages/system/GuardianActivity.tsx +1 -2
- package/src/client/pages/system/PluginBarChart.tsx +106 -87
- package/src/client/pages/system/PluginReportRecordSet.tsx +25 -24
- package/src/client/pages/system/README.md +23 -0
- package/src/client/pages/system/SystemNavigation.tsx +5 -1
- package/src/client/pages/system/WorkspaceDeleteDialog.tsx +111 -0
- package/src/client/pages/system/WorkspaceDetails.tsx +200 -43
- package/src/client/pages/system/WorkspaceEditor.tsx +59 -61
- package/src/client/pages/system/WorkspaceFormPage.tsx +23 -47
- package/src/client/pages/system/WorkspaceList.tsx +20 -30
- package/src/client/pages/system/WorkspacesPage.tsx +117 -119
- package/src/client/pages/system/sandboxSize.ts +8 -0
- package/src/client/pages/system/workspaceQueries.ts +53 -0
- package/src/client/pages/user/PluginUserPage.tsx +51 -2
- package/src/client/pages/user/pluginUserPageData.ts +1 -6
- package/src/client/styles.ts +4 -0
- package/src/client.tsx +7 -1
- package/src/mock-reporting/automation-collection.ts +4 -2
- package/src/mock-reporting/fixtures.ts +23 -7
- package/src/mock-reporting/routes.ts +12 -0
- package/src/mock-reporting/watches.ts +94 -0
- package/src/shell.ts +4 -9
- package/src/tailwind.css +0 -10
- package/src/client/components/Button.tsx +0 -118
- package/src/client/components/Field.tsx +0 -53
- package/src/client/components/SegmentedTabs.tsx +0 -119
- package/src/client/components/Select.tsx +0 -52
- package/src/client/components/Skeleton.tsx +0 -17
- package/src/client/components/TextInput.tsx +0 -58
- package/src/client/components/Tooltip.tsx +0 -216
- package/src/client/components/charts/useChartLayout.ts +0 -27
- package/src/client/components/ui/sheet.LICENSE +0 -21
- package/src/client/conversations/ConversationDetailsFrame.tsx +0 -119
- package/src/client/conversations/CopyMarkdownButton.tsx +0 -65
- package/src/client/pages/automations/AutomationDetailsDrawer.tsx +0 -219
- package/src/client/pages/dev/ChartFixtures.tsx +0 -325
- package/src/client/pages/dev/ComponentsPage.tsx +0 -899
- package/src/client/pages/dev/ConversationFixture.tsx +0 -231
- package/src/client/pages/dev/ObjectFixtures.tsx +0 -138
- package/src/client/pages/dev/SegmentedFixtures.tsx +0 -59
- package/src/client/pages/dev/SelectFixtures.tsx +0 -62
- package/src/client/pages/dev/TranscriptActivityFixtures.tsx +0 -206
- package/src/client/pages/memory/MemoryDetailsDrawer.tsx +0 -167
- package/src/client/pages/system/SandboxSizeSummary.tsx +0 -47
- package/src/client/pages/system/SnapshotSummary.tsx +0 -78
|
@@ -13,6 +13,16 @@ import {
|
|
|
13
13
|
totalConversationCost,
|
|
14
14
|
} from "../format";
|
|
15
15
|
import { ShimmerText } from "../components/ShimmerText";
|
|
16
|
+
import {
|
|
17
|
+
Table,
|
|
18
|
+
TableBody,
|
|
19
|
+
TableCell,
|
|
20
|
+
TableFooter,
|
|
21
|
+
TableHead,
|
|
22
|
+
TableHeader,
|
|
23
|
+
TableRow,
|
|
24
|
+
} from "../components/ui/table";
|
|
25
|
+
import { cn } from "../styles";
|
|
16
26
|
import type { Conversation } from "../types";
|
|
17
27
|
import { liveModelId } from "./ConversationMeta";
|
|
18
28
|
import {
|
|
@@ -70,7 +80,7 @@ function tokenTypeParts(
|
|
|
70
80
|
return parts.length ? parts : undefined;
|
|
71
81
|
}
|
|
72
82
|
|
|
73
|
-
const CELL = "text-right align-top font-sans tabular-nums";
|
|
83
|
+
const CELL = "px-0 text-right align-top font-sans tabular-nums";
|
|
74
84
|
|
|
75
85
|
/**
|
|
76
86
|
* One usage table. Every table in the breakdown shares column widths, so the
|
|
@@ -85,85 +95,118 @@ function BreakdownTable(props: {
|
|
|
85
95
|
total?: { cost: string; count: string; label: string };
|
|
86
96
|
}) {
|
|
87
97
|
return (
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
>
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
<
|
|
99
|
-
<
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
98
|
+
<Table
|
|
99
|
+
aria-label={props.label}
|
|
100
|
+
className="min-w-72 table-fixed text-xs leading-relaxed"
|
|
101
|
+
>
|
|
102
|
+
<colgroup>
|
|
103
|
+
<col />
|
|
104
|
+
<col className="w-20" />
|
|
105
|
+
<col className="w-20" />
|
|
106
|
+
</colgroup>
|
|
107
|
+
<TableHeader>
|
|
108
|
+
<TableRow className="border-dashboard-border">
|
|
109
|
+
<TableHead
|
|
110
|
+
scope="col"
|
|
111
|
+
className="px-0 py-2 leading-relaxed font-normal"
|
|
112
|
+
>
|
|
113
|
+
{props.label}
|
|
114
|
+
</TableHead>
|
|
115
|
+
<TableHead
|
|
116
|
+
scope="col"
|
|
117
|
+
className="px-0 py-2 text-right leading-relaxed font-normal"
|
|
118
|
+
>
|
|
119
|
+
{props.countLabel}
|
|
120
|
+
</TableHead>
|
|
121
|
+
<TableHead
|
|
122
|
+
scope="col"
|
|
123
|
+
className="px-0 py-2 text-right leading-relaxed font-normal"
|
|
124
|
+
>
|
|
125
|
+
Cost
|
|
126
|
+
</TableHead>
|
|
127
|
+
</TableRow>
|
|
128
|
+
</TableHeader>
|
|
129
|
+
{props.rows.map((row) => {
|
|
130
|
+
const padding = row.parts ? "pt-2 pb-0.5" : "py-2";
|
|
131
|
+
return (
|
|
132
|
+
<TableBody
|
|
113
133
|
className="border-b border-dashboard-border-subtle last-of-type:border-b-0"
|
|
114
134
|
key={row.key}
|
|
115
135
|
>
|
|
116
|
-
|
|
117
|
-
|
|
136
|
+
{/* The tbody draws the group border, so its rows have none. */}
|
|
137
|
+
<TableRow className="border-0">
|
|
138
|
+
<TableHead
|
|
118
139
|
scope="row"
|
|
119
|
-
className={
|
|
140
|
+
className={cn(
|
|
141
|
+
"pl-0 pr-3 align-top leading-relaxed font-normal text-dashboard-text",
|
|
142
|
+
padding,
|
|
143
|
+
)}
|
|
120
144
|
>
|
|
121
145
|
<span className="block truncate">{row.label}</span>
|
|
122
|
-
</
|
|
123
|
-
<
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
<td className={`${CELL} ${row.parts ? "pt-2 pb-0.5" : "py-2"}`}>
|
|
127
|
-
{row.cost}
|
|
128
|
-
</td>
|
|
129
|
-
</tr>
|
|
146
|
+
</TableHead>
|
|
147
|
+
<TableCell className={cn(CELL, padding)}>{row.count}</TableCell>
|
|
148
|
+
<TableCell className={cn(CELL, padding)}>{row.cost}</TableCell>
|
|
149
|
+
</TableRow>
|
|
130
150
|
{row.parts?.map((part, index, parts) => {
|
|
131
|
-
const
|
|
151
|
+
const partPadding =
|
|
152
|
+
index === parts.length - 1 ? "pt-0 pb-2" : "py-0";
|
|
132
153
|
return (
|
|
133
|
-
<
|
|
134
|
-
className="text-2xs leading-5
|
|
154
|
+
<TableRow
|
|
155
|
+
className="border-0 text-2xs leading-5"
|
|
135
156
|
key={part.label}
|
|
136
157
|
>
|
|
137
|
-
<
|
|
158
|
+
<TableHead
|
|
138
159
|
scope="row"
|
|
139
|
-
className={
|
|
160
|
+
className={cn(
|
|
161
|
+
"truncate pl-3 pr-3 align-top text-2xs leading-5 font-normal text-dashboard-text-faint",
|
|
162
|
+
partPadding,
|
|
163
|
+
)}
|
|
140
164
|
>
|
|
141
165
|
{part.label}
|
|
142
|
-
</
|
|
143
|
-
<
|
|
144
|
-
|
|
145
|
-
|
|
166
|
+
</TableHead>
|
|
167
|
+
<TableCell
|
|
168
|
+
className={cn(
|
|
169
|
+
CELL,
|
|
170
|
+
"text-dashboard-text-faint",
|
|
171
|
+
partPadding,
|
|
172
|
+
)}
|
|
173
|
+
>
|
|
174
|
+
{part.count}
|
|
175
|
+
</TableCell>
|
|
176
|
+
<TableCell
|
|
177
|
+
className={cn(
|
|
178
|
+
CELL,
|
|
179
|
+
"text-dashboard-text-faint",
|
|
180
|
+
partPadding,
|
|
181
|
+
)}
|
|
182
|
+
>
|
|
183
|
+
{part.cost}
|
|
184
|
+
</TableCell>
|
|
185
|
+
</TableRow>
|
|
146
186
|
);
|
|
147
187
|
})}
|
|
148
|
-
</
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
</
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
188
|
+
</TableBody>
|
|
189
|
+
);
|
|
190
|
+
})}
|
|
191
|
+
{props.total ? (
|
|
192
|
+
<TableFooter className="font-semibold">
|
|
193
|
+
<TableRow>
|
|
194
|
+
<TableHead
|
|
195
|
+
scope="row"
|
|
196
|
+
className="px-0 py-2 leading-relaxed font-semibold text-dashboard-text"
|
|
197
|
+
>
|
|
198
|
+
{props.total.label}
|
|
199
|
+
</TableHead>
|
|
200
|
+
<TableCell className="px-0 py-2 text-right font-sans tabular-nums">
|
|
201
|
+
{props.total.count}
|
|
202
|
+
</TableCell>
|
|
203
|
+
<TableCell className="px-0 py-2 text-right font-sans tabular-nums">
|
|
204
|
+
{props.total.cost}
|
|
205
|
+
</TableCell>
|
|
206
|
+
</TableRow>
|
|
207
|
+
</TableFooter>
|
|
208
|
+
) : null}
|
|
209
|
+
</Table>
|
|
167
210
|
);
|
|
168
211
|
}
|
|
169
212
|
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { useQuery } from "@tanstack/react-query";
|
|
2
|
+
import {
|
|
3
|
+
Bot,
|
|
4
|
+
ChevronRight,
|
|
5
|
+
CircleDot,
|
|
6
|
+
Eye,
|
|
7
|
+
FolderGit2,
|
|
8
|
+
GitPullRequest,
|
|
9
|
+
Hash,
|
|
10
|
+
LockKeyhole,
|
|
11
|
+
type LucideIcon,
|
|
12
|
+
Package,
|
|
13
|
+
Rocket,
|
|
14
|
+
Tag,
|
|
15
|
+
Timer,
|
|
16
|
+
Users,
|
|
17
|
+
} from "lucide-react";
|
|
18
|
+
import { useState, type ReactNode } from "react";
|
|
19
|
+
import {
|
|
20
|
+
conversationWatchesReportSchema,
|
|
21
|
+
type ConversationWatch,
|
|
22
|
+
} from "@sentry/junior/api/schema";
|
|
23
|
+
|
|
24
|
+
import { formatTime } from "../format";
|
|
25
|
+
import { fetchDashboardJson } from "../http";
|
|
26
|
+
import { dashboardDisclosureChevronClass } from "../styles";
|
|
27
|
+
import { DetailsList, DetailsRow } from "./DetailsList";
|
|
28
|
+
|
|
29
|
+
/** Load the active Watches that one Conversation owns. */
|
|
30
|
+
export function useConversationWatches(conversationId: string) {
|
|
31
|
+
return useQuery({
|
|
32
|
+
queryFn: ({ signal }) =>
|
|
33
|
+
fetchDashboardJson(
|
|
34
|
+
conversationWatchesReportSchema,
|
|
35
|
+
`/api/conversations/${encodeURIComponent(conversationId)}/watches`,
|
|
36
|
+
signal,
|
|
37
|
+
),
|
|
38
|
+
queryKey: ["conversation", conversationId, "watches"],
|
|
39
|
+
retry: false,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Show what can wake this Conversation: one line per active Watch. A line
|
|
45
|
+
* expands to show the reason and the event types that deliver to it.
|
|
46
|
+
*/
|
|
47
|
+
export function ConversationWatches(props: { watches: ConversationWatch[] }) {
|
|
48
|
+
return (
|
|
49
|
+
<DetailsList
|
|
50
|
+
itemKey={(watch) => watch.id}
|
|
51
|
+
items={props.watches}
|
|
52
|
+
renderItem={(watch) => <WatchRow watch={watch} />}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const resourceIcons: Partial<Record<string, LucideIcon>> = {
|
|
58
|
+
channel: Hash,
|
|
59
|
+
deployment: Rocket,
|
|
60
|
+
deployment_source: Rocket,
|
|
61
|
+
issue: CircleDot,
|
|
62
|
+
pull_request: GitPullRequest,
|
|
63
|
+
release_source: Tag,
|
|
64
|
+
repository: FolderGit2,
|
|
65
|
+
team: Users,
|
|
66
|
+
timer: Timer,
|
|
67
|
+
workspace_snapshot: Package,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Show one Watch as a single line with its resource. Clicking the line shows
|
|
72
|
+
* why the Watch exists and which events wake the Conversation. The expiry is
|
|
73
|
+
* an internal limit, so the row does not show it.
|
|
74
|
+
*/
|
|
75
|
+
function WatchRow(props: { watch: ConversationWatch }) {
|
|
76
|
+
const { watch } = props;
|
|
77
|
+
const [expanded, setExpanded] = useState(false);
|
|
78
|
+
const Icon = watch.redacted
|
|
79
|
+
? LockKeyhole
|
|
80
|
+
: (resourceIcons[watch.resourceType] ?? Eye);
|
|
81
|
+
const resource = `${watch.namespace} ${watch.resourceType.replaceAll("_", " ")}`;
|
|
82
|
+
// A timer has one event, and its fire time already says when it wakes.
|
|
83
|
+
const events = watch.firesAt ? [] : watch.events;
|
|
84
|
+
const filters = Object.entries(watch.match ?? {});
|
|
85
|
+
const row = (
|
|
86
|
+
<DetailsRow
|
|
87
|
+
expanded={expanded}
|
|
88
|
+
icon={<Icon aria-hidden="true" className="size-3.5" />}
|
|
89
|
+
meta={
|
|
90
|
+
<span className="grid size-5 place-items-center">
|
|
91
|
+
<ChevronRight
|
|
92
|
+
aria-hidden="true"
|
|
93
|
+
className={dashboardDisclosureChevronClass}
|
|
94
|
+
/>
|
|
95
|
+
</span>
|
|
96
|
+
}
|
|
97
|
+
onToggle={() => setExpanded((value) => !value)}
|
|
98
|
+
title={watch.label ?? `Private ${resource}`}
|
|
99
|
+
/>
|
|
100
|
+
);
|
|
101
|
+
if (!expanded) return row;
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<>
|
|
105
|
+
{row}
|
|
106
|
+
{/* Indent under the title: icon column, column gap, and row padding. */}
|
|
107
|
+
<div className="grid min-w-0 gap-1.5 pb-2 pl-9 pr-1.5 pt-0.5 text-xs leading-4 text-dashboard-text-muted">
|
|
108
|
+
{watch.intent ? (
|
|
109
|
+
<p className="m-0 break-words">{watch.intent}</p>
|
|
110
|
+
) : null}
|
|
111
|
+
{watch.firesAt ? (
|
|
112
|
+
<p className="m-0">
|
|
113
|
+
Fires{" "}
|
|
114
|
+
<time dateTime={watch.firesAt}>
|
|
115
|
+
{formatTime(watch.firesAt, {
|
|
116
|
+
dateStyle: "medium",
|
|
117
|
+
timeStyle: "short",
|
|
118
|
+
})}
|
|
119
|
+
</time>
|
|
120
|
+
</p>
|
|
121
|
+
) : null}
|
|
122
|
+
{events.length > 0 || filters.length > 0 ? (
|
|
123
|
+
<div className="flex flex-wrap items-center gap-1">
|
|
124
|
+
{events.map((event) => (
|
|
125
|
+
<WatchChip key={event}>
|
|
126
|
+
{watchEventLabel(event, watch.resourceType)}
|
|
127
|
+
</WatchChip>
|
|
128
|
+
))}
|
|
129
|
+
{filters.map(([key, value]) => (
|
|
130
|
+
<WatchChip filter key={`match:${key}`}>
|
|
131
|
+
{key.replaceAll("_", " ")}: {[value].flat().join(", ")}
|
|
132
|
+
</WatchChip>
|
|
133
|
+
))}
|
|
134
|
+
</div>
|
|
135
|
+
) : null}
|
|
136
|
+
{watch.childAgent ? (
|
|
137
|
+
<p className="m-0 flex min-w-0 items-center gap-1">
|
|
138
|
+
<Bot aria-hidden="true" className="size-3 shrink-0" />
|
|
139
|
+
<span className="truncate">
|
|
140
|
+
via {watch.childAgent.name ?? "subagent"}
|
|
141
|
+
</span>
|
|
142
|
+
</p>
|
|
143
|
+
) : null}
|
|
144
|
+
</div>
|
|
145
|
+
</>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Label one event type, or a dashed match filter that narrows the events. */
|
|
150
|
+
function WatchChip(props: { children: ReactNode; filter?: boolean }) {
|
|
151
|
+
return (
|
|
152
|
+
<span
|
|
153
|
+
className={
|
|
154
|
+
props.filter
|
|
155
|
+
? "min-w-0 truncate rounded-md border border-dashed border-dashboard-border px-1.5 py-px text-xs leading-4 text-dashboard-text-muted"
|
|
156
|
+
: "min-w-0 truncate rounded-md bg-dashboard-fill-soft px-1.5 py-px text-xs leading-4 text-dashboard-text-subtle"
|
|
157
|
+
}
|
|
158
|
+
>
|
|
159
|
+
{props.children}
|
|
160
|
+
</span>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Turn an event type into a short trigger label. The row already names the
|
|
166
|
+
* resource, so `pull_request.checks.failed` reads as "checks failed".
|
|
167
|
+
*/
|
|
168
|
+
function watchEventLabel(event: string, resourceType: string): string {
|
|
169
|
+
const parts = event.split(".");
|
|
170
|
+
const rest =
|
|
171
|
+
parts[0] === resourceType && parts.length > 1 ? parts.slice(1) : parts;
|
|
172
|
+
return rest.join(" ").replaceAll("_", " ");
|
|
173
|
+
}
|
|
@@ -5,8 +5,8 @@ import { useNavigate, useParams } from "react-router";
|
|
|
5
5
|
|
|
6
6
|
import { useConversationsData } from "../api";
|
|
7
7
|
import { ConversationSidebar } from "./ConversationSidebar";
|
|
8
|
-
import { ToggleButton } from "../components/Button";
|
|
9
8
|
import { SearchInput } from "../components/SearchInput";
|
|
9
|
+
import { ToggleGroup, ToggleGroupItem } from "../components/ui/toggle-group";
|
|
10
10
|
import { pageCount, pageItems, PagePagination } from "../components/Pagination";
|
|
11
11
|
import { ConversationHomeList } from "./ConversationHomeList";
|
|
12
12
|
import { ConversationComposer } from "./ConversationComposer";
|
|
@@ -171,7 +171,6 @@ function NewConversationView(props: {
|
|
|
171
171
|
): Promise<void>;
|
|
172
172
|
}) {
|
|
173
173
|
const [visibility, setVisibility] = useState<"private" | "public">("public");
|
|
174
|
-
const isPublic = visibility === "public";
|
|
175
174
|
|
|
176
175
|
return (
|
|
177
176
|
<section
|
|
@@ -185,32 +184,23 @@ function NewConversationView(props: {
|
|
|
185
184
|
draftId="new"
|
|
186
185
|
error={props.error}
|
|
187
186
|
footerStart={
|
|
188
|
-
<
|
|
187
|
+
<ToggleGroup
|
|
189
188
|
aria-label="Conversation visibility"
|
|
190
|
-
className="inline-flex shrink-0
|
|
191
|
-
|
|
189
|
+
className="inline-flex shrink-0 gap-0.5 rounded-lg border border-dashboard-border bg-dashboard-surface-panel p-0.5"
|
|
190
|
+
onValueChange={([next]) => {
|
|
191
|
+
if (next) setVisibility(next);
|
|
192
|
+
}}
|
|
193
|
+
value={[visibility]}
|
|
192
194
|
>
|
|
193
|
-
<
|
|
194
|
-
className="min-w-20 gap-1.5"
|
|
195
|
-
onClick={() => setVisibility("public")}
|
|
196
|
-
pressed={isPublic}
|
|
197
|
-
type="button"
|
|
198
|
-
variant="segment"
|
|
199
|
-
>
|
|
195
|
+
<ToggleGroupItem className="min-w-20 gap-1.5" value="public">
|
|
200
196
|
<Globe2 aria-hidden="true" className="size-3" />
|
|
201
197
|
Public
|
|
202
|
-
</
|
|
203
|
-
<
|
|
204
|
-
className="min-w-20 gap-1.5"
|
|
205
|
-
onClick={() => setVisibility("private")}
|
|
206
|
-
pressed={!isPublic}
|
|
207
|
-
type="button"
|
|
208
|
-
variant="segment"
|
|
209
|
-
>
|
|
198
|
+
</ToggleGroupItem>
|
|
199
|
+
<ToggleGroupItem className="min-w-20 gap-1.5" value="private">
|
|
210
200
|
<LockKeyhole aria-hidden="true" className="size-3" />
|
|
211
201
|
Private
|
|
212
|
-
</
|
|
213
|
-
</
|
|
202
|
+
</ToggleGroupItem>
|
|
203
|
+
</ToggleGroup>
|
|
214
204
|
}
|
|
215
205
|
label="Start a conversation"
|
|
216
206
|
restoreDraftOnError
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Skeleton } from "../components/
|
|
1
|
+
import { Skeleton } from "../components/ui/skeleton";
|
|
2
2
|
import { dashboardContainerClass, cn } from "../styles";
|
|
3
3
|
import { ChatLayout } from "./ChatLayout";
|
|
4
4
|
import { ConversationHomeListLoading } from "./ConversationHomeList";
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { ChevronRight } from "lucide-react";
|
|
2
|
+
import { useState, type ReactNode } from "react";
|
|
3
|
+
|
|
4
|
+
import { cn, dashboardDisclosureChevronClass } from "../styles";
|
|
5
|
+
|
|
6
|
+
/** Rows a details list shows before the reader asks for the rest. */
|
|
7
|
+
const COLLAPSED_ROW_COUNT = 3;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Show rows in a details card. A list with more than three rows shows the
|
|
11
|
+
* first three and a row that shows the rest.
|
|
12
|
+
*/
|
|
13
|
+
export function DetailsList<T>(props: {
|
|
14
|
+
items: readonly T[];
|
|
15
|
+
itemKey(item: T): string;
|
|
16
|
+
renderItem(item: T): ReactNode;
|
|
17
|
+
}) {
|
|
18
|
+
const [expanded, setExpanded] = useState(false);
|
|
19
|
+
const hidden = props.items.length - COLLAPSED_ROW_COUNT;
|
|
20
|
+
const visible =
|
|
21
|
+
expanded || hidden <= 0
|
|
22
|
+
? props.items
|
|
23
|
+
: props.items.slice(0, COLLAPSED_ROW_COUNT);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
// Rows own their padding, so a hover fill sits inside the card with an
|
|
27
|
+
// even inset. The negative margins keep row text on the card padding.
|
|
28
|
+
<ul className="-mx-1.5 -mb-1.5 -mt-1 grid list-none gap-0.5 p-0">
|
|
29
|
+
{visible.map((item) => (
|
|
30
|
+
<li key={props.itemKey(item)}>{props.renderItem(item)}</li>
|
|
31
|
+
))}
|
|
32
|
+
{hidden > 0 ? (
|
|
33
|
+
<li>
|
|
34
|
+
<button
|
|
35
|
+
aria-expanded={expanded}
|
|
36
|
+
className="group flex min-h-8 w-full items-center gap-2.5 rounded-md px-1.5 py-1.5 text-left text-xs font-medium leading-4 text-dashboard-text-muted transition-colors hover:bg-dashboard-surface-hover hover:text-dashboard-text focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-dashboard-focus"
|
|
37
|
+
onClick={() => setExpanded((value) => !value)}
|
|
38
|
+
type="button"
|
|
39
|
+
>
|
|
40
|
+
<span className="grid size-5 shrink-0 place-items-center">
|
|
41
|
+
<ChevronRight
|
|
42
|
+
aria-hidden="true"
|
|
43
|
+
className={dashboardDisclosureChevronClass}
|
|
44
|
+
/>
|
|
45
|
+
</span>
|
|
46
|
+
{expanded ? "Show fewer" : `Show ${hidden} more`}
|
|
47
|
+
</button>
|
|
48
|
+
</li>
|
|
49
|
+
) : null}
|
|
50
|
+
</ul>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Lay out one details list row: an icon, a title, and short trailing text on
|
|
56
|
+
* the first line, then optional lines under the title. A row with `href`
|
|
57
|
+
* opens that link in a new tab. A row with `onToggle` is a disclosure button
|
|
58
|
+
* that shows or hides content the caller renders under it.
|
|
59
|
+
*/
|
|
60
|
+
export function DetailsRow(props: {
|
|
61
|
+
children?: ReactNode;
|
|
62
|
+
expanded?: boolean;
|
|
63
|
+
href?: string;
|
|
64
|
+
icon: ReactNode;
|
|
65
|
+
meta?: ReactNode;
|
|
66
|
+
onToggle?(): void;
|
|
67
|
+
title: ReactNode;
|
|
68
|
+
/** Native hover text with detail that the row does not show. */
|
|
69
|
+
tooltip?: string;
|
|
70
|
+
}) {
|
|
71
|
+
const interactive = props.href !== undefined || props.onToggle !== undefined;
|
|
72
|
+
const className = cn(
|
|
73
|
+
"grid w-full min-w-0 grid-cols-[auto_minmax(0,1fr)_auto] items-start gap-x-2.5 gap-y-1 rounded-md px-1.5 py-1.5 text-left text-inherit no-underline",
|
|
74
|
+
interactive &&
|
|
75
|
+
"group transition-colors hover:bg-dashboard-surface-hover focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-dashboard-focus",
|
|
76
|
+
);
|
|
77
|
+
const content = (
|
|
78
|
+
<>
|
|
79
|
+
<span className="grid size-5 shrink-0 place-items-center text-dashboard-text-muted">
|
|
80
|
+
{props.icon}
|
|
81
|
+
</span>
|
|
82
|
+
<span className="min-w-0 truncate text-sm font-medium leading-5 text-dashboard-text">
|
|
83
|
+
{props.title}
|
|
84
|
+
</span>
|
|
85
|
+
<span className="shrink-0 text-xs leading-5 tabular-nums text-dashboard-text-muted">
|
|
86
|
+
{props.meta}
|
|
87
|
+
</span>
|
|
88
|
+
{props.children}
|
|
89
|
+
</>
|
|
90
|
+
);
|
|
91
|
+
if (props.href) {
|
|
92
|
+
return (
|
|
93
|
+
<a
|
|
94
|
+
className={className}
|
|
95
|
+
href={props.href}
|
|
96
|
+
rel="noreferrer"
|
|
97
|
+
target="_blank"
|
|
98
|
+
title={props.tooltip}
|
|
99
|
+
>
|
|
100
|
+
{content}
|
|
101
|
+
</a>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
if (props.onToggle) {
|
|
105
|
+
return (
|
|
106
|
+
<button
|
|
107
|
+
aria-expanded={props.expanded ?? false}
|
|
108
|
+
className={className}
|
|
109
|
+
onClick={props.onToggle}
|
|
110
|
+
title={props.tooltip}
|
|
111
|
+
type="button"
|
|
112
|
+
>
|
|
113
|
+
{content}
|
|
114
|
+
</button>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
return (
|
|
118
|
+
<div className={className} title={props.tooltip}>
|
|
119
|
+
{content}
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Place one extra line under the title of a `DetailsRow`. */
|
|
125
|
+
export function DetailsRowLine(props: {
|
|
126
|
+
children: ReactNode;
|
|
127
|
+
className?: string;
|
|
128
|
+
}) {
|
|
129
|
+
return (
|
|
130
|
+
<div
|
|
131
|
+
className={cn(
|
|
132
|
+
"col-start-2 col-end-4 min-w-0 text-xs leading-4 text-dashboard-text-muted",
|
|
133
|
+
props.className,
|
|
134
|
+
)}
|
|
135
|
+
>
|
|
136
|
+
{props.children}
|
|
137
|
+
</div>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import type { ConversationReportEvent } from "@sentry/junior/api/schema";
|
|
2
|
-
import
|
|
2
|
+
import { ChevronRight } from "lucide-react";
|
|
3
|
+
import { useState, type ReactNode } from "react";
|
|
4
|
+
|
|
5
|
+
import { HighlightedCode } from "../code";
|
|
6
|
+
import { CopyButton } from "../components/CopyButton";
|
|
7
|
+
import {
|
|
8
|
+
Collapsible,
|
|
9
|
+
CollapsibleContent,
|
|
10
|
+
CollapsibleTrigger,
|
|
11
|
+
} from "../components/ui/collapsible";
|
|
12
|
+
import { dashboardDisclosureChevronClass } from "../styles";
|
|
3
13
|
|
|
4
14
|
import { RedactedMarker } from "./TranscriptRedacted";
|
|
5
15
|
import { TranscriptText } from "./TranscriptText";
|
|
@@ -178,14 +188,38 @@ export function EventDetails({ event }: { event: ConversationReportEvent }) {
|
|
|
178
188
|
</DetailSection>
|
|
179
189
|
) : null}
|
|
180
190
|
{content}
|
|
181
|
-
<
|
|
182
|
-
|
|
191
|
+
<RawEventJson event={event} />
|
|
192
|
+
</div>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function RawEventJson({ event }: { event: ConversationReportEvent }) {
|
|
197
|
+
const [open, setOpen] = useState(false);
|
|
198
|
+
const json = open ? JSON.stringify(event, null, 2) : "";
|
|
199
|
+
return (
|
|
200
|
+
<div className="relative min-w-0 border-t border-dashboard-border pt-4">
|
|
201
|
+
<Collapsible onOpenChange={setOpen} open={open}>
|
|
202
|
+
<CollapsibleTrigger className="group flex min-h-9 cursor-pointer items-center gap-1 pr-12 text-xs text-dashboard-text-muted hover:text-dashboard-text focus-visible:outline-2 focus-visible:outline-dashboard-focus">
|
|
203
|
+
<ChevronRight
|
|
204
|
+
aria-hidden="true"
|
|
205
|
+
className={dashboardDisclosureChevronClass}
|
|
206
|
+
/>
|
|
183
207
|
Raw JSON
|
|
184
|
-
</
|
|
185
|
-
<
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
208
|
+
</CollapsibleTrigger>
|
|
209
|
+
<CollapsibleContent>
|
|
210
|
+
<div className="absolute right-0 top-4">
|
|
211
|
+
<CopyButton getText={() => json} label="Copy JSON" />
|
|
212
|
+
</div>
|
|
213
|
+
<div
|
|
214
|
+
aria-label="Raw event JSON"
|
|
215
|
+
className="mt-3 max-h-80 min-w-0 overflow-auto overscroll-contain rounded-lg border border-dashboard-border bg-dashboard-surface-panel p-3 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-dashboard-focus"
|
|
216
|
+
role="region"
|
|
217
|
+
tabIndex={0}
|
|
218
|
+
>
|
|
219
|
+
<HighlightedCode code={json} language="json" />
|
|
220
|
+
</div>
|
|
221
|
+
</CollapsibleContent>
|
|
222
|
+
</Collapsible>
|
|
189
223
|
</div>
|
|
190
224
|
);
|
|
191
225
|
}
|