@sentry/junior-dashboard 0.264.0 → 0.265.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/BottomSheet.tsx +12 -2
- package/src/client/components/CopyButton.tsx +83 -0
- package/src/client/components/DetailsPanel.tsx +133 -0
- package/src/client/components/Drawer.tsx +15 -24
- package/src/client/components/FilterBar.tsx +16 -31
- package/src/client/components/Metric.tsx +33 -23
- package/src/client/components/PageContentSkeleton.tsx +1 -1
- package/src/client/components/Pagination.tsx +1 -1
- package/src/client/components/ParticipantAvatarStack.tsx +40 -36
- package/src/client/components/ProfileMenu.tsx +27 -93
- package/src/client/components/README.md +86 -77
- 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/charts/ActivityChart.tsx +173 -288
- package/src/client/components/charts/CacheWrites.tsx +63 -50
- 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/button.tsx +85 -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/field.tsx +49 -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/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/table.tsx +80 -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/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 +8 -3
- package/src/client/conversations/ConversationHeaderActions.tsx +90 -82
- 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 +48 -29
- package/src/client/conversations/ConversationWatches.tsx +165 -0
- package/src/client/conversations/ConversationWorkspace.tsx +12 -22
- package/src/client/conversations/ConversationWorkspaceLoading.tsx +1 -1
- package/src/client/conversations/DetailsList.tsx +124 -0
- package/src/client/conversations/EventDetails.tsx +42 -8
- package/src/client/conversations/ForkConversationButton.tsx +27 -16
- package/src/client/conversations/ObjectCard.tsx +20 -8
- package/src/client/conversations/PendingMailboxStack.tsx +52 -33
- package/src/client/conversations/README.md +86 -13
- package/src/client/conversations/ShareConversationButton.tsx +25 -13
- 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/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 +8 -2
- package/src/client/conversations/TranscriptMessageView.tsx +6 -1
- package/src/client/conversations/TranscriptReasoningView.tsx +54 -68
- package/src/client/conversations/TranscriptTimestamp.tsx +9 -7
- package/src/client/conversations/TranscriptToolView.tsx +1 -1
- package/src/client/conversations/TranscriptTurnContextView.tsx +99 -27
- package/src/client/conversations/TranscriptView.tsx +63 -27
- package/src/client/conversations/eventLog.ts +57 -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/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
|
@@ -663,6 +663,35 @@ export function useConversationData(conversationId: string | undefined) {
|
|
|
663
663
|
},
|
|
664
664
|
);
|
|
665
665
|
}, [conversationId, detailData, mailbox, outbox.data, queryClient]);
|
|
666
|
+
// The title is generated after the first message, and only the open
|
|
667
|
+
// Conversation polls. Copy a newer title into loaded feeds, such as the
|
|
668
|
+
// sidebar and the home list, so they do not keep the old one.
|
|
669
|
+
const displayTitle = detail.data?.displayTitle;
|
|
670
|
+
const detailUpdatedAt = detail.dataUpdatedAt;
|
|
671
|
+
useEffect(() => {
|
|
672
|
+
if (!conversationId || !displayTitle) return;
|
|
673
|
+
const feeds = queryClient
|
|
674
|
+
.getQueryCache()
|
|
675
|
+
.findAll({ queryKey: ["dashboard", "conversations"] });
|
|
676
|
+
for (const query of feeds) {
|
|
677
|
+
const feed = query.state.data as ConversationFeed | undefined;
|
|
678
|
+
if (!feed || query.state.dataUpdatedAt > detailUpdatedAt) continue;
|
|
679
|
+
const stale = feed.conversations.some(
|
|
680
|
+
(conversation) =>
|
|
681
|
+
conversation.conversationId === conversationId &&
|
|
682
|
+
conversation.displayTitle !== displayTitle,
|
|
683
|
+
);
|
|
684
|
+
if (!stale) continue;
|
|
685
|
+
queryClient.setQueryData<ConversationFeed>(query.queryKey, {
|
|
686
|
+
...feed,
|
|
687
|
+
conversations: feed.conversations.map((conversation) =>
|
|
688
|
+
conversation.conversationId === conversationId
|
|
689
|
+
? { ...conversation, displayTitle }
|
|
690
|
+
: conversation,
|
|
691
|
+
),
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
}, [conversationId, detailUpdatedAt, displayTitle, queryClient]);
|
|
666
695
|
const historyStatus = detailData?.eventHistory.status;
|
|
667
696
|
const historyQueryKey = useMemo(
|
|
668
697
|
() => ["conversation", conversationId, "history", historyStatus] as const,
|
|
@@ -15,6 +15,7 @@ import type { ConversationTranscript } from "../types";
|
|
|
15
15
|
import type { TranscriptViewMode } from "./transcriptRenderModel";
|
|
16
16
|
|
|
17
17
|
const BOTTOM_PROXIMITY_PX = 96;
|
|
18
|
+
const PREVIOUS_PAGE_PROXIMITY_PX = 600;
|
|
18
19
|
const USER_SCROLL_DELTA_PX = 2;
|
|
19
20
|
|
|
20
21
|
type ScrollRoot = HTMLElement | Window;
|
|
@@ -650,6 +651,39 @@ export function usePinnedTranscriptBottom(input: {
|
|
|
650
651
|
);
|
|
651
652
|
}
|
|
652
653
|
|
|
654
|
+
/** Load the previous history page when the reader scrolls near the top. */
|
|
655
|
+
export function useAutoLoadPreviousPage(input: {
|
|
656
|
+
enabled: boolean;
|
|
657
|
+
historyVersion: string;
|
|
658
|
+
onLoad: () => void;
|
|
659
|
+
}): RefCallback<HTMLElement> {
|
|
660
|
+
const [sentinel, setSentinel] = useState<HTMLElement | null>(null);
|
|
661
|
+
const onLoadRef = useRef(input.onLoad);
|
|
662
|
+
onLoadRef.current = input.onLoad;
|
|
663
|
+
|
|
664
|
+
useEffect(() => {
|
|
665
|
+
if (!input.enabled || !sentinel) return;
|
|
666
|
+
if (typeof IntersectionObserver === "undefined") return;
|
|
667
|
+
|
|
668
|
+
const root = scrollRootFor(sentinel);
|
|
669
|
+
const observer = new IntersectionObserver(
|
|
670
|
+
(entries) => {
|
|
671
|
+
if (!entries.some((entry) => entry.isIntersecting)) return;
|
|
672
|
+
observer.disconnect();
|
|
673
|
+
onLoadRef.current();
|
|
674
|
+
},
|
|
675
|
+
{
|
|
676
|
+
root: root && !isWindowRoot(root) ? root : null,
|
|
677
|
+
rootMargin: `${PREVIOUS_PAGE_PROXIMITY_PX}px 0px 0px 0px`,
|
|
678
|
+
},
|
|
679
|
+
);
|
|
680
|
+
observer.observe(sentinel);
|
|
681
|
+
return () => observer.disconnect();
|
|
682
|
+
}, [input.enabled, input.historyVersion, sentinel]);
|
|
683
|
+
|
|
684
|
+
return setSentinel;
|
|
685
|
+
}
|
|
686
|
+
|
|
653
687
|
/** Keep the previously visible content at the same viewport offset after a prepend. */
|
|
654
688
|
export function scrollTopAfterPrepend(
|
|
655
689
|
previous: Pick<ScrollSnapshot, "scrollHeight" | "scrollTop">,
|
package/src/client/format.ts
CHANGED
|
@@ -791,6 +791,11 @@ export function locationPath(locationId: string): string {
|
|
|
791
791
|
return `/system/locations/${encodeURIComponent(locationId)}`;
|
|
792
792
|
}
|
|
793
793
|
|
|
794
|
+
/** Build the Workspace list route that opens one Workspace's details. */
|
|
795
|
+
export function workspacePath(workspaceId: string): string {
|
|
796
|
+
return `/system/workspaces/${encodeURIComponent(workspaceId)}`;
|
|
797
|
+
}
|
|
798
|
+
|
|
794
799
|
/** Build the canonical automation detail route for an automation id. */
|
|
795
800
|
export function automationPath(automationId: string): string {
|
|
796
801
|
return `/automations/${encodeURIComponent(automationId)}`;
|
|
@@ -11,7 +11,7 @@ import { del, fetchDashboardJson, post } from "../http";
|
|
|
11
11
|
import { dashboardContainerClass } from "../styles";
|
|
12
12
|
import { getDashboardAgentName } from "../agentName";
|
|
13
13
|
import { useDocumentTitle } from "../documentTitle";
|
|
14
|
-
import { Button } from "../components/
|
|
14
|
+
import { Button } from "../components/ui/button";
|
|
15
15
|
import { InlineError } from "../components/InlineError";
|
|
16
16
|
import { Card } from "../components/layout/Card";
|
|
17
17
|
|
|
@@ -168,7 +168,7 @@ export function ConnectionsPage() {
|
|
|
168
168
|
connectMutation.reset();
|
|
169
169
|
disconnectMutation.mutate(connection);
|
|
170
170
|
}}
|
|
171
|
-
|
|
171
|
+
variant="danger"
|
|
172
172
|
>
|
|
173
173
|
{pendingProvider === connection.provider
|
|
174
174
|
? "Disconnecting…"
|
|
@@ -10,10 +10,10 @@ import { deleteDashboardResource, fetchDashboardJson, post } from "../http";
|
|
|
10
10
|
import { dashboardContainerClass } from "../styles";
|
|
11
11
|
import { getDashboardAgentName } from "../agentName";
|
|
12
12
|
import { useDocumentTitle } from "../documentTitle";
|
|
13
|
-
import { Button } from "../components/
|
|
13
|
+
import { Button } from "../components/ui/button";
|
|
14
|
+
import { Input } from "../components/ui/input";
|
|
14
15
|
import { InlineError } from "../components/InlineError";
|
|
15
16
|
import { Card } from "../components/layout/Card";
|
|
16
|
-
import { TextInput } from "../components/TextInput";
|
|
17
17
|
|
|
18
18
|
const personalTokensQueryKey = ["dashboard", "personal-tokens"] as const;
|
|
19
19
|
|
|
@@ -129,7 +129,7 @@ export function PersonalTokensPage() {
|
|
|
129
129
|
</div>
|
|
130
130
|
) : (
|
|
131
131
|
<div className="mt-5 flex gap-2">
|
|
132
|
-
<
|
|
132
|
+
<Input
|
|
133
133
|
aria-label="Token name"
|
|
134
134
|
className="min-w-0 flex-1"
|
|
135
135
|
maxLength={80}
|
|
@@ -189,7 +189,7 @@ export function PersonalTokensPage() {
|
|
|
189
189
|
revokeTokenMutation.mutate(token);
|
|
190
190
|
}}
|
|
191
191
|
size="icon"
|
|
192
|
-
|
|
192
|
+
variant="danger"
|
|
193
193
|
type="button"
|
|
194
194
|
>
|
|
195
195
|
<Trash2 size={16} />
|
|
@@ -5,11 +5,11 @@ import {
|
|
|
5
5
|
dashboardIdentitySchema,
|
|
6
6
|
type DashboardIdentity,
|
|
7
7
|
} from "../../api/schema";
|
|
8
|
-
import { Button } from "../components/
|
|
9
|
-
import {
|
|
8
|
+
import { Button } from "../components/ui/button";
|
|
9
|
+
import { Input } from "../components/ui/input";
|
|
10
|
+
import { Field, FieldDescription, FieldLabel } from "../components/ui/field";
|
|
10
11
|
import { InlineError } from "../components/InlineError";
|
|
11
12
|
import { Card } from "../components/layout/Card";
|
|
12
|
-
import { TextInput } from "../components/TextInput";
|
|
13
13
|
import { useDocumentTitle } from "../documentTitle";
|
|
14
14
|
import { patch } from "../http";
|
|
15
15
|
import { dashboardContainerClass } from "../styles";
|
|
@@ -64,13 +64,9 @@ export function SettingsPage({ identity }: SettingsPageProps) {
|
|
|
64
64
|
<form className="mt-6" onSubmit={submit}>
|
|
65
65
|
<Card className="mb-0" padding="md" variant="raised">
|
|
66
66
|
<h2 className="m-0 text-lg font-bold">Profile</h2>
|
|
67
|
-
<Field
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
htmlFor="display-name"
|
|
71
|
-
label="Display name"
|
|
72
|
-
>
|
|
73
|
-
<TextInput
|
|
67
|
+
<Field className="mt-5">
|
|
68
|
+
<FieldLabel htmlFor="display-name">Display name</FieldLabel>
|
|
69
|
+
<Input
|
|
74
70
|
autoComplete="name"
|
|
75
71
|
id="display-name"
|
|
76
72
|
maxLength={80}
|
|
@@ -82,6 +78,9 @@ export function SettingsPage({ identity }: SettingsPageProps) {
|
|
|
82
78
|
}}
|
|
83
79
|
value={displayName}
|
|
84
80
|
/>
|
|
81
|
+
<FieldDescription id="display-name-help">
|
|
82
|
+
Your display name is shown with your conversations and activity.
|
|
83
|
+
</FieldDescription>
|
|
85
84
|
</Field>
|
|
86
85
|
<div className="mt-5 flex items-center gap-3">
|
|
87
86
|
<Button disabled={!canSave} type="submit">
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
type serviceAccountSchema,
|
|
14
14
|
} from "@sentry/junior/api/schema";
|
|
15
15
|
import { getDashboardAgentName } from "../../agentName";
|
|
16
|
-
import { Button } from "../../components/
|
|
16
|
+
import { Button } from "../../components/ui/button";
|
|
17
17
|
import { InlineError } from "../../components/InlineError";
|
|
18
18
|
import { StatusChip } from "../../components/StatusChip";
|
|
19
19
|
import { Card } from "../../components/layout/Card";
|
|
@@ -192,7 +192,7 @@ export function ServiceAccountsSection(props: {
|
|
|
192
192
|
connectMutation.reset();
|
|
193
193
|
disconnectMutation.mutate(account);
|
|
194
194
|
}}
|
|
195
|
-
|
|
195
|
+
variant="danger"
|
|
196
196
|
>
|
|
197
197
|
Disconnect
|
|
198
198
|
</Button>
|
|
@@ -4,21 +4,27 @@ import {
|
|
|
4
4
|
type TimeRangeDays,
|
|
5
5
|
} from "../../components/controls/TimeRangeSelector";
|
|
6
6
|
import type { AutomationExecutionDay } 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";
|
|
16
|
+
import { ChartContainer, ChartTooltip } from "../../components/ui/chart";
|
|
17
17
|
|
|
18
18
|
import { Card } from "../../components/layout/Card";
|
|
19
19
|
import { formatCostSummary } from "../../format";
|
|
20
20
|
|
|
21
|
-
const
|
|
21
|
+
const chartConfig = {
|
|
22
|
+
costUsd: { color: "var(--color-cyan-300)", label: "Spend" },
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function formatSpend(value: number): string {
|
|
26
|
+
return formatCostSummary({ total: value });
|
|
27
|
+
}
|
|
22
28
|
|
|
23
29
|
/** Render linked conversation spend for completed automation executions. */
|
|
24
30
|
export function AutomationCostChart(props: {
|
|
@@ -28,11 +34,6 @@ export function AutomationCostChart(props: {
|
|
|
28
34
|
}) {
|
|
29
35
|
const bucketUnit = props.bucketUnit ?? "day";
|
|
30
36
|
const days = props.days;
|
|
31
|
-
// Cost charts need a wider left gutter for currency tick labels.
|
|
32
|
-
const layout = createActivityChartLayout(200, { left: 112 });
|
|
33
|
-
const step =
|
|
34
|
-
days.length > 0 ? layout.plotWidth / days.length : layout.plotWidth;
|
|
35
|
-
const barWidth = Math.max(2, Math.min(13, step * 0.68));
|
|
36
37
|
const totals = days.map((day) => day.costUsd);
|
|
37
38
|
const total = totals.reduce((sum, value) => sum + value, 0);
|
|
38
39
|
const maximum = Math.max(0.01, ...totals);
|
|
@@ -51,66 +52,44 @@ export function AutomationCostChart(props: {
|
|
|
51
52
|
</div>
|
|
52
53
|
|
|
53
54
|
<div className="relative mt-5 overflow-hidden">
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
layout={layout}
|
|
55
|
+
<ChartContainer
|
|
56
|
+
className="aspect-auto h-50 w-full"
|
|
57
|
+
config={chartConfig}
|
|
58
58
|
>
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
x={x - (Math.max(barWidth, 8) - barWidth) / 2}
|
|
94
|
-
y={layout.top}
|
|
95
|
-
/>
|
|
96
|
-
</g>
|
|
97
|
-
</ActivityChartTooltip>
|
|
98
|
-
);
|
|
99
|
-
})}
|
|
100
|
-
<ActivityChartAverageLine
|
|
101
|
-
unit={timeRangeBucketAverageUnit(bucketUnit)}
|
|
102
|
-
average={average}
|
|
103
|
-
format={(value) => formatCostSummary({ total: value })}
|
|
104
|
-
layout={layout}
|
|
105
|
-
maximum={maximum}
|
|
106
|
-
stroke="var(--color-slate-200)"
|
|
107
|
-
/>
|
|
108
|
-
<ActivityChartDateLabels
|
|
109
|
-
dates={days.map((day) => day.date)}
|
|
110
|
-
layout={layout}
|
|
111
|
-
xPosition={(index) => layout.left + index * step + step / 2}
|
|
112
|
-
/>
|
|
113
|
-
</ChartSvg>
|
|
59
|
+
<BarChart
|
|
60
|
+
aria-label={`Automation execution spend during the last ${props.range === 1 ? "24 hours" : `${props.range} days`}`}
|
|
61
|
+
data={days}
|
|
62
|
+
margin={activityChartMargin}
|
|
63
|
+
role="img"
|
|
64
|
+
>
|
|
65
|
+
<CartesianGrid strokeDasharray="3 5" vertical={false} />
|
|
66
|
+
<ActivityChartYAxis format={formatSpend} maximum={maximum} />
|
|
67
|
+
<ActivityChartXAxis keys={days.map((day) => day.date)} />
|
|
68
|
+
<ChartTooltip
|
|
69
|
+
content={
|
|
70
|
+
<ActivityChartTooltipContent
|
|
71
|
+
rows={(day: AutomationExecutionDay) => [
|
|
72
|
+
["spend", formatSpend(day.costUsd)],
|
|
73
|
+
]}
|
|
74
|
+
/>
|
|
75
|
+
}
|
|
76
|
+
/>
|
|
77
|
+
<Bar
|
|
78
|
+
dataKey="costUsd"
|
|
79
|
+
fill="var(--color-costUsd)"
|
|
80
|
+
fillOpacity={0.82}
|
|
81
|
+
isAnimationActive={false}
|
|
82
|
+
maxBarSize={13}
|
|
83
|
+
radius={1}
|
|
84
|
+
/>
|
|
85
|
+
<ActivityChartAverageLine
|
|
86
|
+
unit={timeRangeBucketAverageUnit(bucketUnit)}
|
|
87
|
+
average={average}
|
|
88
|
+
format={formatSpend}
|
|
89
|
+
stroke="var(--color-slate-200)"
|
|
90
|
+
/>
|
|
91
|
+
</BarChart>
|
|
92
|
+
</ChartContainer>
|
|
114
93
|
{!hasCost ? (
|
|
115
94
|
<div className="pointer-events-none absolute inset-0 grid place-items-center pt-12 font-sans text-xs text-dashboard-text-muted">
|
|
116
95
|
No linked spend in this period.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
3
|
+
import type { AutomationSummary } from "@sentry/junior/api/schema";
|
|
4
|
+
import {
|
|
5
|
+
AlertDialog,
|
|
6
|
+
AlertDialogContent,
|
|
7
|
+
AlertDialogTitle,
|
|
8
|
+
AlertDialogDescription,
|
|
9
|
+
AlertDialogFooter,
|
|
10
|
+
AlertDialogCancel,
|
|
11
|
+
AlertDialogAction,
|
|
12
|
+
} from "../../components/ui/alert-dialog";
|
|
13
|
+
import { FormNotice } from "../../components/FormNotice";
|
|
14
|
+
import { useToastManager } from "../../components/ui/toast";
|
|
15
|
+
import { deleteDashboardResource } from "../../http";
|
|
16
|
+
|
|
17
|
+
/** Confirm deletion by name and keep a failed request available for retry. */
|
|
18
|
+
export function AutomationDeleteDialog(props: {
|
|
19
|
+
automation: AutomationSummary;
|
|
20
|
+
onClose(): void;
|
|
21
|
+
onDeleted(): void;
|
|
22
|
+
returnFocus(): HTMLElement | false;
|
|
23
|
+
}) {
|
|
24
|
+
const [open, setOpen] = useState(true);
|
|
25
|
+
const queryClient = useQueryClient();
|
|
26
|
+
const toast = useToastManager();
|
|
27
|
+
const deletion = useMutation({
|
|
28
|
+
mutationFn: () =>
|
|
29
|
+
deleteDashboardResource(
|
|
30
|
+
`/api/automations/${encodeURIComponent(props.automation.id)}`,
|
|
31
|
+
),
|
|
32
|
+
onSuccess: async () => {
|
|
33
|
+
await queryClient.invalidateQueries({
|
|
34
|
+
queryKey: ["dashboard", "automations"],
|
|
35
|
+
});
|
|
36
|
+
props.onDeleted();
|
|
37
|
+
toast.add({
|
|
38
|
+
title: "Automation deleted",
|
|
39
|
+
description: props.automation.title,
|
|
40
|
+
type: "success",
|
|
41
|
+
});
|
|
42
|
+
setOpen(false);
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
return (
|
|
46
|
+
<AlertDialog
|
|
47
|
+
open={open}
|
|
48
|
+
onOpenChange={(next, details) => {
|
|
49
|
+
if (deletion.isPending) details.cancel();
|
|
50
|
+
else setOpen(next);
|
|
51
|
+
}}
|
|
52
|
+
onOpenChangeComplete={(next) => {
|
|
53
|
+
if (!next) props.onClose();
|
|
54
|
+
}}
|
|
55
|
+
>
|
|
56
|
+
<AlertDialogContent finalFocus={props.returnFocus}>
|
|
57
|
+
<AlertDialogTitle>Delete automation?</AlertDialogTitle>
|
|
58
|
+
<AlertDialogDescription>
|
|
59
|
+
Delete{" "}
|
|
60
|
+
<strong className="font-medium text-dashboard-text">
|
|
61
|
+
{props.automation.title}
|
|
62
|
+
</strong>
|
|
63
|
+
? This removes the Automation and stops future triggers. Work already
|
|
64
|
+
queued or running may finish.
|
|
65
|
+
</AlertDialogDescription>
|
|
66
|
+
{deletion.error ? (
|
|
67
|
+
<FormNotice
|
|
68
|
+
tone="error"
|
|
69
|
+
title="The automation could not be deleted. Try again."
|
|
70
|
+
/>
|
|
71
|
+
) : null}
|
|
72
|
+
<AlertDialogFooter>
|
|
73
|
+
<AlertDialogCancel disabled={deletion.isPending}>
|
|
74
|
+
Cancel
|
|
75
|
+
</AlertDialogCancel>
|
|
76
|
+
<AlertDialogAction
|
|
77
|
+
variant="danger"
|
|
78
|
+
disabled={deletion.isPending}
|
|
79
|
+
onClick={() => deletion.mutate()}
|
|
80
|
+
>
|
|
81
|
+
{deletion.isPending ? "Deleting…" : "Delete automation"}
|
|
82
|
+
</AlertDialogAction>
|
|
83
|
+
</AlertDialogFooter>
|
|
84
|
+
</AlertDialogContent>
|
|
85
|
+
</AlertDialog>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import type { AutomationSummary } from "@sentry/junior/api/schema";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import { Link, useLocation } from "react-router";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
Accordion,
|
|
7
|
+
AccordionContent,
|
|
8
|
+
AccordionItem,
|
|
9
|
+
AccordionTrigger,
|
|
10
|
+
} from "../../components/ui/accordion";
|
|
11
|
+
import { Button } from "../../components/ui/button";
|
|
12
|
+
import { DetailsPanel } from "../../components/DetailsPanel";
|
|
13
|
+
import { InlineError } from "../../components/InlineError";
|
|
14
|
+
import { PageContentSkeleton } from "../../components/PageContentSkeleton";
|
|
15
|
+
import {
|
|
16
|
+
timeRangeLabel,
|
|
17
|
+
type TimeRangeDays,
|
|
18
|
+
} from "../../components/controls/TimeRangeSelector";
|
|
19
|
+
import { TranscriptText } from "../../conversations/TranscriptText";
|
|
20
|
+
import { peoplePath } from "../../format";
|
|
21
|
+
import { dashboardInlineLinkClass } from "../../styles";
|
|
22
|
+
import { TriggerFilters } from "./TriggerFilters";
|
|
23
|
+
import { AutomationLifecycle } from "./AutomationLifecycle";
|
|
24
|
+
import { AutomationRecentRuns } from "./AutomationRecentRuns";
|
|
25
|
+
import { AutomationStatus } from "./AutomationStatus";
|
|
26
|
+
import {
|
|
27
|
+
automationScheduleLabel,
|
|
28
|
+
formatAutomationRunTime,
|
|
29
|
+
} from "./automationFormat";
|
|
30
|
+
import { automationOutcomeLabel } from "./automationOutcomes";
|
|
31
|
+
|
|
32
|
+
/** Keep Automation context scrollable above persistent, permission-aware actions. */
|
|
33
|
+
export function AutomationDetails(props: {
|
|
34
|
+
onClose(): void;
|
|
35
|
+
onDelete(source: HTMLButtonElement): void;
|
|
36
|
+
onRetry(): void;
|
|
37
|
+
error: boolean;
|
|
38
|
+
range: TimeRangeDays;
|
|
39
|
+
automation: AutomationSummary | undefined;
|
|
40
|
+
}) {
|
|
41
|
+
const location = useLocation();
|
|
42
|
+
const { automation } = props;
|
|
43
|
+
return (
|
|
44
|
+
<DetailsPanel
|
|
45
|
+
label="Automation details"
|
|
46
|
+
sheetTitle={automation?.title}
|
|
47
|
+
onClose={props.onClose}
|
|
48
|
+
title={
|
|
49
|
+
<h2 className="m-0 break-words font-display text-base font-medium text-dashboard-text">
|
|
50
|
+
{automation?.title ?? "Automation details"}
|
|
51
|
+
</h2>
|
|
52
|
+
}
|
|
53
|
+
>
|
|
54
|
+
<div className="grid h-full min-h-0 grid-rows-[minmax(0,1fr)_auto]">
|
|
55
|
+
<div
|
|
56
|
+
aria-label="Automation content"
|
|
57
|
+
role="region"
|
|
58
|
+
tabIndex={0}
|
|
59
|
+
className="min-h-0 overflow-y-auto overscroll-contain p-4 outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-dashboard-focus [overflow-wrap:anywhere]"
|
|
60
|
+
>
|
|
61
|
+
{automation ? (
|
|
62
|
+
<AutomationDetailsContent
|
|
63
|
+
automation={automation}
|
|
64
|
+
range={props.range}
|
|
65
|
+
/>
|
|
66
|
+
) : null}
|
|
67
|
+
{props.error ? (
|
|
68
|
+
<InlineError>
|
|
69
|
+
Automation details could not be loaded. It may be unavailable or
|
|
70
|
+
you may not have access.{" "}
|
|
71
|
+
<Button onClick={props.onRetry}>Try again</Button>
|
|
72
|
+
</InlineError>
|
|
73
|
+
) : !automation ? (
|
|
74
|
+
<PageContentSkeleton
|
|
75
|
+
label="Loading automation details"
|
|
76
|
+
variant="panel"
|
|
77
|
+
/>
|
|
78
|
+
) : null}
|
|
79
|
+
</div>
|
|
80
|
+
{automation ? (
|
|
81
|
+
<AutomationLifecycle
|
|
82
|
+
automation={automation}
|
|
83
|
+
onDelete={props.onDelete}
|
|
84
|
+
editPath={`/automations/${encodeURIComponent(automation.id)}/edit${location.search}`}
|
|
85
|
+
/>
|
|
86
|
+
) : null}
|
|
87
|
+
</div>
|
|
88
|
+
</DetailsPanel>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function AutomationDetailsContent({
|
|
93
|
+
automation,
|
|
94
|
+
range,
|
|
95
|
+
}: {
|
|
96
|
+
automation: AutomationSummary;
|
|
97
|
+
range: TimeRangeDays;
|
|
98
|
+
}) {
|
|
99
|
+
const location = useLocation();
|
|
100
|
+
return (
|
|
101
|
+
<div className="grid min-w-0 gap-5">
|
|
102
|
+
<AutomationStatus automation={automation} />
|
|
103
|
+
<section aria-label="Instructions" className="text-sm leading-relaxed">
|
|
104
|
+
<TranscriptText
|
|
105
|
+
mentions={automation.mentions ?? []}
|
|
106
|
+
text={automation.instruction}
|
|
107
|
+
/>
|
|
108
|
+
</section>
|
|
109
|
+
<dl className="m-0 grid gap-3 text-xs leading-relaxed">
|
|
110
|
+
<AutomationFact label="Trigger">
|
|
111
|
+
{automation.kind === "scheduled" ? (
|
|
112
|
+
<p className="m-0">{automationScheduleLabel(automation)}</p>
|
|
113
|
+
) : (
|
|
114
|
+
<>
|
|
115
|
+
<p className="m-0 break-words">{automation.resource}</p>
|
|
116
|
+
<p className="m-0 mt-1 text-dashboard-text-muted">
|
|
117
|
+
{automation.trigger.events.join(", ")}
|
|
118
|
+
</p>
|
|
119
|
+
<TriggerFilters filters={automation.trigger.filters} />
|
|
120
|
+
</>
|
|
121
|
+
)}
|
|
122
|
+
</AutomationFact>
|
|
123
|
+
<AutomationFact label="On success">
|
|
124
|
+
{automation.outcomes.length === 0 ? (
|
|
125
|
+
<span className="text-dashboard-text-muted">No message</span>
|
|
126
|
+
) : (
|
|
127
|
+
<ol className="m-0 grid list-none gap-2 p-0">
|
|
128
|
+
{automation.outcomes.map((outcome, index) => (
|
|
129
|
+
<li key={index}>
|
|
130
|
+
Message{" "}
|
|
131
|
+
{automationOutcomeLabel(outcome, automation.destination)}
|
|
132
|
+
</li>
|
|
133
|
+
))}
|
|
134
|
+
</ol>
|
|
135
|
+
)}
|
|
136
|
+
</AutomationFact>
|
|
137
|
+
</dl>
|
|
138
|
+
<AutomationRecentRuns automation={automation} />
|
|
139
|
+
<Accordion className="border-t border-dashboard-border pt-1">
|
|
140
|
+
<AccordionItem value="about">
|
|
141
|
+
<AccordionTrigger className="min-h-11 text-xs text-dashboard-text-muted">
|
|
142
|
+
About this Automation
|
|
143
|
+
</AccordionTrigger>
|
|
144
|
+
<AccordionContent>
|
|
145
|
+
<dl className="m-0 grid gap-3 text-xs leading-relaxed">
|
|
146
|
+
<AutomationFact label="Created by">
|
|
147
|
+
{automation.createdByEmail ? (
|
|
148
|
+
<Link
|
|
149
|
+
className={dashboardInlineLinkClass}
|
|
150
|
+
to={peoplePath(automation.createdByEmail)}
|
|
151
|
+
>
|
|
152
|
+
{automation.ownedByViewer ? "you" : automation.createdBy}
|
|
153
|
+
</Link>
|
|
154
|
+
) : automation.ownedByViewer ? (
|
|
155
|
+
"you"
|
|
156
|
+
) : (
|
|
157
|
+
automation.createdBy
|
|
158
|
+
)}
|
|
159
|
+
</AutomationFact>
|
|
160
|
+
<AutomationFact label="Created">
|
|
161
|
+
{formatAutomationRunTime(automation.createdAt)}
|
|
162
|
+
</AutomationFact>
|
|
163
|
+
<AutomationFact label="Accounts">
|
|
164
|
+
{automation.credentialMode === "creator"
|
|
165
|
+
? `Uses accounts connected by ${automation.ownedByViewer ? "you" : automation.createdBy}.`
|
|
166
|
+
: "Junior’s accounts only"}
|
|
167
|
+
</AutomationFact>
|
|
168
|
+
<AutomationFact label="Runs">
|
|
169
|
+
{automation.runs[range]} runs / {timeRangeLabel(range)} ·{" "}
|
|
170
|
+
{automation.totalRuns} total
|
|
171
|
+
</AutomationFact>
|
|
172
|
+
</dl>
|
|
173
|
+
<Link
|
|
174
|
+
className={`${dashboardInlineLinkClass} mt-3 inline-block py-2 text-xs`}
|
|
175
|
+
to={`/automations/${encodeURIComponent(automation.id)}/versions${location.search}`}
|
|
176
|
+
>
|
|
177
|
+
Version history
|
|
178
|
+
</Link>
|
|
179
|
+
</AccordionContent>
|
|
180
|
+
</AccordionItem>
|
|
181
|
+
</Accordion>
|
|
182
|
+
</div>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function AutomationFact(props: { label: string; children: ReactNode }) {
|
|
187
|
+
return (
|
|
188
|
+
<div className="grid min-w-0 grid-cols-[5.25rem_minmax(0,1fr)] gap-3">
|
|
189
|
+
<dt className="text-dashboard-text-muted">{props.label}</dt>
|
|
190
|
+
<dd className="m-0 min-w-0">{props.children}</dd>
|
|
191
|
+
</div>
|
|
192
|
+
);
|
|
193
|
+
}
|