@agent-native/core 0.146.3 → 0.146.4
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 +1 -1
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +13 -10
- package/corpus/templates/analytics/.agents/skills/self-modifying-code/SKILL.md +7 -3
- package/corpus/templates/analytics/.agents/skills/upgrade-agent-native/SKILL.md +5 -2
- package/corpus/templates/analytics/actions/compose-dashboard.ts +53 -6
- package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +8 -2
- package/corpus/templates/analytics/actions/migrate-first-party-analytics-to-bigquery.ts +3 -1
- package/corpus/templates/analytics/actions/mutate-dashboard.ts +48 -9
- package/corpus/templates/analytics/actions/update-dashboard.ts +80 -6
- package/corpus/templates/analytics/changelog/2026-08-07-dashboard-creation-and-edits-now-save-faster-with-reliable-s.md +6 -0
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +19 -19
- package/corpus/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +21 -2
- package/corpus/templates/analytics/server/lib/bigquery.ts +59 -24
- package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +44 -8
- package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +3 -5
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +33 -69
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +90 -24
- package/corpus/templates/analytics/server/lib/real-data-actions.ts +1 -1
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +17 -2
- package/corpus/templates/analytics/shared/dashboard-report-timeouts.ts +2 -0
- package/corpus/templates/assets/.agents/skills/self-modifying-code/SKILL.md +7 -3
- package/corpus/templates/assets/.agents/skills/upgrade-agent-native/SKILL.md +5 -2
- package/corpus/templates/assets/netlify.toml +1 -1
- package/corpus/templates/assets/package.json +1 -0
- package/corpus/templates/assets/scripts/migrate-production.ts +24 -0
- package/corpus/templates/assets/server/plugins/db.ts +1 -1
- package/corpus/templates/assets/tsconfig.json +1 -0
- package/corpus/templates/brain/.agents/skills/self-modifying-code/SKILL.md +7 -3
- package/corpus/templates/brain/.agents/skills/upgrade-agent-native/SKILL.md +5 -2
- package/corpus/templates/brain/netlify.toml +1 -1
- package/corpus/templates/brain/package.json +2 -0
- package/corpus/templates/brain/scripts/migrate-production.ts +24 -0
- package/corpus/templates/brain/server/plugins/db.ts +1 -1
- package/corpus/templates/brain/tsconfig.json +1 -0
- package/corpus/templates/calendar/.agents/skills/self-modifying-code/SKILL.md +7 -3
- package/corpus/templates/calendar/.agents/skills/upgrade-agent-native/SKILL.md +5 -2
- package/corpus/templates/calendar/netlify.toml +1 -1
- package/corpus/templates/calendar/package.json +1 -0
- package/corpus/templates/calendar/scripts/migrate-production.ts +24 -0
- package/corpus/templates/calendar/server/plugins/db.ts +1 -1
- package/corpus/templates/calendar/tsconfig.json +1 -0
- package/corpus/templates/chat/.agents/skills/self-modifying-code/SKILL.md +7 -3
- package/corpus/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +5 -2
- package/corpus/templates/chat/netlify.toml +1 -1
- package/corpus/templates/chat/package.json +1 -0
- package/corpus/templates/chat/scripts/migrate-production.ts +31 -0
- package/corpus/templates/chat/tsconfig.json +1 -0
- package/corpus/templates/clips/.agents/skills/self-modifying-code/SKILL.md +7 -3
- package/corpus/templates/clips/.agents/skills/upgrade-agent-native/SKILL.md +5 -2
- package/corpus/templates/clips/netlify.toml +1 -1
- package/corpus/templates/clips/package.json +1 -0
- package/corpus/templates/clips/scripts/migrate-production.ts +24 -0
- package/corpus/templates/clips/server/plugins/db.ts +1 -1
- package/corpus/templates/clips/tsconfig.json +1 -0
- package/corpus/templates/content/.agents/skills/self-modifying-code/SKILL.md +7 -3
- package/corpus/templates/content/.agents/skills/upgrade-agent-native/SKILL.md +5 -2
- package/corpus/templates/content/netlify.toml +1 -1
- package/corpus/templates/content/package.json +2 -0
- package/corpus/templates/content/scripts/migrate-production.ts +28 -0
- package/corpus/templates/content/server/plugins/db.ts +2 -2
- package/corpus/templates/crm/.agents/skills/upgrade-agent-native/SKILL.md +5 -2
- package/corpus/templates/crm/netlify.toml +1 -1
- package/corpus/templates/crm/package.json +2 -0
- package/corpus/templates/crm/scripts/migrate-production.ts +24 -0
- package/corpus/templates/crm/server/plugins/db.ts +1 -1
- package/corpus/templates/crm/tsconfig.json +1 -0
- package/corpus/templates/design/.agents/skills/self-modifying-code/SKILL.md +7 -3
- package/corpus/templates/design/.agents/skills/upgrade-agent-native/SKILL.md +5 -2
- package/corpus/templates/design/netlify.toml +1 -1
- package/corpus/templates/design/package.json +1 -0
- package/corpus/templates/design/scripts/migrate-production.ts +24 -0
- package/corpus/templates/design/server/plugins/db.ts +1 -1
- package/corpus/templates/design/tsconfig.json +1 -0
- package/corpus/templates/dispatch/.agents/skills/self-modifying-code/SKILL.md +7 -3
- package/corpus/templates/dispatch/.agents/skills/upgrade-agent-native/SKILL.md +5 -2
- package/corpus/templates/dispatch/netlify.toml +1 -1
- package/corpus/templates/dispatch/package.json +1 -0
- package/corpus/templates/dispatch/scripts/migrate-production.ts +23 -0
- package/corpus/templates/dispatch/tsconfig.json +1 -0
- package/corpus/templates/factory/.agents/skills/self-modifying-code/SKILL.md +7 -3
- package/corpus/templates/factory/.agents/skills/upgrade-agent-native/SKILL.md +5 -2
- package/corpus/templates/factory/netlify.toml +1 -1
- package/corpus/templates/factory/package.json +1 -0
- package/corpus/templates/factory/scripts/migrate-production.ts +31 -0
- package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +2 -0
- package/corpus/templates/factory/tsconfig.json +1 -0
- package/corpus/templates/forms/.agents/skills/self-modifying-code/SKILL.md +7 -3
- package/corpus/templates/forms/.agents/skills/upgrade-agent-native/SKILL.md +5 -2
- package/corpus/templates/forms/netlify.toml +1 -1
- package/corpus/templates/forms/package.json +2 -0
- package/corpus/templates/forms/scripts/migrate-production.ts +24 -0
- package/corpus/templates/forms/server/plugins/db.ts +1 -1
- package/corpus/templates/forms/tsconfig.json +1 -0
- package/corpus/templates/macros/.agents/skills/upgrade-agent-native/SKILL.md +5 -2
- package/corpus/templates/macros/netlify.toml +1 -1
- package/corpus/templates/macros/package.json +1 -0
- package/corpus/templates/macros/scripts/migrate-production.ts +24 -0
- package/corpus/templates/macros/server/plugins/db.ts +1 -1
- package/corpus/templates/macros/tsconfig.json +1 -0
- package/corpus/templates/mail/.agents/skills/self-modifying-code/SKILL.md +7 -3
- package/corpus/templates/mail/.agents/skills/upgrade-agent-native/SKILL.md +5 -2
- package/corpus/templates/mail/netlify.toml +1 -1
- package/corpus/templates/mail/package.json +1 -0
- package/corpus/templates/mail/scripts/migrate-production.ts +24 -0
- package/corpus/templates/mail/server/plugins/db.ts +1 -1
- package/corpus/templates/mail/tsconfig.json +1 -0
- package/corpus/templates/plan/.agents/skills/self-modifying-code/SKILL.md +7 -3
- package/corpus/templates/plan/.agents/skills/upgrade-agent-native/SKILL.md +5 -2
- package/corpus/templates/plan/netlify.toml +1 -1
- package/corpus/templates/plan/package.json +1 -0
- package/corpus/templates/plan/scripts/migrate-production.ts +24 -0
- package/corpus/templates/plan/server/plugins/00-mcp.ts +3 -2
- package/corpus/templates/plan/server/plugins/db.ts +1 -1
- package/corpus/templates/plan/tsconfig.json +1 -0
- package/corpus/templates/slides/.agents/skills/self-modifying-code/SKILL.md +7 -3
- package/corpus/templates/slides/.agents/skills/upgrade-agent-native/SKILL.md +5 -2
- package/corpus/templates/slides/netlify.toml +1 -1
- package/corpus/templates/slides/package.json +1 -0
- package/corpus/templates/slides/scripts/migrate-production.ts +24 -0
- package/corpus/templates/slides/server/plugins/db.ts +1 -1
- package/corpus/templates/slides/tsconfig.json +1 -0
- package/corpus/templates/tasks/.agents/skills/self-modifying-code/SKILL.md +7 -3
- package/corpus/templates/tasks/.agents/skills/upgrade-agent-native/SKILL.md +5 -2
- package/corpus/templates/tasks/netlify.toml +1 -1
- package/corpus/templates/tasks/package.json +1 -0
- package/corpus/templates/tasks/scripts/migrate-production.ts +24 -0
- package/corpus/templates/tasks/server/plugins/db.ts +1 -1
- package/corpus/templates/tasks/tsconfig.json +1 -0
- package/dist/action.d.ts +2 -0
- package/dist/agent/production-agent.d.ts +1 -0
- package/dist/agent/production-agent.js +2 -0
- package/dist/agent/thread-data-builder.d.ts +4 -0
- package/dist/agent/thread-data-builder.js +44 -0
- package/dist/automations/service.d.ts +1 -0
- package/dist/automations/service.js +21 -2
- package/dist/cli/create.js +20 -2
- package/dist/client/agent-page/AgentJobsTab.js +3 -1
- package/dist/client/agent-page/AutomationDetailsDialog.js +9 -2
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/db/migrations.js +7 -13
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/jobs/actions/list-automation-runs.d.ts +1 -0
- package/dist/jobs/actions/list-automation-runs.js +9 -2
- package/dist/jobs/actions/list-recurring-jobs.js +3 -1
- package/dist/jobs/actions/manage-recurring-job.js +1 -1
- package/dist/jobs/actions/run-automation-now.js +1 -0
- package/dist/jobs/background-automation-runner.js +2 -0
- package/dist/jobs/frontmatter.d.ts +2 -0
- package/dist/jobs/frontmatter.js +4 -0
- package/dist/jobs/run-history.d.ts +4 -0
- package/dist/jobs/run-history.js +28 -8
- package/dist/jobs/run-now.d.ts +4 -1
- package/dist/jobs/run-now.js +4 -3
- package/dist/jobs/scheduler.js +18 -0
- package/dist/jobs/tools.d.ts +3 -2
- package/dist/jobs/tools.js +26 -12
- package/dist/localization/default-messages.d.ts +1 -0
- package/dist/localization/default-messages.js +1 -0
- 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/resources/handlers.d.ts +1 -1
- package/dist/server/action-routes.d.ts +2 -0
- package/dist/server/action-routes.js +1 -0
- package/dist/server/agent-chat-plugin.js +3 -2
- package/dist/server/builder-browser.js +24 -8
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/chat/.agents/skills/self-modifying-code/SKILL.md +7 -3
- package/dist/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +5 -2
- package/dist/templates/chat/netlify.toml +1 -1
- package/dist/templates/chat/package.json +1 -0
- package/dist/templates/chat/scripts/migrate-production.ts +31 -0
- package/dist/templates/chat/tsconfig.json +1 -0
- package/dist/templates/default/.agents/skills/self-modifying-code/SKILL.md +7 -3
- package/dist/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +5 -2
- package/dist/templates/default/package.json +1 -0
- package/dist/templates/default/scripts/migrate-production.ts +31 -0
- package/dist/templates/default/tsconfig.json +1 -0
- package/dist/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +5 -2
- package/dist/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +7 -3
- package/dist/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +5 -2
- package/dist/templates/workspace-root/AGENTS.md +2 -1
- package/dist/triggers/actions/list-automations.js +1 -1
- package/dist/triggers/actions/manage-automation.js +5 -1
- package/dist/triggers/actions.d.ts +1 -1
- package/dist/triggers/actions.js +21 -15
- package/dist/triggers/dispatcher.js +17 -2
- package/dist/triggers/routes.d.ts +1 -0
- package/dist/triggers/routes.js +1 -0
- package/package.json +1 -1
- package/src/templates/chat/.agents/skills/self-modifying-code/SKILL.md +7 -3
- package/src/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +5 -2
- package/src/templates/chat/netlify.toml +1 -1
- package/src/templates/chat/package.json +1 -0
- package/src/templates/chat/scripts/migrate-production.ts +31 -0
- package/src/templates/chat/tsconfig.json +1 -0
- package/src/templates/default/.agents/skills/self-modifying-code/SKILL.md +7 -3
- package/src/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +5 -2
- package/src/templates/default/package.json +1 -0
- package/src/templates/default/scripts/migrate-production.ts +31 -0
- package/src/templates/default/tsconfig.json +1 -0
- package/src/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +5 -2
- package/src/templates/workspace-core/.agents/skills/self-modifying-code/SKILL.md +7 -3
- package/src/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +5 -2
- package/src/templates/workspace-root/AGENTS.md +2 -1
package/corpus/README.md
CHANGED
|
@@ -464,9 +464,10 @@ Filters auto-apply on change — there is no Apply button. Each filter change wr
|
|
|
464
464
|
## Modifying A Dashboard
|
|
465
465
|
|
|
466
466
|
For existing dashboard edits, default to `mutate-dashboard`. It gives the
|
|
467
|
-
agent a
|
|
468
|
-
|
|
469
|
-
|
|
467
|
+
agent a typed mutation API without exposing arbitrary JavaScript execution.
|
|
468
|
+
Pass structured `operations` for normal agent calls; keep the legacy `code`
|
|
469
|
+
form for short layout/config edits only. Large SQL payloads belong in the
|
|
470
|
+
server-side first-party metric catalog, not in a prompt argument.
|
|
470
471
|
The server parses only documented `dashboard.*` method calls, applies the
|
|
471
472
|
resulting operations in memory, validates the final dashboard config, writes
|
|
472
473
|
SQL once, syncs collab, and returns compact proof.
|
|
@@ -670,8 +671,9 @@ After a mutation, navigate to the dashboard if the user is elsewhere. The app sy
|
|
|
670
671
|
|
|
671
672
|
### Reordering Panels
|
|
672
673
|
|
|
673
|
-
For simple "move this chart/section" requests, prefer `mutate-dashboard` with
|
|
674
|
-
|
|
674
|
+
For simple "move this chart/section" requests, prefer `mutate-dashboard` with
|
|
675
|
+
structured operations. The short script form is also supported for one compact
|
|
676
|
+
move:
|
|
675
677
|
|
|
676
678
|
```json
|
|
677
679
|
{
|
|
@@ -694,9 +696,9 @@ When the user asks to change existing panels:
|
|
|
694
696
|
|
|
695
697
|
1. Read the current dashboard with `get-sql-dashboard` compact mode unless full
|
|
696
698
|
SQL/config is required.
|
|
697
|
-
2. Call `mutate-dashboard` once with every change in
|
|
698
|
-
|
|
699
|
-
|
|
699
|
+
2. Call `mutate-dashboard` once with every change in `operations`. Use panel
|
|
700
|
+
ids, not shifted array indexes. The short `code` form is only for compact
|
|
701
|
+
layout/config edits; do not stream a large multi-panel SQL script.
|
|
700
702
|
3. Verify the returned `panelCount`, `appliedOps`, `firstPanelIds`, and
|
|
701
703
|
`summary`. If possible, read the affected panels back and confirm the exact
|
|
702
704
|
fields changed.
|
|
@@ -742,8 +744,9 @@ For a **first-party analytics** dashboard, prefer `compose-dashboard` over hand-
|
|
|
742
744
|
- **Never hand-author large first-party configs panel-by-panel.** Call `compose-dashboard` with the metric keys instead.
|
|
743
745
|
- Unknown metric keys are skipped and reported in `unknownMetrics` (not fatal). Each panel's SQL is validated independently — valid panels save, invalid ones are reported in `invalidMetrics`.
|
|
744
746
|
- By default (no `overwrite`), composing into an existing dashboard APPENDS the new panels and skips ids already present. `overwrite: true` replaces the whole config.
|
|
747
|
+
- Set `refreshExisting: true` when refreshing a catalog-backed dashboard. Matching metric panels are replaced in place, unrelated panels and layout order are preserved, and the server returns `refreshedExistingIds`.
|
|
745
748
|
- Each metric accepts an optional per-metric `window` of `'30d' | '90d' | 'all'` (only affects windowed virality/time metrics) and `title` / `chartType` / `width` overrides. Request `'all'` only when the user asks for all-time coverage, and describe it as full available history.
|
|
746
|
-
- Returns `{ dashboardId, panelCount, createdMetrics, unknownMetrics, invalidMetrics, skippedExistingIds }` — report `panelCount
|
|
749
|
+
- Returns `{ saved, dashboardId, panelCount, createdMetrics, refreshedExistingIds, unknownMetrics, invalidMetrics, skippedExistingIds }` — report `saved: true`, `panelCount`, and any invalid/unknown metrics as proof-of-done.
|
|
747
750
|
|
|
748
751
|
Available metric keys: `total-signups`, `signups-over-time`, `signups-by-template`, `sessions-by-app`, `sessions-over-time`, `replay-sessions`, `replay-chunks-over-time`, `recent-replay-sessions`, `signed-in-vs-anon`, `total-template-clicks`, `total-demo-clicks`, `total-cli-copies`, `template-interest-over-time`, `clicks-by-template`, `demo-clicks-by-template`, `cli-copies-by-template`, `cli-copies-over-time`, `pageviews-over-time`, `top-referrer-domains`, `referred-signups-30d`, `viral-signup-share-30d`, `clip-share-signups-30d`, `signups-by-referral-source`, `referred-signups-over-time`, `top-referrers`, `share-funnel-30d`, `viral-participation-rate-90d`, `viral-coefficient-90d`, `activated-referrers-90d`.
|
|
749
752
|
|
|
@@ -760,7 +763,7 @@ This is the dashboard-specific application of the framework-wide `reliable-mutat
|
|
|
760
763
|
Hosted agent runs have a **~40s budget**. Many sequential `update-dashboard` calls (one per panel, plus schema-discovery calls) will blow that budget and leave the dashboard in a partial state — earlier inserts looked like they succeeded (✓), but nothing actually persisted. Avoid this:
|
|
761
764
|
|
|
762
765
|
- **For a large first-party dashboard, use `compose-dashboard`** (see the section above): name the metrics, the server generates the panels in one call. Do not hand-author the big config.
|
|
763
|
-
- **Batch ALL edits into ONE `mutate-dashboard` call.**
|
|
766
|
+
- **Batch ALL edits into ONE `mutate-dashboard` call.** Structured operations can move,
|
|
764
767
|
insert, remove, duplicate, and update many panels. Never loop dashboard edit
|
|
765
768
|
actions panel-by-panel.
|
|
766
769
|
- To bulk edit existing panels, use selectors:
|
|
@@ -36,8 +36,10 @@ Tier 4 includes **all** of the following — not only editing package source:
|
|
|
36
36
|
- Files under `node_modules/@agent-native/*` (core, dispatch, scheduling, …)
|
|
37
37
|
- `pnpm.overrides`, `overrides`, `resolutions`, or `patchedDependencies` that
|
|
38
38
|
target any `@agent-native/*` package
|
|
39
|
-
-
|
|
40
|
-
|
|
39
|
+
- `pnpm patch`, `pnpm patch-commit`, or local package patch artifacts for any
|
|
40
|
+
dependency
|
|
41
|
+
- Invented "dispatch/core behavior" shims meant to paper over a version skew
|
|
42
|
+
or failed upgrade
|
|
41
43
|
|
|
42
44
|
This does not prohibit intentional app-owned UI customization. When public
|
|
43
45
|
props and composition are insufficient, the `customizing-agent-native` skill
|
|
@@ -100,8 +102,10 @@ inline strings in components.
|
|
|
100
102
|
- Don't confuse readable package source with app-owned code: use
|
|
101
103
|
`customizing-agent-native` and `agent-native eject` for supported ownership
|
|
102
104
|
transfer
|
|
105
|
+
- Don't use `pnpm patch` / `pnpm patch-commit`, commit `patches/` artifacts, or
|
|
106
|
+
add `pnpm.patchedDependencies` to "make the app run" after a version bump
|
|
103
107
|
- Don't add `pnpm.overrides` / `patchedDependencies` / `resolutions` for
|
|
104
|
-
`@agent-native/*` to
|
|
108
|
+
`@agent-native/*` to paper over a broken upgrade
|
|
105
109
|
- Don't invent local dispatch/core behavior overrides when upgrade fails —
|
|
106
110
|
run `npx @agent-native/core@latest upgrade`, then fix app-level errors only
|
|
107
111
|
- Don't modify `.agents/skills/` or `AGENTS.md` unless explicitly requested
|
|
@@ -15,8 +15,9 @@ metadata:
|
|
|
15
15
|
|
|
16
16
|
When an older Agent Native app/branch needs to run on current packages, use
|
|
17
17
|
`agent-native upgrade`. Never "fix" upgrade breakage with
|
|
18
|
-
`pnpm
|
|
19
|
-
|
|
18
|
+
`pnpm patch`, `pnpm patch-commit`, `pnpm.patchedDependencies`,
|
|
19
|
+
`pnpm.overrides`, `patchedDependencies`, `resolutions`, local patch artifacts,
|
|
20
|
+
or edits under `node_modules/@agent-native/*` — especially not against
|
|
20
21
|
`@agent-native/core` or `@agent-native/dispatch`.
|
|
21
22
|
|
|
22
23
|
## Why
|
|
@@ -109,6 +110,8 @@ install → refresh scaffold skills → verify, then fix **app** code only.
|
|
|
109
110
|
|
|
110
111
|
- Don't add `pnpm.overrides`, `overrides`, `resolutions`, or
|
|
111
112
|
`patchedDependencies` for any `@agent-native/*` package
|
|
113
|
+
- Don't run `pnpm patch` or `pnpm patch-commit`, or commit files under
|
|
114
|
+
`patches/`, for any dependency
|
|
112
115
|
- Don't edit `node_modules/@agent-native/core` or
|
|
113
116
|
`node_modules/@agent-native/dispatch`
|
|
114
117
|
- Don't invent local "dispatch behavior" shims to paper over version skew
|
|
@@ -111,8 +111,8 @@ export default defineAction({
|
|
|
111
111
|
"Build a large first-party analytics dashboard in ONE fast call: name the metrics you want and the SERVER generates the validated SQL + chart config for every panel. " +
|
|
112
112
|
"Do NOT hand-author large `update-dashboard` configs panel-by-panel — producing/streaming a big multi-panel config inside the ~40s run budget fails and thrashes. " +
|
|
113
113
|
"Each metric expands into a complete first-party panel from the shipped, already-validated metric catalog. Unknown metric keys are skipped and reported (not fatal); each panel's SQL is validated independently (valid panels are saved, invalid ones reported), and the dashboard is assembled and saved in a single atomic store write. " +
|
|
114
|
-
"If the dashboard already exists and `overwrite` is false (default), the new panels are APPENDED (panels whose id is already present are skipped)
|
|
115
|
-
"Returns { dashboardId, panelCount, createdMetrics, unknownMetrics, invalidMetrics, urlPath, deepLink, message } — use panelCount as proof-of-done. " +
|
|
114
|
+
"If the dashboard already exists and `overwrite` is false (default), the new panels are APPENDED (panels whose id is already present are skipped). Set `refreshExisting: true` to atomically replace matching catalog panels in place while preserving unrelated panels. With `overwrite: true` the config is replaced. " +
|
|
115
|
+
"Returns { dashboardId, panelCount, createdMetrics, refreshedExistingIds, unknownMetrics, invalidMetrics, urlPath, deepLink, message } — use panelCount as proof-of-done. " +
|
|
116
116
|
`Available metric keys: ${METRIC_KEYS.join(", ")}. ` +
|
|
117
117
|
"Each metric accepts an optional per-metric `window` of '30d' | '90d' | 'all' (only affects windowed virality/time metrics). Catalog panels are time-scoped by construction; custom first-party SQL added later must use `{{timeRange}}` or declare `config.timeScope`.",
|
|
118
118
|
schema: z.object({
|
|
@@ -142,6 +142,12 @@ export default defineAction({
|
|
|
142
142
|
.describe(
|
|
143
143
|
"If true, replace the whole dashboard config. If false (default) and the dashboard exists, APPEND the new panels (skipping ids already present).",
|
|
144
144
|
),
|
|
145
|
+
refreshExisting: z
|
|
146
|
+
.boolean()
|
|
147
|
+
.optional()
|
|
148
|
+
.describe(
|
|
149
|
+
"When appending to an existing dashboard, replace panels whose ids match the requested catalog metrics in place, preserving unrelated panels and layout order. Use this to refresh a catalog-backed dashboard without sending SQL through the prompt.",
|
|
150
|
+
),
|
|
145
151
|
}),
|
|
146
152
|
mcpApp: {
|
|
147
153
|
compactCatalog: true,
|
|
@@ -238,6 +244,7 @@ export default defineAction({
|
|
|
238
244
|
let finalConfig: Record<string, unknown>;
|
|
239
245
|
let appendedCount = composedPanels.length;
|
|
240
246
|
let skippedExistingIds: string[] = [];
|
|
247
|
+
let refreshedExistingIds: string[] = [];
|
|
241
248
|
|
|
242
249
|
if (existing && !args.overwrite) {
|
|
243
250
|
// Append: preserve existing panels + order, add only new panel ids.
|
|
@@ -260,18 +267,36 @@ export default defineAction({
|
|
|
260
267
|
.map((p) => (typeof p?.id === "string" ? p.id : null))
|
|
261
268
|
.filter((id): id is string => !!id),
|
|
262
269
|
);
|
|
270
|
+
const composedById = new Map(
|
|
271
|
+
composedPanels.map((panel) => [panel.id, panel]),
|
|
272
|
+
);
|
|
273
|
+
const refreshed = new Set<string>();
|
|
274
|
+
const mergedExistingPanels = existingPanels.map((panel) => {
|
|
275
|
+
const id = typeof panel?.id === "string" ? panel.id : "";
|
|
276
|
+
const replacement = args.refreshExisting
|
|
277
|
+
? composedById.get(id)
|
|
278
|
+
: undefined;
|
|
279
|
+
if (!replacement) return panel;
|
|
280
|
+
refreshed.add(id);
|
|
281
|
+
return replacement;
|
|
282
|
+
});
|
|
263
283
|
const toAppend: ComposedPanel[] = [];
|
|
264
284
|
const skipped: string[] = [];
|
|
285
|
+
const appendedIds = new Set<string>();
|
|
265
286
|
for (const panel of composedPanels) {
|
|
287
|
+
if (refreshed.has(panel.id)) continue;
|
|
266
288
|
if (existingIds.has(panel.id)) {
|
|
267
289
|
skipped.push(panel.id);
|
|
268
290
|
continue;
|
|
269
291
|
}
|
|
292
|
+
if (appendedIds.has(panel.id)) continue;
|
|
270
293
|
toAppend.push(panel);
|
|
294
|
+
appendedIds.add(panel.id);
|
|
271
295
|
existingIds.add(panel.id);
|
|
272
296
|
}
|
|
273
297
|
appendedCount = toAppend.length;
|
|
274
|
-
skippedExistingIds = skipped;
|
|
298
|
+
skippedExistingIds = Array.from(new Set(skipped));
|
|
299
|
+
refreshedExistingIds = Array.from(refreshed);
|
|
275
300
|
const merged = withFilters({
|
|
276
301
|
...existingConfig,
|
|
277
302
|
name:
|
|
@@ -279,7 +304,7 @@ export default defineAction({
|
|
|
279
304
|
existingConfig.name.trim()
|
|
280
305
|
? existingConfig.name
|
|
281
306
|
: dashboardName,
|
|
282
|
-
panels: [...
|
|
307
|
+
panels: [...mergedExistingPanels, ...toAppend],
|
|
283
308
|
});
|
|
284
309
|
return { kind: "sql" as const, body: merged };
|
|
285
310
|
},
|
|
@@ -304,11 +329,30 @@ export default defineAction({
|
|
|
304
329
|
? (finalConfig.panels as unknown[]).length
|
|
305
330
|
: 0;
|
|
306
331
|
|
|
307
|
-
|
|
332
|
+
const changed =
|
|
333
|
+
!existing ||
|
|
334
|
+
args.overwrite === true ||
|
|
335
|
+
appendedCount > 0 ||
|
|
336
|
+
refreshedExistingIds.length > 0;
|
|
337
|
+
|
|
338
|
+
if (changed) {
|
|
339
|
+
queueDashboardCollabSync(args.dashboardId, finalConfig, "agent");
|
|
340
|
+
}
|
|
308
341
|
|
|
309
342
|
const parts: string[] = [];
|
|
310
343
|
if (existing && !args.overwrite) {
|
|
311
|
-
|
|
344
|
+
if (!changed) {
|
|
345
|
+
parts.push(`No changes were needed for "${args.dashboardId}"`);
|
|
346
|
+
} else if (refreshedExistingIds.length > 0) {
|
|
347
|
+
parts.push(
|
|
348
|
+
`Refreshed ${refreshedExistingIds.length} existing panel(s)`,
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
if (changed) {
|
|
352
|
+
parts.push(
|
|
353
|
+
`Appended ${appendedCount} panel(s) to "${args.dashboardId}"`,
|
|
354
|
+
);
|
|
355
|
+
}
|
|
312
356
|
if (skippedExistingIds.length > 0) {
|
|
313
357
|
parts.push(`${skippedExistingIds.length} already present`);
|
|
314
358
|
}
|
|
@@ -330,11 +374,14 @@ export default defineAction({
|
|
|
330
374
|
parts.push(`Dashboard now has ${panelCount} panel(s).`);
|
|
331
375
|
|
|
332
376
|
return {
|
|
377
|
+
saved: true,
|
|
378
|
+
changed,
|
|
333
379
|
id: args.dashboardId,
|
|
334
380
|
dashboardId: args.dashboardId,
|
|
335
381
|
name: dashboardName,
|
|
336
382
|
panelCount,
|
|
337
383
|
createdMetrics,
|
|
384
|
+
refreshedExistingIds,
|
|
338
385
|
unknownMetrics,
|
|
339
386
|
invalidMetrics,
|
|
340
387
|
skippedExistingIds,
|
|
@@ -136,6 +136,10 @@ export const DASHBOARD_MUTATION_EXAMPLES = [
|
|
|
136
136
|
'dashboard.insertPanel({"id":"personal-widget-slot","title":"Personal Widget Slot","chartType":"extension","width":3,"config":{"extensionSlotId":"analytics.dashboard.<dashboard-id>.panel.personal-widget-slot"}}).atBottom();',
|
|
137
137
|
] as const;
|
|
138
138
|
|
|
139
|
+
/** Keep the legacy script surface bounded before it reaches the parser. */
|
|
140
|
+
export const MAX_DASHBOARD_MUTATION_CODE_LENGTH = 12_000;
|
|
141
|
+
export const MAX_DASHBOARD_MUTATION_OPERATIONS = 100;
|
|
142
|
+
|
|
139
143
|
export type DashboardMutationOperation =
|
|
140
144
|
| {
|
|
141
145
|
op: "movePanels";
|
|
@@ -1494,8 +1498,10 @@ export function parseDashboardMutationScript(
|
|
|
1494
1498
|
config: Record<string, unknown>,
|
|
1495
1499
|
code: string,
|
|
1496
1500
|
): DashboardMutationOperation[] {
|
|
1497
|
-
if (code.length >
|
|
1498
|
-
throw new Error(
|
|
1501
|
+
if (code.length > MAX_DASHBOARD_MUTATION_CODE_LENGTH) {
|
|
1502
|
+
throw new Error(
|
|
1503
|
+
`mutation script is too large; keep it under ${MAX_DASHBOARD_MUTATION_CODE_LENGTH} chars or use structured operations/compose-dashboard`,
|
|
1504
|
+
);
|
|
1499
1505
|
}
|
|
1500
1506
|
if (hasTemplateLiteralSyntax(code)) {
|
|
1501
1507
|
throw new Error("mutation script does not support template literals");
|
|
@@ -44,7 +44,9 @@ const migrationSchema = z.object({
|
|
|
44
44
|
.min(1)
|
|
45
45
|
.max(750)
|
|
46
46
|
.optional()
|
|
47
|
-
.describe(
|
|
47
|
+
.describe(
|
|
48
|
+
"Optional per-page limit used when mode is prepare; for an existing job, it can only increase the bounded batch size.",
|
|
49
|
+
),
|
|
48
50
|
confirm: z.boolean().optional(),
|
|
49
51
|
});
|
|
50
52
|
|
|
@@ -19,6 +19,8 @@ import {
|
|
|
19
19
|
applyDashboardMutationOperations,
|
|
20
20
|
DASHBOARD_MUTATION_API_TYPES,
|
|
21
21
|
DASHBOARD_MUTATION_EXAMPLES,
|
|
22
|
+
MAX_DASHBOARD_MUTATION_CODE_LENGTH,
|
|
23
|
+
MAX_DASHBOARD_MUTATION_OPERATIONS,
|
|
22
24
|
parseDashboardMutationScript,
|
|
23
25
|
type DashboardMutationOperation,
|
|
24
26
|
type DashboardMutationResult,
|
|
@@ -104,6 +106,11 @@ function parseJsonArrayString(
|
|
|
104
106
|
if (!Array.isArray(parsed)) {
|
|
105
107
|
throw new Error(`${fieldName} must be a JSON array`);
|
|
106
108
|
}
|
|
109
|
+
if (parsed.length > MAX_DASHBOARD_MUTATION_OPERATIONS) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`${fieldName} has ${parsed.length} operations; keep it at or below ${MAX_DASHBOARD_MUTATION_OPERATIONS}`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
107
114
|
return parsed.map((op, index) => {
|
|
108
115
|
try {
|
|
109
116
|
return mutationOperationSchema.parse(op) as DashboardMutationOperation;
|
|
@@ -115,7 +122,7 @@ function parseJsonArrayString(
|
|
|
115
122
|
|
|
116
123
|
const operationsInputSchema = z
|
|
117
124
|
.union([
|
|
118
|
-
z.array(mutationOperationSchema),
|
|
125
|
+
z.array(mutationOperationSchema).max(MAX_DASHBOARD_MUTATION_OPERATIONS),
|
|
119
126
|
z.string().transform((value) => {
|
|
120
127
|
const trimmed = value.trim();
|
|
121
128
|
return trimmed ? parseJsonArrayString(trimmed, "operations") : undefined;
|
|
@@ -134,7 +141,7 @@ function nonEmptyOperations(
|
|
|
134
141
|
}
|
|
135
142
|
|
|
136
143
|
const apiHelp =
|
|
137
|
-
"
|
|
144
|
+
"Short, constrained TypeScript-like dashboard mutation script for compatibility. Prefer structured `operations` for agent calls; use this only for small layout/config edits. The server parses only calls on `dashboard`; it does not execute arbitrary JavaScript. " +
|
|
138
145
|
"No variables, imports, loops, functions, templates, network, filesystem, or DB access. Arguments must be JSON-compatible literals, so quote object keys. " +
|
|
139
146
|
"Subjects: dashboard.set, dashboard.setFilterDefault, dashboard.panel, dashboard.panels, dashboard.panelsMatching, dashboard.section, dashboard.insertPanel. " +
|
|
140
147
|
'For a simple default-filter change, use `dashboard.setFilterDefault("emailFilter","exclude_builder");`; it verifies the filter and option value without resending every filter or revalidating unchanged panel SQL. ' +
|
|
@@ -148,7 +155,26 @@ const agentInputSchema = z.object({
|
|
|
148
155
|
.string()
|
|
149
156
|
.min(1)
|
|
150
157
|
.describe("Dashboard id, e.g. 'agent-native-templates-first-party'."),
|
|
151
|
-
|
|
158
|
+
operations: z
|
|
159
|
+
.array(mutationOperationSchema)
|
|
160
|
+
.max(MAX_DASHBOARD_MUTATION_OPERATIONS)
|
|
161
|
+
.optional()
|
|
162
|
+
.describe(
|
|
163
|
+
"Preferred agent input: structured dashboard edits applied atomically in one save. Use panel ids, not array indexes. For first-party metric refreshes, use compose-dashboard with metric keys instead of embedding SQL here.",
|
|
164
|
+
),
|
|
165
|
+
code: z
|
|
166
|
+
.string()
|
|
167
|
+
.max(MAX_DASHBOARD_MUTATION_CODE_LENGTH)
|
|
168
|
+
.optional()
|
|
169
|
+
.describe(apiHelp),
|
|
170
|
+
dryRun: z
|
|
171
|
+
.boolean()
|
|
172
|
+
.optional()
|
|
173
|
+
.describe("Validate the mutation without saving it."),
|
|
174
|
+
returnConfig: z
|
|
175
|
+
.boolean()
|
|
176
|
+
.optional()
|
|
177
|
+
.describe("Include the full resulting config only when it is needed."),
|
|
152
178
|
});
|
|
153
179
|
|
|
154
180
|
function resolveScope() {
|
|
@@ -212,10 +238,13 @@ function sqlValidationScope(
|
|
|
212
238
|
async function validateMutationSql(
|
|
213
239
|
config: Record<string, unknown>,
|
|
214
240
|
operations: DashboardMutationOperation[],
|
|
241
|
+
signal?: AbortSignal,
|
|
215
242
|
): Promise<string | null> {
|
|
216
243
|
const scope = sqlValidationScope(operations);
|
|
217
244
|
if (scope === null) return null;
|
|
218
|
-
return validatePanelSql(config, scope === "all" ? undefined : scope
|
|
245
|
+
return validatePanelSql(config, scope === "all" ? undefined : scope, {
|
|
246
|
+
signal,
|
|
247
|
+
});
|
|
219
248
|
}
|
|
220
249
|
|
|
221
250
|
function movedPanelIdsFrom(operations: DashboardMutationOperation[]): string[] {
|
|
@@ -239,11 +268,11 @@ function helpResult() {
|
|
|
239
268
|
|
|
240
269
|
export default defineAction({
|
|
241
270
|
description:
|
|
242
|
-
"Apply general SQL dashboard edits through a small typed mutation API in ONE atomic save. " +
|
|
271
|
+
"Apply general SQL dashboard edits through a small typed mutation API in ONE atomic save. Prefer structured `operations` for agent calls; use the short `code` form only for compact layout/config edits. " +
|
|
243
272
|
"Prefer this for dashboard layout and panel edits: move panels by id, edit titles/SQL/width/config, remove panels, duplicate panels, insert panels, or patch dashboard fields. " +
|
|
244
273
|
"For user placement requests like 'second row' or 'next to return rates', use row-aware placement such as `dashboard.insertPanel(...).nextTo(\"retention-over-time\")` or `.atRow(2)`, then verify rendered rows from `get-sql-dashboard.layout.groups`. " +
|
|
245
274
|
"This is code-shaped but not arbitrary code execution: the server parses the allowed dashboard methods, validates the resulting config with the same invariants as update-dashboard, saves once, syncs collab, and returns compact proof. First-party SQL must be explicitly time-bound as described in the API help; server validation rejects unbound first-party SQL. " +
|
|
246
|
-
"
|
|
275
|
+
"Structured operations avoid brittle JSON-pointer indexes and native-array serialization issues. Do not put a large multi-panel SQL payload in `code`; use `compose-dashboard` for catalog metrics or structured operations for a bounded custom edit. " +
|
|
247
276
|
`Common example: ${DASHBOARD_MUTATION_EXAMPLES[0]}`,
|
|
248
277
|
schema: z.object({
|
|
249
278
|
dashboardId: z
|
|
@@ -254,7 +283,11 @@ export default defineAction({
|
|
|
254
283
|
.string()
|
|
255
284
|
.optional()
|
|
256
285
|
.describe("Legacy alias for dashboardId. Prefer dashboardId."),
|
|
257
|
-
code: z
|
|
286
|
+
code: z
|
|
287
|
+
.string()
|
|
288
|
+
.max(MAX_DASHBOARD_MUTATION_CODE_LENGTH)
|
|
289
|
+
.optional()
|
|
290
|
+
.describe(apiHelp),
|
|
258
291
|
operations: operationsInputSchema.describe(
|
|
259
292
|
"Structured equivalent of the typed script. Native callers should pass an array of mutation ops; shell/legacy callers may pass a JSON string. " +
|
|
260
293
|
"Supported ops: movePanels, removePanels, updatePanel, updatePanelPath, insertPanel, duplicatePanel, setDashboard, setFilterDefault.",
|
|
@@ -288,7 +321,8 @@ export default defineAction({
|
|
|
288
321
|
height: 680,
|
|
289
322
|
}),
|
|
290
323
|
},
|
|
291
|
-
|
|
324
|
+
timeoutMs: 35_000,
|
|
325
|
+
run: async (args, actionContext) => {
|
|
292
326
|
const code = nonEmptyCode(args.code);
|
|
293
327
|
const requestedOperations = nonEmptyOperations(args.operations);
|
|
294
328
|
const wantsHelpOnly =
|
|
@@ -357,7 +391,11 @@ export default defineAction({
|
|
|
357
391
|
root = computed.nextRoot;
|
|
358
392
|
operations = computed.nextOperations;
|
|
359
393
|
mutation = computed.nextMutation;
|
|
360
|
-
const sqlError = await validateMutationSql(
|
|
394
|
+
const sqlError = await validateMutationSql(
|
|
395
|
+
root,
|
|
396
|
+
operations,
|
|
397
|
+
actionContext?.signal,
|
|
398
|
+
);
|
|
361
399
|
if (sqlError) throw new Error(sqlError);
|
|
362
400
|
} else {
|
|
363
401
|
const saved = await upsertDashboardWithRetry(
|
|
@@ -368,6 +406,7 @@ export default defineAction({
|
|
|
368
406
|
const sqlError = await validateMutationSql(
|
|
369
407
|
computed.nextRoot,
|
|
370
408
|
computed.nextOperations,
|
|
409
|
+
actionContext?.signal,
|
|
371
410
|
);
|
|
372
411
|
if (sqlError) throw new Error(sqlError);
|
|
373
412
|
root = computed.nextRoot;
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
} from "../server/lib/dashboards-store";
|
|
17
17
|
import { parseDemoDescriptor } from "../server/lib/demo-source";
|
|
18
18
|
import { validateFirstPartyAnalyticsSql } from "../server/lib/first-party-analytics.js";
|
|
19
|
+
import { DASHBOARD_SQL_VALIDATION_TIMEOUT_MS } from "../shared/dashboard-report-timeouts.js";
|
|
19
20
|
import {
|
|
20
21
|
applyPanelOrder,
|
|
21
22
|
compactDashboardResult,
|
|
@@ -388,15 +389,30 @@ export function validateDashboardConfig(
|
|
|
388
389
|
return null;
|
|
389
390
|
}
|
|
390
391
|
|
|
392
|
+
const MAX_CONCURRENT_SQL_VALIDATIONS = 8;
|
|
393
|
+
|
|
394
|
+
export interface ValidatePanelSqlOptions {
|
|
395
|
+
signal?: AbortSignal;
|
|
396
|
+
}
|
|
397
|
+
|
|
391
398
|
/** Validate every query panel, or only the supplied ids for a targeted edit. */
|
|
392
399
|
export async function validatePanelSql(
|
|
393
400
|
config: Record<string, unknown>,
|
|
394
401
|
panelIds?: ReadonlySet<string>,
|
|
402
|
+
options: ValidatePanelSqlOptions = {},
|
|
395
403
|
): Promise<string | null> {
|
|
396
404
|
const panels = config.panels;
|
|
397
405
|
if (!Array.isArray(panels)) return null;
|
|
398
406
|
const vars = buildDryRunVars(config);
|
|
407
|
+
const bigQueryPanels: Array<{
|
|
408
|
+
index: number;
|
|
409
|
+
panel: Record<string, unknown>;
|
|
410
|
+
sql: string;
|
|
411
|
+
}> = [];
|
|
399
412
|
for (let i = 0; i < panels.length; i++) {
|
|
413
|
+
if (options.signal?.aborted) {
|
|
414
|
+
throw new Error("Dashboard SQL validation was cancelled");
|
|
415
|
+
}
|
|
400
416
|
const p = panels[i] as Record<string, unknown>;
|
|
401
417
|
if (panelIds && (typeof p.id !== "string" || !panelIds.has(p.id))) {
|
|
402
418
|
continue;
|
|
@@ -458,14 +474,72 @@ export async function validatePanelSql(
|
|
|
458
474
|
if (!raw.trim()) continue;
|
|
459
475
|
const sql = interpolate(raw, vars);
|
|
460
476
|
if (!sql.trim()) continue;
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
477
|
+
bigQueryPanels.push({ index: i, panel: p, sql });
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (bigQueryPanels.length === 0) return null;
|
|
481
|
+
|
|
482
|
+
// A dashboard save is one logical operation. Validate the selected BigQuery
|
|
483
|
+
// panels as one bounded batch so a slow panel cannot multiply the per-query
|
|
484
|
+
// timeout by the number of panels in the mutation.
|
|
485
|
+
const validationController = new AbortController();
|
|
486
|
+
const abortFromCaller = () => validationController.abort();
|
|
487
|
+
options.signal?.addEventListener("abort", abortFromCaller, { once: true });
|
|
488
|
+
let timedOut = false;
|
|
489
|
+
const timeout = setTimeout(() => {
|
|
490
|
+
timedOut = true;
|
|
491
|
+
validationController.abort();
|
|
492
|
+
}, DASHBOARD_SQL_VALIDATION_TIMEOUT_MS);
|
|
493
|
+
const errors: Array<string | null> = Array(bigQueryPanels.length).fill(null);
|
|
494
|
+
let nextIndex = 0;
|
|
495
|
+
|
|
496
|
+
const worker = async () => {
|
|
497
|
+
while (!validationController.signal.aborted) {
|
|
498
|
+
const taskIndex = nextIndex++;
|
|
499
|
+
const task = bigQueryPanels[taskIndex];
|
|
500
|
+
if (!task) return;
|
|
501
|
+
|
|
502
|
+
let err: string | null;
|
|
503
|
+
try {
|
|
504
|
+
err = await dryRunQuery(task.sql, {
|
|
505
|
+
signal: validationController.signal,
|
|
506
|
+
});
|
|
507
|
+
} catch (e: any) {
|
|
508
|
+
err = e?.message ?? String(e);
|
|
509
|
+
}
|
|
510
|
+
errors[taskIndex] = err;
|
|
466
511
|
}
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
try {
|
|
515
|
+
await Promise.all(
|
|
516
|
+
Array.from(
|
|
517
|
+
{
|
|
518
|
+
length: Math.min(
|
|
519
|
+
MAX_CONCURRENT_SQL_VALIDATIONS,
|
|
520
|
+
bigQueryPanels.length,
|
|
521
|
+
),
|
|
522
|
+
},
|
|
523
|
+
() => worker(),
|
|
524
|
+
),
|
|
525
|
+
);
|
|
526
|
+
} finally {
|
|
527
|
+
clearTimeout(timeout);
|
|
528
|
+
options.signal?.removeEventListener("abort", abortFromCaller);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (options.signal?.aborted && !timedOut) {
|
|
532
|
+
throw new Error("Dashboard SQL validation was cancelled");
|
|
533
|
+
}
|
|
534
|
+
if (timedOut) {
|
|
535
|
+
return `Dashboard SQL validation timed out after ${Math.round(DASHBOARD_SQL_VALIDATION_TIMEOUT_MS / 1000)} seconds`;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
for (let i = 0; i < bigQueryPanels.length; i++) {
|
|
539
|
+
const err = errors[i];
|
|
467
540
|
if (err) {
|
|
468
|
-
|
|
541
|
+
const task = bigQueryPanels[i];
|
|
542
|
+
return `panel[${task.index}] "${task.panel.title || task.panel.id}" SQL is invalid: ${err}`;
|
|
469
543
|
}
|
|
470
544
|
}
|
|
471
545
|
return null;
|