@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,4 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
+
import { matchPath, useLocation } from "react-router";
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
PageContentSkeleton,
|
|
@@ -8,29 +9,47 @@ import { PageHeader } from "../../components/layout/PageHeader";
|
|
|
8
9
|
import { PageLayout } from "../../components/layout/PageLayout";
|
|
9
10
|
import { SecondaryNavigation } from "../../components/layout/SecondaryNavigation";
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
/** Identify the Automation list and its selected Automation routes. */
|
|
13
|
+
function isAutomationListPath(pathname: string): boolean {
|
|
14
|
+
const match = matchPath("/automations/:automationId", pathname);
|
|
15
|
+
return Boolean(match && match.params.automationId?.toLowerCase() !== "runs");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Identify Automation routes whose list scrolls on its own beside details. */
|
|
19
|
+
export function hasAutomationDetailsFrame(pathname: string): boolean {
|
|
20
|
+
return (
|
|
21
|
+
isAutomationListPath(pathname) ||
|
|
22
|
+
Boolean(matchPath("/automations/runs", pathname)) ||
|
|
23
|
+
Boolean(matchPath("/automations/:automationId/executions", pathname))
|
|
24
|
+
);
|
|
25
|
+
}
|
|
12
26
|
|
|
13
|
-
function
|
|
14
|
-
|
|
15
|
-
pathname
|
|
16
|
-
|
|
17
|
-
)
|
|
18
|
-
return true;
|
|
19
|
-
const match = pathname.match(/^\/automations\/([^/]+)$/);
|
|
20
|
-
return Boolean(match?.[1] && !RESERVED_TASK_SEGMENTS.has(match[1]));
|
|
27
|
+
function isAutomationListTabPath(pathname: string): boolean {
|
|
28
|
+
return (
|
|
29
|
+
isAutomationListPath(pathname) ||
|
|
30
|
+
Boolean(matchPath("/automations/:automationId/edit", pathname))
|
|
31
|
+
);
|
|
21
32
|
}
|
|
22
33
|
|
|
23
34
|
const taskNavigationItems = [
|
|
24
35
|
{ end: true, label: "Overview", to: "/automations" },
|
|
25
|
-
{
|
|
36
|
+
{
|
|
37
|
+
isActive: isAutomationListTabPath,
|
|
38
|
+
label: "Automations",
|
|
39
|
+
to: "/automations/list",
|
|
40
|
+
},
|
|
26
41
|
{ label: "Runs", to: "/automations/runs" },
|
|
27
42
|
];
|
|
28
43
|
|
|
29
44
|
/** Keep Automation navigation consistent across lists, details, and editing. */
|
|
30
|
-
export function AutomationsNavigation(props: {
|
|
45
|
+
export function AutomationsNavigation(props: {
|
|
46
|
+
children: ReactNode;
|
|
47
|
+
className?: string;
|
|
48
|
+
}) {
|
|
31
49
|
return (
|
|
32
50
|
<SecondaryNavigation
|
|
33
51
|
ariaLabel="Automations navigation"
|
|
52
|
+
className={props.className}
|
|
34
53
|
items={taskNavigationItems}
|
|
35
54
|
>
|
|
36
55
|
{props.children}
|
|
@@ -40,9 +59,20 @@ export function AutomationsNavigation(props: { children: ReactNode }) {
|
|
|
40
59
|
|
|
41
60
|
/** Place the shared secondary navigation above one Automations page. */
|
|
42
61
|
export function AutomationsPageLayout(props: { children: ReactNode }) {
|
|
62
|
+
const list = hasAutomationDetailsFrame(useLocation().pathname);
|
|
43
63
|
return (
|
|
44
|
-
<AutomationsNavigation
|
|
45
|
-
|
|
64
|
+
<AutomationsNavigation
|
|
65
|
+
className={
|
|
66
|
+
list
|
|
67
|
+
? "grid h-full min-h-0 grid-rows-[auto_minmax(0,1fr)] md:grid-rows-[minmax(0,1fr)]"
|
|
68
|
+
: undefined
|
|
69
|
+
}
|
|
70
|
+
>
|
|
71
|
+
{list ? (
|
|
72
|
+
<div className="min-h-0">{props.children}</div>
|
|
73
|
+
) : (
|
|
74
|
+
<PageLayout>{props.children}</PageLayout>
|
|
75
|
+
)}
|
|
46
76
|
</AutomationsNavigation>
|
|
47
77
|
);
|
|
48
78
|
}
|
|
@@ -54,10 +84,12 @@ export function AutomationsRouteLoading(props: {
|
|
|
54
84
|
title: string;
|
|
55
85
|
variant: Extract<PageContentSkeletonVariant, "list" | "stats">;
|
|
56
86
|
}) {
|
|
57
|
-
|
|
87
|
+
const list = hasAutomationDetailsFrame(useLocation().pathname);
|
|
88
|
+
const content = (
|
|
58
89
|
<>
|
|
59
90
|
<PageHeader description={props.description} title={props.title} />
|
|
60
91
|
<PageContentSkeleton label={props.label} variant={props.variant} />
|
|
61
92
|
</>
|
|
62
93
|
);
|
|
94
|
+
return list ? <PageLayout>{content}</PageLayout> : content;
|
|
63
95
|
}
|
|
@@ -28,3 +28,60 @@ shows its error when a public reader cannot make a version active.
|
|
|
28
28
|
|
|
29
29
|
Browser behavior lives in `e2e/automation-editor.spec.ts`. API and persistence
|
|
30
30
|
coverage lives in core's `tests/integration/api/automation-edits.test.ts`.
|
|
31
|
+
|
|
32
|
+
## Automation details
|
|
33
|
+
|
|
34
|
+
The URL selects one Automation in the list. Route matching follows React
|
|
35
|
+
Router, including trailing slashes. `AutomationDetails` uses the shared
|
|
36
|
+
`DetailsPanel`: a 400px panel at 1280px and above, and a Base UI bottom sheet on
|
|
37
|
+
smaller screens. The list stays interactive beside the panel. Each pane scrolls
|
|
38
|
+
on its own. `VisualViewportShell` owns the viewport height and mobile body lock;
|
|
39
|
+
`AutomationsPageLayout` keeps navigation above the list and details.
|
|
40
|
+
|
|
41
|
+
Loading and access errors stay inside the details frame. Closing keeps the list
|
|
42
|
+
query string and returns focus to the selected row. If that row is not on the
|
|
43
|
+
current page, focus returns to the list. Selecting another Automation resets
|
|
44
|
+
its disclosures and lifecycle feedback. Deleting the selected Automation closes
|
|
45
|
+
its details. Edit and version history retain the list query string.
|
|
46
|
+
|
|
47
|
+
The existing Automation browser journeys cover selection, loading and retry,
|
|
48
|
+
focus, mobile dismissal, deletion, and lifecycle actions. Use mock reporting to
|
|
49
|
+
review scheduled and event Automations in both themes at desktop and mobile
|
|
50
|
+
widths. Keep layout and scrollbar checks in manual QA.
|
|
51
|
+
|
|
52
|
+
Details lead with lifecycle, the next trigger, and the last execution result.
|
|
53
|
+
Instructions follow, then compact trigger and delivery rows. The sheet header
|
|
54
|
+
uses the Automation name. `AutomationStatus`
|
|
55
|
+
keeps a failed execution distinct from whether future runs remain enabled. It
|
|
56
|
+
shows block reasons and links to the last Conversation when attention is needed.
|
|
57
|
+
Scheduled times use the shared shadcn Accordion to show the schedule timezone
|
|
58
|
+
and browser time. About this Automation uses the same component. Both start
|
|
59
|
+
collapsed and reset when the selection changes.
|
|
60
|
+
Paused, blocked, completed, and unavailable Automations explain why there is no
|
|
61
|
+
next run instead of showing "None".
|
|
62
|
+
|
|
63
|
+
`AutomationRecentRuns` uses the existing execution-history query and cache. It
|
|
64
|
+
shows up to three newest terminal runs, links to their Conversations when one
|
|
65
|
+
was recorded, and displays recorded runtime and estimated cost. It omits run
|
|
66
|
+
titles that repeat the Automation name. Its loading,
|
|
67
|
+
empty, and retry states do not replace Automation settings. It does not infer
|
|
68
|
+
queued or running work from terminal records.
|
|
69
|
+
|
|
70
|
+
`AutomationLifecycle` owns revision-checked actions in a fixed footer. Edit and
|
|
71
|
+
Delete use labeled icons there; list rows have no action menu. Public readers
|
|
72
|
+
can edit settings. Delete and lifecycle actions stay creator-only. The shared
|
|
73
|
+
shadcn Popover provides pause help beside those controls. The
|
|
74
|
+
scroll region above it owns the summary and settings. Creator, creation date,
|
|
75
|
+
connected accounts, run counts, and version history stay under About this
|
|
76
|
+
Automation. Selection changes remount the content and actions together.
|
|
77
|
+
|
|
78
|
+
Filters use a shadcn Popover at 768px and above and the shared BottomSheet below
|
|
79
|
+
that width. Both edit the same URL filters immediately. Active filter chips
|
|
80
|
+
remove one filter without clearing search, scope, or other filters. The mobile
|
|
81
|
+
sheet fits its controls instead of taking the full details height.
|
|
82
|
+
|
|
83
|
+
`AutomationDeleteDialog` names the Automation before deletion. It keeps errors
|
|
84
|
+
and retry inside the dialog and blocks dismissal while the request is pending.
|
|
85
|
+
Cancel returns focus to Delete. Successful deletion closes the selected details
|
|
86
|
+
and returns focus to the list. Success toasts confirm Save, Pause, Resume, and
|
|
87
|
+
Delete. Their state belongs to the mounted dashboard's Toaster provider.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** List an Event trigger's filters. Every filter must match. */
|
|
2
|
+
export function TriggerFilters(props: { filters: readonly string[] }) {
|
|
3
|
+
if (props.filters.length === 0) {
|
|
4
|
+
return (
|
|
5
|
+
<p className="m-0 mt-1 text-xs text-dashboard-text-muted">
|
|
6
|
+
No filters. Runs on every selected event.
|
|
7
|
+
</p>
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
return (
|
|
11
|
+
<div className="mt-1 text-xs text-dashboard-text-muted">
|
|
12
|
+
<p className="m-0">Every filter must match:</p>
|
|
13
|
+
<ul className="m-0 list-disc pl-5">
|
|
14
|
+
{props.filters.map((filter) => (
|
|
15
|
+
<li className="break-words" key={filter}>
|
|
16
|
+
{filter}
|
|
17
|
+
</li>
|
|
18
|
+
))}
|
|
19
|
+
</ul>
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
AutomationExecution,
|
|
3
|
+
AutomationSummary,
|
|
4
|
+
} from "@sentry/junior/api/schema";
|
|
5
|
+
import type { StatusChipTone } from "../../components/StatusChip";
|
|
2
6
|
import { formatTime } from "../../format";
|
|
3
7
|
|
|
4
8
|
type ScheduledAutomation = Extract<AutomationSummary, { kind: "scheduled" }>;
|
|
5
|
-
type EventAutomation = Extract<AutomationSummary, { kind: "event" }>;
|
|
6
9
|
|
|
7
10
|
/** Show the schedule with its timezone unless the schedule text already names it. */
|
|
8
11
|
export function automationScheduleLabel(
|
|
@@ -13,31 +16,42 @@ export function automationScheduleLabel(
|
|
|
13
16
|
: `${automation.schedule} (${automation.timezone})`;
|
|
14
17
|
}
|
|
15
18
|
|
|
16
|
-
/**
|
|
17
|
-
export function
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
/** Summarize event match conditions as one readable line. */
|
|
24
|
-
export function automationEventConditions(automation: EventAutomation): string {
|
|
25
|
-
return Object.entries(automation.match ?? {})
|
|
26
|
-
.map(
|
|
27
|
-
([field, value]) =>
|
|
28
|
-
`${field}: ${Array.isArray(value) ? value.join(" or ") : String(value)}`,
|
|
29
|
-
)
|
|
30
|
-
.join(" · ");
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** Format a run time with date, time, and zone for details and tooltips. */
|
|
34
|
-
export function formatAutomationRunTime(value: string): string {
|
|
35
|
-
return formatTime(value, {
|
|
19
|
+
/** Format a run in an explicit timezone, or the dashboard timezone by default. */
|
|
20
|
+
export function formatAutomationRunTime(
|
|
21
|
+
value: string,
|
|
22
|
+
timeZone?: string,
|
|
23
|
+
): string {
|
|
24
|
+
const options: Intl.DateTimeFormatOptions = {
|
|
36
25
|
day: "numeric",
|
|
37
26
|
hour: "numeric",
|
|
38
27
|
minute: "2-digit",
|
|
39
28
|
month: "short",
|
|
40
29
|
timeZoneName: "short",
|
|
41
30
|
year: "numeric",
|
|
42
|
-
}
|
|
31
|
+
};
|
|
32
|
+
return timeZone
|
|
33
|
+
? new Intl.DateTimeFormat(undefined, { ...options, timeZone }).format(
|
|
34
|
+
new Date(value),
|
|
35
|
+
)
|
|
36
|
+
: formatTime(value, options);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Map a run outcome to its status color. */
|
|
40
|
+
export function automationRunStatusTone(
|
|
41
|
+
status: AutomationExecution["status"],
|
|
42
|
+
): StatusChipTone {
|
|
43
|
+
if (status === "failed") return "danger";
|
|
44
|
+
if (status === "blocked") return "warning";
|
|
45
|
+
return "success";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Name a run by its Conversation title, else by its Automation. */
|
|
49
|
+
export function automationRunTitle(
|
|
50
|
+
run: AutomationExecution,
|
|
51
|
+
automationTitle: string,
|
|
52
|
+
): string {
|
|
53
|
+
return (
|
|
54
|
+
run.title?.trim() ||
|
|
55
|
+
(run.conversationId ? automationTitle : "No conversation")
|
|
56
|
+
);
|
|
43
57
|
}
|
|
@@ -4,24 +4,32 @@ import {
|
|
|
4
4
|
type TimeRangeDays,
|
|
5
5
|
} from "../../components/controls/TimeRangeSelector";
|
|
6
6
|
import type { CodeActivityDay } from "@sentry/junior/api/schema";
|
|
7
|
+
import { Bar, BarChart, CartesianGrid } from "recharts";
|
|
7
8
|
import {
|
|
8
9
|
ActivityChartAverageLine,
|
|
9
|
-
ActivityChartDateLabels,
|
|
10
|
-
ActivityChartGrid,
|
|
11
10
|
activityChartAverage,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
activityChartMargin,
|
|
12
|
+
ActivityChartTooltipContent,
|
|
13
|
+
ActivityChartXAxis,
|
|
14
|
+
ActivityChartYAxis,
|
|
16
15
|
} from "../../components/charts/ActivityChart";
|
|
17
16
|
import { ChartLegend } from "../../components/charts/ChartLegend";
|
|
18
17
|
|
|
19
18
|
import { Card } from "../../components/layout/Card";
|
|
19
|
+
import { ChartContainer, ChartTooltip } from "../../components/ui/chart";
|
|
20
20
|
import { formatActivityChartAverage } from "../../format";
|
|
21
21
|
|
|
22
22
|
const CREATED_COLOR = "var(--color-cyan-300)";
|
|
23
23
|
const MERGED_COLOR = "var(--color-emerald-300)";
|
|
24
24
|
const CLOSED_COLOR = "var(--color-rose-400)";
|
|
25
|
+
const series = [
|
|
26
|
+
{ color: CREATED_COLOR, key: "created", label: "Created" },
|
|
27
|
+
{ color: MERGED_COLOR, key: "merged", label: "Merged" },
|
|
28
|
+
{ color: CLOSED_COLOR, key: "closed", label: "Closed" },
|
|
29
|
+
] as const;
|
|
30
|
+
const chartConfig = Object.fromEntries(
|
|
31
|
+
series.map((item) => [item.key, { color: item.color, label: item.label }]),
|
|
32
|
+
);
|
|
25
33
|
|
|
26
34
|
/** Render created, merged, and closed code changes over a trailing window. */
|
|
27
35
|
export function CodeActivityChart(props: {
|
|
@@ -33,12 +41,6 @@ export function CodeActivityChart(props: {
|
|
|
33
41
|
const bucketUnit = props.bucketUnit ?? "day";
|
|
34
42
|
|
|
35
43
|
const days = props.days;
|
|
36
|
-
const layout = createActivityChartLayout(220, { left: 40 });
|
|
37
|
-
const step =
|
|
38
|
-
days.length > 0 ? layout.plotWidth / days.length : layout.plotWidth;
|
|
39
|
-
const barWidth = Math.max(2, Math.min(10, step * 0.28));
|
|
40
|
-
const gap = Math.max(1, barWidth * 0.2);
|
|
41
|
-
const groupWidth = barWidth * 3 + gap * 2;
|
|
42
44
|
const totals = days.map((day) => day.created + day.merged + day.closed);
|
|
43
45
|
const maximum = Math.max(
|
|
44
46
|
1,
|
|
@@ -58,99 +60,55 @@ export function CodeActivityChart(props: {
|
|
|
58
60
|
Code changes created, merged, and closed each day.
|
|
59
61
|
</p>
|
|
60
62
|
</div>
|
|
61
|
-
<ChartLegend
|
|
62
|
-
ariaLabel="Code change legend"
|
|
63
|
-
items={[
|
|
64
|
-
{ color: CREATED_COLOR, key: "created", label: "Created" },
|
|
65
|
-
{ color: MERGED_COLOR, key: "merged", label: "Merged" },
|
|
66
|
-
{ color: CLOSED_COLOR, key: "closed", label: "Closed" },
|
|
67
|
-
]}
|
|
68
|
-
/>
|
|
63
|
+
<ChartLegend ariaLabel="Code change legend" items={series} />
|
|
69
64
|
</div>
|
|
70
65
|
|
|
71
66
|
<div className="relative mt-5 overflow-hidden">
|
|
72
|
-
<
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
layout={layout}
|
|
67
|
+
<ChartContainer
|
|
68
|
+
className="aspect-auto h-55 w-full"
|
|
69
|
+
config={chartConfig}
|
|
76
70
|
>
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
fill={entry.color}
|
|
119
|
-
height={height}
|
|
120
|
-
key={entry.key}
|
|
121
|
-
opacity={0.86}
|
|
122
|
-
rx="1"
|
|
123
|
-
width={barWidth}
|
|
124
|
-
x={x}
|
|
125
|
-
y={layout.top + layout.plotHeight - height}
|
|
126
|
-
/>
|
|
127
|
-
);
|
|
128
|
-
})}
|
|
129
|
-
<rect
|
|
130
|
-
fill="transparent"
|
|
131
|
-
height={layout.plotHeight}
|
|
132
|
-
width={Math.max(groupWidth, 8)}
|
|
133
|
-
x={groupX - (Math.max(groupWidth, 8) - groupWidth) / 2}
|
|
134
|
-
y={layout.top}
|
|
135
|
-
/>
|
|
136
|
-
</g>
|
|
137
|
-
</ActivityChartTooltip>
|
|
138
|
-
);
|
|
139
|
-
})}
|
|
140
|
-
<ActivityChartAverageLine
|
|
141
|
-
unit={timeRangeBucketAverageUnit(bucketUnit)}
|
|
142
|
-
average={average}
|
|
143
|
-
format={formatActivityChartAverage}
|
|
144
|
-
layout={layout}
|
|
145
|
-
maximum={maximum}
|
|
146
|
-
stroke="var(--color-slate-200)"
|
|
147
|
-
/>
|
|
148
|
-
<ActivityChartDateLabels
|
|
149
|
-
dates={days.map((day) => day.date)}
|
|
150
|
-
layout={layout}
|
|
151
|
-
xPosition={(index) => layout.left + index * step + step / 2}
|
|
152
|
-
/>
|
|
153
|
-
</ChartSvg>
|
|
71
|
+
<BarChart
|
|
72
|
+
aria-label={`Code changes during the last ${props.range === 1 ? "24 hours" : `${props.range} days`}`}
|
|
73
|
+
barGap={1}
|
|
74
|
+
data={days}
|
|
75
|
+
margin={activityChartMargin}
|
|
76
|
+
role="img"
|
|
77
|
+
>
|
|
78
|
+
<CartesianGrid strokeDasharray="3 5" vertical={false} />
|
|
79
|
+
<ActivityChartYAxis maximum={maximum} />
|
|
80
|
+
<ActivityChartXAxis keys={days.map((day) => day.date)} />
|
|
81
|
+
<ChartTooltip
|
|
82
|
+
content={
|
|
83
|
+
<ActivityChartTooltipContent
|
|
84
|
+
rows={(day: CodeActivityDay) =>
|
|
85
|
+
series.map((item) => [
|
|
86
|
+
item.label.toLowerCase(),
|
|
87
|
+
day[item.key],
|
|
88
|
+
])
|
|
89
|
+
}
|
|
90
|
+
/>
|
|
91
|
+
}
|
|
92
|
+
/>
|
|
93
|
+
{series.map((item) => (
|
|
94
|
+
<Bar
|
|
95
|
+
dataKey={item.key}
|
|
96
|
+
fill={`var(--color-${item.key})`}
|
|
97
|
+
fillOpacity={0.86}
|
|
98
|
+
isAnimationActive={false}
|
|
99
|
+
key={item.key}
|
|
100
|
+
maxBarSize={10}
|
|
101
|
+
radius={1}
|
|
102
|
+
/>
|
|
103
|
+
))}
|
|
104
|
+
<ActivityChartAverageLine
|
|
105
|
+
unit={timeRangeBucketAverageUnit(bucketUnit)}
|
|
106
|
+
average={average}
|
|
107
|
+
format={formatActivityChartAverage}
|
|
108
|
+
stroke="var(--color-slate-200)"
|
|
109
|
+
/>
|
|
110
|
+
</BarChart>
|
|
111
|
+
</ChartContainer>
|
|
154
112
|
{!hasActivity ? (
|
|
155
113
|
<div className="pointer-events-none absolute inset-0 grid place-items-center pt-12 font-sans text-xs text-dashboard-text-muted">
|
|
156
114
|
No code changes in this period.
|
|
@@ -12,6 +12,14 @@ import {
|
|
|
12
12
|
import { Card } from "../../components/layout/Card";
|
|
13
13
|
import { PageHeader } from "../../components/layout/PageHeader";
|
|
14
14
|
import { PageLayout } from "../../components/layout/PageLayout";
|
|
15
|
+
import {
|
|
16
|
+
Table,
|
|
17
|
+
TableBody,
|
|
18
|
+
TableCell,
|
|
19
|
+
TableHead,
|
|
20
|
+
TableHeader,
|
|
21
|
+
TableRow,
|
|
22
|
+
} from "../../components/ui/table";
|
|
15
23
|
import { CodeActivityChart } from "./CodeActivityChart";
|
|
16
24
|
import { CodeChangeRow } from "./CodeChangeRow";
|
|
17
25
|
import {
|
|
@@ -88,61 +96,50 @@ function CodeOverview(props: {
|
|
|
88
96
|
</EmptyTelemetry>
|
|
89
97
|
</div>
|
|
90
98
|
) : (
|
|
91
|
-
<
|
|
92
|
-
<
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
<
|
|
100
|
-
<
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
<td className="px-4 py-3 text-right font-sans text-sm tabular-nums whitespace-nowrap text-dashboard-text">
|
|
136
|
-
{formatMergeRate(repository.mergeRate)}
|
|
137
|
-
</td>
|
|
138
|
-
<td className="px-4 py-3 text-right font-sans text-sm tabular-nums whitespace-nowrap text-dashboard-text">
|
|
139
|
-
{formatCodeCost(repository.medianCostUsd)}
|
|
140
|
-
</td>
|
|
141
|
-
</tr>
|
|
142
|
-
))}
|
|
143
|
-
</tbody>
|
|
144
|
-
</table>
|
|
145
|
-
</div>
|
|
99
|
+
<Table className="min-w-[36rem] table-fixed">
|
|
100
|
+
<colgroup>
|
|
101
|
+
<col />
|
|
102
|
+
<col className="w-28" />
|
|
103
|
+
<col className="w-28" />
|
|
104
|
+
<col className="w-28" />
|
|
105
|
+
</colgroup>
|
|
106
|
+
<TableHeader>
|
|
107
|
+
<TableRow>
|
|
108
|
+
<TableHead className="px-4">Repository</TableHead>
|
|
109
|
+
<TableHead className="px-4 text-right">Created</TableHead>
|
|
110
|
+
<TableHead className="px-4 text-right">Merge rate</TableHead>
|
|
111
|
+
<TableHead className="px-4 text-right">Median cost</TableHead>
|
|
112
|
+
</TableRow>
|
|
113
|
+
</TableHeader>
|
|
114
|
+
<TableBody>
|
|
115
|
+
{data.repositories.map((repository) => (
|
|
116
|
+
<TableRow key={repository.id}>
|
|
117
|
+
<TableCell className="min-w-0 px-4 py-3">
|
|
118
|
+
<div className="truncate font-display text-sm text-dashboard-text">
|
|
119
|
+
<Link
|
|
120
|
+
className="text-inherit no-underline hover:text-dashboard-accent-text"
|
|
121
|
+
to={codeRepositoryPath(repository.id)}
|
|
122
|
+
>
|
|
123
|
+
{repository.name}
|
|
124
|
+
</Link>
|
|
125
|
+
</div>
|
|
126
|
+
<div className="mt-1 truncate font-mono text-xs text-dashboard-text-muted">
|
|
127
|
+
{repository.provider}
|
|
128
|
+
</div>
|
|
129
|
+
</TableCell>
|
|
130
|
+
<TableCell className="px-4 py-3 text-right font-sans tabular-nums">
|
|
131
|
+
{repository.created}
|
|
132
|
+
</TableCell>
|
|
133
|
+
<TableCell className="px-4 py-3 text-right font-sans tabular-nums">
|
|
134
|
+
{formatMergeRate(repository.mergeRate)}
|
|
135
|
+
</TableCell>
|
|
136
|
+
<TableCell className="px-4 py-3 text-right font-sans tabular-nums">
|
|
137
|
+
{formatCodeCost(repository.medianCostUsd)}
|
|
138
|
+
</TableCell>
|
|
139
|
+
</TableRow>
|
|
140
|
+
))}
|
|
141
|
+
</TableBody>
|
|
142
|
+
</Table>
|
|
146
143
|
)}
|
|
147
144
|
</Card>
|
|
148
145
|
{data.changes.length > 0 ? (
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
useCodeRepositoryConversationsData,
|
|
8
8
|
useCodeRepositoryData,
|
|
9
9
|
} from "../../api";
|
|
10
|
-
import {
|
|
10
|
+
import { buttonVariants } from "../../components/ui/button";
|
|
11
11
|
import { EmptyTelemetry } from "../../components/EmptyTelemetry";
|
|
12
12
|
import { PageRouteLoading } from "../../components/PageRouteLoading";
|
|
13
13
|
import {
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
import { PageHeader } from "../../components/layout/PageHeader";
|
|
19
19
|
import { PageLayout } from "../../components/layout/PageLayout";
|
|
20
20
|
import { SecondaryNavigation } from "../../components/layout/SecondaryNavigation";
|
|
21
|
-
import { buildConversations } from "../../format";
|
|
21
|
+
import { buildConversations, workspacePath } from "../../format";
|
|
22
22
|
import { CodeActivityChart } from "./CodeActivityChart";
|
|
23
23
|
import { CodeChangeRow } from "./CodeChangeRow";
|
|
24
24
|
import { CodeSummaryCards } from "./CodeSummaryCards";
|
|
@@ -98,7 +98,8 @@ export function RepositoryPage(props: { tab: CodeRepositoryTab }) {
|
|
|
98
98
|
<div className="flex flex-wrap items-center gap-2">
|
|
99
99
|
<TimeRangeSelector onChange={setRange} value={range} />
|
|
100
100
|
{repository.url ? (
|
|
101
|
-
<
|
|
101
|
+
<Link
|
|
102
|
+
className={buttonVariants()}
|
|
102
103
|
rel="noopener noreferrer"
|
|
103
104
|
target="_blank"
|
|
104
105
|
to={repository.url}
|
|
@@ -108,7 +109,7 @@ export function RepositoryPage(props: { tab: CodeRepositoryTab }) {
|
|
|
108
109
|
aria-hidden="true"
|
|
109
110
|
className="size-3.5 shrink-0"
|
|
110
111
|
/>
|
|
111
|
-
</
|
|
112
|
+
</Link>
|
|
112
113
|
) : null}
|
|
113
114
|
</div>
|
|
114
115
|
}
|
|
@@ -156,10 +157,7 @@ function RepositoryDescription(props: { data: CodeRepositoryReport }) {
|
|
|
156
157
|
{workspaces.map((workspace, index) => (
|
|
157
158
|
<span key={workspace.id}>
|
|
158
159
|
{index > 0 ? ", " : null}
|
|
159
|
-
<Link
|
|
160
|
-
className={inlineLinkClass}
|
|
161
|
-
to={`/system/workspaces/${encodeURIComponent(workspace.id)}`}
|
|
162
|
-
>
|
|
160
|
+
<Link className={inlineLinkClass} to={workspacePath(workspace.id)}>
|
|
163
161
|
{workspace.name}
|
|
164
162
|
</Link>
|
|
165
163
|
</span>
|