@agent-native/core 0.77.24 → 0.78.1
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 +2 -2
- package/corpus/core/CHANGELOG.md +20 -0
- package/corpus/core/docs/content/template-analytics.md +5 -0
- package/corpus/core/docs/content/tracking.md +45 -0
- package/corpus/core/package.json +3 -1
- package/corpus/core/src/agent/types.ts +22 -0
- package/corpus/core/src/client/AgentPanel.tsx +34 -18
- package/corpus/core/src/client/AssistantChat.tsx +40 -0
- package/corpus/core/src/client/agent-chat.ts +173 -0
- package/corpus/core/src/client/analytics.ts +141 -5
- package/corpus/core/src/client/composer/TiptapComposer.tsx +316 -60
- package/corpus/core/src/client/composer/extensions/MentionReference.tsx +3 -0
- package/corpus/core/src/client/composer/types.ts +22 -0
- package/corpus/core/src/client/index.ts +14 -0
- package/corpus/core/src/client/session-replay.ts +800 -0
- package/corpus/core/src/db/schema.ts +12 -0
- package/corpus/core/src/deploy/build.ts +75 -0
- package/corpus/core/src/private-blob/index.ts +18 -0
- package/corpus/core/src/private-blob/registry.ts +241 -0
- package/corpus/core/src/private-blob/types.ts +55 -0
- package/corpus/core/src/scripts/utils.ts +34 -6
- package/corpus/core/src/server/agent-chat-plugin.ts +10 -0
- package/corpus/core/src/server/email.ts +5 -0
- package/corpus/core/src/server/index.ts +1 -0
- package/corpus/core/src/styles/agent-native.css +24 -0
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +1 -1
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +43 -0
- package/corpus/templates/analytics/AGENTS.md +32 -1
- package/corpus/templates/analytics/DEVELOPING.md +9 -1
- package/corpus/templates/analytics/actions/delete-dashboard-report-subscription.ts +24 -0
- package/corpus/templates/analytics/actions/get-session-replay-events.ts +50 -0
- package/corpus/templates/analytics/actions/get-session-replay-summary.ts +27 -0
- package/corpus/templates/analytics/actions/list-dashboard-report-subscriptions.ts +24 -0
- package/corpus/templates/analytics/actions/list-session-recordings.ts +59 -0
- package/corpus/templates/analytics/actions/navigate.ts +15 -5
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +2 -2
- package/corpus/templates/analytics/actions/save-dashboard-report-subscription.ts +36 -0
- package/corpus/templates/analytics/actions/send-dashboard-report-now.ts +60 -0
- package/corpus/templates/analytics/actions/view-screen.ts +50 -2
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +51 -2
- package/corpus/templates/analytics/app/components/layout/Header.tsx +4 -1
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +14 -2
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +6 -6
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +35 -6
- package/corpus/templates/analytics/app/i18n-data.ts +1119 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/EmailReportDialog.tsx +437 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +11 -12
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +43 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +136 -43
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/report-filters.ts +26 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +450 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +419 -0
- package/corpus/templates/analytics/app/routes/overview.tsx +13 -6
- package/corpus/templates/analytics/app/routes/sessions.$recordingId.tsx +10 -0
- package/corpus/templates/analytics/app/routes/sessions._index.tsx +10 -0
- package/corpus/templates/analytics/changelog/2026-06-25-analytics-now-opens-directly-to-ask-instead-of-the-old-overv.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-25-dashboard-charts-now-show-reliable-blue-placement-lines-whil.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-dashboards-can-now-send-scheduled-daily-email-reports-from-t.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-session-replay-browser-sessions-can-now-be-reviewed-from.md +6 -0
- package/corpus/templates/analytics/package.json +5 -2
- package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +131 -0
- package/corpus/templates/analytics/scripts/seed-session-replay.ts +198 -0
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +140 -27
- package/corpus/templates/analytics/server/db/index.ts +12 -0
- package/corpus/templates/analytics/server/db/schema.ts +131 -0
- package/corpus/templates/analytics/server/handlers/session-replay.ts +281 -0
- package/corpus/templates/analytics/server/handlers/sql-query.ts +8 -348
- package/corpus/templates/analytics/server/jobs/dashboard-report.ts +66 -0
- package/corpus/templates/analytics/server/jobs/session-replay-retention.ts +29 -0
- package/corpus/templates/analytics/server/lib/dashboard-panel-query.ts +365 -0
- package/corpus/templates/analytics/server/lib/dashboard-report-subscriptions.ts +473 -0
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +405 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +63 -18
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +98 -26
- package/corpus/templates/analytics/server/lib/session-replay.ts +1645 -0
- package/corpus/templates/analytics/server/plugins/auth.ts +1 -1
- package/corpus/templates/analytics/server/plugins/dashboard-report-jobs.ts +47 -0
- package/corpus/templates/analytics/server/plugins/db.ts +200 -0
- package/corpus/templates/analytics/server/plugins/session-replay-retention-jobs.ts +41 -0
- package/corpus/templates/analytics/server/routes/api/analytics/replay.options.ts +1 -0
- package/corpus/templates/analytics/server/routes/api/analytics/replay.post.ts +1 -0
- package/corpus/templates/analytics/server/routes/api/dashboard-reports/run.post.ts +61 -0
- package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId]/chunks/[seq].get.ts +1 -0
- package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId]/events.get.ts +1 -0
- package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId]/manifest.get.ts +1 -0
- package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId].get.ts +1 -0
- package/corpus/templates/analytics/server/routes/api/session-replay/recordings.get.ts +1 -0
- package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +10 -2
- package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +14 -7
- package/corpus/templates/assets/.agents/skills/library-management/SKILL.md +12 -0
- package/corpus/templates/assets/AGENTS.md +18 -3
- package/corpus/templates/assets/actions/_image-model-default.ts +24 -0
- package/corpus/templates/assets/actions/dismiss-variant-slots.ts +15 -22
- package/corpus/templates/assets/actions/generate-asset.ts +30 -23
- package/corpus/templates/assets/actions/generate-image-batch.ts +76 -29
- package/corpus/templates/assets/actions/generate-image.ts +46 -30
- package/corpus/templates/assets/actions/generate-video.ts +18 -3
- package/corpus/templates/assets/actions/list-assets.ts +39 -8
- package/corpus/templates/assets/actions/list-libraries.ts +24 -2
- package/corpus/templates/assets/actions/navigate.ts +1 -1
- package/corpus/templates/assets/actions/open-asset-picker.ts +38 -1
- package/corpus/templates/assets/actions/refresh-generation-run.ts +10 -0
- package/corpus/templates/assets/actions/save-generated-image.ts +21 -19
- package/corpus/templates/assets/actions/variant-slots.ts +131 -28
- package/corpus/templates/assets/actions/view-screen.ts +9 -0
- package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +425 -0
- package/corpus/templates/assets/app/components/layout/Header.tsx +3 -1
- package/corpus/templates/assets/app/components/layout/Layout.tsx +5 -0
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +4 -6
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +47 -21
- package/corpus/templates/assets/app/i18n-data.ts +1815 -0
- package/corpus/templates/assets/app/lib/libraries.ts +6 -0
- package/corpus/templates/assets/app/lib/picker-chat-handoff.ts +71 -0
- package/corpus/templates/assets/app/routes/_index.tsx +4 -0
- package/corpus/templates/assets/app/routes/asset.$id.tsx +2 -2
- package/corpus/templates/assets/app/routes/audit.tsx +1 -1
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +1557 -2164
- package/corpus/templates/assets/app/routes/brand-kits._index.tsx +7 -191
- package/corpus/templates/assets/app/routes/brand-kits.tsx +1 -5
- package/corpus/templates/assets/app/routes/libraries.tsx +4 -4
- package/corpus/templates/assets/app/routes/library.$id.tsx +9 -7
- package/corpus/templates/assets/app/routes/library.tsx +1454 -175
- package/corpus/templates/assets/server/db/index.ts +1 -1
- package/corpus/templates/assets/server/lib/generation.ts +86 -15
- package/corpus/templates/assets/server/plugins/agent-chat.ts +196 -0
- package/corpus/templates/assets/server/plugins/db.ts +5 -0
- package/corpus/templates/assets/shared/api.ts +6 -3
- package/dist/agent/types.d.ts +21 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +6 -2
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +10 -4
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +6 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +15 -3
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/agent-chat.d.ts +32 -0
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +113 -0
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/analytics.d.ts +22 -3
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +96 -4
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +2 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +228 -56
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/composer/extensions/MentionReference.d.ts.map +1 -1
- package/dist/client/composer/extensions/MentionReference.js +3 -0
- package/dist/client/composer/extensions/MentionReference.js.map +1 -1
- package/dist/client/composer/types.d.ts +21 -0
- package/dist/client/composer/types.d.ts.map +1 -1
- package/dist/client/composer/types.js.map +1 -1
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -2
- package/dist/client/index.js.map +1 -1
- package/dist/client/session-replay.d.ts +41 -0
- package/dist/client/session-replay.d.ts.map +1 -0
- package/dist/client/session-replay.js +595 -0
- package/dist/client/session-replay.js.map +1 -0
- 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/db/schema.d.ts +3 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +6 -2
- package/dist/db/schema.js.map +1 -1
- package/dist/deploy/build.d.ts +8 -0
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +66 -0
- package/dist/deploy/build.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +8 -8
- package/dist/private-blob/index.d.ts +3 -0
- package/dist/private-blob/index.d.ts.map +1 -0
- package/dist/private-blob/index.js +2 -0
- package/dist/private-blob/index.js.map +1 -0
- package/dist/private-blob/registry.d.ts +10 -0
- package/dist/private-blob/registry.d.ts.map +1 -0
- package/dist/private-blob/registry.js +152 -0
- package/dist/private-blob/registry.js.map +1 -0
- package/dist/private-blob/types.d.ts +51 -0
- package/dist/private-blob/types.d.ts.map +1 -0
- package/dist/private-blob/types.js +2 -0
- package/dist/private-blob/types.js.map +1 -0
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/scripts/utils.d.ts +4 -4
- package/dist/scripts/utils.d.ts.map +1 -1
- package/dist/scripts/utils.js +30 -6
- package/dist/scripts/utils.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +5 -0
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +2 -2
- package/dist/server/email.d.ts +2 -0
- package/dist/server/email.d.ts.map +1 -1
- package/dist/server/email.js +3 -0
- package/dist/server/email.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/styles/agent-native.css +24 -0
- package/docs/content/template-analytics.md +5 -0
- package/docs/content/tracking.md +45 -0
- package/package.json +3 -1
- package/corpus/templates/analytics/app/pages/overview/OverviewPage.tsx +0 -209
|
@@ -1,255 +1,12 @@
|
|
|
1
1
|
import { readBody } from "@agent-native/core/server";
|
|
2
2
|
import { defineEventHandler, setResponseStatus } from "h3";
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import { runQuery } from "../lib/bigquery";
|
|
4
|
+
import { runApiHandlerWithContext } from "../lib/credentials";
|
|
6
5
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import { queryFirstPartyAnalytics } from "../lib/first-party-analytics";
|
|
12
|
-
import { runReport } from "../lib/google-analytics";
|
|
13
|
-
import {
|
|
14
|
-
runPrometheusPanel,
|
|
15
|
-
serializePanelDescriptorInput,
|
|
16
|
-
} from "../lib/prometheus";
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* ga4 panels carry a JSON blob in `sql` describing the GA4 Data API call.
|
|
20
|
-
* Shape: { metrics: string[]; dimensions?: string[]; days?: number;
|
|
21
|
-
* startDate?: string; endDate?: string }. Dates are resolved from
|
|
22
|
-
* `days` when startDate/endDate are omitted so seeded dashboards can use
|
|
23
|
-
* the simpler `{"days": 30}` form.
|
|
24
|
-
*/
|
|
25
|
-
async function runGa4Panel(raw: string): Promise<{
|
|
26
|
-
rows: Record<string, unknown>[];
|
|
27
|
-
schema: { name: string; type: string }[];
|
|
28
|
-
}> {
|
|
29
|
-
let parsed: {
|
|
30
|
-
metrics?: unknown;
|
|
31
|
-
dimensions?: unknown;
|
|
32
|
-
days?: unknown;
|
|
33
|
-
startDate?: unknown;
|
|
34
|
-
endDate?: unknown;
|
|
35
|
-
filter?: unknown;
|
|
36
|
-
};
|
|
37
|
-
try {
|
|
38
|
-
parsed = JSON.parse(raw);
|
|
39
|
-
} catch (err: any) {
|
|
40
|
-
throw new Error(
|
|
41
|
-
`ga4 panel sql must be a JSON object with metrics/dimensions/days: ${err?.message ?? err}`,
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
if (!parsed || typeof parsed !== "object") {
|
|
45
|
-
throw new Error("ga4 panel sql must be a JSON object");
|
|
46
|
-
}
|
|
47
|
-
const metrics = Array.isArray(parsed.metrics)
|
|
48
|
-
? parsed.metrics.filter((m): m is string => typeof m === "string")
|
|
49
|
-
: [];
|
|
50
|
-
if (metrics.length === 0) {
|
|
51
|
-
throw new Error("ga4 panel requires at least one metric");
|
|
52
|
-
}
|
|
53
|
-
const dimensions = Array.isArray(parsed.dimensions)
|
|
54
|
-
? parsed.dimensions.filter((d): d is string => typeof d === "string")
|
|
55
|
-
: [];
|
|
56
|
-
const days =
|
|
57
|
-
typeof parsed.days === "number" ? parsed.days : Number(parsed.days);
|
|
58
|
-
const startDate =
|
|
59
|
-
typeof parsed.startDate === "string" && parsed.startDate
|
|
60
|
-
? parsed.startDate
|
|
61
|
-
: Number.isFinite(days) && days > 0
|
|
62
|
-
? `${days}daysAgo`
|
|
63
|
-
: "7daysAgo";
|
|
64
|
-
const endDate =
|
|
65
|
-
typeof parsed.endDate === "string" && parsed.endDate
|
|
66
|
-
? parsed.endDate
|
|
67
|
-
: "today";
|
|
68
|
-
|
|
69
|
-
const dimensionFilter =
|
|
70
|
-
parsed.filter && typeof parsed.filter === "object"
|
|
71
|
-
? (parsed.filter as Record<string, unknown>)
|
|
72
|
-
: undefined;
|
|
73
|
-
|
|
74
|
-
const report = await runReport(
|
|
75
|
-
dimensions,
|
|
76
|
-
metrics,
|
|
77
|
-
{ startDate, endDate },
|
|
78
|
-
dimensionFilter,
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
// Flatten each GA4 row to { dimensionName: value, metricName: value } so
|
|
82
|
-
// downstream chart renderers treat it identically to SQL rows. Metrics are
|
|
83
|
-
// parsed to numbers since every chart type (metric card, bar, line, table)
|
|
84
|
-
// relies on numeric typing for y-axis detection.
|
|
85
|
-
const rows: Record<string, unknown>[] = (report.rows ?? []).map((row) => {
|
|
86
|
-
const out: Record<string, unknown> = {};
|
|
87
|
-
dimensions.forEach((name, i) => {
|
|
88
|
-
out[name] = row.dimensionValues?.[i]?.value ?? "";
|
|
89
|
-
});
|
|
90
|
-
metrics.forEach((name, i) => {
|
|
91
|
-
const raw = row.metricValues?.[i]?.value ?? "0";
|
|
92
|
-
const num = Number(raw);
|
|
93
|
-
out[name] = Number.isFinite(num) ? num : raw;
|
|
94
|
-
});
|
|
95
|
-
return out;
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
const schema = [
|
|
99
|
-
...dimensions.map((name) => ({ name, type: "string" })),
|
|
100
|
-
...metrics.map((name) => ({ name, type: "number" })),
|
|
101
|
-
];
|
|
102
|
-
return { rows, schema };
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Amplitude panels carry a JSON blob in `sql` describing the segmentation
|
|
107
|
-
* API call. Shape: { event: string; metric?: "totals"|"uniques";
|
|
108
|
-
* groupBy?: string; days?: number; startDate?: string; endDate?: string }.
|
|
109
|
-
*/
|
|
110
|
-
async function runAmplitudePanel(raw: string): Promise<{
|
|
111
|
-
rows: Record<string, unknown>[];
|
|
112
|
-
schema: { name: string; type: string }[];
|
|
113
|
-
}> {
|
|
114
|
-
let parsed: {
|
|
115
|
-
event?: unknown;
|
|
116
|
-
metric?: unknown;
|
|
117
|
-
groupBy?: unknown;
|
|
118
|
-
days?: unknown;
|
|
119
|
-
startDate?: unknown;
|
|
120
|
-
endDate?: unknown;
|
|
121
|
-
};
|
|
122
|
-
try {
|
|
123
|
-
parsed = JSON.parse(raw);
|
|
124
|
-
} catch (err: any) {
|
|
125
|
-
throw new Error(
|
|
126
|
-
`amplitude panel sql must be a JSON object: ${err?.message ?? err}`,
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
if (!parsed || typeof parsed !== "object") {
|
|
130
|
-
throw new Error("amplitude panel sql must be a JSON object");
|
|
131
|
-
}
|
|
132
|
-
if (typeof parsed.event !== "string" || !parsed.event.trim()) {
|
|
133
|
-
throw new Error("amplitude panel requires an 'event' field");
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const eventType = parsed.event.trim();
|
|
137
|
-
const groupBy =
|
|
138
|
-
typeof parsed.groupBy === "string" ? parsed.groupBy : undefined;
|
|
139
|
-
|
|
140
|
-
const days =
|
|
141
|
-
typeof parsed.days === "number" ? parsed.days : Number(parsed.days);
|
|
142
|
-
const now = new Date();
|
|
143
|
-
const startDate =
|
|
144
|
-
Number.isFinite(days) && days > 0
|
|
145
|
-
? new Date(now.getTime() - days * 86_400_000)
|
|
146
|
-
: new Date(now.getTime() - 30 * 86_400_000);
|
|
147
|
-
|
|
148
|
-
const fmt = (d: Date) => d.toISOString().slice(0, 10).replace(/-/g, "");
|
|
149
|
-
const startStr =
|
|
150
|
-
typeof parsed.startDate === "string" && parsed.startDate
|
|
151
|
-
? parsed.startDate.replace(/-/g, "")
|
|
152
|
-
: fmt(startDate);
|
|
153
|
-
const endStr =
|
|
154
|
-
typeof parsed.endDate === "string" && parsed.endDate
|
|
155
|
-
? parsed.endDate.replace(/-/g, "")
|
|
156
|
-
: fmt(now);
|
|
157
|
-
|
|
158
|
-
const response = groupBy
|
|
159
|
-
? await getUserSegmentation(eventType, startStr, endStr, groupBy)
|
|
160
|
-
: await queryEvents(eventType, startStr, endStr);
|
|
161
|
-
|
|
162
|
-
return flattenAmplitudeResponse(response, groupBy);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function flattenAmplitudeResponse(
|
|
166
|
-
response: unknown,
|
|
167
|
-
groupBy?: string,
|
|
168
|
-
): {
|
|
169
|
-
rows: Record<string, unknown>[];
|
|
170
|
-
schema: { name: string; type: string }[];
|
|
171
|
-
} {
|
|
172
|
-
const data = (response as any)?.data;
|
|
173
|
-
if (!data) return { rows: [], schema: [] };
|
|
174
|
-
|
|
175
|
-
const xValues: string[] = Array.isArray(data.xValues) ? data.xValues : [];
|
|
176
|
-
const series = data.series;
|
|
177
|
-
|
|
178
|
-
if (!series || (Array.isArray(series) && series.length === 0)) {
|
|
179
|
-
return { rows: [], schema: [] };
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Normalize YYYYMMDD xValues → YYYY-MM-DD for chart rendering
|
|
183
|
-
const normDate = (d: string) =>
|
|
184
|
-
/^\d{8}$/.test(d)
|
|
185
|
-
? `${d.slice(0, 4)}-${d.slice(4, 6)}-${d.slice(6, 8)}`
|
|
186
|
-
: d;
|
|
187
|
-
|
|
188
|
-
if (!groupBy) {
|
|
189
|
-
// No grouping — time series. series is [[n1, n2, ...]] or [{date: {value:n}}, ...]
|
|
190
|
-
const firstSeries = Array.isArray(series) ? series[0] : series;
|
|
191
|
-
const rows: Record<string, unknown>[] = xValues.map((dateStr, i) => {
|
|
192
|
-
let count = 0;
|
|
193
|
-
if (Array.isArray(firstSeries)) {
|
|
194
|
-
count = typeof firstSeries[i] === "number" ? firstSeries[i] : 0;
|
|
195
|
-
} else if (firstSeries && typeof firstSeries === "object") {
|
|
196
|
-
const entry = (firstSeries as Record<string, any>)[dateStr];
|
|
197
|
-
count = entry?.value ?? 0;
|
|
198
|
-
}
|
|
199
|
-
return { date: normDate(dateStr), count };
|
|
200
|
-
});
|
|
201
|
-
return {
|
|
202
|
-
rows,
|
|
203
|
-
schema: [
|
|
204
|
-
{ name: "date", type: "string" },
|
|
205
|
-
{ name: "count", type: "number" },
|
|
206
|
-
],
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// With groupBy — aggregate each series across dates into one row per group
|
|
211
|
-
const seriesLabels: unknown[] = Array.isArray(data.seriesLabels)
|
|
212
|
-
? data.seriesLabels
|
|
213
|
-
: [];
|
|
214
|
-
const rows: Record<string, unknown>[] = [];
|
|
215
|
-
|
|
216
|
-
if (Array.isArray(series)) {
|
|
217
|
-
for (let i = 0; i < series.length; i++) {
|
|
218
|
-
const s = series[i];
|
|
219
|
-
let label = `Group ${i}`;
|
|
220
|
-
if (seriesLabels[i]) {
|
|
221
|
-
const entry = seriesLabels[i];
|
|
222
|
-
if (Array.isArray(entry) && entry.length >= 2) {
|
|
223
|
-
label = String(entry[entry.length - 1]);
|
|
224
|
-
} else {
|
|
225
|
-
label = String(entry);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
let total = 0;
|
|
230
|
-
if (Array.isArray(s)) {
|
|
231
|
-
for (const n of s) total += typeof n === "number" ? n : 0;
|
|
232
|
-
} else if (s && typeof s === "object") {
|
|
233
|
-
for (const val of Object.values(s as Record<string, any>)) {
|
|
234
|
-
total += val?.value ?? (typeof val === "number" ? val : 0);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
rows.push({ [groupBy]: label, count: total });
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Sort descending by count
|
|
243
|
-
rows.sort((a, b) => (b.count as number) - (a.count as number));
|
|
244
|
-
|
|
245
|
-
return {
|
|
246
|
-
rows,
|
|
247
|
-
schema: [
|
|
248
|
-
{ name: groupBy, type: "string" },
|
|
249
|
-
{ name: "count", type: "number" },
|
|
250
|
-
],
|
|
251
|
-
};
|
|
252
|
-
}
|
|
6
|
+
isDashboardPanelSource,
|
|
7
|
+
normalizeDashboardPanelQuery,
|
|
8
|
+
runDashboardPanelQuery,
|
|
9
|
+
} from "../lib/dashboard-panel-query";
|
|
253
10
|
|
|
254
11
|
export const handleSqlQuery = defineEventHandler(async (event) => {
|
|
255
12
|
return runApiHandlerWithContext(event, async (ctx) => {
|
|
@@ -258,17 +15,7 @@ export const handleSqlQuery = defineEventHandler(async (event) => {
|
|
|
258
15
|
source?: unknown;
|
|
259
16
|
};
|
|
260
17
|
|
|
261
|
-
if (
|
|
262
|
-
typeof source !== "string" ||
|
|
263
|
-
![
|
|
264
|
-
"bigquery",
|
|
265
|
-
"ga4",
|
|
266
|
-
"amplitude",
|
|
267
|
-
"first-party",
|
|
268
|
-
"demo",
|
|
269
|
-
"prometheus",
|
|
270
|
-
].includes(source)
|
|
271
|
-
) {
|
|
18
|
+
if (!isDashboardPanelSource(source)) {
|
|
272
19
|
setResponseStatus(event, 400);
|
|
273
20
|
return {
|
|
274
21
|
error:
|
|
@@ -276,96 +23,9 @@ export const handleSqlQuery = defineEventHandler(async (event) => {
|
|
|
276
23
|
};
|
|
277
24
|
}
|
|
278
25
|
|
|
279
|
-
let query: string;
|
|
280
|
-
if (source === "prometheus" || source === "demo") {
|
|
281
|
-
if (rawQuery === undefined || rawQuery === null || rawQuery === "") {
|
|
282
|
-
setResponseStatus(event, 400);
|
|
283
|
-
return { error: "Missing or invalid query" };
|
|
284
|
-
}
|
|
285
|
-
try {
|
|
286
|
-
query =
|
|
287
|
-
source === "demo"
|
|
288
|
-
? serializeDemoDescriptorInput(rawQuery)
|
|
289
|
-
: serializePanelDescriptorInput(rawQuery);
|
|
290
|
-
} catch (err: any) {
|
|
291
|
-
setResponseStatus(event, 400);
|
|
292
|
-
return { error: err?.message || "Missing or invalid query" };
|
|
293
|
-
}
|
|
294
|
-
} else {
|
|
295
|
-
if (!rawQuery || typeof rawQuery !== "string") {
|
|
296
|
-
setResponseStatus(event, 400);
|
|
297
|
-
return { error: "Missing or invalid query" };
|
|
298
|
-
}
|
|
299
|
-
query = rawQuery;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
26
|
try {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
event,
|
|
306
|
-
"BIGQUERY_PROJECT_ID",
|
|
307
|
-
"BigQuery",
|
|
308
|
-
);
|
|
309
|
-
if (missing) return missing;
|
|
310
|
-
const result = await runQuery(query);
|
|
311
|
-
return result;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
if (source === "ga4") {
|
|
315
|
-
const missingProp = await requireCredential(
|
|
316
|
-
event,
|
|
317
|
-
"GA4_PROPERTY_ID",
|
|
318
|
-
"Google Analytics",
|
|
319
|
-
);
|
|
320
|
-
if (missingProp) return missingProp;
|
|
321
|
-
const missingCreds = await requireCredential(
|
|
322
|
-
event,
|
|
323
|
-
"GOOGLE_APPLICATION_CREDENTIALS_JSON",
|
|
324
|
-
"Google Analytics",
|
|
325
|
-
);
|
|
326
|
-
if (missingCreds) return missingCreds;
|
|
327
|
-
return await runGa4Panel(query);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
if (source === "amplitude") {
|
|
331
|
-
const missingKey = await requireCredential(
|
|
332
|
-
event,
|
|
333
|
-
"AMPLITUDE_API_KEY",
|
|
334
|
-
"Amplitude",
|
|
335
|
-
);
|
|
336
|
-
if (missingKey) return missingKey;
|
|
337
|
-
const missingSecret = await requireCredential(
|
|
338
|
-
event,
|
|
339
|
-
"AMPLITUDE_SECRET_KEY",
|
|
340
|
-
"Amplitude",
|
|
341
|
-
);
|
|
342
|
-
if (missingSecret) return missingSecret;
|
|
343
|
-
return await runAmplitudePanel(query);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
if (source === "first-party") {
|
|
347
|
-
return await queryFirstPartyAnalytics(query, {
|
|
348
|
-
userEmail: ctx.userEmail,
|
|
349
|
-
orgId: ctx.orgId ?? null,
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
if (source === "demo") {
|
|
354
|
-
return await runDemoPanel(query);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
if (source === "prometheus") {
|
|
358
|
-
const missingUrl = await requireCredential(
|
|
359
|
-
event,
|
|
360
|
-
"PROMETHEUS_URL",
|
|
361
|
-
"Prometheus",
|
|
362
|
-
);
|
|
363
|
-
if (missingUrl) return missingUrl;
|
|
364
|
-
return await runPrometheusPanel(query);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
setResponseStatus(event, 400);
|
|
368
|
-
return { error: "Unsupported source" };
|
|
27
|
+
const query = normalizeDashboardPanelQuery(source, rawQuery);
|
|
28
|
+
return await runDashboardPanelQuery({ source, query, ctx });
|
|
369
29
|
} catch (error: any) {
|
|
370
30
|
const message = error?.message || String(error);
|
|
371
31
|
console.error(`SQL query error (${source}):`, message);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { runWithRequestContext } from "@agent-native/core/server/request-context";
|
|
2
|
+
|
|
3
|
+
import { sendDashboardReportSubscription } from "../lib/dashboard-report";
|
|
4
|
+
import {
|
|
5
|
+
claimDueDashboardReportSubscriptions,
|
|
6
|
+
markDashboardReportResult,
|
|
7
|
+
} from "../lib/dashboard-report-subscriptions";
|
|
8
|
+
|
|
9
|
+
let running = false;
|
|
10
|
+
const DEFAULT_MAX_REPORTS_PER_SWEEP = 5;
|
|
11
|
+
|
|
12
|
+
function maxReportsPerSweep(): number {
|
|
13
|
+
const raw = process.env.DASHBOARD_REPORT_SWEEP_LIMIT?.trim();
|
|
14
|
+
if (!raw) return DEFAULT_MAX_REPORTS_PER_SWEEP;
|
|
15
|
+
const parsed = Number.parseInt(raw, 10);
|
|
16
|
+
return Number.isFinite(parsed) && parsed > 0
|
|
17
|
+
? parsed
|
|
18
|
+
: DEFAULT_MAX_REPORTS_PER_SWEEP;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Run one dashboard report sweep. Exported for deployment-specific scheduled
|
|
23
|
+
* functions that should not rely on a long-lived Node process.
|
|
24
|
+
*/
|
|
25
|
+
export async function runDashboardReportsOnce(): Promise<{
|
|
26
|
+
processed: number;
|
|
27
|
+
failed: number;
|
|
28
|
+
remaining: number;
|
|
29
|
+
}> {
|
|
30
|
+
if (running) return { processed: 0, failed: 0, remaining: 0 };
|
|
31
|
+
running = true;
|
|
32
|
+
let processed = 0;
|
|
33
|
+
let failed = 0;
|
|
34
|
+
let remaining = 0;
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const sweepLimit = maxReportsPerSweep();
|
|
38
|
+
const batch = await claimDueDashboardReportSubscriptions(sweepLimit);
|
|
39
|
+
remaining = batch.length >= sweepLimit ? 1 : 0;
|
|
40
|
+
for (const sub of batch) {
|
|
41
|
+
processed++;
|
|
42
|
+
try {
|
|
43
|
+
await runWithRequestContext(
|
|
44
|
+
{
|
|
45
|
+
userEmail: sub.ownerEmail,
|
|
46
|
+
orgId: sub.orgId ?? undefined,
|
|
47
|
+
},
|
|
48
|
+
() => sendDashboardReportSubscription(sub),
|
|
49
|
+
);
|
|
50
|
+
await markDashboardReportResult(sub, "success");
|
|
51
|
+
} catch (err: any) {
|
|
52
|
+
failed++;
|
|
53
|
+
const message = err?.message ?? String(err);
|
|
54
|
+
console.error(
|
|
55
|
+
`[dashboard-report] Subscription ${sub.id} failed:`,
|
|
56
|
+
message,
|
|
57
|
+
);
|
|
58
|
+
await markDashboardReportResult(sub, "error", message);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
} finally {
|
|
62
|
+
running = false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return { processed, failed, remaining };
|
|
66
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { runSessionReplayRetentionSweep } from "../lib/session-replay";
|
|
2
|
+
|
|
3
|
+
let running = false;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Run one session replay retention sweep. Exported for deployment-specific
|
|
7
|
+
* scheduled functions that should not rely on a long-lived Node process.
|
|
8
|
+
*/
|
|
9
|
+
export async function runSessionReplayRetentionOnce(): Promise<{
|
|
10
|
+
finalized: number;
|
|
11
|
+
expired: number;
|
|
12
|
+
chunks: number;
|
|
13
|
+
blobDeleteFailures: number;
|
|
14
|
+
}> {
|
|
15
|
+
if (running) {
|
|
16
|
+
return {
|
|
17
|
+
finalized: 0,
|
|
18
|
+
expired: 0,
|
|
19
|
+
chunks: 0,
|
|
20
|
+
blobDeleteFailures: 0,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
running = true;
|
|
24
|
+
try {
|
|
25
|
+
return await runSessionReplayRetentionSweep();
|
|
26
|
+
} finally {
|
|
27
|
+
running = false;
|
|
28
|
+
}
|
|
29
|
+
}
|