@lightdash/query-sdk 3.96.1 → 3.98.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/apiTransport.d.ts +23 -0
- package/dist/apiTransport.js +97 -1
- package/dist/features.js +14 -0
- package/dist/generated/sdkVersion.d.ts +1 -1
- package/dist/generated/sdkVersion.js +1 -1
- package/dist/hostContext.d.ts +15 -3
- package/dist/hostContext.js +37 -5
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -1
- package/dist/insights.d.ts +8 -0
- package/dist/insights.js +30 -0
- package/dist/postMessageTransport.js +13 -2
- package/dist/spaceCharts.d.ts +26 -0
- package/dist/spaceCharts.js +82 -0
- package/dist/types.d.ts +36 -0
- package/dist/types.js +7 -0
- package/package.json +2 -2
package/dist/apiTransport.d.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* → returns results when status is 'ready'
|
|
9
9
|
*/
|
|
10
10
|
import type { Column, LightdashClientConfig, Transport } from './types';
|
|
11
|
+
import { type SpaceChartsResult } from './types';
|
|
11
12
|
/**
|
|
12
13
|
* A function that performs HTTP requests and returns parsed JSON results.
|
|
13
14
|
* The default implementation uses `Authorization: ApiKey` header.
|
|
@@ -24,10 +25,32 @@ export declare const createFieldQualifier: (exploreName: string, baseTable: stri
|
|
|
24
25
|
export type ExploreBaseTableResolver = (exploreName: string) => Promise<string | null>;
|
|
25
26
|
export type ApiTransportOptions = {
|
|
26
27
|
resolveExploreBaseTable: ExploreBaseTableResolver | null;
|
|
28
|
+
/** Omitted/null = the transport reports the capability unavailable. */
|
|
29
|
+
listSpaceCharts?: SpaceChartsResolver | null;
|
|
27
30
|
};
|
|
28
31
|
export declare const createApiExploreBaseTableResolver: (fetchFn: FetchAdapter, projectUuid: string) => ExploreBaseTableResolver;
|
|
29
32
|
/** Asks the host (app bridge or CLI preview proxy) via its virtual route. */
|
|
30
33
|
export declare const createHostExploreBaseTableResolver: (fetchFn: FetchAdapter) => ExploreBaseTableResolver;
|
|
34
|
+
/** Lists the saved charts the viewer can see in a space (slug or uuid). */
|
|
35
|
+
export type SpaceChartsResolver = (space: string) => Promise<SpaceChartsResult>;
|
|
36
|
+
/**
|
|
37
|
+
* Thrown when the transport has the capability but the running host does not
|
|
38
|
+
* (a current SDK bundle on an older Lightdash). `useSpaceCharts` maps it to
|
|
39
|
+
* `available: false` instead of an error, so galleries hide cleanly.
|
|
40
|
+
*/
|
|
41
|
+
export declare class SpaceChartsUnsupportedError extends Error {
|
|
42
|
+
constructor();
|
|
43
|
+
}
|
|
44
|
+
/** Normalize an untrusted body into a `SpaceChartsResult`. */
|
|
45
|
+
export declare const normalizeSpaceChartsResult: (value: unknown) => SpaceChartsResult;
|
|
46
|
+
/** Asks the host (app bridge or CLI preview proxy) via its virtual route. */
|
|
47
|
+
export declare const createHostSpaceChartsResolver: (fetchFn: FetchAdapter) => SpaceChartsResolver;
|
|
48
|
+
/**
|
|
49
|
+
* Resolves against the real API (local dev with a PAT): the spaces list to
|
|
50
|
+
* match the slug/uuid, then the space detail for its chart summaries — the
|
|
51
|
+
* same two calls the host makes when answering the virtual route.
|
|
52
|
+
*/
|
|
53
|
+
export declare const createApiSpaceChartsResolver: (fetchFn: FetchAdapter, projectUuid: string) => SpaceChartsResolver;
|
|
31
54
|
/**
|
|
32
55
|
* Thrown for a non-2xx response. `status` lets hosts tell a recoverable
|
|
33
56
|
* 429 (rate limited, retry later) from a real failure.
|
package/dist/apiTransport.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* → returns results when status is 'ready'
|
|
9
9
|
*/
|
|
10
10
|
import { AppStateError } from './appState';
|
|
11
|
-
import { VIZ_DRILL_DOWN_PATH, VIZ_POINT_MENU_PATH, VIZ_UNDERLYING_DATA_OPEN_PATH, VIZ_UNDERLYING_DATA_PATH, VIZ_SUBTOTALS_PATH, EXPLORE_BASE_TABLE_PATH, } from './types';
|
|
11
|
+
import { VIZ_DRILL_DOWN_PATH, VIZ_POINT_MENU_PATH, VIZ_UNDERLYING_DATA_OPEN_PATH, VIZ_UNDERLYING_DATA_PATH, VIZ_SUBTOTALS_PATH, EXPLORE_BASE_TABLE_PATH, SPACE_CHARTS_PATH, } from './types';
|
|
12
12
|
// Mirrors the explorer's `useInfiniteQueryResults` polling rhythm so the
|
|
13
13
|
// SDK behaves like a normal Lightdash chart: 500-row pages, exponential
|
|
14
14
|
// backoff starting at 250ms, capped at 1000ms.
|
|
@@ -98,6 +98,92 @@ export const createHostExploreBaseTableResolver = (fetchFn) => async (exploreNam
|
|
|
98
98
|
const result = await fetchFn('POST', EXPLORE_BASE_TABLE_PATH, body);
|
|
99
99
|
return toBaseTable(result?.baseTable);
|
|
100
100
|
};
|
|
101
|
+
/**
|
|
102
|
+
* Thrown when the transport has the capability but the running host does not
|
|
103
|
+
* (a current SDK bundle on an older Lightdash). `useSpaceCharts` maps it to
|
|
104
|
+
* `available: false` instead of an error, so galleries hide cleanly.
|
|
105
|
+
*/
|
|
106
|
+
export class SpaceChartsUnsupportedError extends Error {
|
|
107
|
+
constructor() {
|
|
108
|
+
super('Space charts listing is not supported by this Lightdash host');
|
|
109
|
+
this.name = 'SpaceChartsUnsupportedError';
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const EMPTY_SPACE_CHARTS = { space: null, charts: [] };
|
|
113
|
+
const toNullableString = (value) => typeof value === 'string' && value.length > 0 ? value : null;
|
|
114
|
+
/** Normalize an untrusted body into a `SpaceChartsResult`. */
|
|
115
|
+
export const normalizeSpaceChartsResult = (value) => {
|
|
116
|
+
if (typeof value !== 'object' || value === null)
|
|
117
|
+
return EMPTY_SPACE_CHARTS;
|
|
118
|
+
const { space, charts } = value;
|
|
119
|
+
let spaceInfo = null;
|
|
120
|
+
if (typeof space === 'object' && space !== null) {
|
|
121
|
+
const { uuid, name, slug } = space;
|
|
122
|
+
if (typeof uuid === 'string' &&
|
|
123
|
+
typeof name === 'string' &&
|
|
124
|
+
typeof slug === 'string') {
|
|
125
|
+
spaceInfo = { uuid, name, slug };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const chartList = (Array.isArray(charts) ? charts : []).flatMap((chart) => {
|
|
129
|
+
if (typeof chart !== 'object' || chart === null)
|
|
130
|
+
return [];
|
|
131
|
+
const entry = chart;
|
|
132
|
+
if (typeof entry.uuid !== 'string' || typeof entry.name !== 'string') {
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
return [
|
|
136
|
+
{
|
|
137
|
+
uuid: entry.uuid,
|
|
138
|
+
name: entry.name,
|
|
139
|
+
description: toNullableString(entry.description),
|
|
140
|
+
chartKind: toNullableString(entry.chartKind),
|
|
141
|
+
slug: toNullableString(entry.slug),
|
|
142
|
+
updatedAt: toNullableString(entry.updatedAt),
|
|
143
|
+
},
|
|
144
|
+
];
|
|
145
|
+
});
|
|
146
|
+
return { space: spaceInfo, charts: chartList };
|
|
147
|
+
};
|
|
148
|
+
/** Asks the host (app bridge or CLI preview proxy) via its virtual route. */
|
|
149
|
+
export const createHostSpaceChartsResolver = (fetchFn) => async (space) => {
|
|
150
|
+
const body = { space };
|
|
151
|
+
const result = await fetchFn('POST', SPACE_CHARTS_PATH, body);
|
|
152
|
+
return normalizeSpaceChartsResult(result);
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* Resolves against the real API (local dev with a PAT): the spaces list to
|
|
156
|
+
* match the slug/uuid, then the space detail for its chart summaries — the
|
|
157
|
+
* same two calls the host makes when answering the virtual route.
|
|
158
|
+
*/
|
|
159
|
+
export const createApiSpaceChartsResolver = (fetchFn, projectUuid) => async (space) => {
|
|
160
|
+
try {
|
|
161
|
+
const spaces = await fetchFn('GET', `/api/v1/projects/${projectUuid}/spaces`);
|
|
162
|
+
const match = (Array.isArray(spaces) ? spaces : []).find((entry) => {
|
|
163
|
+
if (typeof entry !== 'object' || entry === null)
|
|
164
|
+
return false;
|
|
165
|
+
const { uuid, slug } = entry;
|
|
166
|
+
return uuid === space || slug === space;
|
|
167
|
+
});
|
|
168
|
+
if (typeof match?.uuid !== 'string')
|
|
169
|
+
return EMPTY_SPACE_CHARTS;
|
|
170
|
+
const detail = await fetchFn('GET', `/api/v1/projects/${projectUuid}/spaces/${match.uuid}`);
|
|
171
|
+
if (typeof detail !== 'object' || detail === null)
|
|
172
|
+
return EMPTY_SPACE_CHARTS;
|
|
173
|
+
return normalizeSpaceChartsResult({
|
|
174
|
+
space: detail,
|
|
175
|
+
charts: detail.queries,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
catch (e) {
|
|
179
|
+
// Same face as the host route: no access answers like not found.
|
|
180
|
+
if (e instanceof LightdashApiError &&
|
|
181
|
+
(e.status === 403 || e.status === 404)) {
|
|
182
|
+
return EMPTY_SPACE_CHARTS;
|
|
183
|
+
}
|
|
184
|
+
throw e;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
101
187
|
function buildUnderlyingDataFilters(query, row, qualify) {
|
|
102
188
|
const sourceFilters = buildApiFilters(query.filters.map((f) => ({
|
|
103
189
|
...f,
|
|
@@ -564,14 +650,21 @@ export function createApiTransport(config, adapter, options) {
|
|
|
564
650
|
// endpoint, so only the default adapter looks base tables up by default.
|
|
565
651
|
// Behind a dev proxy (CLI preview) the proxy answers the host route.
|
|
566
652
|
let defaultResolver = null;
|
|
653
|
+
let defaultSpaceCharts = null;
|
|
567
654
|
if (adapter === undefined) {
|
|
568
655
|
defaultResolver = config.useProxy
|
|
569
656
|
? createHostExploreBaseTableResolver(fetchFn)
|
|
570
657
|
: createApiExploreBaseTableResolver(fetchFn, config.projectUuid);
|
|
658
|
+
defaultSpaceCharts = config.useProxy
|
|
659
|
+
? createHostSpaceChartsResolver(fetchFn)
|
|
660
|
+
: createApiSpaceChartsResolver(fetchFn, config.projectUuid);
|
|
571
661
|
}
|
|
572
662
|
const resolveExploreBaseTable = options === undefined
|
|
573
663
|
? defaultResolver
|
|
574
664
|
: options.resolveExploreBaseTable;
|
|
665
|
+
const listSpaceCharts = options === undefined
|
|
666
|
+
? defaultSpaceCharts
|
|
667
|
+
: (options.listSpaceCharts ?? null);
|
|
575
668
|
const baseTableByExplore = new Map();
|
|
576
669
|
const getFieldQualifier = async (query) => {
|
|
577
670
|
const { exploreName } = query;
|
|
@@ -879,5 +972,8 @@ export function createApiTransport(config, adapter, options) {
|
|
|
879
972
|
async openVizPointMenu(intent) {
|
|
880
973
|
return fetchFn('POST', VIZ_POINT_MENU_PATH, intent);
|
|
881
974
|
},
|
|
975
|
+
// Left absent (not a thrower) when no resolver exists, so
|
|
976
|
+
// `useSpaceCharts().available` honestly reports the capability.
|
|
977
|
+
...(listSpaceCharts ? { listSpaceCharts } : {}),
|
|
882
978
|
};
|
|
883
979
|
}
|
package/dist/features.js
CHANGED
|
@@ -169,6 +169,13 @@ export const SDK_FEATURES = [
|
|
|
169
169
|
description: 'Author-written guidance for the AI analysis — language and formatting rules (e.g. British English, amounts in EUR) and a line or two of domain vocabulary — applied to every summary the app shows.',
|
|
170
170
|
wiring: "Pass the guidance where the app renders its view-level analysis: useInsights({ instructions: '<guidance>' }), or the instructions prop on the shipped InsightsSummary component. One string per app; keep it to a few short sentences.",
|
|
171
171
|
},
|
|
172
|
+
{
|
|
173
|
+
key: 'analysis-on-load',
|
|
174
|
+
appliesTo: ['data_app'],
|
|
175
|
+
label: 'AI analysis on load',
|
|
176
|
+
description: 'Run the AI analysis as soon as the app loads, without the viewer clicking Analyse. A stored analysis of the same rows is reused.',
|
|
177
|
+
wiring: 'Only when the user asks for it: set the analyseOnLoad prop on the shipped InsightsSummary component, or useInsights({ analyseOnLoad: true }) in a custom block. One value per app.',
|
|
178
|
+
},
|
|
172
179
|
{
|
|
173
180
|
key: 'ai-prompt',
|
|
174
181
|
appliesTo: ['data_app'],
|
|
@@ -218,6 +225,13 @@ export const SDK_FEATURES = [
|
|
|
218
225
|
description: "Result columns carry the semantic layer's url link patterns, so values with configured links render as links, matching dashboards.",
|
|
219
226
|
wiring: 'In cell renderers (tables, underlying-data dialogs), call resolveColumnUrls({ column, row, columns, format }) and render the value as a link when it returns entries, styled visibly as a link.',
|
|
220
227
|
},
|
|
228
|
+
{
|
|
229
|
+
key: 'space-charts',
|
|
230
|
+
appliesTo: ['data_app'],
|
|
231
|
+
label: 'List charts in a space',
|
|
232
|
+
description: 'List the saved charts in a Lightdash space at runtime, so chart galleries update as charts are saved — limited to spaces the viewer can already access.',
|
|
233
|
+
wiring: "Call useSpaceCharts('<space slug or uuid>') where the app needs the list, render from its charts array (uuid, name, description, chartKind), and pass chart.uuid to savedChart(...) to show one. Check available first — it stays false on hosts without the capability.",
|
|
234
|
+
},
|
|
221
235
|
];
|
|
222
236
|
export const SDK_FEATURE_KEYS = SDK_FEATURES.map((f) => f.key);
|
|
223
237
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "3.
|
|
1
|
+
export declare const SDK_VERSION = "3.98.0";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by scripts/generateSdkVersion.mjs (prebuild) — do not edit.
|
|
2
|
-
export const SDK_VERSION = '3.
|
|
2
|
+
export const SDK_VERSION = '3.98.0';
|
package/dist/hostContext.d.ts
CHANGED
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Identity the host rides on its `lightdash:sdk:ready`
|
|
3
|
-
* uuid this bundle is deployed as
|
|
4
|
-
*
|
|
2
|
+
* Identity and capabilities the host rides on its `lightdash:sdk:ready`
|
|
3
|
+
* handshake: the app uuid this bundle is deployed as, and which negotiated
|
|
4
|
+
* host features exist (`spaceCharts`). Absent for older hosts and for
|
|
5
|
+
* bundles run outside Lightdash (CLI preview), where host-negotiated
|
|
6
|
+
* features stay off.
|
|
5
7
|
*/
|
|
6
8
|
type HostContext = {
|
|
7
9
|
appUuid: string | null;
|
|
10
|
+
/** Host answers the space-charts virtual route. False for older hosts. */
|
|
11
|
+
spaceCharts: boolean;
|
|
12
|
+
/** A ready handshake has been seen at all. */
|
|
13
|
+
ready: boolean;
|
|
8
14
|
};
|
|
15
|
+
/**
|
|
16
|
+
* Resolves once a ready handshake has been seen, or after `timeoutMs` —
|
|
17
|
+
* mirrors the fetch adapter's readiness gate so capability checks don't
|
|
18
|
+
* hang on hosts that never handshake.
|
|
19
|
+
*/
|
|
20
|
+
export declare function waitForHostContext(timeoutMs: number): Promise<HostContext>;
|
|
9
21
|
/** Called by `createPostMessageTransport()`; one listener per bundle. */
|
|
10
22
|
export declare function mountHostContext(targetWindow: Window): () => void;
|
|
11
23
|
export declare function useHostAppUuid(): string | null;
|
package/dist/hostContext.js
CHANGED
|
@@ -1,18 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Identity the host rides on its `lightdash:sdk:ready`
|
|
3
|
-
* uuid this bundle is deployed as
|
|
4
|
-
*
|
|
2
|
+
* Identity and capabilities the host rides on its `lightdash:sdk:ready`
|
|
3
|
+
* handshake: the app uuid this bundle is deployed as, and which negotiated
|
|
4
|
+
* host features exist (`spaceCharts`). Absent for older hosts and for
|
|
5
|
+
* bundles run outside Lightdash (CLI preview), where host-negotiated
|
|
6
|
+
* features stay off.
|
|
5
7
|
*/
|
|
6
8
|
import { useSyncExternalStore } from 'react';
|
|
7
9
|
const SDK_READY_MESSAGE = 'lightdash:sdk:ready';
|
|
8
|
-
const EMPTY_CONTEXT = {
|
|
10
|
+
const EMPTY_CONTEXT = {
|
|
11
|
+
appUuid: null,
|
|
12
|
+
spaceCharts: false,
|
|
13
|
+
ready: false,
|
|
14
|
+
};
|
|
9
15
|
let value = EMPTY_CONTEXT;
|
|
10
16
|
const listeners = new Set();
|
|
17
|
+
let readyResolvers = [];
|
|
11
18
|
const set = (next) => {
|
|
12
|
-
if (next.appUuid === value.appUuid
|
|
19
|
+
if (next.appUuid === value.appUuid &&
|
|
20
|
+
next.spaceCharts === value.spaceCharts &&
|
|
21
|
+
next.ready === value.ready)
|
|
13
22
|
return;
|
|
14
23
|
value = next;
|
|
15
24
|
listeners.forEach((listener) => listener());
|
|
25
|
+
if (next.ready) {
|
|
26
|
+
readyResolvers.forEach((resolve) => resolve());
|
|
27
|
+
readyResolvers = [];
|
|
28
|
+
}
|
|
16
29
|
};
|
|
17
30
|
const subscribe = (listener) => {
|
|
18
31
|
listeners.add(listener);
|
|
@@ -20,6 +33,22 @@ const subscribe = (listener) => {
|
|
|
20
33
|
listeners.delete(listener);
|
|
21
34
|
};
|
|
22
35
|
};
|
|
36
|
+
/**
|
|
37
|
+
* Resolves once a ready handshake has been seen, or after `timeoutMs` —
|
|
38
|
+
* mirrors the fetch adapter's readiness gate so capability checks don't
|
|
39
|
+
* hang on hosts that never handshake.
|
|
40
|
+
*/
|
|
41
|
+
export function waitForHostContext(timeoutMs) {
|
|
42
|
+
if (value.ready)
|
|
43
|
+
return Promise.resolve(value);
|
|
44
|
+
return new Promise((resolve) => {
|
|
45
|
+
const timer = setTimeout(() => resolve(value), timeoutMs);
|
|
46
|
+
readyResolvers.push(() => {
|
|
47
|
+
clearTimeout(timer);
|
|
48
|
+
resolve(value);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
23
52
|
let activeCleanup = null;
|
|
24
53
|
/** Called by `createPostMessageTransport()`; one listener per bundle. */
|
|
25
54
|
export function mountHostContext(targetWindow) {
|
|
@@ -33,6 +62,8 @@ export function mountHostContext(targetWindow) {
|
|
|
33
62
|
return;
|
|
34
63
|
set({
|
|
35
64
|
appUuid: typeof data.appUuid === 'string' ? data.appUuid : null,
|
|
65
|
+
spaceCharts: data.spaceCharts === true,
|
|
66
|
+
ready: true,
|
|
36
67
|
});
|
|
37
68
|
};
|
|
38
69
|
activeCleanup?.();
|
|
@@ -54,4 +85,5 @@ export function peekHostContext() {
|
|
|
54
85
|
}
|
|
55
86
|
export function resetHostContext() {
|
|
56
87
|
value = EMPTY_CONTEXT;
|
|
88
|
+
readyResolvers = [];
|
|
57
89
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { drillDown } from './drillDown';
|
|
|
4
4
|
export { createClient, LightdashClient } from './client';
|
|
5
5
|
export { useLightdash } from './useLightdash';
|
|
6
6
|
export { LightdashProvider, useLightdashClient } from './LightdashProvider';
|
|
7
|
-
export { createApiTransport, LightdashApiError, type FetchAdapter, } from './apiTransport';
|
|
7
|
+
export { createApiTransport, LightdashApiError, SpaceChartsUnsupportedError, type FetchAdapter, } from './apiTransport';
|
|
8
8
|
export { createPostMessageTransport } from './postMessageTransport';
|
|
9
9
|
export { resolveColumnUrls, type ResolvedColumnUrl } from './columnUrls';
|
|
10
10
|
export { SDK_FEATURES, SDK_FEATURE_KEYS, SDK_FIXES, SDK_FIX_KEYS, SDK_FEATURE_TARGETS, SDK_MANIFEST_MESSAGE_TYPE, type SdkFeature, type SdkFeatureTarget, type SdkFix, type SdkManifestMessage, } from './features';
|
|
@@ -28,6 +28,9 @@ export type { AppStateAuthor, AppStateErrorCode, AppStateErrorPayload, AppStateH
|
|
|
28
28
|
export type { SdkAppStateOp, SdkAppStateRequest, SdkAppStateResponse, } from './postMessageTransport';
|
|
29
29
|
export { useAiPrompt } from './aiPrompt';
|
|
30
30
|
export type { AiPrompt, AiPromptOptions, AiPromptSource } from './aiPrompt';
|
|
31
|
+
export { useSpaceCharts } from './spaceCharts';
|
|
32
|
+
export type { SpaceCharts } from './spaceCharts';
|
|
33
|
+
export type { SpaceChartsResult, SpaceChartSummary } from './types';
|
|
31
34
|
export { useInsights } from './insights';
|
|
32
35
|
export type { Insight, InsightInvestigation, InsightSeverity, InsightsPayload, InsightsStatus, QueryInsights, ViewInsights, ViewInsightsOptions, } from './insights';
|
|
33
36
|
export { createGradientColorScale } from './gradientColorScale';
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ export { useLightdash } from './useLightdash';
|
|
|
10
10
|
// Provider
|
|
11
11
|
export { LightdashProvider, useLightdashClient } from './LightdashProvider';
|
|
12
12
|
// Transports
|
|
13
|
-
export { createApiTransport, LightdashApiError, } from './apiTransport';
|
|
13
|
+
export { createApiTransport, LightdashApiError, SpaceChartsUnsupportedError, } from './apiTransport';
|
|
14
14
|
export { createPostMessageTransport } from './postMessageTransport';
|
|
15
15
|
export { resolveColumnUrls } from './columnUrls';
|
|
16
16
|
// Feature manifest (also exported via the "./features" subpath for hosts)
|
|
@@ -32,6 +32,8 @@ export { useUrlState } from './urlState';
|
|
|
32
32
|
export { AppStateError, useAppState, useSharedAppState, matchesDefaultShape, } from './appState';
|
|
33
33
|
// Free-form AI prompts over loaded results (data apps)
|
|
34
34
|
export { useAiPrompt } from './aiPrompt';
|
|
35
|
+
// List the saved charts the viewer can see in a space (data apps)
|
|
36
|
+
export { useSpaceCharts } from './spaceCharts';
|
|
35
37
|
// AI analysis pushed by the host (data apps)
|
|
36
38
|
export { useInsights } from './insights';
|
|
37
39
|
export { createGradientColorScale } from './gradientColorScale';
|
package/dist/insights.d.ts
CHANGED
|
@@ -69,6 +69,8 @@ export type InsightsRequestMessage = {
|
|
|
69
69
|
/** Author guidance the host forwards to the analysis model, set via
|
|
70
70
|
* `useInsights({ instructions })`. One string per app. */
|
|
71
71
|
instructions?: string;
|
|
72
|
+
/** Set via `useInsights({ analyseOnLoad })`; absent = not declared. */
|
|
73
|
+
analyseOnLoad?: boolean;
|
|
72
74
|
};
|
|
73
75
|
export type InsightAction = {
|
|
74
76
|
action: 'analyse';
|
|
@@ -98,6 +100,8 @@ export declare const EMPTY_INSIGHTS: InsightsPayload;
|
|
|
98
100
|
export declare const parseInsightsPayload: (value: unknown) => InsightsPayload | null;
|
|
99
101
|
/** Module-internal (exported for tests); `useInsights({ instructions })` wraps it. */
|
|
100
102
|
export declare const setInsightInstructions: (value: string | undefined) => void;
|
|
103
|
+
/** Module-internal (exported for tests); `useInsights({ analyseOnLoad })` wraps it. */
|
|
104
|
+
export declare const setInsightAnalyseOnLoad: (value: boolean | undefined) => void;
|
|
101
105
|
/**
|
|
102
106
|
* Listen for the host's analysis and ask for the current one now. Called by
|
|
103
107
|
* `createClient()` on the postMessage transport. One listener per bundle.
|
|
@@ -119,6 +123,9 @@ export type ViewInsightsOptions = {
|
|
|
119
123
|
* line or two of domain vocabulary. One string per app (the last value
|
|
120
124
|
* set wins); the host caps its length. */
|
|
121
125
|
instructions?: string;
|
|
126
|
+
/** Analyse the view as soon as its queries settle, without a click.
|
|
127
|
+
* A stored analysis of the same rows is reused. One value per app. */
|
|
128
|
+
analyseOnLoad?: boolean;
|
|
122
129
|
};
|
|
123
130
|
export type ViewInsights = InsightsPayload & {
|
|
124
131
|
/** Ask the host to analyse the current view (or re-analyse when stale). */
|
|
@@ -155,6 +162,7 @@ export declare function useHostAiAvailable(): boolean;
|
|
|
155
162
|
*
|
|
156
163
|
* const view = useInsights(); // executive summary + every anomaly
|
|
157
164
|
* const view = useInsights({ instructions: 'Use British English.' });
|
|
165
|
+
* const view = useInsights({ analyseOnLoad: true });
|
|
158
166
|
* const orders = useLightdash(ordersQuery);
|
|
159
167
|
* const insights = useInsights(orders); // anomalies on this chart's rows
|
|
160
168
|
*
|
package/dist/insights.js
CHANGED
|
@@ -128,12 +128,17 @@ let activeCleanup = null;
|
|
|
128
128
|
let inUse = false;
|
|
129
129
|
// App-wide author guidance for the analysis model; last writer wins.
|
|
130
130
|
let authorInstructions = null;
|
|
131
|
+
// App-wide analyse-on-load choice; null = not declared, last writer wins.
|
|
132
|
+
let authorAnalyseOnLoad = null;
|
|
131
133
|
const requestMessage = () => ({
|
|
132
134
|
type: INSIGHTS_REQUEST_MESSAGE,
|
|
133
135
|
...(inUse ? { inUse: true } : {}),
|
|
134
136
|
...(authorInstructions !== null
|
|
135
137
|
? { instructions: authorInstructions }
|
|
136
138
|
: {}),
|
|
139
|
+
...(authorAnalyseOnLoad !== null
|
|
140
|
+
? { analyseOnLoad: authorAnalyseOnLoad }
|
|
141
|
+
: {}),
|
|
137
142
|
});
|
|
138
143
|
const markInUse = () => {
|
|
139
144
|
if (inUse)
|
|
@@ -149,6 +154,14 @@ export const setInsightInstructions = (value) => {
|
|
|
149
154
|
authorInstructions = next;
|
|
150
155
|
hostWindow?.postMessage(requestMessage(), '*');
|
|
151
156
|
};
|
|
157
|
+
/** Module-internal (exported for tests); `useInsights({ analyseOnLoad })` wraps it. */
|
|
158
|
+
export const setInsightAnalyseOnLoad = (value) => {
|
|
159
|
+
const next = value ?? null;
|
|
160
|
+
if (next === authorAnalyseOnLoad)
|
|
161
|
+
return;
|
|
162
|
+
authorAnalyseOnLoad = next;
|
|
163
|
+
hostWindow?.postMessage(requestMessage(), '*');
|
|
164
|
+
};
|
|
152
165
|
const createStore = () => {
|
|
153
166
|
let payload = EMPTY_INSIGHTS;
|
|
154
167
|
const listeners = new Set();
|
|
@@ -304,6 +317,22 @@ export function useInsights(sourceOrOptions) {
|
|
|
304
317
|
declaredInstructionsRef.current = true;
|
|
305
318
|
setInsightInstructions(instructions);
|
|
306
319
|
}, [instructions]);
|
|
320
|
+
const analyseOnLoad = source === undefined
|
|
321
|
+
? sourceOrOptions
|
|
322
|
+
?.analyseOnLoad
|
|
323
|
+
: undefined;
|
|
324
|
+
const declaredAnalyseOnLoadRef = useRef(false);
|
|
325
|
+
useEffect(() => {
|
|
326
|
+
if (analyseOnLoad === undefined) {
|
|
327
|
+
if (!declaredAnalyseOnLoadRef.current)
|
|
328
|
+
return;
|
|
329
|
+
declaredAnalyseOnLoadRef.current = false;
|
|
330
|
+
setInsightAnalyseOnLoad(undefined);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
declaredAnalyseOnLoadRef.current = true;
|
|
334
|
+
setInsightAnalyseOnLoad(analyseOnLoad);
|
|
335
|
+
}, [analyseOnLoad]);
|
|
307
336
|
const payload = useInsightsPayload();
|
|
308
337
|
const investigate = useCallback((anomalyId) => postInsightAction({ action: 'investigate', anomalyId }), []);
|
|
309
338
|
const canContinue = payload.canContinue;
|
|
@@ -360,6 +389,7 @@ export function resetInsightsState() {
|
|
|
360
389
|
hostWindow = null;
|
|
361
390
|
inUse = false;
|
|
362
391
|
authorInstructions = null;
|
|
392
|
+
authorAnalyseOnLoad = null;
|
|
363
393
|
mounted.clear();
|
|
364
394
|
flushScheduled = false;
|
|
365
395
|
}
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
* response back. All query logic (field qualification, polling, result
|
|
9
9
|
* mapping) stays in the SDK's apiTransport.
|
|
10
10
|
*/
|
|
11
|
-
import { createApiTransport, createHostExploreBaseTableResolver, } from './apiTransport';
|
|
11
|
+
import { createApiTransport, createHostExploreBaseTableResolver, createHostSpaceChartsResolver, SpaceChartsUnsupportedError, } from './apiTransport';
|
|
12
12
|
import { AppStateError } from './appState';
|
|
13
13
|
import { mountDeliveryRender } from './deliveryRender';
|
|
14
|
-
import { mountHostContext } from './hostContext';
|
|
14
|
+
import { mountHostContext, waitForHostContext } from './hostContext';
|
|
15
15
|
import { announceSdkManifest } from './manifest';
|
|
16
16
|
import { mountRuntimeErrorObserver } from './runtimeErrors';
|
|
17
17
|
const DEFAULT_TIMEOUT_MS = 120000;
|
|
@@ -280,6 +280,17 @@ export function createPostMessageTransport(config) {
|
|
|
280
280
|
});
|
|
281
281
|
const httpTransport = createApiTransport({ apiKey: '', baseUrl: '', projectUuid: config.projectUuid }, adapter, {
|
|
282
282
|
resolveExploreBaseTable: createHostExploreBaseTableResolver(adapter),
|
|
283
|
+
// Negotiated on the ready handshake: only hosts that announce
|
|
284
|
+
// `spaceCharts: true` answer the virtual route. On an older host
|
|
285
|
+
// the typed rejection makes `useSpaceCharts` report
|
|
286
|
+
// `available: false` instead of surfacing a blocked-route error.
|
|
287
|
+
listSpaceCharts: async (space) => {
|
|
288
|
+
const host = await waitForHostContext(READY_TIMEOUT_MS);
|
|
289
|
+
if (!host.spaceCharts) {
|
|
290
|
+
throw new SpaceChartsUnsupportedError();
|
|
291
|
+
}
|
|
292
|
+
return createHostSpaceChartsResolver(adapter)(space);
|
|
293
|
+
},
|
|
283
294
|
});
|
|
284
295
|
const externalFetch = createPostMessageExternalFetch({
|
|
285
296
|
targetWindow: config.targetWindow,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useSpaceCharts('<space slug or uuid>')` — the saved charts the viewer can
|
|
3
|
+
* see in a space, resolved at runtime with the viewer's own credentials. The
|
|
4
|
+
* list reflects existing space permissions: a space the viewer can't access
|
|
5
|
+
* answers exactly like one that doesn't exist (`space: null`, no charts).
|
|
6
|
+
*
|
|
7
|
+
* Feed an entry's `uuid` into `savedChart(...)` to render it.
|
|
8
|
+
*/
|
|
9
|
+
import { type SpaceChartSummary } from './types';
|
|
10
|
+
export type SpaceCharts = {
|
|
11
|
+
/** False on a transport or host without the capability. On an older
|
|
12
|
+
* host this starts true and settles to false after the first lookup. */
|
|
13
|
+
available: boolean;
|
|
14
|
+
/** Null while loading, on error, or when the space isn't accessible. */
|
|
15
|
+
space: {
|
|
16
|
+
uuid: string;
|
|
17
|
+
name: string;
|
|
18
|
+
slug: string;
|
|
19
|
+
} | null;
|
|
20
|
+
charts: SpaceChartSummary[];
|
|
21
|
+
loading: boolean;
|
|
22
|
+
error: Error | null;
|
|
23
|
+
/** Re-fetch the list, e.g. after the viewer saved a chart elsewhere. */
|
|
24
|
+
refresh: () => void;
|
|
25
|
+
};
|
|
26
|
+
export declare function useSpaceCharts(space: string): SpaceCharts;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useSpaceCharts('<space slug or uuid>')` — the saved charts the viewer can
|
|
3
|
+
* see in a space, resolved at runtime with the viewer's own credentials. The
|
|
4
|
+
* list reflects existing space permissions: a space the viewer can't access
|
|
5
|
+
* answers exactly like one that doesn't exist (`space: null`, no charts).
|
|
6
|
+
*
|
|
7
|
+
* Feed an entry's `uuid` into `savedChart(...)` to render it.
|
|
8
|
+
*/
|
|
9
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
10
|
+
import { useTransport } from './LightdashProvider';
|
|
11
|
+
const EMPTY_RESULT = { space: null, charts: [] };
|
|
12
|
+
// Instanceof is unreliable across the bundle boundary; match by name.
|
|
13
|
+
const isUnsupportedError = (e) => e instanceof Error &&
|
|
14
|
+
(e.name === 'SpaceChartsUnsupportedError' ||
|
|
15
|
+
// Older mediators reject the unknown route instead of negotiating:
|
|
16
|
+
// the bridge with `Blocked: POST /__sdk/space-charts`, the CLI
|
|
17
|
+
// preview proxy with "... is not a data-app SDK route ...".
|
|
18
|
+
/\/__sdk\/space-charts/.test(e.message));
|
|
19
|
+
export function useSpaceCharts(space) {
|
|
20
|
+
const transport = useTransport();
|
|
21
|
+
const listSpaceCharts = transport.listSpaceCharts;
|
|
22
|
+
// Hosts that predate the capability reject the first lookup with a
|
|
23
|
+
// typed/blocked error; that settles availability to false for good.
|
|
24
|
+
const [hostUnsupported, setHostUnsupported] = useState(false);
|
|
25
|
+
const available = listSpaceCharts !== undefined && !hostUnsupported;
|
|
26
|
+
const [result, setResult] = useState(EMPTY_RESULT);
|
|
27
|
+
const [loading, setLoading] = useState(available);
|
|
28
|
+
const [error, setError] = useState(null);
|
|
29
|
+
const [fetchCount, setFetchCount] = useState(0);
|
|
30
|
+
// Drops responses from superseded fetches (space change, refresh, unmount).
|
|
31
|
+
const fetchSeq = useRef(0);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!listSpaceCharts || hostUnsupported || space.trim().length === 0) {
|
|
34
|
+
setResult(EMPTY_RESULT);
|
|
35
|
+
setLoading(false);
|
|
36
|
+
setError(space.trim().length === 0 && available
|
|
37
|
+
? new Error('useSpaceCharts: pass a space slug or uuid')
|
|
38
|
+
: null);
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
fetchSeq.current += 1;
|
|
42
|
+
const seq = fetchSeq.current;
|
|
43
|
+
setLoading(true);
|
|
44
|
+
setError(null);
|
|
45
|
+
listSpaceCharts(space.trim())
|
|
46
|
+
.then((value) => {
|
|
47
|
+
if (fetchSeq.current !== seq)
|
|
48
|
+
return;
|
|
49
|
+
setResult(value);
|
|
50
|
+
setLoading(false);
|
|
51
|
+
})
|
|
52
|
+
.catch((e) => {
|
|
53
|
+
if (fetchSeq.current !== seq)
|
|
54
|
+
return;
|
|
55
|
+
setResult(EMPTY_RESULT);
|
|
56
|
+
if (isUnsupportedError(e)) {
|
|
57
|
+
// Not an error state: the host simply doesn't have the
|
|
58
|
+
// feature. `available` flips to false and stays there.
|
|
59
|
+
setHostUnsupported(true);
|
|
60
|
+
setError(null);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
setError(e instanceof Error ? e : new Error(String(e)));
|
|
64
|
+
}
|
|
65
|
+
setLoading(false);
|
|
66
|
+
});
|
|
67
|
+
return () => {
|
|
68
|
+
// Invalidate in-flight responses for the unmount/re-run.
|
|
69
|
+
if (fetchSeq.current === seq)
|
|
70
|
+
fetchSeq.current += 1;
|
|
71
|
+
};
|
|
72
|
+
}, [listSpaceCharts, hostUnsupported, space, fetchCount, available]);
|
|
73
|
+
const refresh = useCallback(() => setFetchCount((n) => n + 1), []);
|
|
74
|
+
return {
|
|
75
|
+
available,
|
|
76
|
+
space: result.space,
|
|
77
|
+
charts: result.charts,
|
|
78
|
+
loading,
|
|
79
|
+
error,
|
|
80
|
+
refresh,
|
|
81
|
+
};
|
|
82
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -299,6 +299,36 @@ export type ExploreBaseTableRequest = {
|
|
|
299
299
|
export type ExploreBaseTableResult = {
|
|
300
300
|
baseTable: string | null;
|
|
301
301
|
};
|
|
302
|
+
/**
|
|
303
|
+
* Host-answered virtual route returning the saved charts in a space.
|
|
304
|
+
* Duplicated from `@lightdash/common` (`APP_SDK_SPACE_CHARTS_PATH`) — this
|
|
305
|
+
* package must not depend on common. The bridge and the CLI preview proxy
|
|
306
|
+
* answer it with the viewer's own credentials; the backend has no such route.
|
|
307
|
+
*/
|
|
308
|
+
export declare const SPACE_CHARTS_PATH = "/__sdk/space-charts";
|
|
309
|
+
export type SpaceChartsRequest = {
|
|
310
|
+
space: string;
|
|
311
|
+
};
|
|
312
|
+
/** One saved chart in a space, as listed by `useSpaceCharts`. */
|
|
313
|
+
export type SpaceChartSummary = {
|
|
314
|
+
uuid: string;
|
|
315
|
+
name: string;
|
|
316
|
+
description: string | null;
|
|
317
|
+
/** e.g. `line`, `vertical_bar`, `table` — null on older charts. */
|
|
318
|
+
chartKind: string | null;
|
|
319
|
+
slug: string | null;
|
|
320
|
+
/** ISO timestamp of the chart's last save. */
|
|
321
|
+
updatedAt: string | null;
|
|
322
|
+
};
|
|
323
|
+
export type SpaceChartsResult = {
|
|
324
|
+
/** Null when the space doesn't exist or the viewer can't access it. */
|
|
325
|
+
space: {
|
|
326
|
+
uuid: string;
|
|
327
|
+
name: string;
|
|
328
|
+
slug: string;
|
|
329
|
+
} | null;
|
|
330
|
+
charts: SpaceChartSummary[];
|
|
331
|
+
};
|
|
302
332
|
export type LightdashClientConfig = {
|
|
303
333
|
/** Lightdash instance URL */
|
|
304
334
|
baseUrl: string;
|
|
@@ -474,4 +504,10 @@ export type Transport = {
|
|
|
474
504
|
* `useAiPrompt().available` is false when absent.
|
|
475
505
|
*/
|
|
476
506
|
aiPrompt?: (request: AiPromptRequest) => Promise<AiPromptResult>;
|
|
507
|
+
/**
|
|
508
|
+
* List the saved charts the viewer can see in a space (by slug or uuid).
|
|
509
|
+
* Optional so custom transports predating the capability stay valid —
|
|
510
|
+
* `useSpaceCharts().available` is false when absent.
|
|
511
|
+
*/
|
|
512
|
+
listSpaceCharts?: (space: string) => Promise<SpaceChartsResult>;
|
|
477
513
|
};
|
package/dist/types.js
CHANGED
|
@@ -41,3 +41,10 @@ export const VIZ_POINT_MENU_PATH = '/__sdk/viz/point-menu';
|
|
|
41
41
|
* the backend has no such route.
|
|
42
42
|
*/
|
|
43
43
|
export const EXPLORE_BASE_TABLE_PATH = '/__sdk/explore-base-table';
|
|
44
|
+
/**
|
|
45
|
+
* Host-answered virtual route returning the saved charts in a space.
|
|
46
|
+
* Duplicated from `@lightdash/common` (`APP_SDK_SPACE_CHARTS_PATH`) — this
|
|
47
|
+
* package must not depend on common. The bridge and the CLI preview proxy
|
|
48
|
+
* answer it with the viewer's own credentials; the backend has no such route.
|
|
49
|
+
*/
|
|
50
|
+
export const SPACE_CHARTS_PATH = '/__sdk/space-charts';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightdash/query-sdk",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.98.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "SDK for building custom data apps against the Lightdash semantic layer",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@lightdash/common": "3.
|
|
29
|
+
"@lightdash/common": "3.98.0",
|
|
30
30
|
"@types/node": "22.13.1",
|
|
31
31
|
"@types/react": "19.3.0",
|
|
32
32
|
"@types/react-dom": "19.3.0",
|