@agent-native/core 0.120.1 → 0.120.3
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 +3 -3
- package/corpus/core/CHANGELOG.md +29 -0
- package/corpus/core/docs/content/extensions.mdx +23 -4
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +4 -0
- package/corpus/core/src/agent/run-manager.ts +36 -10
- package/corpus/core/src/agent/types.ts +2 -0
- package/corpus/core/src/cli/context-xray-local.ts +1 -1
- package/corpus/core/src/cli/index.ts +63 -18
- package/corpus/core/src/cli/skills.ts +5 -0
- package/corpus/core/src/cli/telemetry.ts +118 -1
- package/corpus/core/src/client/AgentPanel.tsx +10 -0
- package/corpus/core/src/client/CommandMenu.tsx +13 -1
- package/corpus/core/src/client/agent-page/AgentTabsPage.tsx +39 -4
- package/corpus/core/src/client/analytics.ts +13 -0
- package/corpus/core/src/client/error-capture.ts +6 -1
- package/corpus/core/src/client/extensions/ExtensionsListPage.tsx +28 -8
- package/corpus/core/src/client/extensions/agent-native-extension-runtime.ts +1 -1
- package/corpus/core/src/client/navigation/index.ts +2 -0
- package/corpus/core/src/client/org/OrgSwitcher.tsx +14 -32
- package/corpus/core/src/client/settings/ExtensionsSettingsContent.tsx +24 -0
- package/corpus/core/src/client/settings/SettingsPanel.tsx +10 -0
- package/corpus/core/src/deploy/build.ts +3 -0
- package/corpus/core/src/extensions/theme.ts +1 -1
- package/corpus/core/src/localization/default-messages.ts +2 -0
- package/corpus/core/src/server/analytics.ts +154 -25
- package/corpus/core/src/server/core-routes-plugin.ts +10 -1
- package/corpus/core/src/server/framework-request-handler.ts +17 -26
- package/corpus/core/src/styles/agent-native.css +13 -0
- package/corpus/core/src/templates/chat/app/components/layout/Layout.tsx +3 -0
- package/corpus/core/src/templates/chat/app/components/layout/Sidebar.tsx +113 -51
- package/corpus/core/src/templates/chat/app/global.css +1 -1
- package/corpus/core/src/templates/chat/app/routes/extensions._index.tsx +2 -2
- package/corpus/core/src/templates/chat/netlify.toml +1 -0
- package/corpus/core/src/templates/default/app/global.css +1 -1
- package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +16 -0
- package/corpus/core/src/tracking/error-capture.ts +151 -0
- package/corpus/core/src/tracking/index.ts +5 -0
- package/corpus/core/src/vite/client.ts +6 -0
- package/corpus/templates/analytics/.agents/skills/adhoc-analysis/SKILL.md +27 -43
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +19 -8
- package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +2 -2
- package/corpus/templates/analytics/AGENTS.md +20 -8
- package/corpus/templates/analytics/actions/get-explorer-dashboard.ts +3 -0
- package/corpus/templates/analytics/actions/list-dashboard-usage-stats.ts +140 -46
- package/corpus/templates/analytics/actions/migrate-analytics-artifacts.ts +48 -0
- package/corpus/templates/analytics/actions/save-analysis.ts +2 -3
- package/corpus/templates/analytics/actions/view-screen.ts +1 -1
- package/corpus/templates/analytics/app/components/dashboard/CumulativeNetChart.tsx +1 -1
- package/corpus/templates/analytics/app/components/dashboard/DashboardHistoryPanel.tsx +1 -4
- package/corpus/templates/analytics/app/components/dashboard/DashboardMetadata.tsx +77 -0
- package/corpus/templates/analytics/app/components/dashboard/DashboardPanelSkeleton.tsx +7 -1
- package/corpus/templates/analytics/app/components/dashboard/DataTable.tsx +1 -1
- package/corpus/templates/analytics/app/components/dashboard/DateRangePicker.tsx +2 -2
- package/corpus/templates/analytics/app/components/dashboard/MetricCard.tsx +1 -1
- package/corpus/templates/analytics/app/components/dashboard/RecentActivity.tsx +1 -1
- package/corpus/templates/analytics/app/components/dashboard/RevenueChart.tsx +1 -1
- package/corpus/templates/analytics/app/components/dashboard/RevenueComparisonChart.tsx +1 -1
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +41 -9
- package/corpus/templates/analytics/app/components/dashboard/StatsCard.tsx +1 -1
- package/corpus/templates/analytics/app/components/dashboard/TimeSeriesChart.tsx +1 -1
- package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +2 -0
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +28 -14
- package/corpus/templates/analytics/app/components/layout/NewDashboardDialog.tsx +1 -0
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +255 -626
- package/corpus/templates/analytics/app/global.css +31 -1
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +2 -2
- package/corpus/templates/analytics/app/i18n-data.ts +80 -0
- package/corpus/templates/analytics/app/lib/dashboard-report-capture.ts +42 -0
- package/corpus/templates/analytics/app/pages/Agents.tsx +49 -2
- package/corpus/templates/analytics/app/pages/adhoc/_shared/KpiChart.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/explorer/components/ChartTypePicker.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/explorer/components/DateRangePicker.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/explorer/components/EventRow.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/explorer/components/SqlPreview.tsx +2 -2
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +54 -11
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/DashboardFilterBar.tsx +3 -3
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/EmailReportDialog.tsx +2 -2
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +4 -54
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +178 -69
- package/corpus/templates/analytics/app/pages/analyses/AnalysisDetail.tsx +2 -2
- package/corpus/templates/analytics/app/root.tsx +28 -0
- package/corpus/templates/analytics/app/routes/analyses._index.tsx +3 -2
- package/corpus/templates/analytics/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/analytics/changelog/2026-07-23-analytics-now-groups-saved-analyses-and-embedded-extensions-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-23-dashboard-menus-show-who-created-and-last-updated-each-dashb.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-23-dashboard-usage-now-loads-reliably-for-organizations-with-ma.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-23-long-dashboard-building-requests-now-continue-without-stoppi.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-24-analytics-charts-now-explain-slow-loads-and-offer-a-direct-r.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-24-daily-dashboard-emails-now-load-signed-reports-more-reliably.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-24-manage-agent-is-clearly-marked-as-a-link-to-its-dedicated-pa.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/analytics/netlify.toml +4 -0
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +1 -1
- package/corpus/templates/analytics/server/db/schema.ts +9 -0
- package/corpus/templates/analytics/server/jobs/dashboard-report.ts +21 -0
- package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +0 -1
- package/corpus/templates/analytics/server/lib/agent-readable-resource-context.ts +1 -0
- package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +11 -0
- package/corpus/templates/analytics/server/lib/dashboard-report-subscriptions.ts +54 -0
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +235 -26
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +4 -1
- package/corpus/templates/analytics/server/lib/migrate-analytics-artifacts.ts +1062 -0
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +6 -52
- package/corpus/templates/analytics/server/plugins/core-routes.ts +1 -1
- package/corpus/templates/analytics/server/plugins/db.ts +19 -0
- package/corpus/templates/analytics/shared/dashboard-report-timeouts.ts +1 -1
- package/corpus/templates/assets/actions/generate-image-batch.ts +36 -1
- package/corpus/templates/assets/actions/generate-image.ts +28 -1
- package/corpus/templates/assets/app/components/asset/AssetPreviewDialog.tsx +376 -0
- package/corpus/templates/assets/app/components/create/RecentDraftsSection.tsx +14 -4
- package/corpus/templates/assets/app/components/layout/Layout.tsx +5 -0
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +164 -82
- package/corpus/templates/assets/app/global.css +1 -1
- package/corpus/templates/assets/app/i18n-data.ts +10 -0
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +99 -10
- package/corpus/templates/assets/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/assets/app/routes/library.tsx +21 -198
- package/corpus/templates/assets/changelog/2026-07-21-unified-asset-preview-click-any-asset-to-open-one-preview-wi.md +6 -0
- package/corpus/templates/assets/changelog/2026-07-23-assets-image-generation-now-recovers-after-a-completed-tool-.md +6 -0
- package/corpus/templates/assets/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/assets/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/assets/netlify.toml +1 -0
- package/corpus/templates/brain/app/components/layout/Layout.tsx +3 -0
- package/corpus/templates/brain/app/components/layout/Sidebar.tsx +114 -51
- package/corpus/templates/brain/app/global.css +1 -1
- package/corpus/templates/brain/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/brain/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/brain/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/brain/netlify.toml +1 -0
- package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +110 -57
- package/corpus/templates/calendar/app/global.css +1 -1
- package/corpus/templates/calendar/app/routes/_app.extensions._index.tsx +2 -16
- package/corpus/templates/calendar/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/calendar/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/calendar/netlify.toml +1 -0
- package/corpus/templates/chat/app/components/layout/Layout.tsx +3 -0
- package/corpus/templates/chat/app/components/layout/Sidebar.tsx +113 -51
- package/corpus/templates/chat/app/global.css +1 -1
- package/corpus/templates/chat/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/chat/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/chat/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/chat/netlify.toml +1 -0
- package/corpus/templates/clips/app/components/library/library-layout.tsx +117 -37
- package/corpus/templates/clips/app/components/library/organization-switcher.tsx +2 -2
- package/corpus/templates/clips/app/components/player/video-player.tsx +16 -1
- package/corpus/templates/clips/app/global.css +1 -1
- package/corpus/templates/clips/app/hooks/use-view-tracking.ts +55 -12
- package/corpus/templates/clips/app/routes/_app.extensions._index.tsx +2 -16
- package/corpus/templates/clips/app/routes/embed.$shareId.tsx +6 -5
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +7 -7
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -5
- package/corpus/templates/clips/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-23-meeting-recording-meters-and-dismissed-prompts.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/clips/desktop/src/hooks/useMicMeter.ts +1 -1
- package/corpus/templates/clips/desktop/src/lib/meeting-notification-dismissal.ts +2 -0
- package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +1 -3
- package/corpus/templates/clips/desktop/src/overlays/live-audio-bars.tsx +86 -0
- package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +24 -0
- package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +5 -69
- package/corpus/templates/clips/desktop/src/styles.css +35 -3
- package/corpus/templates/clips/desktop/src-tauri/src/adhoc_meetings_watcher.rs +13 -44
- package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +119 -17
- package/corpus/templates/clips/netlify.toml +1 -0
- package/corpus/templates/clips/server/plugins/db.ts +113 -68
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +69 -49
- package/corpus/templates/content/app/global.css +1 -1
- package/corpus/templates/content/app/routes/_app.extensions._index.tsx +2 -2
- package/corpus/templates/content/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
- package/corpus/templates/content/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/content/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/content/netlify.toml +1 -0
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +115 -42
- package/corpus/templates/design/app/entry.client.tsx +14 -1
- package/corpus/templates/design/app/global.css +1 -1
- package/corpus/templates/design/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/design/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
- package/corpus/templates/design/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/design/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/design/netlify.toml +1 -0
- package/corpus/templates/dispatch/app/dispatch-extensions.tsx +1 -1
- package/corpus/templates/dispatch/app/global.css +1 -1
- package/corpus/templates/dispatch/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/dispatch/netlify.toml +1 -0
- package/corpus/templates/forms/app/components/layout/Layout.tsx +3 -0
- package/corpus/templates/forms/app/components/layout/Sidebar.tsx +113 -82
- package/corpus/templates/forms/app/global.css +1 -1
- package/corpus/templates/forms/app/routes/_app.extensions._index.tsx +2 -2
- package/corpus/templates/forms/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
- package/corpus/templates/forms/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/forms/changelog/2026-07-24-forms-sidebar-actions-now-keep-search-language-feedback-and-.md +6 -0
- package/corpus/templates/forms/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/forms/netlify.toml +1 -0
- package/corpus/templates/macros/app/components/DailyProgress.tsx +5 -5
- package/corpus/templates/macros/app/components/ExerciseCard.tsx +2 -2
- package/corpus/templates/macros/app/components/MealCard.tsx +2 -2
- package/corpus/templates/macros/app/components/WeightCard.tsx +2 -2
- package/corpus/templates/macros/app/components/layout/AppLayout.tsx +92 -34
- package/corpus/templates/macros/app/components/ui/button.tsx +1 -2
- package/corpus/templates/macros/app/components/ui/card.tsx +1 -1
- package/corpus/templates/macros/app/global.css +1 -1
- package/corpus/templates/macros/app/routes/_index.tsx +1 -1
- package/corpus/templates/macros/app/routes/analytics.tsx +4 -7
- package/corpus/templates/macros/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/macros/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/macros/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/macros/netlify.toml +1 -0
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +125 -46
- package/corpus/templates/mail/app/global.css +1 -1
- package/corpus/templates/mail/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/mail/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/mail/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/mail/netlify.toml +1 -0
- package/corpus/templates/plan/app/components/layout/Layout.tsx +3 -0
- package/corpus/templates/plan/app/components/layout/Sidebar.tsx +128 -65
- package/corpus/templates/plan/app/global.css +1 -1
- package/corpus/templates/plan/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/plan/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/plan/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/plan/netlify.toml +1 -0
- package/corpus/templates/slides/app/components/layout/Sidebar.tsx +116 -36
- package/corpus/templates/slides/app/global.css +1 -1
- package/corpus/templates/slides/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/slides/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/corpus/templates/slides/netlify.toml +1 -0
- package/corpus/templates/tasks/app/components/layout/Sidebar.tsx +37 -6
- package/corpus/templates/tasks/app/global.css +1 -1
- package/corpus/templates/tasks/app/routes/extensions._index.tsx +2 -2
- package/corpus/templates/tasks/app/routes/settings.tsx +31 -0
- package/corpus/templates/tasks/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/corpus/templates/tasks/netlify.toml +1 -0
- package/corpus/toolkit/CHANGELOG.md +8 -0
- package/corpus/toolkit/README.md +8 -1
- package/corpus/toolkit/agent-native.eject.json +1 -1
- package/corpus/toolkit/package.json +1 -1
- package/corpus/toolkit/src/app-shell/index.ts +1 -0
- package/corpus/toolkit/src/app-shell/sidebar-footer-actions.tsx +48 -0
- package/corpus/toolkit/src/chat-history.css +52 -1
- package/corpus/toolkit/src/design-system/default-adapter.tsx +1 -1
- package/corpus/toolkit/src/ui/button.tsx +1 -2
- package/corpus/toolkit/src/ui/card.tsx +1 -1
- package/corpus/toolkit/src/ui/table.tsx +4 -11
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +4 -0
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts +9 -0
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +25 -10
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/types.d.ts +2 -2
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/cli/context-xray-local.js +1 -1
- package/dist/cli/context-xray-local.js.map +1 -1
- package/dist/cli/index.js +56 -17
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +5 -0
- package/dist/cli/skills.js.map +1 -1
- package/dist/cli/telemetry.d.ts +7 -0
- package/dist/cli/telemetry.d.ts.map +1 -1
- package/dist/cli/telemetry.js +91 -1
- package/dist/cli/telemetry.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +7 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +2 -2
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/CommandMenu.d.ts +2 -0
- package/dist/client/CommandMenu.d.ts.map +1 -1
- package/dist/client/CommandMenu.js +12 -1
- package/dist/client/CommandMenu.js.map +1 -1
- package/dist/client/agent-page/AgentTabsPage.d.ts +4 -0
- package/dist/client/agent-page/AgentTabsPage.d.ts.map +1 -1
- package/dist/client/agent-page/AgentTabsPage.js +8 -4
- package/dist/client/agent-page/AgentTabsPage.js.map +1 -1
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +7 -0
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/error-capture.d.ts.map +1 -1
- package/dist/client/error-capture.js +8 -1
- package/dist/client/error-capture.js.map +1 -1
- package/dist/client/extensions/ExtensionsListPage.d.ts +5 -1
- package/dist/client/extensions/ExtensionsListPage.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionsListPage.js +6 -4
- package/dist/client/extensions/ExtensionsListPage.js.map +1 -1
- package/dist/client/extensions/agent-native-extension-runtime.js +1 -1
- package/dist/client/extensions/agent-native-extension-runtime.js.map +1 -1
- package/dist/client/navigation/index.d.ts +1 -1
- package/dist/client/navigation/index.d.ts.map +1 -1
- package/dist/client/navigation/index.js +1 -1
- package/dist/client/navigation/index.js.map +1 -1
- package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
- package/dist/client/org/OrgSwitcher.js +9 -13
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/settings/ExtensionsSettingsContent.d.ts +2 -0
- package/dist/client/settings/ExtensionsSettingsContent.d.ts.map +1 -0
- package/dist/client/settings/ExtensionsSettingsContent.js +10 -0
- package/dist/client/settings/ExtensionsSettingsContent.js.map +1 -0
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +10 -1
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/deploy/build.js +1 -0
- package/dist/deploy/build.js.map +1 -1
- package/dist/extensions/theme.js +1 -1
- package/dist/extensions/theme.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/localization/default-messages.d.ts +2 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +2 -0
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
- package/dist/provider-api/actions/provider-api.d.ts +9 -9
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/analytics.d.ts +9 -4
- package/dist/server/analytics.d.ts.map +1 -1
- package/dist/server/analytics.js +111 -22
- package/dist/server/analytics.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +8 -1
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/framework-request-handler.d.ts.map +1 -1
- package/dist/server/framework-request-handler.js +17 -27
- package/dist/server/framework-request-handler.js.map +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/styles/agent-native.css +13 -0
- package/dist/templates/chat/app/components/layout/Layout.tsx +3 -0
- package/dist/templates/chat/app/components/layout/Sidebar.tsx +113 -51
- package/dist/templates/chat/app/global.css +1 -1
- package/dist/templates/chat/app/routes/extensions._index.tsx +2 -2
- package/dist/templates/chat/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/dist/templates/chat/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/dist/templates/chat/netlify.toml +1 -0
- package/dist/templates/default/app/global.css +1 -1
- package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +16 -0
- package/dist/tracking/error-capture.d.ts +14 -0
- package/dist/tracking/error-capture.d.ts.map +1 -0
- package/dist/tracking/error-capture.js +113 -0
- package/dist/tracking/error-capture.js.map +1 -0
- package/dist/tracking/index.d.ts +1 -0
- package/dist/tracking/index.d.ts.map +1 -1
- package/dist/tracking/index.js +1 -0
- package/dist/tracking/index.js.map +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +2 -0
- package/dist/vite/client.js.map +1 -1
- package/docs/content/extensions.mdx +23 -4
- package/package.json +2 -2
- package/src/agent/production-agent.ts +4 -0
- package/src/agent/run-manager.ts +36 -10
- package/src/agent/types.ts +2 -0
- package/src/cli/context-xray-local.ts +1 -1
- package/src/cli/index.ts +63 -18
- package/src/cli/skills.ts +5 -0
- package/src/cli/telemetry.ts +118 -1
- package/src/client/AgentPanel.tsx +10 -0
- package/src/client/CommandMenu.tsx +13 -1
- package/src/client/agent-page/AgentTabsPage.tsx +39 -4
- package/src/client/analytics.ts +13 -0
- package/src/client/error-capture.ts +6 -1
- package/src/client/extensions/ExtensionsListPage.tsx +28 -8
- package/src/client/extensions/agent-native-extension-runtime.ts +1 -1
- package/src/client/navigation/index.ts +2 -0
- package/src/client/org/OrgSwitcher.tsx +14 -32
- package/src/client/settings/ExtensionsSettingsContent.tsx +24 -0
- package/src/client/settings/SettingsPanel.tsx +10 -0
- package/src/deploy/build.ts +3 -0
- package/src/extensions/theme.ts +1 -1
- package/src/localization/default-messages.ts +2 -0
- package/src/server/analytics.ts +154 -25
- package/src/server/core-routes-plugin.ts +10 -1
- package/src/server/framework-request-handler.ts +17 -26
- package/src/styles/agent-native.css +13 -0
- package/src/templates/chat/app/components/layout/Layout.tsx +3 -0
- package/src/templates/chat/app/components/layout/Sidebar.tsx +113 -51
- package/src/templates/chat/app/global.css +1 -1
- package/src/templates/chat/app/routes/extensions._index.tsx +2 -2
- package/src/templates/chat/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
- package/src/templates/chat/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
- package/src/templates/chat/netlify.toml +1 -0
- package/src/templates/default/app/global.css +1 -1
- package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +16 -0
- package/src/tracking/error-capture.ts +151 -0
- package/src/tracking/index.ts +5 -0
- package/src/vite/client.ts +6 -0
- package/corpus/templates/analytics/app/components/layout/NewAnalysisDialog.tsx +0 -165
- package/corpus/templates/analytics/app/pages/analyses/AnalysesList.tsx +0 -175
|
@@ -10,9 +10,13 @@ functions = "templates/analytics/.netlify/functions-internal"
|
|
|
10
10
|
|
|
11
11
|
[build.environment]
|
|
12
12
|
GA_MEASUREMENT_ID = "G-ESF7FYXGN9"
|
|
13
|
+
GTM_CONTAINER_ID = "GTM-N3WSTXZ"
|
|
13
14
|
NITRO_PRESET = "netlify"
|
|
14
15
|
NPM_CONFIG_PRODUCTION = "false"
|
|
15
16
|
AGENT_PROD_CODE_EXECUTION = "sandboxed"
|
|
17
|
+
# Analytics dashboard creation can span multiple model/tool rounds; keep those
|
|
18
|
+
# turns on the durable background worker instead of the short foreground wall.
|
|
19
|
+
AGENT_CHAT_DURABLE_BACKGROUND = "true"
|
|
16
20
|
|
|
17
21
|
# A2A delegation calls run a full agent loop (LLM + tool calls + DB queries).
|
|
18
22
|
# Hosted agent chat uses a soft timeout before this function timeout so the
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"chartType": "area",
|
|
76
76
|
"source": "first-party",
|
|
77
77
|
"width": 2,
|
|
78
|
-
"sql": "WITH
|
|
78
|
+
"sql": "WITH digits AS (SELECT 0 AS n UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9), offsets AS (SELECT ones.n + tens.n * 10 + hundreds.n * 100 AS n FROM digits ones CROSS JOIN digits tens CROSS JOIN digits hundreds WHERE hundreds.n < 8), signup_events AS (SELECT event_date AS date, COALESCE(NULLIF(template, ''), NULLIF(properties::jsonb ->> 'templateId', ''), NULLIF(properties::jsonb ->> 'agent_native_template', ''), NULLIF(properties::jsonb ->> 'agentNativeTemplate', ''), NULLIF(app, ''), NULLIF(properties::jsonb ->> 'agent_native_app', ''), NULLIF(properties::jsonb ->> 'agentNativeApp', ''), 'unknown') AS template FROM analytics_events WHERE event_name = 'signup' AND (event_date <= to_char(CURRENT_DATE, 'YYYY-MM-DD') AND ('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND event_date >= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '30d' AND event_date >= to_char(CURRENT_DATE - INTERVAL '30 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '90d' AND event_date >= to_char(CURRENT_DATE - INTERVAL '90 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '180d' AND event_date >= to_char(CURRENT_DATE - INTERVAL '180 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '365d' AND event_date >= to_char(CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD')))) AND ('{{emailFilter}}' IN ('', 'all') OR ('{{emailFilter}}' = 'exclude_builder' AND lower(coalesce(user_id, '')) NOT LIKE '%@builder.io') OR ('{{emailFilter}}' = 'only_builder' AND lower(coalesce(user_id, '')) LIKE '%@builder.io'))), bounds AS (SELECT MIN(date::date) AS start_date, MAX(date::date) AS end_date FROM signup_events), dates AS (SELECT to_char(bounds.start_date + offsets.n, 'YYYY-MM-DD') AS date FROM bounds CROSS JOIN offsets WHERE bounds.start_date IS NOT NULL AND bounds.start_date + offsets.n <= bounds.end_date), templates AS (SELECT DISTINCT template FROM signup_events), daily AS (SELECT date, template, COUNT(*) AS count FROM signup_events GROUP BY date, template) SELECT dates.date, templates.template, COALESCE(daily.count, 0) AS count FROM dates CROSS JOIN templates LEFT JOIN daily ON daily.date = dates.date AND daily.template = templates.template ORDER BY dates.date, templates.template",
|
|
79
79
|
"config": {
|
|
80
80
|
"xKey": "date",
|
|
81
81
|
"yKey": "count",
|
|
@@ -67,6 +67,10 @@ export const dashboardRevisions = table(
|
|
|
67
67
|
t.dashboardId,
|
|
68
68
|
t.createdAt,
|
|
69
69
|
),
|
|
70
|
+
orgDashboardIdx: index("dashboard_revisions_org_dashboard_idx").on(
|
|
71
|
+
t.orgId,
|
|
72
|
+
t.dashboardId,
|
|
73
|
+
),
|
|
70
74
|
}),
|
|
71
75
|
);
|
|
72
76
|
|
|
@@ -109,6 +113,11 @@ export const dashboardReportSubscriptions = table(
|
|
|
109
113
|
enum: ["success", "error", "running"],
|
|
110
114
|
}),
|
|
111
115
|
lastError: text("last_error"),
|
|
116
|
+
lastCaptureAt: text("last_capture_at"),
|
|
117
|
+
lastCaptureMode: text("last_capture_mode", {
|
|
118
|
+
enum: ["full", "partial", "none"],
|
|
119
|
+
}),
|
|
120
|
+
lastCaptureError: text("last_capture_error"),
|
|
112
121
|
createdAt: text("created_at").notNull().default(now()),
|
|
113
122
|
updatedAt: text("updated_at").notNull().default(now()),
|
|
114
123
|
ownerEmail: text("owner_email").notNull().default("local@localhost"),
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
claimDueDashboardReportSubscriptions,
|
|
6
6
|
dashboardReportRetryAt,
|
|
7
7
|
markDashboardReportResult,
|
|
8
|
+
recordDashboardReportCaptureOutcome,
|
|
8
9
|
} from "../lib/dashboard-report-subscriptions";
|
|
9
10
|
|
|
10
11
|
let running = false;
|
|
@@ -26,6 +27,24 @@ async function persistDashboardReportResult(
|
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
async function persistDashboardReportCaptureOutcome(
|
|
31
|
+
...args: Parameters<typeof recordDashboardReportCaptureOutcome>
|
|
32
|
+
): Promise<void> {
|
|
33
|
+
try {
|
|
34
|
+
const persisted = await recordDashboardReportCaptureOutcome(...args);
|
|
35
|
+
if (!persisted) {
|
|
36
|
+
console.warn(
|
|
37
|
+
`[dashboard-report] Capture checkpoint was superseded for subscription ${args[0].id}`,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
} catch (err) {
|
|
41
|
+
console.error(
|
|
42
|
+
`[dashboard-report] Failed to persist capture checkpoint for subscription ${args[0].id}:`,
|
|
43
|
+
err instanceof Error ? err.message : String(err),
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
29
48
|
function maxReportsPerSweep(): number {
|
|
30
49
|
if (process.env.NETLIFY === "true") return 1;
|
|
31
50
|
const raw = process.env.DASHBOARD_REPORT_SWEEP_LIMIT?.trim();
|
|
@@ -71,6 +90,8 @@ export async function runDashboardReportsOnce(): Promise<{
|
|
|
71
90
|
() =>
|
|
72
91
|
sendDashboardReportSubscription(sub, {
|
|
73
92
|
skipEmailWithoutScreenshot: retryAt !== null,
|
|
93
|
+
onCaptureOutcome: (outcome) =>
|
|
94
|
+
persistDashboardReportCaptureOutcome(sub, outcome),
|
|
74
95
|
...(deliveryDeadlineAt ? { deadlineAt: deliveryDeadlineAt } : {}),
|
|
75
96
|
}),
|
|
76
97
|
);
|
|
@@ -15,7 +15,6 @@ export const INITIAL_TOOL_NAMES = [
|
|
|
15
15
|
"list-session-recordings",
|
|
16
16
|
"list-analyses",
|
|
17
17
|
"get-analysis",
|
|
18
|
-
"save-analysis",
|
|
19
18
|
// Dashboard/extension INSPECTION stays on the initial surface so a
|
|
20
19
|
// template-clone request can resolve and inspect the source on the first
|
|
21
20
|
// turn. The MUTATING writers (update-dashboard, mutate-dashboard,
|
|
@@ -105,6 +105,7 @@ export function buildDashboardAgentContext(
|
|
|
105
105
|
hiddenBy: dashboard.hiddenBy,
|
|
106
106
|
createdAt: dashboard.createdAt,
|
|
107
107
|
updatedAt: dashboard.updatedAt,
|
|
108
|
+
updatedBy: dashboard.updatedBy,
|
|
108
109
|
url: `/dashboards/${dashboard.id}`,
|
|
109
110
|
};
|
|
110
111
|
return options.includeConfig === false ? base : { ...base, ...config };
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
+
LEGACY_SIGNUPS_OVER_TIME_SQL,
|
|
3
|
+
LEGACY_SEED_SIGNUPS_OVER_TIME_SQL,
|
|
4
|
+
SIGNUPS_OVER_TIME_SQL,
|
|
2
5
|
type ExactFirstPartyPanelReplacement,
|
|
3
6
|
repairFirstPartyObservedRetentionPanels,
|
|
4
7
|
} from "./first-party-metric-catalog";
|
|
@@ -12,6 +15,14 @@ const NEW_VS_RECURRING_USERS_DESCRIPTION =
|
|
|
12
15
|
|
|
13
16
|
const CANONICAL_CUSTOM_PANEL_REPLACEMENTS: readonly ExactFirstPartyPanelReplacement[] =
|
|
14
17
|
[
|
|
18
|
+
{
|
|
19
|
+
id: "signups-over-time",
|
|
20
|
+
legacySql: [
|
|
21
|
+
LEGACY_SEED_SIGNUPS_OVER_TIME_SQL,
|
|
22
|
+
LEGACY_SIGNUPS_OVER_TIME_SQL,
|
|
23
|
+
],
|
|
24
|
+
sql: SIGNUPS_OVER_TIME_SQL,
|
|
25
|
+
},
|
|
15
26
|
{
|
|
16
27
|
id: "new-vs-recurring-users",
|
|
17
28
|
legacySql: [LEGACY_NEW_VS_RECURRING_USERS_SQL],
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
|
|
3
3
|
import { recordChange } from "@agent-native/core/server";
|
|
4
|
+
import { EMBED_TOKEN_QUERY_PARAM } from "@agent-native/core/shared";
|
|
4
5
|
import { and, asc, eq, isNull, lte, or, sql } from "drizzle-orm";
|
|
5
6
|
|
|
6
7
|
import { getDb, schema } from "../db/index.js";
|
|
@@ -34,12 +35,22 @@ export interface DashboardReportSubscription {
|
|
|
34
35
|
lastRunAt: string | null;
|
|
35
36
|
lastStatus: "success" | "error" | "running" | null;
|
|
36
37
|
lastError: string | null;
|
|
38
|
+
lastCaptureAt: string | null;
|
|
39
|
+
lastCaptureMode: DashboardReportCaptureMode | null;
|
|
40
|
+
lastCaptureError: string | null;
|
|
37
41
|
createdAt: string;
|
|
38
42
|
updatedAt: string;
|
|
39
43
|
ownerEmail: string;
|
|
40
44
|
orgId: string | null;
|
|
41
45
|
}
|
|
42
46
|
|
|
47
|
+
export type DashboardReportCaptureMode = "full" | "partial" | "none";
|
|
48
|
+
|
|
49
|
+
export interface DashboardReportCaptureOutcome {
|
|
50
|
+
mode: DashboardReportCaptureMode;
|
|
51
|
+
error?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
43
54
|
export interface AccessCtx {
|
|
44
55
|
email: string;
|
|
45
56
|
orgId: string | null;
|
|
@@ -89,7 +100,15 @@ function nowIso(): string {
|
|
|
89
100
|
const DASHBOARD_REPORT_ERROR_MAX_LENGTH = 2_000;
|
|
90
101
|
const DASHBOARD_REPORT_ERROR_OMISSION = "\n… [truncated] …\n";
|
|
91
102
|
|
|
103
|
+
export function redactDashboardReportDiagnostic(value: string): string {
|
|
104
|
+
return value.replace(
|
|
105
|
+
new RegExp(`(${EMBED_TOKEN_QUERY_PARAM}=)[^&\\s]+`, "gi"),
|
|
106
|
+
"$1[REDACTED]",
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
92
110
|
export function truncateDashboardReportError(error: string): string {
|
|
111
|
+
error = redactDashboardReportDiagnostic(error);
|
|
93
112
|
if (error.length <= DASHBOARD_REPORT_ERROR_MAX_LENGTH) return error;
|
|
94
113
|
|
|
95
114
|
const retainedLength =
|
|
@@ -307,6 +326,9 @@ function rowToSubscription(row: any): DashboardReportSubscription {
|
|
|
307
326
|
lastRunAt: row.lastRunAt ?? null,
|
|
308
327
|
lastStatus: row.lastStatus ?? null,
|
|
309
328
|
lastError: row.lastError ?? null,
|
|
329
|
+
lastCaptureAt: row.lastCaptureAt ?? null,
|
|
330
|
+
lastCaptureMode: row.lastCaptureMode ?? null,
|
|
331
|
+
lastCaptureError: row.lastCaptureError ?? null,
|
|
310
332
|
createdAt: row.createdAt,
|
|
311
333
|
updatedAt: row.updatedAt,
|
|
312
334
|
ownerEmail: row.ownerEmail,
|
|
@@ -578,3 +600,35 @@ export async function markDashboardReportResult(
|
|
|
578
600
|
})
|
|
579
601
|
.where(eq(schema.dashboardReportSubscriptions.id, sub.id));
|
|
580
602
|
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Persists the capture result before email delivery so a serverless cutoff
|
|
606
|
+
* cannot erase the browser diagnostics after a fallback message is accepted.
|
|
607
|
+
*/
|
|
608
|
+
export async function recordDashboardReportCaptureOutcome(
|
|
609
|
+
sub: DashboardReportSubscription,
|
|
610
|
+
outcome: DashboardReportCaptureOutcome,
|
|
611
|
+
): Promise<boolean> {
|
|
612
|
+
if (!sub.lastRunAt) return false;
|
|
613
|
+
|
|
614
|
+
const capturedAt = nowIso();
|
|
615
|
+
const db = getDb() as any;
|
|
616
|
+
const rows = await db
|
|
617
|
+
.update(schema.dashboardReportSubscriptions)
|
|
618
|
+
.set({
|
|
619
|
+
lastCaptureAt: capturedAt,
|
|
620
|
+
lastCaptureMode: outcome.mode,
|
|
621
|
+
lastCaptureError: outcome.error
|
|
622
|
+
? truncateDashboardReportError(outcome.error)
|
|
623
|
+
: null,
|
|
624
|
+
updatedAt: capturedAt,
|
|
625
|
+
})
|
|
626
|
+
.where(
|
|
627
|
+
and(
|
|
628
|
+
eq(schema.dashboardReportSubscriptions.id, sub.id),
|
|
629
|
+
eq(schema.dashboardReportSubscriptions.lastRunAt, sub.lastRunAt),
|
|
630
|
+
),
|
|
631
|
+
)
|
|
632
|
+
.returning();
|
|
633
|
+
return Boolean(rows[0]);
|
|
634
|
+
}
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
getReportDashboard,
|
|
30
30
|
normalizeDashboardReportRecipients,
|
|
31
31
|
type AccessCtx,
|
|
32
|
+
type DashboardReportCaptureOutcome,
|
|
32
33
|
type DashboardReportSubscription,
|
|
33
34
|
} from "./dashboard-report-subscriptions";
|
|
34
35
|
|
|
@@ -437,6 +438,7 @@ async function waitForDashboardReportReady(
|
|
|
437
438
|
timeout: number,
|
|
438
439
|
consoleErrors: string[] = [],
|
|
439
440
|
failedRequests: string[] = [],
|
|
441
|
+
expectedPanelIds: string[] = [],
|
|
440
442
|
): Promise<boolean> {
|
|
441
443
|
try {
|
|
442
444
|
await page.waitForFunction(
|
|
@@ -460,22 +462,34 @@ async function waitForDashboardReportReady(
|
|
|
460
462
|
const detail = await page
|
|
461
463
|
.evaluate(`(() => {
|
|
462
464
|
const root = document.querySelector("[data-dashboard-report-capture]");
|
|
465
|
+
const expectedPanelIds = ${JSON.stringify(expectedPanelIds.slice(0, REPORT_PANEL_STATE_DIAGNOSTICS_LIMIT))};
|
|
466
|
+
const cleanText = (value, limit = ${REPORT_PANEL_STATE_TEXT_LIMIT}) =>
|
|
467
|
+
String(value ?? "").replace(/\\s+/g, " ").trim().slice(0, limit);
|
|
468
|
+
const panelStates = expectedPanelIds.map((id) => {
|
|
469
|
+
const panel = Array.from(
|
|
470
|
+
root?.querySelectorAll("[data-dashboard-report-panel-id]") ?? [],
|
|
471
|
+
).find((node) =>
|
|
472
|
+
node.getAttribute("data-dashboard-report-panel-id") === id,
|
|
473
|
+
);
|
|
474
|
+
if (!panel) return { id: cleanText(id, 96), state: "missing" };
|
|
475
|
+
const errorNode = panel.querySelector(
|
|
476
|
+
"[data-dashboard-report-panel-error='true'], .text-red-400",
|
|
477
|
+
);
|
|
478
|
+
const error = cleanText(errorNode?.textContent);
|
|
479
|
+
return {
|
|
480
|
+
id: cleanText(id, 96),
|
|
481
|
+
title: cleanText(panel.getAttribute("data-dashboard-report-panel-title"), 120),
|
|
482
|
+
state: panel.querySelector("[data-dashboard-report-loading='true']")
|
|
483
|
+
? "loading"
|
|
484
|
+
: error
|
|
485
|
+
? "errored"
|
|
486
|
+
: "ready",
|
|
487
|
+
...(error ? { error } : {}),
|
|
488
|
+
};
|
|
489
|
+
});
|
|
463
490
|
return {
|
|
464
491
|
ready: root?.getAttribute("data-dashboard-report-ready") ?? null,
|
|
465
|
-
|
|
466
|
-
loadingPanels: Array.from(
|
|
467
|
-
root?.querySelectorAll("[data-dashboard-report-loading='true']") ?? [],
|
|
468
|
-
).reduce((panels, loadingNode) => {
|
|
469
|
-
const panel = loadingNode.closest("[data-dashboard-report-panel-id]");
|
|
470
|
-
const id = panel?.getAttribute("data-dashboard-report-panel-id");
|
|
471
|
-
if (!id || panels.some((entry) => entry.id === id)) return panels;
|
|
472
|
-
panels.push({
|
|
473
|
-
id,
|
|
474
|
-
title: panel?.getAttribute("data-dashboard-report-panel-title") ?? "",
|
|
475
|
-
});
|
|
476
|
-
return panels;
|
|
477
|
-
}, []),
|
|
478
|
-
text: document.body?.innerText?.slice(0, 1000) ?? "",
|
|
492
|
+
panelStates,
|
|
479
493
|
url: location.href,
|
|
480
494
|
};
|
|
481
495
|
})()`)
|
|
@@ -596,6 +610,93 @@ async function runBoundedBrowserCleanup(
|
|
|
596
610
|
const DIAGNOSTICS_PROBE_TIMEOUT_MS = 2_000;
|
|
597
611
|
const DIAGNOSTICS_MAX_LENGTH = 700;
|
|
598
612
|
const DIAGNOSTICS_COLLECTOR_LIMIT = 5;
|
|
613
|
+
const REPORT_PANEL_STATE_DIAGNOSTICS_LIMIT = REPORT_PANEL_CHUNK_SIZE;
|
|
614
|
+
const REPORT_PANEL_STATE_TEXT_LIMIT = 160;
|
|
615
|
+
const DIAGNOSTICS_URL_LIMIT = 120;
|
|
616
|
+
const DIAGNOSTICS_TEXT_SNAPSHOT_LIMIT = 180;
|
|
617
|
+
const DIAGNOSTICS_REQUEST_TRACKING_LIMIT = 50;
|
|
618
|
+
const DIAGNOSTICS_IN_FLIGHT_SERIALIZATION_LIMIT = 3;
|
|
619
|
+
|
|
620
|
+
type InFlightReportRequest = {
|
|
621
|
+
method: string;
|
|
622
|
+
url: string;
|
|
623
|
+
startedAt: number;
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
type RecentReportResource = {
|
|
627
|
+
method: string;
|
|
628
|
+
url: string;
|
|
629
|
+
status?: number;
|
|
630
|
+
ageMs?: number;
|
|
631
|
+
outcome: "response" | "finished" | "failed";
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
function redactDiagnosticUrl(value: unknown): string {
|
|
635
|
+
return String(value ?? "")
|
|
636
|
+
.replace(
|
|
637
|
+
/([?&](?:__an_embed_token|token|access_token|authorization|sql|query)=)[^&\s]*/gi,
|
|
638
|
+
"$1[REDACTED]",
|
|
639
|
+
)
|
|
640
|
+
.slice(0, DIAGNOSTICS_URL_LIMIT);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function sanitizeDiagnosticText(
|
|
644
|
+
value: unknown,
|
|
645
|
+
limit = DIAGNOSTICS_TEXT_SNAPSHOT_LIMIT,
|
|
646
|
+
): string {
|
|
647
|
+
return errorMessage(String(value ?? ""))
|
|
648
|
+
.replace(/\b(?:bearer\s+)[a-z0-9._~+/=-]+/gi, "Bearer [REDACTED]")
|
|
649
|
+
.replace(
|
|
650
|
+
/\b((?:access[_-]?token|auth(?:orization)?|session|cookie)\s*[:=]\s*)[^\s,;"'<>]+/gi,
|
|
651
|
+
"$1[REDACTED]",
|
|
652
|
+
)
|
|
653
|
+
.replace(/\s+/g, " ")
|
|
654
|
+
.trim()
|
|
655
|
+
.slice(0, limit);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function requestDiagnostic(req: any): InFlightReportRequest {
|
|
659
|
+
return {
|
|
660
|
+
method: sanitizeDiagnosticText(req.method?.(), 16) || "GET",
|
|
661
|
+
url: redactDiagnosticUrl(req.url?.()),
|
|
662
|
+
startedAt: Date.now(),
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function requestDiagnosticPriority(url: string): number {
|
|
667
|
+
return /\/(?:_agent-native\/actions|auth|session)(?:\/|[?]|$)|get-sql-dashboard/i.test(
|
|
668
|
+
url,
|
|
669
|
+
)
|
|
670
|
+
? 1
|
|
671
|
+
: 0;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function retainInFlightRequest(
|
|
675
|
+
requests: Map<any, InFlightReportRequest>,
|
|
676
|
+
req: any,
|
|
677
|
+
) {
|
|
678
|
+
if (
|
|
679
|
+
!requests.has(req) &&
|
|
680
|
+
requests.size >= DIAGNOSTICS_REQUEST_TRACKING_LIMIT
|
|
681
|
+
) {
|
|
682
|
+
const discard = [...requests.entries()].sort(([, left], [, right]) => {
|
|
683
|
+
const priority =
|
|
684
|
+
requestDiagnosticPriority(left.url) -
|
|
685
|
+
requestDiagnosticPriority(right.url);
|
|
686
|
+
return priority || left.startedAt - right.startedAt;
|
|
687
|
+
})[0];
|
|
688
|
+
if (discard) requests.delete(discard[0]);
|
|
689
|
+
}
|
|
690
|
+
requests.set(req, requestDiagnostic(req));
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function pushRecentResource(
|
|
694
|
+
resources: RecentReportResource[],
|
|
695
|
+
resource: RecentReportResource,
|
|
696
|
+
) {
|
|
697
|
+
if (resources.length >= DIAGNOSTICS_COLLECTOR_LIMIT) resources.shift();
|
|
698
|
+
resources.push(resource);
|
|
699
|
+
}
|
|
599
700
|
|
|
600
701
|
// netlify.toml's memory = "2gb" is plan-gated and can be silently ignored, so
|
|
601
702
|
// capture the actual lambda memory ceiling alongside current RSS.
|
|
@@ -614,8 +715,24 @@ async function collectPageDiagnostics(
|
|
|
614
715
|
page: any,
|
|
615
716
|
consoleErrors: string[],
|
|
616
717
|
failedRequests: string[],
|
|
718
|
+
inFlightRequests: Map<any, InFlightReportRequest>,
|
|
719
|
+
recentResources: RecentReportResource[],
|
|
617
720
|
): Promise<string> {
|
|
618
721
|
const memory = memoryDiagnostics();
|
|
722
|
+
const pendingRequests = () =>
|
|
723
|
+
[...inFlightRequests.values()]
|
|
724
|
+
.sort((left, right) => {
|
|
725
|
+
const priority =
|
|
726
|
+
requestDiagnosticPriority(right.url) -
|
|
727
|
+
requestDiagnosticPriority(left.url);
|
|
728
|
+
return priority || right.startedAt - left.startedAt;
|
|
729
|
+
})
|
|
730
|
+
.slice(0, DIAGNOSTICS_IN_FLIGHT_SERIALIZATION_LIMIT)
|
|
731
|
+
.map((request) => ({
|
|
732
|
+
method: request.method,
|
|
733
|
+
url: request.url,
|
|
734
|
+
ageMs: Math.max(0, Date.now() - request.startedAt),
|
|
735
|
+
}));
|
|
619
736
|
try {
|
|
620
737
|
let responsive = true;
|
|
621
738
|
let probeTimeout: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -638,7 +755,9 @@ async function collectPageDiagnostics(
|
|
|
638
755
|
if (!responsive) {
|
|
639
756
|
return `${memory} page unresponsive (renderer hung or crashed); consoleErrors=${JSON.stringify(
|
|
640
757
|
consoleErrors,
|
|
641
|
-
)} failedRequests=${JSON.stringify(failedRequests)}
|
|
758
|
+
)} failedRequests=${JSON.stringify(failedRequests)} inFlightRequests=${JSON.stringify(
|
|
759
|
+
pendingRequests(),
|
|
760
|
+
)} recentResources=${JSON.stringify(recentResources)}`.slice(
|
|
642
761
|
0,
|
|
643
762
|
DIAGNOSTICS_MAX_LENGTH,
|
|
644
763
|
);
|
|
@@ -651,8 +770,7 @@ async function collectPageDiagnostics(
|
|
|
651
770
|
// page may already be closed; leave url empty
|
|
652
771
|
}
|
|
653
772
|
|
|
654
|
-
let
|
|
655
|
-
let bodyText = "";
|
|
773
|
+
let pageState: Record<string, unknown> = {};
|
|
656
774
|
let detailsTimeout: ReturnType<typeof setTimeout> | undefined;
|
|
657
775
|
try {
|
|
658
776
|
// page.evaluate ignores setDefaultTimeout, so race it like the probe —
|
|
@@ -662,7 +780,21 @@ async function collectPageDiagnostics(
|
|
|
662
780
|
page.evaluate(
|
|
663
781
|
`(() => ({
|
|
664
782
|
title: document.title,
|
|
665
|
-
|
|
783
|
+
readyState: document.readyState,
|
|
784
|
+
capture: (() => {
|
|
785
|
+
const root = document.querySelector("[data-dashboard-report-capture]");
|
|
786
|
+
return {
|
|
787
|
+
present: Boolean(root),
|
|
788
|
+
ready: root?.getAttribute("data-dashboard-report-ready") ?? null,
|
|
789
|
+
phase: root?.getAttribute("data-dashboard-report-phase") ?? root?.dataset?.phase ?? null,
|
|
790
|
+
fetchState: root?.getAttribute("data-dashboard-report-fetch-state") ?? root?.dataset?.fetchState ?? null,
|
|
791
|
+
error: root?.getAttribute("data-dashboard-report-error") ?? root?.dataset?.error ?? null,
|
|
792
|
+
childCount: root?.children.length ?? 0,
|
|
793
|
+
htmlLength: root?.innerHTML.length ?? 0,
|
|
794
|
+
textLength: root?.textContent.length ?? 0,
|
|
795
|
+
};
|
|
796
|
+
})(),
|
|
797
|
+
bodyTextSnapshot: document.body?.textContent?.replace(/\\s+/g, " ").trim().slice(0, ${DIAGNOSTICS_TEXT_SNAPSHOT_LIMIT}) ?? "",
|
|
666
798
|
}))()`,
|
|
667
799
|
),
|
|
668
800
|
),
|
|
@@ -673,18 +805,46 @@ async function collectPageDiagnostics(
|
|
|
673
805
|
);
|
|
674
806
|
}),
|
|
675
807
|
]);
|
|
676
|
-
|
|
677
|
-
|
|
808
|
+
const raw = details as any;
|
|
809
|
+
pageState = {
|
|
810
|
+
title: sanitizeDiagnosticText(raw?.title),
|
|
811
|
+
readyState: sanitizeDiagnosticText(raw?.readyState, 32),
|
|
812
|
+
capture: raw?.capture
|
|
813
|
+
? {
|
|
814
|
+
present: Boolean(raw.capture.present),
|
|
815
|
+
ready: sanitizeDiagnosticText(raw.capture.ready, 32) || null,
|
|
816
|
+
phase: sanitizeDiagnosticText(raw.capture.phase, 80) || null,
|
|
817
|
+
fetchState:
|
|
818
|
+
sanitizeDiagnosticText(raw.capture.fetchState, 80) || null,
|
|
819
|
+
error: sanitizeDiagnosticText(raw.capture.error) || null,
|
|
820
|
+
childCount: Number.isFinite(raw.capture.childCount)
|
|
821
|
+
? raw.capture.childCount
|
|
822
|
+
: 0,
|
|
823
|
+
htmlLength: Number.isFinite(raw.capture.htmlLength)
|
|
824
|
+
? raw.capture.htmlLength
|
|
825
|
+
: 0,
|
|
826
|
+
textLength: Number.isFinite(raw.capture.textLength)
|
|
827
|
+
? raw.capture.textLength
|
|
828
|
+
: 0,
|
|
829
|
+
}
|
|
830
|
+
: { present: false },
|
|
831
|
+
bodyTextSnapshot: sanitizeDiagnosticText(raw?.bodyTextSnapshot),
|
|
832
|
+
};
|
|
678
833
|
} catch {
|
|
679
834
|
// best effort; leave title/bodyText empty
|
|
680
835
|
} finally {
|
|
681
836
|
if (detailsTimeout) clearTimeout(detailsTimeout);
|
|
682
837
|
}
|
|
683
838
|
|
|
839
|
+
const { capture, readyState, title, bodyTextSnapshot } = pageState;
|
|
684
840
|
return `${memory} page state: ${JSON.stringify({
|
|
685
|
-
|
|
841
|
+
capture,
|
|
842
|
+
readyState,
|
|
843
|
+
inFlightRequests: pendingRequests(),
|
|
844
|
+
recentResources,
|
|
845
|
+
url: redactDiagnosticUrl(url),
|
|
686
846
|
title,
|
|
687
|
-
|
|
847
|
+
bodyTextSnapshot,
|
|
688
848
|
consoleErrors,
|
|
689
849
|
failedRequests,
|
|
690
850
|
})}`.slice(0, DIAGNOSTICS_MAX_LENGTH);
|
|
@@ -743,30 +903,64 @@ async function captureDashboardChunk(
|
|
|
743
903
|
let captureStage = "pre-seeding the embed session";
|
|
744
904
|
const consoleErrors: string[] = [];
|
|
745
905
|
const failedRequests: string[] = [];
|
|
906
|
+
const inFlightRequests = new Map<any, InFlightReportRequest>();
|
|
907
|
+
const recentResources: RecentReportResource[] = [];
|
|
746
908
|
page.on("console", (msg: any) => {
|
|
747
909
|
if (
|
|
748
910
|
msg.type() !== "error" ||
|
|
749
911
|
consoleErrors.length >= DIAGNOSTICS_COLLECTOR_LIMIT
|
|
750
912
|
)
|
|
751
913
|
return;
|
|
752
|
-
consoleErrors.push(msg.text()
|
|
914
|
+
consoleErrors.push(sanitizeDiagnosticText(msg.text()));
|
|
915
|
+
});
|
|
916
|
+
page.on("request", (req: any) => {
|
|
917
|
+
retainInFlightRequest(inFlightRequests, req);
|
|
753
918
|
});
|
|
754
919
|
page.on("requestfailed", (req: any) => {
|
|
920
|
+
const request = inFlightRequests.get(req) ?? requestDiagnostic(req);
|
|
921
|
+
inFlightRequests.delete(req);
|
|
922
|
+
pushRecentResource(recentResources, {
|
|
923
|
+
method: request.method,
|
|
924
|
+
url: request.url,
|
|
925
|
+
ageMs: Math.max(0, Date.now() - request.startedAt),
|
|
926
|
+
outcome: "failed",
|
|
927
|
+
});
|
|
755
928
|
if (failedRequests.length >= DIAGNOSTICS_COLLECTOR_LIMIT) return;
|
|
756
929
|
failedRequests.push(
|
|
757
|
-
`${
|
|
930
|
+
`${request.method} ${request.url}: ${sanitizeDiagnosticText(req.failure()?.errorText ?? "failed")}`,
|
|
758
931
|
);
|
|
759
932
|
});
|
|
760
933
|
page.on("response", (res: any) => {
|
|
934
|
+
const req = res.request();
|
|
935
|
+
const request = inFlightRequests.get(req) ?? requestDiagnostic(req);
|
|
936
|
+
inFlightRequests.delete(req);
|
|
937
|
+
pushRecentResource(recentResources, {
|
|
938
|
+
method: request.method,
|
|
939
|
+
url: request.url,
|
|
940
|
+
status: res.status(),
|
|
941
|
+
ageMs: Math.max(0, Date.now() - request.startedAt),
|
|
942
|
+
outcome: "response",
|
|
943
|
+
});
|
|
761
944
|
if (
|
|
762
945
|
res.status() < 400 ||
|
|
763
946
|
failedRequests.length >= DIAGNOSTICS_COLLECTOR_LIMIT
|
|
764
947
|
)
|
|
765
948
|
return;
|
|
766
949
|
failedRequests.push(
|
|
767
|
-
`${
|
|
950
|
+
`${request.method} ${request.url}: HTTP ${res.status()}`,
|
|
768
951
|
);
|
|
769
952
|
});
|
|
953
|
+
page.on("requestfinished", (req: any) => {
|
|
954
|
+
const request = inFlightRequests.get(req);
|
|
955
|
+
if (!request) return;
|
|
956
|
+
inFlightRequests.delete(req);
|
|
957
|
+
pushRecentResource(recentResources, {
|
|
958
|
+
method: request.method,
|
|
959
|
+
url: request.url,
|
|
960
|
+
ageMs: Math.max(0, Date.now() - request.startedAt),
|
|
961
|
+
outcome: "finished",
|
|
962
|
+
});
|
|
963
|
+
});
|
|
770
964
|
try {
|
|
771
965
|
try {
|
|
772
966
|
await page.context().addCookies([
|
|
@@ -801,7 +995,13 @@ async function captureDashboardChunk(
|
|
|
801
995
|
});
|
|
802
996
|
} catch (err) {
|
|
803
997
|
const diagnostics = errorMessage(
|
|
804
|
-
await collectPageDiagnostics(
|
|
998
|
+
await collectPageDiagnostics(
|
|
999
|
+
page,
|
|
1000
|
+
consoleErrors,
|
|
1001
|
+
failedRequests,
|
|
1002
|
+
inFlightRequests,
|
|
1003
|
+
recentResources,
|
|
1004
|
+
),
|
|
805
1005
|
);
|
|
806
1006
|
throw new Error(`${errorMessage(err)}; ${diagnostics}`);
|
|
807
1007
|
}
|
|
@@ -811,6 +1011,7 @@ async function captureDashboardChunk(
|
|
|
811
1011
|
boundedStageTimeout(attempt.readyTimeout ?? timeout, deadlineAt),
|
|
812
1012
|
consoleErrors,
|
|
813
1013
|
failedRequests,
|
|
1014
|
+
expectedPanelIds,
|
|
814
1015
|
);
|
|
815
1016
|
captureStage = "validating the report chunk panels";
|
|
816
1017
|
await assertDashboardReportPanelWindow(page, expectedPanelIds);
|
|
@@ -825,6 +1026,7 @@ async function captureDashboardChunk(
|
|
|
825
1026
|
),
|
|
826
1027
|
consoleErrors,
|
|
827
1028
|
failedRequests,
|
|
1029
|
+
expectedPanelIds,
|
|
828
1030
|
);
|
|
829
1031
|
captureStage = "revalidating the report chunk panels";
|
|
830
1032
|
await assertDashboardReportPanelWindow(page, expectedPanelIds);
|
|
@@ -1138,6 +1340,9 @@ export async function sendDashboardReportSubscription(
|
|
|
1138
1340
|
requireScreenshot?: boolean;
|
|
1139
1341
|
skipEmailWithoutScreenshot?: boolean;
|
|
1140
1342
|
deadlineAt?: number;
|
|
1343
|
+
onCaptureOutcome?: (
|
|
1344
|
+
outcome: DashboardReportCaptureOutcome,
|
|
1345
|
+
) => Promise<void>;
|
|
1141
1346
|
} = {},
|
|
1142
1347
|
): Promise<{
|
|
1143
1348
|
dashboardUrl: string;
|
|
@@ -1169,6 +1374,10 @@ export async function sendDashboardReportSubscription(
|
|
|
1169
1374
|
!options.skipEmailWithoutScreenshot && !options.requireScreenshot,
|
|
1170
1375
|
captureTimeoutMs,
|
|
1171
1376
|
);
|
|
1377
|
+
await options.onCaptureOutcome?.({
|
|
1378
|
+
mode: capture.mode,
|
|
1379
|
+
...(capture.error ? { error: capture.error } : {}),
|
|
1380
|
+
});
|
|
1172
1381
|
if (capture.mode !== "full" && options.requireScreenshot) {
|
|
1173
1382
|
throw new Error(
|
|
1174
1383
|
capture.error
|
|
@@ -221,7 +221,10 @@ export function usesFirstPartyDashboardFilters(sql: string): boolean {
|
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
const TOTAL_SIGNUPS_SQL = `SELECT COUNT(*) AS signups FROM analytics_events WHERE event_name = 'signup' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER}`;
|
|
224
|
-
const
|
|
224
|
+
const LEGACY_SEED_SIGNUPS_TIME_RANGE_FILTER = `('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND ${EVENT_DATE_SQL} >= ${daysAgoSql(7)}) OR ('{{timeRange}}' = '30d' AND ${EVENT_DATE_SQL} >= ${daysAgoSql(30)}) OR ('{{timeRange}}' = '90d' AND ${EVENT_DATE_SQL} >= ${daysAgoSql(90)}) OR ('{{timeRange}}' = '180d' AND ${EVENT_DATE_SQL} >= ${daysAgoSql(180)}) OR ('{{timeRange}}' = '365d' AND ${EVENT_DATE_SQL} >= ${daysAgoSql(365)}))`;
|
|
225
|
+
export const LEGACY_SEED_SIGNUPS_OVER_TIME_SQL = `WITH offsets AS (SELECT (ROW_NUMBER() OVER (ORDER BY ${EVENT_DATE_SQL}) - 1)::int AS n FROM analytics_events LIMIT 800), signup_events AS (SELECT ${EVENT_DATE_SQL} AS date, ${TEMPLATE_EXPR} AS template FROM analytics_events WHERE event_name = 'signup' AND ${LEGACY_SEED_SIGNUPS_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER}), bounds AS (SELECT MIN(date::date) AS start_date, MAX(date::date) AS end_date FROM signup_events), dates AS (SELECT to_char(bounds.start_date + offsets.n, 'YYYY-MM-DD') AS date FROM bounds CROSS JOIN offsets WHERE bounds.start_date IS NOT NULL AND bounds.start_date + offsets.n <= bounds.end_date), templates AS (SELECT DISTINCT template FROM signup_events), daily AS (SELECT date, template, COUNT(*) AS count FROM signup_events GROUP BY date, template) SELECT dates.date, templates.template, COALESCE(daily.count, 0) AS count FROM dates CROSS JOIN templates LEFT JOIN daily ON daily.date = dates.date AND daily.template = templates.template ORDER BY dates.date, templates.template`;
|
|
226
|
+
export const LEGACY_SIGNUPS_OVER_TIME_SQL = `WITH offsets AS (SELECT (ROW_NUMBER() OVER (ORDER BY ${EVENT_DATE_SQL}) - 1)::int AS n FROM analytics_events LIMIT 800), signup_events AS (SELECT ${EVENT_DATE_SQL} AS date, ${TEMPLATE_EXPR} AS template FROM analytics_events WHERE event_name = 'signup' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER}), bounds AS (SELECT MIN(date::date) AS start_date, MAX(date::date) AS end_date FROM signup_events), dates AS (SELECT to_char(bounds.start_date + offsets.n, 'YYYY-MM-DD') AS date FROM bounds CROSS JOIN offsets WHERE bounds.start_date IS NOT NULL AND bounds.start_date + offsets.n <= bounds.end_date), templates AS (SELECT DISTINCT template FROM signup_events), daily AS (SELECT date, template, COUNT(*) AS count FROM signup_events GROUP BY date, template) SELECT dates.date, templates.template, COALESCE(daily.count, 0) AS count FROM dates CROSS JOIN templates LEFT JOIN daily ON daily.date = dates.date AND daily.template = templates.template ORDER BY dates.date, templates.template`;
|
|
227
|
+
export const SIGNUPS_OVER_TIME_SQL = `WITH digits AS (SELECT 0 AS n UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9), offsets AS (SELECT ones.n + tens.n * 10 + hundreds.n * 100 AS n FROM digits ones CROSS JOIN digits tens CROSS JOIN digits hundreds WHERE hundreds.n < 8), signup_events AS (SELECT ${EVENT_DATE_SQL} AS date, ${TEMPLATE_EXPR} AS template FROM analytics_events WHERE event_name = 'signup' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER}), bounds AS (SELECT MIN(date::date) AS start_date, MAX(date::date) AS end_date FROM signup_events), dates AS (SELECT to_char(bounds.start_date + offsets.n, 'YYYY-MM-DD') AS date FROM bounds CROSS JOIN offsets WHERE bounds.start_date IS NOT NULL AND bounds.start_date + offsets.n <= bounds.end_date), templates AS (SELECT DISTINCT template FROM signup_events), daily AS (SELECT date, template, COUNT(*) AS count FROM signup_events GROUP BY date, template) SELECT dates.date, templates.template, COALESCE(daily.count, 0) AS count FROM dates CROSS JOIN templates LEFT JOIN daily ON daily.date = dates.date AND daily.template = templates.template ORDER BY dates.date, templates.template`;
|
|
225
228
|
export const LEGACY_RETENTION_OVER_TIME_SQL = `WITH base AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS user_key, ${EVENT_DATE_SQL} AS event_date, user_id FROM analytics_events WHERE ${SIGNED_IN_PRODUCT_ACTIVITY_FILTER} AND ${DASHBOARD_EMAIL_FILTER}), first_seen AS (SELECT user_key, MIN(event_date) AS cohort_date FROM base GROUP BY user_key), anchor_dates AS (SELECT DISTINCT cohort_date AS date FROM first_seen WHERE cohort_date <= ${daysAgoSql(14)} AND ${dashboardTimeRangeFilter("cohort_date")}), cohort_windows AS (SELECT a.date, f.user_key, f.cohort_date FROM anchor_dates a JOIN first_seen f ON f.cohort_date >= ${rollingWindowStartSql()} AND f.cohort_date <= a.date), cohort_sizes AS (SELECT date, COUNT(DISTINCT user_key) AS users FROM cohort_windows GROUP BY date), periods AS (SELECT '1-7d return' AS period UNION ALL SELECT '7-14d return' AS period), retained AS (SELECT cw.date, '1-7d return' AS period, COUNT(DISTINCT cw.user_key) AS retained FROM cohort_windows cw JOIN base b ON b.user_key = cw.user_key AND b.event_date > cw.cohort_date AND b.event_date <= to_char(cw.cohort_date::date + INTERVAL '7 days', 'YYYY-MM-DD') GROUP BY cw.date UNION ALL SELECT cw.date, '7-14d return' AS period, COUNT(DISTINCT cw.user_key) AS retained FROM cohort_windows cw JOIN base b ON b.user_key = cw.user_key AND b.event_date >= to_char(cw.cohort_date::date + INTERVAL '7 days', 'YYYY-MM-DD') AND b.event_date <= to_char(cw.cohort_date::date + INTERVAL '14 days', 'YYYY-MM-DD') GROUP BY cw.date) SELECT cs.date, p.period, COALESCE(r.retained, 0) AS retained_users, cs.users AS cohort_users, COALESCE(r.retained::float / NULLIF(cs.users, 0), 0) AS rate FROM cohort_sizes cs CROSS JOIN periods p LEFT JOIN retained r ON r.date = cs.date AND r.period = p.period WHERE cs.users >= ${RETENTION_MIN_COHORT_SIZE} ORDER BY cs.date, p.period`;
|
|
226
229
|
export const LEGACY_ONE_DAY_RETENTION_BY_TEMPLATE_SQL = `WITH base AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS user_key, ${TEMPLATE_EXPR} AS template, ${EVENT_DATE_SQL} AS event_date, user_id FROM analytics_events WHERE ${SIGNED_IN_ACTIVITY_FILTER} AND ${DASHBOARD_EMAIL_FILTER} AND ${KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER}), ranked_first_seen AS (SELECT user_key, template, event_date AS cohort_date, ROW_NUMBER() OVER (PARTITION BY user_key ORDER BY event_date, template) AS rn FROM base), first_seen AS (SELECT user_key, template, cohort_date FROM ranked_first_seen WHERE rn = 1), cohorts AS (SELECT user_key, template, cohort_date FROM first_seen WHERE cohort_date <= ${daysAgoSql(7)} AND ${dashboardTimeRangeFilter("cohort_date")}), cohort_sizes AS (SELECT template, COUNT(DISTINCT user_key) AS users FROM cohorts GROUP BY template), retained AS (SELECT c.template, COUNT(DISTINCT c.user_key) AS retained FROM cohorts c JOIN base b ON b.user_key = c.user_key AND b.event_date > c.cohort_date AND b.event_date <= to_char(c.cohort_date::date + INTERVAL '7 days', 'YYYY-MM-DD') GROUP BY c.template) SELECT cs.template, COALESCE(r.retained, 0) AS retained_users, cs.users AS cohort_users, COALESCE(r.retained::float / NULLIF(cs.users, 0), 0) AS rate FROM cohort_sizes cs LEFT JOIN retained r ON r.template = cs.template WHERE cs.users >= ${PER_TEMPLATE_RETENTION_MIN_COHORT_SIZE} ORDER BY rate DESC, cs.users DESC, cs.template`;
|
|
227
230
|
export const LEGACY_SEVEN_DAY_RETENTION_BY_TEMPLATE_SQL = `WITH base AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS user_key, ${TEMPLATE_EXPR} AS template, ${EVENT_DATE_SQL} AS event_date, user_id FROM analytics_events WHERE ${SIGNED_IN_ACTIVITY_FILTER} AND ${DASHBOARD_EMAIL_FILTER} AND ${KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER}), ranked_first_seen AS (SELECT user_key, template, event_date AS cohort_date, ROW_NUMBER() OVER (PARTITION BY user_key ORDER BY event_date, template) AS rn FROM base), first_seen AS (SELECT user_key, template, cohort_date FROM ranked_first_seen WHERE rn = 1), cohorts AS (SELECT user_key, template, cohort_date FROM first_seen WHERE cohort_date <= ${daysAgoSql(14)} AND ${dashboardTimeRangeFilter("cohort_date")}), cohort_sizes AS (SELECT template, COUNT(DISTINCT user_key) AS users FROM cohorts GROUP BY template), retained AS (SELECT c.template, COUNT(DISTINCT c.user_key) AS retained FROM cohorts c JOIN base b ON b.user_key = c.user_key AND b.event_date >= to_char(c.cohort_date::date + INTERVAL '7 days', 'YYYY-MM-DD') AND b.event_date <= to_char(c.cohort_date::date + INTERVAL '14 days', 'YYYY-MM-DD') GROUP BY c.template) SELECT cs.template, COALESCE(r.retained, 0) AS retained_users, cs.users AS cohort_users, COALESCE(r.retained::float / NULLIF(cs.users, 0), 0) AS rate FROM cohort_sizes cs LEFT JOIN retained r ON r.template = cs.template WHERE cs.users >= ${PER_TEMPLATE_RETENTION_MIN_COHORT_SIZE} ORDER BY rate DESC, cs.users DESC, cs.template`;
|