@agent-native/core-corpus 0.210.0-nightly-20261019183757 → 0.210.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 +1 -1
- package/corpus/templates/analytics/.agents/skills/actions/SKILL.md +2 -2
- package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +16 -0
- package/corpus/templates/analytics/CHANGELOG.md +71 -0
- package/corpus/templates/analytics/actions/configure-first-party-events-model.ts +161 -0
- package/corpus/templates/analytics/changelog/2026-10-16-first-party-charts-can-read-a-scheduled-warehouse-events-mod.md +6 -0
- package/corpus/templates/analytics/server/lib/analytics-alerts.ts +9 -2
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +394 -36
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +17 -5
- package/corpus/templates/analytics/shared/sql-query-contract.ts +21 -0
- package/corpus/templates/assets/.agents/skills/actions/SKILL.md +2 -2
- package/corpus/templates/assets/CHANGELOG.md +6 -0
- package/corpus/templates/brain/.agents/skills/actions/SKILL.md +2 -2
- package/corpus/templates/brain/CHANGELOG.md +10 -0
- package/corpus/templates/calendar/.agents/skills/actions/SKILL.md +2 -2
- package/corpus/templates/calendar/CHANGELOG.md +6 -0
- package/corpus/templates/chat/.agents/skills/actions/SKILL.md +2 -2
- package/corpus/templates/clips/.agents/skills/actions/SKILL.md +2 -2
- package/corpus/templates/clips/CHANGELOG.md +78 -24
- package/corpus/templates/content/.agents/skills/actions/SKILL.md +2 -2
- package/corpus/templates/content/CHANGELOG.md +24 -0
- package/corpus/templates/crm/.agents/skills/actions/SKILL.md +2 -2
- package/corpus/templates/design/.agents/skills/actions/SKILL.md +2 -2
- package/corpus/templates/design/CHANGELOG.md +155 -1
- package/corpus/templates/dispatch/.agents/skills/actions/SKILL.md +2 -2
- package/corpus/templates/dispatch/CHANGELOG.md +6 -0
- package/corpus/templates/factory/.agents/skills/actions/SKILL.md +2 -2
- package/corpus/templates/factory/.agents/skills/review-prs/SKILL.md +44 -21
- package/corpus/templates/factory/CHANGELOG.md +4 -0
- package/corpus/templates/forms/.agents/skills/actions/SKILL.md +2 -2
- package/corpus/templates/mail/.agents/skills/actions/SKILL.md +2 -2
- package/corpus/templates/mail/CHANGELOG.md +12 -0
- package/corpus/templates/plan/.agents/skills/actions/SKILL.md +2 -2
- package/corpus/templates/plan/CHANGELOG.md +6 -0
- package/corpus/templates/slides/.agents/skills/actions/SKILL.md +2 -2
- package/corpus/templates/slides/CHANGELOG.md +24 -0
- package/corpus/templates/tasks/.agents/skills/actions/SKILL.md +2 -2
- package/package.json +1 -1
- package/corpus/templates/analytics/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
- package/corpus/templates/assets/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
- package/corpus/templates/brain/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
- package/corpus/templates/calendar/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
- package/corpus/templates/chat/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
- package/corpus/templates/clips/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
- package/corpus/templates/content/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
- package/corpus/templates/crm/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
- package/corpus/templates/design/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
- package/corpus/templates/dispatch/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
- package/corpus/templates/forms/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
- package/corpus/templates/mail/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
- package/corpus/templates/plan/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
- package/corpus/templates/slides/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
package/corpus/README.md
CHANGED
|
@@ -170,10 +170,10 @@ Everything else — CRUD, settings, search, list/detail reads, auth state, anyth
|
|
|
170
170
|
- **Do** keep one action, one job; document a reusable action (when to use it, key args, return fields to preserve) in `AGENTS.md` once it's called from outside one narrow screen; promote workflow-heavy actions (provider-backed, cross-app, MCP/A2A, multi-step) into a skill.
|
|
171
171
|
- **Do** use `fail(message, { errorCode, statusCode })` for user-friendly errors and import primitives from `@agent-native/core`(`/action`) instead of redefining them; use the core `upload-image` action or `uploadFile()` for durable images/files — never base64 into SQL, markdown, or action results.
|
|
172
172
|
- **Do** signal failure by throwing, using `fail()` for expected caller-readable failures. Never return `{ error: ... }` as a failure result: a normal return is treated as a successful action. Reserve bare errors for unexpected internal faults.
|
|
173
|
-
- **Do** use `fail()` rather than a bare `Error` for caller-readable failures. It carries the action's message, `errorCode`, and `details` to the browser; unexpected errors become a generic 500. Reserve bare throws for internal faults.
|
|
173
|
+
- **Do** use `fail()` rather than a bare `Error` for caller-readable failures. It carries the action's message, `errorCode`, and `details` to the browser and to agents; unexpected errors become a generic 500. Reserve bare throws for internal faults.
|
|
174
174
|
- **Do** give `fail()` the status that matches the cause: it defaults to `400`, and `useActionQuery` retries only `429`, `502`, `503`, and `504`. Mutations do not retry; use transient statuses only for retryable failures.
|
|
175
175
|
- **Do** render `actionErrorMessage(error) ?? yourCopy` in UI, never bare `error.message`. The message keeps an `Action <name> failed:` prefix that belongs in a console, so a toast built from it reads "Action update-brand-kit failed: That name is taken." The helper returns only what the action wrote, and `undefined` when nothing did (network drop, proxy HTML page), which is why the fallback is not optional.
|
|
176
|
-
- **Do** pass a real `errorCode` when the agent should branch on the failure rather than re-read it. Codes other than the default `action_failed` are appended to the tool result as `(errorCode: not_found)`, on both the in-app agent and MCP
|
|
176
|
+
- **Do** pass a real `errorCode` when the agent should branch on the failure rather than re-read it. Codes other than the default `action_failed` are appended to the tool result as `(errorCode: not_found)`, on both the in-app agent and MCP. `details` follow as an `Error details: {...}` JSON line, and as `structuredContent.error` over MCP, capped at 2,000 characters, so keep them to what the agent acts on next: ids, limits, field names. The status never reaches either.
|
|
177
177
|
- **Don't** re-export actions as REST — `/_agent-native/actions/:name` is already the REST surface; duplicating it under `/api/*` hides the operation from agents.
|
|
178
178
|
- **Don't** reach for provider integrations, `outputSchema`, `authorize`, `needsApproval`, or `_agentImages` without checking `references/` first — each has sharp edges covered there, not here.
|
|
179
179
|
|
|
@@ -110,6 +110,22 @@ BigQuery `insertId` values for retry-safe writes. After cutover, `/track`
|
|
|
110
110
|
writes and event queries use BigQuery; public-key metadata, derived exception
|
|
111
111
|
issues, and session-replay data remain in SQL.
|
|
112
112
|
|
|
113
|
+
After cutover, an org admin can point charts at a scheduled warehouse events
|
|
114
|
+
model with `configure-first-party-events-model`; run `check` first, which
|
|
115
|
+
dry-runs as the app and saves nothing. With a model set, a first-party query
|
|
116
|
+
reads `analytics_events` from the model only when its WHERE filters
|
|
117
|
+
`event_name` to named events (`= 'x'` or `IN (...)`, ANDed, no OR or NOT),
|
|
118
|
+
because the model has no `http.response` rows; its result carries
|
|
119
|
+
`eventsSource` and `sourceGeneration` (the model build it read). Unfiltered
|
|
120
|
+
queries, `SELECT *`, and queries that name `public_key_id` or `context`, read
|
|
121
|
+
`http.response`, or read `properties` other than through `JSON_VALUE`,
|
|
122
|
+
`JSON_EXTRACT_SCALAR`, `JSON_VALUE_ARRAY`, or `JSON_EXTRACT_STRING_ARRAY` stay
|
|
123
|
+
on the raw table, as do alerts and onboarding journeys;
|
|
124
|
+
`eventsSource.modelSkipped` says why. A model result trails the newest events
|
|
125
|
+
by up to one rebuild, so for the last few minutes give the model's age from
|
|
126
|
+
`get-analytics-source-generation` (a metadata read, no query) rather than
|
|
127
|
+
calling the count complete.
|
|
128
|
+
|
|
113
129
|
Example pageviews query for a local calendar day:
|
|
114
130
|
|
|
115
131
|
```sql
|
|
@@ -3,10 +3,51 @@
|
|
|
3
3
|
All notable user-facing changes to Agent-Native Analytics are documented here. Open it any
|
|
4
4
|
time from the command menu (Cmd+K → "What's new") or from Settings.
|
|
5
5
|
|
|
6
|
+
## 2026-10-19
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- Dashboard upgrades preserve saved history, custom data sources, and legacy edits.
|
|
11
|
+
- The agent sees the row breakdown open in your current browser tab.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Apps no longer keep server functions and databases warm by default; set `AGENT_NATIVE_ENABLE_KEEP_WARM=1` to opt in.
|
|
16
|
+
|
|
17
|
+
## 2026-10-17
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- Dashboard descriptions and custom blocks now open safe Markdown links in a new tab
|
|
22
|
+
|
|
23
|
+
### Improved
|
|
24
|
+
|
|
25
|
+
- Analytics distinguishes email-domain filters from creator self-view exclusions
|
|
26
|
+
- Analytics validates explicit day-first date ranges before querying dbt metrics
|
|
27
|
+
- Keyboard chart point lists show every visible metric with localized dates and values
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- Failed chart drilldowns no longer allow exporting stale cached rows
|
|
32
|
+
- Keep chart-point pagination stable and preserve aggregate result columns
|
|
33
|
+
- Keyboard chart controls now describe the chart points they open
|
|
34
|
+
|
|
6
35
|
## 2026-10-16
|
|
7
36
|
|
|
37
|
+
### Added
|
|
38
|
+
|
|
39
|
+
- Click a table row to see its numbers broken down by app, or another column, in a side panel.
|
|
40
|
+
- Connect a personal Sigma account to import its dashboards
|
|
41
|
+
- First-party charts can read a scheduled warehouse events model instead of the raw event table, so repeat loads reuse BigQuery's cache between model rebuilds.
|
|
42
|
+
|
|
8
43
|
### Improved
|
|
9
44
|
|
|
45
|
+
- Analytics preserves requested organization grain and guides supported ratio query retries
|
|
46
|
+
- Charts that compute their window from today's date can now reuse BigQuery's cached results within the day instead of rerunning the full query on every load.
|
|
47
|
+
- Dashboard callouts and descriptions support Markdown links
|
|
48
|
+
- Dashboard filters show your selection immediately while charts update.
|
|
49
|
+
- Dashboard panels can be resized by dragging their edges.
|
|
50
|
+
- Keyboard chart point lists now paginate and show each value
|
|
10
51
|
- Dashboard freshness stays in the title row, with compact update times on wide cards and a clock on narrow cards.
|
|
11
52
|
- Analytics preserves metric breakdowns across continued queries and keeps partial totals clearly labeled.
|
|
12
53
|
- Analytics query errors now explain safe next steps without exposing raw SQL or backend details
|
|
@@ -14,6 +55,29 @@ time from the command menu (Cmd+K → "What's new") or from Settings.
|
|
|
14
55
|
|
|
15
56
|
### Fixed
|
|
16
57
|
|
|
58
|
+
- Alert polls enforce a query byte limit and report evaluations that exceed it as errors.
|
|
59
|
+
- All-time dashboard panels keep their historical label when you edit a title or description
|
|
60
|
+
- Analytics answers owner-defined dbt metrics through the Semantic Layer and say when dbt is not ready, instead of reading warehouse tables as the approved definition.
|
|
61
|
+
- Analytics can now count its own first-party events strictly from Postgres when you rule out BigQuery, and refuses instead of quietly querying BigQuery when the data only lives there
|
|
62
|
+
- Analytics recognizes more natural date range formats, including day-first and same-month shorthand.
|
|
63
|
+
- Chart drilldowns are accessible from the keyboard
|
|
64
|
+
- Clarified dbt setup errors when organization context is unavailable
|
|
65
|
+
- Month and quarter comparisons now stay aligned with their named periods, including requests that use “with”.
|
|
66
|
+
- Dashboard event colors render consistently in email reports
|
|
67
|
+
- Dashboard filters show when navigation temporarily prevents changes and close open menus safely
|
|
68
|
+
- Dashboard panel sizes stay put when multiple edits save together
|
|
69
|
+
- Date-based metric comparisons now honor both requested periods.
|
|
70
|
+
- First-party Analytics health checks bound BigQuery reads and identify recent activity counts.
|
|
71
|
+
- First-party dashboards apply the selected time range earlier to avoid unnecessary BigQuery event scans.
|
|
72
|
+
- Fixed a dashboard crash caused by an invalid panel description.
|
|
73
|
+
- Fixed dashboard panels reverting to stale SQL, repeating query validation while resizing, and lacking a reset to content-based height.
|
|
74
|
+
- Formatted negative numbers stay numeric in CSV exports, and funnel drilldowns remain accessible to screen readers
|
|
75
|
+
- Funnel outcome stages now open drilldown details, and CSV exports preserve negative numeric values.
|
|
76
|
+
- Markdown links in chart descriptions and custom blocks open external pages in a new tab.
|
|
77
|
+
- Organization breakdowns now distinguish a root organization from its individual organizations
|
|
78
|
+
- Ratio recovery preserves fractional results for integer data
|
|
79
|
+
- Resized dashboard panels keep their size through concurrent edits
|
|
80
|
+
- Resizing a dashboard panel keeps its size when other edits are saved.
|
|
17
81
|
- Explorer flags incomplete query results
|
|
18
82
|
- Chart drilldowns preserve the selected series and report lookup or export failures safely.
|
|
19
83
|
- CSV downloads show when results are limited to the first 500 records
|
|
@@ -21,6 +85,10 @@ time from the command menu (Cmd+K → "What's new") or from Settings.
|
|
|
21
85
|
- Normalize typed date literals in first-party BigQuery queries
|
|
22
86
|
- Treat incomplete schema and truncated BigQuery results as partial evidence
|
|
23
87
|
|
|
88
|
+
### Changed
|
|
89
|
+
|
|
90
|
+
- Built-in dashboards stay read-only and update with each release; Duplicate to customize makes your own editable copy
|
|
91
|
+
|
|
24
92
|
## 2026-10-15
|
|
25
93
|
|
|
26
94
|
### Added
|
|
@@ -33,6 +101,7 @@ time from the command menu (Cmd+K → "What's new") or from Settings.
|
|
|
33
101
|
|
|
34
102
|
### Improved
|
|
35
103
|
|
|
104
|
+
- Collapsed Analytics navigation icons stay centered when the list is short
|
|
36
105
|
- Active-user answers distinguish Builder product activity from Agent-Native app opens, and dbt metric follow-ups keep their original filters.
|
|
37
106
|
- Analytics keeps ambiguous active-user questions open across product activity and membership sources, and validates dbt metric date ranges before reporting results
|
|
38
107
|
- Analytics keeps ambiguous active-user searches open across Builder activity, organization membership, and app opens until you choose the population.
|
|
@@ -47,6 +116,7 @@ time from the command menu (Cmd+K → "What's new") or from Settings.
|
|
|
47
116
|
|
|
48
117
|
### Fixed
|
|
49
118
|
|
|
119
|
+
- Collapsed navigation icons stay centered in the Analytics sidebar
|
|
50
120
|
- Dashboard query costs and refresh confirmations reflect actual execution, and disconnected sources clear saved rows
|
|
51
121
|
- Dashboard results disappear when access is withdrawn, and Explorer query costs exclude cached scans
|
|
52
122
|
- Analytics now distinguishes empty and partial dbt metric results from complete answers.
|
|
@@ -264,6 +334,7 @@ time from the command menu (Cmd+K → "What's new") or from Settings.
|
|
|
264
334
|
|
|
265
335
|
### Improved
|
|
266
336
|
|
|
337
|
+
- AI tools connected over MCP now get a checked result format for the analytics health check and the connected database tables list
|
|
267
338
|
- Session replays now show the date and time the session started, and the Sessions list shows start times in a shorter, easier-to-scan format.
|
|
268
339
|
- Agent dashboard edits are now checked against the live chart before they save, so the agent no longer reports a chart change that did not happen.
|
|
269
340
|
|
|
@@ -0,0 +1,161 @@
|
|
|
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 { requireAnalyticsAdminContext } from "../server/lib/db-admin-connections.js";
|
|
10
|
+
import {
|
|
11
|
+
firstPartyEventsModelProjection,
|
|
12
|
+
getFirstPartyAnalyticsBackend,
|
|
13
|
+
saveFirstPartyEventsModelTable,
|
|
14
|
+
} from "../server/lib/first-party-analytics-backend.js";
|
|
15
|
+
import { resolveSourceGeneration } from "../server/lib/source-generation.js";
|
|
16
|
+
|
|
17
|
+
const TABLE_ID = /^[a-z][a-z0-9-]{4,61}[a-z0-9]\.[A-Za-z_]\w*\.[A-Za-z_]\w*$/;
|
|
18
|
+
|
|
19
|
+
type ModelCheck =
|
|
20
|
+
| {
|
|
21
|
+
status: "ok";
|
|
22
|
+
table: string;
|
|
23
|
+
modifiedAt: string;
|
|
24
|
+
ageMs: number;
|
|
25
|
+
stale: boolean;
|
|
26
|
+
recentDayBytes: number | null;
|
|
27
|
+
fullScanBytes: number | null;
|
|
28
|
+
}
|
|
29
|
+
| { status: "unavailable"; table: string; reason: string; message?: string };
|
|
30
|
+
|
|
31
|
+
function utcDateDaysAgo(days: number): string {
|
|
32
|
+
return new Date(Date.now() - days * 86_400_000).toISOString().slice(0, 10);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Dry runs only: BigQuery plans the query and reports the bytes it would
|
|
36
|
+
// scan, but runs and bills nothing.
|
|
37
|
+
async function checkModel(
|
|
38
|
+
table: string,
|
|
39
|
+
signal?: AbortSignal,
|
|
40
|
+
): Promise<ModelCheck> {
|
|
41
|
+
const generation = await resolveSourceGeneration([table], {
|
|
42
|
+
force: true,
|
|
43
|
+
signal,
|
|
44
|
+
});
|
|
45
|
+
if (generation.status === "unavailable") {
|
|
46
|
+
return { status: "unavailable", table, reason: generation.reason };
|
|
47
|
+
}
|
|
48
|
+
const projection = `(${firstPartyEventsModelProjection(table)}) AS analytics_events`;
|
|
49
|
+
const recent = await dryRunQuerySchema(
|
|
50
|
+
`SELECT COUNT(*) AS events FROM ${projection} WHERE event_date >= DATE '${utcDateDaysAgo(1)}'`,
|
|
51
|
+
{ signal },
|
|
52
|
+
);
|
|
53
|
+
if (recent.error) {
|
|
54
|
+
return {
|
|
55
|
+
status: "unavailable",
|
|
56
|
+
table,
|
|
57
|
+
reason: "query_denied",
|
|
58
|
+
message: recent.error,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
// Eligible chart SQL need not bound event_date, so a model that refuses an
|
|
62
|
+
// unbounded read (a required partition filter, say) would fail those charts.
|
|
63
|
+
const full = await dryRunQuerySchema(
|
|
64
|
+
`SELECT COUNT(*) AS events FROM ${projection}`,
|
|
65
|
+
{ signal },
|
|
66
|
+
);
|
|
67
|
+
if (full.error) {
|
|
68
|
+
return {
|
|
69
|
+
status: "unavailable",
|
|
70
|
+
table,
|
|
71
|
+
reason: "full_read_denied",
|
|
72
|
+
message: full.error,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
status: "ok",
|
|
77
|
+
table,
|
|
78
|
+
modifiedAt: generation.oldestModifiedAt,
|
|
79
|
+
ageMs: generation.ageMs,
|
|
80
|
+
stale: generation.stale,
|
|
81
|
+
recentDayBytes: recent.totalBytesProcessed ?? null,
|
|
82
|
+
fullScanBytes: full.totalBytesProcessed ?? null,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export default defineAction({
|
|
87
|
+
// set and clear replace the org-wide model every first-party chart reads.
|
|
88
|
+
mcpAnnotations: {
|
|
89
|
+
readOnlyHint: false,
|
|
90
|
+
destructiveHint: true,
|
|
91
|
+
openWorldHint: true,
|
|
92
|
+
},
|
|
93
|
+
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 a one-day and a full read would scan; 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
|
+
schema: z.object({
|
|
96
|
+
mode: z.enum(["check", "set", "clear"]),
|
|
97
|
+
table: z
|
|
98
|
+
.string()
|
|
99
|
+
.trim()
|
|
100
|
+
.optional()
|
|
101
|
+
.describe(
|
|
102
|
+
"Model table as project.dataset.table. Required for set; check defaults to the configured model.",
|
|
103
|
+
),
|
|
104
|
+
}),
|
|
105
|
+
needsApproval: ({ mode }) => mode !== "check",
|
|
106
|
+
run: async ({ mode, table }, context) => {
|
|
107
|
+
const userEmail = getRequestUserEmail();
|
|
108
|
+
if (!userEmail) {
|
|
109
|
+
fail("Sign in to configure the Analytics model", {
|
|
110
|
+
errorCode: "unauthenticated",
|
|
111
|
+
statusCode: 401,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
const orgId = getRequestOrgId();
|
|
115
|
+
if (!orgId) {
|
|
116
|
+
fail("An active organization is required to configure the model", {
|
|
117
|
+
errorCode: "organization_required",
|
|
118
|
+
statusCode: 403,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
const signal = context?.signal;
|
|
122
|
+
const scope =
|
|
123
|
+
mode === "check"
|
|
124
|
+
? { userEmail, orgId }
|
|
125
|
+
: await requireAnalyticsAdminContext({ userEmail, orgId });
|
|
126
|
+
|
|
127
|
+
if (mode === "clear") {
|
|
128
|
+
await saveFirstPartyEventsModelTable(scope, null);
|
|
129
|
+
return { status: "cleared" as const };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const target =
|
|
133
|
+
table ||
|
|
134
|
+
(mode === "check"
|
|
135
|
+
? (await getFirstPartyAnalyticsBackend(scope, signal)).modelTable
|
|
136
|
+
: null);
|
|
137
|
+
if (!target) {
|
|
138
|
+
fail("Name the model table as project.dataset.table", {
|
|
139
|
+
errorCode: "model_table_required",
|
|
140
|
+
statusCode: 400,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if (!TABLE_ID.test(target)) {
|
|
144
|
+
fail("The model table must be project.dataset.table", {
|
|
145
|
+
errorCode: "invalid_model_table",
|
|
146
|
+
statusCode: 400,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const check = await checkModel(target, signal);
|
|
151
|
+
if (mode === "check") return check;
|
|
152
|
+
if (check.status !== "ok") {
|
|
153
|
+
fail(
|
|
154
|
+
`The app cannot read ${target} (${check.reason}); the model was not saved`,
|
|
155
|
+
{ errorCode: "model_unreadable", statusCode: 424 },
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
await saveFirstPartyEventsModelTable(scope, target);
|
|
159
|
+
return { ...check, status: "saved" as const };
|
|
160
|
+
},
|
|
161
|
+
});
|
|
@@ -153,6 +153,13 @@ const DEFAULT_AGENT_CHAT_STUCK_ALERT_WINDOW_MINUTES = 10;
|
|
|
153
153
|
const DEFAULT_AGENT_CHAT_STUCK_ALERT_COOLDOWN_MINUTES = 60;
|
|
154
154
|
const DEFAULT_ALERT_SCOPE_PAGE_SIZE = 500;
|
|
155
155
|
const BIGQUERY_ALERT_PAGE_SIZE = 5_000;
|
|
156
|
+
|
|
157
|
+
// Alerts watch the last few minutes, which an hourly events model has not
|
|
158
|
+
// built yet.
|
|
159
|
+
const ALERT_QUERY_OPTIONS = {
|
|
160
|
+
eventsSource: "raw",
|
|
161
|
+
maxBytesBilled: ANALYTICS_ALERT_MAX_BYTES_BILLED,
|
|
162
|
+
} as const;
|
|
156
163
|
const ALERT_RULE_DEFAULTS_KEY = "analytics-alert-rule-defaults";
|
|
157
164
|
|
|
158
165
|
function safeJsonParse<T>(raw: unknown, fallback: T): T {
|
|
@@ -1178,7 +1185,7 @@ export async function evaluateBigQueryAnalyticsAlertBatch(
|
|
|
1178
1185
|
queryFirstPartyAnalytics(
|
|
1179
1186
|
query,
|
|
1180
1187
|
{ userEmail: first.ownerEmail, orgId: first.orgId },
|
|
1181
|
-
|
|
1188
|
+
ALERT_QUERY_OPTIONS,
|
|
1182
1189
|
),
|
|
1183
1190
|
);
|
|
1184
1191
|
if (result.truncated || result.rows.length !== 1) {
|
|
@@ -1543,7 +1550,7 @@ async function loadCandidateEvents(
|
|
|
1543
1550
|
const result = await queryFirstPartyAnalytics(
|
|
1544
1551
|
buildBigQueryAlertQuery(rule, windowStart, windowEnd, cursor),
|
|
1545
1552
|
{ userEmail: rule.ownerEmail, orgId: rule.orgId },
|
|
1546
|
-
|
|
1553
|
+
ALERT_QUERY_OPTIONS,
|
|
1547
1554
|
);
|
|
1548
1555
|
if (result.truncated) {
|
|
1549
1556
|
throw new Error(
|