@agent-native/core-corpus 0.210.2 → 0.211.0-nightly-20261020183616
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/actions/references/action-fields.md +5 -3
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +41 -2
- package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +4 -2
- package/corpus/templates/analytics/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/analytics/actions/build-data-index.ts +11 -2
- package/corpus/templates/analytics/actions/configure-content-collection.ts +29 -4
- package/corpus/templates/analytics/actions/configure-first-party-events-model.ts +9 -7
- package/corpus/templates/analytics/actions/create-analytics-public-key.ts +4 -0
- package/corpus/templates/analytics/actions/estimate-content-signups-cost.ts +72 -0
- package/corpus/templates/analytics/actions/estimate-query-cost.ts +16 -134
- package/corpus/templates/analytics/actions/list-sigma-workbooks.ts +123 -42
- package/corpus/templates/analytics/actions/migrate-sigma-workbook.ts +20 -17
- package/corpus/templates/analytics/actions/revoke-analytics-public-key.ts +4 -0
- package/corpus/templates/analytics/app/components/dashboard/SigmaMigrationDialog.tsx +246 -680
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +1 -1
- package/corpus/templates/analytics/app/i18n/en-US.ts +54 -55
- package/corpus/templates/analytics/app/pages/DashboardOverview.tsx +0 -1
- package/corpus/templates/analytics/app/pages/DataSources.tsx +56 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/filter-vars.ts +4 -3
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +26 -8
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/interpolate.ts +3 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/panel-cost-notice.ts +172 -0
- package/corpus/templates/analytics/app/pages/data-sources/SearchConsoleDetail.tsx +441 -0
- package/corpus/templates/analytics/changelog/2026-10-15-a-content-dashboard-lists-every-blog-article-with-its-author.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-15-data-sources-has-a-google-search-console-card-that-shows-the.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-16-the-content-dashboard-can-show-signups-per-blog-article.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-19-creating-or-revoking-a-public-analytics-key-now-asks-you-to-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-19-search-sigma-workbooks-by-name-and-open-ones-a-teammate-alre.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-20-blog-signup-attribution-excludes-signups-before-the-first-vi.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-20-collapsed-analytics-navigation-starts-at-the-top.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-20-content-keeps-successful-coverage-dates-and-missing-day-gaps.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-20-private-github-repositories-can-be-indexed-with-your-connect.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-20-search-console-preserves-backfill-and-paused-collection.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-20-search-console-refreshes-historical-coverage-when-properties.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-20-search-console-setup-keeps-saved-properties-readable-and-sup.md +6 -0
- package/corpus/templates/analytics/seeds/dashboards/content-view.json +182 -0
- package/corpus/templates/analytics/server/db/schema.ts +1 -0
- package/corpus/templates/analytics/server/lib/bi-migration/external-dashboards-store.ts +56 -18
- package/corpus/templates/analytics/server/lib/bi-migration/unfinished-import.ts +64 -0
- package/corpus/templates/analytics/server/lib/builtin-dashboard-pass.ts +17 -2
- package/corpus/templates/analytics/server/lib/content-metrics/collect.ts +2 -0
- package/corpus/templates/analytics/server/lib/content-metrics/config.ts +92 -3
- package/corpus/templates/analytics/server/lib/content-metrics/legacy-content-view.json +172 -0
- package/corpus/templates/analytics/server/lib/content-metrics/migration.ts +2 -0
- package/corpus/templates/analytics/server/lib/content-metrics/steps/daily-metrics.ts +8 -3
- package/corpus/templates/analytics/server/lib/content-metrics/steps/search-console.ts +16 -3
- package/corpus/templates/analytics/server/lib/content-metrics/steps/signups.ts +240 -0
- package/corpus/templates/analytics/server/lib/content-metrics/store.ts +5 -0
- package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +21 -0
- package/corpus/templates/analytics/server/lib/dashboard-panel-verification.ts +300 -5
- package/corpus/templates/analytics/server/lib/dashboard-seeds.ts +2 -0
- package/corpus/templates/analytics/server/lib/dashboards-store.ts +28 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +128 -24
- package/corpus/templates/analytics/server/lib/google-analytics.ts +56 -16
- package/corpus/templates/analytics/server/lib/index-build-github.ts +5 -0
- package/corpus/templates/analytics/server/lib/provider-credentials.ts +47 -2
- package/corpus/templates/analytics/server/lib/query-cost-estimate.ts +184 -5
- package/corpus/templates/analytics/server/plugins/db.ts +9 -1
- package/corpus/templates/analytics/shared/builtin-dashboard-ids.ts +2 -0
- package/corpus/templates/analytics/shared/search-console-property.ts +78 -0
- package/corpus/templates/assets/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/assets/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/brain/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/brain/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/calendar/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/calendar/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/calendar/actions/delete-event.ts +1 -3
- package/corpus/templates/calendar/actions/delete-events.ts +1 -2
- package/corpus/templates/calendar/actions/event-action-helpers.ts +0 -4
- package/corpus/templates/calendar/actions/update-event.ts +2 -6
- package/corpus/templates/chat/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/chat/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/clips/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/clips/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/clips/app/components/player/chapter-list.tsx +7 -6
- package/corpus/templates/clips/app/components/player/comment-composer.tsx +5 -1
- package/corpus/templates/clips/app/components/player/player-controls.tsx +1 -0
- package/corpus/templates/clips/app/components/player/scrubber.tsx +4 -2
- package/corpus/templates/clips/app/i18n/en-US.ts +3 -3
- package/corpus/templates/clips/app/lib/extension-auth.ts +70 -0
- package/corpus/templates/clips/app/root.tsx +22 -10
- package/corpus/templates/clips/app/routes/_app.notifications.tsx +3 -0
- package/corpus/templates/clips/app/routes/import.tsx +115 -110
- package/corpus/templates/clips/changelog/2026-10-19-agents-see-a-clip-s-frames-as-soon-as-it-finishes-processing.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-19-chapter-list-times-now-match-the-player-s-clock-and-each-cha.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-19-editing-a-comment-puts-the-cursor-after-its-text-and-reply-o.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-19-youtube-import-followups.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-20-locally-saved-desktop-recordings-now-appear-in-the-recovery-.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +29 -186
- package/corpus/templates/clips/desktop/src/components/RecordingRecovery.tsx +171 -16
- package/corpus/templates/clips/desktop/src/lib/record-first.ts +9 -32
- package/corpus/templates/clips/desktop/src/lib/recording-recovery.ts +30 -1
- package/corpus/templates/clips/server/lib/agent-frame-request.ts +5 -2
- package/corpus/templates/content/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/content/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/content/actions/_content-database-personal-view.ts +5 -15
- package/corpus/templates/content/actions/_database-navigation.ts +3 -3
- package/corpus/templates/content/actions/_database-utils.ts +10 -22
- package/corpus/templates/content/actions/_database-view-setup.ts +16 -76
- package/corpus/templates/content/actions/create-comment-ai-suggestion.ts +93 -44
- package/corpus/templates/content/actions/export-document.ts +2 -14
- package/corpus/templates/content/actions/suggest-document-edit.ts +5 -7
- package/corpus/templates/content/actions/update-content-database-view.ts +2 -14
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +82 -30
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +8 -26
- package/corpus/templates/content/app/components/editor/PageDraftRecovery.tsx +72 -7
- package/corpus/templates/content/app/components/editor/TableHoverControls.tsx +292 -103
- package/corpus/templates/content/app/components/editor/ThreadSuggestion.tsx +147 -40
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +108 -11
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +122 -153
- package/corpus/templates/content/app/components/editor/database/filter-sort.ts +8 -783
- package/corpus/templates/content/app/components/editor/live-editor-session.ts +5 -1
- package/corpus/templates/content/app/components/editor/page-draft-journal.ts +61 -1
- package/corpus/templates/content/app/components/editor/thread-suggestions.ts +30 -0
- package/corpus/templates/content/app/components/editor/useDocumentReconcileRecovery.ts +23 -4
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +36 -10
- package/corpus/templates/content/changelog/2026-10-16-opening-a-page-in-a-new-tab-while-another-tab-is-still-savin.md +6 -0
- package/corpus/templates/content/changelog/2026-10-16-typing-steadily-in-one-tab-no-longer-makes-another-open-tab-.md +6 -0
- package/corpus/templates/content/changelog/2026-10-19-database-views-can-filter-the-same-property-more-than-once-n.md +6 -0
- package/corpus/templates/content/changelog/2026-10-19-edits-made-from-a-block-s-menu-or-a-table-s-row-and-column-m.md +6 -0
- package/corpus/templates/content/changelog/2026-10-19-starting-a-comment-puts-the-cursor-in-the-comment-box-ready-.md +6 -0
- package/corpus/templates/content/changelog/2026-10-19-table-row-and-column-options-now-appear-for-the-row-or-colum.md +6 -0
- package/corpus/templates/content/docs/product/capabilities/content.revision.suggestions.md +3 -1
- package/corpus/templates/content/e2e/helpers.ts +10 -0
- package/corpus/templates/content/shared/api.ts +18 -12
- package/corpus/templates/content/shared/database-query.ts +107 -13
- package/corpus/templates/content/shared/suggestion-diff.ts +35 -0
- package/corpus/templates/crm/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/crm/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/design/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/design/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/design/.generated/bridge/native-builtin-metadata.generated.ts +301 -0
- package/corpus/templates/design/.generated/bridge/native-worker-source.generated.ts +6 -0
- package/corpus/templates/design/actions/delete-file.ts +1 -4
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +10 -1
- package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +24 -4
- package/corpus/templates/design/app/pages/design-editor/commands/mode-change.ts +14 -9
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-modes.ts +23 -2
- package/corpus/templates/design/app/pages/design-editor/tool-state.ts +29 -0
- package/corpus/templates/design/changelog/2026-10-19-reply-and-edit-on-a-comment-in-the-sidebar-or-on-a-canvas-pi.md +6 -0
- package/corpus/templates/design/changelog/2026-10-20-comments-return-to-the-mode-they-came-from-and-layers-search.md +6 -0
- package/corpus/templates/design/e2e/global-setup.ts +185 -85
- package/corpus/templates/dispatch/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/dispatch/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/dispatch/actions/apply-workspace-connection-setup.ts +4 -0
- package/corpus/templates/dispatch/actions/delete-workspace-connection.ts +4 -0
- package/corpus/templates/dispatch/actions/set-workspace-connection-grant.ts +4 -0
- package/corpus/templates/dispatch/actions/upsert-workspace-connection.ts +4 -0
- package/corpus/templates/dispatch/changelog/2026-10-19-vault-secrets-grants-link-tokens-app-access-and-workspace-co.md +6 -0
- package/corpus/templates/factory/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +105 -33
- package/corpus/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +128 -74
- package/corpus/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +39 -11
- package/corpus/templates/forms/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/forms/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/mail/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/mail/.agents/skills/contacts-and-crm/SKILL.md +22 -17
- package/corpus/templates/mail/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/mail/AGENTS.md +1 -1
- package/corpus/templates/mail/actions/provider-api-request.ts +3 -3
- package/corpus/templates/mail/app/components/email/IntegrationsSidebar.tsx +211 -54
- package/corpus/templates/mail/app/hooks/use-inbox-threads.ts +26 -2
- package/corpus/templates/mail/app/i18n/en-US.ts +5 -1
- package/corpus/templates/mail/app/lib/gong-connection.ts +27 -0
- package/corpus/templates/mail/app/lib/thread-cache.ts +110 -9
- package/corpus/templates/mail/app/root.tsx +10 -2
- package/corpus/templates/mail/changelog/2026-10-20-connected-gong-settings-now-open-the-shared-integration-mana.md +6 -0
- package/corpus/templates/mail/changelog/2026-10-20-gong-connection-details-now-match-the-call-data-available-in.md +6 -0
- package/corpus/templates/mail/changelog/2026-10-20-improve-gong-setup-with-a-click-to-connect-flow-and-permissi.md +6 -0
- package/corpus/templates/mail/changelog/2026-10-20-mail-lets-you-retry-when-it-cannot-check-your-gong-access.md +6 -0
- package/corpus/templates/mail/changelog/2026-10-20-mail-thread-cache-stays-isolated-when-mailbox-scope-changes.md +6 -0
- package/corpus/templates/mail/changelog/2026-10-20-the-inbox-and-recently-opened-threads-now-open-instantly-fro.md +6 -0
- package/corpus/templates/mail/server/handlers/emails.ts +9 -0
- package/corpus/templates/plan/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/plan/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/plan/app/pages/PlansPage.tsx +173 -33
- package/corpus/templates/plan/changelog/2026-10-19-opening-a-comment-thread-puts-the-cursor-in-the-reply-box-an.md +6 -0
- package/corpus/templates/slides/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/slides/.deploy-tmp/agent-native-provided-plugins.mjs +10 -0
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +6 -42
- package/corpus/templates/slides/app/components/editor/FloatingToolbar.tsx +3 -7
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +5 -14
- package/corpus/templates/slides/app/i18n/en-US.ts +0 -2
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +27 -33
- package/corpus/templates/slides/scripts/edit-fidelity/run.ts +1 -3
- package/corpus/templates/tasks/.agents/skills/actions/references/action-fields.md +5 -3
- package/package.json +1 -1
package/corpus/README.md
CHANGED
|
@@ -95,9 +95,9 @@ MCP clients see no `outputSchema` by default. Set `mcpOutputSchema: true` (requi
|
|
|
95
95
|
|
|
96
96
|
`authorize` decides whether the caller may run the action at all. It wraps
|
|
97
97
|
`run`, so it holds at **all six dispatch sites** — agent tool, HTTP, frontend
|
|
98
|
-
hook, MCP, A2A, CLI
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
hook, MCP, A2A, CLI. Use `authorize` for "not everyone may do this"; use
|
|
99
|
+
`needsApproval` to ask a human to bless one call a permitted caller is already
|
|
100
|
+
allowed to make.
|
|
101
101
|
|
|
102
102
|
```ts
|
|
103
103
|
import { coachAccess } from "../lib/access.js"; // defineAppRoles(...)
|
|
@@ -140,4 +140,6 @@ export default defineAction({
|
|
|
140
140
|
|
|
141
141
|
When the gate is truthy and the call isn't yet approved, the loop emits an `approval_required` event and **stops the turn — `run()` never executes**. A predicate gates conditionally (e.g. only external recipients) and **fails closed**: a throw is treated as "approval required". It receives its own copy of the prepared snapshot of the validated arguments, defaults applied, and the approval binds to the snapshot's hash, so an approved call runs only with the arguments that were approved (a Date binds by its time). The human approves via the chat UI's Approve affordance, which re-issues the turn with the call's `approvalKey`, and only then does the action run.
|
|
142
142
|
|
|
143
|
+
Approval is given in the app, so HTTP never grants it. The HTTP action route and the generated Workers entry run a gated call only for the proven app UI (a frontend call from the signed-in user, same origin, holding the short-lived UI capability; `isProvenUiRequest`, the proof `uiOnly` uses) or the app's MCP directory widget. Every other HTTP caller (bearer or API key, MCP OAuth token, org service token, A2A, extension iframe, mobile app) gets `409 approval_required`, and a service identity can never approve. The shared client presents the capability with every write from a signed-in session (minted once per user, reused for nine minutes, never for reads or public pages), so a gated button works on its first request. A call without proof gets `403 ui_capability_required`; the client then re-mints and retries once, which is only the fallback for an expired or rejected capability. Every surface evaluates the gate through `preparedCallNeedsApproval`, so a throwing predicate asks there too. A caller that must run unattended needs a different action, never a bypass of the gate.
|
|
144
|
+
|
|
143
145
|
**Keep approvals rare** — the default is off and almost every action should leave it off. The canonical example is Mail's `send-email` (`needsApproval: true`). See the `security` skill and the Human Approval doc.
|
|
@@ -144,8 +144,32 @@ collection stays off until a property owner adds the service account that
|
|
|
144
144
|
`get-content-collection` returns as `serviceAccount.email`; then set its
|
|
145
145
|
`searchConsoleSites` and turn it on.
|
|
146
146
|
|
|
147
|
+
The `signups` step writes `signups_first_touch` per article: people who
|
|
148
|
+
first landed on the article and later signed up, on the day of that first
|
|
149
|
+
visit, so a window's days add up to its total. It is the only step that reads
|
|
150
|
+
the warehouse. It stays `not_configured` until `configure-content-collection`
|
|
151
|
+
sets its `signups` tables, `signupSource`, `signupsSince` and
|
|
152
|
+
`maxBytesBilled`. Measure the cap with `estimate-content-signups-cost` (a dry
|
|
153
|
+
run, free) and get the spend approved before turning the step on. Each run
|
|
154
|
+
dry-runs first, stops as `cost_limited` without querying when the estimate is
|
|
155
|
+
over the cap, and runs the join with that cap as BigQuery's billing limit.
|
|
156
|
+
|
|
157
|
+
The catalog's "Content" dashboard (`content-view`) is the reference for these
|
|
158
|
+
tables: start from its panels before writing new content SQL. Its Articles
|
|
159
|
+
panel shows why a metric is missing (`failed`, `not set up`, `still
|
|
160
|
+
arriving`) in a column next to the blank number; keep that pattern. The
|
|
161
|
+
first-party SQL policy rejects functions outside its allow-list (for example
|
|
162
|
+
`concat_ws`) and reads `CASE (` as a function call, so join a status column
|
|
163
|
+
instead of a scalar subquery inside `CASE`. Its trend chart uses wide rows
|
|
164
|
+
(`yKeys`), not `pivot`: a pivot fills a missing series with 0, so a day a
|
|
165
|
+
source didn't report would draw as a zero instead of a gap. Content is a
|
|
166
|
+
built-in: turning collection on with `configure-content-collection` lists it
|
|
167
|
+
in the organization's Dashboards at `content-view` (`contentDashboardId` in the
|
|
168
|
+
result). It is read-only; build on it with `duplicate-dashboard`.
|
|
169
|
+
|
|
147
170
|
There is no dbt Semantic Layer panel source. For a Semantic Layer metric, answer in chat with `query-dbt-semantic-metric`; build a warehouse panel only on a table the owner names.
|
|
148
171
|
|
|
172
|
+
|
|
149
173
|
AI-generated first-party panels are dashboard-time-bound by default. Set
|
|
150
174
|
`config.timeScope` to `"dashboard"` and include the matching dashboard time
|
|
151
175
|
filter in the SQL. The allowed values are:
|
|
@@ -467,8 +491,9 @@ visualizations outside these contracts may remain Custom Blocks.
|
|
|
467
491
|
|
|
468
492
|
## Sigma workbook import
|
|
469
493
|
|
|
470
|
-
The dashboard menu opens the Sigma import flow
|
|
471
|
-
|
|
494
|
+
The dashboard menu opens the Sigma import flow: a searchable table of every
|
|
495
|
+
workbook the user can see in Sigma, with the owner of each migrated one and a
|
|
496
|
+
Migrate button on the rest. Imports remain disabled for elements whose source project
|
|
472
497
|
Analytics cannot verify: Sigma's connection API reports provider type and
|
|
473
498
|
metadata but does not expose a BigQuery project identifier. Do not call
|
|
474
499
|
`migrate-sigma-workbook` for an unverified source or treat an Analytics
|
|
@@ -489,6 +514,16 @@ encrypted organization secrets and links them to an Analytics-only connection.
|
|
|
489
514
|
Credential values must never pass through agent chat, action arguments, logs,
|
|
490
515
|
source, or screenshots.
|
|
491
516
|
|
|
517
|
+
Each Sigma workbook migrates to at most one Analytics dashboard per
|
|
518
|
+
organization. `list-sigma-workbooks` reads migration status only for workbooks
|
|
519
|
+
the caller's Sigma connection returns, so status follows Sigma access. For a
|
|
520
|
+
migrated workbook it returns the dashboard id, link, and owner email and
|
|
521
|
+
display name to every org member, and the title and `canOpen: true` only when the caller can open
|
|
522
|
+
the dashboard. Point users at that dashboard, or its owner for access, instead
|
|
523
|
+
of importing again. Anyone whose Sigma login can open a workbook may retry its
|
|
524
|
+
failed import or take over an import stuck for ten minutes; only the person who
|
|
525
|
+
migrated a workbook may re-import it.
|
|
526
|
+
|
|
492
527
|
If Sigma adds verifiable source mapping in the future, the planned import will
|
|
493
528
|
create a private snapshot in the user's personal `Sigma` folder. Other source
|
|
494
529
|
types and unsupported elements will become review callouts. Preserve migration
|
|
@@ -768,6 +803,10 @@ When the user asks to change an existing panel on the open dashboard:
|
|
|
768
803
|
`verified: false`, an error, or the user saying the change is not visible,
|
|
769
804
|
call `inspect-dashboard-panel` for that panel, fix what it reports, and
|
|
770
805
|
verify again. Never describe a visible change you have not verified.
|
|
806
|
+
An edit that changes a panel's query also returns `cost`: what one refresh
|
|
807
|
+
of the changed panels scans and costs at the default filters, and per month
|
|
808
|
+
for the dashboard's email reports. The summary repeats it; when it says the
|
|
809
|
+
cost is over a threshold, tell the user the number.
|
|
771
810
|
`update-dashboard`, `compose-dashboard`, and `restore-dashboard-revision`
|
|
772
811
|
return the same fields. Editing a dashboard you can only view fails with
|
|
773
812
|
`dashboard_forbidden` before any SQL runs.
|
|
@@ -114,8 +114,10 @@ After cutover, an org admin can point charts at a scheduled warehouse events
|
|
|
114
114
|
model with `configure-first-party-events-model`; run `check` first, which
|
|
115
115
|
dry-runs as the app and saves nothing. With a model set, a first-party query
|
|
116
116
|
reads `analytics_events` from the model only when its WHERE filters
|
|
117
|
-
`event_name` to named events (`= 'x'` or `IN (...)
|
|
118
|
-
|
|
117
|
+
`event_name` to named events (`= 'x'` or `IN (...)`) in a top-level AND
|
|
118
|
+
conjunct, or in a parenthesized OR whose every branch does, because the model
|
|
119
|
+
has no `http.response` rows. Other parenthesized conditions may use OR and
|
|
120
|
+
NOT; an OR, NOT, or CASE at the WHERE's own level keeps the query raw; its result carries
|
|
119
121
|
`eventsSource` and `sourceGeneration` (the model build it read). Unfiltered
|
|
120
122
|
queries, `SELECT *`, and queries that name `public_key_id` or `context`, read
|
|
121
123
|
`http.response`, or read `properties` other than through `JSON_VALUE`,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// AUTO-GENERATED by @agent-native/core deploy build
|
|
2
|
+
import { markDefaultPluginProvided } from "@agent-native/core/server/edge";
|
|
3
|
+
|
|
4
|
+
const pluginStems = ["agent-chat","analytics-alert-jobs","analytics-rollup-backfill-jobs","auth","collab","config","content-metrics-collection-jobs","core-routes","creative-context","dashboard-report-jobs","db","feature-flags","labs","onboarding","otel","session-replay-retention-jobs","transactional-emails","uptime-monitor-jobs"];
|
|
5
|
+
|
|
6
|
+
export default function markBuildDiscoveredPlugins(nitroApp) {
|
|
7
|
+
for (const stem of pluginStems) {
|
|
8
|
+
markDefaultPluginProvided(nitroApp, stem);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { defineAction, fail } from "@agent-native/core/action";
|
|
2
2
|
import {
|
|
3
|
+
getRequestContext,
|
|
3
4
|
getRequestOrgId,
|
|
4
5
|
getRequestUserEmail,
|
|
6
|
+
runWithRequestContext,
|
|
5
7
|
} from "@agent-native/core/server";
|
|
6
8
|
import { getOrgSetting, mutateOrgSetting } from "@agent-native/core/settings";
|
|
7
9
|
import { z } from "zod";
|
|
@@ -110,8 +112,15 @@ export default defineAction({
|
|
|
110
112
|
});
|
|
111
113
|
let outcome: SourceIndexRunOutcome;
|
|
112
114
|
try {
|
|
113
|
-
const { bundle: builtBundle } =
|
|
114
|
-
|
|
115
|
+
const { bundle: builtBundle } = await runWithRequestContext(
|
|
116
|
+
{
|
|
117
|
+
...(getRequestContext() ?? {}),
|
|
118
|
+
userEmail: admin.userEmail,
|
|
119
|
+
orgId,
|
|
120
|
+
credentialScope: "org",
|
|
121
|
+
},
|
|
122
|
+
() => buildDbtSourceIndexFromGitHub(repository),
|
|
123
|
+
);
|
|
115
124
|
// Computed before the write so a bad source fails the run instead of
|
|
116
125
|
// leaving a stored index with no recorded revisions.
|
|
117
126
|
// Check the new build before the write, then report its final source IDs
|
|
@@ -5,9 +5,12 @@ import {
|
|
|
5
5
|
} from "@agent-native/core/server";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
|
|
8
|
+
import { installBuiltinDashboardOnce } from "../server/lib/builtin-dashboards.js";
|
|
8
9
|
import {
|
|
9
10
|
contentCollectionConfigSchema,
|
|
10
11
|
searchConsoleSiteSchema,
|
|
12
|
+
signupsSettingsUpdateSchema,
|
|
13
|
+
storedSearchConsoleSiteSchema,
|
|
11
14
|
} from "../server/lib/content-metrics/config.js";
|
|
12
15
|
import { readContentCollectionStatus } from "../server/lib/content-metrics/status.js";
|
|
13
16
|
import {
|
|
@@ -17,6 +20,8 @@ import {
|
|
|
17
20
|
import { requireAnalyticsAdminContext } from "../server/lib/db-admin-connections.js";
|
|
18
21
|
import { cliBoolean } from "./schema-helpers.js";
|
|
19
22
|
|
|
23
|
+
const CONTENT_VIEW_DASHBOARD_ID = "content-view";
|
|
24
|
+
|
|
20
25
|
export default defineAction({
|
|
21
26
|
mcpAnnotations: {
|
|
22
27
|
readOnlyHint: false,
|
|
@@ -24,7 +29,7 @@ export default defineAction({
|
|
|
24
29
|
openWorldHint: false,
|
|
25
30
|
},
|
|
26
31
|
description:
|
|
27
|
-
"Turn the organization's daily Content view collection on or off and choose its steps. Requires an organization owner or admin. The caller becomes the stored owner: the daily job resolves provider connections as them. Omitted step settings keep their saved values.",
|
|
32
|
+
"Turn the organization's daily Content view collection on or off and choose its steps. Requires an organization owner or admin. The caller becomes the stored owner: the daily job resolves provider connections as them. Omitted step settings keep their saved values. Turning collection on also lists the built-in Content dashboard in the organization's Dashboards; the result's contentDashboardId opens it at /dashboards/<id>.",
|
|
28
33
|
schema: z.object({
|
|
29
34
|
enabled: cliBoolean.describe("Whether the daily collection runs."),
|
|
30
35
|
builderBlog: cliBoolean
|
|
@@ -39,15 +44,20 @@ export default defineAction({
|
|
|
39
44
|
"Collect daily Search Console clicks and impressions. Turn on only after a property owner has given the service account access.",
|
|
40
45
|
),
|
|
41
46
|
searchConsoleSites: z
|
|
42
|
-
.array(
|
|
47
|
+
.array(storedSearchConsoleSiteSchema)
|
|
43
48
|
.max(20)
|
|
44
49
|
.optional()
|
|
45
50
|
.describe(
|
|
46
51
|
'Search Console properties to collect, replacing the saved list, e.g. [{"siteUrl":"sc-domain:example.com","pathPrefix":"/blog/"}].',
|
|
47
52
|
),
|
|
53
|
+
signups: signupsSettingsUpdateSchema
|
|
54
|
+
.optional()
|
|
55
|
+
.describe(
|
|
56
|
+
"Per-article signups settings, merged into the saved ones: enabled, signupEventsTable and firstPageviewsTable (project.dataset.table), signupSource (its signups are also counted on their own, as Source signups), signupsSince (YYYY-MM-DD) and maxBytesBilled. The step stays not set up until all are set; measure maxBytesBilled with estimate-content-signups-cost, and get it approved before turning the step on, since every run scans the warehouse.",
|
|
57
|
+
),
|
|
48
58
|
}),
|
|
49
59
|
run: async (
|
|
50
|
-
{ enabled, builderBlog, ga4, searchConsole, searchConsoleSites },
|
|
60
|
+
{ enabled, builderBlog, ga4, searchConsole, searchConsoleSites, signups },
|
|
51
61
|
ctx,
|
|
52
62
|
) => {
|
|
53
63
|
const admin = await requireAnalyticsAdminContext({
|
|
@@ -67,8 +77,23 @@ export default defineAction({
|
|
|
67
77
|
enabled: searchConsole ?? saved.searchConsole.enabled,
|
|
68
78
|
sites: searchConsoleSites ?? saved.searchConsole.sites,
|
|
69
79
|
},
|
|
80
|
+
signups: { ...saved.signups, ...signups },
|
|
70
81
|
});
|
|
82
|
+
if (enabled && config.searchConsole.enabled) {
|
|
83
|
+
z.array(searchConsoleSiteSchema)
|
|
84
|
+
.max(20)
|
|
85
|
+
.parse(config.searchConsole.sites);
|
|
86
|
+
}
|
|
87
|
+
if (enabled) {
|
|
88
|
+
await installBuiltinDashboardOnce(
|
|
89
|
+
{ email: admin.userEmail, orgId: admin.orgId },
|
|
90
|
+
CONTENT_VIEW_DASHBOARD_ID,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
71
93
|
await writeContentCollection(scope, { enabled, config });
|
|
72
|
-
return
|
|
94
|
+
return {
|
|
95
|
+
...(await readContentCollectionStatus(admin.orgId, admin.userEmail)),
|
|
96
|
+
contentDashboardId: enabled ? CONTENT_VIEW_DASHBOARD_ID : null,
|
|
97
|
+
};
|
|
73
98
|
},
|
|
74
99
|
});
|
|
@@ -33,7 +33,9 @@ function utcDateDaysAgo(days: number): string {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
// Dry runs only: BigQuery plans the query and reports the bytes it would
|
|
36
|
-
// scan, but runs and bills nothing.
|
|
36
|
+
// scan, but runs and bills nothing. Both read every projected column: BigQuery
|
|
37
|
+
// bills only the columns a query reads, so COUNT(*) would report almost nothing
|
|
38
|
+
// while a chart can read any of them.
|
|
37
39
|
async function checkModel(
|
|
38
40
|
table: string,
|
|
39
41
|
signal?: AbortSignal,
|
|
@@ -47,7 +49,7 @@ async function checkModel(
|
|
|
47
49
|
}
|
|
48
50
|
const projection = `(${firstPartyEventsModelProjection(table)}) AS analytics_events`;
|
|
49
51
|
const recent = await dryRunQuerySchema(
|
|
50
|
-
`SELECT
|
|
52
|
+
`SELECT * FROM ${projection} WHERE event_date = DATE '${utcDateDaysAgo(1)}'`,
|
|
51
53
|
{ signal },
|
|
52
54
|
);
|
|
53
55
|
if (recent.error) {
|
|
@@ -60,10 +62,10 @@ async function checkModel(
|
|
|
60
62
|
}
|
|
61
63
|
// Eligible chart SQL need not bound event_date, so a model that refuses an
|
|
62
64
|
// unbounded read (a required partition filter, say) would fail those charts.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
);
|
|
65
|
+
// guard:allow-unbounded-read — dry run: BigQuery plans it and bills nothing.
|
|
66
|
+
const full = await dryRunQuerySchema(`SELECT * FROM ${projection}`, {
|
|
67
|
+
signal,
|
|
68
|
+
});
|
|
67
69
|
if (full.error) {
|
|
68
70
|
return {
|
|
69
71
|
status: "unavailable",
|
|
@@ -91,7 +93,7 @@ export default defineAction({
|
|
|
91
93
|
openWorldHint: true,
|
|
92
94
|
},
|
|
93
95
|
description:
|
|
94
|
-
"Check or set the warehouse events model that first-party Analytics charts read instead of the raw event table. `check` (any org member) reads the model's table metadata and dry-runs the chart projection against it as the app, reporting whether the app can query it and the bytes
|
|
96
|
+
"Check or set the warehouse events model that first-party Analytics charts read instead of the raw event table. `check` (any org member) reads the model's table metadata and dry-runs the chart projection against it as the app, reporting whether the app can query it and the most bytes one read of yesterday's full UTC day, or of the whole model, could scan (every column; a chart that reads the model more than once, such as a self-join, scans that much per read); it runs and bills no query. `set` (org admin, needs approval) runs the same check and saves the model only if it passes. `clear` (org admin, needs approval) sends charts back to the raw table. Charts that read columns or events the model lacks, alerts, and onboarding journeys keep reading the raw table either way.",
|
|
95
97
|
schema: z.object({
|
|
96
98
|
mode: z.enum(["check", "set", "clear"]),
|
|
97
99
|
table: z
|
|
@@ -22,6 +22,10 @@ export default defineAction({
|
|
|
22
22
|
.optional()
|
|
23
23
|
.describe("Human label for this key, e.g. 'Hosted templates'."),
|
|
24
24
|
}),
|
|
25
|
+
// It creates, changes, or revokes a credential or who may use one: every
|
|
26
|
+
// call needs the user's approval, and "always allow" is not offered.
|
|
27
|
+
needsApproval: true,
|
|
28
|
+
allowPersistentApproval: false,
|
|
25
29
|
run: async (args) => {
|
|
26
30
|
return createAnalyticsPublicKey(resolveScope(), args.name ?? "Default key");
|
|
27
31
|
},
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { defineAction, fail } from "@agent-native/core/action";
|
|
2
|
+
import {
|
|
3
|
+
getRequestOrgId,
|
|
4
|
+
getRequestUserEmail,
|
|
5
|
+
} from "@agent-native/core/server";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
|
|
8
|
+
import { dryRunQuerySchema } from "../server/lib/bigquery.js";
|
|
9
|
+
import { ContentStepStop } from "../server/lib/content-metrics/step.js";
|
|
10
|
+
import {
|
|
11
|
+
buildSignupsQuery,
|
|
12
|
+
signupsTables,
|
|
13
|
+
} from "../server/lib/content-metrics/steps/signups.js";
|
|
14
|
+
import { readContentCollection } from "../server/lib/content-metrics/store.js";
|
|
15
|
+
import { requireAnalyticsAdminContext } from "../server/lib/db-admin-connections.js";
|
|
16
|
+
|
|
17
|
+
export default defineAction({
|
|
18
|
+
description:
|
|
19
|
+
"Dry-run today's per-article signups join for the organization's Content view and return the bytes BigQuery estimates it would scan, next to the saved maxBytesBilled cap. A dry run scans and bills nothing. Requires an organization owner or admin and the signups warehouse settings from configure-content-collection; use the estimate to choose maxBytesBilled before turning the signups step on.",
|
|
20
|
+
schema: z.object({}),
|
|
21
|
+
readOnly: true,
|
|
22
|
+
run: async (_args, ctx) => {
|
|
23
|
+
const admin = await requireAnalyticsAdminContext({
|
|
24
|
+
userEmail: getRequestUserEmail() || ctx?.userEmail,
|
|
25
|
+
orgId: getRequestOrgId() || ctx?.orgId || null,
|
|
26
|
+
});
|
|
27
|
+
const collection = await readContentCollection({
|
|
28
|
+
ownerEmail: admin.userEmail,
|
|
29
|
+
orgId: admin.orgId,
|
|
30
|
+
});
|
|
31
|
+
if (!collection) {
|
|
32
|
+
fail("Content collection isn't set up for this organization yet.", {
|
|
33
|
+
errorCode: "content_collection_not_configured",
|
|
34
|
+
statusCode: 412,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const signups = collection.config.signups;
|
|
38
|
+
let tables: ReturnType<typeof signupsTables>;
|
|
39
|
+
try {
|
|
40
|
+
tables = signupsTables(signups);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (!(error instanceof ContentStepStop)) throw error;
|
|
43
|
+
fail(error.message, {
|
|
44
|
+
errorCode: error.failure.errorCode,
|
|
45
|
+
statusCode: 412,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
const query = buildSignupsQuery(tables, new Date());
|
|
49
|
+
const estimate = await dryRunQuerySchema(query.sql);
|
|
50
|
+
if (estimate.error) {
|
|
51
|
+
fail(`The signups dry run failed: ${estimate.error}`, {
|
|
52
|
+
errorCode: "signups_dry_run_failed",
|
|
53
|
+
statusCode: 502,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
if (estimate.totalBytesProcessed === undefined) {
|
|
57
|
+
fail("BigQuery returned no byte estimate for the signups join.", {
|
|
58
|
+
errorCode: "signups_estimate_unavailable",
|
|
59
|
+
statusCode: 502,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
estimatedBytes: estimate.totalBytesProcessed,
|
|
64
|
+
maxBytesBilled: signups.maxBytesBilled,
|
|
65
|
+
withinCap:
|
|
66
|
+
signups.maxBytesBilled === null
|
|
67
|
+
? null
|
|
68
|
+
: estimate.totalBytesProcessed <= signups.maxBytesBilled,
|
|
69
|
+
window: { startDate: query.startDate, endDate: query.endDate },
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
});
|
|
@@ -11,141 +11,35 @@ import { isSafeDashboardId } from "../server/lib/dashboard-id";
|
|
|
11
11
|
import {
|
|
12
12
|
pageDashboardConfig,
|
|
13
13
|
resolveDashboardPanelQueries,
|
|
14
|
-
type ResolvedDashboardPanelQuery,
|
|
15
14
|
} from "../server/lib/dashboard-panel-verification";
|
|
16
|
-
import { countEnabledDashboardReportSubscriptions } from "../server/lib/dashboard-report-subscriptions";
|
|
17
15
|
import { loadDashboardSeed } from "../server/lib/dashboard-seeds";
|
|
18
16
|
import { getDashboard } from "../server/lib/dashboards-store";
|
|
19
17
|
import type { AnalyticsScope } from "../server/lib/first-party-analytics";
|
|
20
18
|
import {
|
|
21
|
-
|
|
19
|
+
describeEstimate,
|
|
20
|
+
describeReportSchedules,
|
|
22
21
|
estimateBigQuerySql,
|
|
23
22
|
estimateFirstPartySql,
|
|
24
|
-
|
|
23
|
+
estimateResolvedPanels,
|
|
25
24
|
formatBytes,
|
|
26
25
|
formatUsd,
|
|
27
26
|
ON_DEMAND_USD_PER_TIB,
|
|
28
27
|
QUERY_COST_CAVEATS,
|
|
29
28
|
QUERY_COST_THRESHOLDS,
|
|
29
|
+
scheduledDashboardRuns,
|
|
30
30
|
totalCost,
|
|
31
31
|
type PanelCostEstimate,
|
|
32
|
-
type QueryCostEstimate,
|
|
33
32
|
} from "../server/lib/query-cost-estimate";
|
|
34
33
|
import { clipHead } from "../shared/panel-render-contract";
|
|
35
34
|
|
|
36
35
|
const MAX_PANELS = 60;
|
|
37
|
-
const DRY_RUN_CONCURRENCY = 6;
|
|
38
36
|
/** Stop starting dry runs here so the action returns before its 55 s timeout. */
|
|
39
37
|
const DASHBOARD_DEADLINE_MS = 40_000;
|
|
40
38
|
|
|
41
|
-
function timedOutPanel(
|
|
42
|
-
panel: { panelId: string; title: string; source: string },
|
|
43
|
-
error: string,
|
|
44
|
-
): PanelCostEstimate {
|
|
45
|
-
return {
|
|
46
|
-
panelId: panel.panelId,
|
|
47
|
-
title: panel.title,
|
|
48
|
-
source: panel.source,
|
|
49
|
-
status: "failed",
|
|
50
|
-
failure: "timeout",
|
|
51
|
-
error,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Estimate one panel, but give up when the dashboard deadline passes, even
|
|
57
|
-
* mid-way through credential, table, or backend lookups, so finished panels
|
|
58
|
-
* still come back before the action's own timeout.
|
|
59
|
-
*/
|
|
60
|
-
async function estimatePanelBefore(
|
|
61
|
-
deadline: number,
|
|
62
|
-
panel: ResolvedDashboardPanelQuery,
|
|
63
|
-
scope: AnalyticsScope,
|
|
64
|
-
runsPerMonth: number | null,
|
|
65
|
-
signal?: AbortSignal,
|
|
66
|
-
): Promise<PanelCostEstimate> {
|
|
67
|
-
const remaining = deadline - Date.now();
|
|
68
|
-
if (remaining <= 0) {
|
|
69
|
-
return timedOutPanel(
|
|
70
|
-
panel,
|
|
71
|
-
"Skipped: this request ran out of time. Estimate this panel on its own with panelId.",
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
const controller = new AbortController();
|
|
75
|
-
const forwardAbort = () => controller.abort(signal?.reason);
|
|
76
|
-
if (signal?.aborted) forwardAbort();
|
|
77
|
-
signal?.addEventListener("abort", forwardAbort, { once: true });
|
|
78
|
-
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
79
|
-
const timedOut = new Promise<PanelCostEstimate>((resolve) => {
|
|
80
|
-
timer = setTimeout(() => {
|
|
81
|
-
resolve(
|
|
82
|
-
timedOutPanel(
|
|
83
|
-
panel,
|
|
84
|
-
"Stopped: this panel's dry run didn't finish in time. Estimate it on its own with panelId.",
|
|
85
|
-
),
|
|
86
|
-
);
|
|
87
|
-
controller.abort();
|
|
88
|
-
}, remaining);
|
|
89
|
-
});
|
|
90
|
-
try {
|
|
91
|
-
return await Promise.race([
|
|
92
|
-
estimateResolvedPanel(panel, scope, runsPerMonth, controller.signal),
|
|
93
|
-
timedOut,
|
|
94
|
-
]);
|
|
95
|
-
} finally {
|
|
96
|
-
clearTimeout(timer);
|
|
97
|
-
signal?.removeEventListener("abort", forwardAbort);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
async function mapPool<T, R>(
|
|
102
|
-
items: readonly T[],
|
|
103
|
-
concurrency: number,
|
|
104
|
-
fn: (item: T) => Promise<R>,
|
|
105
|
-
): Promise<R[]> {
|
|
106
|
-
const results: R[] = Array(items.length);
|
|
107
|
-
let next = 0;
|
|
108
|
-
const worker = async () => {
|
|
109
|
-
while (next < items.length) {
|
|
110
|
-
const index = next++;
|
|
111
|
-
results[index] = await fn(items[index]);
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
await Promise.all(
|
|
115
|
-
Array.from({ length: Math.min(concurrency, items.length) }, worker),
|
|
116
|
-
);
|
|
117
|
-
return results;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
39
|
function sentence(text: string): string {
|
|
121
40
|
return /[.!?]$/.test(text) ? text : `${text}.`;
|
|
122
41
|
}
|
|
123
42
|
|
|
124
|
-
function describeEstimate(estimate: QueryCostEstimate): string {
|
|
125
|
-
if (
|
|
126
|
-
estimate.status === "not_bigquery" ||
|
|
127
|
-
estimate.status === "not_estimated"
|
|
128
|
-
) {
|
|
129
|
-
return estimate.reason;
|
|
130
|
-
}
|
|
131
|
-
if (estimate.status === "failed") {
|
|
132
|
-
return `no estimate (${estimate.failure}): ${estimate.error}`;
|
|
133
|
-
}
|
|
134
|
-
const monthly =
|
|
135
|
-
estimate.usdPerMonth === null
|
|
136
|
-
? "not scheduled"
|
|
137
|
-
: `~${formatUsd(estimate.usdPerMonth)}/month over ${estimate.runsPerMonth} scheduled runs`;
|
|
138
|
-
const flags = [
|
|
139
|
-
estimate.overPerRunThreshold
|
|
140
|
-
? `over the ${formatUsd(QUERY_COST_THRESHOLDS.perRunUsd)}/run threshold`
|
|
141
|
-
: null,
|
|
142
|
-
estimate.overPerMonthThreshold
|
|
143
|
-
? `over the ${formatUsd(QUERY_COST_THRESHOLDS.perMonthUsd)}/month threshold`
|
|
144
|
-
: null,
|
|
145
|
-
].filter(Boolean);
|
|
146
|
-
return `~${formatBytes(estimate.estimatedBytes)}, ${formatUsd(estimate.usdPerRun)} per run, ${monthly}${flags.length ? ` (${flags.join("; ")})` : ""}`;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
43
|
export default defineAction({
|
|
150
44
|
description:
|
|
151
45
|
"Free cost estimate for BigQuery SQL. Dry-runs the query through Analytics' own BigQuery connection (BigQuery reads and bills nothing) and returns the bytes it would scan, dollars per run at on-demand pricing, and dollars per month for scheduled runs. " +
|
|
@@ -312,20 +206,15 @@ export default defineAction({
|
|
|
312
206
|
);
|
|
313
207
|
}
|
|
314
208
|
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
const panels: PanelCostEstimate[] = await mapPool(
|
|
325
|
-
estimated,
|
|
326
|
-
DRY_RUN_CONCURRENCY,
|
|
327
|
-
(panel) =>
|
|
328
|
-
estimatePanelBefore(deadline, panel, scope, runsPerMonth, signal),
|
|
209
|
+
const schedule = await scheduledDashboardRuns(dashboardId, scope);
|
|
210
|
+
const reportSchedules = schedule.reportSchedules;
|
|
211
|
+
const runsPerMonth = args.runsPerMonth ?? schedule.runsPerMonth;
|
|
212
|
+
const panels: PanelCostEstimate[] = await estimateResolvedPanels(
|
|
213
|
+
selected.slice(0, MAX_PANELS),
|
|
214
|
+
scope,
|
|
215
|
+
runsPerMonth,
|
|
216
|
+
startedAt + DASHBOARD_DEADLINE_MS,
|
|
217
|
+
signal,
|
|
329
218
|
);
|
|
330
219
|
const total = totalCost(panels);
|
|
331
220
|
const flagged = panels.filter(
|
|
@@ -358,16 +247,10 @@ export default defineAction({
|
|
|
358
247
|
: args.runsPerMonth === undefined
|
|
359
248
|
? `~${formatUsd(total.usdPerMonth)}/month if its email reports use these filters`
|
|
360
249
|
: `~${formatUsd(total.usdPerMonth)}/month in scheduled runs`;
|
|
361
|
-
const totalFlags = {
|
|
362
|
-
overPerRunThreshold: total.usdPerRun > QUERY_COST_THRESHOLDS.perRunUsd,
|
|
363
|
-
overPerMonthThreshold:
|
|
364
|
-
total.usdPerMonth !== null &&
|
|
365
|
-
total.usdPerMonth > QUERY_COST_THRESHOLDS.perMonthUsd,
|
|
366
|
-
};
|
|
367
250
|
const totalOver = [
|
|
368
|
-
|
|
251
|
+
total.overPerRunThreshold &&
|
|
369
252
|
`the ${formatUsd(QUERY_COST_THRESHOLDS.perRunUsd)}/run`,
|
|
370
|
-
|
|
253
|
+
total.overPerMonthThreshold &&
|
|
371
254
|
`the ${formatUsd(QUERY_COST_THRESHOLDS.perMonthUsd)}/month`,
|
|
372
255
|
].filter(Boolean);
|
|
373
256
|
|
|
@@ -383,11 +266,10 @@ export default defineAction({
|
|
|
383
266
|
args.runsPerMonth !== undefined
|
|
384
267
|
? "caller"
|
|
385
268
|
: reportSchedules > 0
|
|
386
|
-
?
|
|
269
|
+
? describeReportSchedules(reportSchedules, orgId)
|
|
387
270
|
: "none",
|
|
388
271
|
total: {
|
|
389
272
|
...total,
|
|
390
|
-
...totalFlags,
|
|
391
273
|
partial,
|
|
392
274
|
scope: onePanel ? "panel" : "dashboard",
|
|
393
275
|
},
|