@agent-native/core 0.78.9 → 0.79.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/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +11 -0
- package/corpus/core/docs/content/actions.md +4 -2
- package/corpus/core/docs/content/agent-surfaces.md +9 -0
- package/corpus/core/docs/content/extensions.md +10 -0
- package/corpus/core/docs/content/generative-ui.md +217 -0
- package/corpus/core/docs/content/key-concepts.md +2 -2
- package/corpus/core/docs/content/mcp-apps.md +3 -5
- package/corpus/core/docs/content/native-chat-ui.md +9 -5
- package/corpus/core/docs/content/tracking.md +9 -3
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/action-ui.ts +1 -0
- package/corpus/core/src/client/analytics.ts +195 -12
- package/corpus/core/src/client/chat/index.ts +1 -0
- package/corpus/core/src/client/chat/tool-call-display.tsx +5 -2
- package/corpus/core/src/client/chat/widgets/InlineExtensionWidget.tsx +139 -0
- package/corpus/core/src/client/chat/widgets/builtin-tool-renderers.tsx +27 -1
- package/corpus/core/src/client/conversation/AgentConversation.tsx +38 -0
- package/corpus/core/src/client/conversation/code-agent-transcript.ts +9 -0
- package/corpus/core/src/client/conversation/types.ts +4 -0
- package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +27 -5
- package/corpus/core/src/client/extensions/ExtensionViewer.tsx +26 -5
- package/corpus/core/src/client/extensions/InlineExtensionFrame.tsx +654 -0
- package/corpus/core/src/client/extensions/iframe-bridge.ts +30 -0
- package/corpus/core/src/client/extensions/index.ts +5 -0
- package/corpus/core/src/client/index.ts +1 -0
- package/corpus/core/src/client/session-replay.ts +89 -14
- package/corpus/core/src/code-agents/transcript-normalizer.ts +14 -0
- package/corpus/core/src/extensions/actions.ts +218 -1
- package/corpus/core/src/extensions/html-shell.ts +70 -3
- package/corpus/core/src/extensions/routes.ts +12 -4
- package/corpus/core/src/index.browser.ts +1 -0
- package/corpus/core/src/index.ts +1 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +21 -9
- package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +13 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/references/api.md +2 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +115 -0
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +198 -59
- package/corpus/templates/analytics/AGENTS.md +12 -0
- package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +1105 -0
- package/corpus/templates/analytics/actions/dashboard-panel-order.ts +169 -0
- package/corpus/templates/analytics/actions/get-sql-dashboard.ts +89 -6
- package/corpus/templates/analytics/actions/mutate-dashboard.ts +333 -0
- package/corpus/templates/analytics/actions/reorder-dashboard-panels.ts +214 -0
- package/corpus/templates/analytics/actions/send-dashboard-report-now.ts +48 -21
- package/corpus/templates/analytics/actions/update-dashboard.ts +193 -100
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +11 -0
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +1 -1
- package/corpus/templates/analytics/app/global.css +81 -10
- package/corpus/templates/analytics/app/i18n-data.ts +249 -4
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/ChartCard.tsx +3 -3
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +41 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/EmailReportDialog.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +4 -4
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +48 -18
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +32 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +204 -70
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +1101 -227
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +160 -133
- package/corpus/templates/analytics/changelog/2026-06-26-agents-can-now-edit-dashboards-through-a-typed-mutation-scri.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-dashboard-bar-chart-hovers-stay-subtle-in-dark-mode.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-dashboard-chart-moves-are-now-handled-by-panel-id-making-sim.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-dashboard-charts-drag-smoothly-with-stable-cards-inactive-ch.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-dashboard-charts-show-a-refresh-spinner-while-keeping-existi.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-dashboard-edits-stay-stable-while-chart-order-and-deletions-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-dashboard-email-reports-queue-immediate-sends.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-explorer-dashboards-now-drag-charts-with-a-stable-preview-an.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-session-replay-playback-can-now-be-toggled-by-clicking-the-r.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-session-replays-now-open-from-the-full-row-and-play-with-tim.md +6 -0
- package/corpus/templates/analytics/server/lib/dashboard-report-subscriptions.ts +21 -0
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +41 -17
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +5 -0
- package/dist/action-ui.d.ts +1 -0
- package/dist/action-ui.d.ts.map +1 -1
- package/dist/action-ui.js +1 -0
- package/dist/action-ui.js.map +1 -1
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +173 -12
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/chat/index.d.ts +1 -1
- package/dist/client/chat/index.d.ts.map +1 -1
- package/dist/client/chat/index.js +1 -1
- package/dist/client/chat/index.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +3 -1
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/chat/widgets/InlineExtensionWidget.d.ts +17 -0
- package/dist/client/chat/widgets/InlineExtensionWidget.d.ts.map +1 -0
- package/dist/client/chat/widgets/InlineExtensionWidget.js +85 -0
- package/dist/client/chat/widgets/InlineExtensionWidget.js.map +1 -0
- package/dist/client/chat/widgets/builtin-tool-renderers.d.ts.map +1 -1
- package/dist/client/chat/widgets/builtin-tool-renderers.js +15 -2
- package/dist/client/chat/widgets/builtin-tool-renderers.js.map +1 -1
- package/dist/client/conversation/AgentConversation.d.ts.map +1 -1
- package/dist/client/conversation/AgentConversation.js +33 -0
- package/dist/client/conversation/AgentConversation.js.map +1 -1
- package/dist/client/conversation/code-agent-transcript.js +8 -0
- package/dist/client/conversation/code-agent-transcript.js.map +1 -1
- package/dist/client/conversation/types.d.ts +4 -0
- package/dist/client/conversation/types.d.ts.map +1 -1
- package/dist/client/conversation/types.js.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.js +29 -1
- package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
- package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionViewer.js +28 -1
- package/dist/client/extensions/ExtensionViewer.js.map +1 -1
- package/dist/client/extensions/InlineExtensionFrame.d.ts +25 -0
- package/dist/client/extensions/InlineExtensionFrame.d.ts.map +1 -0
- package/dist/client/extensions/InlineExtensionFrame.js +488 -0
- package/dist/client/extensions/InlineExtensionFrame.js.map +1 -0
- package/dist/client/extensions/iframe-bridge.d.ts +2 -0
- package/dist/client/extensions/iframe-bridge.d.ts.map +1 -1
- package/dist/client/extensions/iframe-bridge.js +23 -0
- package/dist/client/extensions/iframe-bridge.js.map +1 -1
- package/dist/client/extensions/index.d.ts +1 -0
- package/dist/client/extensions/index.d.ts.map +1 -1
- package/dist/client/extensions/index.js +1 -0
- package/dist/client/extensions/index.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 +11 -1
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +74 -14
- package/dist/client/session-replay.js.map +1 -1
- package/dist/code-agents/transcript-normalizer.d.ts +2 -0
- package/dist/code-agents/transcript-normalizer.d.ts.map +1 -1
- package/dist/code-agents/transcript-normalizer.js +8 -0
- package/dist/code-agents/transcript-normalizer.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/extensions/actions.d.ts.map +1 -1
- package/dist/extensions/actions.js +200 -1
- package/dist/extensions/actions.js.map +1 -1
- package/dist/extensions/html-shell.d.ts.map +1 -1
- package/dist/extensions/html-shell.js +70 -3
- package/dist/extensions/html-shell.js.map +1 -1
- package/dist/extensions/routes.d.ts.map +1 -1
- package/dist/extensions/routes.js +11 -4
- package/dist/extensions/routes.js.map +1 -1
- package/dist/index.browser.d.ts +1 -1
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +1 -1
- package/dist/index.browser.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +21 -9
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +13 -0
- package/dist/templates/workspace-core/.agents/skills/extensions/references/api.md +2 -0
- package/dist/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +115 -0
- package/docs/content/actions.md +4 -2
- package/docs/content/agent-surfaces.md +9 -0
- package/docs/content/extensions.md +10 -0
- package/docs/content/generative-ui.md +217 -0
- package/docs/content/key-concepts.md +2 -2
- package/docs/content/mcp-apps.md +3 -5
- package/docs/content/native-chat-ui.md +9 -5
- package/docs/content/tracking.md +9 -3
- package/package.json +1 -1
- package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +13 -0
- package/src/templates/workspace-core/.agents/skills/extensions/references/api.md +2 -0
- package/src/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +115 -0
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { useT } from "@agent-native/core/client";
|
|
2
2
|
import { useDraggable } from "@dnd-kit/core";
|
|
3
|
-
import { CSS } from "@dnd-kit/utilities";
|
|
4
3
|
import {
|
|
5
4
|
IconGripVertical,
|
|
6
5
|
IconDotsVertical,
|
|
@@ -11,8 +10,8 @@ import {
|
|
|
11
10
|
IconCode,
|
|
12
11
|
IconDownload,
|
|
13
12
|
} from "@tabler/icons-react";
|
|
14
|
-
import { useQueryClient } from "@tanstack/react-query";
|
|
15
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
13
|
+
import { useIsFetching, useQueryClient } from "@tanstack/react-query";
|
|
14
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
16
15
|
|
|
17
16
|
import { ChartFillHeight, SqlChart } from "@/components/dashboard/SqlChart";
|
|
18
17
|
import {
|
|
@@ -39,6 +38,7 @@ import {
|
|
|
39
38
|
DropdownMenuSeparator,
|
|
40
39
|
DropdownMenuTrigger,
|
|
41
40
|
} from "@/components/ui/dropdown-menu";
|
|
41
|
+
import { Spinner } from "@/components/ui/spinner";
|
|
42
42
|
import {
|
|
43
43
|
Tooltip,
|
|
44
44
|
TooltipContent,
|
|
@@ -72,8 +72,10 @@ export function SqlChartCard({
|
|
|
72
72
|
}: SqlChartCardProps) {
|
|
73
73
|
const t = useT();
|
|
74
74
|
const queryClient = useQueryClient();
|
|
75
|
-
const { attributes, listeners, setNodeRef,
|
|
76
|
-
|
|
75
|
+
const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
|
|
76
|
+
id: panel.id,
|
|
77
|
+
disabled: !editable,
|
|
78
|
+
});
|
|
77
79
|
|
|
78
80
|
const [confirmOpen, setConfirmOpen] = useState(false);
|
|
79
81
|
const [expanded, setExpanded] = useState(false);
|
|
@@ -82,6 +84,20 @@ export function SqlChartCard({
|
|
|
82
84
|
eagerLoad || panel.chartType === "section",
|
|
83
85
|
);
|
|
84
86
|
const cardRef = useRef<HTMLDivElement | null>(null);
|
|
87
|
+
const chartQueryKey = useMemo(
|
|
88
|
+
() =>
|
|
89
|
+
[
|
|
90
|
+
"sql-chart",
|
|
91
|
+
panel.id,
|
|
92
|
+
serializePanelSql(resolvedSql ?? panel.sql),
|
|
93
|
+
panel.source,
|
|
94
|
+
] as const,
|
|
95
|
+
[panel.id, panel.source, panel.sql, resolvedSql],
|
|
96
|
+
);
|
|
97
|
+
const chartFetchCount = useIsFetching({ queryKey: chartQueryKey });
|
|
98
|
+
const chartHasCachedData =
|
|
99
|
+
queryClient.getQueryData(chartQueryKey) !== undefined;
|
|
100
|
+
const isChartRefreshing = chartHasCachedData && chartFetchCount > 0;
|
|
85
101
|
|
|
86
102
|
const setCardNodeRef = useCallback(
|
|
87
103
|
(node: HTMLDivElement | null) => {
|
|
@@ -98,14 +114,9 @@ export function SqlChartCard({
|
|
|
98
114
|
const handleRefresh = useCallback(() => {
|
|
99
115
|
setShouldLoadData(true);
|
|
100
116
|
void queryClient.invalidateQueries({
|
|
101
|
-
queryKey:
|
|
102
|
-
"sql-chart",
|
|
103
|
-
panel.id,
|
|
104
|
-
serializePanelSql(resolvedSql ?? panel.sql),
|
|
105
|
-
panel.source,
|
|
106
|
-
],
|
|
117
|
+
queryKey: chartQueryKey,
|
|
107
118
|
});
|
|
108
|
-
}, [
|
|
119
|
+
}, [chartQueryKey, queryClient]);
|
|
109
120
|
|
|
110
121
|
useEffect(() => {
|
|
111
122
|
if (eagerLoad) {
|
|
@@ -145,9 +156,7 @@ export function SqlChartCard({
|
|
|
145
156
|
}, [panel.id]);
|
|
146
157
|
|
|
147
158
|
const style = {
|
|
148
|
-
transform: CSS.Transform.toString(transform),
|
|
149
159
|
zIndex: isDragging ? 50 : undefined,
|
|
150
|
-
opacity: isDragging ? 0.7 : 1,
|
|
151
160
|
};
|
|
152
161
|
|
|
153
162
|
// Section panels render as a flush header row (no card chrome, full width)
|
|
@@ -158,7 +167,8 @@ export function SqlChartCard({
|
|
|
158
167
|
<div
|
|
159
168
|
ref={setCardNodeRef}
|
|
160
169
|
style={style}
|
|
161
|
-
|
|
170
|
+
data-dragging={isDragging ? "true" : undefined}
|
|
171
|
+
className="dashboard-section-card group relative mt-2 first:mt-0"
|
|
162
172
|
>
|
|
163
173
|
<div className="flex items-center gap-2 border-b border-border pb-2">
|
|
164
174
|
<h2 className="text-base font-semibold flex-1">{panel.title}</h2>
|
|
@@ -261,14 +271,34 @@ export function SqlChartCard({
|
|
|
261
271
|
<div
|
|
262
272
|
ref={setCardNodeRef}
|
|
263
273
|
style={style}
|
|
264
|
-
|
|
274
|
+
data-dragging={isDragging ? "true" : undefined}
|
|
275
|
+
className="dashboard-chart-card group relative h-full hover:z-20 focus-within:z-20"
|
|
265
276
|
>
|
|
266
277
|
<Card className="flex h-full flex-col overflow-visible">
|
|
267
278
|
<CardHeader className="pb-2 flex flex-row items-center gap-2 shrink-0">
|
|
268
279
|
<CardTitle className="text-sm font-medium flex-1 truncate">
|
|
269
280
|
{panel.title}
|
|
270
281
|
</CardTitle>
|
|
271
|
-
<div
|
|
282
|
+
<div
|
|
283
|
+
className={`flex items-center gap-1 transition-opacity ${
|
|
284
|
+
isChartRefreshing
|
|
285
|
+
? "opacity-100"
|
|
286
|
+
: "opacity-0 group-hover:opacity-100 focus-within:opacity-100"
|
|
287
|
+
}`}
|
|
288
|
+
>
|
|
289
|
+
{isChartRefreshing ? (
|
|
290
|
+
<Tooltip>
|
|
291
|
+
<TooltipTrigger asChild>
|
|
292
|
+
<span className="inline-flex size-6 items-center justify-center rounded text-muted-foreground">
|
|
293
|
+
<Spinner
|
|
294
|
+
className="size-3.5"
|
|
295
|
+
aria-label={t("sqlDashboard.refreshing")}
|
|
296
|
+
/>
|
|
297
|
+
</span>
|
|
298
|
+
</TooltipTrigger>
|
|
299
|
+
<TooltipContent>{t("sqlDashboard.refreshing")}</TooltipContent>
|
|
300
|
+
</Tooltip>
|
|
301
|
+
) : null}
|
|
272
302
|
{showPanelMenu ? (
|
|
273
303
|
<DropdownMenu>
|
|
274
304
|
<Tooltip>
|
|
@@ -363,7 +393,7 @@ export function SqlChartCard({
|
|
|
363
393
|
) : null}
|
|
364
394
|
</div>
|
|
365
395
|
</CardHeader>
|
|
366
|
-
<CardContent className="flex flex-1 flex-col overflow-visible pt-0">
|
|
396
|
+
<CardContent className="dashboard-chart-content flex flex-1 flex-col overflow-visible pt-0">
|
|
367
397
|
<SqlChart
|
|
368
398
|
panel={panel}
|
|
369
399
|
resolvedSql={resolvedSql}
|
|
@@ -26,10 +26,42 @@ export type DashboardDropSlot =
|
|
|
26
26
|
columnIndex: number;
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
+
export type DashboardPointerCoordinates = {
|
|
30
|
+
x: number;
|
|
31
|
+
y: number;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type DashboardClientRect = {
|
|
35
|
+
left: number;
|
|
36
|
+
right: number;
|
|
37
|
+
top: number;
|
|
38
|
+
bottom: number;
|
|
39
|
+
};
|
|
40
|
+
|
|
29
41
|
function rowKey(panels: SqlPanel[], index: number): string {
|
|
30
42
|
return panels.map((panel) => panel.id).join(":") || `empty-${index}`;
|
|
31
43
|
}
|
|
32
44
|
|
|
45
|
+
export function distanceFromPointerToRect(
|
|
46
|
+
pointer: DashboardPointerCoordinates,
|
|
47
|
+
rect: DashboardClientRect,
|
|
48
|
+
): number {
|
|
49
|
+
const dx =
|
|
50
|
+
pointer.x < rect.left
|
|
51
|
+
? rect.left - pointer.x
|
|
52
|
+
: pointer.x > rect.right
|
|
53
|
+
? pointer.x - rect.right
|
|
54
|
+
: 0;
|
|
55
|
+
const dy =
|
|
56
|
+
pointer.y < rect.top
|
|
57
|
+
? rect.top - pointer.y
|
|
58
|
+
: pointer.y > rect.bottom
|
|
59
|
+
? pointer.y - rect.bottom
|
|
60
|
+
: 0;
|
|
61
|
+
|
|
62
|
+
return Math.hypot(dx, dy);
|
|
63
|
+
}
|
|
64
|
+
|
|
33
65
|
export function rebalanceRowWidths(
|
|
34
66
|
panels: SqlPanel[],
|
|
35
67
|
columns: number,
|
|
@@ -14,16 +14,18 @@ import {
|
|
|
14
14
|
type CollabUser,
|
|
15
15
|
} from "@agent-native/core/client";
|
|
16
16
|
import {
|
|
17
|
-
closestCenter,
|
|
18
17
|
useDroppable,
|
|
19
18
|
DndContext,
|
|
19
|
+
DragOverlay,
|
|
20
20
|
pointerWithin,
|
|
21
21
|
PointerSensor,
|
|
22
22
|
useSensor,
|
|
23
23
|
useSensors,
|
|
24
24
|
type DragEndEvent,
|
|
25
25
|
type DragOverEvent,
|
|
26
|
+
type DragStartEvent,
|
|
26
27
|
type CollisionDetection,
|
|
28
|
+
type Collision,
|
|
27
29
|
} from "@dnd-kit/core";
|
|
28
30
|
import {
|
|
29
31
|
IconArchive,
|
|
@@ -31,6 +33,7 @@ import {
|
|
|
31
33
|
IconDots,
|
|
32
34
|
IconEye,
|
|
33
35
|
IconEyeOff,
|
|
36
|
+
IconGripVertical,
|
|
34
37
|
IconInfoCircle,
|
|
35
38
|
IconMail,
|
|
36
39
|
IconPencil,
|
|
@@ -42,6 +45,7 @@ import {
|
|
|
42
45
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
43
46
|
import {
|
|
44
47
|
Fragment,
|
|
48
|
+
memo,
|
|
45
49
|
useState,
|
|
46
50
|
useEffect,
|
|
47
51
|
useCallback,
|
|
@@ -102,6 +106,7 @@ import BlankDashboard from "../BlankDashboard";
|
|
|
102
106
|
import { DashboardSkeleton } from "../DashboardSkeleton";
|
|
103
107
|
import {
|
|
104
108
|
buildDashboardPanelGroups,
|
|
109
|
+
distanceFromPointerToRect,
|
|
105
110
|
dropSlotId,
|
|
106
111
|
isDropSlotAvailable,
|
|
107
112
|
movePanelToDropSlot,
|
|
@@ -189,6 +194,100 @@ function DashboardDropLine({
|
|
|
189
194
|
);
|
|
190
195
|
}
|
|
191
196
|
|
|
197
|
+
function DashboardDragPreview({ panel }: { panel: SqlPanel | null }) {
|
|
198
|
+
if (!panel) return null;
|
|
199
|
+
|
|
200
|
+
return (
|
|
201
|
+
<div className="dashboard-drag-preview flex max-w-64 items-center gap-2 rounded-md border bg-background/95 px-3 py-2 text-sm font-medium text-foreground shadow-lg ring-1 ring-primary/20">
|
|
202
|
+
<IconGripVertical className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
203
|
+
<span className="truncate">{panel.title}</span>
|
|
204
|
+
</div>
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* A single chart cell, memoized so that drag interactions — which re-render the
|
|
210
|
+
* dashboard page on every drop-slot change — do NOT re-render every chart's
|
|
211
|
+
* Recharts subtree. During a drag the panel, vars, remoteEditor, and the
|
|
212
|
+
* stable callbacks below don't change, so React skips these cells entirely and
|
|
213
|
+
* only the lightweight drop-line indicators update. This keeps dragging smooth
|
|
214
|
+
* on dense dashboards. Outside a drag, prop changes (filter/vars edits, remote
|
|
215
|
+
* collaborator highlights, panel edits) still re-render normally.
|
|
216
|
+
*/
|
|
217
|
+
const PanelCell = memo(function PanelCell({
|
|
218
|
+
panel,
|
|
219
|
+
vars,
|
|
220
|
+
remoteEditor,
|
|
221
|
+
editable,
|
|
222
|
+
eagerLoad,
|
|
223
|
+
onRemovePanel,
|
|
224
|
+
onEditPanel,
|
|
225
|
+
onSavePanel,
|
|
226
|
+
}: {
|
|
227
|
+
panel: SqlPanel;
|
|
228
|
+
vars: Record<string, string>;
|
|
229
|
+
remoteEditor: { color: string; name: string } | undefined;
|
|
230
|
+
editable: boolean;
|
|
231
|
+
eagerLoad: boolean;
|
|
232
|
+
onRemovePanel: (panelId: string) => void;
|
|
233
|
+
onEditPanel: (panel: SqlPanel) => void;
|
|
234
|
+
onSavePanel: (panel: SqlPanel) => Promise<void>;
|
|
235
|
+
}) {
|
|
236
|
+
const resolved = useMemo(
|
|
237
|
+
() =>
|
|
238
|
+
panel.config?.description
|
|
239
|
+
? {
|
|
240
|
+
...panel,
|
|
241
|
+
config: {
|
|
242
|
+
...panel.config,
|
|
243
|
+
description: interpolate(panel.config.description, vars),
|
|
244
|
+
},
|
|
245
|
+
}
|
|
246
|
+
: panel,
|
|
247
|
+
[panel, vars],
|
|
248
|
+
);
|
|
249
|
+
const resolvedSql = useMemo(
|
|
250
|
+
() => interpolate(serializePanelSql(panel.sql), vars),
|
|
251
|
+
[panel.sql, vars],
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
return (
|
|
255
|
+
<div
|
|
256
|
+
className="dashboard-grid-cell relative h-full"
|
|
257
|
+
style={
|
|
258
|
+
remoteEditor
|
|
259
|
+
? {
|
|
260
|
+
outline: `2px solid ${remoteEditor.color}`,
|
|
261
|
+
outlineOffset: 2,
|
|
262
|
+
borderRadius: 8,
|
|
263
|
+
}
|
|
264
|
+
: undefined
|
|
265
|
+
}
|
|
266
|
+
>
|
|
267
|
+
{remoteEditor && (
|
|
268
|
+
<span
|
|
269
|
+
className="absolute -top-2.5 left-3 px-1.5 text-[10px] font-medium rounded z-10"
|
|
270
|
+
style={{
|
|
271
|
+
backgroundColor: remoteEditor.color,
|
|
272
|
+
color: "#fff",
|
|
273
|
+
}}
|
|
274
|
+
>
|
|
275
|
+
{remoteEditor.name}
|
|
276
|
+
</span>
|
|
277
|
+
)}
|
|
278
|
+
<SqlChartCard
|
|
279
|
+
panel={resolved}
|
|
280
|
+
resolvedSql={resolvedSql}
|
|
281
|
+
onRemove={() => onRemovePanel(panel.id)}
|
|
282
|
+
onEdit={() => onEditPanel(panel)}
|
|
283
|
+
onSaveSql={(sql) => onSavePanel({ ...panel, sql })}
|
|
284
|
+
editable={editable}
|
|
285
|
+
eagerLoad={eagerLoad}
|
|
286
|
+
/>
|
|
287
|
+
</div>
|
|
288
|
+
);
|
|
289
|
+
});
|
|
290
|
+
|
|
192
291
|
type FetchedDashboard = {
|
|
193
292
|
id: string;
|
|
194
293
|
config: SqlDashboardConfig;
|
|
@@ -237,7 +336,7 @@ async function fetchDashboard(id: string): Promise<FetchedDashboard | null> {
|
|
|
237
336
|
try {
|
|
238
337
|
const data: any = await callAction(
|
|
239
338
|
"get-sql-dashboard",
|
|
240
|
-
{ id },
|
|
339
|
+
{ id, includeConfig: true },
|
|
241
340
|
{ method: "GET" },
|
|
242
341
|
);
|
|
243
342
|
if (!data || data.error) return null;
|
|
@@ -319,6 +418,9 @@ export default function SqlDashboardPage() {
|
|
|
319
418
|
const [emailReportOpen, setEmailReportOpen] = useState(false);
|
|
320
419
|
const [activeDropSlot, setActiveDropSlot] =
|
|
321
420
|
useState<DashboardDropSlot | null>(null);
|
|
421
|
+
const [activeDragPanelId, setActiveDragPanelId] = useState<string | null>(
|
|
422
|
+
null,
|
|
423
|
+
);
|
|
322
424
|
const viewedDashboardIdRef = useRef<string | null>(null);
|
|
323
425
|
const canEdit = !reportScreenshot && resourceCanEdit(resourceAccess);
|
|
324
426
|
const canManage = !reportScreenshot && resourceCanManage(resourceAccess);
|
|
@@ -636,6 +738,24 @@ export default function SqlDashboardPage() {
|
|
|
636
738
|
[collabDocId],
|
|
637
739
|
);
|
|
638
740
|
|
|
741
|
+
const updateCachedDashboardConfig = useCallback(
|
|
742
|
+
(updated: SqlDashboardConfig) => {
|
|
743
|
+
if (!dashboardId) return;
|
|
744
|
+
queryClient.setQueriesData<FetchedDashboard | null>(
|
|
745
|
+
{ queryKey: ["data", "sql-dashboard", dashboardId] },
|
|
746
|
+
(prev) => (prev ? { ...prev, config: updated } : prev),
|
|
747
|
+
);
|
|
748
|
+
queryClient.setQueryData<PrefetchSnapshot<FetchedDashboard | null>>(
|
|
749
|
+
sqlDashboardPrefetchKey(dashboardId),
|
|
750
|
+
(prev) =>
|
|
751
|
+
prev?.data
|
|
752
|
+
? { ...prev, data: { ...prev.data, config: updated } }
|
|
753
|
+
: prev,
|
|
754
|
+
);
|
|
755
|
+
},
|
|
756
|
+
[dashboardId, queryClient],
|
|
757
|
+
);
|
|
758
|
+
|
|
639
759
|
/**
|
|
640
760
|
* Persist without throwing — background save used for drag reorder, width
|
|
641
761
|
* toggle, title/description edits, and panel delete. If the save fails
|
|
@@ -650,6 +770,7 @@ export default function SqlDashboardPage() {
|
|
|
650
770
|
return;
|
|
651
771
|
}
|
|
652
772
|
setDashboard(updated);
|
|
773
|
+
updateCachedDashboardConfig(updated);
|
|
653
774
|
pushToCollab(updated);
|
|
654
775
|
saveDashboard(dashboardId, updated)
|
|
655
776
|
.then(() => {
|
|
@@ -676,7 +797,14 @@ export default function SqlDashboardPage() {
|
|
|
676
797
|
);
|
|
677
798
|
});
|
|
678
799
|
},
|
|
679
|
-
[
|
|
800
|
+
[
|
|
801
|
+
dashboardId,
|
|
802
|
+
canEdit,
|
|
803
|
+
queryClient,
|
|
804
|
+
pushToCollab,
|
|
805
|
+
t,
|
|
806
|
+
updateCachedDashboardConfig,
|
|
807
|
+
],
|
|
680
808
|
);
|
|
681
809
|
|
|
682
810
|
/**
|
|
@@ -691,6 +819,7 @@ export default function SqlDashboardPage() {
|
|
|
691
819
|
}
|
|
692
820
|
await saveDashboard(dashboardId, updated);
|
|
693
821
|
setDashboard(updated);
|
|
822
|
+
updateCachedDashboardConfig(updated);
|
|
694
823
|
pushToCollab(updated);
|
|
695
824
|
queryClient.removeQueries({
|
|
696
825
|
queryKey: sqlDashboardPrefetchKey(dashboardId),
|
|
@@ -701,7 +830,14 @@ export default function SqlDashboardPage() {
|
|
|
701
830
|
queryKey: ["data", "sql-dashboard", dashboardId],
|
|
702
831
|
});
|
|
703
832
|
},
|
|
704
|
-
[
|
|
833
|
+
[
|
|
834
|
+
dashboardId,
|
|
835
|
+
canEdit,
|
|
836
|
+
queryClient,
|
|
837
|
+
pushToCollab,
|
|
838
|
+
t,
|
|
839
|
+
updateCachedDashboardConfig,
|
|
840
|
+
],
|
|
705
841
|
);
|
|
706
842
|
|
|
707
843
|
const removePanel = useCallback(
|
|
@@ -756,13 +892,22 @@ export default function SqlDashboardPage() {
|
|
|
756
892
|
useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
|
|
757
893
|
);
|
|
758
894
|
|
|
895
|
+
const handleDragStart = useCallback((event: DragStartEvent) => {
|
|
896
|
+
setActiveDragPanelId(String(event.active.id));
|
|
897
|
+
}, []);
|
|
898
|
+
|
|
759
899
|
const handleDragOver = useCallback((event: DragOverEvent) => {
|
|
760
|
-
|
|
900
|
+
const nextSlot = readDropSlot(event.over?.data.current);
|
|
901
|
+
setActiveDropSlot((currentSlot) => {
|
|
902
|
+
if (!nextSlot) return currentSlot === null ? currentSlot : null;
|
|
903
|
+
return sameDropSlot(currentSlot, nextSlot) ? currentSlot : nextSlot;
|
|
904
|
+
});
|
|
761
905
|
}, []);
|
|
762
906
|
|
|
763
907
|
const handleDragEnd = useCallback(
|
|
764
908
|
(event: DragEndEvent) => {
|
|
765
909
|
setActiveDropSlot(null);
|
|
910
|
+
setActiveDragPanelId(null);
|
|
766
911
|
if (!dashboard) return;
|
|
767
912
|
const slot = readDropSlot(event.over?.data.current);
|
|
768
913
|
if (!slot) return;
|
|
@@ -784,6 +929,7 @@ export default function SqlDashboardPage() {
|
|
|
784
929
|
|
|
785
930
|
const handleDragCancel = useCallback(() => {
|
|
786
931
|
setActiveDropSlot(null);
|
|
932
|
+
setActiveDragPanelId(null);
|
|
787
933
|
}, []);
|
|
788
934
|
|
|
789
935
|
const handleSaveName = useCallback(() => {
|
|
@@ -889,6 +1035,14 @@ export default function SqlDashboardPage() {
|
|
|
889
1035
|
const panelGroups = useMemo(() => {
|
|
890
1036
|
return buildDashboardPanelGroups(visiblePanels, dashboardColumns);
|
|
891
1037
|
}, [visiblePanels, dashboardColumns]);
|
|
1038
|
+
const activeDragPanel = useMemo(
|
|
1039
|
+
() =>
|
|
1040
|
+
activeDragPanelId
|
|
1041
|
+
? (visiblePanels.find((panel) => panel.id === activeDragPanelId) ??
|
|
1042
|
+
null)
|
|
1043
|
+
: null,
|
|
1044
|
+
[activeDragPanelId, visiblePanels],
|
|
1045
|
+
);
|
|
892
1046
|
|
|
893
1047
|
const dashboardCollisionDetection = useCallback<CollisionDetection>(
|
|
894
1048
|
(args) => {
|
|
@@ -910,9 +1064,28 @@ export default function SqlDashboardPage() {
|
|
|
910
1064
|
});
|
|
911
1065
|
if (exactCollisions.length > 0) return exactCollisions;
|
|
912
1066
|
|
|
913
|
-
return
|
|
914
|
-
|
|
915
|
-
|
|
1067
|
+
if (!args.pointerCoordinates) return [];
|
|
1068
|
+
|
|
1069
|
+
const collisions: Collision[] = [];
|
|
1070
|
+
for (const droppableContainer of droppableContainers) {
|
|
1071
|
+
const rect = args.droppableRects.get(droppableContainer.id);
|
|
1072
|
+
if (!rect) continue;
|
|
1073
|
+
|
|
1074
|
+
collisions.push({
|
|
1075
|
+
id: droppableContainer.id,
|
|
1076
|
+
data: {
|
|
1077
|
+
droppableContainer,
|
|
1078
|
+
value: distanceFromPointerToRect(args.pointerCoordinates, rect),
|
|
1079
|
+
},
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
return collisions.sort((a, b) => {
|
|
1084
|
+
const aValue =
|
|
1085
|
+
typeof a.data?.value === "number" ? a.data.value : Number.MAX_VALUE;
|
|
1086
|
+
const bValue =
|
|
1087
|
+
typeof b.data?.value === "number" ? b.data.value : Number.MAX_VALUE;
|
|
1088
|
+
return aValue - bValue;
|
|
916
1089
|
});
|
|
917
1090
|
},
|
|
918
1091
|
[panelGroups],
|
|
@@ -1418,71 +1591,16 @@ export default function SqlDashboardPage() {
|
|
|
1418
1591
|
<DndContext
|
|
1419
1592
|
sensors={sensors}
|
|
1420
1593
|
collisionDetection={dashboardCollisionDetection}
|
|
1594
|
+
onDragStart={canEdit ? handleDragStart : undefined}
|
|
1421
1595
|
onDragOver={canEdit ? handleDragOver : undefined}
|
|
1422
1596
|
onDragEnd={canEdit ? handleDragEnd : undefined}
|
|
1423
1597
|
onDragCancel={handleDragCancel}
|
|
1424
1598
|
>
|
|
1425
|
-
<div
|
|
1599
|
+
<div
|
|
1600
|
+
className="dashboard-grid-container flex flex-col gap-1"
|
|
1601
|
+
data-dashboard-dragging={activeDragPanel ? "true" : undefined}
|
|
1602
|
+
>
|
|
1426
1603
|
{panelGroups.map((group) => {
|
|
1427
|
-
const renderPanelCell = (panel: SqlPanel) => {
|
|
1428
|
-
const resolved = panel.config?.description
|
|
1429
|
-
? {
|
|
1430
|
-
...panel,
|
|
1431
|
-
config: {
|
|
1432
|
-
...panel.config,
|
|
1433
|
-
description: interpolate(
|
|
1434
|
-
panel.config.description,
|
|
1435
|
-
vars,
|
|
1436
|
-
),
|
|
1437
|
-
},
|
|
1438
|
-
}
|
|
1439
|
-
: panel;
|
|
1440
|
-
const remoteEditor = reportScreenshot
|
|
1441
|
-
? undefined
|
|
1442
|
-
: remoteEditingPanels.get(panel.id);
|
|
1443
|
-
return (
|
|
1444
|
-
<div
|
|
1445
|
-
key={panel.id}
|
|
1446
|
-
className="dashboard-grid-cell relative h-full"
|
|
1447
|
-
style={
|
|
1448
|
-
{
|
|
1449
|
-
...(remoteEditor
|
|
1450
|
-
? {
|
|
1451
|
-
outline: `2px solid ${remoteEditor.color}`,
|
|
1452
|
-
outlineOffset: 2,
|
|
1453
|
-
borderRadius: 8,
|
|
1454
|
-
}
|
|
1455
|
-
: null),
|
|
1456
|
-
} as React.CSSProperties
|
|
1457
|
-
}
|
|
1458
|
-
>
|
|
1459
|
-
{remoteEditor && (
|
|
1460
|
-
<span
|
|
1461
|
-
className="absolute -top-2.5 left-3 px-1.5 text-[10px] font-medium rounded z-10"
|
|
1462
|
-
style={{
|
|
1463
|
-
backgroundColor: remoteEditor.color,
|
|
1464
|
-
color: "#fff",
|
|
1465
|
-
}}
|
|
1466
|
-
>
|
|
1467
|
-
{remoteEditor.name}
|
|
1468
|
-
</span>
|
|
1469
|
-
)}
|
|
1470
|
-
<SqlChartCard
|
|
1471
|
-
panel={resolved}
|
|
1472
|
-
resolvedSql={interpolate(
|
|
1473
|
-
serializePanelSql(panel.sql),
|
|
1474
|
-
vars,
|
|
1475
|
-
)}
|
|
1476
|
-
onRemove={() => removePanel(panel.id)}
|
|
1477
|
-
onEdit={() => openEditPanel(panel)}
|
|
1478
|
-
onSaveSql={(sql) => handleSavePanel({ ...panel, sql })}
|
|
1479
|
-
editable={canEdit}
|
|
1480
|
-
eagerLoad={reportScreenshot}
|
|
1481
|
-
/>
|
|
1482
|
-
</div>
|
|
1483
|
-
);
|
|
1484
|
-
};
|
|
1485
|
-
|
|
1486
1604
|
const renderSection = (section: SqlPanel) => {
|
|
1487
1605
|
const remoteEditor = reportScreenshot
|
|
1488
1606
|
? undefined
|
|
@@ -1501,7 +1619,7 @@ export default function SqlDashboardPage() {
|
|
|
1501
1619
|
: section;
|
|
1502
1620
|
return (
|
|
1503
1621
|
<div
|
|
1504
|
-
className="relative"
|
|
1622
|
+
className="dashboard-section-cell relative"
|
|
1505
1623
|
style={
|
|
1506
1624
|
remoteEditor
|
|
1507
1625
|
? {
|
|
@@ -1571,7 +1689,20 @@ export default function SqlDashboardPage() {
|
|
|
1571
1689
|
/>
|
|
1572
1690
|
{row.panels.map((panel, columnIndex) => (
|
|
1573
1691
|
<Fragment key={panel.id}>
|
|
1574
|
-
|
|
1692
|
+
<PanelCell
|
|
1693
|
+
panel={panel}
|
|
1694
|
+
vars={vars}
|
|
1695
|
+
remoteEditor={
|
|
1696
|
+
reportScreenshot
|
|
1697
|
+
? undefined
|
|
1698
|
+
: remoteEditingPanels.get(panel.id)
|
|
1699
|
+
}
|
|
1700
|
+
editable={canEdit}
|
|
1701
|
+
eagerLoad={reportScreenshot}
|
|
1702
|
+
onRemovePanel={removePanel}
|
|
1703
|
+
onEditPanel={openEditPanel}
|
|
1704
|
+
onSavePanel={handleSavePanel}
|
|
1705
|
+
/>
|
|
1575
1706
|
<DashboardDropLine
|
|
1576
1707
|
slot={{
|
|
1577
1708
|
type: "column",
|
|
@@ -1593,6 +1724,9 @@ export default function SqlDashboardPage() {
|
|
|
1593
1724
|
);
|
|
1594
1725
|
})}
|
|
1595
1726
|
</div>
|
|
1727
|
+
<DragOverlay adjustScale={false} dropAnimation={null} zIndex={1000}>
|
|
1728
|
+
<DashboardDragPreview panel={activeDragPanel} />
|
|
1729
|
+
</DragOverlay>
|
|
1596
1730
|
</DndContext>
|
|
1597
1731
|
)}
|
|
1598
1732
|
|