@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,17 +1,43 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { SlidersHorizontal } from "lucide-react";
|
|
3
|
-
import { cn } from "../../styles";
|
|
1
|
+
import { useState, useSyncExternalStore } from "react";
|
|
2
|
+
import { SlidersHorizontal, X } from "lucide-react";
|
|
4
3
|
import type {
|
|
5
4
|
AutomationList,
|
|
6
5
|
AutomationListQuery,
|
|
7
6
|
} from "@sentry/junior/api/schema";
|
|
8
|
-
import {
|
|
7
|
+
import { BottomSheet } from "../../components/BottomSheet";
|
|
8
|
+
import {
|
|
9
|
+
Popover,
|
|
10
|
+
PopoverContent,
|
|
11
|
+
PopoverTitle,
|
|
12
|
+
PopoverTrigger,
|
|
13
|
+
} from "../../components/ui/popover";
|
|
14
|
+
import { Button } from "../../components/ui/button";
|
|
9
15
|
import { DirectorySortSelect } from "../../components/controls/DirectorySortSelect";
|
|
10
16
|
import { FilterGroup } from "../../components/FilterBar";
|
|
11
17
|
import { SearchInput } from "../../components/SearchInput";
|
|
12
|
-
import {
|
|
18
|
+
import { NativeSelect } from "../../components/ui/native-select";
|
|
19
|
+
import { ToggleGroup, ToggleGroupItem } from "../../components/ui/toggle-group";
|
|
13
20
|
|
|
14
|
-
|
|
21
|
+
const filterMediaQuery = "(min-width: 768px)";
|
|
22
|
+
const stateOptions = [
|
|
23
|
+
{ value: "active", label: "Active" },
|
|
24
|
+
{ value: "blocked", label: "Blocked" },
|
|
25
|
+
{ value: "paused", label: "Paused" },
|
|
26
|
+
{ value: "unavailable", label: "Trigger unavailable" },
|
|
27
|
+
{ value: "completed", label: "Completed" },
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
function subscribeToFilterLayout(onChange: () => void) {
|
|
31
|
+
const media = window.matchMedia(filterMediaQuery);
|
|
32
|
+
media.addEventListener("change", onChange);
|
|
33
|
+
return () => media.removeEventListener("change", onChange);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function filtersFitPopover() {
|
|
37
|
+
return window.matchMedia(filterMediaQuery).matches;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Keep URL filters visible while editing them in a popover or mobile sheet. */
|
|
15
41
|
export function AutomationFilters(props: {
|
|
16
42
|
filters: AutomationListQuery;
|
|
17
43
|
data: AutomationList | undefined;
|
|
@@ -21,17 +47,68 @@ export function AutomationFilters(props: {
|
|
|
21
47
|
}) {
|
|
22
48
|
const { filters, data, onChange } = props;
|
|
23
49
|
const [expanded, setExpanded] = useState(false);
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
50
|
+
const desktop = useSyncExternalStore(
|
|
51
|
+
subscribeToFilterLayout,
|
|
52
|
+
filtersFitPopover,
|
|
53
|
+
() => false,
|
|
54
|
+
);
|
|
55
|
+
const activeFilters = [
|
|
56
|
+
{
|
|
57
|
+
key: "type",
|
|
58
|
+
label: "Type",
|
|
59
|
+
value: filters.type === "all" ? "" : filters.type,
|
|
60
|
+
text: filters.type === "scheduled" ? "Scheduled" : "Event",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
key: "creator",
|
|
64
|
+
label: "Creator",
|
|
65
|
+
value: filters.creator,
|
|
66
|
+
text:
|
|
67
|
+
data?.creators.find((option) => option.value === filters.creator)
|
|
68
|
+
?.label ?? filters.creator,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
key: "destination",
|
|
72
|
+
label: "Destination",
|
|
73
|
+
value: filters.destination,
|
|
74
|
+
text:
|
|
75
|
+
data?.destinations.find(
|
|
76
|
+
(option) => option.value === filters.destination,
|
|
77
|
+
)?.label ?? filters.destination,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
key: "state",
|
|
81
|
+
label: "State",
|
|
82
|
+
value: filters.state === "all" ? "" : filters.state,
|
|
83
|
+
text: stateOptions.find((option) => option.value === filters.state)
|
|
84
|
+
?.label,
|
|
85
|
+
},
|
|
86
|
+
].filter((filter) => filter.value);
|
|
87
|
+
const trigger = (
|
|
88
|
+
<Button
|
|
89
|
+
className="h-11 shrink-0"
|
|
90
|
+
aria-expanded={expanded}
|
|
91
|
+
aria-haspopup="dialog"
|
|
92
|
+
onClick={desktop ? undefined : () => setExpanded(true)}
|
|
93
|
+
>
|
|
94
|
+
<SlidersHorizontal aria-hidden="true" size={14} /> Filters
|
|
95
|
+
{activeFilters.length ? ` (${activeFilters.length})` : ""}
|
|
96
|
+
</Button>
|
|
97
|
+
);
|
|
98
|
+
const controls = (
|
|
99
|
+
<AutomationFilterFields filters={filters} data={data} onChange={onChange} />
|
|
100
|
+
);
|
|
31
101
|
return (
|
|
32
102
|
<div className="grid gap-3">
|
|
33
103
|
<div className="grid gap-2 lg:grid-cols-[auto_minmax(16rem,1fr)] lg:items-center lg:gap-4">
|
|
34
|
-
<
|
|
104
|
+
<ToggleGroup
|
|
105
|
+
aria-label="Scope"
|
|
106
|
+
className="flex-wrap gap-2"
|
|
107
|
+
onValueChange={([next]) => {
|
|
108
|
+
if (next) onChange("scope", next);
|
|
109
|
+
}}
|
|
110
|
+
value={[filters.scope]}
|
|
111
|
+
>
|
|
35
112
|
{(
|
|
36
113
|
[
|
|
37
114
|
["all", "All"],
|
|
@@ -40,20 +117,19 @@ export function AutomationFilters(props: {
|
|
|
40
117
|
["attention", "Needs attention"],
|
|
41
118
|
] as const
|
|
42
119
|
).map(([value, label]) => (
|
|
43
|
-
<
|
|
44
|
-
className="min-h-11
|
|
120
|
+
<ToggleGroupItem
|
|
121
|
+
className="min-h-11 lg:min-h-9"
|
|
45
122
|
key={value}
|
|
46
|
-
|
|
47
|
-
pressed={filters.scope === value}
|
|
123
|
+
value={value}
|
|
48
124
|
variant="pill"
|
|
49
125
|
>
|
|
50
126
|
{label}
|
|
51
127
|
{data && value !== "attention" ? (
|
|
52
128
|
<span className="ml-1.5 opacity-65">{data.counts[value]}</span>
|
|
53
129
|
) : null}
|
|
54
|
-
</
|
|
130
|
+
</ToggleGroupItem>
|
|
55
131
|
))}
|
|
56
|
-
</
|
|
132
|
+
</ToggleGroup>
|
|
57
133
|
<div className="flex items-center gap-2">
|
|
58
134
|
<SearchInput
|
|
59
135
|
className="flex-1"
|
|
@@ -63,68 +139,107 @@ export function AutomationFilters(props: {
|
|
|
63
139
|
value={props.searchText}
|
|
64
140
|
onChange={props.onSearch}
|
|
65
141
|
/>
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
142
|
+
{desktop ? (
|
|
143
|
+
<Popover open={expanded} onOpenChange={setExpanded}>
|
|
144
|
+
<PopoverTrigger render={trigger} />
|
|
145
|
+
<PopoverContent align="end" className="w-80">
|
|
146
|
+
<PopoverTitle>Filters</PopoverTitle>
|
|
147
|
+
<div className="mt-4">{controls}</div>
|
|
148
|
+
</PopoverContent>
|
|
149
|
+
</Popover>
|
|
150
|
+
) : (
|
|
151
|
+
trigger
|
|
152
|
+
)}
|
|
75
153
|
</div>
|
|
76
154
|
</div>
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
"
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
155
|
+
{activeFilters.length > 0 ? (
|
|
156
|
+
<div
|
|
157
|
+
aria-label="Active filters"
|
|
158
|
+
role="group"
|
|
159
|
+
className="flex min-w-0 flex-wrap gap-2"
|
|
160
|
+
>
|
|
161
|
+
{activeFilters.map((filter) => (
|
|
162
|
+
<Button
|
|
163
|
+
key={filter.key}
|
|
164
|
+
aria-label={`Remove ${filter.key} filter`}
|
|
165
|
+
className="min-h-9 max-w-full text-xs"
|
|
166
|
+
title={`${filter.label}: ${filter.text}`}
|
|
167
|
+
onClick={() => onChange(filter.key, "")}
|
|
168
|
+
>
|
|
169
|
+
<span className="truncate">
|
|
170
|
+
{filter.label}: {filter.text}
|
|
171
|
+
</span>
|
|
172
|
+
<X aria-hidden="true" className="shrink-0" size={12} />
|
|
173
|
+
</Button>
|
|
174
|
+
))}
|
|
175
|
+
</div>
|
|
176
|
+
) : null}
|
|
177
|
+
{!desktop && expanded ? (
|
|
178
|
+
<BottomSheet
|
|
179
|
+
label="Filters"
|
|
180
|
+
size="content"
|
|
181
|
+
onClose={() => setExpanded(false)}
|
|
182
|
+
>
|
|
183
|
+
<div className="p-4 pb-[max(1rem,env(safe-area-inset-bottom))]">
|
|
184
|
+
{controls}
|
|
185
|
+
</div>
|
|
186
|
+
</BottomSheet>
|
|
187
|
+
) : null}
|
|
188
|
+
</div>
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function AutomationFilterFields({
|
|
193
|
+
filters,
|
|
194
|
+
data,
|
|
195
|
+
onChange,
|
|
196
|
+
}: Pick<
|
|
197
|
+
Parameters<typeof AutomationFilters>[0],
|
|
198
|
+
"filters" | "data" | "onChange"
|
|
199
|
+
>) {
|
|
200
|
+
return (
|
|
201
|
+
<div className="grid gap-4">
|
|
202
|
+
<FilterGroup label="Type">
|
|
203
|
+
<ToggleGroup
|
|
204
|
+
className="flex-wrap gap-2"
|
|
205
|
+
onValueChange={([next]) => {
|
|
206
|
+
if (next) onChange("type", next);
|
|
207
|
+
}}
|
|
208
|
+
value={[filters.type]}
|
|
87
209
|
>
|
|
88
210
|
{(["all", "scheduled", "event"] as const).map((value) => (
|
|
89
|
-
<
|
|
211
|
+
<ToggleGroupItem
|
|
90
212
|
className="min-h-9"
|
|
91
213
|
key={value}
|
|
92
|
-
|
|
93
|
-
pressed={filters.type === value}
|
|
214
|
+
value={value}
|
|
94
215
|
variant="pill"
|
|
95
216
|
>
|
|
96
217
|
{value}
|
|
97
|
-
</
|
|
218
|
+
</ToggleGroupItem>
|
|
98
219
|
))}
|
|
99
|
-
</
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
{ value: "unavailable", label: "Trigger unavailable" },
|
|
123
|
-
{ value: "completed", label: "Completed" },
|
|
124
|
-
]}
|
|
125
|
-
onChange={(value) => onChange("state", value)}
|
|
126
|
-
/>
|
|
127
|
-
</div>
|
|
220
|
+
</ToggleGroup>
|
|
221
|
+
</FilterGroup>
|
|
222
|
+
<AutomationFilterSelect
|
|
223
|
+
label="Creator"
|
|
224
|
+
emptyLabel="All"
|
|
225
|
+
value={filters.creator ?? ""}
|
|
226
|
+
options={data?.creators ?? []}
|
|
227
|
+
onChange={(value) => onChange("creator", value)}
|
|
228
|
+
/>
|
|
229
|
+
<AutomationFilterSelect
|
|
230
|
+
label="Destination"
|
|
231
|
+
emptyLabel="All"
|
|
232
|
+
value={filters.destination ?? ""}
|
|
233
|
+
options={data?.destinations ?? []}
|
|
234
|
+
onChange={(value) => onChange("destination", value)}
|
|
235
|
+
/>
|
|
236
|
+
<AutomationFilterSelect
|
|
237
|
+
label="State"
|
|
238
|
+
emptyLabel="All"
|
|
239
|
+
value={filters.state === "all" ? "" : filters.state}
|
|
240
|
+
options={stateOptions}
|
|
241
|
+
onChange={(value) => onChange("state", value)}
|
|
242
|
+
/>
|
|
128
243
|
</div>
|
|
129
244
|
);
|
|
130
245
|
}
|
|
@@ -158,7 +273,7 @@ function AutomationFilterSelect(props: {
|
|
|
158
273
|
}) {
|
|
159
274
|
return (
|
|
160
275
|
<FilterGroup label={props.label}>
|
|
161
|
-
<
|
|
276
|
+
<NativeSelect
|
|
162
277
|
aria-label={props.ariaLabel ?? `Filter by ${props.label.toLowerCase()}`}
|
|
163
278
|
variant="filter"
|
|
164
279
|
value={props.value}
|
|
@@ -174,7 +289,7 @@ function AutomationFilterSelect(props: {
|
|
|
174
289
|
{option.label}
|
|
175
290
|
</option>
|
|
176
291
|
))}
|
|
177
|
-
</
|
|
292
|
+
</NativeSelect>
|
|
178
293
|
</FilterGroup>
|
|
179
294
|
);
|
|
180
295
|
}
|
|
@@ -1,32 +1,44 @@
|
|
|
1
1
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
2
|
+
import {
|
|
3
|
+
Popover,
|
|
4
|
+
PopoverTrigger,
|
|
5
|
+
PopoverContent,
|
|
6
|
+
PopoverDescription,
|
|
7
|
+
} from "../../components/ui/popover";
|
|
8
|
+
import { useToastManager } from "../../components/ui/toast";
|
|
9
|
+
import {
|
|
10
|
+
Tooltip,
|
|
11
|
+
TooltipContent,
|
|
12
|
+
TooltipTrigger,
|
|
13
|
+
} from "../../components/ui/tooltip";
|
|
14
|
+
import { Link } from "react-router";
|
|
15
|
+
import { CircleHelp, Pencil, Settings2, Trash2 } from "lucide-react";
|
|
2
16
|
import {
|
|
3
17
|
automationEditSchema,
|
|
4
18
|
type AutomationSummary,
|
|
5
19
|
} from "@sentry/junior/api/schema";
|
|
6
|
-
import {
|
|
7
|
-
import { Button, ButtonLink } from "../../components/Button";
|
|
8
|
-
import { StatusChip } from "../../components/StatusChip";
|
|
20
|
+
import { Button } from "../../components/ui/button";
|
|
9
21
|
import { FormNotice } from "../../components/FormNotice";
|
|
10
22
|
import { DashboardApiError, fetchDashboardJson, post } from "../../http";
|
|
11
23
|
|
|
12
|
-
/**
|
|
24
|
+
/** Keep revision-checked lifecycle actions available in the fixed details footer. */
|
|
13
25
|
export function AutomationLifecycle({
|
|
14
26
|
automation,
|
|
15
27
|
editPath,
|
|
28
|
+
onDelete,
|
|
16
29
|
}: {
|
|
17
30
|
automation: AutomationSummary;
|
|
18
31
|
editPath: string;
|
|
32
|
+
onDelete(source: HTMLButtonElement): void;
|
|
19
33
|
}) {
|
|
34
|
+
const toast = useToastManager();
|
|
20
35
|
const queryClient = useQueryClient();
|
|
21
36
|
const completed = automation.status === "completed";
|
|
22
37
|
const paused = automation.status === "paused";
|
|
23
38
|
const blocked = automation.status === "blocked";
|
|
24
|
-
const unavailable =
|
|
25
|
-
automation.kind === "event" && !automation.triggerAvailable;
|
|
26
39
|
// Public readers can edit settings, but only the creator controls lifecycle.
|
|
27
40
|
const canEdit = !completed;
|
|
28
41
|
const canPause = automation.ownedByViewer && !completed;
|
|
29
|
-
const blockReason = automation.statusReason;
|
|
30
42
|
const action = paused || blocked ? "resume" : "pause";
|
|
31
43
|
const mutation = useMutation({
|
|
32
44
|
mutationFn: async () => {
|
|
@@ -46,40 +58,77 @@ export function AutomationLifecycle({
|
|
|
46
58
|
revision: current.revision,
|
|
47
59
|
});
|
|
48
60
|
},
|
|
49
|
-
onSuccess: async () => {
|
|
61
|
+
onSuccess: async (updated) => {
|
|
50
62
|
await queryClient.invalidateQueries({
|
|
51
63
|
queryKey: ["dashboard", "automations"],
|
|
52
64
|
});
|
|
65
|
+
toast.add({
|
|
66
|
+
title:
|
|
67
|
+
updated.status === "blocked"
|
|
68
|
+
? "Automation is still blocked"
|
|
69
|
+
: action === "pause"
|
|
70
|
+
? "Automation paused"
|
|
71
|
+
: "Automation resumed",
|
|
72
|
+
description:
|
|
73
|
+
updated.status === "blocked"
|
|
74
|
+
? "Fix the reported requirement before resuming again."
|
|
75
|
+
: action === "pause"
|
|
76
|
+
? "Work already queued or running may finish."
|
|
77
|
+
: "Future triggers are enabled.",
|
|
78
|
+
type: updated.status === "blocked" ? "warning" : "success",
|
|
79
|
+
});
|
|
53
80
|
},
|
|
54
81
|
});
|
|
55
|
-
const status =
|
|
56
|
-
automation.status === "active" && unavailable
|
|
57
|
-
? "Trigger unavailable"
|
|
58
|
-
: automation.status;
|
|
59
|
-
const failed =
|
|
60
|
-
automation.lastRunStatus === "failed" ||
|
|
61
|
-
automation.lastRunStatus === "blocked";
|
|
62
82
|
return (
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
83
|
+
<footer
|
|
84
|
+
aria-label="Automation actions"
|
|
85
|
+
className="grid gap-3 border-t border-dashboard-border bg-dashboard-bg-elevated p-3 pb-[max(0.75rem,env(safe-area-inset-bottom))]"
|
|
86
|
+
>
|
|
87
|
+
<div className="flex items-center gap-2">
|
|
88
|
+
<div className="mr-auto flex items-center gap-1">
|
|
89
|
+
<Tooltip>
|
|
90
|
+
<TooltipTrigger
|
|
91
|
+
render={
|
|
92
|
+
<Link
|
|
93
|
+
aria-label={canEdit ? "Edit automation" : "View settings"}
|
|
94
|
+
className="grid size-11 place-items-center rounded-md text-dashboard-text-muted no-underline hover:bg-dashboard-fill-strong hover:text-dashboard-text focus-visible:outline-2 focus-visible:outline-dashboard-focus"
|
|
95
|
+
to={editPath}
|
|
96
|
+
>
|
|
97
|
+
{canEdit ? (
|
|
98
|
+
<Pencil aria-hidden="true" size={16} />
|
|
99
|
+
) : (
|
|
100
|
+
<Settings2 aria-hidden="true" size={16} />
|
|
101
|
+
)}
|
|
102
|
+
</Link>
|
|
103
|
+
}
|
|
104
|
+
/>
|
|
105
|
+
<TooltipContent size="compact">
|
|
106
|
+
{canEdit ? "Edit automation" : "View settings"}
|
|
107
|
+
</TooltipContent>
|
|
108
|
+
</Tooltip>
|
|
109
|
+
{automation.ownedByViewer ? (
|
|
110
|
+
<Tooltip>
|
|
111
|
+
<TooltipTrigger
|
|
112
|
+
render={
|
|
113
|
+
<Button
|
|
114
|
+
focusableWhenDisabled
|
|
115
|
+
aria-label="Delete automation"
|
|
116
|
+
className="min-h-11 min-w-11"
|
|
117
|
+
size="icon"
|
|
118
|
+
variant="danger"
|
|
119
|
+
onClick={(event) => onDelete(event.currentTarget)}
|
|
120
|
+
disabled={mutation.isPending}
|
|
121
|
+
>
|
|
122
|
+
<Trash2 aria-hidden="true" size={16} />
|
|
123
|
+
</Button>
|
|
124
|
+
}
|
|
125
|
+
/>
|
|
126
|
+
<TooltipContent size="compact">Delete automation</TooltipContent>
|
|
127
|
+
</Tooltip>
|
|
128
|
+
) : null}
|
|
77
129
|
</div>
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
{canEdit ? "Edit automation" : "View settings"}
|
|
81
|
-
</ButtonLink>
|
|
82
|
-
{canPause ? (
|
|
130
|
+
{canPause ? (
|
|
131
|
+
<>
|
|
83
132
|
<Button
|
|
84
133
|
className="min-h-11"
|
|
85
134
|
disabled={mutation.isPending}
|
|
@@ -91,50 +140,32 @@ export function AutomationLifecycle({
|
|
|
91
140
|
? "Pause"
|
|
92
141
|
: "Resume"}
|
|
93
142
|
</Button>
|
|
94
|
-
|
|
95
|
-
|
|
143
|
+
<Popover>
|
|
144
|
+
<PopoverTrigger
|
|
145
|
+
render={
|
|
146
|
+
<Button
|
|
147
|
+
aria-label="About pausing"
|
|
148
|
+
className="min-h-11 min-w-11"
|
|
149
|
+
size="icon"
|
|
150
|
+
/>
|
|
151
|
+
}
|
|
152
|
+
>
|
|
153
|
+
<CircleHelp aria-hidden="true" size={16} />
|
|
154
|
+
</PopoverTrigger>
|
|
155
|
+
<PopoverContent side="top" align="end" aria-label="About pausing">
|
|
156
|
+
<PopoverDescription>
|
|
157
|
+
Pausing stops new runs. Work already queued or running may
|
|
158
|
+
finish. Resuming skips missed runs and waits for the next
|
|
159
|
+
scheduled time or new event.
|
|
160
|
+
{paused && automation.statusReason
|
|
161
|
+
? " Removing the pause keeps the block until you fix it and resume again."
|
|
162
|
+
: ""}
|
|
163
|
+
</PopoverDescription>
|
|
164
|
+
</PopoverContent>
|
|
165
|
+
</Popover>
|
|
166
|
+
</>
|
|
167
|
+
) : null}
|
|
96
168
|
</div>
|
|
97
|
-
{blocked || blockReason ? (
|
|
98
|
-
<p className="m-0 break-words text-dashboard-text-muted">
|
|
99
|
-
{blockReason ?? "Check the last run to see what needs fixing."}{" "}
|
|
100
|
-
{paused ? "Fix this before resuming." : "Fix this, then resume."}
|
|
101
|
-
</p>
|
|
102
|
-
) : null}
|
|
103
|
-
{unavailable ? (
|
|
104
|
-
<p className="m-0 text-dashboard-text-muted">
|
|
105
|
-
This event source is unavailable. Enable its plugin or choose another
|
|
106
|
-
event.
|
|
107
|
-
</p>
|
|
108
|
-
) : null}
|
|
109
|
-
{failed ? (
|
|
110
|
-
<p className="m-0 text-dashboard-text-muted">
|
|
111
|
-
Last run {automation.lastRunStatus}.
|
|
112
|
-
{automation.status === "active" && !unavailable
|
|
113
|
-
? " Future runs are still on."
|
|
114
|
-
: ""}{" "}
|
|
115
|
-
<Link
|
|
116
|
-
className="text-dashboard-text underline underline-offset-2"
|
|
117
|
-
to={`/automations/${encodeURIComponent(automation.id)}/executions`}
|
|
118
|
-
>
|
|
119
|
-
View run history
|
|
120
|
-
</Link>
|
|
121
|
-
</p>
|
|
122
|
-
) : null}
|
|
123
|
-
{canPause ? (
|
|
124
|
-
<details className="text-dashboard-text-muted">
|
|
125
|
-
<summary className="w-fit cursor-pointer py-2 text-xs hover:text-dashboard-text focus-visible:outline focus-visible:outline-dashboard-focus">
|
|
126
|
-
About pausing
|
|
127
|
-
</summary>
|
|
128
|
-
<p className="mt-1 mb-0 leading-relaxed">
|
|
129
|
-
Pausing stops new runs. Work already queued or running may finish.
|
|
130
|
-
Resuming skips missed runs and waits for the next scheduled time or
|
|
131
|
-
new event.
|
|
132
|
-
{paused && blockReason
|
|
133
|
-
? " Removing the pause keeps the block until you fix it and resume again."
|
|
134
|
-
: ""}
|
|
135
|
-
</p>
|
|
136
|
-
</details>
|
|
137
|
-
) : null}
|
|
138
169
|
{mutation.error ? (
|
|
139
170
|
<FormNotice tone="error" title="Could not update this automation.">
|
|
140
171
|
{mutation.error instanceof DashboardApiError
|
|
@@ -142,6 +173,6 @@ export function AutomationLifecycle({
|
|
|
142
173
|
: mutation.error.message}
|
|
143
174
|
</FormNotice>
|
|
144
175
|
) : null}
|
|
145
|
-
</
|
|
176
|
+
</footer>
|
|
146
177
|
);
|
|
147
178
|
}
|
|
@@ -3,8 +3,9 @@ import type {
|
|
|
3
3
|
AutomationEdit,
|
|
4
4
|
AutomationSummary,
|
|
5
5
|
} from "@sentry/junior/api/schema";
|
|
6
|
-
import { Button } from "../../components/
|
|
7
|
-
import {
|
|
6
|
+
import { Button } from "../../components/ui/button";
|
|
7
|
+
import { NativeSelect } from "../../components/ui/native-select";
|
|
8
|
+
import { RadioGroup, RadioGroupItem } from "../../components/ui/radio-group";
|
|
8
9
|
import { cn } from "../../styles";
|
|
9
10
|
import type { AutomationDraft } from "./automationDraft";
|
|
10
11
|
import { automationOutcomeLabel } from "./automationOutcomes";
|
|
@@ -32,6 +33,8 @@ export function AutomationOutcomeFields(props: {
|
|
|
32
33
|
value: AutomationDraft["outcomes"];
|
|
33
34
|
original: AutomationEdit["outcomes"];
|
|
34
35
|
destination: AutomationSummary["destination"];
|
|
36
|
+
/** Base UI radios do not inherit a native fieldset's disabled state. */
|
|
37
|
+
disabled?: boolean;
|
|
35
38
|
onChange(value: AutomationDraft["outcomes"]): void;
|
|
36
39
|
}) {
|
|
37
40
|
const defaultOutcomes: AutomationDraft["outcomes"] = props.original.length
|
|
@@ -65,26 +68,25 @@ export function AutomationOutcomeFields(props: {
|
|
|
65
68
|
];
|
|
66
69
|
return (
|
|
67
70
|
<>
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
</fieldset>
|
|
71
|
+
<RadioGroup
|
|
72
|
+
aria-label="Success messages"
|
|
73
|
+
disabled={props.disabled}
|
|
74
|
+
className="flex flex-wrap gap-x-6 gap-y-3"
|
|
75
|
+
onValueChange={(send: boolean) =>
|
|
76
|
+
props.onChange(send ? defaultOutcomes : [])
|
|
77
|
+
}
|
|
78
|
+
value={props.value.length > 0}
|
|
79
|
+
>
|
|
80
|
+
{[true, false].map((send) => (
|
|
81
|
+
<label
|
|
82
|
+
key={String(send)}
|
|
83
|
+
className="inline-flex min-h-8 cursor-pointer items-center gap-2 text-sm"
|
|
84
|
+
>
|
|
85
|
+
<RadioGroupItem value={send} />
|
|
86
|
+
{send ? "Send a message" : "No success message"}
|
|
87
|
+
</label>
|
|
88
|
+
))}
|
|
89
|
+
</RadioGroup>
|
|
88
90
|
{props.value.length ? (
|
|
89
91
|
<div className="grid gap-4 sm:gap-2">
|
|
90
92
|
{props.value.map((outcome, index) => {
|
|
@@ -106,7 +108,7 @@ export function AutomationOutcomeFields(props: {
|
|
|
106
108
|
"order-last basis-full sm:order-none sm:basis-auto",
|
|
107
109
|
)}
|
|
108
110
|
>
|
|
109
|
-
<
|
|
111
|
+
<NativeSelect
|
|
110
112
|
aria-label={`Message ${index + 1} destination`}
|
|
111
113
|
value={
|
|
112
114
|
typeof outcome.destination === "string"
|
|
@@ -130,7 +132,7 @@ export function AutomationOutcomeFields(props: {
|
|
|
130
132
|
{option.label}
|
|
131
133
|
</option>
|
|
132
134
|
))}
|
|
133
|
-
</
|
|
135
|
+
</NativeSelect>
|
|
134
136
|
</div>
|
|
135
137
|
{props.value.length > 1 ? (
|
|
136
138
|
<>
|