@agent-native/core 0.79.21 → 0.79.22
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/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +10 -0
- package/corpus/core/docs/content/deployment.md +82 -82
- package/corpus/core/docs/content/faq.md +1 -1
- package/corpus/core/docs/content/tracking.md +3 -3
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/engine/openai-compatible-endpoint.ts +25 -0
- package/corpus/core/src/agent/engine/provider-env-vars.ts +2 -0
- package/corpus/core/src/agent/engine/registry.ts +59 -11
- package/corpus/core/src/client/FeedbackButton.tsx +11 -1
- package/corpus/core/src/client/agent-engine-key.ts +57 -10
- package/corpus/core/src/client/analytics.ts +78 -27
- package/corpus/core/src/client/blocks/library/HighlightedCode.tsx +1 -1
- package/corpus/core/src/client/index.ts +2 -0
- package/corpus/core/src/client/session-replay.ts +159 -17
- package/corpus/core/src/client/settings/SettingsPanel.tsx +119 -4
- package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +5 -0
- package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +8 -3
- package/corpus/core/src/scripts/agent-engines/test-agent-engine.ts +54 -6
- package/corpus/core/src/server/agent-engine-api-key-route.ts +83 -12
- package/corpus/core/src/server/core-routes-plugin.ts +26 -2
- package/corpus/core/src/styles/blocks.css +23 -7
- package/corpus/templates/analytics/actions/list-session-recordings.ts +1 -1
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +22 -4
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +64 -50
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +38 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-sync.ts +89 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +138 -59
- package/corpus/templates/analytics/changelog/2026-06-27-dashboard-charts-drag-smoothly-when-rearranging-panels.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-27-dashboard-email-reports-still-send-when-the-live-screenshot-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-27-fixed-dashboard-edits-so-charts-no-longer-briefly-snap-back-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-27-mobile-navigation-chrome-and-motion.md +1 -0
- package/corpus/templates/analytics/changelog/2026-06-27-session-replay-lists-now-show-only-signed-in-recordings-with.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-27-sidebar-loading-placeholders-are-visible-in-light-mode.md +6 -0
- package/corpus/templates/analytics/netlify.toml +4 -0
- package/corpus/templates/analytics/scripts/seed-session-replay.ts +1 -0
- package/corpus/templates/analytics/server/jobs/dashboard-report.ts +1 -0
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +120 -29
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +1 -1
- package/corpus/templates/analytics/server/lib/session-replay.ts +158 -46
- package/corpus/templates/assets/changelog/2026-06-27-mobile-navigation-chrome-and-motion.md +1 -0
- package/corpus/templates/clips/app/components/capture-install-options.tsx +0 -9
- package/corpus/templates/clips/changelog/2026-06-27-clips-no-longer-shows-desktop-app-prompts-on-mobile-screens.md +1 -0
- package/corpus/templates/clips/changelog/2026-06-27-recorder-download-options-now-show-the-chrome-extension-and-.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-27-recorder-setup-mobile-capture-options.md +1 -0
- package/corpus/templates/clips/chrome-extension/package.json +1 -1
- package/corpus/templates/clips/chrome-extension/src/sentry.ts +17 -1
- package/corpus/templates/clips/desktop/package.json +1 -0
- package/corpus/templates/clips/desktop/scripts/check-tauri-prereqs.mjs +45 -0
- package/corpus/templates/clips/desktop/src/lib/sentry.ts +71 -0
- package/corpus/templates/clips/desktop/src/main.tsx +2 -0
- package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +2 -1
- package/corpus/templates/clips/desktop/src-tauri/build.rs +29 -0
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +3 -0
- package/corpus/templates/clips/desktop/src-tauri/src/logfile.rs +1 -0
- package/corpus/templates/clips/desktop/src-tauri/src/sentry_report.rs +142 -0
- package/corpus/templates/clips/desktop/vite.config.ts +66 -0
- package/corpus/templates/design/changelog/2026-06-27-mobile-navigation-chrome-and-motion.md +1 -0
- package/corpus/templates/forms/actions/export-responses.ts +3 -2
- package/corpus/templates/forms/actions/list-responses.ts +2 -1
- package/corpus/templates/forms/actions/response-insights.ts +13 -4
- package/corpus/templates/forms/changelog/2026-06-27-anonymous-feedback-no-longer-shows-internal-agent-native-ano.md +6 -0
- package/corpus/templates/forms/server/handlers/submissions.ts +5 -8
- package/corpus/templates/forms/server/lib/integrations.ts +9 -6
- package/corpus/templates/forms/shared/submitter-email.ts +20 -0
- package/corpus/templates/forms/shared/types.ts +1 -1
- package/corpus/templates/plan/app/components/plan/wireframe/html-artboard.css +49 -10
- package/corpus/templates/slides/changelog/2026-06-27-mobile-navigation-chrome-and-motion.md +1 -0
- package/dist/agent/engine/openai-compatible-endpoint.d.ts +3 -0
- package/dist/agent/engine/openai-compatible-endpoint.d.ts.map +1 -0
- package/dist/agent/engine/openai-compatible-endpoint.js +23 -0
- package/dist/agent/engine/openai-compatible-endpoint.js.map +1 -0
- package/dist/agent/engine/provider-env-vars.d.ts +1 -0
- package/dist/agent/engine/provider-env-vars.d.ts.map +1 -1
- package/dist/agent/engine/provider-env-vars.js +1 -0
- package/dist/agent/engine/provider-env-vars.js.map +1 -1
- package/dist/agent/engine/registry.d.ts.map +1 -1
- package/dist/agent/engine/registry.js +39 -12
- package/dist/agent/engine/registry.js.map +1 -1
- package/dist/client/FeedbackButton.d.ts.map +1 -1
- package/dist/client/FeedbackButton.js +6 -1
- package/dist/client/FeedbackButton.js.map +1 -1
- package/dist/client/agent-engine-key.d.ts +14 -0
- package/dist/client/agent-engine-key.d.ts.map +1 -1
- package/dist/client/agent-engine-key.js +35 -10
- package/dist/client/agent-engine-key.js.map +1 -1
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +71 -24
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/blocks/library/HighlightedCode.d.ts +1 -1
- package/dist/client/blocks/library/HighlightedCode.js +1 -1
- package/dist/client/blocks/library/HighlightedCode.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/session-replay.d.ts +1 -1
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +115 -17
- package/dist/client/session-replay.js.map +1 -1
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +42 -6
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +7 -7
- package/dist/progress/routes.d.ts +1 -1
- package/dist/scripts/agent-engines/manage-agent-engine.d.ts.map +1 -1
- package/dist/scripts/agent-engines/manage-agent-engine.js +4 -0
- package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
- package/dist/scripts/agent-engines/set-agent-engine.d.ts.map +1 -1
- package/dist/scripts/agent-engines/set-agent-engine.js +5 -4
- package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
- package/dist/scripts/agent-engines/test-agent-engine.d.ts.map +1 -1
- package/dist/scripts/agent-engines/test-agent-engine.js +37 -5
- package/dist/scripts/agent-engines/test-agent-engine.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +4 -4
- package/dist/server/agent-engine-api-key-route.d.ts.map +1 -1
- package/dist/server/agent-engine-api-key-route.js +71 -11
- package/dist/server/agent-engine-api-key-route.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +20 -3
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/styles/blocks.css +23 -7
- package/docs/content/deployment.md +82 -82
- package/docs/content/faq.md +1 -1
- package/docs/content/tracking.md +3 -3
- package/package.json +1 -1
|
@@ -120,6 +120,8 @@ const DASHBOARD_SORT_MODE_KEY = "dashboard-sort-mode";
|
|
|
120
120
|
const ANALYSIS_SORT_MODE_KEY = "analysis-sort-mode";
|
|
121
121
|
const DASHBOARDS_OPEN_KEY = "analytics-sidebar-dashboards-open";
|
|
122
122
|
const ANALYSES_OPEN_KEY = "analytics-sidebar-analyses-open";
|
|
123
|
+
const SIDEBAR_SKELETON_CLASS =
|
|
124
|
+
"bg-sidebar-foreground/12 dark:bg-sidebar-foreground/10";
|
|
123
125
|
|
|
124
126
|
type SidebarSortMode = "most-used" | "alphabetical" | "manual";
|
|
125
127
|
|
|
@@ -2056,9 +2058,17 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
|
|
|
2056
2058
|
key={`sql-skeleton-${i}`}
|
|
2057
2059
|
className="flex items-center gap-2 px-3 py-1"
|
|
2058
2060
|
>
|
|
2059
|
-
<Skeleton className="h-3.5 w-3.5 shrink-0 rounded-sm" />
|
|
2060
2061
|
<Skeleton
|
|
2061
|
-
className=
|
|
2062
|
+
className={cn(
|
|
2063
|
+
"h-3.5 w-3.5 shrink-0 rounded-sm",
|
|
2064
|
+
SIDEBAR_SKELETON_CLASS,
|
|
2065
|
+
)}
|
|
2066
|
+
/>
|
|
2067
|
+
<Skeleton
|
|
2068
|
+
className={cn(
|
|
2069
|
+
"h-3 rounded",
|
|
2070
|
+
SIDEBAR_SKELETON_CLASS,
|
|
2071
|
+
)}
|
|
2062
2072
|
style={{ width: `${60 + ((i * 17) % 30)}%` }}
|
|
2063
2073
|
/>
|
|
2064
2074
|
</div>
|
|
@@ -2184,9 +2194,17 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
|
|
|
2184
2194
|
key={`analysis-skeleton-${i}`}
|
|
2185
2195
|
className="flex items-center gap-2 px-3 py-1"
|
|
2186
2196
|
>
|
|
2187
|
-
<Skeleton className="h-3.5 w-3.5 shrink-0 rounded-sm" />
|
|
2188
2197
|
<Skeleton
|
|
2189
|
-
className=
|
|
2198
|
+
className={cn(
|
|
2199
|
+
"h-3.5 w-3.5 shrink-0 rounded-sm",
|
|
2200
|
+
SIDEBAR_SKELETON_CLASS,
|
|
2201
|
+
)}
|
|
2202
|
+
/>
|
|
2203
|
+
<Skeleton
|
|
2204
|
+
className={cn(
|
|
2205
|
+
"h-3 rounded",
|
|
2206
|
+
SIDEBAR_SKELETON_CLASS,
|
|
2207
|
+
)}
|
|
2190
2208
|
style={{ width: `${60 + ((i * 17) % 30)}%` }}
|
|
2191
2209
|
/>
|
|
2192
2210
|
</div>
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
IconDownload,
|
|
12
12
|
} from "@tabler/icons-react";
|
|
13
13
|
import { useIsFetching, useQueryClient } from "@tanstack/react-query";
|
|
14
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
14
|
+
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
15
15
|
|
|
16
16
|
import { ChartFillHeight, SqlChart } from "@/components/dashboard/SqlChart";
|
|
17
17
|
import {
|
|
@@ -59,8 +59,51 @@ interface SqlChartCardProps {
|
|
|
59
59
|
onSaveSql?: (sql: string) => Promise<void>;
|
|
60
60
|
editable?: boolean;
|
|
61
61
|
eagerLoad?: boolean;
|
|
62
|
+
isDragSource?: boolean;
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
const PanelDragHandle = memo(function PanelDragHandle({
|
|
66
|
+
panelId,
|
|
67
|
+
label,
|
|
68
|
+
className,
|
|
69
|
+
iconClassName,
|
|
70
|
+
}: {
|
|
71
|
+
panelId: string;
|
|
72
|
+
label: string;
|
|
73
|
+
className: string;
|
|
74
|
+
iconClassName: string;
|
|
75
|
+
}) {
|
|
76
|
+
const { attributes, listeners, setActivatorNodeRef, setNodeRef } =
|
|
77
|
+
useDraggable({
|
|
78
|
+
id: panelId,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const setHandleRef = useCallback(
|
|
82
|
+
(node: HTMLButtonElement | null) => {
|
|
83
|
+
setNodeRef(node);
|
|
84
|
+
setActivatorNodeRef(node);
|
|
85
|
+
},
|
|
86
|
+
[setActivatorNodeRef, setNodeRef],
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<Tooltip>
|
|
91
|
+
<TooltipTrigger asChild>
|
|
92
|
+
<button
|
|
93
|
+
ref={setHandleRef}
|
|
94
|
+
className={className}
|
|
95
|
+
aria-label={label}
|
|
96
|
+
{...attributes}
|
|
97
|
+
{...listeners}
|
|
98
|
+
>
|
|
99
|
+
<IconGripVertical className={iconClassName} />
|
|
100
|
+
</button>
|
|
101
|
+
</TooltipTrigger>
|
|
102
|
+
<TooltipContent>{label}</TooltipContent>
|
|
103
|
+
</Tooltip>
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
|
|
64
107
|
export function SqlChartCard({
|
|
65
108
|
panel,
|
|
66
109
|
resolvedSql,
|
|
@@ -69,13 +112,10 @@ export function SqlChartCard({
|
|
|
69
112
|
onSaveSql,
|
|
70
113
|
editable = true,
|
|
71
114
|
eagerLoad = false,
|
|
115
|
+
isDragSource = false,
|
|
72
116
|
}: SqlChartCardProps) {
|
|
73
117
|
const t = useT();
|
|
74
118
|
const queryClient = useQueryClient();
|
|
75
|
-
const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
|
|
76
|
-
id: panel.id,
|
|
77
|
-
disabled: !editable,
|
|
78
|
-
});
|
|
79
119
|
|
|
80
120
|
const [confirmOpen, setConfirmOpen] = useState(false);
|
|
81
121
|
const [expanded, setExpanded] = useState(false);
|
|
@@ -99,13 +139,9 @@ export function SqlChartCard({
|
|
|
99
139
|
queryClient.getQueryData(chartQueryKey) !== undefined;
|
|
100
140
|
const isChartRefreshing = chartHasCachedData && chartFetchCount > 0;
|
|
101
141
|
|
|
102
|
-
const setCardNodeRef = useCallback(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
cardRef.current = node;
|
|
106
|
-
},
|
|
107
|
-
[setNodeRef],
|
|
108
|
-
);
|
|
142
|
+
const setCardNodeRef = useCallback((node: HTMLDivElement | null) => {
|
|
143
|
+
cardRef.current = node;
|
|
144
|
+
}, []);
|
|
109
145
|
|
|
110
146
|
const handleExportCsvChange = useCallback((handler: (() => void) | null) => {
|
|
111
147
|
setExportCsv(handler ? () => handler : null);
|
|
@@ -155,10 +191,6 @@ export function SqlChartCard({
|
|
|
155
191
|
setExportCsv(null);
|
|
156
192
|
}, [panel.id]);
|
|
157
193
|
|
|
158
|
-
const style = {
|
|
159
|
-
zIndex: isDragging ? 50 : undefined,
|
|
160
|
-
};
|
|
161
|
-
|
|
162
194
|
// Section panels render as a flush header row (no card chrome, full width)
|
|
163
195
|
// so they read as dividers between groups of panels rather than as another
|
|
164
196
|
// tile in the grid.
|
|
@@ -166,8 +198,8 @@ export function SqlChartCard({
|
|
|
166
198
|
return (
|
|
167
199
|
<div
|
|
168
200
|
ref={setCardNodeRef}
|
|
169
|
-
style={
|
|
170
|
-
data-dragging={
|
|
201
|
+
style={isDragSource ? { zIndex: 50 } : undefined}
|
|
202
|
+
data-dragging={isDragSource ? "true" : undefined}
|
|
171
203
|
className="dashboard-section-card group relative mt-2 first:mt-0"
|
|
172
204
|
>
|
|
173
205
|
<div className="flex items-center gap-2 border-b border-border pb-2">
|
|
@@ -209,21 +241,12 @@ export function SqlChartCard({
|
|
|
209
241
|
</DropdownMenuItem>
|
|
210
242
|
</DropdownMenuContent>
|
|
211
243
|
</DropdownMenu>
|
|
212
|
-
<
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
{...listeners}
|
|
219
|
-
>
|
|
220
|
-
<IconGripVertical className="h-3.5 w-3.5" />
|
|
221
|
-
</button>
|
|
222
|
-
</TooltipTrigger>
|
|
223
|
-
<TooltipContent>
|
|
224
|
-
{t("sqlDashboard.dragToReorder")}
|
|
225
|
-
</TooltipContent>
|
|
226
|
-
</Tooltip>
|
|
244
|
+
<PanelDragHandle
|
|
245
|
+
panelId={panel.id}
|
|
246
|
+
label={t("sqlDashboard.dragToReorder")}
|
|
247
|
+
className="p-1 rounded cursor-grab active:cursor-grabbing text-muted-foreground/50 hover:text-muted-foreground"
|
|
248
|
+
iconClassName="h-3.5 w-3.5"
|
|
249
|
+
/>
|
|
227
250
|
</div>
|
|
228
251
|
) : null}
|
|
229
252
|
</div>
|
|
@@ -270,8 +293,8 @@ export function SqlChartCard({
|
|
|
270
293
|
return (
|
|
271
294
|
<div
|
|
272
295
|
ref={setCardNodeRef}
|
|
273
|
-
style={
|
|
274
|
-
data-dragging={
|
|
296
|
+
style={isDragSource ? { zIndex: 50 } : undefined}
|
|
297
|
+
data-dragging={isDragSource ? "true" : undefined}
|
|
275
298
|
className="dashboard-chart-card group relative h-full hover:z-20 focus-within:z-20"
|
|
276
299
|
>
|
|
277
300
|
<Card className="flex h-full flex-col overflow-visible">
|
|
@@ -375,21 +398,12 @@ export function SqlChartCard({
|
|
|
375
398
|
</DropdownMenu>
|
|
376
399
|
) : null}
|
|
377
400
|
{editable ? (
|
|
378
|
-
<
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
{...listeners}
|
|
385
|
-
>
|
|
386
|
-
<IconGripVertical className="h-3.5 w-3.5" />
|
|
387
|
-
</button>
|
|
388
|
-
</TooltipTrigger>
|
|
389
|
-
<TooltipContent>
|
|
390
|
-
{t("sqlDashboard.dragToReorder")}
|
|
391
|
-
</TooltipContent>
|
|
392
|
-
</Tooltip>
|
|
401
|
+
<PanelDragHandle
|
|
402
|
+
panelId={panel.id}
|
|
403
|
+
label={t("sqlDashboard.dragToReorder")}
|
|
404
|
+
className="p-1 rounded cursor-grab active:cursor-grabbing text-muted-foreground/50 hover:text-muted-foreground"
|
|
405
|
+
iconClassName="h-3.5 w-3.5"
|
|
406
|
+
/>
|
|
393
407
|
) : null}
|
|
394
408
|
</div>
|
|
395
409
|
</CardHeader>
|
|
@@ -279,6 +279,44 @@ export function isDropSlotAvailable(
|
|
|
279
279
|
return row.panels.length < group.columns;
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
+
export function availableDropSlotIdsForPanel(
|
|
283
|
+
groups: DashboardPanelGroup[],
|
|
284
|
+
panelId: string,
|
|
285
|
+
): Set<string> {
|
|
286
|
+
const ids = new Set<string>();
|
|
287
|
+
if (!panelIsInRenderedRow(groups, panelId)) return ids;
|
|
288
|
+
|
|
289
|
+
for (const group of groups) {
|
|
290
|
+
for (let rowIndex = 0; rowIndex <= group.rows.length; rowIndex++) {
|
|
291
|
+
ids.add(dropSlotId({ type: "row", groupKey: group.key, rowIndex }));
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
for (let rowIndex = 0; rowIndex < group.rows.length; rowIndex++) {
|
|
295
|
+
const row = group.rows[rowIndex];
|
|
296
|
+
const rowContainsPanel = row.panels.some((panel) => panel.id === panelId);
|
|
297
|
+
if (!rowContainsPanel && row.panels.length >= group.columns) continue;
|
|
298
|
+
if (rowContainsPanel && row.panels.length <= 1) continue;
|
|
299
|
+
|
|
300
|
+
for (
|
|
301
|
+
let columnIndex = 0;
|
|
302
|
+
columnIndex <= row.panels.length;
|
|
303
|
+
columnIndex++
|
|
304
|
+
) {
|
|
305
|
+
ids.add(
|
|
306
|
+
dropSlotId({
|
|
307
|
+
type: "column",
|
|
308
|
+
groupKey: group.key,
|
|
309
|
+
rowIndex,
|
|
310
|
+
columnIndex,
|
|
311
|
+
}),
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return ids;
|
|
318
|
+
}
|
|
319
|
+
|
|
282
320
|
export function movePanelToDropSlot(
|
|
283
321
|
panels: SqlPanel[],
|
|
284
322
|
panelId: string,
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
export type DashboardAdoptionHold = {
|
|
2
|
+
dashboardId: string;
|
|
3
|
+
minUpdatedAtMs: number | null;
|
|
4
|
+
expiresAt: number;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
type PrefetchSnapshotLike<T> = {
|
|
8
|
+
data: T;
|
|
9
|
+
syncVersion: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function dashboardPrefetchInitialData<T>(
|
|
13
|
+
snapshot: PrefetchSnapshotLike<T> | undefined,
|
|
14
|
+
syncVersion: number,
|
|
15
|
+
): T | undefined {
|
|
16
|
+
if (!snapshot || snapshot.syncVersion !== syncVersion) return undefined;
|
|
17
|
+
return snapshot.data;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function dashboardUpdatedAtMs(value: string | null | undefined) {
|
|
21
|
+
if (!value) return null;
|
|
22
|
+
const ms = Date.parse(value);
|
|
23
|
+
return Number.isFinite(ms) ? ms : null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function createDashboardAdoptionHold({
|
|
27
|
+
dashboardId,
|
|
28
|
+
currentUpdatedAt,
|
|
29
|
+
now = Date.now(),
|
|
30
|
+
ttlMs = 10_000,
|
|
31
|
+
}: {
|
|
32
|
+
dashboardId: string;
|
|
33
|
+
currentUpdatedAt: string | null;
|
|
34
|
+
now?: number;
|
|
35
|
+
ttlMs?: number;
|
|
36
|
+
}): DashboardAdoptionHold {
|
|
37
|
+
const currentUpdatedAtMs = dashboardUpdatedAtMs(currentUpdatedAt);
|
|
38
|
+
return {
|
|
39
|
+
dashboardId,
|
|
40
|
+
minUpdatedAtMs: currentUpdatedAtMs ?? now,
|
|
41
|
+
expiresAt: now + ttlMs,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function shouldAdoptDashboardQueryResult({
|
|
46
|
+
dashboardId,
|
|
47
|
+
loaded,
|
|
48
|
+
isPlaceholderData,
|
|
49
|
+
fetchedId,
|
|
50
|
+
fetchedUpdatedAt,
|
|
51
|
+
currentUpdatedAt,
|
|
52
|
+
hold,
|
|
53
|
+
now = Date.now(),
|
|
54
|
+
}: {
|
|
55
|
+
dashboardId: string | undefined;
|
|
56
|
+
loaded: boolean;
|
|
57
|
+
isPlaceholderData: boolean;
|
|
58
|
+
fetchedId: string | null | undefined;
|
|
59
|
+
fetchedUpdatedAt: string | null | undefined;
|
|
60
|
+
currentUpdatedAt: string | null;
|
|
61
|
+
hold: DashboardAdoptionHold | null;
|
|
62
|
+
now?: number;
|
|
63
|
+
}): { adopt: boolean; clearHold: boolean } {
|
|
64
|
+
if (!dashboardId) return { adopt: false, clearHold: false };
|
|
65
|
+
if (loaded && isPlaceholderData) {
|
|
66
|
+
return { adopt: false, clearHold: false };
|
|
67
|
+
}
|
|
68
|
+
if (fetchedId && fetchedId !== dashboardId) {
|
|
69
|
+
return { adopt: false, clearHold: false };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const fetchedMs = dashboardUpdatedAtMs(fetchedUpdatedAt);
|
|
73
|
+
const currentMs = dashboardUpdatedAtMs(currentUpdatedAt);
|
|
74
|
+
const activeHold = hold?.dashboardId === dashboardId ? hold : null;
|
|
75
|
+
const clearHold = !!activeHold && now >= activeHold.expiresAt;
|
|
76
|
+
|
|
77
|
+
if (activeHold && !clearHold && activeHold.minUpdatedAtMs !== null) {
|
|
78
|
+
if (fetchedMs === null || fetchedMs <= activeHold.minUpdatedAtMs) {
|
|
79
|
+
return { adopt: false, clearHold: false };
|
|
80
|
+
}
|
|
81
|
+
return { adopt: true, clearHold: true };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (currentMs !== null && fetchedMs !== null && fetchedMs < currentMs) {
|
|
85
|
+
return { adopt: false, clearHold };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return { adopt: true, clearHold };
|
|
89
|
+
}
|