@agent-native/core 0.99.1 → 0.99.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 +1 -1
- package/corpus/core/CHANGELOG.md +19 -0
- package/corpus/core/docs/content/a2a-protocol.mdx +42 -0
- package/corpus/core/docs/content/locales/ar-SA/a2a-protocol.mdx +42 -0
- package/corpus/core/docs/content/locales/de-DE/a2a-protocol.mdx +42 -0
- package/corpus/core/docs/content/locales/es-ES/a2a-protocol.mdx +42 -0
- package/corpus/core/docs/content/locales/fr-FR/a2a-protocol.mdx +42 -0
- package/corpus/core/docs/content/locales/hi-IN/a2a-protocol.mdx +42 -0
- package/corpus/core/docs/content/locales/ja-JP/a2a-protocol.mdx +42 -0
- package/corpus/core/docs/content/locales/ko-KR/a2a-protocol.mdx +42 -0
- package/corpus/core/docs/content/locales/pt-BR/a2a-protocol.mdx +42 -0
- package/corpus/core/docs/content/locales/zh-CN/a2a-protocol.mdx +42 -0
- package/corpus/core/docs/content/locales/zh-TW/a2a-protocol.mdx +42 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +12 -38
- package/corpus/core/src/audit/config.ts +0 -1
- package/corpus/core/src/chat-threads/store.ts +57 -41
- package/corpus/core/src/client/AssistantChat.tsx +1 -2
- package/corpus/core/src/client/chat/message-components.tsx +15 -3
- package/corpus/core/src/client/chat/tool-call-display.tsx +18 -1
- package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +5 -9
- package/corpus/core/src/client/extensions/ExtensionViewer.tsx +0 -133
- package/corpus/core/src/client/index.ts +7 -0
- package/corpus/core/src/client/settings/DemoModeSection.tsx +9 -65
- package/corpus/core/src/client/settings/SettingsPanel.tsx +1 -1
- package/corpus/core/src/client/use-db-sync.ts +3 -15
- package/corpus/core/src/client/use-demo-mode-status.ts +16 -30
- package/corpus/core/src/demo/browser-state.ts +47 -0
- package/corpus/core/src/demo/fetch-interceptor.ts +12 -59
- package/corpus/core/src/deploy/workspace-deploy.ts +2 -2
- package/corpus/core/src/server/action-discovery.ts +0 -1
- package/corpus/core/src/server/core-routes-plugin.ts +0 -30
- package/corpus/core/src/templates/workspace-core/.agents/skills/native-navigation/SKILL.md +67 -0
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +48 -7
- package/corpus/templates/analytics/AGENTS.md +4 -4
- package/corpus/templates/analytics/actions/compose-dashboard.ts +12 -1
- package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +15 -4
- package/corpus/templates/analytics/actions/mutate-dashboard.ts +2 -1
- package/corpus/templates/analytics/actions/update-dashboard.ts +14 -0
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +21 -27
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +4 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/interpolate.ts +21 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +14 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +31 -12
- package/corpus/templates/analytics/changelog/2026-07-13-dashboard-charts-now-honor-the-selected-time-range-by-defaul.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-13-keep-real-authorized-identities-in-analytics-incident-lookup.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-13-the-analytics-agents-tab-is-available-again-from-the-main-na.md +6 -0
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +3 -3
- package/corpus/templates/analytics/server/lib/agent-readable-resource-context.ts +4 -0
- package/corpus/templates/analytics/server/lib/dashboard-time-scope.ts +170 -0
- package/corpus/templates/analytics/server/lib/error-capture.ts +6 -62
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +20 -5
- package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +1 -1
- package/corpus/templates/analytics/server/lib/session-replay.ts +5 -111
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +1 -0
- package/corpus/templates/assets/app/routes/library.tsx +3 -4
- package/corpus/templates/assets/changelog/2026-07-13-internal-library-links-open-in-new-tabs.md +6 -0
- package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +43 -40
- package/corpus/templates/calendar/app/pages/ManageBookingPage.tsx +6 -8
- package/corpus/templates/calendar/changelog/2026-07-13-booking-links-and-previews-open-in-new-tabs.md +6 -0
- package/corpus/templates/clips/app/components/player/sign-in-prompt-dialog.tsx +4 -7
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +19 -9
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +5 -3
- package/corpus/templates/clips/changelog/2026-07-13-recording-links-open-in-new-tabs.md +6 -0
- package/corpus/templates/content/app/components/editor/ContentReferencePreview.tsx +6 -8
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +5 -3
- package/corpus/templates/content/app/components/editor/database/FormView.tsx +6 -9
- package/corpus/templates/content/changelog/2026-07-13-document-links-open-in-new-tabs.md +6 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +33 -16
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +18 -1
- package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +4 -4
- package/corpus/templates/design/app/pages/DesignSystems.tsx +8 -10
- package/corpus/templates/design/app/pages/Index.tsx +6 -1
- package/corpus/templates/design/app/pages/Present.tsx +12 -6
- package/corpus/templates/design/changelog/2026-07-13-design-navigation-links-open-in-new-tabs.md +6 -0
- package/corpus/templates/design/changelog/2026-07-13-design-session-replays-include-preview-iframes.md +6 -0
- package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +11 -11
- package/corpus/templates/forms/app/pages/FormsListPage.tsx +6 -8
- package/corpus/templates/forms/changelog/2026-07-13-form-navigation-links-open-in-new-tabs.md +6 -0
- package/corpus/templates/mail/app/pages/DraftQueuePage.tsx +3 -8
- package/corpus/templates/mail/changelog/2026-07-13-settings-links-open-in-new-tabs.md +6 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +13 -37
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/audit/config.d.ts.map +1 -1
- package/dist/audit/config.js +0 -1
- package/dist/audit/config.js.map +1 -1
- package/dist/chat-threads/store.d.ts.map +1 -1
- package/dist/chat-threads/store.js +47 -33
- package/dist/chat-threads/store.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +1 -1
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/chat/message-components.d.ts.map +1 -1
- package/dist/client/chat/message-components.js +9 -3
- package/dist/client/chat/message-components.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts +3 -1
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +10 -2
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.js +2 -6
- package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
- package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionViewer.js +2 -59
- package/dist/client/extensions/ExtensionViewer.js.map +1 -1
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/settings/DemoModeSection.d.ts +1 -13
- package/dist/client/settings/DemoModeSection.d.ts.map +1 -1
- package/dist/client/settings/DemoModeSection.js +5 -52
- package/dist/client/settings/DemoModeSection.js.map +1 -1
- package/dist/client/settings/SettingsPanel.js +1 -1
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/use-db-sync.d.ts.map +1 -1
- package/dist/client/use-db-sync.js +3 -7
- package/dist/client/use-db-sync.js.map +1 -1
- package/dist/client/use-demo-mode-status.d.ts +3 -4
- package/dist/client/use-demo-mode-status.d.ts.map +1 -1
- package/dist/client/use-demo-mode-status.js +8 -23
- package/dist/client/use-demo-mode-status.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/demo/browser-state.d.ts +10 -0
- package/dist/demo/browser-state.d.ts.map +1 -0
- package/dist/demo/browser-state.js +49 -0
- package/dist/demo/browser-state.js.map +1 -0
- package/dist/demo/fetch-interceptor.d.ts +2 -7
- package/dist/demo/fetch-interceptor.d.ts.map +1 -1
- package/dist/demo/fetch-interceptor.js +12 -56
- package/dist/demo/fetch-interceptor.js.map +1 -1
- package/dist/deploy/workspace-deploy.js +2 -2
- package/dist/deploy/workspace-deploy.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +5 -5
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +0 -1
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +0 -27
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/templates/workspace-core/.agents/skills/native-navigation/SKILL.md +67 -0
- package/docs/content/a2a-protocol.mdx +42 -0
- package/docs/content/locales/ar-SA/a2a-protocol.mdx +42 -0
- package/docs/content/locales/de-DE/a2a-protocol.mdx +42 -0
- package/docs/content/locales/es-ES/a2a-protocol.mdx +42 -0
- package/docs/content/locales/fr-FR/a2a-protocol.mdx +42 -0
- package/docs/content/locales/hi-IN/a2a-protocol.mdx +42 -0
- package/docs/content/locales/ja-JP/a2a-protocol.mdx +42 -0
- package/docs/content/locales/ko-KR/a2a-protocol.mdx +42 -0
- package/docs/content/locales/pt-BR/a2a-protocol.mdx +42 -0
- package/docs/content/locales/zh-CN/a2a-protocol.mdx +42 -0
- package/docs/content/locales/zh-TW/a2a-protocol.mdx +42 -0
- package/package.json +1 -1
- package/src/templates/workspace-core/.agents/skills/native-navigation/SKILL.md +67 -0
- package/corpus/core/src/demo/actions/toggle-demo-mode.ts +0 -23
- package/corpus/core/src/demo/config.ts +0 -57
- package/dist/demo/actions/toggle-demo-mode.d.ts +0 -8
- package/dist/demo/actions/toggle-demo-mode.d.ts.map +0 -1
- package/dist/demo/actions/toggle-demo-mode.js +0 -21
- package/dist/demo/actions/toggle-demo-mode.js.map +0 -1
- package/dist/demo/config.d.ts +0 -9
- package/dist/demo/config.d.ts.map +0 -1
- package/dist/demo/config.js +0 -58
- package/dist/demo/config.js.map +0 -1
|
@@ -13,6 +13,7 @@ import { z } from "zod";
|
|
|
13
13
|
|
|
14
14
|
import { interpolate } from "../app/pages/adhoc/sql-dashboard/interpolate";
|
|
15
15
|
import { dryRunQuery } from "../server/lib/bigquery";
|
|
16
|
+
import { validateFirstPartyDashboardTimeScope } from "../server/lib/dashboard-time-scope";
|
|
16
17
|
import {
|
|
17
18
|
upsertDashboard,
|
|
18
19
|
upsertDashboardWithRetry,
|
|
@@ -421,8 +422,20 @@ export async function validatePanelSql(
|
|
|
421
422
|
const raw = typeof p.sql === "string" ? p.sql : "";
|
|
422
423
|
if (raw.trim()) {
|
|
423
424
|
try {
|
|
425
|
+
const timeScopeError = validateFirstPartyDashboardTimeScope(
|
|
426
|
+
p,
|
|
427
|
+
config,
|
|
428
|
+
i,
|
|
429
|
+
);
|
|
430
|
+
if (timeScopeError) return timeScopeError;
|
|
424
431
|
validateFirstPartyAnalyticsSql(interpolate(raw, vars));
|
|
425
432
|
} catch (e: any) {
|
|
433
|
+
if (
|
|
434
|
+
typeof e?.message === "string" &&
|
|
435
|
+
e.message.startsWith("panel[")
|
|
436
|
+
) {
|
|
437
|
+
return e.message;
|
|
438
|
+
}
|
|
426
439
|
return `panel[${i}] "${p.title || p.id}" first-party analytics SQL is invalid: ${e?.message ?? e}`;
|
|
427
440
|
}
|
|
428
441
|
}
|
|
@@ -552,6 +565,7 @@ export default defineAction({
|
|
|
552
565
|
"Use this action when creating a brand-new dashboard from a complete config, for the UI full-config save path, or for an explicitly requested low-level JSON-pointer compatibility edit. " +
|
|
553
566
|
"Do not use `ops` or `panelOrder` for ordinary agent edits like moving charts, adding panels to an existing dashboard, changing widths, or updating panel config; call `mutate-dashboard` once with the full edit script. " +
|
|
554
567
|
"When this action is appropriate, provide only one of `ops`, `panelOrder`, or `config`; `config` replaces the whole dashboard config. " +
|
|
568
|
+
"First-party event panels must bind to a declared dashboard time filter with `{{timeRange}}` or date-range variables. Intentional fixed-window, cohort-history, and all-time exceptions must be explicit in `panel.config.timeScope`; unbounded first-party SQL is rejected at save time. " +
|
|
555
569
|
"To add a shipped catalog template's panels to an existing dashboard, prefer `install-dashboard-template` with `mergePanels: true` — it appends the template's panels in one call without you having to author each panel. " +
|
|
556
570
|
"The result is compact by default: `panelCount`, `appliedOps`, `panelOrder`, `firstPanelIds`, and `summary`. Set `returnConfig: true` only when you truly need the full config in the tool result. " +
|
|
557
571
|
"The UI auto-refreshes after this action — do NOT call `refresh-screen`.",
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
IconReportAnalytics,
|
|
15
15
|
IconSearch,
|
|
16
16
|
IconArchive,
|
|
17
|
+
IconActivity,
|
|
17
18
|
IconHeartbeat,
|
|
18
19
|
IconPin,
|
|
19
20
|
IconPlus,
|
|
@@ -26,7 +27,6 @@ import {
|
|
|
26
27
|
IconPlayerPlay,
|
|
27
28
|
IconLayoutSidebarLeftCollapse,
|
|
28
29
|
IconLayoutSidebarLeftExpand,
|
|
29
|
-
IconShieldCheck,
|
|
30
30
|
} from "@tabler/icons-react";
|
|
31
31
|
import {
|
|
32
32
|
useQuery,
|
|
@@ -97,7 +97,7 @@ import {
|
|
|
97
97
|
type ChatThreadSummary,
|
|
98
98
|
} from "@agent-native/core/client";
|
|
99
99
|
import { ExtensionsSidebarSection } from "@agent-native/core/client/extensions";
|
|
100
|
-
import { OrgSwitcher
|
|
100
|
+
import { OrgSwitcher } from "@agent-native/core/client/org";
|
|
101
101
|
|
|
102
102
|
import {
|
|
103
103
|
AlertDialog,
|
|
@@ -1593,7 +1593,6 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
|
|
|
1593
1593
|
const navigate = useNavigate();
|
|
1594
1594
|
const t = useT();
|
|
1595
1595
|
const queryClient = useQueryClient();
|
|
1596
|
-
const { canManageOrg } = useOrgRole();
|
|
1597
1596
|
const { setTheme } = useTheme();
|
|
1598
1597
|
|
|
1599
1598
|
const isAskRoute = location.pathname === "/ask";
|
|
@@ -2428,16 +2427,12 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
|
|
|
2428
2427
|
href: "/monitoring",
|
|
2429
2428
|
active: location.pathname.startsWith("/monitoring"),
|
|
2430
2429
|
},
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
active: location.pathname.startsWith("/agents"),
|
|
2438
|
-
},
|
|
2439
|
-
]
|
|
2440
|
-
: []),
|
|
2430
|
+
{
|
|
2431
|
+
icon: IconActivity,
|
|
2432
|
+
label: t("navigation.agents"),
|
|
2433
|
+
href: "/agents",
|
|
2434
|
+
active: location.pathname.startsWith("/agents"),
|
|
2435
|
+
},
|
|
2441
2436
|
{
|
|
2442
2437
|
icon: IconDatabase,
|
|
2443
2438
|
label: t("navigation.dataSources"),
|
|
@@ -2641,20 +2636,19 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
|
|
|
2641
2636
|
{t("navigation.monitoring")}
|
|
2642
2637
|
</Link>
|
|
2643
2638
|
|
|
2644
|
-
{
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
) : null}
|
|
2639
|
+
{/* Agents link */}
|
|
2640
|
+
<Link
|
|
2641
|
+
to="/agents"
|
|
2642
|
+
className={cn(
|
|
2643
|
+
"order-6 flex items-center gap-3 rounded-lg px-3 py-2 transition-colors hover:text-primary",
|
|
2644
|
+
location.pathname.startsWith("/agents")
|
|
2645
|
+
? "bg-sidebar-accent text-sidebar-accent-foreground"
|
|
2646
|
+
: "text-muted-foreground hover:bg-sidebar-accent/50",
|
|
2647
|
+
)}
|
|
2648
|
+
>
|
|
2649
|
+
<IconActivity className="h-4 w-4" />
|
|
2650
|
+
{t("navigation.agents")}
|
|
2651
|
+
</Link>
|
|
2658
2652
|
|
|
2659
2653
|
{/* Data Sources link */}
|
|
2660
2654
|
<Link
|
|
@@ -297,7 +297,10 @@ const PanelCell = memo(function PanelCell({
|
|
|
297
297
|
[panel, vars],
|
|
298
298
|
);
|
|
299
299
|
const resolvedSql = useMemo(
|
|
300
|
-
() =>
|
|
300
|
+
() =>
|
|
301
|
+
interpolate(serializePanelSql(panel.sql), vars, {
|
|
302
|
+
failClosedTimeVariables: true,
|
|
303
|
+
}),
|
|
301
304
|
[panel.sql, vars],
|
|
302
305
|
);
|
|
303
306
|
const handleSelectForChat = useCallback(
|
|
@@ -14,9 +14,23 @@ function escapeSqlValue(value: string): string {
|
|
|
14
14
|
return value.replace(/'/g, "''");
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
export interface InterpolateOptions {
|
|
18
|
+
/**
|
|
19
|
+
* Replace missing/empty time variables with a value that cannot satisfy the
|
|
20
|
+
* canonical dashboard predicates. This keeps a stale or malformed panel
|
|
21
|
+
* fail-closed at render time instead of turning into an all-time query.
|
|
22
|
+
*/
|
|
23
|
+
failClosedTimeVariables?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isTimeVariable(name: string): boolean {
|
|
27
|
+
return name === "timeRange" || /(?:Start|End)$/.test(name);
|
|
28
|
+
}
|
|
29
|
+
|
|
17
30
|
export function interpolate(
|
|
18
31
|
sql: string | undefined | null,
|
|
19
32
|
vars: Record<string, string> = {},
|
|
33
|
+
options: InterpolateOptions = {},
|
|
20
34
|
): string {
|
|
21
35
|
// Defensive: a malformed dashboard config (e.g. agent wrote a panel without
|
|
22
36
|
// a `sql` field) should not crash the page. Treat missing/non-string SQL
|
|
@@ -33,6 +47,13 @@ export function interpolate(
|
|
|
33
47
|
|
|
34
48
|
return withConditionals.replace(/\{\{(\w+)\}\}/g, (_match, name) => {
|
|
35
49
|
const value = vars[name];
|
|
50
|
+
if (
|
|
51
|
+
options.failClosedTimeVariables &&
|
|
52
|
+
isTimeVariable(name) &&
|
|
53
|
+
(value == null || value.length === 0)
|
|
54
|
+
) {
|
|
55
|
+
return "__missing_dashboard_time_filter__";
|
|
56
|
+
}
|
|
36
57
|
if (value == null) return "";
|
|
37
58
|
return escapeSqlValue(String(value));
|
|
38
59
|
});
|
|
@@ -63,7 +63,21 @@ export interface PivotConfig {
|
|
|
63
63
|
valueKey: string;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Declares how a query's time coverage relates to the dashboard filter.
|
|
68
|
+
* `dashboard` is the default for ordinary event metrics; the other values are
|
|
69
|
+
* explicit exceptions that make intentional history scans visible to agents
|
|
70
|
+
* and reviewers.
|
|
71
|
+
*/
|
|
72
|
+
export type DashboardTimeScope =
|
|
73
|
+
| "dashboard"
|
|
74
|
+
| "fixed-window"
|
|
75
|
+
| "cohort-history"
|
|
76
|
+
| "all-time";
|
|
77
|
+
|
|
66
78
|
export interface SqlPanelConfig {
|
|
79
|
+
/** Time coverage contract for first-party SQL panels. */
|
|
80
|
+
timeScope?: DashboardTimeScope;
|
|
67
81
|
xKey?: string;
|
|
68
82
|
yKey?: string;
|
|
69
83
|
yKeys?: string[];
|
|
@@ -47,6 +47,13 @@ import { Link, useParams } from "react-router";
|
|
|
47
47
|
|
|
48
48
|
import { Button } from "@/components/ui/button";
|
|
49
49
|
import { Card, CardContent } from "@/components/ui/card";
|
|
50
|
+
import {
|
|
51
|
+
DropdownMenu,
|
|
52
|
+
DropdownMenuContent,
|
|
53
|
+
DropdownMenuRadioGroup,
|
|
54
|
+
DropdownMenuRadioItem,
|
|
55
|
+
DropdownMenuTrigger,
|
|
56
|
+
} from "@/components/ui/dropdown-menu";
|
|
50
57
|
import { Input } from "@/components/ui/input";
|
|
51
58
|
import {
|
|
52
59
|
Popover,
|
|
@@ -1087,22 +1094,34 @@ function ReplayPlayer({
|
|
|
1087
1094
|
{formatClock(totalTime)}
|
|
1088
1095
|
</span>
|
|
1089
1096
|
|
|
1090
|
-
<
|
|
1091
|
-
|
|
1097
|
+
<DropdownMenu>
|
|
1098
|
+
<DropdownMenuTrigger asChild>
|
|
1092
1099
|
<button
|
|
1093
|
-
key={option}
|
|
1094
1100
|
type="button"
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
"bg-background text-foreground shadow-sm",
|
|
1099
|
-
)}
|
|
1100
|
-
onClick={() => updateSpeed(option)}
|
|
1101
|
+
disabled={disabled}
|
|
1102
|
+
aria-label={`${speed}x`}
|
|
1103
|
+
className="inline-flex h-8 min-w-11 items-center justify-center rounded-md border px-2 text-xs font-medium tabular-nums transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50"
|
|
1101
1104
|
>
|
|
1102
|
-
{
|
|
1105
|
+
{speed}x
|
|
1103
1106
|
</button>
|
|
1104
|
-
|
|
1105
|
-
|
|
1107
|
+
</DropdownMenuTrigger>
|
|
1108
|
+
<DropdownMenuContent align="center" side="top">
|
|
1109
|
+
<DropdownMenuRadioGroup
|
|
1110
|
+
value={String(speed)}
|
|
1111
|
+
onValueChange={(value) => updateSpeed(Number(value))}
|
|
1112
|
+
>
|
|
1113
|
+
{SPEED_OPTIONS.map((option) => (
|
|
1114
|
+
<DropdownMenuRadioItem
|
|
1115
|
+
key={option}
|
|
1116
|
+
value={String(option)}
|
|
1117
|
+
className="tabular-nums"
|
|
1118
|
+
>
|
|
1119
|
+
{option}x
|
|
1120
|
+
</DropdownMenuRadioItem>
|
|
1121
|
+
))}
|
|
1122
|
+
</DropdownMenuRadioGroup>
|
|
1123
|
+
</DropdownMenuContent>
|
|
1124
|
+
</DropdownMenu>
|
|
1106
1125
|
|
|
1107
1126
|
<Tooltip>
|
|
1108
1127
|
<TooltipTrigger asChild>
|
|
@@ -226,7 +226,7 @@
|
|
|
226
226
|
"chartType": "bar",
|
|
227
227
|
"source": "first-party",
|
|
228
228
|
"width": 2,
|
|
229
|
-
"sql": "SELECT COALESCE(NULLIF(app, ''), 'unknown') AS app, COUNT(*) AS count FROM analytics_events WHERE event_name = 'session status' GROUP BY COALESCE(NULLIF(app, ''), 'unknown') ORDER BY count DESC LIMIT 20",
|
|
229
|
+
"sql": "SELECT COALESCE(NULLIF(app, ''), 'unknown') AS app, COUNT(*) AS count FROM analytics_events WHERE event_name = 'session status' 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')) GROUP BY COALESCE(NULLIF(app, ''), 'unknown') ORDER BY count DESC LIMIT 20",
|
|
230
230
|
"config": {
|
|
231
231
|
"xKey": "app",
|
|
232
232
|
"yKey": "count",
|
|
@@ -240,7 +240,7 @@
|
|
|
240
240
|
"chartType": "bar",
|
|
241
241
|
"source": "first-party",
|
|
242
242
|
"width": 1,
|
|
243
|
-
"sql": "SELECT COALESCE(NULLIF(signed_in, ''), 'unknown') AS signed_in, COUNT(*) AS count FROM analytics_events WHERE event_name = 'session status' GROUP BY COALESCE(NULLIF(signed_in, ''), 'unknown') ORDER BY signed_in",
|
|
243
|
+
"sql": "SELECT COALESCE(NULLIF(signed_in, ''), 'unknown') AS signed_in, COUNT(*) AS count FROM analytics_events WHERE event_name = 'session status' 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')) GROUP BY COALESCE(NULLIF(signed_in, ''), 'unknown') ORDER BY signed_in",
|
|
244
244
|
"config": {
|
|
245
245
|
"xKey": "signed_in",
|
|
246
246
|
"yKey": "count",
|
|
@@ -254,7 +254,7 @@
|
|
|
254
254
|
"chartType": "area",
|
|
255
255
|
"source": "first-party",
|
|
256
256
|
"width": 1,
|
|
257
|
-
"sql": "SELECT event_date AS date, COUNT(*) AS count FROM analytics_events WHERE event_name = 'session status' GROUP BY event_date ORDER BY date",
|
|
257
|
+
"sql": "SELECT event_date AS date, COUNT(*) AS count FROM analytics_events WHERE event_name = 'session status' 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')) GROUP BY event_date ORDER BY date",
|
|
258
258
|
"config": {
|
|
259
259
|
"xKey": "date",
|
|
260
260
|
"yKey": "count",
|
|
@@ -56,6 +56,10 @@ function panelSummaries(config: Record<string, unknown>) {
|
|
|
56
56
|
width: typeof panel.width === "number" ? panel.width : undefined,
|
|
57
57
|
columns: typeof panel.columns === "number" ? panel.columns : undefined,
|
|
58
58
|
tab: typeof panel.tab === "string" ? panel.tab : undefined,
|
|
59
|
+
timeScope:
|
|
60
|
+
typeof panelConfig.timeScope === "string"
|
|
61
|
+
? panelConfig.timeScope
|
|
62
|
+
: undefined,
|
|
59
63
|
description:
|
|
60
64
|
typeof panelConfig.description === "string"
|
|
61
65
|
? panelConfig.description
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import type { DashboardTimeScope } from "../../app/pages/adhoc/sql-dashboard/types";
|
|
2
|
+
|
|
3
|
+
export const DASHBOARD_TIME_SCOPES: readonly DashboardTimeScope[] = [
|
|
4
|
+
"dashboard",
|
|
5
|
+
"fixed-window",
|
|
6
|
+
"cohort-history",
|
|
7
|
+
"all-time",
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
type DashboardFilterLike = {
|
|
11
|
+
id?: unknown;
|
|
12
|
+
key?: unknown;
|
|
13
|
+
type?: unknown;
|
|
14
|
+
default?: unknown;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type DashboardPanelLike = {
|
|
18
|
+
id?: unknown;
|
|
19
|
+
title?: unknown;
|
|
20
|
+
chartType?: unknown;
|
|
21
|
+
source?: unknown;
|
|
22
|
+
sql?: unknown;
|
|
23
|
+
config?: unknown;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type DashboardConfigLike = {
|
|
27
|
+
filters?: unknown;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const TEMPORAL_VARIABLE_RE = /\{\{(timeRange|[A-Za-z_]\w*(?:Start|End))\}\}/g;
|
|
31
|
+
|
|
32
|
+
/** Return the time variables a panel actually references. */
|
|
33
|
+
export function extractDashboardTimeVariables(sql: string): string[] {
|
|
34
|
+
const variables = new Set<string>();
|
|
35
|
+
for (const match of sql.matchAll(TEMPORAL_VARIABLE_RE)) {
|
|
36
|
+
variables.add(match[1]);
|
|
37
|
+
}
|
|
38
|
+
return [...variables];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function filterId(filter: DashboardFilterLike): string {
|
|
42
|
+
const value = filter.id ?? filter.key;
|
|
43
|
+
return typeof value === "string" ? value.trim() : "";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function filtersFrom(config: DashboardConfigLike): DashboardFilterLike[] {
|
|
47
|
+
return Array.isArray(config.filters)
|
|
48
|
+
? (config.filters as DashboardFilterLike[])
|
|
49
|
+
: [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function panelConfig(panel: DashboardPanelLike): Record<string, unknown> {
|
|
53
|
+
return panel.config && typeof panel.config === "object"
|
|
54
|
+
? (panel.config as Record<string, unknown>)
|
|
55
|
+
: {};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function hasExplicitLowerBound(sql: string): boolean {
|
|
59
|
+
// This intentionally recognizes the bounded shapes used by the shipped
|
|
60
|
+
// first-party catalog. It is a compatibility escape hatch for fixed-window
|
|
61
|
+
// catalog metrics; new ad-hoc panels should use a dashboard placeholder.
|
|
62
|
+
return /\b(?:event_date|timestamp|started_at|ended_at|cohort_date|created_at|date)\b[\s\S]{0,100}?(?:>=|>)\s*[\s\S]{0,160}?(?:CURRENT_DATE|CURRENT_TIMESTAMP|NOW\s*\(|INTERVAL\s*['"]|DATE\s*['"]|TIMESTAMP\s*['"]|\b20\d{2}-\d{2}-\d{2}\b)/i.test(
|
|
63
|
+
sql,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function hasIntentionalHistoryDescription(
|
|
68
|
+
panel: DashboardPanelLike,
|
|
69
|
+
config: Record<string, unknown>,
|
|
70
|
+
): boolean {
|
|
71
|
+
const values = [panel.title, config.description].filter(
|
|
72
|
+
(value): value is string => typeof value === "string",
|
|
73
|
+
);
|
|
74
|
+
return /all[- ]?time|lifetime|histor(?:y|ical)/i.test(values.join(" "));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function scopeValue(panel: DashboardPanelLike): DashboardTimeScope | undefined {
|
|
78
|
+
const value = panelConfig(panel).timeScope;
|
|
79
|
+
return typeof value === "string" &&
|
|
80
|
+
(DASHBOARD_TIME_SCOPES as readonly string[]).includes(value)
|
|
81
|
+
? (value as DashboardTimeScope)
|
|
82
|
+
: undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Validate the temporal contract for a first-party dashboard panel.
|
|
87
|
+
*
|
|
88
|
+
* Ordinary panels must bind to a dashboard filter. Intentional exceptions are
|
|
89
|
+
* explicit in `config.timeScope`, while legacy fixed-window catalog SQL is
|
|
90
|
+
* accepted when its lower bound is visible in the SQL itself.
|
|
91
|
+
*/
|
|
92
|
+
export function validateFirstPartyDashboardTimeScope(
|
|
93
|
+
panel: DashboardPanelLike,
|
|
94
|
+
dashboard: DashboardConfigLike,
|
|
95
|
+
index: number,
|
|
96
|
+
): string | null {
|
|
97
|
+
if (panel.source !== "first-party") return null;
|
|
98
|
+
if (panel.chartType === "section" || panel.chartType === "extension") {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const sql = typeof panel.sql === "string" ? panel.sql : "";
|
|
103
|
+
const variables = extractDashboardTimeVariables(sql);
|
|
104
|
+
const filters = filtersFrom(dashboard);
|
|
105
|
+
const label =
|
|
106
|
+
typeof panel.title === "string" && panel.title.trim()
|
|
107
|
+
? `"${panel.title}"`
|
|
108
|
+
: `at index ${index}`;
|
|
109
|
+
|
|
110
|
+
for (const variable of variables) {
|
|
111
|
+
const expectedFilterId =
|
|
112
|
+
variable === "timeRange"
|
|
113
|
+
? "timeRange"
|
|
114
|
+
: variable.replace(/(?:Start|End)$/, "");
|
|
115
|
+
const filter = filters.find(
|
|
116
|
+
(candidate) => filterId(candidate) === expectedFilterId,
|
|
117
|
+
);
|
|
118
|
+
if (!filter) {
|
|
119
|
+
return `panel[${index}] ${label} uses {{${variable}}} but config.filters has no matching "${expectedFilterId}" filter; bind first-party SQL to a declared dashboard time filter`;
|
|
120
|
+
}
|
|
121
|
+
if (variable === "timeRange") {
|
|
122
|
+
if (filter.type !== "select") {
|
|
123
|
+
return `panel[${index}] ${label} uses {{timeRange}}, but filter "timeRange" must have type "select"`;
|
|
124
|
+
}
|
|
125
|
+
} else if (filter.type !== "date-range") {
|
|
126
|
+
return `panel[${index}] ${label} uses {{${variable}}}, but filter "${expectedFilterId}" must have type "date-range"`;
|
|
127
|
+
}
|
|
128
|
+
if (typeof filter.default !== "string" || !filter.default.trim()) {
|
|
129
|
+
return `panel[${index}] ${label} uses a time filter without a non-empty default; choose a bounded default such as "90d" so missing filter state cannot become all-time`;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const configuredScope = panelConfig(panel).timeScope;
|
|
134
|
+
if (
|
|
135
|
+
configuredScope !== undefined &&
|
|
136
|
+
(typeof configuredScope !== "string" ||
|
|
137
|
+
!(DASHBOARD_TIME_SCOPES as readonly string[]).includes(configuredScope))
|
|
138
|
+
) {
|
|
139
|
+
return `panel[${index}] ${label} config.timeScope must be one of ${DASHBOARD_TIME_SCOPES.join(", ")}`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const scope = scopeValue(panel);
|
|
143
|
+
const hasDashboardBinding = variables.length > 0;
|
|
144
|
+
const hasLowerBound = hasExplicitLowerBound(sql);
|
|
145
|
+
|
|
146
|
+
if (scope === "dashboard" && !hasDashboardBinding) {
|
|
147
|
+
return `panel[${index}] ${label} declares timeScope "dashboard" but does not reference {{timeRange}} or a date-range variable`;
|
|
148
|
+
}
|
|
149
|
+
if (scope === "fixed-window" && hasDashboardBinding) {
|
|
150
|
+
return `panel[${index}] ${label} declares timeScope "fixed-window" but uses dashboard time variables; use timeScope "dashboard" or remove the placeholders`;
|
|
151
|
+
}
|
|
152
|
+
if (scope === "fixed-window" && !hasLowerBound) {
|
|
153
|
+
return `panel[${index}] ${label} declares timeScope "fixed-window" without a recognizable lower date bound`;
|
|
154
|
+
}
|
|
155
|
+
if (scope === "all-time" && hasDashboardBinding) {
|
|
156
|
+
return `panel[${index}] ${label} declares timeScope "all-time" but uses dashboard time variables; use timeScope "dashboard" for filter-bound output`;
|
|
157
|
+
}
|
|
158
|
+
if (
|
|
159
|
+
scope === "all-time" &&
|
|
160
|
+
!hasIntentionalHistoryDescription(panel, panelConfig(panel))
|
|
161
|
+
) {
|
|
162
|
+
return `panel[${index}] ${label} declares timeScope "all-time"; add a description or title that says lifetime, historical, or all-time so the exception is explicit`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!scope && !hasDashboardBinding && !hasLowerBound) {
|
|
166
|
+
return `panel[${index}] ${label} reads first-party analytics without a time bound; use {{timeRange}} with a non-empty default filter, or explicitly set config.timeScope to "cohort-history" or "all-time" for intentional history scans`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
@@ -18,7 +18,6 @@ import { fileURLToPath } from "node:url";
|
|
|
18
18
|
* reads go through `accessFilter` so an org-scoped key surfaces its issues to
|
|
19
19
|
* the whole org exactly like session recordings.
|
|
20
20
|
*/
|
|
21
|
-
import { appStateGet } from "@agent-native/core/application-state";
|
|
22
21
|
import { notifyWithDelivery } from "@agent-native/core/notifications";
|
|
23
22
|
import { recordChange } from "@agent-native/core/server";
|
|
24
23
|
import { accessFilter } from "@agent-native/core/sharing";
|
|
@@ -1099,56 +1098,6 @@ export interface ListErrorIssuesFilters {
|
|
|
1099
1098
|
limit?: number;
|
|
1100
1099
|
}
|
|
1101
1100
|
|
|
1102
|
-
export const ERROR_REPORTING_ANONYMOUS_EMAIL = "anonymous@builder.io";
|
|
1103
|
-
const ERROR_REPORTING_EMAIL_PATTERN = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi;
|
|
1104
|
-
|
|
1105
|
-
/**
|
|
1106
|
-
* Error reports contain emails in identity fields, messages, stacks, URLs,
|
|
1107
|
-
* tags, extra context, and breadcrumbs. Redact every string at this owning
|
|
1108
|
-
* read seam so demo mode never depends on browser interception timing.
|
|
1109
|
-
*/
|
|
1110
|
-
export function anonymizeErrorReportingEmails<T>(value: T): T {
|
|
1111
|
-
if (typeof value === "string") {
|
|
1112
|
-
return value.replace(
|
|
1113
|
-
ERROR_REPORTING_EMAIL_PATTERN,
|
|
1114
|
-
ERROR_REPORTING_ANONYMOUS_EMAIL,
|
|
1115
|
-
) as T;
|
|
1116
|
-
}
|
|
1117
|
-
if (Array.isArray(value)) {
|
|
1118
|
-
return value.map((item) => anonymizeErrorReportingEmails(item)) as T;
|
|
1119
|
-
}
|
|
1120
|
-
if (value && typeof value === "object") {
|
|
1121
|
-
if (value instanceof Date) return value;
|
|
1122
|
-
const output: Record<string, unknown> = {};
|
|
1123
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
1124
|
-
output[key] = anonymizeErrorReportingEmails(entry);
|
|
1125
|
-
}
|
|
1126
|
-
return output as T;
|
|
1127
|
-
}
|
|
1128
|
-
return value;
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
async function isErrorDemoModeEnabled(
|
|
1132
|
-
userEmail?: string | null,
|
|
1133
|
-
): Promise<boolean> {
|
|
1134
|
-
if (process.env.DEMO_MODE === "true") return true;
|
|
1135
|
-
if (!userEmail) return false;
|
|
1136
|
-
try {
|
|
1137
|
-
// Keep demo state tied to the caller whose issues are being read.
|
|
1138
|
-
const state = await appStateGet(userEmail, "demo-mode");
|
|
1139
|
-
return state?.enabled === true;
|
|
1140
|
-
} catch {
|
|
1141
|
-
return false;
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
function anonymizeErrorReportingEmailsInDemoMode<T>(
|
|
1146
|
-
value: T,
|
|
1147
|
-
enabled: boolean,
|
|
1148
|
-
): T {
|
|
1149
|
-
return enabled ? anonymizeErrorReportingEmails(value) : value;
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
1101
|
export interface ErrorIssueSummary {
|
|
1153
1102
|
id: string;
|
|
1154
1103
|
fingerprint: string;
|
|
@@ -1221,7 +1170,6 @@ export async function listErrorIssues(
|
|
|
1221
1170
|
scope: ErrorReadScope,
|
|
1222
1171
|
filters: ListErrorIssuesFilters = {},
|
|
1223
1172
|
): Promise<ErrorIssueSummary[]> {
|
|
1224
|
-
const demoModeEnabled = isErrorDemoModeEnabled(scope.userEmail);
|
|
1225
1173
|
const db = getDb() as any;
|
|
1226
1174
|
const limit = Math.min(
|
|
1227
1175
|
MAX_ISSUE_LIMIT,
|
|
@@ -1312,7 +1260,7 @@ export async function listErrorIssues(
|
|
|
1312
1260
|
template: row.template ?? null,
|
|
1313
1261
|
sparkline: sparklines.get(row.id) ?? new Array(SPARKLINE_DAYS).fill(0),
|
|
1314
1262
|
}));
|
|
1315
|
-
return
|
|
1263
|
+
return issues;
|
|
1316
1264
|
}
|
|
1317
1265
|
|
|
1318
1266
|
export interface ErrorEventDetail {
|
|
@@ -1404,7 +1352,6 @@ export async function getErrorIssue(
|
|
|
1404
1352
|
issueId: string,
|
|
1405
1353
|
options: { eventsLimit?: number } = {},
|
|
1406
1354
|
): Promise<ErrorIssueDetail> {
|
|
1407
|
-
const demoModeEnabled = isErrorDemoModeEnabled(scope.userEmail);
|
|
1408
1355
|
const db = getDb() as any;
|
|
1409
1356
|
const [issueRow] = await db
|
|
1410
1357
|
.select()
|
|
@@ -1530,14 +1477,11 @@ export async function getErrorIssue(
|
|
|
1530
1477
|
sparkline: sparklines.get(issueId) ?? new Array(SPARKLINE_DAYS).fill(0),
|
|
1531
1478
|
};
|
|
1532
1479
|
|
|
1533
|
-
return
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
},
|
|
1539
|
-
await demoModeEnabled,
|
|
1540
|
-
);
|
|
1480
|
+
return {
|
|
1481
|
+
issue,
|
|
1482
|
+
events,
|
|
1483
|
+
sessions: Array.from(sessions.values()),
|
|
1484
|
+
};
|
|
1541
1485
|
}
|
|
1542
1486
|
|
|
1543
1487
|
export interface UpdateErrorIssueInput {
|