@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
|
@@ -43,12 +43,17 @@ export function MentionPeopleProvider(props: {
|
|
|
43
43
|
|
|
44
44
|
/** Render one line of display text with the people the API resolved for it. */
|
|
45
45
|
export function MentionText(props: {
|
|
46
|
+
/**
|
|
47
|
+
* Link people to their page. Set false inside a button, which cannot hold
|
|
48
|
+
* a link.
|
|
49
|
+
*/
|
|
50
|
+
links?: boolean;
|
|
46
51
|
mentions: readonly ActorIdentity[];
|
|
47
52
|
text: string;
|
|
48
53
|
}) {
|
|
49
54
|
return (
|
|
50
55
|
<MentionPeopleProvider mentions={props.mentions}>
|
|
51
|
-
{renderMentionText(props.text, "mention-text")}
|
|
56
|
+
{renderMentionText(props.text, "mention-text", props.links)}
|
|
52
57
|
</MentionPeopleProvider>
|
|
53
58
|
);
|
|
54
59
|
}
|
|
@@ -57,7 +62,11 @@ export function MentionText(props: {
|
|
|
57
62
|
* Render text with Slack `<@U123>` and `<#C123|name>` tokens as readable
|
|
58
63
|
* mentions. It reads people from the nearest `MentionPeopleProvider`.
|
|
59
64
|
*/
|
|
60
|
-
export function renderMentionText(
|
|
65
|
+
export function renderMentionText(
|
|
66
|
+
text: string,
|
|
67
|
+
keyBase: string,
|
|
68
|
+
links = true,
|
|
69
|
+
): ReactNode[] {
|
|
61
70
|
const nodes: ReactNode[] = [];
|
|
62
71
|
let cursor = 0;
|
|
63
72
|
for (const mention of findMentions(text)) {
|
|
@@ -70,7 +79,11 @@ export function renderMentionText(text: string, keyBase: string): ReactNode[] {
|
|
|
70
79
|
);
|
|
71
80
|
}
|
|
72
81
|
nodes.push(
|
|
73
|
-
<MentionView
|
|
82
|
+
<MentionView
|
|
83
|
+
key={`${keyBase}-m-${mention.start}`}
|
|
84
|
+
links={links}
|
|
85
|
+
mention={mention}
|
|
86
|
+
/>,
|
|
74
87
|
);
|
|
75
88
|
cursor = mention.end;
|
|
76
89
|
}
|
|
@@ -95,13 +108,13 @@ function findMentions(text: string): TranscriptMention[] {
|
|
|
95
108
|
}));
|
|
96
109
|
}
|
|
97
110
|
|
|
98
|
-
function MentionView(props: { mention: TranscriptMention }) {
|
|
111
|
+
function MentionView(props: { links: boolean; mention: TranscriptMention }) {
|
|
99
112
|
const people = useContext(TranscriptMentionPeople);
|
|
100
113
|
const { id, label, sigil } = props.mention;
|
|
101
114
|
const person = sigil === "@" ? people.get(id) : undefined;
|
|
102
115
|
const name = person?.fullName ?? person?.slackUserName ?? label ?? id;
|
|
103
116
|
const text = <HighlightText text={`${sigil}${name.replace(/^[@#]/, "")}`} />;
|
|
104
|
-
if (person?.email) {
|
|
117
|
+
if (props.links && person?.email) {
|
|
105
118
|
return (
|
|
106
119
|
<Link
|
|
107
120
|
// Previews sit in cards that disable pointer events on their text.
|
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
|
+
}
|
|
@@ -8,10 +8,11 @@ import {
|
|
|
8
8
|
import { useAutomationData } from "../../api";
|
|
9
9
|
import { useDocumentTitle } from "../../documentTitle";
|
|
10
10
|
import { fetchDashboardJson } from "../../http";
|
|
11
|
-
import { Button } from "../../components/
|
|
11
|
+
import { Button } from "../../components/ui/button";
|
|
12
12
|
import { FormNotice } from "../../components/FormNotice";
|
|
13
13
|
import { StatusChip } from "../../components/StatusChip";
|
|
14
14
|
import { AutomationsNavigation } from "./AutomationsPageLayout";
|
|
15
|
+
import { TriggerFilters } from "./TriggerFilters";
|
|
15
16
|
import { AutomationEditor } from "./AutomationEditor";
|
|
16
17
|
import { AutomationEditorLoading } from "./AutomationEditorLoading";
|
|
17
18
|
import { AutomationFormSection } from "./AutomationFormSection";
|
|
@@ -49,7 +50,7 @@ export function AutomationEditPage(props: { enabled: boolean }) {
|
|
|
49
50
|
value?.kind === "event"
|
|
50
51
|
? value.status !== "active"
|
|
51
52
|
? value.status
|
|
52
|
-
: value.
|
|
53
|
+
: value.trigger.problems.length === 0
|
|
53
54
|
? "active"
|
|
54
55
|
: "unavailable"
|
|
55
56
|
: value?.status;
|
|
@@ -144,12 +145,10 @@ function AutomationReadOnly({ automation }: { automation: AutomationSummary }) {
|
|
|
144
145
|
) : (
|
|
145
146
|
<div className="grid gap-3 text-sm">
|
|
146
147
|
<p className="m-0 break-words">{automation.resource}</p>
|
|
147
|
-
<p className="m-0 break-words">
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
</pre>
|
|
152
|
-
) : null}
|
|
148
|
+
<p className="m-0 break-words">
|
|
149
|
+
{automation.trigger.events.join(", ")}
|
|
150
|
+
</p>
|
|
151
|
+
<TriggerFilters filters={automation.trigger.filters} />
|
|
153
152
|
</div>
|
|
154
153
|
)}
|
|
155
154
|
</AutomationFormSection>
|