@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
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import type { PluginOperationalReport } from "@sentry/junior/api/schema";
|
|
2
2
|
|
|
3
|
+
import { Bar, BarChart, CartesianGrid } from "recharts";
|
|
3
4
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
activityChartMargin,
|
|
6
|
+
ActivityChartTooltipContent,
|
|
7
|
+
ActivityChartXAxis,
|
|
8
|
+
ActivityChartYAxis,
|
|
9
|
+
formatActivityCategoryLabel,
|
|
8
10
|
formatActivityCategoryTooltipLabel,
|
|
9
11
|
isActivityHourBucket,
|
|
10
12
|
} from "../../components/charts/ActivityChart";
|
|
11
13
|
import { ChartLegend } from "../../components/charts/ChartLegend";
|
|
12
14
|
import type { TimeRangeDays } from "../../components/controls/TimeRangeSelector";
|
|
13
|
-
import {
|
|
15
|
+
import { ChartContainer, ChartTooltip } from "../../components/ui/chart";
|
|
14
16
|
import { formatCostSummary } from "../../format";
|
|
15
17
|
|
|
16
18
|
type Widget = NonNullable<PluginOperationalReport["widgets"]>[number];
|
|
@@ -35,34 +37,54 @@ function seriesColor(series: Widget["series"][number], index: number): string {
|
|
|
35
37
|
: (colors[index % colors.length] ?? colors[0]);
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const { widget } = props;
|
|
40
|
+
/**
|
|
41
|
+
* Pick the categories a plugin chart shows for the page range and the value
|
|
42
|
+
* scale they need. The scale always includes zero and is never empty.
|
|
43
|
+
*/
|
|
44
|
+
export function pluginChartScale(widget: Widget, range?: TimeRangeDays) {
|
|
44
45
|
const categories = widget.timeRangeDays
|
|
45
|
-
? widget.categories.slice(-visibleCategoryCount(widget,
|
|
46
|
+
? widget.categories.slice(-visibleCategoryCount(widget, range))
|
|
46
47
|
: widget.categories;
|
|
47
|
-
const seriesFormat = commonSeriesFormat(widget);
|
|
48
|
-
const layout = createActivityChartLayout(250, {
|
|
49
|
-
bottom: 36,
|
|
50
|
-
left: seriesFormat === "usd" ? 104 : 56,
|
|
51
|
-
right: 12,
|
|
52
|
-
top: 16,
|
|
53
|
-
width: 520,
|
|
54
|
-
});
|
|
55
|
-
const step = layout.plotWidth / Math.max(1, categories.length);
|
|
56
|
-
const groupWidth = Math.min(72, step * 0.72);
|
|
57
|
-
const barWidth = groupWidth / Math.max(1, widget.series.length);
|
|
58
48
|
const values = categories.flatMap((category) =>
|
|
59
49
|
widget.series.map((series) => category.values[series.key] ?? 0),
|
|
60
50
|
);
|
|
61
51
|
const minimum = Math.min(0, ...values);
|
|
62
52
|
const dataMaximum = Math.max(0, ...values);
|
|
63
53
|
const maximum = dataMaximum === minimum ? minimum + 1 : dataMaximum;
|
|
64
|
-
|
|
65
|
-
|
|
54
|
+
return { categories, maximum, minimum };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Render a validated plugin-owned categorical bar chart. */
|
|
58
|
+
export function PluginBarChart(props: {
|
|
59
|
+
range?: TimeRangeDays;
|
|
60
|
+
widget: Widget;
|
|
61
|
+
}) {
|
|
62
|
+
const { widget } = props;
|
|
63
|
+
const { categories, maximum, minimum } = pluginChartScale(
|
|
64
|
+
widget,
|
|
65
|
+
props.range,
|
|
66
|
+
);
|
|
67
|
+
const seriesFormat = commonSeriesFormat(widget);
|
|
68
|
+
// Series keys come from plugins, so chart keys use the series index.
|
|
69
|
+
const data = categories.map((category) => ({
|
|
70
|
+
category,
|
|
71
|
+
id: category.id,
|
|
72
|
+
...Object.fromEntries(
|
|
73
|
+
widget.series.map((series, index) => [
|
|
74
|
+
`series-${index}`,
|
|
75
|
+
category.values[series.key] ?? 0,
|
|
76
|
+
]),
|
|
77
|
+
),
|
|
78
|
+
}));
|
|
79
|
+
const labels = new Map(
|
|
80
|
+
categories.map((category) => [category.id, category.label]),
|
|
81
|
+
);
|
|
82
|
+
const chartConfig = Object.fromEntries(
|
|
83
|
+
widget.series.map((series, index) => [
|
|
84
|
+
`series-${index}`,
|
|
85
|
+
{ color: seriesColor(series, index), label: series.label },
|
|
86
|
+
]),
|
|
87
|
+
);
|
|
66
88
|
|
|
67
89
|
return (
|
|
68
90
|
<div className="overflow-hidden rounded-md border border-dashboard-border bg-dashboard-surface-panel">
|
|
@@ -93,69 +115,62 @@ export function PluginBarChart(props: {
|
|
|
93
115
|
{widget.emptyText ?? "No chart data."}
|
|
94
116
|
</p>
|
|
95
117
|
) : (
|
|
96
|
-
<
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
layout={layout}
|
|
118
|
+
<ChartContainer
|
|
119
|
+
className="aspect-auto h-60 w-full"
|
|
120
|
+
config={chartConfig}
|
|
100
121
|
>
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
122
|
+
<BarChart
|
|
123
|
+
aria-label={widget.title}
|
|
124
|
+
data={data}
|
|
125
|
+
margin={activityChartMargin}
|
|
126
|
+
role="img"
|
|
127
|
+
>
|
|
128
|
+
<CartesianGrid strokeDasharray="3 5" vertical={false} />
|
|
129
|
+
<ActivityChartYAxis
|
|
130
|
+
format={(value) => formatChartValue(value, seriesFormat)}
|
|
131
|
+
maximum={maximum}
|
|
132
|
+
minimum={minimum}
|
|
133
|
+
/>
|
|
134
|
+
<ActivityChartXAxis
|
|
135
|
+
centered
|
|
136
|
+
dataKey="id"
|
|
137
|
+
formatLabel={(id) =>
|
|
138
|
+
formatActivityCategoryLabel(labels.get(id) ?? id)
|
|
139
|
+
}
|
|
140
|
+
keys={categories.map((category) => category.id)}
|
|
141
|
+
/>
|
|
142
|
+
<ChartTooltip
|
|
143
|
+
content={
|
|
144
|
+
<ActivityChartTooltipContent
|
|
145
|
+
formatLabel={(id) =>
|
|
146
|
+
formatActivityCategoryTooltipLabel(labels.get(id) ?? id)
|
|
147
|
+
}
|
|
148
|
+
rows={({ category }: (typeof data)[number]) =>
|
|
149
|
+
widget.series.map((series) => [
|
|
150
|
+
series.label,
|
|
151
|
+
formatChartValue(
|
|
152
|
+
category.values[series.key] ?? 0,
|
|
153
|
+
series.format,
|
|
154
|
+
),
|
|
155
|
+
])
|
|
156
|
+
}
|
|
112
157
|
/>
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
value ? 2 : 0
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
content={`${series.label}: ${formatted}`}
|
|
130
|
-
key={`${category.id}:${series.key}`}
|
|
131
|
-
label={formatActivityCategoryTooltipLabel(category.label)}
|
|
132
|
-
>
|
|
133
|
-
<rect
|
|
134
|
-
aria-label={`${formatActivityCategoryTooltipLabel(category.label)}, ${series.label}: ${formatted}`}
|
|
135
|
-
fill={seriesColor(series, seriesIndex)}
|
|
136
|
-
height={renderedHeight}
|
|
137
|
-
opacity={value ? 0.82 : 0.1}
|
|
138
|
-
rx="1.5"
|
|
139
|
-
tabIndex={value ? 0 : -1}
|
|
140
|
-
width={barWidth * 0.75}
|
|
141
|
-
x={
|
|
142
|
-
layout.left +
|
|
143
|
-
categoryIndex * step +
|
|
144
|
-
(step - groupWidth) / 2 +
|
|
145
|
-
seriesIndex * barWidth
|
|
146
|
-
}
|
|
147
|
-
y={value >= 0 ? zeroY - renderedHeight : zeroY}
|
|
148
|
-
/>
|
|
149
|
-
</Tooltip>
|
|
150
|
-
);
|
|
151
|
-
}),
|
|
152
|
-
)}
|
|
153
|
-
<ChartCategoryLabels
|
|
154
|
-
categories={categories}
|
|
155
|
-
layout={layout}
|
|
156
|
-
xPosition={(index) => layout.left + index * step + step / 2}
|
|
157
|
-
/>
|
|
158
|
-
</ChartSvg>
|
|
158
|
+
}
|
|
159
|
+
/>
|
|
160
|
+
{widget.series.map((series, index) => (
|
|
161
|
+
<Bar
|
|
162
|
+
dataKey={`series-${index}`}
|
|
163
|
+
fill={`var(--color-series-${index})`}
|
|
164
|
+
fillOpacity={0.82}
|
|
165
|
+
isAnimationActive={false}
|
|
166
|
+
key={series.key}
|
|
167
|
+
maxBarSize={54 / widget.series.length}
|
|
168
|
+
minPointSize={(value) => (value ? 2 : 0)}
|
|
169
|
+
radius={1.5}
|
|
170
|
+
/>
|
|
171
|
+
))}
|
|
172
|
+
</BarChart>
|
|
173
|
+
</ChartContainer>
|
|
159
174
|
)}
|
|
160
175
|
</div>
|
|
161
176
|
);
|
|
@@ -199,7 +214,11 @@ function commonSeriesFormat(widget: Widget): "usd" | undefined {
|
|
|
199
214
|
return formats.size === 1 ? widget.series[0]?.format : undefined;
|
|
200
215
|
}
|
|
201
216
|
|
|
202
|
-
|
|
217
|
+
/** Format a plugin chart value as USD or as a number without float noise. */
|
|
218
|
+
export function formatChartValue(
|
|
219
|
+
value: number,
|
|
220
|
+
format: "usd" | undefined,
|
|
221
|
+
): string {
|
|
203
222
|
return format === "usd"
|
|
204
223
|
? formatCostSummary({ total: value })
|
|
205
224
|
: formatChartNumber(value);
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import type { PluginOperationalReport } from "@sentry/junior/api/schema";
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
Table,
|
|
5
|
+
TableBody,
|
|
6
|
+
TableCell,
|
|
7
|
+
TableHead,
|
|
8
|
+
TableHeader,
|
|
9
|
+
TableRow,
|
|
10
|
+
} from "../../components/ui/table";
|
|
3
11
|
import { cn } from "../../styles";
|
|
4
12
|
|
|
5
13
|
export type PluginReportRecordSetType = NonNullable<
|
|
@@ -63,42 +71,35 @@ export function PluginReportRecordSet(props: {
|
|
|
63
71
|
</div>
|
|
64
72
|
))}
|
|
65
73
|
</div>
|
|
66
|
-
<div className="hidden
|
|
67
|
-
<
|
|
68
|
-
<
|
|
69
|
-
<
|
|
74
|
+
<div className="hidden border-t border-dashboard-border-subtle sm:block">
|
|
75
|
+
<Table className="min-w-[36rem]">
|
|
76
|
+
<TableHeader className="bg-dashboard-overlay-soft">
|
|
77
|
+
<TableRow>
|
|
70
78
|
{fields.map((field) => (
|
|
71
|
-
<
|
|
72
|
-
className="border-b border-dashboard-border-subtle px-5 py-2.5 font-medium"
|
|
73
|
-
key={field.key}
|
|
74
|
-
scope="col"
|
|
75
|
-
>
|
|
79
|
+
<TableHead className="px-5" key={field.key} scope="col">
|
|
76
80
|
{field.label}
|
|
77
|
-
</
|
|
81
|
+
</TableHead>
|
|
78
82
|
))}
|
|
79
|
-
</
|
|
80
|
-
</
|
|
81
|
-
<
|
|
83
|
+
</TableRow>
|
|
84
|
+
</TableHeader>
|
|
85
|
+
<TableBody>
|
|
82
86
|
{records.map((record) => (
|
|
83
|
-
<
|
|
84
|
-
className={
|
|
85
|
-
"transition-colors hover:bg-dashboard-fill-soft",
|
|
86
|
-
rowToneClass(record.tone),
|
|
87
|
-
)}
|
|
87
|
+
<TableRow
|
|
88
|
+
className={rowToneClass(record.tone)}
|
|
88
89
|
key={record.id}
|
|
89
90
|
>
|
|
90
91
|
{fields.map((field) => (
|
|
91
|
-
<
|
|
92
|
-
className="max-w-72 truncate
|
|
92
|
+
<TableCell
|
|
93
|
+
className="max-w-72 truncate px-5 py-3 font-mono text-xs text-dashboard-text-muted"
|
|
93
94
|
key={field.key}
|
|
94
95
|
>
|
|
95
96
|
{record.values[field.key] ?? ""}
|
|
96
|
-
</
|
|
97
|
+
</TableCell>
|
|
97
98
|
))}
|
|
98
|
-
</
|
|
99
|
+
</TableRow>
|
|
99
100
|
))}
|
|
100
|
-
</
|
|
101
|
-
</
|
|
101
|
+
</TableBody>
|
|
102
|
+
</Table>
|
|
102
103
|
</div>
|
|
103
104
|
</>
|
|
104
105
|
)}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# System pages
|
|
2
|
+
|
|
3
|
+
## Workspaces
|
|
4
|
+
|
|
5
|
+
`/system/workspaces` lists the Workspace recipes. The URL selects one recipe:
|
|
6
|
+
`/system/workspaces/:id` keeps the list and opens `WorkspaceDetails` in the
|
|
7
|
+
shared `DetailsPanel`. The panel is 400px wide at 1280px and above. Smaller
|
|
8
|
+
screens use the bottom sheet. Repository pages link to these details.
|
|
9
|
+
|
|
10
|
+
The panel shows the list item at once. The detail read adds the current
|
|
11
|
+
snapshot, because the list report does not include snapshots. Snapshot loading
|
|
12
|
+
and errors stay in the Snapshot row. Usage shows switches for the last 30 days.
|
|
13
|
+
Workspace switch counts are daily only.
|
|
14
|
+
|
|
15
|
+
Edit and Delete are in the panel header. List rows have no actions. Edit opens
|
|
16
|
+
the full-page editor at `/system/workspaces/:id/edit`. Save and Cancel return to
|
|
17
|
+
the details. A new Workspace opens its details after create.
|
|
18
|
+
|
|
19
|
+
Close and Escape return focus to the selected row. `WorkspaceDeleteDialog`
|
|
20
|
+
names the recipe, keeps errors inside the dialog, and blocks dismissal while the
|
|
21
|
+
request runs. A successful delete closes the details and shows a toast.
|
|
22
|
+
|
|
23
|
+
Browser coverage lives in `e2e/system.spec.ts`.
|
|
@@ -12,10 +12,14 @@ const systemNavigationItems = [
|
|
|
12
12
|
];
|
|
13
13
|
|
|
14
14
|
/** Render the stable secondary navigation shared by System pages. */
|
|
15
|
-
export function SystemNavigation(props: {
|
|
15
|
+
export function SystemNavigation(props: {
|
|
16
|
+
children: ReactNode;
|
|
17
|
+
className?: string;
|
|
18
|
+
}) {
|
|
16
19
|
return (
|
|
17
20
|
<SecondaryNavigation
|
|
18
21
|
ariaLabel="System navigation"
|
|
22
|
+
className={props.className}
|
|
19
23
|
items={systemNavigationItems}
|
|
20
24
|
>
|
|
21
25
|
{props.children}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
3
|
+
import type {
|
|
4
|
+
WorkspaceListReport,
|
|
5
|
+
WorkspaceReport,
|
|
6
|
+
} from "@sentry/junior/api/schema";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
AlertDialog,
|
|
10
|
+
AlertDialogAction,
|
|
11
|
+
AlertDialogCancel,
|
|
12
|
+
AlertDialogContent,
|
|
13
|
+
AlertDialogDescription,
|
|
14
|
+
AlertDialogFooter,
|
|
15
|
+
AlertDialogTitle,
|
|
16
|
+
} from "../../components/ui/alert-dialog";
|
|
17
|
+
import { useToastManager } from "../../components/ui/toast";
|
|
18
|
+
import { FormNotice } from "../../components/FormNotice";
|
|
19
|
+
import { deleteDashboardResource } from "../../http";
|
|
20
|
+
import {
|
|
21
|
+
readWorkspaceApiError,
|
|
22
|
+
workspaceQueryKey,
|
|
23
|
+
workspacesQueryKey,
|
|
24
|
+
} from "./workspaceQueries";
|
|
25
|
+
|
|
26
|
+
/** Confirm Workspace deletion by name and keep a failed request open for retry. */
|
|
27
|
+
export function WorkspaceDeleteDialog(props: {
|
|
28
|
+
onClose(): void;
|
|
29
|
+
onDeleted(): void;
|
|
30
|
+
returnFocus(): HTMLElement | false;
|
|
31
|
+
workspace: WorkspaceReport;
|
|
32
|
+
}) {
|
|
33
|
+
const [open, setOpen] = useState(true);
|
|
34
|
+
const queryClient = useQueryClient();
|
|
35
|
+
const toast = useToastManager();
|
|
36
|
+
const deletion = useMutation({
|
|
37
|
+
mutationFn: () =>
|
|
38
|
+
deleteDashboardResource(
|
|
39
|
+
`/api/workspaces/${encodeURIComponent(props.workspace.id)}`,
|
|
40
|
+
),
|
|
41
|
+
onSuccess: async () => {
|
|
42
|
+
await queryClient.cancelQueries({ queryKey: workspacesQueryKey });
|
|
43
|
+
queryClient.setQueryData<WorkspaceListReport>(
|
|
44
|
+
workspacesQueryKey,
|
|
45
|
+
(current) =>
|
|
46
|
+
current && {
|
|
47
|
+
...current,
|
|
48
|
+
workspaces: current.workspaces.filter(
|
|
49
|
+
(item) => item.id !== props.workspace.id,
|
|
50
|
+
),
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
props.onDeleted();
|
|
54
|
+
// Drop the detail read too, so a later visit shows the not-found state.
|
|
55
|
+
queryClient.removeQueries({
|
|
56
|
+
queryKey: workspaceQueryKey(props.workspace.id),
|
|
57
|
+
});
|
|
58
|
+
toast.add({
|
|
59
|
+
title: "Workspace deleted",
|
|
60
|
+
description: props.workspace.name,
|
|
61
|
+
type: "success",
|
|
62
|
+
});
|
|
63
|
+
setOpen(false);
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
return (
|
|
67
|
+
<AlertDialog
|
|
68
|
+
open={open}
|
|
69
|
+
onOpenChange={(next, details) => {
|
|
70
|
+
if (deletion.isPending) details.cancel();
|
|
71
|
+
else setOpen(next);
|
|
72
|
+
}}
|
|
73
|
+
onOpenChangeComplete={(next) => {
|
|
74
|
+
if (!next) props.onClose();
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
77
|
+
<AlertDialogContent finalFocus={props.returnFocus}>
|
|
78
|
+
<AlertDialogTitle>Delete Workspace?</AlertDialogTitle>
|
|
79
|
+
<AlertDialogDescription>
|
|
80
|
+
Delete{" "}
|
|
81
|
+
<strong className="font-medium text-dashboard-text">
|
|
82
|
+
{props.workspace.name}
|
|
83
|
+
</strong>
|
|
84
|
+
? Active conversations keep their current Sandbox until the next
|
|
85
|
+
switch.
|
|
86
|
+
</AlertDialogDescription>
|
|
87
|
+
{deletion.error ? (
|
|
88
|
+
<FormNotice
|
|
89
|
+
tone="error"
|
|
90
|
+
title={readWorkspaceApiError(
|
|
91
|
+
deletion.error,
|
|
92
|
+
"Could not delete the Workspace. Try again.",
|
|
93
|
+
)}
|
|
94
|
+
/>
|
|
95
|
+
) : null}
|
|
96
|
+
<AlertDialogFooter>
|
|
97
|
+
<AlertDialogCancel disabled={deletion.isPending}>
|
|
98
|
+
Cancel
|
|
99
|
+
</AlertDialogCancel>
|
|
100
|
+
<AlertDialogAction
|
|
101
|
+
disabled={deletion.isPending}
|
|
102
|
+
onClick={() => deletion.mutate()}
|
|
103
|
+
variant="danger"
|
|
104
|
+
>
|
|
105
|
+
{deletion.isPending ? "Deleting…" : "Delete Workspace"}
|
|
106
|
+
</AlertDialogAction>
|
|
107
|
+
</AlertDialogFooter>
|
|
108
|
+
</AlertDialogContent>
|
|
109
|
+
</AlertDialog>
|
|
110
|
+
);
|
|
111
|
+
}
|