@burdenoff/microfe-bigconsole 2026.1001.1 → 2026.1001.2
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/bigconsole/components/preview/AiPreviewContent.d.ts +3 -11
- package/dist/bigconsole/components/preview/AiPreviewContent.d.ts.map +1 -1
- package/dist/bigconsole/components/preview/AiPreviewContent.js +78 -83
- package/dist/bigconsole/components/preview/AiPreviewContent.js.map +1 -1
- package/dist/bigconsole/components/preview/parserPreview.d.ts +19 -0
- package/dist/bigconsole/components/preview/parserPreview.d.ts.map +1 -0
- package/dist/bigconsole/components/preview/parserPreview.js +31 -0
- package/dist/bigconsole/components/preview/parserPreview.js.map +1 -0
- package/package.json +1 -1
|
@@ -7,17 +7,9 @@ export declare function DashboardContent({ dashboardId, live }: {
|
|
|
7
7
|
live?: boolean;
|
|
8
8
|
}): import("react").JSX.Element;
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* A parser writes its transformed rows into a data sink named by its own
|
|
14
|
-
* `outputKey` (`ParserCoreFields`), and `getDataSinkData` accepts `{ key }`. So
|
|
15
|
-
* the output is fully readable without executing anything: read the parser,
|
|
16
|
-
* follow `outputKey`, read the sink.
|
|
17
|
-
*
|
|
18
|
-
* ★ Apollo is queried directly rather than via `useParserOperations.fetchParser`,
|
|
19
|
-
* because that helper calls `addParser()` and would publish into the global
|
|
20
|
-
* parser store — trading defect 2's leak for the same leak one store over.
|
|
10
|
+
* Read-only evaluation of the saved expression against up to 50 current source
|
|
11
|
+
* rows. outputKey is a widget binding, not a physical DataSink. The governed
|
|
12
|
+
* query applies masking and returns sampling metadata; it persists no output.
|
|
21
13
|
*/
|
|
22
14
|
export declare function ParserContent({ parserId, live }: {
|
|
23
15
|
parserId?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AiPreviewContent.d.ts","sourceRoot":"","sources":["../../../../src/bigconsole/components/preview/AiPreviewContent.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AiPreviewContent.d.ts","sourceRoot":"","sources":["../../../../src/bigconsole/components/preview/AiPreviewContent.tsx"],"names":[],"mappings":"AA6JA,wBAAgB,eAAe,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,+BAmB3E;AAED,wBAAgB,gBAAgB,CAAC,EAAE,WAAW,EAAE,IAAY,EAAE,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,+BAiBvG;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,+BAwBrF;AA6CD,wBAAgB,cAAc,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,+BA0B5F"}
|
|
@@ -1,44 +1,45 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useAssistantRunStore as
|
|
3
|
-
import
|
|
4
|
-
import { normalizeWidget as
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
1
|
+
import { ListWidgetsByDashboardDocument as e } from "../../../generated/wspace-operations.js";
|
|
2
|
+
import { useAssistantRunStore as t } from "../../assistant/assistantRunStore.js";
|
|
3
|
+
import n from "../datasink/DataSinkTableViewer.js";
|
|
4
|
+
import { normalizeWidget as r } from "../../hooks/useWidgetOperations.js";
|
|
5
|
+
import i from "../../hooks/useDataSinkOperations.js";
|
|
6
|
+
import a from "../widgets/WidgetWrapper.js";
|
|
7
|
+
import o from "../DrilldownDashboardRenderer.js";
|
|
8
|
+
import { loadParserPreview as s, parserSampleCaption as c } from "./parserPreview.js";
|
|
9
|
+
import { useEffect as l, useRef as u, useState as d } from "react";
|
|
10
|
+
import { Spinner as f } from "@burdenoff/fe-libs/ui";
|
|
11
|
+
import { useApolloClient as p } from "@apollo/client/react";
|
|
12
|
+
import { jsx as m, jsxs as h } from "react/jsx-runtime";
|
|
12
13
|
//#region src/bigconsole/components/preview/AiPreviewContent.tsx
|
|
13
|
-
var
|
|
14
|
-
function
|
|
15
|
-
return /* @__PURE__ */
|
|
16
|
-
className:
|
|
14
|
+
var g = 4e3, _ = "flex min-h-[6rem] min-w-0 items-center justify-center gap-2 rounded-card bg-bg-sunken px-3 py-6 text-center text-sm text-text-muted";
|
|
15
|
+
function v({ label: e, testId: t }) {
|
|
16
|
+
return /* @__PURE__ */ h("div", {
|
|
17
|
+
className: _,
|
|
17
18
|
"data-testid": t,
|
|
18
|
-
children: [/* @__PURE__ */
|
|
19
|
+
children: [/* @__PURE__ */ m(f, { className: "size-4 shrink-0" }), e]
|
|
19
20
|
});
|
|
20
21
|
}
|
|
21
|
-
function
|
|
22
|
-
return /* @__PURE__ */
|
|
23
|
-
className:
|
|
22
|
+
function y({ text: e, testId: t }) {
|
|
23
|
+
return /* @__PURE__ */ m("p", {
|
|
24
|
+
className: _,
|
|
24
25
|
"data-testid": t,
|
|
25
26
|
children: e
|
|
26
27
|
});
|
|
27
28
|
}
|
|
28
|
-
function
|
|
29
|
-
return /* @__PURE__ */
|
|
30
|
-
className: `${
|
|
29
|
+
function b({ text: e, testId: t }) {
|
|
30
|
+
return /* @__PURE__ */ m("p", {
|
|
31
|
+
className: `${_} text-status-error-text`,
|
|
31
32
|
"data-testid": t,
|
|
32
33
|
children: e
|
|
33
34
|
});
|
|
34
35
|
}
|
|
35
|
-
function
|
|
36
|
-
let i =
|
|
37
|
-
return _.current = e,
|
|
38
|
-
if (!
|
|
39
|
-
let e = !1,
|
|
40
|
-
if (!
|
|
41
|
-
|
|
36
|
+
function x(e, n, r) {
|
|
37
|
+
let i = t((e) => e.contentRevision), [a, o] = d(null), [s, c] = d(!!n), [f, p] = d(null), [m, h] = d(n), _ = u(e);
|
|
38
|
+
return _.current = e, n !== m && (h(n), o(null), p(null), c(!!n)), l(() => {
|
|
39
|
+
if (!n) return;
|
|
40
|
+
let e = !1, t = !1, i = async () => {
|
|
41
|
+
if (!t) {
|
|
42
|
+
t = !0;
|
|
42
43
|
try {
|
|
43
44
|
let t = await _.current();
|
|
44
45
|
if (e) return;
|
|
@@ -47,19 +48,19 @@ function b(e, t, r) {
|
|
|
47
48
|
if (e) return;
|
|
48
49
|
p(t instanceof Error ? t : /* @__PURE__ */ Error("Failed to load preview content"));
|
|
49
50
|
} finally {
|
|
50
|
-
|
|
51
|
+
t = !1, e || c(!1);
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
};
|
|
54
55
|
i();
|
|
55
56
|
let a = window.setInterval(() => {
|
|
56
57
|
r && (typeof document < "u" && document.visibilityState !== "visible" || i());
|
|
57
|
-
},
|
|
58
|
+
}, g);
|
|
58
59
|
return () => {
|
|
59
60
|
e = !0, window.clearInterval(a);
|
|
60
61
|
};
|
|
61
62
|
}, [
|
|
62
|
-
|
|
63
|
+
n,
|
|
63
64
|
r,
|
|
64
65
|
i
|
|
65
66
|
]), {
|
|
@@ -68,121 +69,115 @@ function b(e, t, r) {
|
|
|
68
69
|
error: f
|
|
69
70
|
};
|
|
70
71
|
}
|
|
71
|
-
function
|
|
72
|
-
let { getDataSinkData:
|
|
73
|
-
let t = await
|
|
72
|
+
function S({ id: e, live: t }) {
|
|
73
|
+
let { getDataSinkData: r } = i({ skipFetch: !0 }), { data: a, loading: o, error: s } = x(async () => {
|
|
74
|
+
let t = await r({ id: e }, { limit: 50 });
|
|
74
75
|
return t ? t.raw : null;
|
|
75
76
|
}, e, t);
|
|
76
|
-
return e ? s ? /* @__PURE__ */
|
|
77
|
+
return e ? s ? /* @__PURE__ */ m(b, {
|
|
77
78
|
text: "Could not load the data sink's rows.",
|
|
78
79
|
testId: "ai-preview-datasink-error"
|
|
79
|
-
}) : o ? /* @__PURE__ */
|
|
80
|
+
}) : o ? /* @__PURE__ */ m(v, {
|
|
80
81
|
label: "Loading the data sink’s data…",
|
|
81
82
|
testId: "ai-preview-datasink-loading"
|
|
82
|
-
}) : /* @__PURE__ */
|
|
83
|
+
}) : /* @__PURE__ */ m("div", {
|
|
83
84
|
"data-testid": "ai-preview-content-datasink",
|
|
84
85
|
className: "min-w-0",
|
|
85
|
-
children: /* @__PURE__ */
|
|
86
|
-
data:
|
|
86
|
+
children: /* @__PURE__ */ m(n, {
|
|
87
|
+
data: a,
|
|
87
88
|
maxHeight: "300px"
|
|
88
89
|
})
|
|
89
|
-
}) : /* @__PURE__ */
|
|
90
|
+
}) : /* @__PURE__ */ m(y, {
|
|
90
91
|
text: "Waiting for the data sink to be created…",
|
|
91
92
|
testId: "ai-preview-datasink-waiting"
|
|
92
93
|
});
|
|
93
94
|
}
|
|
94
|
-
function
|
|
95
|
-
let r =
|
|
96
|
-
return e ? /* @__PURE__ */
|
|
95
|
+
function C({ dashboardId: e, live: n = !1 }) {
|
|
96
|
+
let r = t((e) => e.contentRevision);
|
|
97
|
+
return e ? /* @__PURE__ */ m("div", {
|
|
97
98
|
"data-testid": "ai-preview-content-dashboard",
|
|
98
99
|
className: "min-w-0",
|
|
99
|
-
children: /* @__PURE__ */
|
|
100
|
+
children: /* @__PURE__ */ m(o, {
|
|
100
101
|
dashboardId: e,
|
|
101
102
|
params: {},
|
|
102
|
-
live:
|
|
103
|
+
live: n,
|
|
103
104
|
refreshRevision: r
|
|
104
105
|
}, e)
|
|
105
|
-
}) : /* @__PURE__ */
|
|
106
|
+
}) : /* @__PURE__ */ m(y, {
|
|
106
107
|
text: "Waiting for the dashboard to be created…",
|
|
107
108
|
testId: "ai-preview-dashboard-waiting"
|
|
108
109
|
});
|
|
109
110
|
}
|
|
110
|
-
function
|
|
111
|
-
let
|
|
112
|
-
|
|
113
|
-
let n = (await i.query({
|
|
114
|
-
query: e,
|
|
115
|
-
variables: { id: t },
|
|
116
|
-
fetchPolicy: "network-only"
|
|
117
|
-
})).data?.parser?.outputKey;
|
|
118
|
-
if (!n) return null;
|
|
119
|
-
let r = await o({ key: n }, { limit: 50 });
|
|
120
|
-
return r ? r.raw : null;
|
|
121
|
-
}, t, n);
|
|
122
|
-
return t ? l ? /* @__PURE__ */ p(y, {
|
|
111
|
+
function w({ parserId: e, live: t }) {
|
|
112
|
+
let r = p(), { data: i, loading: a, error: o } = x(() => e ? s(r, e) : Promise.resolve(null), e, t), l = c(i);
|
|
113
|
+
return e ? o ? /* @__PURE__ */ m(b, {
|
|
123
114
|
text: "Could not read the parser's output.",
|
|
124
115
|
testId: "ai-preview-parser-error"
|
|
125
|
-
}) :
|
|
116
|
+
}) : a ? /* @__PURE__ */ m(v, {
|
|
126
117
|
label: "Loading the parser’s output…",
|
|
127
118
|
testId: "ai-preview-parser-loading"
|
|
128
|
-
}) : /* @__PURE__ */
|
|
119
|
+
}) : /* @__PURE__ */ h("div", {
|
|
129
120
|
"data-testid": "ai-preview-content-parser",
|
|
130
121
|
className: "min-w-0",
|
|
131
|
-
children: /* @__PURE__ */ p
|
|
132
|
-
data:
|
|
122
|
+
children: [l && /* @__PURE__ */ m("p", {
|
|
123
|
+
"data-testid": "ai-preview-parser-sample",
|
|
124
|
+
className: "mb-2 text-xs text-text-muted",
|
|
125
|
+
children: l
|
|
126
|
+
}), /* @__PURE__ */ m(n, {
|
|
127
|
+
data: i?.output ?? null,
|
|
133
128
|
maxHeight: "280px"
|
|
134
|
-
})
|
|
135
|
-
}) : /* @__PURE__ */
|
|
129
|
+
})]
|
|
130
|
+
}) : /* @__PURE__ */ m(y, {
|
|
136
131
|
text: "Waiting for the parser to be created…",
|
|
137
132
|
testId: "ai-preview-parser-waiting"
|
|
138
133
|
});
|
|
139
134
|
}
|
|
140
|
-
function
|
|
135
|
+
function T(e) {
|
|
141
136
|
let t = e.toUpperCase();
|
|
142
137
|
return t.includes("METRIC") || t.includes("KPI") || t.includes("CARD") || t.includes("STAT") ? "min(160px, 40vh)" : t.includes("TABLE") || t.includes("LIST") ? "min(280px, 50vh)" : "min(320px, 50vh)";
|
|
143
138
|
}
|
|
144
|
-
function
|
|
145
|
-
let
|
|
146
|
-
query:
|
|
147
|
-
variables: { dashboardId:
|
|
139
|
+
function E(t, n) {
|
|
140
|
+
let i = p(), { data: a, loading: o, error: s } = x(async () => t ? ((await i.query({
|
|
141
|
+
query: e,
|
|
142
|
+
variables: { dashboardId: t },
|
|
148
143
|
fetchPolicy: "network-only"
|
|
149
|
-
})).data?.listWidgetsByDashboard || []).map(
|
|
144
|
+
})).data?.listWidgetsByDashboard || []).map(r) : null, t, n);
|
|
150
145
|
return {
|
|
151
146
|
widgets: a ?? [],
|
|
152
147
|
loading: o,
|
|
153
148
|
error: s
|
|
154
149
|
};
|
|
155
150
|
}
|
|
156
|
-
function
|
|
157
|
-
let { widgets: n, loading: r, error: i } =
|
|
158
|
-
return e ? i ? /* @__PURE__ */
|
|
151
|
+
function D({ dashboardId: e, live: t }) {
|
|
152
|
+
let { widgets: n, loading: r, error: i } = E(e, t);
|
|
153
|
+
return e ? i ? /* @__PURE__ */ m(b, {
|
|
159
154
|
text: "Could not load this dashboard's widgets.",
|
|
160
155
|
testId: "ai-preview-widget-error"
|
|
161
|
-
}) : r && n.length === 0 ? /* @__PURE__ */
|
|
156
|
+
}) : r && n.length === 0 ? /* @__PURE__ */ m(v, {
|
|
162
157
|
label: "Loading widgets…",
|
|
163
158
|
testId: "ai-preview-widget-loading"
|
|
164
|
-
}) : n.length === 0 ? /* @__PURE__ */
|
|
159
|
+
}) : n.length === 0 ? /* @__PURE__ */ m(y, {
|
|
165
160
|
text: "No widgets on this dashboard yet.",
|
|
166
161
|
testId: "ai-preview-widget-empty"
|
|
167
|
-
}) : /* @__PURE__ */
|
|
162
|
+
}) : /* @__PURE__ */ m("div", {
|
|
168
163
|
"data-testid": "ai-preview-content-widget",
|
|
169
164
|
className: "flex min-w-0 flex-col gap-list-gap",
|
|
170
|
-
children: n.map((e) => /* @__PURE__ */
|
|
165
|
+
children: n.map((e) => /* @__PURE__ */ m("div", {
|
|
171
166
|
className: "min-w-0 overflow-hidden rounded-card border border-[var(--color-border-subtle)] bg-bg-canvas",
|
|
172
|
-
style: { height:
|
|
173
|
-
children: /* @__PURE__ */
|
|
167
|
+
style: { height: T(e.type) },
|
|
168
|
+
children: /* @__PURE__ */ m(a, {
|
|
174
169
|
widget: e,
|
|
175
170
|
isLoading: !1,
|
|
176
171
|
error: null,
|
|
177
172
|
filterValues: {}
|
|
178
173
|
})
|
|
179
174
|
}, e.id))
|
|
180
|
-
}) : /* @__PURE__ */
|
|
175
|
+
}) : /* @__PURE__ */ m(y, {
|
|
181
176
|
text: "Waiting for the dashboard — widgets live on it.",
|
|
182
177
|
testId: "ai-preview-widget-waiting"
|
|
183
178
|
});
|
|
184
179
|
}
|
|
185
180
|
//#endregion
|
|
186
|
-
export {
|
|
181
|
+
export { C as DashboardContent, S as DataSinkContent, w as ParserContent, D as WidgetsContent };
|
|
187
182
|
|
|
188
183
|
//# sourceMappingURL=AiPreviewContent.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AiPreviewContent.js","names":[],"sources":["../../../../src/bigconsole/components/preview/AiPreviewContent.tsx"],"sourcesContent":["/**\n * Live content renderers for the AI preview tabs.\n *\n * Each tab shows the REAL content of the entity the assistant just built —\n * the data sink's rows, the live dashboard, the parser's transformed output,\n * the rendered widget charts — not a list with an Open button. They reuse the\n * same components the real pages use (DataSinkTableViewer, DrilldownDashboard-\n * Renderer, WidgetWrapper) so what you see in the preview is what you get.\n *\n * These require Apollo + the BigConsole providers, which is why they live only\n * in the full AiPreviewPanel (mounted inside BigConsoleRoot), never in the\n * store-only app-shell mini panel.\n *\n * ── BC0 (BOFF-7333; BOFF-7270, BOFF-7271) ────────────────────────────────────\n * A PREVIEW MUST OBSERVE, NEVER MUTATE, AND NEVER PUBLISH.\n *\n * Three defects made this panel change the system it was previewing:\n *\n * 1. The Parser tab polled `executeParser` — a MUTATION — every 4s for 60s,\n * re-running the transform server-side up to ~15 times per turn. It now\n * reads the parser (query), follows its `outputKey`, and reads that sink\n * read-only. Zero mutations.\n * 2. The Widgets tab went through `useWidgetOperations`, which publishes into\n * the GLOBAL widget store — so opening the preview overwrote the widgets an\n * already-open dashboard was rendering. It now holds widgets in local state.\n * 3. `useLiveContent` never reset on key change, so the PREVIOUS entity's rows\n * stayed on screen as though current, and an un-caught rejection left the\n * tab spinning forever with no error surface.\n *\n * ★ Deliberately NOT changed: the other `executeParser` callers\n * (`ParserViewPage`, `CustomWidgetBuilderSheet`, `PropertiesPanel`). Those are\n * user-initiated \"run this parser\" actions where executing IS the intent. Only\n * the passive preview had no business mutating.\n */\nimport { useEffect, useRef, useState } from 'react';\nimport { useApolloClient } from '@apollo/client/react';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\n\nimport { DataSinkTableViewer } from '../datasink/DataSinkTableViewer';\nimport { DrilldownDashboardRenderer } from '../DrilldownDashboardRenderer';\nimport { WidgetWrapper } from '../widgets/WidgetWrapper';\nimport { useDataSinkOperations } from '../../hooks/useDataSinkOperations';\nimport { normalizeWidget } from '../../hooks/useWidgetOperations';\nimport { GetParserDocument, ListWidgetsByDashboardDocument } from '../../../generated/wspace-operations';\nimport type { Parser, Widget } from '../../types';\nimport { useAssistantRunStore } from '../../assistant/assistantRunStore';\n\n/** While the build is live, re-pull content on this cadence so it fills in. */\nconst CONTENT_POLL_MS = 4000;\n\n/**\n * Loading and waiting states share one recessed, height-reserved block so the\n * panel does not jump every time a tab fills in — previously each tab had its\n * own bare paragraph and its own height.\n */\nconst PLACEHOLDER_BOX =\n 'flex min-h-[6rem] min-w-0 items-center justify-center gap-2 rounded-card bg-bg-sunken px-3 py-6 text-center text-sm text-text-muted';\n\nfunction CenteredSpinner({ label, testId }: { label: string; testId?: string }) {\n return (\n <div className={PLACEHOLDER_BOX} data-testid={testId}>\n <Spinner className=\"size-4 shrink-0\" />\n {label}\n </div>\n );\n}\n\nfunction Waiting({ text, testId }: { text: string; testId?: string }) {\n return (\n <p className={PLACEHOLDER_BOX} data-testid={testId}>\n {text}\n </p>\n );\n}\n\n/**\n * ★ BC0: the tab had no error surface at all. An un-caught rejection inside the\n * poller left `loading` true forever, so a failed read was indistinguishable\n * from a slow one — and it surfaced as an unhandled promise rejection.\n */\nfunction ErrorBox({ text, testId }: { text: string; testId?: string }) {\n return (\n <p className={`${PLACEHOLDER_BOX} text-status-error-text`} data-testid={testId}>\n {text}\n </p>\n );\n}\n\n/**\n * Fetch-on-mount and confirmed-result revisions, then re-fetch every CONTENT_POLL_MS while `live`. Returns { data, loading, error }. `fetcher` must be\n * stable-ish; we hold it in a ref so the effect keys only on `key`/`live`.\n *\n * ★★ BC0: RESETS ON KEY CHANGE. Previously `data` survived a key change, so\n * switching to a different entity rendered the PREVIOUS entity's rows as though\n * they were the new one's — with `loading` already false, nothing said otherwise.\n */\nfunction useLiveContent<T>(fetcher: () => Promise<T | null>, key: string | undefined, live: boolean) {\n const revision = useAssistantRunStore((state) => state.contentRevision);\n const [data, setData] = useState<T | null>(null);\n const [loading, setLoading] = useState(Boolean(key));\n const [error, setError] = useState<Error | null>(null);\n const [renderedKey, setRenderedKey] = useState(key);\n const fetcherRef = useRef(fetcher);\n fetcherRef.current = fetcher;\n\n // ★★ The reset happens DURING RENDER, not in an effect. An effect-based reset\n // commits one frame carrying the PREVIOUS entity's rows before clearing them —\n // which is the very defect being fixed here. Adjusting state while rendering is\n // React's supported pattern for \"a prop changed, discard the derived state\": it\n // re-runs this component immediately, before anything reaches the DOM, so the\n // stale rows are never painted. A key going undefined clears the render too.\n if (key !== renderedKey) {\n setRenderedKey(key);\n setData(null);\n setError(null);\n setLoading(Boolean(key));\n }\n\n useEffect(() => {\n if (!key) return;\n\n let cancelled = false;\n let fetching = false;\n const pull = async () => {\n if (fetching) return;\n fetching = true;\n try {\n const result = await fetcherRef.current();\n if (cancelled) return;\n setData(result ?? null);\n setError(null);\n } catch (err) {\n if (cancelled) return;\n // A later successful poll clears this, so a transient blip does not\n // strand the tab on an error.\n setError(err instanceof Error ? err : new Error('Failed to load preview content'));\n } finally {\n fetching = false;\n if (!cancelled) setLoading(false);\n }\n };\n void pull();\n const intervalId = window.setInterval(() => {\n if (!live) return;\n if (typeof document !== 'undefined' && document.visibilityState !== 'visible') return;\n void pull();\n }, CONTENT_POLL_MS);\n return () => {\n cancelled = true;\n window.clearInterval(intervalId);\n };\n }, [key, live, revision]);\n\n return { data, loading, error };\n}\n\nexport function DataSinkContent({ id, live }: { id?: string; live: boolean }) {\n const { getDataSinkData } = useDataSinkOperations({ skipFetch: true });\n const { data, loading, error } = useLiveContent(\n async () => {\n const res = await getDataSinkData({ id }, { limit: 50 });\n return res ? res.raw : null;\n },\n id,\n live\n );\n\n if (!id) return <Waiting text=\"Waiting for the data sink to be created…\" testId=\"ai-preview-datasink-waiting\" />;\n if (error) return <ErrorBox text=\"Could not load the data sink's rows.\" testId=\"ai-preview-datasink-error\" />;\n if (loading) return <CenteredSpinner label=\"Loading the data sink’s data…\" testId=\"ai-preview-datasink-loading\" />;\n return (\n <div data-testid=\"ai-preview-content-datasink\" className=\"min-w-0\">\n <DataSinkTableViewer data={data} maxHeight=\"300px\" />\n </div>\n );\n}\n\nexport function DashboardContent({ dashboardId, live = false }: { dashboardId?: string; live?: boolean }) {\n const revision = useAssistantRunStore((state) => state.contentRevision);\n if (!dashboardId)\n return <Waiting text=\"Waiting for the dashboard to be created…\" testId=\"ai-preview-dashboard-waiting\" />;\n // No inner `max-h + overflow-y-auto`: the panel already owns a vertical\n // scroll, and nesting a second one put two scrollbars in ~440px of space.\n return (\n <div data-testid=\"ai-preview-content-dashboard\" className=\"min-w-0\">\n <DrilldownDashboardRenderer\n key={dashboardId}\n dashboardId={dashboardId}\n params={{}}\n live={live}\n refreshRevision={revision}\n />\n </div>\n );\n}\n\n/**\n * ★★★ BC0 — READ-ONLY. This tab used to call `executeParser`, a mutation, on\n * every poll: ~15 server-side re-executions per turn purely to look at output.\n *\n * A parser writes its transformed rows into a data sink named by its own\n * `outputKey` (`ParserCoreFields`), and `getDataSinkData` accepts `{ key }`. So\n * the output is fully readable without executing anything: read the parser,\n * follow `outputKey`, read the sink.\n *\n * ★ Apollo is queried directly rather than via `useParserOperations.fetchParser`,\n * because that helper calls `addParser()` and would publish into the global\n * parser store — trading defect 2's leak for the same leak one store over.\n */\nexport function ParserContent({ parserId, live }: { parserId?: string; live: boolean }) {\n const client = useApolloClient();\n const { getDataSinkData } = useDataSinkOperations({ skipFetch: true });\n const { data, loading, error } = useLiveContent(\n async () => {\n if (!parserId) return null;\n const res = await client.query<{ parser?: Parser | null }>({\n query: GetParserDocument,\n variables: { id: parserId },\n fetchPolicy: 'network-only',\n });\n const outputKey = res.data?.parser?.outputKey;\n if (!outputKey) return null;\n const sink = await getDataSinkData({ key: outputKey }, { limit: 50 });\n return sink ? sink.raw : null;\n },\n parserId,\n live\n );\n\n if (!parserId) return <Waiting text=\"Waiting for the parser to be created…\" testId=\"ai-preview-parser-waiting\" />;\n if (error) return <ErrorBox text=\"Could not read the parser's output.\" testId=\"ai-preview-parser-error\" />;\n // \"Running the parser…\" would now be a lie — nothing is executed.\n if (loading) return <CenteredSpinner label=\"Loading the parser’s output…\" testId=\"ai-preview-parser-loading\" />;\n // The \"Parser output (transformed rows)\" caption used to sit here; it now\n // lives on the section header, so the tab does not label itself twice.\n return (\n <div data-testid=\"ai-preview-content-parser\" className=\"min-w-0\">\n <DataSinkTableViewer data={data} maxHeight=\"280px\" />\n </div>\n );\n}\n\n/**\n * Recharts needs a definite height or it renders 0px tall, but a hard 320px box\n * eats a short viewport whole. `min(px, vh)` keeps the definite height Recharts\n * needs while letting the box shrink on small screens.\n */\nfunction widgetBoxHeight(type: string): string {\n const t = type.toUpperCase();\n if (t.includes('METRIC') || t.includes('KPI') || t.includes('CARD') || t.includes('STAT')) return 'min(160px, 40vh)';\n if (t.includes('TABLE') || t.includes('LIST')) return 'min(280px, 50vh)';\n return 'min(320px, 50vh)';\n}\n\n/**\n * ★★★ BC0 — COMPONENT-LOCAL widgets.\n *\n * `useWidgetOperations` publishes every fetch into the GLOBAL widget store, so\n * merely opening this preview replaced the widget set an already-open dashboard\n * was rendering. The query and the normalisation are reused — `normalizeWidget`\n * is exported from that hook precisely so the two paths cannot drift — but the\n * result stays here, in this component's state.\n *\n * It also inherits `useLiveContent`'s result refresh and visibility check,\n * replacing the bespoke interval this tab used to run.\n */\nfunction usePreviewWidgets(dashboardId: string | undefined, live: boolean) {\n const client = useApolloClient();\n const { data, loading, error } = useLiveContent<Widget[]>(\n async () => {\n if (!dashboardId) return null;\n const result = await client.query<Record<string, unknown>>({\n query: ListWidgetsByDashboardDocument,\n variables: { dashboardId },\n fetchPolicy: 'network-only',\n });\n const list = (result.data?.listWidgetsByDashboard as Record<string, unknown>[]) || [];\n return list.map(normalizeWidget);\n },\n dashboardId,\n live\n );\n return { widgets: data ?? [], loading, error };\n}\n\nexport function WidgetsContent({ dashboardId, live }: { dashboardId?: string; live: boolean }) {\n const { widgets, loading, error } = usePreviewWidgets(dashboardId, live);\n\n if (!dashboardId)\n return <Waiting text=\"Waiting for the dashboard — widgets live on it.\" testId=\"ai-preview-widget-waiting\" />;\n if (error) return <ErrorBox text=\"Could not load this dashboard's widgets.\" testId=\"ai-preview-widget-error\" />;\n if (loading && widgets.length === 0)\n return <CenteredSpinner label=\"Loading widgets…\" testId=\"ai-preview-widget-loading\" />;\n if (widgets.length === 0)\n return <Waiting text=\"No widgets on this dashboard yet.\" testId=\"ai-preview-widget-empty\" />;\n\n return (\n <div data-testid=\"ai-preview-content-widget\" className=\"flex min-w-0 flex-col gap-list-gap\">\n {widgets.map((widget) => (\n <div\n key={widget.id}\n // `border-border-subtle` is registered as a frozen light hex in this\n // shell, so the box kept a light border in dark mode.\n className=\"min-w-0 overflow-hidden rounded-card border border-[var(--color-border-subtle)] bg-bg-canvas\"\n style={{ height: widgetBoxHeight(widget.type) }}\n >\n <WidgetWrapper widget={widget} isLoading={false} error={null} filterValues={{}} />\n </div>\n ))}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AiPreviewContent.js","names":[],"sources":["../../../../src/bigconsole/components/preview/AiPreviewContent.tsx"],"sourcesContent":["/**\n * Live content renderers for the AI preview tabs.\n *\n * Each tab shows the REAL content of the entity the assistant just built —\n * the data sink's rows, the live dashboard, the parser's transformed output,\n * the rendered widget charts — not a list with an Open button. They reuse the\n * same components the real pages use (DataSinkTableViewer, DrilldownDashboard-\n * Renderer, WidgetWrapper) so what you see in the preview is what you get.\n *\n * These require Apollo + the BigConsole providers, which is why they live only\n * in the full AiPreviewPanel (mounted inside BigConsoleRoot), never in the\n * store-only app-shell mini panel.\n *\n * ── BC0 (BOFF-7333; BOFF-7270, BOFF-7271) ────────────────────────────────────\n * A PREVIEW MUST OBSERVE, NEVER MUTATE, AND NEVER PUBLISH.\n *\n * Three defects made this panel change the system it was previewing:\n *\n * 1. The Parser tab polled `executeParser` — a MUTATION — every 4s for 60s,\n * re-running the transform server-side up to ~15 times per turn. It now\n * reads the parser and evaluates a governed source sample through queries.\n * It does not persist output or call mutations.\n * 2. The Widgets tab went through `useWidgetOperations`, which publishes into\n * the GLOBAL widget store — so opening the preview overwrote the widgets an\n * already-open dashboard was rendering. It now holds widgets in local state.\n * 3. `useLiveContent` never reset on key change, so the PREVIOUS entity's rows\n * stayed on screen as though current, and an un-caught rejection left the\n * tab spinning forever with no error surface.\n *\n * ★ Deliberately NOT changed: the other `executeParser` callers\n * (`ParserViewPage`, `CustomWidgetBuilderSheet`, `PropertiesPanel`). Those are\n * user-initiated \"run this parser\" actions where executing IS the intent. Only\n * the passive preview had no business mutating.\n */\nimport { useEffect, useRef, useState } from 'react';\nimport { useApolloClient } from '@apollo/client/react';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\n\nimport { DataSinkTableViewer } from '../datasink/DataSinkTableViewer';\nimport { DrilldownDashboardRenderer } from '../DrilldownDashboardRenderer';\nimport { WidgetWrapper } from '../widgets/WidgetWrapper';\nimport { useDataSinkOperations } from '../../hooks/useDataSinkOperations';\nimport { normalizeWidget } from '../../hooks/useWidgetOperations';\nimport { ListWidgetsByDashboardDocument } from '../../../generated/wspace-operations';\nimport type { Widget } from '../../types';\nimport { useAssistantRunStore } from '../../assistant/assistantRunStore';\nimport { loadParserPreview, parserSampleCaption } from './parserPreview';\n\n/** While the build is live, re-pull content on this cadence so it fills in. */\nconst CONTENT_POLL_MS = 4000;\n\n/**\n * Loading and waiting states share one recessed, height-reserved block so the\n * panel does not jump every time a tab fills in — previously each tab had its\n * own bare paragraph and its own height.\n */\nconst PLACEHOLDER_BOX =\n 'flex min-h-[6rem] min-w-0 items-center justify-center gap-2 rounded-card bg-bg-sunken px-3 py-6 text-center text-sm text-text-muted';\n\nfunction CenteredSpinner({ label, testId }: { label: string; testId?: string }) {\n return (\n <div className={PLACEHOLDER_BOX} data-testid={testId}>\n <Spinner className=\"size-4 shrink-0\" />\n {label}\n </div>\n );\n}\n\nfunction Waiting({ text, testId }: { text: string; testId?: string }) {\n return (\n <p className={PLACEHOLDER_BOX} data-testid={testId}>\n {text}\n </p>\n );\n}\n\n/**\n * ★ BC0: the tab had no error surface at all. An un-caught rejection inside the\n * poller left `loading` true forever, so a failed read was indistinguishable\n * from a slow one — and it surfaced as an unhandled promise rejection.\n */\nfunction ErrorBox({ text, testId }: { text: string; testId?: string }) {\n return (\n <p className={`${PLACEHOLDER_BOX} text-status-error-text`} data-testid={testId}>\n {text}\n </p>\n );\n}\n\n/**\n * Fetch-on-mount and confirmed-result revisions, then re-fetch every CONTENT_POLL_MS while `live`. Returns { data, loading, error }. `fetcher` must be\n * stable-ish; we hold it in a ref so the effect keys only on `key`/`live`.\n *\n * ★★ BC0: RESETS ON KEY CHANGE. Previously `data` survived a key change, so\n * switching to a different entity rendered the PREVIOUS entity's rows as though\n * they were the new one's — with `loading` already false, nothing said otherwise.\n */\nfunction useLiveContent<T>(fetcher: () => Promise<T | null>, key: string | undefined, live: boolean) {\n const revision = useAssistantRunStore((state) => state.contentRevision);\n const [data, setData] = useState<T | null>(null);\n const [loading, setLoading] = useState(Boolean(key));\n const [error, setError] = useState<Error | null>(null);\n const [renderedKey, setRenderedKey] = useState(key);\n const fetcherRef = useRef(fetcher);\n fetcherRef.current = fetcher;\n\n // ★★ The reset happens DURING RENDER, not in an effect. An effect-based reset\n // commits one frame carrying the PREVIOUS entity's rows before clearing them —\n // which is the very defect being fixed here. Adjusting state while rendering is\n // React's supported pattern for \"a prop changed, discard the derived state\": it\n // re-runs this component immediately, before anything reaches the DOM, so the\n // stale rows are never painted. A key going undefined clears the render too.\n if (key !== renderedKey) {\n setRenderedKey(key);\n setData(null);\n setError(null);\n setLoading(Boolean(key));\n }\n\n useEffect(() => {\n if (!key) return;\n\n let cancelled = false;\n let fetching = false;\n const pull = async () => {\n if (fetching) return;\n fetching = true;\n try {\n const result = await fetcherRef.current();\n if (cancelled) return;\n setData(result ?? null);\n setError(null);\n } catch (err) {\n if (cancelled) return;\n // A later successful poll clears this, so a transient blip does not\n // strand the tab on an error.\n setError(err instanceof Error ? err : new Error('Failed to load preview content'));\n } finally {\n fetching = false;\n if (!cancelled) setLoading(false);\n }\n };\n void pull();\n const intervalId = window.setInterval(() => {\n if (!live) return;\n if (typeof document !== 'undefined' && document.visibilityState !== 'visible') return;\n void pull();\n }, CONTENT_POLL_MS);\n return () => {\n cancelled = true;\n window.clearInterval(intervalId);\n };\n }, [key, live, revision]);\n\n return { data, loading, error };\n}\n\nexport function DataSinkContent({ id, live }: { id?: string; live: boolean }) {\n const { getDataSinkData } = useDataSinkOperations({ skipFetch: true });\n const { data, loading, error } = useLiveContent(\n async () => {\n const res = await getDataSinkData({ id }, { limit: 50 });\n return res ? res.raw : null;\n },\n id,\n live\n );\n\n if (!id) return <Waiting text=\"Waiting for the data sink to be created…\" testId=\"ai-preview-datasink-waiting\" />;\n if (error) return <ErrorBox text=\"Could not load the data sink's rows.\" testId=\"ai-preview-datasink-error\" />;\n if (loading) return <CenteredSpinner label=\"Loading the data sink’s data…\" testId=\"ai-preview-datasink-loading\" />;\n return (\n <div data-testid=\"ai-preview-content-datasink\" className=\"min-w-0\">\n <DataSinkTableViewer data={data} maxHeight=\"300px\" />\n </div>\n );\n}\n\nexport function DashboardContent({ dashboardId, live = false }: { dashboardId?: string; live?: boolean }) {\n const revision = useAssistantRunStore((state) => state.contentRevision);\n if (!dashboardId)\n return <Waiting text=\"Waiting for the dashboard to be created…\" testId=\"ai-preview-dashboard-waiting\" />;\n // No inner `max-h + overflow-y-auto`: the panel already owns a vertical\n // scroll, and nesting a second one put two scrollbars in ~440px of space.\n return (\n <div data-testid=\"ai-preview-content-dashboard\" className=\"min-w-0\">\n <DrilldownDashboardRenderer\n key={dashboardId}\n dashboardId={dashboardId}\n params={{}}\n live={live}\n refreshRevision={revision}\n />\n </div>\n );\n}\n\n/**\n * Read-only evaluation of the saved expression against up to 50 current source\n * rows. outputKey is a widget binding, not a physical DataSink. The governed\n * query applies masking and returns sampling metadata; it persists no output.\n */\nexport function ParserContent({ parserId, live }: { parserId?: string; live: boolean }) {\n const client = useApolloClient();\n const { data, loading, error } = useLiveContent(\n () => (parserId ? loadParserPreview(client, parserId) : Promise.resolve(null)),\n parserId,\n live\n );\n const sampleCaption = parserSampleCaption(data);\n\n if (!parserId) return <Waiting text=\"Waiting for the parser to be created…\" testId=\"ai-preview-parser-waiting\" />;\n if (error) return <ErrorBox text=\"Could not read the parser's output.\" testId=\"ai-preview-parser-error\" />;\n if (loading) return <CenteredSpinner label=\"Loading the parser’s output…\" testId=\"ai-preview-parser-loading\" />;\n // The \"Parser output (transformed rows)\" caption used to sit here; it now\n // lives on the section header, so the tab does not label itself twice.\n return (\n <div data-testid=\"ai-preview-content-parser\" className=\"min-w-0\">\n {sampleCaption && (\n <p data-testid=\"ai-preview-parser-sample\" className=\"mb-2 text-xs text-text-muted\">\n {sampleCaption}\n </p>\n )}\n <DataSinkTableViewer data={data?.output ?? null} maxHeight=\"280px\" />\n </div>\n );\n}\n\n/**\n * Recharts needs a definite height or it renders 0px tall, but a hard 320px box\n * eats a short viewport whole. `min(px, vh)` keeps the definite height Recharts\n * needs while letting the box shrink on small screens.\n */\nfunction widgetBoxHeight(type: string): string {\n const t = type.toUpperCase();\n if (t.includes('METRIC') || t.includes('KPI') || t.includes('CARD') || t.includes('STAT')) return 'min(160px, 40vh)';\n if (t.includes('TABLE') || t.includes('LIST')) return 'min(280px, 50vh)';\n return 'min(320px, 50vh)';\n}\n\n/**\n * ★★★ BC0 — COMPONENT-LOCAL widgets.\n *\n * `useWidgetOperations` publishes every fetch into the GLOBAL widget store, so\n * merely opening this preview replaced the widget set an already-open dashboard\n * was rendering. The query and the normalisation are reused — `normalizeWidget`\n * is exported from that hook precisely so the two paths cannot drift — but the\n * result stays here, in this component's state.\n *\n * It also inherits `useLiveContent`'s result refresh and visibility check,\n * replacing the bespoke interval this tab used to run.\n */\nfunction usePreviewWidgets(dashboardId: string | undefined, live: boolean) {\n const client = useApolloClient();\n const { data, loading, error } = useLiveContent<Widget[]>(\n async () => {\n if (!dashboardId) return null;\n const result = await client.query<Record<string, unknown>>({\n query: ListWidgetsByDashboardDocument,\n variables: { dashboardId },\n fetchPolicy: 'network-only',\n });\n const list = (result.data?.listWidgetsByDashboard as Record<string, unknown>[]) || [];\n return list.map(normalizeWidget);\n },\n dashboardId,\n live\n );\n return { widgets: data ?? [], loading, error };\n}\n\nexport function WidgetsContent({ dashboardId, live }: { dashboardId?: string; live: boolean }) {\n const { widgets, loading, error } = usePreviewWidgets(dashboardId, live);\n\n if (!dashboardId)\n return <Waiting text=\"Waiting for the dashboard — widgets live on it.\" testId=\"ai-preview-widget-waiting\" />;\n if (error) return <ErrorBox text=\"Could not load this dashboard's widgets.\" testId=\"ai-preview-widget-error\" />;\n if (loading && widgets.length === 0)\n return <CenteredSpinner label=\"Loading widgets…\" testId=\"ai-preview-widget-loading\" />;\n if (widgets.length === 0)\n return <Waiting text=\"No widgets on this dashboard yet.\" testId=\"ai-preview-widget-empty\" />;\n\n return (\n <div data-testid=\"ai-preview-content-widget\" className=\"flex min-w-0 flex-col gap-list-gap\">\n {widgets.map((widget) => (\n <div\n key={widget.id}\n // `border-border-subtle` is registered as a frozen light hex in this\n // shell, so the box kept a light border in dark mode.\n className=\"min-w-0 overflow-hidden rounded-card border border-[var(--color-border-subtle)] bg-bg-canvas\"\n style={{ height: widgetBoxHeight(widget.type) }}\n >\n <WidgetWrapper widget={widget} isLoading={false} error={null} filterValues={{}} />\n </div>\n ))}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ApolloClient } from '@apollo/client';
|
|
2
|
+
|
|
3
|
+
/** Evaluate a bounded, governed source sample without persisting parser output. */
|
|
4
|
+
export declare function loadParserPreview(client: Pick<ApolloClient, 'query'>, parserId: string): Promise<{
|
|
5
|
+
__typename?: "ParserTestResult";
|
|
6
|
+
success: boolean;
|
|
7
|
+
output?: Record<string, unknown> | null;
|
|
8
|
+
error?: string | null;
|
|
9
|
+
executionTimeMs?: number | null;
|
|
10
|
+
sampled?: boolean | null;
|
|
11
|
+
sampledRows?: number | null;
|
|
12
|
+
totalRows?: number | null;
|
|
13
|
+
} | null>;
|
|
14
|
+
export declare function parserSampleCaption(preview: {
|
|
15
|
+
sampled?: boolean | null;
|
|
16
|
+
sampledRows?: number | null;
|
|
17
|
+
totalRows?: number | null;
|
|
18
|
+
} | null): string | null;
|
|
19
|
+
//# sourceMappingURL=parserPreview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parserPreview.d.ts","sourceRoot":"","sources":["../../../../src/bigconsole/components/preview/parserPreview.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAQnD,mFAAmF;AACnF,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,EAAE,QAAQ,EAAE,MAAM;;;;;;;;;UAuB5F;AAED,wBAAgB,mBAAmB,CACjC,OAAO,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,GACnG,MAAM,GAAG,IAAI,CAMf"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { GetParserDocument as e, TestParserExpressionDocument as t } from "../../../generated/wspace-operations.js";
|
|
2
|
+
//#region src/bigconsole/components/preview/parserPreview.ts
|
|
3
|
+
async function n(n, r) {
|
|
4
|
+
let i = (await n.query({
|
|
5
|
+
query: e,
|
|
6
|
+
variables: { id: r },
|
|
7
|
+
fetchPolicy: "network-only"
|
|
8
|
+
})).data?.parser;
|
|
9
|
+
if (!i) return null;
|
|
10
|
+
let a = (await n.query({
|
|
11
|
+
query: t,
|
|
12
|
+
variables: { input: {
|
|
13
|
+
type: i.type,
|
|
14
|
+
expression: i.expression,
|
|
15
|
+
dataSinkKey: i.inputSinkKey,
|
|
16
|
+
sampleLimit: 50
|
|
17
|
+
} },
|
|
18
|
+
fetchPolicy: "network-only"
|
|
19
|
+
})).data?.testParserExpression;
|
|
20
|
+
if (!a?.success) throw Error(a?.error || "Could not evaluate the parser preview");
|
|
21
|
+
return a;
|
|
22
|
+
}
|
|
23
|
+
function r(e) {
|
|
24
|
+
if (!e?.sampled) return null;
|
|
25
|
+
let t = e.sampledRows ?? 50;
|
|
26
|
+
return e.totalRows !== null && e.totalRows !== void 0 ? `Preview evaluated ${t} of ${e.totalRows} source rows. Full execution may differ.` : `Preview evaluated ${t} source rows. Full execution may differ.`;
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { n as loadParserPreview, r as parserSampleCaption };
|
|
30
|
+
|
|
31
|
+
//# sourceMappingURL=parserPreview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parserPreview.js","names":[],"sources":["../../../../src/bigconsole/components/preview/parserPreview.ts"],"sourcesContent":["import type { ApolloClient } from '@apollo/client';\nimport {\n GetParserDocument,\n TestParserExpressionDocument,\n type GetParserQuery,\n type TestParserExpressionQuery,\n} from '../../../generated/wspace-operations';\n\n/** Evaluate a bounded, governed source sample without persisting parser output. */\nexport async function loadParserPreview(client: Pick<ApolloClient, 'query'>, parserId: string) {\n const parserResult = await client.query<GetParserQuery>({\n query: GetParserDocument,\n variables: { id: parserId },\n fetchPolicy: 'network-only',\n });\n const parser = parserResult.data?.parser;\n if (!parser) return null;\n const result = await client.query<TestParserExpressionQuery>({\n query: TestParserExpressionDocument,\n variables: {\n input: {\n type: parser.type,\n expression: parser.expression,\n dataSinkKey: parser.inputSinkKey,\n sampleLimit: 50,\n },\n },\n fetchPolicy: 'network-only',\n });\n const preview = result.data?.testParserExpression;\n if (!preview?.success) throw new Error(preview?.error || 'Could not evaluate the parser preview');\n return preview;\n}\n\nexport function parserSampleCaption(\n preview: { sampled?: boolean | null; sampledRows?: number | null; totalRows?: number | null } | null\n): string | null {\n if (!preview?.sampled) return null;\n const rows = preview.sampledRows ?? 50;\n return preview.totalRows !== null && preview.totalRows !== undefined\n ? `Preview evaluated ${rows} of ${preview.totalRows} source rows. Full execution may differ.`\n : `Preview evaluated ${rows} source rows. Full execution may differ.`;\n}\n"],"mappings":";;AASA,eAAsB,EAAkB,GAAqC,GAAkB;CAM7F,IAAM,KALe,MAAM,EAAO,MAAsB;EACtD,OAAO;EACP,WAAW,EAAE,IAAI,GAAU;EAC3B,aAAa;EACd,CAAC,EAC0B,MAAM;AAClC,KAAI,CAAC,EAAQ,QAAO;CAapB,IAAM,KAZS,MAAM,EAAO,MAAiC;EAC3D,OAAO;EACP,WAAW,EACT,OAAO;GACL,MAAM,EAAO;GACb,YAAY,EAAO;GACnB,aAAa,EAAO;GACpB,aAAa;GACd,EACF;EACD,aAAa;EACd,CAAC,EACqB,MAAM;AAC7B,KAAI,CAAC,GAAS,QAAS,OAAU,MAAM,GAAS,SAAS,wCAAwC;AACjG,QAAO;;AAGT,SAAgB,EACd,GACe;AACf,KAAI,CAAC,GAAS,QAAS,QAAO;CAC9B,IAAM,IAAO,EAAQ,eAAe;AACpC,QAAO,EAAQ,cAAc,QAAQ,EAAQ,cAAc,KAAA,IACvD,qBAAqB,EAAK,MAAM,EAAQ,UAAU,4CAClD,qBAAqB,EAAK"}
|