@agent-native/core 0.141.5 → 0.141.7
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 +3 -14
- package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +20 -8
- package/corpus/templates/analytics/.agents/skills/prometheus/SKILL.md +5 -5
- package/corpus/templates/analytics/AGENTS.md +2 -2
- package/corpus/templates/analytics/actions/compose-dashboard.ts +1 -1
- package/corpus/templates/analytics/actions/get-first-party-analytics-health.ts +15 -1
- package/corpus/templates/analytics/actions/migrate-first-party-analytics-to-bigquery.ts +135 -0
- package/corpus/templates/analytics/actions/navigate.ts +1 -1
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +2 -2
- package/corpus/templates/analytics/actions/update-dashboard.ts +0 -1
- package/corpus/templates/analytics/actions/view-screen.ts +0 -20
- package/corpus/templates/analytics/app/components/layout/Header.tsx +0 -1
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +5 -5
- package/corpus/templates/analytics/app/i18n-data.ts +330 -90
- package/corpus/templates/analytics/app/pages/Agents.tsx +0 -3
- package/corpus/templates/analytics/app/pages/DataSources.tsx +66 -30
- package/corpus/templates/analytics/app/pages/Settings.tsx +22 -43
- package/corpus/templates/analytics/app/pages/settings/settings-search.ts +23 -20
- package/corpus/templates/analytics/app/routes/extensions._index.tsx +1 -1
- package/corpus/templates/analytics/app/routes/settings.$.tsx +1 -0
- package/corpus/templates/analytics/app/routes/team.tsx +1 -1
- package/corpus/templates/analytics/changelog/2026-08-05-historical-analytics-rollups-now-repair-automatically-after-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-05-settings-no-longer-includes-an-unnecessary-about-section.md +6 -0
- package/corpus/templates/analytics/docs/schemas/first-party-analytics.md +1 -1
- package/corpus/templates/analytics/netlify.toml +4 -0
- package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +109 -0
- package/corpus/templates/analytics/scripts/first-party-analytics-bigquery.sql +104 -0
- package/corpus/templates/analytics/server/jobs/analytics-rollup-backfill.ts +342 -0
- package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +0 -2
- package/corpus/templates/analytics/server/lib/analytics-rollup-lock.ts +8 -0
- package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +11 -29
- package/corpus/templates/analytics/server/lib/dashboards-store.ts +55 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +1058 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics-health.ts +151 -46
- package/corpus/templates/analytics/server/lib/first-party-analytics-rollups.ts +15 -9
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +59 -17
- package/corpus/templates/analytics/server/lib/real-data-actions.ts +1 -3
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +2 -4
- package/corpus/templates/analytics/server/plugins/analytics-rollup-backfill-jobs.ts +61 -0
- package/corpus/templates/analytics/server/plugins/db.ts +58 -187
- package/corpus/templates/analytics/server/routes/api/analytics-rollups/backfill.post.ts +53 -0
- package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +8 -6
- package/corpus/templates/assets/.agents/skills/library-management/SKILL.md +16 -1
- package/corpus/templates/assets/AGENTS.md +1 -0
- package/corpus/templates/assets/actions/_helpers.ts +12 -0
- package/corpus/templates/assets/actions/generate-image.ts +9 -3
- package/corpus/templates/assets/actions/import-style-from-url.ts +115 -0
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +3 -1
- package/corpus/templates/assets/app/routes/extensions._index.tsx +1 -1
- package/corpus/templates/assets/app/routes/settings.$.tsx +1 -0
- package/corpus/templates/assets/app/routes/settings.tsx +5 -0
- package/corpus/templates/assets/app/routes/team.tsx +1 -1
- package/corpus/templates/assets/changelog/2026-08-05-brand-kits-can-import-a-website-s-hydrated-visual-language-a.md +6 -0
- package/corpus/templates/assets/changelog/2026-08-05-generated-images-now-hand-off-embeddable-previews-to-other-w.md +6 -0
- package/corpus/templates/assets/server/lib/generation.ts +58 -0
- package/corpus/templates/assets/shared/api.ts +12 -0
- package/corpus/templates/brain/app/routes/extensions._index.tsx +1 -1
- package/corpus/templates/brain/app/routes/team.tsx +1 -1
- package/corpus/templates/brain/app/routes.ts +1 -1
- package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +3 -1
- package/corpus/templates/calendar/app/routes/_app.extensions._index.tsx +1 -1
- package/corpus/templates/calendar/app/routes/_app.settings.$.tsx +1 -0
- package/corpus/templates/calendar/app/routes/_app.team.tsx +1 -1
- package/corpus/templates/chat/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/chat/app/hooks/use-navigation-state.ts +1 -1
- package/corpus/templates/chat/app/root.tsx +1 -1
- package/corpus/templates/chat/app/routes/agent.tsx +7 -7
- package/corpus/templates/chat/app/routes/extensions._index.tsx +1 -1
- package/corpus/templates/chat/app/routes/settings.$.tsx +1 -0
- package/corpus/templates/chat/app/routes/team.tsx +1 -1
- package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -1
- package/corpus/templates/clips/app/components/player/transcript-panel.tsx +1 -1
- package/corpus/templates/clips/app/components/recorder/storage-setup-card.tsx +1 -1
- package/corpus/templates/clips/app/routes/_app.extensions._index.tsx +1 -1
- package/corpus/templates/clips/app/routes/_app.settings.$.tsx +1 -0
- package/corpus/templates/clips/changelog/2026-08-05-clips-overlays-now-use-clean-surfaces-without-clipped-shadow.md +6 -0
- package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +2 -2
- package/corpus/templates/clips/desktop/src/styles.css +3 -54
- package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +3 -3
- package/corpus/templates/content/app/routes/_app.extensions._index.tsx +1 -1
- package/corpus/templates/content/app/routes/_app.settings.$.tsx +1 -0
- package/corpus/templates/content/app/routes/_app.team.tsx +1 -1
- package/corpus/templates/content/e2e/global-setup.ts +1 -1
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +6 -1
- package/corpus/templates/design/actions/analyze-brand-assets.ts +7 -7
- package/corpus/templates/design/actions/get-design-system.ts +3 -1
- package/corpus/templates/design/actions/import-from-url.ts +6 -5
- package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +2 -2
- package/corpus/templates/design/app/routes/extensions._index.tsx +1 -1
- package/corpus/templates/design/app/routes/settings.$.tsx +1 -0
- package/corpus/templates/design/app/routes/team.tsx +1 -1
- package/corpus/templates/design/changelog/2026-08-05-missing-design-systems-now-return-a-clear-not-found-response.md +6 -0
- package/corpus/templates/design/changelog/2026-08-05-website-url-imports-now-capture-hydrated-browser-computed-de.md +6 -0
- package/corpus/templates/design/e2e/global-setup.ts +1 -1
- package/corpus/templates/dispatch/app/hooks/use-navigation-state.ts +1 -1
- package/corpus/templates/dispatch/app/routes/settings.$.tsx +1 -0
- package/corpus/templates/dispatch/app/routes/team.tsx +1 -1
- package/corpus/templates/factory/app/hooks/use-navigation-state.ts +1 -1
- package/corpus/templates/factory/app/routes/extensions._index.tsx +1 -1
- package/corpus/templates/factory/app/routes/settings.$.tsx +1 -0
- package/corpus/templates/factory/app/routes/team.tsx +1 -1
- package/corpus/templates/forms/app/routes/_app.extensions._index.tsx +1 -1
- package/corpus/templates/forms/app/routes/_app.settings.$.tsx +1 -0
- package/corpus/templates/forms/app/routes/_app.team.tsx +1 -1
- package/corpus/templates/forms/shared/navigation.ts +1 -1
- package/corpus/templates/macros/app/routes/extensions._index.tsx +1 -1
- package/corpus/templates/macros/app/routes/settings.$.tsx +1 -0
- package/corpus/templates/macros/app/routes/team.tsx +1 -1
- package/corpus/templates/mail/app/routes/extensions._index.tsx +1 -1
- package/corpus/templates/mail/app/routes/settings.$.tsx +1 -0
- package/corpus/templates/plan/app/hooks/use-navigation-state.ts +1 -1
- package/corpus/templates/plan/app/routes/extensions._index.tsx +1 -1
- package/corpus/templates/plan/app/routes/settings.$.tsx +1 -0
- package/corpus/templates/plan/app/routes/team.tsx +1 -1
- package/corpus/templates/slides/.agents/skills/design-systems/SKILL.md +3 -1
- package/corpus/templates/slides/actions/analyze-brand-assets.ts +7 -7
- package/corpus/templates/slides/actions/import-from-url.ts +6 -6
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +2 -2
- package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +1 -1
- package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +3 -0
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +1 -1
- package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +9 -3
- package/corpus/templates/slides/app/context/DeckContext.tsx +71 -20
- package/corpus/templates/slides/app/global.css +6 -0
- package/corpus/templates/slides/app/i18n/en-US.ts +5 -5
- package/corpus/templates/slides/app/lib/create-deck-generation.ts +9 -5
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +17 -0
- package/corpus/templates/slides/app/pages/Index.tsx +16 -5
- package/corpus/templates/slides/app/routes/extensions._index.tsx +1 -1
- package/corpus/templates/slides/app/routes/settings.$.tsx +1 -0
- package/corpus/templates/slides/app/routes/team.tsx +1 -1
- package/corpus/templates/slides/changelog/2026-08-05-failed-deck-creation-and-reference-uploads-now-recover-clean.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-05-new-presentation-prompts-now-clearly-describe-presentation-g.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-05-slides-recover-completed-generation-results-after-a-sync-hic.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-05-website-url-imports-now-capture-hydrated-browser-computed-st.md +6 -0
- package/corpus/templates/tasks/app/components/layout/Header.tsx +3 -1
- package/corpus/templates/tasks/app/routes/extensions._index.tsx +1 -1
- package/corpus/templates/tasks/app/routes/settings.$.tsx +1 -0
- package/dist/agent/engine/ai-sdk-engine.js +49 -3
- package/dist/agent/production-agent.d.ts +1 -0
- package/dist/agent/production-agent.js +8 -0
- package/dist/agent/types.d.ts +7 -0
- package/dist/client/AssistantChat.js +1 -1
- package/dist/client/agent-chat-adapter.d.ts +1 -1
- package/dist/client/agent-chat-adapter.js +30 -3
- package/dist/client/agent-page/AgentWorkspaceContent.d.ts +1 -1
- package/dist/client/agent-page/AgentWorkspaceContent.js +13 -13
- package/dist/client/chat/run-recovery.d.ts +2 -1
- package/dist/client/chat/run-recovery.js +14 -3
- package/dist/client/embed-auth.js +6 -2
- package/dist/client/error-format.d.ts +1 -0
- package/dist/client/error-format.js +16 -2
- package/dist/client/integrations/IntegrationsPanel.js +40 -6
- package/dist/client/org/OrgSwitcher.js +2 -2
- package/dist/client/org/TeamPage.js +92 -72
- package/dist/client/require-session.d.ts +1 -1
- package/dist/client/require-session.js +3 -3
- package/dist/client/resources/use-mcp-servers.d.ts +5 -0
- package/dist/client/resources/use-mcp-servers.js +36 -0
- package/dist/client/settings/AccountSettingsCard.js +40 -35
- package/dist/client/settings/AutomationsSection.js +1 -1
- package/dist/client/settings/DemoModeSection.d.ts +3 -1
- package/dist/client/settings/DemoModeSection.js +4 -1
- package/dist/client/settings/ExtensionsSettingsContent.js +1 -1
- package/dist/client/settings/SchedulingTimezoneField.d.ts +3 -1
- package/dist/client/settings/SchedulingTimezoneField.js +9 -5
- package/dist/client/settings/SettingsPanel.d.ts +4 -1
- package/dist/client/settings/SettingsPanel.js +21 -21
- package/dist/client/settings/SettingsTabsPage.d.ts +2 -2
- package/dist/client/settings/SettingsTabsPage.js +98 -45
- package/dist/client/settings/VoiceTranscriptionSection.js +6 -1
- package/dist/client/settings/useSettingsPanelController.js +47 -8
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/ingestion/website.d.ts +30 -0
- package/dist/localization/default-messages.d.ts +5 -0
- package/dist/localization/default-messages.js +5 -0
- package/dist/mcp-client/oauth-routes.js +1 -1
- package/dist/mcp-client/routes.d.ts +1 -0
- package/dist/mcp-client/routes.js +49 -0
- package/dist/navigation/index.js +16 -5
- package/dist/observability/routes.d.ts +3 -3
- package/dist/provider-api/actions/custom-provider-registration.d.ts +13 -13
- package/dist/provider-api/actions/provider-api.d.ts +11 -11
- package/dist/scripts/call-agent.js +11 -0
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth.js +9 -7
- package/dist/server/identity-sso.js +2 -1
- package/dist/server/onboarding-html.js +57 -34
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/server/workspace-provider-oauth.js +1 -1
- package/dist/setup-connections/onboarding.js +1 -1
- package/dist/shared/index.d.ts +1 -1
- package/dist/shared/index.js +1 -1
- package/dist/shared/sign-in-journey.d.ts +5 -2
- package/dist/shared/sign-in-journey.js +10 -4
- package/dist/templates/chat/app/components/layout/Layout.tsx +1 -1
- package/dist/templates/chat/app/hooks/use-navigation-state.ts +1 -1
- package/dist/templates/chat/app/root.tsx +1 -1
- package/dist/templates/chat/app/routes/agent.tsx +7 -7
- package/dist/templates/chat/app/routes/extensions._index.tsx +1 -1
- package/dist/templates/chat/app/routes/settings.$.tsx +1 -0
- package/dist/templates/chat/app/routes/team.tsx +1 -1
- package/dist/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +37 -6
- package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +4 -2
- package/docs/content/authentication.mdx +4 -5
- package/docs/content/template-analytics-developers.mdx +1 -1
- package/docs/content/template-calendar-developers.mdx +1 -1
- package/docs/content/template-chat-developers.mdx +2 -2
- package/docs/content/template-content-developers.mdx +1 -1
- package/docs/content/template-mail-developers.mdx +1 -1
- package/docs/content/template-plan-developers.mdx +1 -1
- package/docs/content/toolkit-command-navigation.mdx +6 -6
- package/docs/content/toolkit-org-team.mdx +1 -1
- package/docs/content/toolkit-settings.mdx +8 -8
- package/docs/content/toolkit-setup-connections.mdx +1 -1
- package/package.json +1 -1
- package/src/templates/chat/app/components/layout/Layout.tsx +1 -1
- package/src/templates/chat/app/hooks/use-navigation-state.ts +1 -1
- package/src/templates/chat/app/root.tsx +1 -1
- package/src/templates/chat/app/routes/agent.tsx +7 -7
- package/src/templates/chat/app/routes/extensions._index.tsx +1 -1
- package/src/templates/chat/app/routes/settings.$.tsx +1 -0
- package/src/templates/chat/app/routes/team.tsx +1 -1
- package/src/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +37 -6
- package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +4 -2
- package/corpus/templates/analytics/.agents/skills/dashboard-management/references/template-catalog-and-demo.md +0 -62
- package/corpus/templates/analytics/actions/install-dashboard-template.ts +0 -369
- package/corpus/templates/analytics/actions/list-dashboard-templates.ts +0 -47
- package/corpus/templates/analytics/actions/list-native-v2-dashboard-templates.ts +0 -34
- package/corpus/templates/analytics/app/routes/catalog.tsx +0 -240
- package/corpus/templates/analytics/changelog/2026-08-05-historical-analytics-rollups-stay-complete-and-atomic.md +0 -6
- package/corpus/templates/analytics/changelog/2026-08-05-historical-rollups-stay-complete-while-live-events-arrive.md +0 -6
- package/src/templates/default/actions/navigate.spec.ts +0 -53
|
@@ -4,6 +4,10 @@ import { and, gte, inArray, lte } from "drizzle-orm";
|
|
|
4
4
|
|
|
5
5
|
import { getDb, schema } from "../db/index.js";
|
|
6
6
|
import { hasCredential } from "./credentials.js";
|
|
7
|
+
import {
|
|
8
|
+
getFirstPartyAnalyticsBackend,
|
|
9
|
+
getFirstPartyAnalyticsBigQueryMetrics,
|
|
10
|
+
} from "./first-party-analytics-backend.js";
|
|
7
11
|
import type { AnalyticsScope } from "./first-party-analytics.js";
|
|
8
12
|
|
|
9
13
|
export const FIRST_PARTY_ANALYTICS_PRESSURE_THRESHOLDS = {
|
|
@@ -13,11 +17,33 @@ export const FIRST_PARTY_ANALYTICS_PRESSURE_THRESHOLDS = {
|
|
|
13
17
|
recommendMaxQueryMs: 30_000,
|
|
14
18
|
} as const;
|
|
15
19
|
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
const EXTERNAL_BACKEND_DEFINITIONS = [
|
|
21
|
+
{
|
|
22
|
+
id: "bigquery",
|
|
23
|
+
label: "BigQuery",
|
|
24
|
+
role: "warehouse",
|
|
25
|
+
requiredKeys: [
|
|
26
|
+
"GOOGLE_APPLICATION_CREDENTIALS_JSON",
|
|
27
|
+
"BIGQUERY_PROJECT_ID",
|
|
28
|
+
],
|
|
29
|
+
setupLink: buildDeepLink({
|
|
30
|
+
app: "analytics",
|
|
31
|
+
view: "data-sources",
|
|
32
|
+
to: "/data-sources?source=bigquery",
|
|
33
|
+
}),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: "amplitude",
|
|
37
|
+
label: "Amplitude",
|
|
38
|
+
role: "product-analytics",
|
|
39
|
+
requiredKeys: ["AMPLITUDE_API_KEY", "AMPLITUDE_SECRET_KEY"],
|
|
40
|
+
setupLink: buildDeepLink({
|
|
41
|
+
app: "analytics",
|
|
42
|
+
view: "data-sources",
|
|
43
|
+
to: "/data-sources?source=amplitude",
|
|
44
|
+
}),
|
|
45
|
+
},
|
|
46
|
+
] as const;
|
|
21
47
|
|
|
22
48
|
export type FirstPartyAnalyticsQueryClass =
|
|
23
49
|
| "raw-events"
|
|
@@ -39,9 +65,29 @@ export type FirstPartyAnalyticsRecommendation =
|
|
|
39
65
|
| "connect_bigquery"
|
|
40
66
|
| "use_bigquery";
|
|
41
67
|
|
|
68
|
+
export type FirstPartyAnalyticsExternalBackendRecommendation =
|
|
69
|
+
| "none"
|
|
70
|
+
| "connect"
|
|
71
|
+
| "use"
|
|
72
|
+
| "unknown";
|
|
73
|
+
|
|
74
|
+
export type FirstPartyAnalyticsBackendId =
|
|
75
|
+
(typeof EXTERNAL_BACKEND_DEFINITIONS)[number]["id"];
|
|
76
|
+
|
|
77
|
+
export interface FirstPartyAnalyticsBackendStatus {
|
|
78
|
+
id: FirstPartyAnalyticsBackendId;
|
|
79
|
+
label: string;
|
|
80
|
+
role: (typeof EXTERNAL_BACKEND_DEFINITIONS)[number]["role"];
|
|
81
|
+
configured: boolean | null;
|
|
82
|
+
missingRequiredKeys: string[];
|
|
83
|
+
setupLink: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
42
86
|
export interface FirstPartyAnalyticsHealth {
|
|
43
87
|
status: FirstPartyAnalyticsHealthStatus;
|
|
44
88
|
recommendation: FirstPartyAnalyticsRecommendation;
|
|
89
|
+
externalBackendRecommendation: FirstPartyAnalyticsExternalBackendRecommendation;
|
|
90
|
+
externalBackends: FirstPartyAnalyticsBackendStatus[];
|
|
45
91
|
reasons: Array<"event_volume" | "slow_queries" | "query_timeout">;
|
|
46
92
|
observedAt: string;
|
|
47
93
|
metrics: {
|
|
@@ -56,11 +102,8 @@ export interface FirstPartyAnalyticsHealth {
|
|
|
56
102
|
maxQueryDurationMs24h: number;
|
|
57
103
|
};
|
|
58
104
|
thresholds: typeof FIRST_PARTY_ANALYTICS_PRESSURE_THRESHOLDS;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
missingRequiredKeys: string[];
|
|
62
|
-
setupLink: string;
|
|
63
|
-
};
|
|
105
|
+
/** Kept for clients that still read the original BigQuery-only field. */
|
|
106
|
+
bigQuery: FirstPartyAnalyticsBackendStatus;
|
|
64
107
|
}
|
|
65
108
|
|
|
66
109
|
interface QueryPressureEvent {
|
|
@@ -199,18 +242,13 @@ function dateSpanDays(
|
|
|
199
242
|
return Math.floor((last - first) / 86_400_000) + 1;
|
|
200
243
|
}
|
|
201
244
|
|
|
202
|
-
async function
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}> {
|
|
207
|
-
const requiredKeys = [
|
|
208
|
-
"GOOGLE_APPLICATION_CREDENTIALS_JSON",
|
|
209
|
-
"BIGQUERY_PROJECT_ID",
|
|
210
|
-
];
|
|
245
|
+
async function externalBackendStatus(
|
|
246
|
+
scope: AnalyticsScope,
|
|
247
|
+
definition: (typeof EXTERNAL_BACKEND_DEFINITIONS)[number],
|
|
248
|
+
): Promise<FirstPartyAnalyticsBackendStatus> {
|
|
211
249
|
try {
|
|
212
250
|
const configured = await Promise.all(
|
|
213
|
-
requiredKeys.map((key) =>
|
|
251
|
+
definition.requiredKeys.map((key) =>
|
|
214
252
|
hasCredential(key, {
|
|
215
253
|
userEmail: scope.userEmail,
|
|
216
254
|
orgId: scope.orgId,
|
|
@@ -218,22 +256,41 @@ async function bigQueryStatus(scope: AnalyticsScope): Promise<{
|
|
|
218
256
|
),
|
|
219
257
|
);
|
|
220
258
|
return {
|
|
259
|
+
id: definition.id,
|
|
260
|
+
label: definition.label,
|
|
261
|
+
role: definition.role,
|
|
221
262
|
configured: configured.every(Boolean),
|
|
222
|
-
missingRequiredKeys: requiredKeys.filter(
|
|
263
|
+
missingRequiredKeys: definition.requiredKeys.filter(
|
|
223
264
|
(_, index) => !configured[index],
|
|
224
265
|
),
|
|
225
|
-
setupLink:
|
|
266
|
+
setupLink: definition.setupLink,
|
|
226
267
|
};
|
|
227
268
|
} catch (error) {
|
|
228
|
-
console.warn(
|
|
269
|
+
console.warn(
|
|
270
|
+
`[first-party-analytics] ${definition.label} status unavailable:`,
|
|
271
|
+
error,
|
|
272
|
+
);
|
|
229
273
|
return {
|
|
274
|
+
id: definition.id,
|
|
275
|
+
label: definition.label,
|
|
276
|
+
role: definition.role,
|
|
230
277
|
configured: null,
|
|
231
278
|
missingRequiredKeys: [],
|
|
232
|
-
setupLink:
|
|
279
|
+
setupLink: definition.setupLink,
|
|
233
280
|
};
|
|
234
281
|
}
|
|
235
282
|
}
|
|
236
283
|
|
|
284
|
+
async function externalBackendStatuses(
|
|
285
|
+
scope: AnalyticsScope,
|
|
286
|
+
): Promise<FirstPartyAnalyticsBackendStatus[]> {
|
|
287
|
+
return Promise.all(
|
|
288
|
+
EXTERNAL_BACKEND_DEFINITIONS.map((definition) =>
|
|
289
|
+
externalBackendStatus(scope, definition),
|
|
290
|
+
),
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
237
294
|
function recommendationFor(
|
|
238
295
|
status: FirstPartyAnalyticsHealthStatus,
|
|
239
296
|
configured: boolean | null,
|
|
@@ -242,6 +299,18 @@ function recommendationFor(
|
|
|
242
299
|
return configured === true ? "use_bigquery" : "connect_bigquery";
|
|
243
300
|
}
|
|
244
301
|
|
|
302
|
+
function externalBackendRecommendationFor(
|
|
303
|
+
status: FirstPartyAnalyticsHealthStatus,
|
|
304
|
+
backends: FirstPartyAnalyticsBackendStatus[],
|
|
305
|
+
): FirstPartyAnalyticsExternalBackendRecommendation {
|
|
306
|
+
if (status !== "recommend_bigquery") return "none";
|
|
307
|
+
if (backends.some((backend) => backend.configured === true)) return "use";
|
|
308
|
+
if (backends.every((backend) => backend.configured === false)) {
|
|
309
|
+
return "connect";
|
|
310
|
+
}
|
|
311
|
+
return "unknown";
|
|
312
|
+
}
|
|
313
|
+
|
|
245
314
|
export async function getFirstPartyAnalyticsHealth(
|
|
246
315
|
scope: AnalyticsScope,
|
|
247
316
|
): Promise<FirstPartyAnalyticsHealth> {
|
|
@@ -255,19 +324,30 @@ export async function getFirstPartyAnalyticsHealth(
|
|
|
255
324
|
const rollups = schema.analyticsEventDailyRollups;
|
|
256
325
|
const pressure = schema.analyticsQueryPressureDaily;
|
|
257
326
|
const db = getDb() as any;
|
|
327
|
+
const backend = await getFirstPartyAnalyticsBackend(scope);
|
|
258
328
|
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
.
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
329
|
+
const rollupRowsPromise =
|
|
330
|
+
backend.sink === "bigquery"
|
|
331
|
+
? getFirstPartyAnalyticsBigQueryMetrics(scope, backend.table).then(
|
|
332
|
+
(metrics) => [metrics],
|
|
333
|
+
)
|
|
334
|
+
: db
|
|
335
|
+
.select({
|
|
336
|
+
eventCount: sql<number>`coalesce(sum(${rollups.eventCount}), 0)`,
|
|
337
|
+
dailyRollupRows: sql<number>`count(*)`,
|
|
338
|
+
firstEventDate: sql<string | null>`min(${rollups.eventDate})`,
|
|
339
|
+
lastEventDate: sql<string | null>`max(${rollups.eventDate})`,
|
|
340
|
+
})
|
|
341
|
+
.from(rollups)
|
|
342
|
+
.where(
|
|
343
|
+
and(
|
|
344
|
+
inArray(rollups.tenantKey, keys),
|
|
345
|
+
lte(rollups.eventDate, today),
|
|
346
|
+
),
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
const [rollupRows, pressureRows, externalBackends] = await Promise.all([
|
|
350
|
+
rollupRowsPromise,
|
|
271
351
|
db
|
|
272
352
|
.select({
|
|
273
353
|
queryClass: pressure.queryClass,
|
|
@@ -284,9 +364,14 @@ export async function getFirstPartyAnalyticsHealth(
|
|
|
284
364
|
lte(pressure.lastSeenAt, nowTimestamp),
|
|
285
365
|
),
|
|
286
366
|
),
|
|
287
|
-
|
|
367
|
+
externalBackendStatuses(scope),
|
|
288
368
|
]);
|
|
289
369
|
|
|
370
|
+
const bigQuery = externalBackends.find(
|
|
371
|
+
(backend) => backend.id === "bigquery",
|
|
372
|
+
);
|
|
373
|
+
if (!bigQuery) throw new Error("BigQuery backend status is missing");
|
|
374
|
+
|
|
290
375
|
const rollup = (rollupRows[0] ?? {}) as Record<string, unknown>;
|
|
291
376
|
const eventCount = finiteNumber(rollup.eventCount);
|
|
292
377
|
const dailyRollupRows = finiteNumber(rollup.dailyRollupRows);
|
|
@@ -339,15 +424,24 @@ export async function getFirstPartyAnalyticsHealth(
|
|
|
339
424
|
FIRST_PARTY_ANALYTICS_PRESSURE_THRESHOLDS.recommendEventCount / 4 ||
|
|
340
425
|
slowQueryCount24h > 0;
|
|
341
426
|
const status: FirstPartyAnalyticsHealthStatus =
|
|
342
|
-
|
|
343
|
-
?
|
|
344
|
-
: hasMonitorSignal
|
|
427
|
+
backend.sink === "bigquery"
|
|
428
|
+
? slowQueryCount24h > 0 || timeoutCount24h > 0
|
|
345
429
|
? "monitor"
|
|
346
|
-
: "healthy"
|
|
430
|
+
: "healthy"
|
|
431
|
+
: reasons.length > 0
|
|
432
|
+
? "recommend_bigquery"
|
|
433
|
+
: hasMonitorSignal
|
|
434
|
+
? "monitor"
|
|
435
|
+
: "healthy";
|
|
347
436
|
|
|
348
437
|
return {
|
|
349
438
|
status,
|
|
350
439
|
recommendation: recommendationFor(status, bigQuery.configured),
|
|
440
|
+
externalBackendRecommendation: externalBackendRecommendationFor(
|
|
441
|
+
status,
|
|
442
|
+
externalBackends,
|
|
443
|
+
),
|
|
444
|
+
externalBackends,
|
|
351
445
|
reasons,
|
|
352
446
|
observedAt: nowIso(),
|
|
353
447
|
metrics: {
|
|
@@ -367,9 +461,24 @@ export async function getFirstPartyAnalyticsHealth(
|
|
|
367
461
|
}
|
|
368
462
|
|
|
369
463
|
export function unavailableFirstPartyAnalyticsHealth(): FirstPartyAnalyticsHealth {
|
|
464
|
+
const externalBackends = EXTERNAL_BACKEND_DEFINITIONS.map((definition) => ({
|
|
465
|
+
id: definition.id,
|
|
466
|
+
label: definition.label,
|
|
467
|
+
role: definition.role,
|
|
468
|
+
configured: null,
|
|
469
|
+
missingRequiredKeys: [],
|
|
470
|
+
setupLink: definition.setupLink,
|
|
471
|
+
}));
|
|
472
|
+
const bigQuery = externalBackends.find(
|
|
473
|
+
(backend) => backend.id === "bigquery",
|
|
474
|
+
);
|
|
475
|
+
if (!bigQuery) throw new Error("BigQuery backend status is missing");
|
|
476
|
+
|
|
370
477
|
return {
|
|
371
478
|
status: "unavailable",
|
|
372
479
|
recommendation: "none",
|
|
480
|
+
externalBackendRecommendation: "unknown",
|
|
481
|
+
externalBackends,
|
|
373
482
|
reasons: [],
|
|
374
483
|
observedAt: nowIso(),
|
|
375
484
|
metrics: {
|
|
@@ -384,10 +493,6 @@ export function unavailableFirstPartyAnalyticsHealth(): FirstPartyAnalyticsHealt
|
|
|
384
493
|
maxQueryDurationMs24h: 0,
|
|
385
494
|
},
|
|
386
495
|
thresholds: FIRST_PARTY_ANALYTICS_PRESSURE_THRESHOLDS,
|
|
387
|
-
bigQuery
|
|
388
|
-
configured: null,
|
|
389
|
-
missingRequiredKeys: [],
|
|
390
|
-
setupLink: BIGQUERY_SETUP_LINK,
|
|
391
|
-
},
|
|
496
|
+
bigQuery,
|
|
392
497
|
};
|
|
393
498
|
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
import { isPostgres } from "@agent-native/core/db";
|
|
1
2
|
import { sql } from "@agent-native/core/db/schema";
|
|
2
3
|
|
|
3
4
|
import { getDb, schema } from "../db/index.js";
|
|
5
|
+
import {
|
|
6
|
+
FIRST_PARTY_ANALYTICS_ROLLUP_LOCK_KEY,
|
|
7
|
+
FIRST_PARTY_ANALYTICS_ROLLUP_LOCK_SQL,
|
|
8
|
+
} from "./analytics-rollup-lock.js";
|
|
4
9
|
|
|
5
10
|
/**
|
|
6
11
|
* The normalized subset emitted by first-party analytics ingest. Keeping this
|
|
@@ -23,15 +28,6 @@ export interface FirstPartyAnalyticsRollupResult {
|
|
|
23
28
|
userDayCount: number;
|
|
24
29
|
}
|
|
25
30
|
|
|
26
|
-
/**
|
|
27
|
-
* Shared with the historical backfill so live ingest and migration work hold
|
|
28
|
-
* the same transaction-scoped Postgres lock while they update rollups.
|
|
29
|
-
*/
|
|
30
|
-
export const FIRST_PARTY_ANALYTICS_ROLLUP_LOCK_KEY =
|
|
31
|
-
"agent-native:analytics-rollup-backfill";
|
|
32
|
-
export const FIRST_PARTY_ANALYTICS_ROLLUP_LOCK_SQL =
|
|
33
|
-
"SELECT pg_advisory_xact_lock(hashtextextended(?, 0::bigint))";
|
|
34
|
-
|
|
35
31
|
interface DailyRollupRow {
|
|
36
32
|
id: string;
|
|
37
33
|
tenantKey: string;
|
|
@@ -150,6 +146,16 @@ export async function upsertFirstPartyAnalyticsRollups(
|
|
|
150
146
|
}
|
|
151
147
|
|
|
152
148
|
const writeRollups = async (tx: any) => {
|
|
149
|
+
if (isPostgres()) {
|
|
150
|
+
// The historical backfill takes this lock before its raw-event
|
|
151
|
+
// snapshot. Holding it through ingest's rollup increment prevents the
|
|
152
|
+
// backfill's monotonic upsert from losing a concurrently committed
|
|
153
|
+
// event.
|
|
154
|
+
await tx.execute({
|
|
155
|
+
sql: FIRST_PARTY_ANALYTICS_ROLLUP_LOCK_SQL,
|
|
156
|
+
args: [FIRST_PARTY_ANALYTICS_ROLLUP_LOCK_KEY],
|
|
157
|
+
});
|
|
158
|
+
}
|
|
153
159
|
const dailyRows = [...dailyRollups.values()];
|
|
154
160
|
await tx
|
|
155
161
|
.insert(schema.analyticsEventDailyRollups)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { getDbExec
|
|
1
|
+
import { getDbExec } from "@agent-native/core/db";
|
|
2
|
+
import { runWithRequestContext } from "@agent-native/core/server";
|
|
2
3
|
import { and, eq, isNull, or } from "drizzle-orm";
|
|
3
4
|
|
|
4
5
|
import { FIRST_PARTY_ANALYTICS_QUERY_TIMEOUT_MS } from "../../shared/dashboard-report-timeouts.js";
|
|
@@ -8,6 +9,12 @@ import {
|
|
|
8
9
|
ingestAnalyticsExceptionEvents,
|
|
9
10
|
type DerivedExceptionFields,
|
|
10
11
|
} from "./error-capture.js";
|
|
12
|
+
import {
|
|
13
|
+
getFirstPartyAnalyticsBackend,
|
|
14
|
+
getFirstPartyAnalyticsTable,
|
|
15
|
+
insertFirstPartyAnalyticsRows,
|
|
16
|
+
queryFirstPartyAnalyticsInBigQuery,
|
|
17
|
+
} from "./first-party-analytics-backend.js";
|
|
11
18
|
import {
|
|
12
19
|
firstPartyCacheKey,
|
|
13
20
|
withFirstPartyCache,
|
|
@@ -17,11 +24,7 @@ import {
|
|
|
17
24
|
queryOutcomeFromError,
|
|
18
25
|
recordFirstPartyAnalyticsQueryPressure,
|
|
19
26
|
} from "./first-party-analytics-health.js";
|
|
20
|
-
import {
|
|
21
|
-
FIRST_PARTY_ANALYTICS_ROLLUP_LOCK_KEY,
|
|
22
|
-
FIRST_PARTY_ANALYTICS_ROLLUP_LOCK_SQL,
|
|
23
|
-
upsertFirstPartyAnalyticsRollups,
|
|
24
|
-
} from "./first-party-analytics-rollups.js";
|
|
27
|
+
import { upsertFirstPartyAnalyticsRollups } from "./first-party-analytics-rollups.js";
|
|
25
28
|
|
|
26
29
|
export interface AnalyticsScope {
|
|
27
30
|
userEmail: string;
|
|
@@ -508,18 +511,33 @@ export async function recordAnalyticsEvents(
|
|
|
508
511
|
};
|
|
509
512
|
});
|
|
510
513
|
|
|
511
|
-
|
|
514
|
+
const backend = await getFirstPartyAnalyticsBackend({
|
|
515
|
+
userEmail: key.ownerEmail,
|
|
516
|
+
orgId: key.orgId ?? null,
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
if (rows.length && (backend.sink === "dual" || backend.sink === "bigquery")) {
|
|
520
|
+
try {
|
|
521
|
+
await runWithRequestContext(
|
|
522
|
+
{
|
|
523
|
+
userEmail: key.ownerEmail,
|
|
524
|
+
orgId: key.orgId ?? undefined,
|
|
525
|
+
},
|
|
526
|
+
() => insertFirstPartyAnalyticsRows(rows, backend.table),
|
|
527
|
+
);
|
|
528
|
+
} catch (error) {
|
|
529
|
+
if (backend.sink === "bigquery") throw error;
|
|
530
|
+
// Dual-write mode keeps Postgres as the recoverable source until the
|
|
531
|
+
// backfill has completed. A BigQuery outage must not lose live events.
|
|
532
|
+
console.error(
|
|
533
|
+
"[first-party-analytics] BigQuery dual-write failed; retaining Postgres event:",
|
|
534
|
+
error,
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (rows.length && backend.sink !== "bigquery") {
|
|
512
540
|
await db.transaction(async (tx: any) => {
|
|
513
|
-
if (isPostgres()) {
|
|
514
|
-
// The historical backfill takes this lock before its raw-event
|
|
515
|
-
// snapshot. Holding it through ingest's rollup increment prevents the
|
|
516
|
-
// backfill's monotonic upsert from losing a concurrently committed
|
|
517
|
-
// event.
|
|
518
|
-
await tx.execute({
|
|
519
|
-
sql: FIRST_PARTY_ANALYTICS_ROLLUP_LOCK_SQL,
|
|
520
|
-
args: [FIRST_PARTY_ANALYTICS_ROLLUP_LOCK_KEY],
|
|
521
|
-
});
|
|
522
|
-
}
|
|
523
541
|
await tx.insert(schema.analyticsEvents).values(rows);
|
|
524
542
|
await tx
|
|
525
543
|
.update(schema.analyticsPublicKeys)
|
|
@@ -528,6 +546,12 @@ export async function recordAnalyticsEvents(
|
|
|
528
546
|
await upsertFirstPartyAnalyticsRollups(rows, tx);
|
|
529
547
|
});
|
|
530
548
|
}
|
|
549
|
+
if (rows.length && backend.sink === "bigquery") {
|
|
550
|
+
await db
|
|
551
|
+
.update(schema.analyticsPublicKeys)
|
|
552
|
+
.set({ lastUsedAt: receivedAt })
|
|
553
|
+
.where(eq(schema.analyticsPublicKeys.id, key.id));
|
|
554
|
+
}
|
|
531
555
|
|
|
532
556
|
// Fork captured exceptions into the dedicated error-capture tables. This is
|
|
533
557
|
// best-effort: a malformed `$exception` payload must never reject the whole
|
|
@@ -984,6 +1008,24 @@ export async function queryFirstPartyAnalytics(
|
|
|
984
1008
|
options: AnalyticsQueryOptions = {},
|
|
985
1009
|
): Promise<AnalyticsQueryResult> {
|
|
986
1010
|
validateFirstPartyAnalyticsSql(sql);
|
|
1011
|
+
const backend = await getFirstPartyAnalyticsBackend(scope);
|
|
1012
|
+
if (backend.sink === "bigquery") {
|
|
1013
|
+
const usesSessionRecordings = /\bsession_recordings\b/i.test(sql);
|
|
1014
|
+
const usesEventTables =
|
|
1015
|
+
/\banalytics_events\b|\banalytics_event_daily_rollups\b|\banalytics_user_days\b/i.test(
|
|
1016
|
+
sql,
|
|
1017
|
+
);
|
|
1018
|
+
if (usesSessionRecordings && usesEventTables) {
|
|
1019
|
+
throw new Error(
|
|
1020
|
+
"Cross-backend joins are not supported; query first-party event tables in BigQuery and session_recordings in the Analytics SQL store separately.",
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
if (!usesSessionRecordings) {
|
|
1024
|
+
const table = await getFirstPartyAnalyticsTable(backend.table);
|
|
1025
|
+
const scoped = scopedAnalyticsSql(sql, scope);
|
|
1026
|
+
return queryFirstPartyAnalyticsInBigQuery(scoped.sql, scoped.args, table);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
987
1029
|
const scoped = scopedAnalyticsSql(sql, scope);
|
|
988
1030
|
const wrappedSql = `SELECT * FROM (${scoped.sql}) AS first_party_analytics_query LIMIT ${MAX_QUERY_ROWS}`;
|
|
989
1031
|
const timeoutMs = Math.max(
|
|
@@ -65,7 +65,7 @@ export const CORPUS_REDUCTION_ACTIONS = new Set(["run-code"]);
|
|
|
65
65
|
// hasDataQueryAttempt, but they should stop the guard from steering a
|
|
66
66
|
// template-clone turn into "connect a missing source". Deliberately limited
|
|
67
67
|
// to read/inspection actions: update-dashboard/mutate-dashboard/
|
|
68
|
-
// compose-dashboard/
|
|
68
|
+
// compose-dashboard/create-extension/
|
|
69
69
|
// update-extension can all author brand-new SQL or extension content, so
|
|
70
70
|
// calling one of those alone is not proof the turn actually inspected a
|
|
71
71
|
// template rather than inventing it from scratch. If the tool run also
|
|
@@ -74,7 +74,6 @@ export const CORPUS_REDUCTION_ACTIONS = new Set(["run-code"]);
|
|
|
74
74
|
export const DASHBOARD_CONSTRUCTION_ACTIONS = new Set([
|
|
75
75
|
"get-sql-dashboard",
|
|
76
76
|
"list-sql-dashboards",
|
|
77
|
-
"list-dashboard-templates",
|
|
78
77
|
"list-extensions",
|
|
79
78
|
"get-extension",
|
|
80
79
|
]);
|
|
@@ -88,7 +87,6 @@ export const DASHBOARD_MUTATION_ACTIONS = new Set([
|
|
|
88
87
|
"mutate-dashboard",
|
|
89
88
|
"update-dashboard",
|
|
90
89
|
"compose-dashboard",
|
|
91
|
-
"install-dashboard-template",
|
|
92
90
|
"create-extension",
|
|
93
91
|
"update-extension",
|
|
94
92
|
]);
|
|
@@ -53,7 +53,6 @@ function hasSuccessfulDashboardSave(
|
|
|
53
53
|
"update-dashboard",
|
|
54
54
|
"mutate-dashboard",
|
|
55
55
|
"compose-dashboard",
|
|
56
|
-
"install-dashboard-template",
|
|
57
56
|
]);
|
|
58
57
|
return (toolResults ?? []).some((result) => {
|
|
59
58
|
if (result.isError) return false;
|
|
@@ -737,9 +736,8 @@ export function realDataFinalGuard(
|
|
|
737
736
|
'This is a dashboard construction/template-clone request. Resolve the named template\'s id (use `list-sql-dashboards` if you only have a title) and call `get-sql-dashboard` with `includeConfig: true` first. If its panels are `chartType: "extension"`, use `get-extension` then `create-extension` to clone/adapt it, then `update-dashboard` to save the new dashboard. Do not invent SQL panels for an extension-backed template. Ask one clarifying filter question if needed. Only run a data-source query before presenting numbers or authoring invented SQL.',
|
|
738
737
|
fallbackMessage:
|
|
739
738
|
"I need to inspect the template dashboard (and its extension, if it uses one) before creating the new one. Tell me the template dashboard name, or confirm the org/account filter, and I'll clone it without inventing metrics.",
|
|
740
|
-
//
|
|
741
|
-
//
|
|
742
|
-
// the lookup/inspection tools this message asks for.
|
|
739
|
+
// Expand the tool surface so a corrective retry can always reach the
|
|
740
|
+
// lookup/inspection tools this message asks for.
|
|
743
741
|
expandToolSurface: true,
|
|
744
742
|
};
|
|
745
743
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { runAnalyticsRollupBackfillOnce } from "../jobs/analytics-rollup-backfill";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_INTERVAL_MS = 15 * 60 * 1000;
|
|
4
|
+
let skippingLogged = false;
|
|
5
|
+
|
|
6
|
+
declare global {
|
|
7
|
+
var __AGENT_NATIVE_ANALYTICS_ROLLUP_BACKFILL_SCHEDULED_RUNTIME__:
|
|
8
|
+
| boolean
|
|
9
|
+
| undefined;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function platformSchedulerOwnsBackfill(): boolean {
|
|
13
|
+
return (
|
|
14
|
+
process.env.NETLIFY === "true" ||
|
|
15
|
+
Boolean(process.env.NETLIFY_FUNCTION_NAME) ||
|
|
16
|
+
globalThis.__AGENT_NATIVE_ANALYTICS_ROLLUP_BACKFILL_SCHEDULED_RUNTIME__ ===
|
|
17
|
+
true
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function intervalMs(): number {
|
|
22
|
+
const raw = process.env.ANALYTICS_ROLLUP_BACKFILL_INTERVAL_MS?.trim();
|
|
23
|
+
if (!raw) return DEFAULT_INTERVAL_MS;
|
|
24
|
+
const parsed = Number.parseInt(raw, 10);
|
|
25
|
+
return Number.isFinite(parsed) && parsed >= 10_000
|
|
26
|
+
? parsed
|
|
27
|
+
: DEFAULT_INTERVAL_MS;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default function registerAnalyticsRollupBackfillJobs(): void {
|
|
31
|
+
const isProd = process.env.NODE_ENV === "production";
|
|
32
|
+
const flag =
|
|
33
|
+
process.env.ANALYTICS_ROLLUP_BACKFILL_JOBS ??
|
|
34
|
+
process.env.RUN_BACKGROUND_JOBS;
|
|
35
|
+
const enabled =
|
|
36
|
+
!platformSchedulerOwnsBackfill() &&
|
|
37
|
+
(flag === "1" || (isProd && flag !== "0"));
|
|
38
|
+
|
|
39
|
+
if (!enabled) {
|
|
40
|
+
if (!skippingLogged) {
|
|
41
|
+
console.log(
|
|
42
|
+
platformSchedulerOwnsBackfill()
|
|
43
|
+
? "[analytics-rollups] Skipping in-process cron because the platform scheduler owns historical rollup backfills."
|
|
44
|
+
: "[analytics-rollups] Skipping historical rollup backfill (set ANALYTICS_ROLLUP_BACKFILL_JOBS=1 or RUN_BACKGROUND_JOBS=1 to enable in dev; on by default in production)",
|
|
45
|
+
);
|
|
46
|
+
skippingLogged = true;
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const ms = intervalMs();
|
|
52
|
+
setInterval(() => {
|
|
53
|
+
runAnalyticsRollupBackfillOnce().catch((err) =>
|
|
54
|
+
console.error("[analytics-rollups] historical backfill failed:", err),
|
|
55
|
+
);
|
|
56
|
+
}, ms);
|
|
57
|
+
|
|
58
|
+
console.log(
|
|
59
|
+
`[analytics-rollups] Historical rollup backfill scheduled every ${ms / 1000}s.`,
|
|
60
|
+
);
|
|
61
|
+
}
|