@databricks/appkit-ui 0.39.0 → 0.41.0
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/dist/cli/commands/generate-types.js +114 -5
- package/dist/cli/commands/generate-types.js.map +1 -1
- package/dist/cli/commands/spawn-lock.js +116 -0
- package/dist/cli/commands/spawn-lock.js.map +1 -0
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/react/charts/index.d.ts +1 -0
- package/dist/react/charts/index.js +1 -0
- package/dist/react/charts/loading.d.ts +24 -0
- package/dist/react/charts/loading.d.ts.map +1 -0
- package/dist/react/charts/loading.js +20 -2
- package/dist/react/charts/loading.js.map +1 -1
- package/dist/react/charts/wrapper.d.ts.map +1 -1
- package/dist/react/charts/wrapper.js +9 -2
- package/dist/react/charts/wrapper.js.map +1 -1
- package/dist/react/hooks/index.d.ts +3 -1
- package/dist/react/hooks/index.js +2 -0
- package/dist/react/hooks/types.d.ts +28 -1
- package/dist/react/hooks/types.d.ts.map +1 -1
- package/dist/react/hooks/use-analytics-query.d.ts.map +1 -1
- package/dist/react/hooks/use-analytics-query.js +77 -45
- package/dist/react/hooks/use-analytics-query.js.map +1 -1
- package/dist/react/hooks/use-analytics-warehouse-status.js +48 -0
- package/dist/react/hooks/use-analytics-warehouse-status.js.map +1 -0
- package/dist/react/hooks/use-chart-data.d.ts +8 -0
- package/dist/react/hooks/use-chart-data.d.ts.map +1 -1
- package/dist/react/hooks/use-chart-data.js +3 -2
- package/dist/react/hooks/use-chart-data.js.map +1 -1
- package/dist/react/hooks/use-resource-status.d.ts +92 -0
- package/dist/react/hooks/use-resource-status.d.ts.map +1 -0
- package/dist/react/hooks/use-resource-status.js +199 -0
- package/dist/react/hooks/use-resource-status.js.map +1 -0
- package/dist/react/index.d.ts +5 -2
- package/dist/react/index.js +5 -2
- package/dist/react/resource-status-indicator.d.ts +78 -0
- package/dist/react/resource-status-indicator.d.ts.map +1 -0
- package/dist/react/resource-status-indicator.js +155 -0
- package/dist/react/resource-status-indicator.js.map +1 -0
- package/dist/react/ui/index.js +1 -1
- package/dist/react/ui/sonner.js +1 -1
- package/dist/schemas/manifest.d.ts.map +1 -1
- package/dist/schemas/manifest.js +1 -1
- package/dist/schemas/manifest.js.map +1 -1
- package/docs/development/type-generation.md +13 -0
- package/docs/plugins/analytics.md +156 -0
- package/package.json +1 -1
- package/sbom.cdx.json +1 -1
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
import { ArrowClient } from "../../js/arrow/arrow-client.js";
|
|
2
2
|
import { connectSSE } from "../../js/sse/connect-sse.js";
|
|
3
3
|
import "../../js/index.js";
|
|
4
|
+
import { useAnalyticsWarehousePublisher } from "./use-analytics-warehouse-status.js";
|
|
4
5
|
import { useQueryHMR } from "./use-query-hmr.js";
|
|
5
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
6
|
+
import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
6
7
|
|
|
7
8
|
//#region src/react/hooks/use-analytics-query.ts
|
|
8
|
-
/**
|
|
9
|
-
* Shallow structural equality for analytics query parameter objects.
|
|
10
|
-
*
|
|
11
|
-
* Analytics query parameters are produced by the `sql.*` builders and are
|
|
12
|
-
* always plain objects keyed to primitive values (string | number | boolean
|
|
13
|
-
* | null | undefined), so shallow equality is sufficient and substantially
|
|
14
|
-
* cheaper than a full deep-equal.
|
|
15
|
-
*/
|
|
9
|
+
/** Shallow equality for plain-object query parameters (primitive values only). */
|
|
16
10
|
function shallowEqualParams(a, b) {
|
|
17
11
|
if (Object.is(a, b)) return true;
|
|
18
12
|
if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
|
|
@@ -25,12 +19,7 @@ function shallowEqualParams(a, b) {
|
|
|
25
19
|
}
|
|
26
20
|
return true;
|
|
27
21
|
}
|
|
28
|
-
/**
|
|
29
|
-
* Stabilize a value's identity across renders when it is structurally equal
|
|
30
|
-
* to the previous value. Used to make object-literal parameters safe to pass
|
|
31
|
-
* directly to `useAnalyticsQuery` without forcing every consumer to wrap
|
|
32
|
-
* params in `useMemo`.
|
|
33
|
-
*/
|
|
22
|
+
/** Keep structurally-equal params referentially stable across renders. */
|
|
34
23
|
function useStableParams(value) {
|
|
35
24
|
const ref = useRef(value);
|
|
36
25
|
if (!shallowEqualParams(ref.current, value)) ref.current = value;
|
|
@@ -43,6 +32,52 @@ function getDevMode() {
|
|
|
43
32
|
function getArrowStreamUrl(id) {
|
|
44
33
|
return `/api/analytics/arrow-result/${id}`;
|
|
45
34
|
}
|
|
35
|
+
const GENERIC_LOAD_ERROR = "Unable to load data, please try again";
|
|
36
|
+
function isWarehouseStatusPayload(value) {
|
|
37
|
+
return typeof value === "object" && value !== null && typeof value.state === "string";
|
|
38
|
+
}
|
|
39
|
+
async function handleAnalyticsSseMessage(parsed, ctx) {
|
|
40
|
+
if (parsed.type === "warehouse_status") {
|
|
41
|
+
if (!isWarehouseStatusPayload(parsed.status)) {
|
|
42
|
+
ctx.setLoading(false);
|
|
43
|
+
ctx.setError(GENERIC_LOAD_ERROR);
|
|
44
|
+
ctx.unpublishWarehouseStatus();
|
|
45
|
+
console.error("[useAnalyticsQuery] Malformed warehouse_status event", parsed);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
ctx.setWarehouseStatus(parsed.status);
|
|
49
|
+
ctx.publishWarehouseStatus(parsed.status);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (parsed.type === "result") {
|
|
53
|
+
ctx.setLoading(false);
|
|
54
|
+
ctx.setData(parsed.data);
|
|
55
|
+
ctx.unpublishWarehouseStatus();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (parsed.type === "arrow") {
|
|
59
|
+
try {
|
|
60
|
+
const arrowData = await ArrowClient.fetchArrow(getArrowStreamUrl(parsed.statement_id));
|
|
61
|
+
const table = await ArrowClient.processArrowBuffer(arrowData);
|
|
62
|
+
ctx.setLoading(false);
|
|
63
|
+
ctx.setData(table);
|
|
64
|
+
ctx.unpublishWarehouseStatus();
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error("[useAnalyticsQuery] Failed to fetch Arrow data", error);
|
|
67
|
+
ctx.setLoading(false);
|
|
68
|
+
ctx.setError(GENERIC_LOAD_ERROR);
|
|
69
|
+
ctx.unpublishWarehouseStatus();
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (parsed.type === "error" || parsed.error || parsed.code) {
|
|
74
|
+
const errorMsg = parsed.error || parsed.message || "Unable to execute query";
|
|
75
|
+
ctx.setLoading(false);
|
|
76
|
+
ctx.setError(errorMsg);
|
|
77
|
+
ctx.unpublishWarehouseStatus();
|
|
78
|
+
if (parsed.code) console.error(`[useAnalyticsQuery] Code: ${parsed.code}, Message: ${errorMsg}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
46
81
|
/**
|
|
47
82
|
* Subscribe to an analytics query over SSE and returns its latest result.
|
|
48
83
|
* Integration hook between client and analytics plugin.
|
|
@@ -81,7 +116,9 @@ function useAnalyticsQuery(queryKey, parameters, options = {}) {
|
|
|
81
116
|
const [data, setData] = useState(null);
|
|
82
117
|
const [loading, setLoading] = useState(false);
|
|
83
118
|
const [error, setError] = useState(null);
|
|
119
|
+
const [warehouseStatus, setWarehouseStatus] = useState(null);
|
|
84
120
|
const abortControllerRef = useRef(null);
|
|
121
|
+
const { publish: publishWarehouseStatus, unpublish: unpublishWarehouseStatus } = useAnalyticsWarehousePublisher(useId(), queryKey);
|
|
85
122
|
if (!queryKey || queryKey.trim().length === 0) throw new Error("useAnalyticsQuery: 'queryKey' must be a non-empty string.");
|
|
86
123
|
const stableParameters = useStableParams(parameters);
|
|
87
124
|
const payload = useMemo(() => {
|
|
@@ -106,43 +143,29 @@ function useAnalyticsQuery(queryKey, parameters, options = {}) {
|
|
|
106
143
|
setError("Failed to serialize query parameters");
|
|
107
144
|
return;
|
|
108
145
|
}
|
|
109
|
-
|
|
146
|
+
abortControllerRef.current?.abort();
|
|
110
147
|
setLoading(true);
|
|
111
148
|
setError(null);
|
|
112
149
|
setData(null);
|
|
150
|
+
setWarehouseStatus(null);
|
|
151
|
+
publishWarehouseStatus(null);
|
|
113
152
|
const abortController = new AbortController();
|
|
114
153
|
abortControllerRef.current = abortController;
|
|
154
|
+
const sseContext = {
|
|
155
|
+
setLoading,
|
|
156
|
+
setError,
|
|
157
|
+
setData,
|
|
158
|
+
setWarehouseStatus,
|
|
159
|
+
publishWarehouseStatus,
|
|
160
|
+
unpublishWarehouseStatus
|
|
161
|
+
};
|
|
115
162
|
connectSSE({
|
|
116
163
|
url: urlSuffix,
|
|
117
164
|
payload,
|
|
118
165
|
signal: abortController.signal,
|
|
119
166
|
onMessage: async (message) => {
|
|
120
167
|
try {
|
|
121
|
-
|
|
122
|
-
if (parsed.type === "result") {
|
|
123
|
-
setLoading(false);
|
|
124
|
-
setData(parsed.data);
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
if (parsed.type === "arrow") try {
|
|
128
|
-
const arrowData = await ArrowClient.fetchArrow(getArrowStreamUrl(parsed.statement_id));
|
|
129
|
-
const table = await ArrowClient.processArrowBuffer(arrowData);
|
|
130
|
-
setLoading(false);
|
|
131
|
-
setData(table);
|
|
132
|
-
return;
|
|
133
|
-
} catch (error) {
|
|
134
|
-
console.error("[useAnalyticsQuery] Failed to fetch Arrow data", error);
|
|
135
|
-
setLoading(false);
|
|
136
|
-
setError("Unable to load data, please try again");
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
if (parsed.type === "error" || parsed.error || parsed.code) {
|
|
140
|
-
const errorMsg = parsed.error || parsed.message || "Unable to execute query";
|
|
141
|
-
setLoading(false);
|
|
142
|
-
setError(errorMsg);
|
|
143
|
-
if (parsed.code) console.error(`[useAnalyticsQuery] Code: ${parsed.code}, Message: ${errorMsg}`);
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
168
|
+
await handleAnalyticsSseMessage(JSON.parse(message.data), sseContext);
|
|
146
169
|
} catch (error) {
|
|
147
170
|
console.warn("[useAnalyticsQuery] Malformed message received", error);
|
|
148
171
|
}
|
|
@@ -150,7 +173,8 @@ function useAnalyticsQuery(queryKey, parameters, options = {}) {
|
|
|
150
173
|
onError: (error) => {
|
|
151
174
|
if (abortController.signal.aborted) return;
|
|
152
175
|
setLoading(false);
|
|
153
|
-
|
|
176
|
+
unpublishWarehouseStatus();
|
|
177
|
+
let userMessage = GENERIC_LOAD_ERROR;
|
|
154
178
|
if (error instanceof Error) {
|
|
155
179
|
if (error.name === "AbortError") userMessage = "Request timed out, please try again";
|
|
156
180
|
else if (error.message.includes("Failed to fetch")) userMessage = "Network error. Please check your connection.";
|
|
@@ -166,19 +190,27 @@ function useAnalyticsQuery(queryKey, parameters, options = {}) {
|
|
|
166
190
|
}, [
|
|
167
191
|
queryKey,
|
|
168
192
|
payload,
|
|
169
|
-
urlSuffix
|
|
193
|
+
urlSuffix,
|
|
194
|
+
publishWarehouseStatus,
|
|
195
|
+
unpublishWarehouseStatus
|
|
170
196
|
]);
|
|
171
197
|
useEffect(() => {
|
|
172
198
|
if (autoStart) start();
|
|
173
199
|
return () => {
|
|
174
200
|
abortControllerRef.current?.abort();
|
|
201
|
+
unpublishWarehouseStatus();
|
|
175
202
|
};
|
|
176
|
-
}, [
|
|
203
|
+
}, [
|
|
204
|
+
start,
|
|
205
|
+
autoStart,
|
|
206
|
+
unpublishWarehouseStatus
|
|
207
|
+
]);
|
|
177
208
|
useQueryHMR(queryKey, start);
|
|
178
209
|
return {
|
|
179
210
|
data,
|
|
180
211
|
loading,
|
|
181
|
-
error
|
|
212
|
+
error,
|
|
213
|
+
warehouseStatus
|
|
182
214
|
};
|
|
183
215
|
}
|
|
184
216
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-analytics-query.js","names":[],"sources":["../../../src/react/hooks/use-analytics-query.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { ArrowClient, connectSSE } from \"@/js\";\nimport type {\n AnalyticsFormat,\n InferParams,\n InferResultByFormat,\n QueryKey,\n UseAnalyticsQueryOptions,\n UseAnalyticsQueryResult,\n} from \"./types\";\nimport { useQueryHMR } from \"./use-query-hmr\";\n\n/**\n * Shallow structural equality for analytics query parameter objects.\n *\n * Analytics query parameters are produced by the `sql.*` builders and are\n * always plain objects keyed to primitive values (string | number | boolean\n * | null | undefined), so shallow equality is sufficient and substantially\n * cheaper than a full deep-equal.\n */\nfunction shallowEqualParams(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (\n a === null ||\n b === null ||\n typeof a !== \"object\" ||\n typeof b !== \"object\"\n ) {\n return false;\n }\n const aKeys = Object.keys(a as Record<string, unknown>);\n const bKeys = Object.keys(b as Record<string, unknown>);\n if (aKeys.length !== bKeys.length) return false;\n for (const key of aKeys) {\n if (!Object.hasOwn(b, key)) return false;\n if (\n !Object.is(\n (a as Record<string, unknown>)[key],\n (b as Record<string, unknown>)[key],\n )\n ) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Stabilize a value's identity across renders when it is structurally equal\n * to the previous value. Used to make object-literal parameters safe to pass\n * directly to `useAnalyticsQuery` without forcing every consumer to wrap\n * params in `useMemo`.\n */\nfunction useStableParams<T>(value: T): T {\n const ref = useRef<T>(value);\n if (!shallowEqualParams(ref.current, value)) {\n ref.current = value;\n }\n return ref.current;\n}\n\nfunction getDevMode() {\n const url = new URL(window.location.href);\n const searchParams = url.searchParams;\n const dev = searchParams.get(\"dev\");\n\n return dev ? `?dev=${dev}` : \"\";\n}\n\nfunction getArrowStreamUrl(id: string) {\n return `/api/analytics/arrow-result/${id}`;\n}\n\n/**\n * Subscribe to an analytics query over SSE and returns its latest result.\n * Integration hook between client and analytics plugin.\n *\n * The return type is automatically inferred based on the format:\n * - `format: \"JSON_ARRAY\"` (default): Returns typed array from QueryRegistry\n * - `format: \"ARROW_STREAM\"`: Returns TypedArrowTable with row type preserved\n *\n * Note: User context execution is determined by query file naming:\n * - `queryKey.obo.sql`: Executes as user (OBO = on-behalf-of / user delegation)\n * - `queryKey.sql`: Executes as service principal\n *\n * @param queryKey - Analytics query identifier\n * @param parameters - Query parameters (type-safe based on QueryRegistry)\n * @param options - Analytics query settings including format\n * @returns Query result state with format-appropriate data type\n *\n * @example JSON format (default)\n * ```typescript\n * const { data } = useAnalyticsQuery(\"spend_data\", params);\n * // data: Array<{ group_key: string; cost_usd: number; ... }> | null\n * ```\n *\n * @example Arrow format\n * ```typescript\n * const { data } = useAnalyticsQuery(\"spend_data\", params, { format: \"ARROW_STREAM\" });\n * // data: TypedArrowTable<{ group_key: string; cost_usd: number; ... }> | null\n * ```\n */\nexport function useAnalyticsQuery<\n T = unknown,\n K extends QueryKey = QueryKey,\n F extends AnalyticsFormat = \"JSON_ARRAY\",\n>(\n queryKey: K,\n parameters?: InferParams<K> | null,\n options: UseAnalyticsQueryOptions<F> = {} as UseAnalyticsQueryOptions<F>,\n): UseAnalyticsQueryResult<InferResultByFormat<T, K, F>> {\n const format = options?.format ?? \"JSON_ARRAY\";\n const maxParametersSize = options?.maxParametersSize ?? 100 * 1024;\n const autoStart = options?.autoStart ?? true;\n\n const devMode = getDevMode();\n const urlSuffix = `/api/analytics/query/${encodeURIComponent(queryKey)}${devMode}`;\n\n type ResultType = InferResultByFormat<T, K, F>;\n const [data, setData] = useState<ResultType | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const abortControllerRef = useRef<AbortController | null>(null);\n\n if (!queryKey || queryKey.trim().length === 0) {\n throw new Error(\n \"useAnalyticsQuery: 'queryKey' must be a non-empty string.\",\n );\n }\n\n // Stabilize the parameters reference across renders. Without this, a fresh\n // object literal at the call site (e.g. `useAnalyticsQuery(\"k\", { limit: 10 })`)\n // would change identity every render, invalidating the `payload` memo and\n // re-running `start` -> infinite refetch loop.\n const stableParameters = useStableParams(parameters);\n\n const payload = useMemo(() => {\n try {\n const serialized = JSON.stringify({\n parameters: stableParameters,\n format,\n });\n const sizeInBytes = new Blob([serialized]).size;\n if (sizeInBytes > maxParametersSize) {\n throw new Error(\n \"useAnalyticsQuery: Parameters size exceeds the maximum allowed size\",\n );\n }\n\n return serialized;\n } catch (error) {\n console.error(\"useAnalyticsQuery: Failed to serialize parameters\", error);\n return null;\n }\n }, [stableParameters, format, maxParametersSize]);\n\n const start = useCallback(() => {\n if (payload === null) {\n setError(\"Failed to serialize query parameters\");\n return;\n }\n\n // Abort previous request if exists\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n\n setLoading(true);\n setError(null);\n setData(null);\n\n const abortController = new AbortController();\n abortControllerRef.current = abortController;\n\n connectSSE({\n url: urlSuffix,\n payload: payload,\n signal: abortController.signal,\n onMessage: async (message) => {\n try {\n const parsed = JSON.parse(message.data);\n\n // success - JSON format\n if (parsed.type === \"result\") {\n setLoading(false);\n setData(parsed.data as ResultType);\n return;\n }\n\n // success - Arrow format\n if (parsed.type === \"arrow\") {\n try {\n const arrowData = await ArrowClient.fetchArrow(\n getArrowStreamUrl(parsed.statement_id),\n );\n const table = await ArrowClient.processArrowBuffer(arrowData);\n setLoading(false);\n // Table is cast to TypedArrowTable with row type from QueryRegistry\n setData(table as ResultType);\n return;\n } catch (error) {\n console.error(\n \"[useAnalyticsQuery] Failed to fetch Arrow data\",\n error,\n );\n setLoading(false);\n setError(\"Unable to load data, please try again\");\n return;\n }\n }\n\n // error\n if (parsed.type === \"error\" || parsed.error || parsed.code) {\n const errorMsg =\n parsed.error || parsed.message || \"Unable to execute query\";\n\n setLoading(false);\n setError(errorMsg);\n\n if (parsed.code) {\n console.error(\n `[useAnalyticsQuery] Code: ${parsed.code}, Message: ${errorMsg}`,\n );\n }\n return;\n }\n } catch (error) {\n console.warn(\"[useAnalyticsQuery] Malformed message received\", error);\n }\n },\n onError: (error) => {\n if (abortController.signal.aborted) return;\n setLoading(false);\n\n let userMessage = \"Unable to load data, please try again\";\n\n if (error instanceof Error) {\n if (error.name === \"AbortError\") {\n userMessage = \"Request timed out, please try again\";\n } else if (error.message.includes(\"Failed to fetch\")) {\n userMessage = \"Network error. Please check your connection.\";\n }\n\n console.error(\"[useAnalyticsQuery] Error\", {\n queryKey,\n error: error.message,\n stack: error.stack,\n });\n }\n setError(userMessage);\n },\n });\n }, [queryKey, payload, urlSuffix]);\n\n useEffect(() => {\n if (autoStart) {\n start();\n }\n\n return () => {\n abortControllerRef.current?.abort();\n };\n }, [start, autoStart]);\n\n // Enable HMR for query updates in dev mode\n useQueryHMR(queryKey, start);\n\n return { data, loading, error };\n}\n"],"mappings":";;;;;;;;;;;;;;;AAoBA,SAAS,mBAAmB,GAAY,GAAqB;AAC3D,KAAI,OAAO,GAAG,GAAG,EAAE,CAAE,QAAO;AAC5B,KACE,MAAM,QACN,MAAM,QACN,OAAO,MAAM,YACb,OAAO,MAAM,SAEb,QAAO;CAET,MAAM,QAAQ,OAAO,KAAK,EAA6B;CACvD,MAAM,QAAQ,OAAO,KAAK,EAA6B;AACvD,KAAI,MAAM,WAAW,MAAM,OAAQ,QAAO;AAC1C,MAAK,MAAM,OAAO,OAAO;AACvB,MAAI,CAAC,OAAO,OAAO,GAAG,IAAI,CAAE,QAAO;AACnC,MACE,CAAC,OAAO,GACL,EAA8B,MAC9B,EAA8B,KAChC,CAED,QAAO;;AAGX,QAAO;;;;;;;;AAST,SAAS,gBAAmB,OAAa;CACvC,MAAM,MAAM,OAAU,MAAM;AAC5B,KAAI,CAAC,mBAAmB,IAAI,SAAS,MAAM,CACzC,KAAI,UAAU;AAEhB,QAAO,IAAI;;AAGb,SAAS,aAAa;CAGpB,MAAM,MAFM,IAAI,IAAI,OAAO,SAAS,KAAK,CAChB,aACA,IAAI,MAAM;AAEnC,QAAO,MAAM,QAAQ,QAAQ;;AAG/B,SAAS,kBAAkB,IAAY;AACrC,QAAO,+BAA+B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCxC,SAAgB,kBAKd,UACA,YACA,UAAuC,EAAE,EACc;CACvD,MAAM,SAAS,SAAS,UAAU;CAClC,MAAM,oBAAoB,SAAS,qBAAqB,MAAM;CAC9D,MAAM,YAAY,SAAS,aAAa;CAExC,MAAM,UAAU,YAAY;CAC5B,MAAM,YAAY,wBAAwB,mBAAmB,SAAS,GAAG;CAGzE,MAAM,CAAC,MAAM,WAAW,SAA4B,KAAK;CACzD,MAAM,CAAC,SAAS,cAAc,SAAS,MAAM;CAC7C,MAAM,CAAC,OAAO,YAAY,SAAwB,KAAK;CACvD,MAAM,qBAAqB,OAA+B,KAAK;AAE/D,KAAI,CAAC,YAAY,SAAS,MAAM,CAAC,WAAW,EAC1C,OAAM,IAAI,MACR,4DACD;CAOH,MAAM,mBAAmB,gBAAgB,WAAW;CAEpD,MAAM,UAAU,cAAc;AAC5B,MAAI;GACF,MAAM,aAAa,KAAK,UAAU;IAChC,YAAY;IACZ;IACD,CAAC;AAEF,OADoB,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC,OACzB,kBAChB,OAAM,IAAI,MACR,sEACD;AAGH,UAAO;WACA,OAAO;AACd,WAAQ,MAAM,qDAAqD,MAAM;AACzE,UAAO;;IAER;EAAC;EAAkB;EAAQ;EAAkB,CAAC;CAEjD,MAAM,QAAQ,kBAAkB;AAC9B,MAAI,YAAY,MAAM;AACpB,YAAS,uCAAuC;AAChD;;AAIF,MAAI,mBAAmB,QACrB,oBAAmB,QAAQ,OAAO;AAGpC,aAAW,KAAK;AAChB,WAAS,KAAK;AACd,UAAQ,KAAK;EAEb,MAAM,kBAAkB,IAAI,iBAAiB;AAC7C,qBAAmB,UAAU;AAE7B,aAAW;GACT,KAAK;GACI;GACT,QAAQ,gBAAgB;GACxB,WAAW,OAAO,YAAY;AAC5B,QAAI;KACF,MAAM,SAAS,KAAK,MAAM,QAAQ,KAAK;AAGvC,SAAI,OAAO,SAAS,UAAU;AAC5B,iBAAW,MAAM;AACjB,cAAQ,OAAO,KAAmB;AAClC;;AAIF,SAAI,OAAO,SAAS,QAClB,KAAI;MACF,MAAM,YAAY,MAAM,YAAY,WAClC,kBAAkB,OAAO,aAAa,CACvC;MACD,MAAM,QAAQ,MAAM,YAAY,mBAAmB,UAAU;AAC7D,iBAAW,MAAM;AAEjB,cAAQ,MAAoB;AAC5B;cACO,OAAO;AACd,cAAQ,MACN,kDACA,MACD;AACD,iBAAW,MAAM;AACjB,eAAS,wCAAwC;AACjD;;AAKJ,SAAI,OAAO,SAAS,WAAW,OAAO,SAAS,OAAO,MAAM;MAC1D,MAAM,WACJ,OAAO,SAAS,OAAO,WAAW;AAEpC,iBAAW,MAAM;AACjB,eAAS,SAAS;AAElB,UAAI,OAAO,KACT,SAAQ,MACN,6BAA6B,OAAO,KAAK,aAAa,WACvD;AAEH;;aAEK,OAAO;AACd,aAAQ,KAAK,kDAAkD,MAAM;;;GAGzE,UAAU,UAAU;AAClB,QAAI,gBAAgB,OAAO,QAAS;AACpC,eAAW,MAAM;IAEjB,IAAI,cAAc;AAElB,QAAI,iBAAiB,OAAO;AAC1B,SAAI,MAAM,SAAS,aACjB,eAAc;cACL,MAAM,QAAQ,SAAS,kBAAkB,CAClD,eAAc;AAGhB,aAAQ,MAAM,6BAA6B;MACzC;MACA,OAAO,MAAM;MACb,OAAO,MAAM;MACd,CAAC;;AAEJ,aAAS,YAAY;;GAExB,CAAC;IACD;EAAC;EAAU;EAAS;EAAU,CAAC;AAElC,iBAAgB;AACd,MAAI,UACF,QAAO;AAGT,eAAa;AACX,sBAAmB,SAAS,OAAO;;IAEpC,CAAC,OAAO,UAAU,CAAC;AAGtB,aAAY,UAAU,MAAM;AAE5B,QAAO;EAAE;EAAM;EAAS;EAAO"}
|
|
1
|
+
{"version":3,"file":"use-analytics-query.js","names":[],"sources":["../../../src/react/hooks/use-analytics-query.ts"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { ArrowClient, connectSSE } from \"@/js\";\nimport type {\n AnalyticsFormat,\n InferParams,\n InferResultByFormat,\n QueryKey,\n UseAnalyticsQueryOptions,\n UseAnalyticsQueryResult,\n WarehouseStatus,\n} from \"./types\";\nimport { useAnalyticsWarehousePublisher } from \"./use-analytics-warehouse-status\";\nimport { useQueryHMR } from \"./use-query-hmr\";\n\n/** Shallow equality for plain-object query parameters (primitive values only). */\nfunction shallowEqualParams(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (\n a === null ||\n b === null ||\n typeof a !== \"object\" ||\n typeof b !== \"object\"\n ) {\n return false;\n }\n const aKeys = Object.keys(a as Record<string, unknown>);\n const bKeys = Object.keys(b as Record<string, unknown>);\n if (aKeys.length !== bKeys.length) return false;\n for (const key of aKeys) {\n if (!Object.hasOwn(b, key)) return false;\n if (\n !Object.is(\n (a as Record<string, unknown>)[key],\n (b as Record<string, unknown>)[key],\n )\n ) {\n return false;\n }\n }\n return true;\n}\n\n/** Keep structurally-equal params referentially stable across renders. */\nfunction useStableParams<T>(value: T): T {\n const ref = useRef<T>(value);\n if (!shallowEqualParams(ref.current, value)) {\n ref.current = value;\n }\n return ref.current;\n}\n\nfunction getDevMode(): string {\n const dev = new URL(window.location.href).searchParams.get(\"dev\");\n return dev ? `?dev=${dev}` : \"\";\n}\n\nfunction getArrowStreamUrl(id: string): string {\n return `/api/analytics/arrow-result/${id}`;\n}\n\nconst GENERIC_LOAD_ERROR = \"Unable to load data, please try again\";\n\ninterface AnalyticsQuerySseContext<ResultType> {\n setLoading: (loading: boolean) => void;\n setError: (error: string | null) => void;\n setData: (data: ResultType | null) => void;\n setWarehouseStatus: (status: WarehouseStatus | null) => void;\n publishWarehouseStatus: (status: WarehouseStatus | null) => void;\n unpublishWarehouseStatus: () => void;\n}\n\nfunction isWarehouseStatusPayload(value: unknown): value is WarehouseStatus {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as WarehouseStatus).state === \"string\"\n );\n}\n\nasync function handleAnalyticsSseMessage<ResultType>(\n parsed: Record<string, unknown>,\n ctx: AnalyticsQuerySseContext<ResultType>,\n): Promise<void> {\n if (parsed.type === \"warehouse_status\") {\n if (!isWarehouseStatusPayload(parsed.status)) {\n ctx.setLoading(false);\n ctx.setError(GENERIC_LOAD_ERROR);\n ctx.unpublishWarehouseStatus();\n console.error(\n \"[useAnalyticsQuery] Malformed warehouse_status event\",\n parsed,\n );\n return;\n }\n ctx.setWarehouseStatus(parsed.status);\n ctx.publishWarehouseStatus(parsed.status);\n return;\n }\n\n if (parsed.type === \"result\") {\n ctx.setLoading(false);\n ctx.setData(parsed.data as ResultType);\n ctx.unpublishWarehouseStatus();\n return;\n }\n\n if (parsed.type === \"arrow\") {\n try {\n const arrowData = await ArrowClient.fetchArrow(\n getArrowStreamUrl(parsed.statement_id as string),\n );\n const table = await ArrowClient.processArrowBuffer(arrowData);\n ctx.setLoading(false);\n ctx.setData(table as ResultType);\n ctx.unpublishWarehouseStatus();\n } catch (error) {\n console.error(\"[useAnalyticsQuery] Failed to fetch Arrow data\", error);\n ctx.setLoading(false);\n ctx.setError(GENERIC_LOAD_ERROR);\n ctx.unpublishWarehouseStatus();\n }\n return;\n }\n\n if (parsed.type === \"error\" || parsed.error || parsed.code) {\n const errorMsg =\n (parsed.error as string | undefined) ||\n (parsed.message as string | undefined) ||\n \"Unable to execute query\";\n ctx.setLoading(false);\n ctx.setError(errorMsg);\n ctx.unpublishWarehouseStatus();\n if (parsed.code) {\n console.error(\n `[useAnalyticsQuery] Code: ${parsed.code}, Message: ${errorMsg}`,\n );\n }\n }\n}\n\n/**\n * Subscribe to an analytics query over SSE and returns its latest result.\n * Integration hook between client and analytics plugin.\n *\n * The return type is automatically inferred based on the format:\n * - `format: \"JSON_ARRAY\"` (default): Returns typed array from QueryRegistry\n * - `format: \"ARROW_STREAM\"`: Returns TypedArrowTable with row type preserved\n *\n * Note: User context execution is determined by query file naming:\n * - `queryKey.obo.sql`: Executes as user (OBO = on-behalf-of / user delegation)\n * - `queryKey.sql`: Executes as service principal\n *\n * @param queryKey - Analytics query identifier\n * @param parameters - Query parameters (type-safe based on QueryRegistry)\n * @param options - Analytics query settings including format\n * @returns Query result state with format-appropriate data type\n *\n * @example JSON format (default)\n * ```typescript\n * const { data } = useAnalyticsQuery(\"spend_data\", params);\n * // data: Array<{ group_key: string; cost_usd: number; ... }> | null\n * ```\n *\n * @example Arrow format\n * ```typescript\n * const { data } = useAnalyticsQuery(\"spend_data\", params, { format: \"ARROW_STREAM\" });\n * // data: TypedArrowTable<{ group_key: string; cost_usd: number; ... }> | null\n * ```\n */\nexport function useAnalyticsQuery<\n T = unknown,\n K extends QueryKey = QueryKey,\n F extends AnalyticsFormat = \"JSON_ARRAY\",\n>(\n queryKey: K,\n parameters?: InferParams<K> | null,\n options: UseAnalyticsQueryOptions<F> = {} as UseAnalyticsQueryOptions<F>,\n): UseAnalyticsQueryResult<InferResultByFormat<T, K, F>> {\n const format = options?.format ?? \"JSON_ARRAY\";\n const maxParametersSize = options?.maxParametersSize ?? 100 * 1024;\n const autoStart = options?.autoStart ?? true;\n\n const devMode = getDevMode();\n const urlSuffix = `/api/analytics/query/${encodeURIComponent(queryKey)}${devMode}`;\n\n type ResultType = InferResultByFormat<T, K, F>;\n const [data, setData] = useState<ResultType | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [warehouseStatus, setWarehouseStatus] =\n useState<WarehouseStatus | null>(null);\n const abortControllerRef = useRef<AbortController | null>(null);\n\n const publisherId = useId();\n const {\n publish: publishWarehouseStatus,\n unpublish: unpublishWarehouseStatus,\n } = useAnalyticsWarehousePublisher(publisherId, queryKey);\n\n if (!queryKey || queryKey.trim().length === 0) {\n throw new Error(\n \"useAnalyticsQuery: 'queryKey' must be a non-empty string.\",\n );\n }\n\n const stableParameters = useStableParams(parameters);\n\n const payload = useMemo(() => {\n try {\n const serialized = JSON.stringify({\n parameters: stableParameters,\n format,\n });\n const sizeInBytes = new Blob([serialized]).size;\n if (sizeInBytes > maxParametersSize) {\n throw new Error(\n \"useAnalyticsQuery: Parameters size exceeds the maximum allowed size\",\n );\n }\n\n return serialized;\n } catch (error) {\n console.error(\"useAnalyticsQuery: Failed to serialize parameters\", error);\n return null;\n }\n }, [stableParameters, format, maxParametersSize]);\n\n const start = useCallback(() => {\n if (payload === null) {\n setError(\"Failed to serialize query parameters\");\n return;\n }\n\n abortControllerRef.current?.abort();\n\n setLoading(true);\n setError(null);\n setData(null);\n setWarehouseStatus(null);\n publishWarehouseStatus(null);\n\n const abortController = new AbortController();\n abortControllerRef.current = abortController;\n\n const sseContext: AnalyticsQuerySseContext<ResultType> = {\n setLoading,\n setError,\n setData,\n setWarehouseStatus,\n publishWarehouseStatus,\n unpublishWarehouseStatus,\n };\n\n connectSSE({\n url: urlSuffix,\n payload,\n signal: abortController.signal,\n onMessage: async (message) => {\n try {\n const parsed = JSON.parse(message.data) as Record<string, unknown>;\n await handleAnalyticsSseMessage(parsed, sseContext);\n } catch (error) {\n console.warn(\"[useAnalyticsQuery] Malformed message received\", error);\n }\n },\n onError: (error) => {\n if (abortController.signal.aborted) return;\n setLoading(false);\n unpublishWarehouseStatus();\n\n let userMessage = GENERIC_LOAD_ERROR;\n if (error instanceof Error) {\n if (error.name === \"AbortError\") {\n userMessage = \"Request timed out, please try again\";\n } else if (error.message.includes(\"Failed to fetch\")) {\n userMessage = \"Network error. Please check your connection.\";\n }\n console.error(\"[useAnalyticsQuery] Error\", {\n queryKey,\n error: error.message,\n stack: error.stack,\n });\n }\n setError(userMessage);\n },\n });\n }, [\n queryKey,\n payload,\n urlSuffix,\n publishWarehouseStatus,\n unpublishWarehouseStatus,\n ]);\n\n useEffect(() => {\n if (autoStart) {\n start();\n }\n\n return () => {\n abortControllerRef.current?.abort();\n unpublishWarehouseStatus();\n };\n }, [start, autoStart, unpublishWarehouseStatus]);\n\n useQueryHMR(queryKey, start);\n\n return { data, loading, error, warehouseStatus };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { useResourceStatusPublisher } from "./use-resource-status.js";
|
|
2
|
+
import { useCallback, useRef } from "react";
|
|
3
|
+
|
|
4
|
+
//#region src/react/hooks/use-analytics-warehouse-status.ts
|
|
5
|
+
const ANALYTICS_WAREHOUSE_RESOURCE_KIND = "warehouse";
|
|
6
|
+
/**
|
|
7
|
+
* - `RUNNING` → `null`; callers `unpublish` instead.
|
|
8
|
+
* - `STARTING` / `STOPPED` / `STOPPING` → `pending`.
|
|
9
|
+
* - `DELETED` / `DELETING` → `error` (config change required).
|
|
10
|
+
*/
|
|
11
|
+
function severityForWarehouseState(state) {
|
|
12
|
+
switch (state) {
|
|
13
|
+
case "RUNNING": return null;
|
|
14
|
+
case "DELETED":
|
|
15
|
+
case "DELETING": return "error";
|
|
16
|
+
default: return "pending";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Internal hook used by `useAnalyticsQuery` to mirror its current warehouse
|
|
21
|
+
* status into the nearest provider. No-op when no provider is mounted.
|
|
22
|
+
*/
|
|
23
|
+
function useAnalyticsWarehousePublisher(id, queryKey) {
|
|
24
|
+
const { publish: publishGeneric, unpublish } = useResourceStatusPublisher(id, queryKey, { kindHint: ANALYTICS_WAREHOUSE_RESOURCE_KIND });
|
|
25
|
+
const startedAtRef = useRef(null);
|
|
26
|
+
return {
|
|
27
|
+
publish: useCallback((status) => {
|
|
28
|
+
const severity = status && severityForWarehouseState(status.state);
|
|
29
|
+
if (!status || !severity) {
|
|
30
|
+
startedAtRef.current = null;
|
|
31
|
+
publishGeneric(null);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (startedAtRef.current === null) startedAtRef.current = Date.now() - Math.max(0, status.elapsedMs);
|
|
35
|
+
publishGeneric({
|
|
36
|
+
kind: ANALYTICS_WAREHOUSE_RESOURCE_KIND,
|
|
37
|
+
state: status.state,
|
|
38
|
+
severity,
|
|
39
|
+
startedAt: startedAtRef.current
|
|
40
|
+
});
|
|
41
|
+
}, [publishGeneric]),
|
|
42
|
+
unpublish
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
//#endregion
|
|
47
|
+
export { useAnalyticsWarehousePublisher };
|
|
48
|
+
//# sourceMappingURL=use-analytics-warehouse-status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-analytics-warehouse-status.js","names":[],"sources":["../../../src/react/hooks/use-analytics-warehouse-status.ts"],"sourcesContent":["import { useCallback, useRef } from \"react\";\nimport type { WarehouseStatus } from \"./types\";\nimport {\n type ResourceSeverity,\n useResourceStatusPublisher,\n} from \"./use-resource-status\";\n\nconst ANALYTICS_WAREHOUSE_RESOURCE_KIND = \"warehouse\";\n\n/**\n * - `RUNNING` → `null`; callers `unpublish` instead.\n * - `STARTING` / `STOPPED` / `STOPPING` → `pending`.\n * - `DELETED` / `DELETING` → `error` (config change required).\n */\nfunction severityForWarehouseState(\n state: WarehouseStatus[\"state\"],\n): ResourceSeverity | null {\n switch (state) {\n case \"RUNNING\":\n return null;\n case \"DELETED\":\n case \"DELETING\":\n return \"error\";\n default:\n return \"pending\";\n }\n}\n\n/**\n * Internal hook used by `useAnalyticsQuery` to mirror its current warehouse\n * status into the nearest provider. No-op when no provider is mounted.\n */\nexport function useAnalyticsWarehousePublisher(\n id: string,\n queryKey: string,\n): {\n publish: (status: WarehouseStatus | null) => void;\n unpublish: () => void;\n} {\n const { publish: publishGeneric, unpublish } = useResourceStatusPublisher(\n id,\n queryKey,\n { kindHint: ANALYTICS_WAREHOUSE_RESOURCE_KIND },\n );\n\n // Anchor `startedAt` to the first non-null status so `elapsedMs`\n // advances monotonically across successive `warehouse_status` events.\n const startedAtRef = useRef<number | null>(null);\n\n const publish = useCallback(\n (status: WarehouseStatus | null) => {\n // null covers \"register with no status yet\" *and* RUNNING — both\n // keep the slot registered without contributing to the aggregate.\n const severity = status && severityForWarehouseState(status.state);\n if (!status || !severity) {\n startedAtRef.current = null;\n publishGeneric(null);\n return;\n }\n if (startedAtRef.current === null) {\n startedAtRef.current = Date.now() - Math.max(0, status.elapsedMs);\n }\n publishGeneric({\n kind: ANALYTICS_WAREHOUSE_RESOURCE_KIND,\n state: status.state,\n severity,\n startedAt: startedAtRef.current,\n });\n },\n [publishGeneric],\n );\n\n return { publish, unpublish };\n}\n"],"mappings":";;;;AAOA,MAAM,oCAAoC;;;;;;AAO1C,SAAS,0BACP,OACyB;AACzB,SAAQ,OAAR;EACE,KAAK,UACH,QAAO;EACT,KAAK;EACL,KAAK,WACH,QAAO;EACT,QACE,QAAO;;;;;;;AAQb,SAAgB,+BACd,IACA,UAIA;CACA,MAAM,EAAE,SAAS,gBAAgB,cAAc,2BAC7C,IACA,UACA,EAAE,UAAU,mCAAmC,CAChD;CAID,MAAM,eAAe,OAAsB,KAAK;AAyBhD,QAAO;EAAE,SAvBO,aACb,WAAmC;GAGlC,MAAM,WAAW,UAAU,0BAA0B,OAAO,MAAM;AAClE,OAAI,CAAC,UAAU,CAAC,UAAU;AACxB,iBAAa,UAAU;AACvB,mBAAe,KAAK;AACpB;;AAEF,OAAI,aAAa,YAAY,KAC3B,cAAa,UAAU,KAAK,KAAK,GAAG,KAAK,IAAI,GAAG,OAAO,UAAU;AAEnE,kBAAe;IACb,MAAM;IACN,OAAO,OAAO;IACd;IACA,WAAW,aAAa;IACzB,CAAC;KAEJ,CAAC,eAAe,CACjB;EAEiB;EAAW"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ChartData, DataFormat } from "../charts/types.js";
|
|
2
|
+
import { WarehouseStatus } from "./types.js";
|
|
2
3
|
|
|
3
4
|
//#region src/react/hooks/use-chart-data.d.ts
|
|
4
5
|
interface UseChartDataOptions {
|
|
@@ -28,6 +29,13 @@ interface UseChartDataResult {
|
|
|
28
29
|
error: string | null;
|
|
29
30
|
/** Whether the data is empty */
|
|
30
31
|
isEmpty: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Latest warehouse readiness status from SSE. Retains the last value
|
|
34
|
+
* (including `RUNNING`) until the next `start()`; `null` only before
|
|
35
|
+
* the first event. Use with `loading` to distinguish warehouse warm-up
|
|
36
|
+
* from in-flight SQL fetch.
|
|
37
|
+
*/
|
|
38
|
+
warehouseStatus: WarehouseStatus | null;
|
|
31
39
|
}
|
|
32
40
|
/**
|
|
33
41
|
* Hook for fetching chart data in either JSON or Arrow format.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-chart-data.d.ts","names":[],"sources":["../../../src/react/hooks/use-chart-data.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"use-chart-data.d.ts","names":[],"sources":["../../../src/react/hooks/use-chart-data.ts"],"mappings":";;;;UAaiB,mBAAA;;EAEf,QAAA;EAFkC;EAIlC,UAAA,GAAa,MAAA;EAAA;;;;;;;EAQb,MAAA,GAAS,UAAA;EARI;EAUb,WAAA,OAAkB,IAAA,EAAM,CAAA,KAAM,CAAA;AAAA;AAAA,UAGf,kBAAA;EAHA;EAKf,IAAA,EAAM,SAAA;EALY;EAOlB,OAAA;EAP+B;EAS/B,OAAA;EANe;EAQf,KAAA;;EAEA,OAAA;EARA;;;;;;EAeA,eAAA,EAAiB,eAAA;AAAA;;;AAgEnB;;;;;;;;;;;;;;;;;iBAAgB,YAAA,CAAa,OAAA,EAAS,mBAAA,GAAsB,kBAAA"}
|
|
@@ -43,7 +43,7 @@ function useChartData(options) {
|
|
|
43
43
|
const { queryKey, parameters, format = "auto", transformer } = options;
|
|
44
44
|
const resolvedFormat = useMemo(() => resolveFormat(format, parameters), [format, parameters]);
|
|
45
45
|
const isArrowFormat = resolvedFormat === "ARROW_STREAM";
|
|
46
|
-
const { data: rawData, loading, error } = useAnalyticsQuery(queryKey, parameters, {
|
|
46
|
+
const { data: rawData, loading, error, warehouseStatus } = useAnalyticsQuery(queryKey, parameters, {
|
|
47
47
|
autoStart: true,
|
|
48
48
|
format: resolvedFormat
|
|
49
49
|
});
|
|
@@ -71,7 +71,8 @@ function useChartData(options) {
|
|
|
71
71
|
}, [processedData, isArrowFormat]),
|
|
72
72
|
loading,
|
|
73
73
|
error,
|
|
74
|
-
isEmpty
|
|
74
|
+
isEmpty,
|
|
75
|
+
warehouseStatus
|
|
75
76
|
};
|
|
76
77
|
}
|
|
77
78
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-chart-data.js","names":[],"sources":["../../../src/react/hooks/use-chart-data.ts"],"sourcesContent":["import type { Table } from \"apache-arrow\";\nimport { useMemo } from \"react\";\nimport type { ChartData, DataFormat } from \"../charts/types\";\nimport { useAnalyticsQuery } from \"./use-analytics-query\";\n\n/** Threshold for auto-selecting Arrow format (row count hint) */\nconst ARROW_THRESHOLD = 500;\n\n// ============================================================================\n// Hook Options & Result Types\n// ============================================================================\n\nexport interface UseChartDataOptions {\n /** Analytics query key */\n queryKey: string;\n /** Query parameters */\n parameters?: Record<string, unknown>;\n /**\n * Data format preference\n * - \"json\": Force JSON format\n * - \"arrow\": Force Arrow format\n * - \"auto\": Auto-select based on heuristics\n * @default \"auto\"\n */\n format?: DataFormat;\n /** Transform data after fetching */\n transformer?: <T>(data: T) => T;\n}\n\nexport interface UseChartDataResult {\n /** The fetched data (Arrow Table or JSON array) */\n data: ChartData | null;\n /** Whether the data is in Arrow format */\n isArrow: boolean;\n /** Loading state */\n loading: boolean;\n /** Error message if any */\n error: string | null;\n /** Whether the data is empty */\n isEmpty: boolean;\n}\n\n// ============================================================================\n// Format Resolution\n// ============================================================================\n\n/**\n * Resolves the data format based on hints and preferences\n */\nfunction resolveFormat(\n format: DataFormat,\n parameters?: Record<string, unknown>,\n): \"JSON_ARRAY\" | \"ARROW_STREAM\" {\n // Explicit format selection\n if (format === \"json\") return \"JSON_ARRAY\";\n if (format === \"arrow\") return \"ARROW_STREAM\";\n\n // Auto-selection heuristics\n if (format === \"auto\") {\n // Check for explicit hint in parameters\n if (parameters?._preferArrow === true) return \"ARROW_STREAM\";\n if (parameters?._preferJson === true) return \"JSON_ARRAY\";\n\n // Check limit parameter as data size hint\n const limit = parameters?.limit;\n if (typeof limit === \"number\" && limit > ARROW_THRESHOLD) {\n return \"ARROW_STREAM\";\n }\n\n // Check for date range queries (often large)\n if (parameters?.startDate && parameters?.endDate) {\n return \"ARROW_STREAM\";\n }\n\n return \"JSON_ARRAY\";\n }\n\n return \"JSON_ARRAY\";\n}\n\n// ============================================================================\n// Main Hook\n// ============================================================================\n\n/**\n * Hook for fetching chart data in either JSON or Arrow format.\n * Automatically selects the best format based on query hints.\n *\n * @example\n * ```tsx\n * // Auto-select format\n * const { data, isArrow, loading } = useChartData({\n * queryKey: \"spend_data\",\n * parameters: { limit: 1000 }\n * });\n *\n * // Force Arrow format\n * const { data } = useChartData({\n * queryKey: \"big_query\",\n * format: \"arrow\"\n * });\n * ```\n */\nexport function useChartData(options: UseChartDataOptions): UseChartDataResult {\n const { queryKey, parameters, format = \"auto\", transformer } = options;\n\n // Resolve the format to use\n const resolvedFormat = useMemo(\n () => resolveFormat(format, parameters),\n [format, parameters],\n );\n\n const isArrowFormat = resolvedFormat === \"ARROW_STREAM\";\n\n // Fetch data using the analytics query hook\n const {\n data: rawData,\n loading,\n error,\n } = useAnalyticsQuery(queryKey, parameters, {\n autoStart: true,\n format: resolvedFormat,\n });\n\n // Process and transform data\n const processedData = useMemo(() => {\n if (!rawData) return null;\n\n // Apply transformer if provided\n if (transformer) {\n try {\n return transformer(rawData);\n } catch (err) {\n console.error(\"[useChartData] Transformer error:\", err);\n return rawData;\n }\n }\n\n return rawData;\n }, [rawData, transformer]);\n\n // Determine if data is empty\n const isEmpty = useMemo(() => {\n if (!processedData) return true;\n\n // Arrow Table - check using duck typing\n if (\n typeof processedData === \"object\" &&\n \"numRows\" in processedData &&\n typeof (processedData as Table).numRows === \"number\"\n ) {\n return (processedData as Table).numRows === 0;\n }\n\n // JSON Array\n if (Array.isArray(processedData)) {\n return processedData.length === 0;\n }\n\n return true;\n }, [processedData]);\n\n // Detect actual data type (may differ from requested if server doesn't support format)\n const isArrow = useMemo(() => {\n if (!processedData) return isArrowFormat;\n // Duck type check for Arrow Table\n return (\n typeof processedData === \"object\" &&\n processedData !== null &&\n \"schema\" in processedData &&\n \"numRows\" in processedData &&\n typeof (processedData as Table).getChild === \"function\"\n );\n }, [processedData, isArrowFormat]);\n\n return {\n data: processedData as ChartData | null,\n isArrow,\n loading,\n error,\n isEmpty,\n };\n}\n"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"use-chart-data.js","names":[],"sources":["../../../src/react/hooks/use-chart-data.ts"],"sourcesContent":["import type { Table } from \"apache-arrow\";\nimport { useMemo } from \"react\";\nimport type { ChartData, DataFormat } from \"../charts/types\";\nimport type { WarehouseStatus } from \"./types\";\nimport { useAnalyticsQuery } from \"./use-analytics-query\";\n\n/** Threshold for auto-selecting Arrow format (row count hint) */\nconst ARROW_THRESHOLD = 500;\n\n// ============================================================================\n// Hook Options & Result Types\n// ============================================================================\n\nexport interface UseChartDataOptions {\n /** Analytics query key */\n queryKey: string;\n /** Query parameters */\n parameters?: Record<string, unknown>;\n /**\n * Data format preference\n * - \"json\": Force JSON format\n * - \"arrow\": Force Arrow format\n * - \"auto\": Auto-select based on heuristics\n * @default \"auto\"\n */\n format?: DataFormat;\n /** Transform data after fetching */\n transformer?: <T>(data: T) => T;\n}\n\nexport interface UseChartDataResult {\n /** The fetched data (Arrow Table or JSON array) */\n data: ChartData | null;\n /** Whether the data is in Arrow format */\n isArrow: boolean;\n /** Loading state */\n loading: boolean;\n /** Error message if any */\n error: string | null;\n /** Whether the data is empty */\n isEmpty: boolean;\n /**\n * Latest warehouse readiness status from SSE. Retains the last value\n * (including `RUNNING`) until the next `start()`; `null` only before\n * the first event. Use with `loading` to distinguish warehouse warm-up\n * from in-flight SQL fetch.\n */\n warehouseStatus: WarehouseStatus | null;\n}\n\n// ============================================================================\n// Format Resolution\n// ============================================================================\n\n/**\n * Resolves the data format based on hints and preferences\n */\nfunction resolveFormat(\n format: DataFormat,\n parameters?: Record<string, unknown>,\n): \"JSON_ARRAY\" | \"ARROW_STREAM\" {\n // Explicit format selection\n if (format === \"json\") return \"JSON_ARRAY\";\n if (format === \"arrow\") return \"ARROW_STREAM\";\n\n // Auto-selection heuristics\n if (format === \"auto\") {\n // Check for explicit hint in parameters\n if (parameters?._preferArrow === true) return \"ARROW_STREAM\";\n if (parameters?._preferJson === true) return \"JSON_ARRAY\";\n\n // Check limit parameter as data size hint\n const limit = parameters?.limit;\n if (typeof limit === \"number\" && limit > ARROW_THRESHOLD) {\n return \"ARROW_STREAM\";\n }\n\n // Check for date range queries (often large)\n if (parameters?.startDate && parameters?.endDate) {\n return \"ARROW_STREAM\";\n }\n\n return \"JSON_ARRAY\";\n }\n\n return \"JSON_ARRAY\";\n}\n\n// ============================================================================\n// Main Hook\n// ============================================================================\n\n/**\n * Hook for fetching chart data in either JSON or Arrow format.\n * Automatically selects the best format based on query hints.\n *\n * @example\n * ```tsx\n * // Auto-select format\n * const { data, isArrow, loading } = useChartData({\n * queryKey: \"spend_data\",\n * parameters: { limit: 1000 }\n * });\n *\n * // Force Arrow format\n * const { data } = useChartData({\n * queryKey: \"big_query\",\n * format: \"arrow\"\n * });\n * ```\n */\nexport function useChartData(options: UseChartDataOptions): UseChartDataResult {\n const { queryKey, parameters, format = \"auto\", transformer } = options;\n\n // Resolve the format to use\n const resolvedFormat = useMemo(\n () => resolveFormat(format, parameters),\n [format, parameters],\n );\n\n const isArrowFormat = resolvedFormat === \"ARROW_STREAM\";\n\n // Fetch data using the analytics query hook\n const {\n data: rawData,\n loading,\n error,\n warehouseStatus,\n } = useAnalyticsQuery(queryKey, parameters, {\n autoStart: true,\n format: resolvedFormat,\n });\n\n // Process and transform data\n const processedData = useMemo(() => {\n if (!rawData) return null;\n\n // Apply transformer if provided\n if (transformer) {\n try {\n return transformer(rawData);\n } catch (err) {\n console.error(\"[useChartData] Transformer error:\", err);\n return rawData;\n }\n }\n\n return rawData;\n }, [rawData, transformer]);\n\n // Determine if data is empty\n const isEmpty = useMemo(() => {\n if (!processedData) return true;\n\n // Arrow Table - check using duck typing\n if (\n typeof processedData === \"object\" &&\n \"numRows\" in processedData &&\n typeof (processedData as Table).numRows === \"number\"\n ) {\n return (processedData as Table).numRows === 0;\n }\n\n // JSON Array\n if (Array.isArray(processedData)) {\n return processedData.length === 0;\n }\n\n return true;\n }, [processedData]);\n\n // Detect actual data type (may differ from requested if server doesn't support format)\n const isArrow = useMemo(() => {\n if (!processedData) return isArrowFormat;\n // Duck type check for Arrow Table\n return (\n typeof processedData === \"object\" &&\n processedData !== null &&\n \"schema\" in processedData &&\n \"numRows\" in processedData &&\n typeof (processedData as Table).getChild === \"function\"\n );\n }, [processedData, isArrowFormat]);\n\n return {\n data: processedData as ChartData | null,\n isArrow,\n loading,\n error,\n isEmpty,\n warehouseStatus,\n };\n}\n"],"mappings":";;;;;AAOA,MAAM,kBAAkB;;;;AAkDxB,SAAS,cACP,QACA,YAC+B;AAE/B,KAAI,WAAW,OAAQ,QAAO;AAC9B,KAAI,WAAW,QAAS,QAAO;AAG/B,KAAI,WAAW,QAAQ;AAErB,MAAI,YAAY,iBAAiB,KAAM,QAAO;AAC9C,MAAI,YAAY,gBAAgB,KAAM,QAAO;EAG7C,MAAM,QAAQ,YAAY;AAC1B,MAAI,OAAO,UAAU,YAAY,QAAQ,gBACvC,QAAO;AAIT,MAAI,YAAY,aAAa,YAAY,QACvC,QAAO;AAGT,SAAO;;AAGT,QAAO;;;;;;;;;;;;;;;;;;;;;AA0BT,SAAgB,aAAa,SAAkD;CAC7E,MAAM,EAAE,UAAU,YAAY,SAAS,QAAQ,gBAAgB;CAG/D,MAAM,iBAAiB,cACf,cAAc,QAAQ,WAAW,EACvC,CAAC,QAAQ,WAAW,CACrB;CAED,MAAM,gBAAgB,mBAAmB;CAGzC,MAAM,EACJ,MAAM,SACN,SACA,OACA,oBACE,kBAAkB,UAAU,YAAY;EAC1C,WAAW;EACX,QAAQ;EACT,CAAC;CAGF,MAAM,gBAAgB,cAAc;AAClC,MAAI,CAAC,QAAS,QAAO;AAGrB,MAAI,YACF,KAAI;AACF,UAAO,YAAY,QAAQ;WACpB,KAAK;AACZ,WAAQ,MAAM,qCAAqC,IAAI;AACvD,UAAO;;AAIX,SAAO;IACN,CAAC,SAAS,YAAY,CAAC;CAG1B,MAAM,UAAU,cAAc;AAC5B,MAAI,CAAC,cAAe,QAAO;AAG3B,MACE,OAAO,kBAAkB,YACzB,aAAa,iBACb,OAAQ,cAAwB,YAAY,SAE5C,QAAQ,cAAwB,YAAY;AAI9C,MAAI,MAAM,QAAQ,cAAc,CAC9B,QAAO,cAAc,WAAW;AAGlC,SAAO;IACN,CAAC,cAAc,CAAC;AAenB,QAAO;EACL,MAAM;EACN,SAdc,cAAc;AAC5B,OAAI,CAAC,cAAe,QAAO;AAE3B,UACE,OAAO,kBAAkB,YACzB,kBAAkB,QAClB,YAAY,iBACZ,aAAa,iBACb,OAAQ,cAAwB,aAAa;KAE9C,CAAC,eAAe,cAAc,CAAC;EAKhC;EACA;EACA;EACA;EACD"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
3
|
+
|
|
4
|
+
//#region src/react/hooks/use-resource-status.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Cross-kind severity, ordered worst-first (`error > warning > pending`).
|
|
7
|
+
* Callers `unpublish` rather than publishing a status for ready resources.
|
|
8
|
+
*/
|
|
9
|
+
type ResourceSeverity = "pending" | "warning" | "error";
|
|
10
|
+
/**
|
|
11
|
+
* Readiness snapshot for a single resource (SQL warehouse, Lakebase
|
|
12
|
+
* connection, model-serving endpoint, …). Plugins publish these while a
|
|
13
|
+
* user-visible cold start / warm-up / unavailability is in flight.
|
|
14
|
+
*/
|
|
15
|
+
interface ResourceStatus {
|
|
16
|
+
/** Resource family, conventionally lowercase-kebab (`"warehouse"`, `"lakebase"`). */
|
|
17
|
+
kind: string;
|
|
18
|
+
/** Resource-specific raw state, e.g. `"STARTING"`, `"DELETED"`. Opaque to the aggregator. */
|
|
19
|
+
state: string;
|
|
20
|
+
severity: ResourceSeverity;
|
|
21
|
+
/** Human-readable summary forwarded to the indicator UI. */
|
|
22
|
+
summary?: string;
|
|
23
|
+
/** Epoch ms when the publisher started waiting; drives `elapsedMs`. */
|
|
24
|
+
startedAt: number;
|
|
25
|
+
}
|
|
26
|
+
/** Aggregate view of every active publisher; returned by {@link useResourceStatus}. */
|
|
27
|
+
interface AggregatedResourceStatus {
|
|
28
|
+
/** Highest-severity status across all publishers, or `null` when nothing is pending. */
|
|
29
|
+
worst: ResourceStatus | null;
|
|
30
|
+
/** Worst status per `kind`. */
|
|
31
|
+
byKind: Record<string, ResourceStatus>;
|
|
32
|
+
/** De-duped, sorted labels of every publisher with a non-null status. */
|
|
33
|
+
affectedLabels: string[];
|
|
34
|
+
/** Total registered publishers (including those whose status is `null`). */
|
|
35
|
+
activeCount: number;
|
|
36
|
+
/** Milliseconds since the worst entry's `startedAt`; `0` when nothing is pending. */
|
|
37
|
+
elapsedMs: number;
|
|
38
|
+
/** Monotonic counter bumped on every `publish`/`unpublish`. */
|
|
39
|
+
version: number;
|
|
40
|
+
}
|
|
41
|
+
/** Optional filter for {@link useResourceStatus}. */
|
|
42
|
+
interface ResourceStatusFilter {
|
|
43
|
+
/** Restrict the aggregate to a single resource kind. */
|
|
44
|
+
kind?: string;
|
|
45
|
+
}
|
|
46
|
+
interface ResourceStatusProviderProps {
|
|
47
|
+
children: ReactNode;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Mount once near the root of your app to enable a global, cross-plugin
|
|
51
|
+
* readiness aggregate. Plugins publish {@link ResourceStatus} snapshots
|
|
52
|
+
* while a resource is warming up / unavailable; {@link useResourceStatus}
|
|
53
|
+
* exposes the worst across all of them.
|
|
54
|
+
*
|
|
55
|
+
* Without a provider, `useResourceStatus` and `useResourceStatusPublisher`
|
|
56
|
+
* fall back to no-ops, so plugins are safe to call them unconditionally.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```tsx
|
|
60
|
+
* <ResourceStatusProvider>
|
|
61
|
+
* <ResourceStatusIndicator />
|
|
62
|
+
* <App />
|
|
63
|
+
* </ResourceStatusProvider>
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
declare function ResourceStatusProvider({
|
|
67
|
+
children
|
|
68
|
+
}: ResourceStatusProviderProps): react_jsx_runtime0.JSX.Element;
|
|
69
|
+
/**
|
|
70
|
+
* Returns the aggregated resource-readiness snapshot across every active
|
|
71
|
+
* publisher under the nearest {@link ResourceStatusProvider}. Falls back
|
|
72
|
+
* to the empty/idle aggregate when no provider is mounted.
|
|
73
|
+
*
|
|
74
|
+
* @param filter Optional `{ kind }` to scope to a single resource kind.
|
|
75
|
+
*/
|
|
76
|
+
declare function useResourceStatus(filter?: ResourceStatusFilter): AggregatedResourceStatus;
|
|
77
|
+
/**
|
|
78
|
+
* Register a publisher with the nearest {@link ResourceStatusProvider}.
|
|
79
|
+
* Safe to call without a provider — `publish`/`unpublish` are no-ops.
|
|
80
|
+
*
|
|
81
|
+
* @param id Stable identifier (e.g. a `useId()` value).
|
|
82
|
+
* @param label Human-readable label surfaced via `affectedLabels`.
|
|
83
|
+
*/
|
|
84
|
+
declare function useResourceStatusPublisher(id: string, label: string, options?: {
|
|
85
|
+
kindHint?: string;
|
|
86
|
+
}): {
|
|
87
|
+
publish: (status: ResourceStatus | null) => void;
|
|
88
|
+
unpublish: () => void;
|
|
89
|
+
};
|
|
90
|
+
//#endregion
|
|
91
|
+
export { AggregatedResourceStatus, ResourceSeverity, ResourceStatus, ResourceStatusFilter, ResourceStatusProvider, ResourceStatusProviderProps, useResourceStatus, useResourceStatusPublisher };
|
|
92
|
+
//# sourceMappingURL=use-resource-status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-resource-status.d.ts","names":[],"sources":["../../../src/react/hooks/use-resource-status.tsx"],"mappings":";;;;;;;AAgBA;KAAY,gBAAA;;;;AAOZ;;UAAiB,cAAA;EAKW;EAH1B,IAAA;EAEA;EAAA,KAAA;EACA,QAAA,EAAU,gBAAA;EAEV;EAAA,OAAA;EAES;EAAT,SAAA;AAAA;;UAIe,wBAAA;EAER;EAAP,KAAA,EAAO,cAAA;EAEC;EAAR,MAAA,EAAQ,MAAA,SAAe,cAAA;EAAT;EAEd,cAAA;EAJO;EAMP,WAAA;EAJQ;EAMR,SAAA;EAJA;EAMA,OAAA;AAAA;;UAIe,oBAAA;EAJR;EAMP,IAAA;AAAA;AAAA,UAsKe,2BAAA;EACf,QAAA,EAAU,SAAA;AAAA;AADZ;;;;;AAqBA;;;;;;;;;;;;AArBA,iBAqBgB,sBAAA,CAAA;EACd;AAAA,GACC,2BAAA,GAA2B,kBAAA,CAAA,GAAA,CAAA,OAAA;;;AAuB9B;;;;;iBAAgB,iBAAA,CACd,MAAA,GAAS,oBAAA,GACR,wBAAA;;;;AAgCH;;;;iBAAgB,0BAAA,CACd,EAAA,UACA,KAAA,UACA,OAAA;EAAY,QAAA;AAAA;EAEZ,OAAA,GAAU,MAAA,EAAQ,cAAA;EAClB,SAAA;AAAA"}
|