@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.
@@ -1 +1 @@
1
- {"version":3,"file":"previewProxy.d.ts","sourceRoot":"","sources":["../../../src/handlers/apps/previewProxy.ts"],"names":[],"mappings":"AAmBA,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,CAyW7B,CAAC"}
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 { ReactNode } from 'react';
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 = view.status === 'analysing';
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.96.1",
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.96.1",
26
- "@lightdash/warehouses": "3.96.1",
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",