@lightdash/cli 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/.tsbuildinfo.build +1 -1
- package/dist/handlers/apps/previewProxy.d.ts.map +1 -1
- package/dist/handlers/apps/previewProxy.js +33 -0
- package/dist/vendor/template/.claude/skills/sdk-features/SKILL.md +15 -0
- package/dist/vendor/template/references/ai-analysis.md +11 -1
- package/dist/vendor/template/references/chart-references.md +13 -0
- package/dist/vendor/template/skill.md +3 -1
- package/dist/vendor/template/src/components/insights/InsightsSummary.tsx +23 -4
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"previewProxy.d.ts","sourceRoot":"","sources":["../../../src/handlers/apps/previewProxy.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"previewProxy.d.ts","sourceRoot":"","sources":["../../../src/handlers/apps/previewProxy.ts"],"names":[],"mappings":"AAyBA,eAAO,MAAM,0BAA0B,8BAA8B,CAAC;AA6CtE,MAAM,MAAM,kBAAkB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B,CAAC;AAEF,4EAA4E;AAC5E,MAAM,MAAM,qBAAqB,GAAG;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAG5B,0BAA0B,EAAE,MAAM,EAAE,CAAC;CACxC,CAAC;AAyCF;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,SAAU;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,oBAAoB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7C,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IAGd,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,CAAC;CAC5D,KAAG,OAAO,CAAC,kBAAkB,CA4Z7B,CAAC"}
|
|
@@ -221,6 +221,31 @@ const startPreviewProxy = (args) => {
|
|
|
221
221
|
res.writeHead(200, { 'content-type': 'application/json' });
|
|
222
222
|
res.end(JSON.stringify({ status: 'ok', results }));
|
|
223
223
|
};
|
|
224
|
+
// Host-answered virtual route: resolves a slug-or-uuid space reference
|
|
225
|
+
// to its trimmed chart list with the developer's credentials. Unknown
|
|
226
|
+
// space, no access, and upstream failure all answer uniformly empty —
|
|
227
|
+
// same face as the deployed app's bridge.
|
|
228
|
+
const answerSpaceCharts = async (req, res) => {
|
|
229
|
+
const request = (0, common_1.parseAppSdkSpaceChartsRequest)(parseJson(await readBody(req)));
|
|
230
|
+
if (request === null) {
|
|
231
|
+
sendJsonError(res, 400, 'Preview proxy: invalid space-charts request');
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
let results = (0, common_1.emptyAppSdkSpaceCharts)();
|
|
235
|
+
try {
|
|
236
|
+
const spaces = await getUpstreamJson(`/api/v1/projects/${encodeURIComponent(args.projectUuid)}/spaces`);
|
|
237
|
+
const spaceUuid = (0, common_1.resolveAppSdkSpaceUuid)(spaces, request.space);
|
|
238
|
+
if (spaceUuid !== null) {
|
|
239
|
+
const space = await getUpstreamJson(`/api/v1/projects/${encodeURIComponent(args.projectUuid)}/spaces/${encodeURIComponent(spaceUuid)}`);
|
|
240
|
+
results = (0, common_1.extractAppSdkSpaceCharts)(space) ?? results;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
// Keep the uniform empty answer.
|
|
245
|
+
}
|
|
246
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
247
|
+
res.end(JSON.stringify({ status: 'ok', results }));
|
|
248
|
+
};
|
|
224
249
|
const handleRequest = async (req, res) => {
|
|
225
250
|
const method = req.method ?? 'GET';
|
|
226
251
|
const nonceHeader = req.headers[exports.PREVIEW_PROXY_NONCE_HEADER];
|
|
@@ -238,6 +263,14 @@ const startPreviewProxy = (args) => {
|
|
|
238
263
|
sendJsonError(res, 400, 'Preview proxy: malformed request URL');
|
|
239
264
|
return;
|
|
240
265
|
}
|
|
266
|
+
if (pathname === common_1.APP_SDK_SPACE_CHARTS_PATH) {
|
|
267
|
+
answerSpaceCharts(req, res).catch(() => {
|
|
268
|
+
if (!res.headersSent) {
|
|
269
|
+
sendJsonError(res, 400, 'Preview proxy: invalid space-charts request');
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
241
274
|
if (pathname === common_1.APP_SDK_EXPLORE_BASE_TABLE_PATH) {
|
|
242
275
|
answerExploreBaseTable(req, res).catch(() => {
|
|
243
276
|
if (!res.headersSent) {
|
|
@@ -34,6 +34,7 @@ Users describe features by what they see in the Lightdash editor. Translate:
|
|
|
34
34
|
| Lightdash-owned "View underlying data" dialog (viz only) | `viz-host-underlying-data` | app code opt-in |
|
|
35
35
|
| light/dark mode, "matches my Lightdash theme" | `follow-host-theme` | CSS tokens — see below |
|
|
36
36
|
| "executive summary", "what changed", anomaly callouts, "AI insight", "Analyse this view" | `ai-insights` | app code opt-in |
|
|
37
|
+
| chart gallery, "my charts", "show the charts in this space", list that updates as charts are saved | `space-charts` | app code opt-in |
|
|
37
38
|
| "Adapts to small tiles", responsive, works on a phone, readable in a narrow or short tile (viz only) | `viz-responsive-layout` | layout — see below |
|
|
38
39
|
|
|
39
40
|
## Automatic (zero wiring — active on any current-SDK bundle)
|
|
@@ -197,12 +198,26 @@ order, `sortBy` describes result ordering, `totalColumnCount` exposes truncation
|
|
|
197
198
|
`instructions` prop on `InsightsSummary` (or `useInsights({ instructions })`
|
|
198
199
|
in a custom block). One string per app, max 2000 characters. See "Author
|
|
199
200
|
instructions" in `/app/references/ai-analysis.md`.
|
|
201
|
+
- `analysis-on-load`: only when the user asks for the analysis to run on its
|
|
202
|
+
own ("analyse as soon as it loads"): set the `analyseOnLoad` prop on
|
|
203
|
+
`InsightsSummary` (or `useInsights({ analyseOnLoad: true })` in a custom
|
|
204
|
+
block). See "Analyse on load" in `/app/references/ai-analysis.md`.
|
|
200
205
|
- `ai-prompt`: `useAiPrompt()` returns `{ available, ask, loading, text, error }`;
|
|
201
206
|
`ask({ prompt, sources: [{ result, label }], focus?: { row } })` sends an
|
|
202
207
|
author-written question over loaded `useLightdash` results and resolves to
|
|
203
208
|
plain text. `result` is the object `useLightdash` returns (keep a reference
|
|
204
209
|
to it even when destructuring), never a rebuilt `{ data, columns }`. Hide the control when `available` is false; trigger from a user
|
|
205
210
|
action. Full contract in `/app/references/ai-analysis.md`.
|
|
211
|
+
- `space-charts`: `useSpaceCharts('<space slug or uuid>')` returns
|
|
212
|
+
`{ available, space, charts, loading, error, refresh }` — the saved charts
|
|
213
|
+
the viewer can see in that space, resolved at runtime with the viewer's own
|
|
214
|
+
permissions (an inaccessible space answers like a missing one: `space: null`,
|
|
215
|
+
no charts). Each entry carries `{ uuid, name, description, chartKind, slug,
|
|
216
|
+
updatedAt }`; pass `chart.uuid` to `savedChart(...)` to render one. Use it
|
|
217
|
+
for galleries that must pick up newly saved charts without a rebuild; keep
|
|
218
|
+
explicit `savedChart('<uuid>')` references for charts the app lays out by
|
|
219
|
+
hand. Render a friendly empty state for `charts.length === 0` and hide the
|
|
220
|
+
section when `available` is false (older host).
|
|
206
221
|
- `delivery-render`: `useDeliveryRender()` is `true` during scheduled-delivery
|
|
207
222
|
and delivery-preview capture renders. Gate tab/slide DATA mounting on it so
|
|
208
223
|
every tab's queries execute during capture while only the active tab stays
|
|
@@ -49,7 +49,7 @@ const view = useInsights();
|
|
|
49
49
|
// view.analyse() ask Lightdash to (re)run the analysis
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
Render `<InsightsSummary />` near the top of the page. It handles every status (idle, analysing, ready, error, stale) and renders nothing when unavailable. Props: `title`, `idleText`, `instructions`, `className`. Write your own block only when the user asks for a layout the component cannot give, and then keep every status branch.
|
|
52
|
+
Render `<InsightsSummary />` near the top of the page. It handles every status (idle, analysing, ready, error, stale) and renders nothing when unavailable. Props: `title`, `idleText`, `instructions`, `analyseOnLoad`, `className`. Write your own block only when the user asks for a layout the component cannot give, and then keep every status branch.
|
|
53
53
|
|
|
54
54
|
### Author instructions for the analysis
|
|
55
55
|
|
|
@@ -63,6 +63,16 @@ Or equivalently `useInsights({ instructions })` in a custom block. One string pe
|
|
|
63
63
|
|
|
64
64
|
The organization may also set default instructions in its settings; those apply first and the app's instructions are added after them, so keep app instructions to app-specific guidance (domain vocabulary) and leave org-wide style (language, currency) to the org default when the user says it should apply everywhere.
|
|
65
65
|
|
|
66
|
+
### Analyse on load
|
|
67
|
+
|
|
68
|
+
By default the analysis runs when the viewer clicks **Analyse**. When the user wants it to run on its own ("analyse as soon as it loads", "show the summary when the page opens"), set `analyseOnLoad`:
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
<InsightsSummary analyseOnLoad />
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Or equivalently `useInsights({ analyseOnLoad: true })` in a custom block. One value per app (the last value set wins). The host runs the analysis once the page's queries settle and again when the view changes; a stored analysis of the same rows is reused instead of re-run. Do not set it unless the user asked for it.
|
|
75
|
+
|
|
66
76
|
Rules for the block:
|
|
67
77
|
|
|
68
78
|
- Say "AI-generated" in the footer. Never name a model or provider.
|
|
@@ -87,6 +87,19 @@ A linked chart stays in sync with Lightdash and appears in the Queries panel
|
|
|
87
87
|
like any other query. If it can't be run (deleted / no access), the app should
|
|
88
88
|
show its normal error state — don't fabricate data.
|
|
89
89
|
|
|
90
|
+
### Discovering charts at runtime (`useSpaceCharts`)
|
|
91
|
+
|
|
92
|
+
When the user wants a gallery or "my charts" page that picks up charts saved
|
|
93
|
+
to a space **after** the app is built, don't hardcode a UUID list — list the
|
|
94
|
+
space at runtime: `useSpaceCharts('<space slug or uuid>')` returns
|
|
95
|
+
`{ available, space, charts, loading, error, refresh }`, scoped to what the
|
|
96
|
+
viewer can access (an inaccessible space answers `space: null` with no
|
|
97
|
+
charts). Pass an entry's `uuid` to `savedChart(...)` to render it, and treat
|
|
98
|
+
every such chart as LINKED: data-driven and crash-proof per the rules below,
|
|
99
|
+
since its shape is unknown at build time. Render an empty state for zero
|
|
100
|
+
charts and hide the section when `available` is false. The recipe lives in
|
|
101
|
+
the `sdk-features` skill under `space-charts`.
|
|
102
|
+
|
|
90
103
|
### Linked charts must be DATA-DRIVEN and crash-proof
|
|
91
104
|
|
|
92
105
|
A linked chart's query can change in Lightdash *after* the app is generated (the
|
|
@@ -220,6 +220,8 @@ These metrics are **not additive over time**. A balance on Monday plus the balan
|
|
|
220
220
|
|
|
221
221
|
If the prompt lists referenced charts (files under `/tmp/metric-queries/*.json`), read `/app/references/chart-references.md` before writing any query code — it defines the JSON shape, linked-vs-copied chart semantics (`savedChart`), and the field-id mapping rules.
|
|
222
222
|
|
|
223
|
+
If the user asks for a page that lists or renders **whatever charts a Lightdash space contains** (a gallery, "my charts", "charts from the Sales space") — i.e. the set should grow as charts are saved, without rebuilding the app — use `useSpaceCharts('<space slug or uuid>')` and read "Discovering charts at runtime" in `/app/references/chart-references.md`. Never hardcode a UUID list for that ask.
|
|
224
|
+
|
|
223
225
|
## Attached dashboard blueprint
|
|
224
226
|
|
|
225
227
|
If the prompt announces an attached dashboard (blueprint at `/tmp/dashboard/blueprint.json`), read `/app/references/dashboard-blueprint.md` before designing any layout — the blueprint defines the dashboard's tabs, tile grid, and filters, and it is the layout spec to recreate unless the user asks for a different design.
|
|
@@ -779,7 +781,7 @@ For any external HTTP API call, read `/app/references/external-apis.md` and use
|
|
|
779
781
|
|
|
780
782
|
### AI analysis — executive summary and anomalies
|
|
781
783
|
|
|
782
|
-
When the user asks for AI analysis, an executive summary, "what changed and why", anomaly callouts, or any AI-generated insight about the data on the page, read `/app/references/ai-analysis.md` and build its **Default build** with the shipped components in `src/components/insights/` (`InsightsSummary`, `InsightMarker` / `insightCellProps`, `InvestigateMenuItem`, `InvestigationCard`): summary block at the top, markers on every query-bound chart, Investigate in every point menu, the investigation card under the chart. A short ask gets the whole set; only a narrower ask ("just a summary") gets a subset. Never rewrite those components. Lightdash runs the analysis over the queries the app already loaded and pushes the result in; the app renders it and never sends rows or keys. The components render nothing when the host has no analysis to offer. For an author-written question over loaded results (a takeaway under a chart, "explain this row"), use `useAiPrompt` from the same reference. When the user wants the summaries written a certain way (language, currency, domain vocabulary), pass it via the `instructions` prop on `InsightsSummary` — see "Author instructions" in the same reference.
|
|
784
|
+
When the user asks for AI analysis, an executive summary, "what changed and why", anomaly callouts, or any AI-generated insight about the data on the page, read `/app/references/ai-analysis.md` and build its **Default build** with the shipped components in `src/components/insights/` (`InsightsSummary`, `InsightMarker` / `insightCellProps`, `InvestigateMenuItem`, `InvestigationCard`): summary block at the top, markers on every query-bound chart, Investigate in every point menu, the investigation card under the chart. A short ask gets the whole set; only a narrower ask ("just a summary") gets a subset. Never rewrite those components. Lightdash runs the analysis over the queries the app already loaded and pushes the result in; the app renders it and never sends rows or keys. The components render nothing when the host has no analysis to offer. For an author-written question over loaded results (a takeaway under a chart, "explain this row"), use `useAiPrompt` from the same reference. When the user wants the summaries written a certain way (language, currency, domain vocabulary), pass it via the `instructions` prop on `InsightsSummary` — see "Author instructions" in the same reference. When the user wants the analysis to run as soon as the app loads, set the `analyseOnLoad` prop on `InsightsSummary` — see "Analyse on load" in the same reference; never set it unasked.
|
|
783
785
|
|
|
784
786
|
## Visual Design
|
|
785
787
|
|
|
@@ -6,7 +6,7 @@ import { useInsights } from '@lightdash/query-sdk';
|
|
|
6
6
|
import { Button } from '@/components/ui/button';
|
|
7
7
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
8
8
|
import { Loader2, RefreshCw, Sparkles } from 'lucide-react';
|
|
9
|
-
import type
|
|
9
|
+
import { useEffect, useState, type ReactNode } from 'react';
|
|
10
10
|
|
|
11
11
|
type Props = {
|
|
12
12
|
title?: ReactNode;
|
|
@@ -15,6 +15,8 @@ type Props = {
|
|
|
15
15
|
/** Guidance for the analysis model: language/format rules and a line or
|
|
16
16
|
* two of domain vocabulary. One string per app. */
|
|
17
17
|
instructions?: string;
|
|
18
|
+
/** Run the analysis when the view loads, without a click. */
|
|
19
|
+
analyseOnLoad?: boolean;
|
|
18
20
|
className?: string;
|
|
19
21
|
};
|
|
20
22
|
|
|
@@ -22,13 +24,30 @@ export function InsightsSummary({
|
|
|
22
24
|
title = 'Executive summary',
|
|
23
25
|
idleText = 'Click Analyse for an AI-generated read of everything on this page.',
|
|
24
26
|
instructions,
|
|
27
|
+
analyseOnLoad,
|
|
25
28
|
className,
|
|
26
29
|
}: Props) {
|
|
27
30
|
// Always pass the options object so clearing the prop clears the
|
|
28
31
|
// guidance instead of leaving the previous value in force.
|
|
29
|
-
const view = useInsights({ instructions });
|
|
32
|
+
const view = useInsights({ instructions, analyseOnLoad });
|
|
33
|
+
// The host only starts once the app reports analyseOnLoad, so show the
|
|
34
|
+
// run as pending meanwhile; a short cap keeps an older host from
|
|
35
|
+
// leaving it stuck.
|
|
36
|
+
const [pendingAutoRun, setPendingAutoRun] = useState(!!analyseOnLoad);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (!pendingAutoRun) return undefined;
|
|
39
|
+
// `unavailable` is also the SDK's state before the host's first push.
|
|
40
|
+
if (view.status !== 'idle' && view.status !== 'unavailable') {
|
|
41
|
+
setPendingAutoRun(false);
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
const timer = setTimeout(() => setPendingAutoRun(false), 5000);
|
|
45
|
+
return () => clearTimeout(timer);
|
|
46
|
+
}, [pendingAutoRun, view.status]);
|
|
30
47
|
if (view.status === 'unavailable') return null;
|
|
31
|
-
const busy =
|
|
48
|
+
const busy =
|
|
49
|
+
view.status === 'analysing' ||
|
|
50
|
+
(pendingAutoRun && view.status === 'idle');
|
|
32
51
|
|
|
33
52
|
return (
|
|
34
53
|
<Card className={className}>
|
|
@@ -52,7 +71,7 @@ export function InsightsSummary({
|
|
|
52
71
|
</Button>
|
|
53
72
|
</CardHeader>
|
|
54
73
|
<CardContent className="space-y-3">
|
|
55
|
-
{view.status === 'idle' && (
|
|
74
|
+
{view.status === 'idle' && !busy && (
|
|
56
75
|
<p className="text-sm text-muted-foreground">{idleText}</p>
|
|
57
76
|
)}
|
|
58
77
|
{busy && (
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightdash/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.98.0",
|
|
4
4
|
"description": "Lightdash CLI tool",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"@actions/core": "1.11.1",
|
|
23
23
|
"@aws-sdk/credential-providers": "3.995.0",
|
|
24
24
|
"@casl/ability": "6.8.0",
|
|
25
|
-
"@lightdash/common": "3.
|
|
26
|
-
"@lightdash/warehouses": "3.
|
|
25
|
+
"@lightdash/common": "3.98.0",
|
|
26
|
+
"@lightdash/warehouses": "3.98.0",
|
|
27
27
|
"@sidvind/better-ajv-errors": "7.0.0",
|
|
28
28
|
"@types/columnify": "1.5.1",
|
|
29
29
|
"ajv": "8.18.0",
|