@agent-native/core 0.131.7 → 0.132.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 +123 -0
- package/corpus/core/docs/content/durable-background-runs.mdx +15 -6
- package/corpus/core/docs/design/durable-agent-runs.md +15 -11
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/activity.ts +127 -10
- package/corpus/core/src/a2a/index.ts +3 -0
- package/corpus/core/src/agent/durable-background.ts +38 -28
- package/corpus/core/src/agent/harness/background.ts +12 -2
- package/corpus/core/src/agent/production-agent.ts +18 -15
- package/corpus/core/src/agent/run-store.ts +45 -3
- package/corpus/core/src/application-state/handlers.ts +45 -0
- package/corpus/core/src/application-state/index.ts +3 -0
- package/corpus/core/src/application-state/store.ts +38 -34
- package/corpus/core/src/audit/redact.ts +104 -18
- package/corpus/core/src/cli/create.ts +115 -16
- package/corpus/core/src/client/AgentPanel.tsx +2 -7
- package/corpus/core/src/client/AssistantChat.tsx +10 -2
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +3 -0
- package/corpus/core/src/client/RunStuckBanner.tsx +29 -6
- package/corpus/core/src/client/agent-chat.ts +4 -9
- package/corpus/core/src/client/application-state.ts +115 -12
- package/corpus/core/src/client/dynamic-suggestions.ts +4 -12
- package/corpus/core/src/client/frame-protocol.ts +5 -1
- package/corpus/core/src/client/guided-questions.tsx +12 -23
- package/corpus/core/src/db/client.ts +148 -28
- package/corpus/core/src/db/create-get-db.ts +39 -6
- package/corpus/core/src/db/runtime-diagnostics.ts +41 -6
- package/corpus/core/src/deploy/build.ts +26 -33
- package/corpus/core/src/feature-flags/store.ts +10 -5
- package/corpus/core/src/integrations/a2a-continuations-store.ts +11 -0
- package/corpus/core/src/integrations/config-store.ts +13 -0
- package/corpus/core/src/integrations/google-docs-poller.ts +26 -2
- package/corpus/core/src/mcp-client/routes.ts +33 -2
- package/corpus/core/src/onboarding/plugin.ts +31 -25
- package/corpus/core/src/org/accept-pending.ts +2 -0
- package/corpus/core/src/org/auto-join-domain.ts +2 -0
- package/corpus/core/src/org/context.ts +35 -10
- package/corpus/core/src/org/handlers.ts +6 -0
- package/corpus/core/src/org/request-org-cache.ts +71 -0
- package/corpus/core/src/resources/store.ts +10 -9
- package/corpus/core/src/scripts/runner.ts +6 -3
- package/corpus/core/src/secrets/storage.ts +97 -5
- package/corpus/core/src/server/action-change.ts +33 -0
- package/corpus/core/src/server/action-routes.ts +11 -9
- package/corpus/core/src/server/agent-teams.ts +10 -2
- package/corpus/core/src/server/better-auth-instance.ts +39 -12
- package/corpus/core/src/server/core-routes-plugin.ts +19 -2
- package/corpus/core/src/server/credential-provider.ts +57 -14
- package/corpus/core/src/server/http-response-telemetry.ts +1 -0
- package/corpus/core/src/server/poll.ts +95 -57
- package/corpus/core/src/server/voice-providers-status.ts +19 -4
- package/corpus/core/src/workspace-files/tool.ts +18 -11
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +38 -0
- package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +1 -1
- package/corpus/templates/analytics/actions/ensure-native-v2-dashboards.ts +299 -0
- package/corpus/templates/analytics/actions/list-native-v2-dashboard-templates.ts +34 -0
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +82 -0
- package/corpus/templates/analytics/app/components/layout/NewDashboardDialog.tsx +2 -2
- package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +40 -34
- package/corpus/templates/analytics/app/hooks/use-user-pref.ts +3 -3
- package/corpus/templates/analytics/app/i18n-data.ts +40 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +18 -10
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +14 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +1 -0
- package/corpus/templates/analytics/app/routes/chart.tsx +3 -0
- package/corpus/templates/analytics/changelog/2026-07-30-analytics-dashboards-now-expose-native-funnel-heatmap-callou.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-30-analytics-now-ships-reusable-native-v2-dashboard-manifests-w.md +6 -0
- package/corpus/templates/analytics/server/lib/dashboard-report-render.ts +81 -1
- package/corpus/templates/analytics/server/lib/native-v2-dashboards.ts +457 -0
- package/corpus/templates/analytics/server/lib/report-chart-svg.ts +73 -2
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/analytics/shared/dashboard-funnel.ts +83 -0
- package/corpus/templates/clips/actions/generate-workflow.ts +6 -4
- package/corpus/templates/clips/actions/reconcile-workflow-generation.ts +257 -0
- package/corpus/templates/clips/app/hooks/use-auto-title.ts +166 -4
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +1 -1
- package/corpus/templates/clips/changelog/2026-07-27-stopped-agent-runs-no-longer-leave-generated-workflow-cards-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-30-workflow-generation-now-stops-retrying-after-repeated-failur.md +6 -0
- package/corpus/templates/clips/shared/workflow.ts +5 -0
- package/corpus/templates/content/changelog/2026-07-30-long-running-delegated-requests-now-continue-reliably.md +6 -0
- package/corpus/templates/content/netlify.toml +3 -0
- package/corpus/templates/content/server/plugins/agent-chat.ts +1 -0
- package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +2 -1
- package/corpus/templates/slides/actions/patch-deck.ts +25 -1
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +17 -4
- package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +9 -1
- package/corpus/templates/slides/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/slides/app/context/DeckContext.tsx +11 -3
- package/corpus/templates/slides/app/lib/sanitize-slide-html.ts +39 -11
- package/corpus/templates/slides/app/pages/Index.tsx +2 -2
- package/corpus/templates/slides/changelog/2026-07-30-slide-previews-keep-their-aspect-ratio-isolate-embedded-styl.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-30-slides-no-longer-lose-decks-while-organization-access-is-loa.md +6 -0
- package/corpus/toolkit/CHANGELOG.md +8 -0
- package/corpus/toolkit/package.json +1 -1
- package/corpus/toolkit/src/composer/runtime-adapters.tsx +16 -11
- package/dist/a2a/activity.d.ts +22 -4
- package/dist/a2a/activity.d.ts.map +1 -1
- package/dist/a2a/activity.js +97 -11
- package/dist/a2a/activity.js.map +1 -1
- package/dist/a2a/index.d.ts +1 -1
- package/dist/a2a/index.d.ts.map +1 -1
- package/dist/a2a/index.js +1 -1
- package/dist/a2a/index.js.map +1 -1
- package/dist/agent/durable-background.d.ts +13 -8
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +36 -28
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/agent/harness/background.d.ts.map +1 -1
- package/dist/agent/harness/background.js +12 -2
- package/dist/agent/harness/background.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +18 -15
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-store.d.ts +2 -0
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +44 -3
- package/dist/agent/run-store.js.map +1 -1
- package/dist/application-state/handlers.d.ts +15 -0
- package/dist/application-state/handlers.d.ts.map +1 -1
- package/dist/application-state/handlers.js +42 -2
- package/dist/application-state/handlers.js.map +1 -1
- package/dist/application-state/index.d.ts +2 -2
- package/dist/application-state/index.d.ts.map +1 -1
- package/dist/application-state/index.js +2 -2
- package/dist/application-state/index.js.map +1 -1
- package/dist/application-state/store.d.ts +11 -0
- package/dist/application-state/store.d.ts.map +1 -1
- package/dist/application-state/store.js +37 -40
- package/dist/application-state/store.js.map +1 -1
- package/dist/audit/redact.d.ts +25 -2
- package/dist/audit/redact.d.ts.map +1 -1
- package/dist/audit/redact.js +85 -21
- package/dist/audit/redact.js.map +1 -1
- package/dist/cli/create.d.ts +8 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +98 -16
- package/dist/cli/create.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +2 -7
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +10 -2
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +1 -1
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/RunStuckBanner.d.ts +11 -1
- package/dist/client/RunStuckBanner.d.ts.map +1 -1
- package/dist/client/RunStuckBanner.js +20 -7
- package/dist/client/RunStuckBanner.js.map +1 -1
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +4 -6
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/application-state.d.ts +11 -0
- package/dist/client/application-state.d.ts.map +1 -1
- package/dist/client/application-state.js +87 -9
- package/dist/client/application-state.js.map +1 -1
- package/dist/client/dynamic-suggestions.d.ts.map +1 -1
- package/dist/client/dynamic-suggestions.js +4 -13
- package/dist/client/dynamic-suggestions.js.map +1 -1
- package/dist/client/frame-protocol.d.ts +1 -0
- package/dist/client/frame-protocol.d.ts.map +1 -1
- package/dist/client/frame-protocol.js.map +1 -1
- package/dist/client/guided-questions.d.ts.map +1 -1
- package/dist/client/guided-questions.js +6 -22
- package/dist/client/guided-questions.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/db/client.d.ts +57 -10
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +122 -31
- package/dist/db/client.js.map +1 -1
- package/dist/db/create-get-db.d.ts.map +1 -1
- package/dist/db/create-get-db.js +31 -7
- package/dist/db/create-get-db.js.map +1 -1
- package/dist/db/runtime-diagnostics.d.ts +1 -0
- package/dist/db/runtime-diagnostics.d.ts.map +1 -1
- package/dist/db/runtime-diagnostics.js +31 -6
- package/dist/db/runtime-diagnostics.js.map +1 -1
- package/dist/deploy/build.d.ts +5 -20
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +18 -30
- package/dist/deploy/build.js.map +1 -1
- package/dist/feature-flags/store.d.ts.map +1 -1
- package/dist/feature-flags/store.js +10 -5
- package/dist/feature-flags/store.js.map +1 -1
- package/dist/integrations/a2a-continuations-store.d.ts.map +1 -1
- package/dist/integrations/a2a-continuations-store.js +12 -0
- package/dist/integrations/a2a-continuations-store.js.map +1 -1
- package/dist/integrations/config-store.d.ts +1 -0
- package/dist/integrations/config-store.d.ts.map +1 -1
- package/dist/integrations/config-store.js +11 -0
- package/dist/integrations/config-store.js.map +1 -1
- package/dist/integrations/google-docs-poller.d.ts.map +1 -1
- package/dist/integrations/google-docs-poller.js +19 -2
- package/dist/integrations/google-docs-poller.js.map +1 -1
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/mcp/screen-memory-stdio.d.ts.map +1 -1
- package/dist/mcp-client/routes.d.ts.map +1 -1
- package/dist/mcp-client/routes.js +30 -2
- package/dist/mcp-client/routes.js.map +1 -1
- package/dist/notifications/routes.d.ts +6 -6
- package/dist/onboarding/plugin.d.ts.map +1 -1
- package/dist/onboarding/plugin.js +12 -8
- package/dist/onboarding/plugin.js.map +1 -1
- package/dist/org/accept-pending.d.ts.map +1 -1
- package/dist/org/accept-pending.js +2 -0
- package/dist/org/accept-pending.js.map +1 -1
- package/dist/org/auto-join-domain.d.ts.map +1 -1
- package/dist/org/auto-join-domain.js +2 -0
- package/dist/org/auto-join-domain.js.map +1 -1
- package/dist/org/context.d.ts.map +1 -1
- package/dist/org/context.js +29 -9
- package/dist/org/context.js.map +1 -1
- package/dist/org/handlers.d.ts.map +1 -1
- package/dist/org/handlers.js +5 -0
- package/dist/org/handlers.js.map +1 -1
- package/dist/org/request-org-cache.d.ts +16 -0
- package/dist/org/request-org-cache.d.ts.map +1 -0
- package/dist/org/request-org-cache.js +62 -0
- package/dist/org/request-org-cache.js.map +1 -0
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/resources/store.d.ts.map +1 -1
- package/dist/resources/store.js +11 -9
- package/dist/resources/store.js.map +1 -1
- package/dist/scripts/runner.d.ts.map +1 -1
- package/dist/scripts/runner.js +3 -3
- package/dist/scripts/runner.js.map +1 -1
- package/dist/secrets/storage.d.ts.map +1 -1
- package/dist/secrets/storage.js +82 -5
- package/dist/secrets/storage.js.map +1 -1
- package/dist/server/action-change.d.ts +20 -0
- package/dist/server/action-change.d.ts.map +1 -1
- package/dist/server/action-change.js +29 -0
- package/dist/server/action-change.js.map +1 -1
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +7 -8
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-teams.d.ts.map +1 -1
- package/dist/server/agent-teams.js +10 -2
- package/dist/server/agent-teams.js.map +1 -1
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +31 -14
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +1 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +18 -4
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/credential-provider.d.ts +16 -0
- package/dist/server/credential-provider.d.ts.map +1 -1
- package/dist/server/credential-provider.js +49 -13
- package/dist/server/credential-provider.js.map +1 -1
- package/dist/server/http-response-telemetry.d.ts.map +1 -1
- package/dist/server/http-response-telemetry.js +1 -0
- package/dist/server/http-response-telemetry.js.map +1 -1
- package/dist/server/poll.d.ts +6 -0
- package/dist/server/poll.d.ts.map +1 -1
- package/dist/server/poll.js +84 -47
- package/dist/server/poll.js.map +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/voice-providers-status.d.ts.map +1 -1
- package/dist/server/voice-providers-status.js +12 -3
- package/dist/server/voice-providers-status.js.map +1 -1
- package/dist/workspace-files/tool.d.ts.map +1 -1
- package/dist/workspace-files/tool.js +18 -11
- package/dist/workspace-files/tool.js.map +1 -1
- package/docs/content/durable-background-runs.mdx +15 -6
- package/docs/design/durable-agent-runs.md +15 -11
- package/package.json +3 -3
- package/src/a2a/activity.ts +127 -10
- package/src/a2a/index.ts +3 -0
- package/src/agent/durable-background.ts +38 -28
- package/src/agent/harness/background.ts +12 -2
- package/src/agent/production-agent.ts +18 -15
- package/src/agent/run-store.ts +45 -3
- package/src/application-state/handlers.ts +45 -0
- package/src/application-state/index.ts +3 -0
- package/src/application-state/store.ts +38 -34
- package/src/audit/redact.ts +104 -18
- package/src/cli/create.ts +115 -16
- package/src/client/AgentPanel.tsx +2 -7
- package/src/client/AssistantChat.tsx +10 -2
- package/src/client/MultiTabAssistantChat.tsx +3 -0
- package/src/client/RunStuckBanner.tsx +29 -6
- package/src/client/agent-chat.ts +4 -9
- package/src/client/application-state.ts +115 -12
- package/src/client/dynamic-suggestions.ts +4 -12
- package/src/client/frame-protocol.ts +5 -1
- package/src/client/guided-questions.tsx +12 -23
- package/src/db/client.ts +148 -28
- package/src/db/create-get-db.ts +39 -6
- package/src/db/runtime-diagnostics.ts +41 -6
- package/src/deploy/build.ts +26 -33
- package/src/feature-flags/store.ts +10 -5
- package/src/integrations/a2a-continuations-store.ts +11 -0
- package/src/integrations/config-store.ts +13 -0
- package/src/integrations/google-docs-poller.ts +26 -2
- package/src/mcp-client/routes.ts +33 -2
- package/src/onboarding/plugin.ts +31 -25
- package/src/org/accept-pending.ts +2 -0
- package/src/org/auto-join-domain.ts +2 -0
- package/src/org/context.ts +35 -10
- package/src/org/handlers.ts +6 -0
- package/src/org/request-org-cache.ts +71 -0
- package/src/resources/store.ts +10 -9
- package/src/scripts/runner.ts +6 -3
- package/src/secrets/storage.ts +97 -5
- package/src/server/action-change.ts +33 -0
- package/src/server/action-routes.ts +11 -9
- package/src/server/agent-teams.ts +10 -2
- package/src/server/better-auth-instance.ts +39 -12
- package/src/server/core-routes-plugin.ts +19 -2
- package/src/server/credential-provider.ts +57 -14
- package/src/server/http-response-telemetry.ts +1 -0
- package/src/server/poll.ts +95 -57
- package/src/server/voice-providers-status.ts +19 -4
- package/src/workspace-files/tool.ts +18 -11
|
@@ -156,6 +156,43 @@ flattens into the baseline and reads as "no data."
|
|
|
156
156
|
- Scheduled email reports render the same two scales, so a dual-axis panel is
|
|
157
157
|
safe to put on a subscribed dashboard.
|
|
158
158
|
|
|
159
|
+
## Reusable Native Dashboard Patterns
|
|
160
|
+
|
|
161
|
+
The recent extension-backed dashboards in Builder Analytics cluster into a few
|
|
162
|
+
repeatable compositions. Prefer these native panels, with a real SQL or Data
|
|
163
|
+
Program result behind each one, when creating a replacement or a new dashboard:
|
|
164
|
+
|
|
165
|
+
| Pattern | Native composition |
|
|
166
|
+
| --- | --- |
|
|
167
|
+
| Customer ROI / value realization | `metric` KPI cards, `line` or `area` trends, `table` detail, and `callout` or `section` panels for the business narrative |
|
|
168
|
+
| Account engagement / outreach | `metric` coverage and adoption cards, a daily `line` trend, `heatmap` or `table` segmentation, and `callout` alerts |
|
|
169
|
+
| GTM pipeline / cross-sell | `funnel` for ordered stages, `metric` totals, `bar` or `line` trends, and a `table` for account-level follow-up |
|
|
170
|
+
| Win/loss analysis | `section` groups with `metric`, `table`, `bar`, `callout`, and trend panels; use a Data Program for provider joins and evidence rows |
|
|
171
|
+
|
|
172
|
+
Funnel panels use `config.xKey` for the stage label and `config.yKey` for a
|
|
173
|
+
non-negative count or value. The renderer preserves the SQL row order, shows
|
|
174
|
+
each stage's share of the first stage, and shows the change from the previous
|
|
175
|
+
stage. Keep the intended stage order in SQL with `ORDER BY`.
|
|
176
|
+
|
|
177
|
+
When a dashboard is being migrated from an extension, create a new v2 copy,
|
|
178
|
+
bind its panels to the real provider schema or Data Programs, and compare it
|
|
179
|
+
with the original before retiring the extension-backed version. Do not invent
|
|
180
|
+
customer-specific SQL, provider joins, cached rows, or extension ids in a
|
|
181
|
+
catalog template. Existing dashboards remain readable while the native
|
|
182
|
+
replacement is validated. Bespoke interaction flows, arbitrary layouts, and
|
|
183
|
+
visualizations outside these contracts may remain Custom Blocks.
|
|
184
|
+
|
|
185
|
+
The source tree ships four provider-free v2 manifests in
|
|
186
|
+
`server/lib/native-v2-dashboards.ts`: Customer ROI, Account Engagement,
|
|
187
|
+
Cross-sell, and Win / Loss. They intentionally contain no customer names,
|
|
188
|
+
provider ids, SQL, cached rows, or guessed joins. After deployment, an
|
|
189
|
+
organization owner or admin provisions them with `ensure-native-v2-dashboards`
|
|
190
|
+
by supplying one real Data Program per binding key. The action validates the
|
|
191
|
+
stored program output contract, shares the programs with the organization,
|
|
192
|
+
creates deterministic `native-*-v2-*` dashboard copies, and preserves the
|
|
193
|
+
extension-backed originals and any existing v2 edits. Do not add these to the
|
|
194
|
+
root demo bootstrap or silently auto-bind them to guessed provider schemas.
|
|
195
|
+
|
|
159
196
|
## When To Use An Extension Instead
|
|
160
197
|
|
|
161
198
|
Native Analytics dashboards are JSON configs rendered by the built-in dashboard
|
|
@@ -474,6 +511,7 @@ type PanelPatch = {
|
|
|
474
511
|
| "table"
|
|
475
512
|
| "pie"
|
|
476
513
|
| "section"
|
|
514
|
+
| "funnel"
|
|
477
515
|
| "heatmap"
|
|
478
516
|
| "callout"
|
|
479
517
|
| "extension";
|
|
@@ -49,7 +49,7 @@ type PanelPatch = {
|
|
|
49
49
|
title?: string;
|
|
50
50
|
sql?: string;
|
|
51
51
|
source?: "bigquery" | "ga4" | "amplitude" | "first-party" | "demo" | "prometheus" | "program";
|
|
52
|
-
chartType?: "line" | "area" | "bar" | "metric" | "table" | "pie" | "section" | "heatmap" | "callout" | "extension";
|
|
52
|
+
chartType?: "line" | "area" | "bar" | "metric" | "table" | "pie" | "funnel" | "section" | "heatmap" | "callout" | "extension";
|
|
53
53
|
width?: number;
|
|
54
54
|
columns?: number;
|
|
55
55
|
tab?: string;
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { defineAction, embedApp } from "@agent-native/core";
|
|
4
|
+
import { getDataProgram } from "@agent-native/core/data-programs";
|
|
5
|
+
import {
|
|
6
|
+
buildDeepLink,
|
|
7
|
+
getRequestOrgId,
|
|
8
|
+
getRequestUserEmail,
|
|
9
|
+
} from "@agent-native/core/server";
|
|
10
|
+
import { resolveAccess } from "@agent-native/core/sharing";
|
|
11
|
+
import setResourceVisibilityAction from "@agent-native/core/sharing/actions/set-resource-visibility";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
|
|
14
|
+
import { getDashboard, upsertDashboard } from "../server/lib/dashboards-store";
|
|
15
|
+
import { requireAnalyticsAdminContext } from "../server/lib/db-admin-connections";
|
|
16
|
+
import {
|
|
17
|
+
compileNativeV2Dashboard,
|
|
18
|
+
getNativeV2DashboardManifest,
|
|
19
|
+
nativeV2DashboardManifests,
|
|
20
|
+
NATIVE_V2_DASHBOARD_IDS,
|
|
21
|
+
type NativeV2Binding,
|
|
22
|
+
type NativeV2BindingKey,
|
|
23
|
+
type NativeV2DashboardId,
|
|
24
|
+
} from "../server/lib/native-v2-dashboards";
|
|
25
|
+
|
|
26
|
+
const ANALYTICS_APP_ID = "analytics";
|
|
27
|
+
|
|
28
|
+
const bindingSchema = z.object({
|
|
29
|
+
programId: z.string().min(1),
|
|
30
|
+
params: z.record(z.string(), z.unknown()).optional(),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
function orgDashboardId(
|
|
34
|
+
orgId: string,
|
|
35
|
+
templateId: NativeV2DashboardId,
|
|
36
|
+
): string {
|
|
37
|
+
const suffix = createHash("sha256").update(orgId).digest("hex").slice(0, 10);
|
|
38
|
+
return `native-${templateId}-${suffix}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function parseOutputColumns(value: string | null): Set<string> {
|
|
42
|
+
if (!value) return new Set();
|
|
43
|
+
try {
|
|
44
|
+
const parsed = JSON.parse(value);
|
|
45
|
+
if (!Array.isArray(parsed)) return new Set();
|
|
46
|
+
return new Set(
|
|
47
|
+
parsed.flatMap((column) =>
|
|
48
|
+
column && typeof column === "object" && typeof column.name === "string"
|
|
49
|
+
? [column.name]
|
|
50
|
+
: [],
|
|
51
|
+
),
|
|
52
|
+
);
|
|
53
|
+
} catch {
|
|
54
|
+
return new Set();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function templateIdFromDashboard(
|
|
59
|
+
config: Record<string, unknown>,
|
|
60
|
+
): string | null {
|
|
61
|
+
const catalog = config.catalog;
|
|
62
|
+
if (!catalog || typeof catalog !== "object" || Array.isArray(catalog)) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const templateId = (catalog as Record<string, unknown>).templateId;
|
|
66
|
+
return typeof templateId === "string" ? templateId : null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function validateAndShareBindings(
|
|
70
|
+
bindings: Record<string, NativeV2Binding>,
|
|
71
|
+
requiredKeys: NativeV2BindingKey[],
|
|
72
|
+
requiredColumnsByBinding: Partial<Record<NativeV2BindingKey, string[]>>,
|
|
73
|
+
admin: { userEmail: string; orgId: string },
|
|
74
|
+
): Promise<void> {
|
|
75
|
+
const missing = requiredKeys.filter((key) => !bindings[key]?.programId);
|
|
76
|
+
if (missing.length > 0) {
|
|
77
|
+
throw new Error(`Missing required v2 bindings: ${missing.join(", ")}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const uniqueProgramIds = new Set(
|
|
81
|
+
requiredKeys.map((key) => bindings[key]?.programId).filter(Boolean),
|
|
82
|
+
);
|
|
83
|
+
const programsToShare: string[] = [];
|
|
84
|
+
for (const programId of uniqueProgramIds) {
|
|
85
|
+
const program = await getDataProgram(programId, ANALYTICS_APP_ID);
|
|
86
|
+
if (!program) {
|
|
87
|
+
throw new Error(`Data program not found: ${programId}`);
|
|
88
|
+
}
|
|
89
|
+
if (program.orgId && program.orgId !== admin.orgId) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`Data program belongs to another organization: ${programId}`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
if (program.archivedAt) {
|
|
95
|
+
throw new Error(`Data program is archived: ${programId}`);
|
|
96
|
+
}
|
|
97
|
+
const access = await resolveAccess("data_program", programId, {
|
|
98
|
+
userEmail: admin.userEmail,
|
|
99
|
+
orgId: admin.orgId,
|
|
100
|
+
});
|
|
101
|
+
if (!access) {
|
|
102
|
+
throw new Error(`No access to data program: ${programId}`);
|
|
103
|
+
}
|
|
104
|
+
const outputColumns = parseOutputColumns(program.outputColumns);
|
|
105
|
+
const missingColumns = [
|
|
106
|
+
...new Set(
|
|
107
|
+
requiredKeys.flatMap((key) =>
|
|
108
|
+
bindings[key]?.programId === programId
|
|
109
|
+
? (requiredColumnsByBinding[key] ?? [])
|
|
110
|
+
: [],
|
|
111
|
+
),
|
|
112
|
+
),
|
|
113
|
+
].filter((column) => !outputColumns.has(column));
|
|
114
|
+
if (missingColumns.length > 0) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`Data program ${programId} is missing required output columns: ${missingColumns.join(", ")}. Re-save it after a successful dry-run.`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (program.visibility !== "org" || program.orgId !== admin.orgId) {
|
|
121
|
+
programsToShare.push(programId);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
for (const programId of programsToShare) {
|
|
126
|
+
await setResourceVisibilityAction.run({
|
|
127
|
+
resourceType: "data_program",
|
|
128
|
+
resourceId: programId,
|
|
129
|
+
visibility: "org",
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface EnsureNativeV2DashboardsResult {
|
|
135
|
+
orgId: string;
|
|
136
|
+
dashboards: Array<{
|
|
137
|
+
templateId: NativeV2DashboardId;
|
|
138
|
+
dashboardId: string;
|
|
139
|
+
status: "created" | "updated" | "preserved";
|
|
140
|
+
requiredBindings: NativeV2BindingKey[];
|
|
141
|
+
}>;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export async function ensureNativeV2Dashboards(
|
|
145
|
+
args: {
|
|
146
|
+
templateIds?: NativeV2DashboardId[];
|
|
147
|
+
bindings: Record<string, NativeV2Binding>;
|
|
148
|
+
overwrite: boolean;
|
|
149
|
+
},
|
|
150
|
+
ctx?: { userEmail?: string; orgId?: string | null },
|
|
151
|
+
): Promise<EnsureNativeV2DashboardsResult> {
|
|
152
|
+
const admin = await requireAnalyticsAdminContext(ctx);
|
|
153
|
+
const templateIds = args.templateIds?.length
|
|
154
|
+
? args.templateIds
|
|
155
|
+
: [...NATIVE_V2_DASHBOARD_IDS];
|
|
156
|
+
const manifests = templateIds.map(getNativeV2DashboardManifest);
|
|
157
|
+
const allRequiredBindings = [
|
|
158
|
+
...new Set(manifests.flatMap((manifest) => manifest.requiredBindings)),
|
|
159
|
+
];
|
|
160
|
+
const requiredColumnsByBinding: Partial<
|
|
161
|
+
Record<NativeV2BindingKey, string[]>
|
|
162
|
+
> = Object.fromEntries(
|
|
163
|
+
manifests.flatMap((manifest) =>
|
|
164
|
+
manifest.panels
|
|
165
|
+
.filter(
|
|
166
|
+
(panel): panel is typeof panel & { bindingKey: NativeV2BindingKey } =>
|
|
167
|
+
Boolean(panel.bindingKey),
|
|
168
|
+
)
|
|
169
|
+
.map((panel) => [panel.bindingKey, panel.requiredColumns ?? []]),
|
|
170
|
+
),
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
await validateAndShareBindings(
|
|
174
|
+
args.bindings,
|
|
175
|
+
allRequiredBindings,
|
|
176
|
+
requiredColumnsByBinding,
|
|
177
|
+
admin,
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
const installedAt = new Date().toISOString();
|
|
181
|
+
const dashboards: EnsureNativeV2DashboardsResult["dashboards"] = [];
|
|
182
|
+
for (const manifest of manifests) {
|
|
183
|
+
const dashboardId = orgDashboardId(admin.orgId, manifest.id);
|
|
184
|
+
const existing = await getDashboard(dashboardId, {
|
|
185
|
+
email: admin.userEmail,
|
|
186
|
+
orgId: admin.orgId,
|
|
187
|
+
});
|
|
188
|
+
if (existing && templateIdFromDashboard(existing.config) !== manifest.id) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
`Refusing to overwrite unrelated dashboard at deterministic id ${dashboardId}.`,
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (existing && !args.overwrite) {
|
|
195
|
+
dashboards.push({
|
|
196
|
+
templateId: manifest.id,
|
|
197
|
+
dashboardId,
|
|
198
|
+
status: "preserved",
|
|
199
|
+
requiredBindings: manifest.requiredBindings,
|
|
200
|
+
});
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const config = compileNativeV2Dashboard(
|
|
205
|
+
manifest,
|
|
206
|
+
args.bindings,
|
|
207
|
+
installedAt,
|
|
208
|
+
);
|
|
209
|
+
await upsertDashboard(
|
|
210
|
+
dashboardId,
|
|
211
|
+
"sql",
|
|
212
|
+
config as unknown as Record<string, unknown>,
|
|
213
|
+
{
|
|
214
|
+
email: admin.userEmail,
|
|
215
|
+
orgId: admin.orgId,
|
|
216
|
+
},
|
|
217
|
+
);
|
|
218
|
+
if (!existing) {
|
|
219
|
+
await setResourceVisibilityAction.run({
|
|
220
|
+
resourceType: "dashboard",
|
|
221
|
+
resourceId: dashboardId,
|
|
222
|
+
visibility: "org",
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
dashboards.push({
|
|
226
|
+
templateId: manifest.id,
|
|
227
|
+
dashboardId,
|
|
228
|
+
status: existing ? "updated" : "created",
|
|
229
|
+
requiredBindings: manifest.requiredBindings,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return { orgId: admin.orgId, dashboards };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export default defineAction({
|
|
237
|
+
description:
|
|
238
|
+
"Provision the four reusable native Analytics v2 dashboard patterns for the active organization from explicit Data Program bindings. " +
|
|
239
|
+
"This is an admin-only post-deploy action: it validates every program exists and exposes the manifest's required output columns, shares the bound programs with the organization, creates deterministic native-* dashboard copies, and never changes the legacy extension dashboards. " +
|
|
240
|
+
"Re-running preserves existing v2 dashboard edits unless overwrite=true.",
|
|
241
|
+
schema: z.object({
|
|
242
|
+
templateIds: z
|
|
243
|
+
.array(z.enum(NATIVE_V2_DASHBOARD_IDS))
|
|
244
|
+
.min(1)
|
|
245
|
+
.optional()
|
|
246
|
+
.describe("Optional subset of native v2 templates to provision."),
|
|
247
|
+
bindings: z
|
|
248
|
+
.record(z.string(), bindingSchema)
|
|
249
|
+
.describe(
|
|
250
|
+
'Binding key to stored Analytics Data Program, e.g. {"roi.kpi": {"programId": "dp_..."}}.',
|
|
251
|
+
),
|
|
252
|
+
overwrite: z
|
|
253
|
+
.boolean()
|
|
254
|
+
.optional()
|
|
255
|
+
.default(false)
|
|
256
|
+
.describe("Replace an existing v2 copy only when explicitly true."),
|
|
257
|
+
}),
|
|
258
|
+
mcpApp: {
|
|
259
|
+
compactCatalog: true,
|
|
260
|
+
resource: embedApp({
|
|
261
|
+
title: "Native Analytics v2 dashboards",
|
|
262
|
+
description: "Open the provisioned native v2 dashboard in Analytics.",
|
|
263
|
+
iframeTitle: "Agent-Native Analytics",
|
|
264
|
+
openLabel: "Open native v2 dashboard",
|
|
265
|
+
height: 680,
|
|
266
|
+
}),
|
|
267
|
+
},
|
|
268
|
+
run: async (args, ctx) => {
|
|
269
|
+
const userEmail = ctx?.userEmail ?? getRequestUserEmail();
|
|
270
|
+
const orgId = ctx?.orgId ?? getRequestOrgId() ?? null;
|
|
271
|
+
return ensureNativeV2Dashboards(
|
|
272
|
+
{
|
|
273
|
+
templateIds: args.templateIds,
|
|
274
|
+
bindings: args.bindings,
|
|
275
|
+
overwrite: args.overwrite,
|
|
276
|
+
},
|
|
277
|
+
{ userEmail, orgId },
|
|
278
|
+
);
|
|
279
|
+
},
|
|
280
|
+
link: ({ result }) => {
|
|
281
|
+
const dashboardId =
|
|
282
|
+
result && typeof result === "object"
|
|
283
|
+
? (result as EnsureNativeV2DashboardsResult).dashboards?.[0]
|
|
284
|
+
?.dashboardId
|
|
285
|
+
: null;
|
|
286
|
+
if (!dashboardId) return null;
|
|
287
|
+
return {
|
|
288
|
+
url: buildDeepLink({
|
|
289
|
+
app: "analytics",
|
|
290
|
+
view: "adhoc",
|
|
291
|
+
params: { dashboardId },
|
|
292
|
+
}),
|
|
293
|
+
label: "Open native v2 dashboard",
|
|
294
|
+
view: "adhoc",
|
|
295
|
+
};
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
export { nativeV2DashboardManifests };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
nativeV2DashboardManifests,
|
|
6
|
+
NATIVE_V2_DASHBOARD_VERSION,
|
|
7
|
+
} from "../server/lib/native-v2-dashboards";
|
|
8
|
+
|
|
9
|
+
export default defineAction({
|
|
10
|
+
description:
|
|
11
|
+
"List the source-controlled native Analytics v2 dashboard manifests, including required Data Program binding keys and output columns. These provider-free manifests are compiled into org dashboards by ensure-native-v2-dashboards.",
|
|
12
|
+
schema: z.object({}),
|
|
13
|
+
http: { method: "GET" },
|
|
14
|
+
readOnly: true,
|
|
15
|
+
publicAgent: { expose: true, readOnly: true, requiresAuth: true },
|
|
16
|
+
run: async () => ({
|
|
17
|
+
version: NATIVE_V2_DASHBOARD_VERSION,
|
|
18
|
+
templates: nativeV2DashboardManifests.map((manifest) => ({
|
|
19
|
+
id: manifest.id,
|
|
20
|
+
name: manifest.name,
|
|
21
|
+
description: manifest.description,
|
|
22
|
+
category: manifest.category,
|
|
23
|
+
tags: manifest.tags,
|
|
24
|
+
requiredBindings: manifest.requiredBindings,
|
|
25
|
+
panels: manifest.panels.map((panel) => ({
|
|
26
|
+
id: panel.id,
|
|
27
|
+
title: panel.title,
|
|
28
|
+
chartType: panel.chartType,
|
|
29
|
+
bindingKey: panel.bindingKey ?? null,
|
|
30
|
+
requiredColumns: panel.requiredColumns ?? [],
|
|
31
|
+
})),
|
|
32
|
+
})),
|
|
33
|
+
}),
|
|
34
|
+
});
|
|
@@ -64,6 +64,8 @@ import {
|
|
|
64
64
|
import { useChartTooltipPortalPosition } from "@/hooks/use-chart-tooltip-portal";
|
|
65
65
|
|
|
66
66
|
const PAGE_SIZE_OPTIONS = [10, 25, 50, 100];
|
|
67
|
+
import { resolveDashboardFunnelRows } from "@shared/dashboard-funnel";
|
|
68
|
+
|
|
67
69
|
import { createDemoChartTrendRows } from "@/lib/demo-chart-trend";
|
|
68
70
|
import { useSqlQuery } from "@/lib/sql-query";
|
|
69
71
|
import {
|
|
@@ -1454,6 +1456,12 @@ export function SqlChart({
|
|
|
1454
1456
|
);
|
|
1455
1457
|
}
|
|
1456
1458
|
|
|
1459
|
+
if (chartType === "funnel") {
|
|
1460
|
+
return withConfigWarning(
|
|
1461
|
+
<FunnelRenderer rows={rows} panel={panel} colors={colors} />,
|
|
1462
|
+
);
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1457
1465
|
if (chartType === "heatmap") {
|
|
1458
1466
|
return withConfigWarning(<HeatmapRenderer rows={rows} panel={panel} />);
|
|
1459
1467
|
}
|
|
@@ -2350,6 +2358,80 @@ function TimeSeriesRenderer({
|
|
|
2350
2358
|
);
|
|
2351
2359
|
}
|
|
2352
2360
|
|
|
2361
|
+
function FunnelRenderer({
|
|
2362
|
+
rows,
|
|
2363
|
+
panel,
|
|
2364
|
+
colors,
|
|
2365
|
+
}: {
|
|
2366
|
+
rows: Record<string, unknown>[];
|
|
2367
|
+
panel: SqlPanel;
|
|
2368
|
+
colors: string[];
|
|
2369
|
+
}) {
|
|
2370
|
+
const t = useT();
|
|
2371
|
+
const funnel = useMemo(
|
|
2372
|
+
() =>
|
|
2373
|
+
resolveDashboardFunnelRows(rows, panel.config?.xKey, panel.config?.yKey),
|
|
2374
|
+
[rows, panel.config?.xKey, panel.config?.yKey],
|
|
2375
|
+
);
|
|
2376
|
+
|
|
2377
|
+
if (funnel.items.length === 0) {
|
|
2378
|
+
return (
|
|
2379
|
+
<div
|
|
2380
|
+
className={`flex items-center justify-center py-8 ${TABLE_PANEL_MIN_HEIGHT_CLASS}`}
|
|
2381
|
+
>
|
|
2382
|
+
<p className="text-sm text-muted-foreground text-center">
|
|
2383
|
+
{t("common.noData")}
|
|
2384
|
+
</p>
|
|
2385
|
+
</div>
|
|
2386
|
+
);
|
|
2387
|
+
}
|
|
2388
|
+
|
|
2389
|
+
const maxValue = Math.max(...funnel.items.map((item) => item.value), 1);
|
|
2390
|
+
const formatter = panel.config?.yFormatter;
|
|
2391
|
+
const funnelColors = colors.length > 0 ? colors : DEFAULT_COLORS;
|
|
2392
|
+
|
|
2393
|
+
return (
|
|
2394
|
+
<div
|
|
2395
|
+
className="space-y-3 py-2"
|
|
2396
|
+
role="img"
|
|
2397
|
+
aria-label={panel.title}
|
|
2398
|
+
data-dashboard-funnel="true"
|
|
2399
|
+
>
|
|
2400
|
+
{funnel.items.map((item, index) => {
|
|
2401
|
+
const width =
|
|
2402
|
+
item.value > 0 ? Math.max(2, (item.value / maxValue) * 100) : 0;
|
|
2403
|
+
const dropOff =
|
|
2404
|
+
item.dropOffPercent === null
|
|
2405
|
+
? null
|
|
2406
|
+
: `${item.dropOffPercent < 0 ? "↑" : "↓"} ${Math.abs(item.dropOffPercent).toFixed(1)}%`;
|
|
2407
|
+
return (
|
|
2408
|
+
<div key={`${item.label}-${index}`} className="space-y-1">
|
|
2409
|
+
<div className="flex items-baseline justify-between gap-3 text-xs">
|
|
2410
|
+
<span className="min-w-0 truncate font-medium text-foreground">
|
|
2411
|
+
{item.label}
|
|
2412
|
+
</span>
|
|
2413
|
+
<span className="shrink-0 tabular-nums text-muted-foreground">
|
|
2414
|
+
{formatYValue(item.value, formatter)} ·{" "}
|
|
2415
|
+
{item.percentOfFirst.toFixed(1)}%
|
|
2416
|
+
{dropOff ? ` · ${dropOff}` : ""}
|
|
2417
|
+
</span>
|
|
2418
|
+
</div>
|
|
2419
|
+
<div className="h-5 overflow-hidden rounded-sm bg-muted/60">
|
|
2420
|
+
<div
|
|
2421
|
+
className="h-full rounded-sm transition-[width]"
|
|
2422
|
+
style={{
|
|
2423
|
+
width: `${width}%`,
|
|
2424
|
+
backgroundColor: funnelColors[index % funnelColors.length],
|
|
2425
|
+
}}
|
|
2426
|
+
/>
|
|
2427
|
+
</div>
|
|
2428
|
+
</div>
|
|
2429
|
+
);
|
|
2430
|
+
})}
|
|
2431
|
+
</div>
|
|
2432
|
+
);
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2353
2435
|
// Heatmap config: `xKey` = x-axis column, `yKey` = numeric value column,
|
|
2354
2436
|
// `color` = optional row-label column. If `color` is omitted, the renderer
|
|
2355
2437
|
// auto-detects the row-label as the first non-x non-value string column.
|
|
@@ -22,9 +22,9 @@ const DASHBOARD_CONTEXT =
|
|
|
22
22
|
"NATIVE PANELS — Create a SQL-driven dashboard by calling the `update-dashboard` action with `dashboardId` and `config`; use a Data Program first when reusable fetching or transformation is the missing layer. " +
|
|
23
23
|
"The config shape is: { name: string, panels: [{ id, title, sql, source, chartType, width, tab?, config? }] }. " +
|
|
24
24
|
"Each panel needs: id (unique string), title, sql (the query), source ('bigquery' | 'ga4' | 'amplitude' | 'first-party' | 'demo' | 'prometheus'), " +
|
|
25
|
-
"chartType ('line' | 'area' | 'bar' | 'metric' | 'table' | 'pie'), width (1 or 2). " +
|
|
25
|
+
"chartType ('line' | 'area' | 'bar' | 'metric' | 'table' | 'pie' | 'funnel' | 'heatmap' | 'callout' | 'section'), width (1 or 2). " +
|
|
26
26
|
"Optional tab labels can use 'Group / Tab' for primary and secondary dashboard tabs. " +
|
|
27
|
-
"Optional config: { xKey, yKey, yKeys, color, colors, yFormatter ('number'|'currency'|'percent'), description, valueLabels }. " +
|
|
27
|
+
"Optional config: { xKey, yKey, yKeys, color, colors, yFormatter ('number'|'currency'|'percent'), description, valueLabels }. For funnel panels, xKey is the stage label, yKey is the non-negative value, and SQL ORDER BY defines stage order. " +
|
|
28
28
|
"For first-party analytics, source is 'first-party' and sql may read analytics_events only; do not use db-query for datasource panels. " +
|
|
29
29
|
"For the built-in demo dashboard, source is 'demo' and sql uses the same Prometheus JSON descriptor shape as source 'prometheus': { promql, mode, range, step }. " +
|
|
30
30
|
"Call `data-source-status` if you need to see which data sources are connected. " +
|
|
@@ -18,6 +18,7 @@ const DASHBOARD_CONTEXT_KEY = "analytics-selected-dashboard";
|
|
|
18
18
|
const DASHBOARD_PANEL_CONTEXT_KEY = "analytics-selected-dashboard-panel";
|
|
19
19
|
const SELECTED_OBJECT_STATE_KEY = "selected-object";
|
|
20
20
|
const SELECTED_OBJECT_SOURCE_FIELD = "__agentNativeSelectedObjectSource";
|
|
21
|
+
const CONTEXT_PUBLISH_DELAY_MS = 250;
|
|
21
22
|
|
|
22
23
|
export interface DashboardChatContextArgs {
|
|
23
24
|
id: string | null | undefined;
|
|
@@ -233,56 +234,61 @@ export function useDashboardChatContext(
|
|
|
233
234
|
);
|
|
234
235
|
}, []);
|
|
235
236
|
|
|
237
|
+
// Dashboard metadata lands in pieces — title first, then panel count, then
|
|
238
|
+
// the access role. Publishing each piece costs a round-trip and a sync event
|
|
239
|
+
// that invalidates every mounted query, so only the settled value is sent.
|
|
236
240
|
useEffect(() => {
|
|
237
241
|
if (!id) return;
|
|
238
242
|
const displayTitle = title?.trim() || id;
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
key: DASHBOARD_CONTEXT_KEY,
|
|
242
|
-
title: `Dashboard: ${displayTitle}`,
|
|
243
|
-
context: dashboardContext({
|
|
244
|
-
id,
|
|
245
|
-
kind,
|
|
246
|
-
title: displayTitle,
|
|
247
|
-
panelCount,
|
|
248
|
-
canEdit,
|
|
249
|
-
}),
|
|
250
|
-
openSidebar: false,
|
|
251
|
-
focus: false,
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
return () => {
|
|
255
|
-
removeAgentChatContextItem({
|
|
243
|
+
const timer = setTimeout(() => {
|
|
244
|
+
setAgentChatContextItem({
|
|
256
245
|
key: DASHBOARD_CONTEXT_KEY,
|
|
246
|
+
title: `Dashboard: ${displayTitle}`,
|
|
247
|
+
context: dashboardContext({
|
|
248
|
+
id,
|
|
249
|
+
kind,
|
|
250
|
+
title: displayTitle,
|
|
251
|
+
panelCount,
|
|
252
|
+
canEdit,
|
|
253
|
+
}),
|
|
257
254
|
openSidebar: false,
|
|
255
|
+
focus: false,
|
|
258
256
|
});
|
|
259
|
-
};
|
|
257
|
+
}, CONTEXT_PUBLISH_DELAY_MS);
|
|
258
|
+
return () => clearTimeout(timer);
|
|
260
259
|
}, [canEdit, id, kind, panelCount, title]);
|
|
261
260
|
|
|
262
261
|
useEffect(() => {
|
|
263
262
|
if (!id || hasSelectedPanel) return;
|
|
264
263
|
const displayTitle = title?.trim() || id;
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
264
|
+
const timer = setTimeout(() => {
|
|
265
|
+
setClientAppState(
|
|
266
|
+
SELECTED_OBJECT_STATE_KEY,
|
|
267
|
+
{
|
|
268
|
+
type: "dashboard",
|
|
269
|
+
id,
|
|
270
|
+
kind,
|
|
271
|
+
title: displayTitle,
|
|
272
|
+
panelCount,
|
|
273
|
+
canEdit,
|
|
274
|
+
[SELECTED_OBJECT_SOURCE_FIELD]: TAB_ID,
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
keepalive: true,
|
|
278
|
+
requestSource: TAB_ID,
|
|
279
|
+
},
|
|
280
|
+
).catch(() => {});
|
|
281
|
+
}, CONTEXT_PUBLISH_DELAY_MS);
|
|
282
|
+
return () => clearTimeout(timer);
|
|
281
283
|
}, [canEdit, hasSelectedPanel, id, kind, panelCount, title]);
|
|
282
284
|
|
|
283
285
|
useEffect(() => {
|
|
284
286
|
if (!id) return;
|
|
285
287
|
return () => {
|
|
288
|
+
removeAgentChatContextItem({
|
|
289
|
+
key: DASHBOARD_CONTEXT_KEY,
|
|
290
|
+
openSidebar: false,
|
|
291
|
+
});
|
|
286
292
|
removeAgentChatContextItem({
|
|
287
293
|
key: DASHBOARD_PANEL_CONTEXT_KEY,
|
|
288
294
|
openSidebar: false,
|
|
@@ -32,13 +32,13 @@ export function useUserPref<T extends Record<string, unknown>>(key: string) {
|
|
|
32
32
|
queryClient.setQueryData(queryKey, value);
|
|
33
33
|
return { previousValue };
|
|
34
34
|
},
|
|
35
|
+
// No invalidate on success: the optimistic value above is exactly what the
|
|
36
|
+
// server stored, so refetching it only buys a round-trip. `onError` is the
|
|
37
|
+
// path that restores truth.
|
|
35
38
|
onError: (_err, _value, context) => {
|
|
36
39
|
queryClient.setQueryData(queryKey, context?.previousValue);
|
|
37
40
|
toast.error("Failed to save preference");
|
|
38
41
|
},
|
|
39
|
-
onSettled: () => {
|
|
40
|
-
queryClient.invalidateQueries({ queryKey });
|
|
41
|
-
},
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
const { mutate: remove } = useMutation({
|