@objectstack/cli 9.0.0 → 9.0.1
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/commands/compile.d.ts.map +1 -1
- package/dist/commands/compile.js +40 -1
- package/dist/commands/compile.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +27 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/lint.d.ts.map +1 -1
- package/dist/commands/lint.js +17 -0
- package/dist/commands/lint.js.map +1 -1
- package/dist/commands/package/publish.d.ts.map +1 -1
- package/dist/commands/package/publish.js +5 -2
- package/dist/commands/package/publish.js.map +1 -1
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +11 -75
- package/dist/commands/serve.js.map +1 -1
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +37 -3
- package/dist/commands/validate.js.map +1 -1
- package/dist/utils/validate-widget-bindings.d.ts +74 -0
- package/dist/utils/validate-widget-bindings.d.ts.map +1 -0
- package/dist/utils/validate-widget-bindings.js +304 -0
- package/dist/utils/validate-widget-bindings.js.map +1 -0
- package/dist/utils/validate-widget-bindings.test.d.ts +2 -0
- package/dist/utils/validate-widget-bindings.test.d.ts.map +1 -0
- package/dist/utils/validate-widget-bindings.test.js +285 -0
- package/dist/utils/validate-widget-bindings.test.js.map +1 -0
- package/package.json +43 -43
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
+
/**
|
|
3
|
+
* Build-time dashboard widget binding diagnostics (issues #1719, #1721).
|
|
4
|
+
*
|
|
5
|
+
* Runs at `objectstack validate`/`compile`/`build` AFTER the stack has been
|
|
6
|
+
* schema-parsed, so every widget's `dataset` reference can be linked to its
|
|
7
|
+
* `defineDataset` and each entry in `dimensions`/`values` resolved to a
|
|
8
|
+
* declared dimension/measure. This is the semantic/cross-reference phase —
|
|
9
|
+
* the rules here cannot run during plain Zod parsing of the raw widget
|
|
10
|
+
* literal (the dataset may even live in another package of the stack).
|
|
11
|
+
*
|
|
12
|
+
* Reference-integrity rules (#1721) — severity `error`, the page is broken:
|
|
13
|
+
*
|
|
14
|
+
* - `widget-dataset-unknown` — `dataset` does not resolve to a declared
|
|
15
|
+
* `Dataset`.
|
|
16
|
+
* - `widget-dimension-unknown` — a `dimensions[]` entry is not a dimension
|
|
17
|
+
* name on the bound dataset.
|
|
18
|
+
* - `widget-measure-unknown` — a `values[]` entry is not a measure name on
|
|
19
|
+
* the bound dataset.
|
|
20
|
+
* - `chart-field-unknown` — a `chartConfig` binding names a field the query
|
|
21
|
+
* result will not contain: `xAxis.field` must be one of the widget's
|
|
22
|
+
* dimensions (or a dataset dimension), and each `yAxis[].field` /
|
|
23
|
+
* `series[].name` must be one of the widget's selected measures
|
|
24
|
+
* (`values`). Post-cutover (ADR-0021) the result rows are keyed by
|
|
25
|
+
* measure NAME (e.g. `sum_amount`), not the base column (`amount`) — a
|
|
26
|
+
* stale base-column reference renders the axis but an empty series.
|
|
27
|
+
*
|
|
28
|
+
* Advisory rules — severity `warning`, build stays green:
|
|
29
|
+
*
|
|
30
|
+
* - `chart-config-missing` — a chart-type widget (bar/line/pie/…) has no
|
|
31
|
+
* `chartConfig`, so the renderer cannot tell which measure to plot.
|
|
32
|
+
* - `table-count-only` (#1719) — a `table`/`pivot` widget whose selected
|
|
33
|
+
* measures are ALL `aggregate: 'count'` and which declares no
|
|
34
|
+
* `dimensions` asks the analytics service for a single summary row. That
|
|
35
|
+
* is the shape a `metric` widget wants — for a table it almost always
|
|
36
|
+
* means the author wanted a per-record listing, which is not an
|
|
37
|
+
* analytics dataset at all (model it as an object-bound ListView,
|
|
38
|
+
* ADR-0017). Evaluated on the WIDGET's binding, not the dataset.
|
|
39
|
+
*
|
|
40
|
+
* Warnings can be deliberately suppressed per widget via
|
|
41
|
+
* `suppressWarnings: ['<rule-id>']`; errors cannot — they describe a
|
|
42
|
+
* binding the analytics service cannot satisfy.
|
|
43
|
+
*/
|
|
44
|
+
export const WIDGET_DATASET_UNKNOWN = 'widget-dataset-unknown';
|
|
45
|
+
export const WIDGET_DIMENSION_UNKNOWN = 'widget-dimension-unknown';
|
|
46
|
+
export const WIDGET_MEASURE_UNKNOWN = 'widget-measure-unknown';
|
|
47
|
+
export const CHART_FIELD_UNKNOWN = 'chart-field-unknown';
|
|
48
|
+
export const CHART_CONFIG_MISSING = 'chart-config-missing';
|
|
49
|
+
export const TABLE_COUNT_ONLY = 'table-count-only';
|
|
50
|
+
/** Coerce a collection (array or name-keyed map) to an array. */
|
|
51
|
+
function asArray(v) {
|
|
52
|
+
if (Array.isArray(v))
|
|
53
|
+
return v;
|
|
54
|
+
if (v && typeof v === 'object') {
|
|
55
|
+
return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
56
|
+
}
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
function asStrings(v) {
|
|
60
|
+
return Array.isArray(v) ? v.filter((s) => typeof s === 'string') : [];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Chart families whose renderer needs a `chartConfig` measure mapping.
|
|
64
|
+
* Single-value types (metric/kpi/gauge/…) plot their lone value and tabular
|
|
65
|
+
* types (table/pivot) render every column, so they are exempt.
|
|
66
|
+
*/
|
|
67
|
+
const CHART_TYPES = new Set([
|
|
68
|
+
'bar', 'horizontal-bar', 'column',
|
|
69
|
+
'line', 'area',
|
|
70
|
+
'pie', 'donut', 'funnel',
|
|
71
|
+
'scatter', 'treemap', 'sankey', 'radar',
|
|
72
|
+
]);
|
|
73
|
+
function levenshtein(a, b) {
|
|
74
|
+
const m = a.length, n = b.length;
|
|
75
|
+
let prev = Array.from({ length: n + 1 }, (_, j) => j);
|
|
76
|
+
for (let i = 1; i <= m; i++) {
|
|
77
|
+
const cur = [i];
|
|
78
|
+
for (let j = 1; j <= n; j++) {
|
|
79
|
+
cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
80
|
+
}
|
|
81
|
+
prev = cur;
|
|
82
|
+
}
|
|
83
|
+
return prev[n];
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Nearest declared name for a typo'd/stale reference, or undefined when
|
|
87
|
+
* nothing is close. Containment is checked first because the cutover's
|
|
88
|
+
* canonical drift is base column → prefixed measure name (`amount` →
|
|
89
|
+
* `sum_amount`), which is far in edit distance but obvious to a human.
|
|
90
|
+
*/
|
|
91
|
+
function didYouMean(input, candidates) {
|
|
92
|
+
let best;
|
|
93
|
+
let bestScore = Infinity;
|
|
94
|
+
for (const c of candidates) {
|
|
95
|
+
let score;
|
|
96
|
+
if (input.length >= 3 && (c.includes(input) || input.includes(c))) {
|
|
97
|
+
score = Math.abs(c.length - input.length);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
const d = levenshtein(input, c);
|
|
101
|
+
if (d > Math.max(2, Math.floor(input.length / 3)))
|
|
102
|
+
continue;
|
|
103
|
+
score = 100 + d;
|
|
104
|
+
}
|
|
105
|
+
if (score < bestScore) {
|
|
106
|
+
bestScore = score;
|
|
107
|
+
best = c;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return best;
|
|
111
|
+
}
|
|
112
|
+
function suggest(input, candidates) {
|
|
113
|
+
const s = didYouMean(input, candidates);
|
|
114
|
+
return s ? ` Did you mean "${s}"?` : '';
|
|
115
|
+
}
|
|
116
|
+
function list(names) {
|
|
117
|
+
const arr = [...names];
|
|
118
|
+
return arr.length > 0 ? arr.join(', ') : '(none)';
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Validate every dashboard widget's dataset binding. Returns the list of
|
|
122
|
+
* findings (empty = clean). Caller decides how to surface them: `error`
|
|
123
|
+
* findings describe bindings the analytics service cannot satisfy and
|
|
124
|
+
* should fail validate/build; `warning` findings are advisory and must
|
|
125
|
+
* never fail the build on their own.
|
|
126
|
+
*/
|
|
127
|
+
export function validateWidgetBindings(stack) {
|
|
128
|
+
const findings = [];
|
|
129
|
+
const datasets = new Map();
|
|
130
|
+
for (const ds of asArray(stack.datasets)) {
|
|
131
|
+
if (typeof ds.name === 'string')
|
|
132
|
+
datasets.set(ds.name, ds);
|
|
133
|
+
}
|
|
134
|
+
const dashboards = asArray(stack.dashboards);
|
|
135
|
+
for (let i = 0; i < dashboards.length; i++) {
|
|
136
|
+
const dash = dashboards[i];
|
|
137
|
+
const dashName = typeof dash.name === 'string' ? dash.name : `(dashboard ${i})`;
|
|
138
|
+
const widgets = Array.isArray(dash.widgets) ? dash.widgets : [];
|
|
139
|
+
for (let j = 0; j < widgets.length; j++) {
|
|
140
|
+
const w = widgets[j];
|
|
141
|
+
const widgetId = typeof w.id === 'string' ? w.id : `(widget ${j})`;
|
|
142
|
+
const where = `dashboard "${dashName}" › widget "${widgetId}"`;
|
|
143
|
+
const path = `dashboards[${i}].widgets[${j}]`;
|
|
144
|
+
const suppressed = (rule) => Array.isArray(w.suppressWarnings) && w.suppressWarnings.includes(rule);
|
|
145
|
+
const push = (f) => {
|
|
146
|
+
if (f.severity === 'warning' && suppressed(f.rule))
|
|
147
|
+
return;
|
|
148
|
+
findings.push({ ...f, where, path });
|
|
149
|
+
};
|
|
150
|
+
// ── (a) dataset reference resolves ──
|
|
151
|
+
const dsName = typeof w.dataset === 'string' ? w.dataset : undefined;
|
|
152
|
+
const dataset = dsName ? datasets.get(dsName) : undefined;
|
|
153
|
+
if (dsName && !dataset) {
|
|
154
|
+
push({
|
|
155
|
+
severity: 'error',
|
|
156
|
+
rule: WIDGET_DATASET_UNKNOWN,
|
|
157
|
+
message: `dataset "${dsName}" does not resolve to a declared dataset.`,
|
|
158
|
+
hint: `Declared datasets: ${list(datasets.keys())}.${suggest(dsName, datasets.keys())} ` +
|
|
159
|
+
`Define the dataset with defineDataset() or fix the reference (ADR-0021).`,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
// Without a resolved dataset there is nothing to check names against.
|
|
163
|
+
if (!dataset)
|
|
164
|
+
continue;
|
|
165
|
+
const dimensionNames = new Set();
|
|
166
|
+
for (const d of asArray(dataset.dimensions)) {
|
|
167
|
+
if (typeof d.name === 'string')
|
|
168
|
+
dimensionNames.add(d.name);
|
|
169
|
+
}
|
|
170
|
+
const measures = new Map();
|
|
171
|
+
for (const m of asArray(dataset.measures)) {
|
|
172
|
+
if (typeof m.name === 'string')
|
|
173
|
+
measures.set(m.name, m);
|
|
174
|
+
}
|
|
175
|
+
// ── (b) every dimensions[] entry is a dataset dimension ──
|
|
176
|
+
const dims = asStrings(w.dimensions);
|
|
177
|
+
for (let k = 0; k < dims.length; k++) {
|
|
178
|
+
if (dimensionNames.has(dims[k]))
|
|
179
|
+
continue;
|
|
180
|
+
push({
|
|
181
|
+
severity: 'error',
|
|
182
|
+
rule: WIDGET_DIMENSION_UNKNOWN,
|
|
183
|
+
message: `dimensions[${k}] "${dims[k]}" is not a dimension of dataset ` +
|
|
184
|
+
`"${dsName}" (declared dimensions: ${list(dimensionNames)}).`,
|
|
185
|
+
hint: `Widgets select dataset dimensions BY NAME.${suggest(dims[k], dimensionNames)} ` +
|
|
186
|
+
`Add the dimension to the dataset or fix the reference.`,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
// ── (c) every values[] entry is a dataset measure ──
|
|
190
|
+
const values = asStrings(w.values);
|
|
191
|
+
for (let k = 0; k < values.length; k++) {
|
|
192
|
+
if (measures.has(values[k]))
|
|
193
|
+
continue;
|
|
194
|
+
push({
|
|
195
|
+
severity: 'error',
|
|
196
|
+
rule: WIDGET_MEASURE_UNKNOWN,
|
|
197
|
+
message: `values[${k}] "${values[k]}" is not a measure of dataset ` +
|
|
198
|
+
`"${dsName}" (declared measures: ${list(measures.keys())}).`,
|
|
199
|
+
hint: `Widgets select dataset measures BY NAME, not by base column.` +
|
|
200
|
+
`${suggest(values[k], measures.keys())} ` +
|
|
201
|
+
`Add the measure to the dataset or fix the reference.`,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
// ── (d) chartConfig bindings resolve against the widget's selection ──
|
|
205
|
+
const chartConfig = (w.chartConfig && typeof w.chartConfig === 'object')
|
|
206
|
+
? w.chartConfig
|
|
207
|
+
: undefined;
|
|
208
|
+
const isChartType = typeof w.type === 'string' && CHART_TYPES.has(w.type);
|
|
209
|
+
if (chartConfig) {
|
|
210
|
+
// The query result carries the widget's selected dimensions and
|
|
211
|
+
// measures; resolve every chartConfig field against that shape.
|
|
212
|
+
const selectedValues = new Set(values.filter((v) => measures.has(v)));
|
|
213
|
+
const xAxis = (chartConfig.xAxis && typeof chartConfig.xAxis === 'object')
|
|
214
|
+
? chartConfig.xAxis
|
|
215
|
+
: undefined;
|
|
216
|
+
// A field naming an entry of the widget's own (already-validated)
|
|
217
|
+
// selection is not re-reported here — rules (b)/(c) own that error.
|
|
218
|
+
if (xAxis && typeof xAxis.field === 'string'
|
|
219
|
+
&& !dimensionNames.has(xAxis.field) && !dims.includes(xAxis.field)) {
|
|
220
|
+
push({
|
|
221
|
+
severity: 'error',
|
|
222
|
+
rule: CHART_FIELD_UNKNOWN,
|
|
223
|
+
message: `chartConfig.xAxis.field "${xAxis.field}" does not resolve to a ` +
|
|
224
|
+
`dimension of dataset "${dsName}" (declared dimensions: ${list(dimensionNames)}).`,
|
|
225
|
+
hint: `Point xAxis.field at a dataset dimension name.${suggest(xAxis.field, dimensionNames)}`,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
const measureField = (label, field) => {
|
|
229
|
+
if (values.includes(field))
|
|
230
|
+
return; // resolvable, or already errored via rule (c)
|
|
231
|
+
const declaredButUnselected = measures.has(field);
|
|
232
|
+
push({
|
|
233
|
+
severity: 'error',
|
|
234
|
+
rule: CHART_FIELD_UNKNOWN,
|
|
235
|
+
message: declaredButUnselected
|
|
236
|
+
? `chartConfig.${label} "${field}" is a measure of dataset "${dsName}" ` +
|
|
237
|
+
`but is not selected in the widget's values (${list(values)}), so the ` +
|
|
238
|
+
`query result will not contain it.`
|
|
239
|
+
: `chartConfig.${label} "${field}" does not resolve to a measure of ` +
|
|
240
|
+
`dataset "${dsName}" (declared measures: ${list(measures.keys())}).`,
|
|
241
|
+
hint: declaredButUnselected
|
|
242
|
+
? `Add "${field}" to the widget's values, or bind the chart to a selected measure.`
|
|
243
|
+
: `Post-cutover data is keyed by the dataset's measure NAME, not the ` +
|
|
244
|
+
`base column.${suggest(field, selectedValues.size > 0 ? selectedValues : measures.keys())}`,
|
|
245
|
+
});
|
|
246
|
+
};
|
|
247
|
+
const yAxes = Array.isArray(chartConfig.yAxis) ? chartConfig.yAxis : [];
|
|
248
|
+
for (let k = 0; k < yAxes.length; k++) {
|
|
249
|
+
const field = yAxes[k]?.field;
|
|
250
|
+
if (typeof field === 'string')
|
|
251
|
+
measureField(`yAxis[${k}].field`, field);
|
|
252
|
+
}
|
|
253
|
+
const series = Array.isArray(chartConfig.series) ? chartConfig.series : [];
|
|
254
|
+
for (let k = 0; k < series.length; k++) {
|
|
255
|
+
const name = series[k]?.name;
|
|
256
|
+
if (typeof name === 'string')
|
|
257
|
+
measureField(`series[${k}].name`, name);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
else if (isChartType) {
|
|
261
|
+
push({
|
|
262
|
+
severity: 'warning',
|
|
263
|
+
rule: CHART_CONFIG_MISSING,
|
|
264
|
+
message: `chart-type widget ('${w.type}') has no chartConfig — the renderer ` +
|
|
265
|
+
`cannot determine which measure to plot, so the series renders empty.`,
|
|
266
|
+
hint: `Add chartConfig with xAxis.field set to a dimension (${list(dims)}) and ` +
|
|
267
|
+
`yAxis[].field set to a measure name (${list(values)}). If the default ` +
|
|
268
|
+
`rendering is intentional, suppress with: suppressWarnings: ['${CHART_CONFIG_MISSING}']`,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
// ── (e) table/pivot bound to a count-only, dimensionless selection ──
|
|
272
|
+
if (w.type !== 'table' && w.type !== 'pivot')
|
|
273
|
+
continue;
|
|
274
|
+
// Grouped by at least one dimension → genuinely aggregated rows.
|
|
275
|
+
if (dims.length > 0)
|
|
276
|
+
continue;
|
|
277
|
+
if (values.length === 0)
|
|
278
|
+
continue;
|
|
279
|
+
const resolved = values.map((v) => measures.get(v));
|
|
280
|
+
// An unresolvable measure name already errored above — don't guess here.
|
|
281
|
+
if (resolved.some((m) => !m))
|
|
282
|
+
continue;
|
|
283
|
+
// Derived measures combine other measures; treat them as non-count even
|
|
284
|
+
// when their (ignored) `aggregate` says otherwise.
|
|
285
|
+
const countOnly = resolved.every((m) => m.aggregate === 'count' && !m.derived);
|
|
286
|
+
if (!countOnly)
|
|
287
|
+
continue;
|
|
288
|
+
push({
|
|
289
|
+
severity: 'warning',
|
|
290
|
+
rule: TABLE_COUNT_ONLY,
|
|
291
|
+
message: `a '${w.type}' widget bound to dataset "${dsName}" selects only count ` +
|
|
292
|
+
`measure(s) (${values.join(', ')}) and no dimensions, so it renders a ` +
|
|
293
|
+
`single summary row — not a per-record list.`,
|
|
294
|
+
hint: `A flat record listing is not an analytics dataset. Model it as an ` +
|
|
295
|
+
`object-bound ListView (ADR-0017) surfaced through app navigation, and ` +
|
|
296
|
+
`use a 'metric' widget here if you only need the count. If a single-row ` +
|
|
297
|
+
`table is intentional, add an explicit dimension or suppress with: ` +
|
|
298
|
+
`suppressWarnings: ['${TABLE_COUNT_ONLY}']`,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return findings;
|
|
303
|
+
}
|
|
304
|
+
//# sourceMappingURL=validate-widget-bindings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-widget-bindings.js","sourceRoot":"","sources":["../../src/utils/validate-widget-bindings.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-widget-bindings.test.d.ts","sourceRoot":"","sources":["../../src/utils/validate-widget-bindings.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { validateWidgetBindings, TABLE_COUNT_ONLY, WIDGET_DATASET_UNKNOWN, WIDGET_DIMENSION_UNKNOWN, WIDGET_MEASURE_UNKNOWN, CHART_FIELD_UNKNOWN, CHART_CONFIG_MISSING, } from './validate-widget-bindings.js';
|
|
3
|
+
/** The downstream repro from issue #1719 — dataset with a count AND a sum
|
|
4
|
+
* measure plus a dimension; the widget selects only the count, no dims. */
|
|
5
|
+
function reproStack(widgetOverrides = {}) {
|
|
6
|
+
return {
|
|
7
|
+
datasets: [{
|
|
8
|
+
name: 'expense_report_metrics',
|
|
9
|
+
label: 'Expense report metrics',
|
|
10
|
+
object: 'expense_report',
|
|
11
|
+
measures: [
|
|
12
|
+
{ name: 'report_count', label: 'report_count', aggregate: 'count' },
|
|
13
|
+
{ name: 'total_amount', label: 'total_amount', aggregate: 'sum', field: 'total_amount' },
|
|
14
|
+
],
|
|
15
|
+
dimensions: [{ name: 'cost_center', field: 'cost_center' }],
|
|
16
|
+
}],
|
|
17
|
+
dashboards: [{
|
|
18
|
+
name: 'expenses_overview_dashboard',
|
|
19
|
+
label: 'Expenses Overview',
|
|
20
|
+
widgets: [{
|
|
21
|
+
id: 'pending_reports_table',
|
|
22
|
+
type: 'table',
|
|
23
|
+
dataset: 'expense_report_metrics',
|
|
24
|
+
values: ['report_count'],
|
|
25
|
+
filter: { status: 'submitted' },
|
|
26
|
+
layout: { x: 0, y: 0, w: 6, h: 4 },
|
|
27
|
+
...widgetOverrides,
|
|
28
|
+
}],
|
|
29
|
+
}],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** The minimal repro from issue #1721 — dataset measure is `sum_amount`, but
|
|
33
|
+
* the chart's yAxis still names the old base column `amount`. */
|
|
34
|
+
function chartStack(widgetOverrides = {}) {
|
|
35
|
+
return {
|
|
36
|
+
datasets: [{
|
|
37
|
+
name: 'expense_line_metrics',
|
|
38
|
+
label: 'Expense line metrics',
|
|
39
|
+
object: 'expense_line',
|
|
40
|
+
dimensions: [{ name: 'category', field: 'category' }],
|
|
41
|
+
measures: [
|
|
42
|
+
{ name: 'sum_amount', label: 'sum_amount', aggregate: 'sum', field: 'amount', certified: true },
|
|
43
|
+
{ name: 'ticket_count', label: 'ticket_count', aggregate: 'count' },
|
|
44
|
+
],
|
|
45
|
+
}],
|
|
46
|
+
dashboards: [{
|
|
47
|
+
name: 'spend_dashboard',
|
|
48
|
+
label: 'Spend',
|
|
49
|
+
widgets: [{
|
|
50
|
+
id: 'spend_by_category',
|
|
51
|
+
type: 'bar',
|
|
52
|
+
dataset: 'expense_line_metrics',
|
|
53
|
+
dimensions: ['category'],
|
|
54
|
+
values: ['sum_amount'],
|
|
55
|
+
chartConfig: {
|
|
56
|
+
type: 'bar',
|
|
57
|
+
xAxis: { field: 'category' },
|
|
58
|
+
yAxis: [{ field: 'sum_amount' }],
|
|
59
|
+
},
|
|
60
|
+
layout: { x: 0, y: 0, w: 6, h: 4 },
|
|
61
|
+
...widgetOverrides,
|
|
62
|
+
}],
|
|
63
|
+
}],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
describe('validateWidgetBindings (reference integrity, issue #1721)', () => {
|
|
67
|
+
it('a fully resolved chart widget is clean', () => {
|
|
68
|
+
expect(validateWidgetBindings(chartStack())).toHaveLength(0);
|
|
69
|
+
});
|
|
70
|
+
it('(a) errors on a dataset reference that does not resolve', () => {
|
|
71
|
+
const findings = validateWidgetBindings(chartStack({ dataset: 'expense_line_metric' }));
|
|
72
|
+
expect(findings).toHaveLength(1);
|
|
73
|
+
expect(findings[0].severity).toBe('error');
|
|
74
|
+
expect(findings[0].rule).toBe(WIDGET_DATASET_UNKNOWN);
|
|
75
|
+
expect(findings[0].message).toContain('expense_line_metric');
|
|
76
|
+
expect(findings[0].hint).toContain('Did you mean "expense_line_metrics"?');
|
|
77
|
+
});
|
|
78
|
+
it('(b) errors on a dimension name the dataset does not declare', () => {
|
|
79
|
+
const findings = validateWidgetBindings(chartStack({ dimensions: ['categry'] }));
|
|
80
|
+
expect(findings).toHaveLength(1);
|
|
81
|
+
expect(findings[0].severity).toBe('error');
|
|
82
|
+
expect(findings[0].rule).toBe(WIDGET_DIMENSION_UNKNOWN);
|
|
83
|
+
expect(findings[0].message).toContain('"categry"');
|
|
84
|
+
expect(findings[0].message).toContain('declared dimensions: category');
|
|
85
|
+
expect(findings[0].hint).toContain('Did you mean "category"?');
|
|
86
|
+
});
|
|
87
|
+
it('(c) errors on a measure name the dataset does not declare', () => {
|
|
88
|
+
const findings = validateWidgetBindings(chartStack({
|
|
89
|
+
type: 'metric',
|
|
90
|
+
values: ['amount'],
|
|
91
|
+
chartConfig: undefined,
|
|
92
|
+
}));
|
|
93
|
+
expect(findings).toHaveLength(1);
|
|
94
|
+
expect(findings[0].severity).toBe('error');
|
|
95
|
+
expect(findings[0].rule).toBe(WIDGET_MEASURE_UNKNOWN);
|
|
96
|
+
expect(findings[0].message).toContain('declared measures: sum_amount, ticket_count');
|
|
97
|
+
expect(findings[0].hint).toContain('Did you mean "sum_amount"?');
|
|
98
|
+
});
|
|
99
|
+
it('(d) errors on the issue repro: yAxis.field naming the stale base column', () => {
|
|
100
|
+
const findings = validateWidgetBindings(chartStack({
|
|
101
|
+
chartConfig: {
|
|
102
|
+
type: 'bar',
|
|
103
|
+
xAxis: { field: 'category' },
|
|
104
|
+
yAxis: [{ field: 'amount' }],
|
|
105
|
+
},
|
|
106
|
+
}));
|
|
107
|
+
expect(findings).toHaveLength(1);
|
|
108
|
+
expect(findings[0].severity).toBe('error');
|
|
109
|
+
expect(findings[0].rule).toBe(CHART_FIELD_UNKNOWN);
|
|
110
|
+
expect(findings[0].where).toContain('spend_by_category');
|
|
111
|
+
expect(findings[0].message).toContain('chartConfig.yAxis[0].field "amount"');
|
|
112
|
+
expect(findings[0].message).toContain('declared measures: sum_amount, ticket_count');
|
|
113
|
+
expect(findings[0].hint).toContain('Did you mean "sum_amount"?');
|
|
114
|
+
});
|
|
115
|
+
it('(d) errors on xAxis.field that is not a dataset dimension', () => {
|
|
116
|
+
const findings = validateWidgetBindings(chartStack({
|
|
117
|
+
chartConfig: {
|
|
118
|
+
type: 'bar',
|
|
119
|
+
xAxis: { field: 'categories' },
|
|
120
|
+
yAxis: [{ field: 'sum_amount' }],
|
|
121
|
+
},
|
|
122
|
+
}));
|
|
123
|
+
expect(findings).toHaveLength(1);
|
|
124
|
+
expect(findings[0].rule).toBe(CHART_FIELD_UNKNOWN);
|
|
125
|
+
expect(findings[0].message).toContain('chartConfig.xAxis.field "categories"');
|
|
126
|
+
expect(findings[0].hint).toContain('Did you mean "category"?');
|
|
127
|
+
});
|
|
128
|
+
it('(d) errors on series[].name that resolves to no selected measure', () => {
|
|
129
|
+
const findings = validateWidgetBindings(chartStack({
|
|
130
|
+
chartConfig: {
|
|
131
|
+
type: 'bar',
|
|
132
|
+
series: [{ name: 'value' }],
|
|
133
|
+
},
|
|
134
|
+
}));
|
|
135
|
+
expect(findings).toHaveLength(1);
|
|
136
|
+
expect(findings[0].rule).toBe(CHART_FIELD_UNKNOWN);
|
|
137
|
+
expect(findings[0].message).toContain('chartConfig.series[0].name "value"');
|
|
138
|
+
});
|
|
139
|
+
it('(d) a declared-but-unselected measure gets the targeted message', () => {
|
|
140
|
+
const findings = validateWidgetBindings(chartStack({
|
|
141
|
+
chartConfig: {
|
|
142
|
+
type: 'bar',
|
|
143
|
+
xAxis: { field: 'category' },
|
|
144
|
+
yAxis: [{ field: 'ticket_count' }],
|
|
145
|
+
},
|
|
146
|
+
}));
|
|
147
|
+
expect(findings).toHaveLength(1);
|
|
148
|
+
expect(findings[0].rule).toBe(CHART_FIELD_UNKNOWN);
|
|
149
|
+
expect(findings[0].message).toContain('not selected in the widget\'s values');
|
|
150
|
+
expect(findings[0].hint).toContain('Add "ticket_count" to the widget\'s values');
|
|
151
|
+
});
|
|
152
|
+
it('(d) warns when a chart-type widget has no chartConfig at all', () => {
|
|
153
|
+
const findings = validateWidgetBindings(chartStack({ chartConfig: undefined }));
|
|
154
|
+
expect(findings).toHaveLength(1);
|
|
155
|
+
expect(findings[0].severity).toBe('warning');
|
|
156
|
+
expect(findings[0].rule).toBe(CHART_CONFIG_MISSING);
|
|
157
|
+
expect(findings[0].message).toContain("'bar'");
|
|
158
|
+
expect(findings[0].hint).toContain(`suppressWarnings: ['${CHART_CONFIG_MISSING}']`);
|
|
159
|
+
});
|
|
160
|
+
it('(d) missing chartConfig is suppressible per widget', () => {
|
|
161
|
+
expect(validateWidgetBindings(chartStack({
|
|
162
|
+
chartConfig: undefined,
|
|
163
|
+
suppressWarnings: [CHART_CONFIG_MISSING],
|
|
164
|
+
}))).toHaveLength(0);
|
|
165
|
+
});
|
|
166
|
+
it('(d) non-chart types do not warn on missing chartConfig', () => {
|
|
167
|
+
for (const type of ['metric', 'kpi', 'gauge', 'table']) {
|
|
168
|
+
expect(validateWidgetBindings(chartStack({ type, chartConfig: undefined }))).toHaveLength(0);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
it('errors are NOT suppressible via suppressWarnings', () => {
|
|
172
|
+
const findings = validateWidgetBindings(chartStack({
|
|
173
|
+
dataset: 'no_such_dataset',
|
|
174
|
+
suppressWarnings: [WIDGET_DATASET_UNKNOWN],
|
|
175
|
+
}));
|
|
176
|
+
expect(findings).toHaveLength(1);
|
|
177
|
+
expect(findings[0].severity).toBe('error');
|
|
178
|
+
});
|
|
179
|
+
it('does not double-report a chartConfig field that names an already-errored selection entry', () => {
|
|
180
|
+
const findings = validateWidgetBindings(chartStack({
|
|
181
|
+
values: ['amount'],
|
|
182
|
+
chartConfig: {
|
|
183
|
+
type: 'bar',
|
|
184
|
+
xAxis: { field: 'category' },
|
|
185
|
+
yAxis: [{ field: 'amount' }],
|
|
186
|
+
},
|
|
187
|
+
}));
|
|
188
|
+
expect(findings).toHaveLength(1);
|
|
189
|
+
expect(findings[0].rule).toBe(WIDGET_MEASURE_UNKNOWN);
|
|
190
|
+
});
|
|
191
|
+
it('a dangling dataset reports once and skips the name checks', () => {
|
|
192
|
+
const findings = validateWidgetBindings(chartStack({
|
|
193
|
+
dataset: 'nope',
|
|
194
|
+
dimensions: ['whatever'],
|
|
195
|
+
values: ['also_whatever'],
|
|
196
|
+
}));
|
|
197
|
+
expect(findings).toHaveLength(1);
|
|
198
|
+
expect(findings[0].rule).toBe(WIDGET_DATASET_UNKNOWN);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
describe('validateWidgetBindings (table-count-only, issue #1719)', () => {
|
|
202
|
+
it('warns on the issue repro: count-only table widget without dimensions', () => {
|
|
203
|
+
const warnings = validateWidgetBindings(reproStack());
|
|
204
|
+
expect(warnings).toHaveLength(1);
|
|
205
|
+
expect(warnings[0].severity).toBe('warning');
|
|
206
|
+
expect(warnings[0].rule).toBe(TABLE_COUNT_ONLY);
|
|
207
|
+
expect(warnings[0].where).toContain('expenses_overview_dashboard');
|
|
208
|
+
expect(warnings[0].where).toContain('pending_reports_table');
|
|
209
|
+
expect(warnings[0].path).toBe('dashboards[0].widgets[0]');
|
|
210
|
+
expect(warnings[0].message).toContain('report_count');
|
|
211
|
+
expect(warnings[0].message).toContain('single summary row');
|
|
212
|
+
expect(warnings[0].hint).toContain('ListView (ADR-0017)');
|
|
213
|
+
expect(warnings[0].hint).toContain(`suppressWarnings: ['${TABLE_COUNT_ONLY}']`);
|
|
214
|
+
});
|
|
215
|
+
it('warns for pivot widgets too', () => {
|
|
216
|
+
const warnings = validateWidgetBindings(reproStack({ type: 'pivot' }));
|
|
217
|
+
expect(warnings).toHaveLength(1);
|
|
218
|
+
expect(warnings[0].message).toContain("'pivot' widget");
|
|
219
|
+
});
|
|
220
|
+
it('is keyed on the WIDGET binding — selecting the sum measure is clean', () => {
|
|
221
|
+
expect(validateWidgetBindings(reproStack({ values: ['total_amount'] }))).toHaveLength(0);
|
|
222
|
+
});
|
|
223
|
+
it('mixed count + non-count selection is clean', () => {
|
|
224
|
+
expect(validateWidgetBindings(reproStack({ values: ['report_count', 'total_amount'] }))).toHaveLength(0);
|
|
225
|
+
});
|
|
226
|
+
it('declaring a dimension on the widget is clean', () => {
|
|
227
|
+
expect(validateWidgetBindings(reproStack({ dimensions: ['cost_center'] }))).toHaveLength(0);
|
|
228
|
+
});
|
|
229
|
+
it('metric widgets are exactly what a count-only binding wants — clean', () => {
|
|
230
|
+
expect(validateWidgetBindings(reproStack({ type: 'metric' }))).toHaveLength(0);
|
|
231
|
+
});
|
|
232
|
+
it('suppressWarnings opts a deliberate single-row table out', () => {
|
|
233
|
+
expect(validateWidgetBindings(reproStack({ suppressWarnings: [TABLE_COUNT_ONLY] }))).toHaveLength(0);
|
|
234
|
+
});
|
|
235
|
+
it('unrelated suppressWarnings entries do not suppress', () => {
|
|
236
|
+
expect(validateWidgetBindings(reproStack({ suppressWarnings: ['some-other-rule'] }))).toHaveLength(1);
|
|
237
|
+
});
|
|
238
|
+
it('a dangling dataset reference is the cross-reference error, not this rule', () => {
|
|
239
|
+
const findings = validateWidgetBindings(reproStack({ dataset: 'no_such_dataset' }));
|
|
240
|
+
expect(findings).toHaveLength(1);
|
|
241
|
+
expect(findings[0].severity).toBe('error');
|
|
242
|
+
expect(findings[0].rule).toBe(WIDGET_DATASET_UNKNOWN);
|
|
243
|
+
});
|
|
244
|
+
it('an unresolvable measure name is the cross-reference error, not this rule', () => {
|
|
245
|
+
const findings = validateWidgetBindings(reproStack({ values: ['no_such_measure'] }));
|
|
246
|
+
expect(findings).toHaveLength(1);
|
|
247
|
+
expect(findings[0].severity).toBe('error');
|
|
248
|
+
expect(findings[0].rule).toBe(WIDGET_MEASURE_UNKNOWN);
|
|
249
|
+
});
|
|
250
|
+
it('treats derived measures as non-count even when aggregate says count', () => {
|
|
251
|
+
const stack = reproStack({ values: ['count_ratio'] });
|
|
252
|
+
stack.datasets[0].measures.push({
|
|
253
|
+
name: 'count_ratio',
|
|
254
|
+
aggregate: 'count',
|
|
255
|
+
derived: { op: 'ratio', of: ['report_count', 'report_count'] },
|
|
256
|
+
});
|
|
257
|
+
expect(validateWidgetBindings(stack)).toHaveLength(0);
|
|
258
|
+
});
|
|
259
|
+
it('count_distinct is a deliberate analytic — clean', () => {
|
|
260
|
+
const stack = reproStack({ values: ['unique_requesters'] });
|
|
261
|
+
stack.datasets[0].measures.push({
|
|
262
|
+
name: 'unique_requesters',
|
|
263
|
+
aggregate: 'count_distinct',
|
|
264
|
+
field: 'requester',
|
|
265
|
+
});
|
|
266
|
+
expect(validateWidgetBindings(stack)).toHaveLength(0);
|
|
267
|
+
});
|
|
268
|
+
it('handles map-keyed datasets/dashboards collections', () => {
|
|
269
|
+
const arrayForm = reproStack();
|
|
270
|
+
const { name: _dsName, ...dsRest } = arrayForm.datasets[0];
|
|
271
|
+
const { name: _dashName, ...dashRest } = arrayForm.dashboards[0];
|
|
272
|
+
const stack = {
|
|
273
|
+
datasets: { expense_report_metrics: dsRest },
|
|
274
|
+
dashboards: { expenses_overview_dashboard: dashRest },
|
|
275
|
+
};
|
|
276
|
+
const warnings = validateWidgetBindings(stack);
|
|
277
|
+
expect(warnings).toHaveLength(1);
|
|
278
|
+
expect(warnings[0].where).toContain('expenses_overview_dashboard');
|
|
279
|
+
});
|
|
280
|
+
it('is silent on stacks without dashboards or datasets', () => {
|
|
281
|
+
expect(validateWidgetBindings({})).toHaveLength(0);
|
|
282
|
+
expect(validateWidgetBindings({ dashboards: [], datasets: [] })).toHaveLength(0);
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
//# sourceMappingURL=validate-widget-bindings.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-widget-bindings.test.js","sourceRoot":"","sources":["../../src/utils/validate-widget-bindings.test.ts"],"names":[],"mappings":"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"}
|