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