@object-ui/core 17.6.0 → 18.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7260 -0
- package/README.md +228 -14
- package/dist/actions/ActionEngine.d.ts +4 -2
- package/dist/actions/ActionEngine.js +13 -8
- package/dist/actions/ActionRunner.d.ts +473 -69
- package/dist/actions/ActionRunner.js +792 -125
- package/dist/actions/TransactionManager.d.ts +7 -2
- package/dist/actions/UndoManager.d.ts +8 -13
- package/dist/actions/actionKeys.d.ts +9 -7
- package/dist/actions/actionKeys.js +67 -14
- package/dist/actions/actionResponse.js +2 -2
- package/dist/actions/index.d.ts +2 -0
- package/dist/actions/index.js +2 -0
- package/dist/actions/navigationTarget.d.ts +41 -0
- package/dist/actions/navigationTarget.js +20 -0
- package/dist/actions/recordDelete.d.ts +154 -0
- package/dist/actions/recordDelete.js +110 -0
- package/dist/adapters/ApiDataSource.d.ts +8 -4
- package/dist/adapters/ApiDataSource.js +19 -7
- package/dist/adapters/ValueDataSource.d.ts +6 -1
- package/dist/adapters/ValueDataSource.js +1090 -98
- package/dist/adapters/batchTransaction.d.ts +1 -1
- package/dist/adapters/batchTransaction.js +1 -1
- package/dist/adapters/resolveDataSource.d.ts +9 -6
- package/dist/adapters/resolveDataSource.js +9 -12
- package/dist/builder/schema-builder.d.ts +38 -81
- package/dist/builder/schema-builder.js +33 -152
- package/dist/data-scope/ViewDataProvider.d.ts +8 -25
- package/dist/data-scope/ViewDataProvider.js +27 -54
- package/dist/data-scope/element-data-source.d.ts +261 -7
- package/dist/data-scope/element-data-source.js +342 -52
- package/dist/data-scope/index.d.ts +9 -4
- package/dist/data-scope/index.js +9 -4
- package/dist/evaluator/ExpressionEvaluator.d.ts +86 -0
- package/dist/evaluator/ExpressionEvaluator.js +184 -12
- package/dist/evaluator/FormulaFunctions.d.ts +0 -10
- package/dist/evaluator/FormulaFunctions.js +166 -27
- package/dist/evaluator/deadlineSettled.d.ts +40 -0
- package/dist/evaluator/deadlineSettled.js +45 -0
- package/dist/evaluator/declaredPredicate.d.ts +93 -9
- package/dist/evaluator/declaredPredicate.js +117 -16
- package/dist/evaluator/fieldRules.d.ts +88 -4
- package/dist/evaluator/fieldRules.js +208 -31
- package/dist/evaluator/index.d.ts +2 -0
- package/dist/evaluator/index.js +2 -0
- package/dist/evaluator/listConditional.d.ts +103 -34
- package/dist/evaluator/listConditional.js +127 -50
- package/dist/evaluator/optionRules.d.ts +77 -6
- package/dist/evaluator/optionRules.js +87 -10
- package/dist/evaluator/predicateInput.d.ts +15 -4
- package/dist/evaluator/predicateInput.js +35 -2
- package/dist/evaluator/rowPredicateCanon.d.ts +7 -18
- package/dist/evaluator/rowPredicateCanon.js +65 -115
- package/dist/evaluator/subjectPermissions.d.ts +105 -0
- package/dist/evaluator/subjectPermissions.js +86 -0
- package/dist/evaluator/unevaluablePredicate.d.ts +57 -0
- package/dist/evaluator/unevaluablePredicate.js +70 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +83 -2
- package/dist/protocols/index.d.ts +0 -1
- package/dist/protocols/index.js +9 -1
- package/dist/registry/Registry.d.ts +382 -34
- package/dist/registry/Registry.js +247 -12
- package/dist/registry/WidgetRegistry.js +7 -3
- package/dist/registry/html-tier-intrinsics.d.ts +90 -0
- package/dist/registry/html-tier-intrinsics.js +110 -0
- package/dist/registry/public-blocks.d.ts +8 -0
- package/dist/registry/public-blocks.js +57 -0
- package/dist/theme/ThemeEngine.js +1 -1
- package/dist/utils/affordanceGrants.d.ts +371 -0
- package/dist/utils/affordanceGrants.js +252 -0
- package/dist/utils/chart-category-key.d.ts +82 -0
- package/dist/utils/chart-category-key.js +84 -0
- package/dist/utils/chart-measure-key.d.ts +64 -0
- package/dist/utils/chart-measure-key.js +65 -0
- package/dist/utils/chart-presentation.d.ts +95 -112
- package/dist/utils/chart-presentation.js +169 -104
- package/dist/utils/chart-series.d.ts +165 -5
- package/dist/utils/chart-series.js +123 -18
- package/dist/utils/column-sortability.d.ts +157 -0
- package/dist/utils/column-sortability.js +217 -0
- package/dist/utils/dashboard-filters.d.ts +38 -6
- package/dist/utils/dashboard-filters.js +107 -61
- package/dist/utils/dataset-format.d.ts +211 -30
- package/dist/utils/dataset-format.js +389 -46
- package/dist/utils/date-display.d.ts +346 -0
- package/dist/utils/date-display.js +644 -0
- package/dist/utils/date-macros.js +7 -0
- package/dist/utils/drill-down.d.ts +78 -0
- package/dist/utils/drill-down.js +93 -0
- package/dist/utils/emptiness.d.ts +96 -0
- package/dist/utils/emptiness.js +101 -0
- package/dist/utils/expand-fields.d.ts +81 -9
- package/dist/utils/expand-fields.js +81 -9
- package/dist/utils/extract-records.d.ts +65 -5
- package/dist/utils/extract-records.js +65 -8
- package/dist/utils/filter-converter.d.ts +158 -6
- package/dist/utils/filter-converter.js +1098 -24
- package/dist/utils/filter-tokens.d.ts +49 -5
- package/dist/utils/filter-tokens.js +105 -33
- package/dist/utils/grouping-fields.d.ts +61 -0
- package/dist/utils/grouping-fields.js +84 -0
- package/dist/utils/managedBy.d.ts +3 -3
- package/dist/utils/managedBy.js +5 -5
- package/dist/utils/native-date-value.d.ts +48 -0
- package/dist/utils/native-date-value.js +167 -0
- package/dist/utils/non-grid-row-ceiling.d.ts +143 -0
- package/dist/utils/non-grid-row-ceiling.js +141 -0
- package/dist/utils/normalize-list-view.d.ts +135 -11
- package/dist/utils/normalize-list-view.js +243 -64
- package/dist/utils/number-display.d.ts +63 -14
- package/dist/utils/number-display.js +34 -12
- package/dist/utils/object-aggregate-query.d.ts +113 -0
- package/dist/utils/object-aggregate-query.js +50 -0
- package/dist/utils/parent-scope.d.ts +123 -0
- package/dist/utils/parent-scope.js +140 -0
- package/dist/utils/predicate-fields.d.ts +45 -12
- package/dist/utils/predicate-fields.js +141 -12
- package/dist/utils/record-color.d.ts +75 -0
- package/dist/utils/record-color.js +65 -0
- package/dist/utils/record-source.d.ts +298 -0
- package/dist/utils/record-source.js +331 -0
- package/dist/utils/record-title.d.ts +148 -8
- package/dist/utils/record-title.js +220 -26
- package/dist/utils/reference-keys.d.ts +21 -22
- package/dist/utils/reference-keys.js +532 -29
- package/dist/utils/sort-query.d.ts +115 -15
- package/dist/utils/sort-query.js +240 -13
- package/dist/utils/text-comparand.d.ts +92 -0
- package/dist/utils/text-comparand.js +99 -0
- package/dist/utils/without-denied-fields.d.ts +49 -0
- package/dist/utils/without-denied-fields.js +52 -0
- package/dist/validation/index.d.ts +8 -4
- package/dist/validation/index.js +8 -4
- package/dist/validation/schema-validator.d.ts +1 -1
- package/dist/validation/schema-validator.js +225 -83
- package/dist/validation/server-owned-value.d.ts +5 -1
- package/dist/validation/server-owned-value.js +5 -1
- package/dist/validation/validators/object-validation-engine.d.ts +1 -1
- package/dist/validation/validators/object-validation-engine.js +14 -3
- package/package.json +8 -9
- package/dist/data-scope/DataScopeManager.d.ts +0 -127
- package/dist/data-scope/DataScopeManager.js +0 -229
- package/dist/protocols/ResponsiveProtocol.d.ts +0 -73
- package/dist/protocols/ResponsiveProtocol.js +0 -158
- package/dist/validation/validation-engine.d.ts +0 -88
- package/dist/validation/validation-engine.js +0 -434
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import type { AnalyticsResult } from '@objectstack/spec/contracts';
|
|
19
19
|
import type { PercentScale } from '@objectstack/spec/data';
|
|
20
|
+
import { type BuiltinAggregateLabels } from './chart-series.js';
|
|
20
21
|
/**
|
|
21
22
|
* Column metadata the analytics server returns alongside the rows — the spec's
|
|
22
23
|
* `AnalyticsResult.fields[]` element BY REFERENCE, never a local restatement of
|
|
@@ -59,28 +60,35 @@ export type DatasetResultField = AnalyticsResult['fields'][number];
|
|
|
59
60
|
*/
|
|
60
61
|
export type { PercentScale } from '@objectstack/spec/data';
|
|
61
62
|
/**
|
|
62
|
-
* Scale a stored
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* `
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* `1
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
63
|
+
* Scale a stored percentage to its DISPLAY magnitude (percentage points), at
|
|
64
|
+
* the storage the caller STATES.
|
|
65
|
+
*
|
|
66
|
+
* `percentScale` is the spec's `PercentScale`: `fraction` is a 0–1 ratio
|
|
67
|
+
* (`0.75` ⇒ 75, `1` ⇒ 100), `whole` is already in percentage points (`75` ⇒
|
|
68
|
+
* 75, `1` ⇒ 1). A caller holding a field definition reads it with the spec's
|
|
69
|
+
* `percentScaleOf` (`@objectstack/spec/data`): a `percent` field stores a
|
|
70
|
+
* fraction unless it declares a `max` above 1. A caller with no field (a
|
|
71
|
+
* computed ratio, a server measure column) states the storage it knows: a
|
|
72
|
+
* `derived: { op: 'ratio' }` measure is a fraction by definition, and the
|
|
73
|
+
* server annotates a measure column with its `percentScale`.
|
|
74
|
+
*
|
|
75
|
+
* ⛔ No storage is inferred from the VALUE any more (objectui#11475). This
|
|
76
|
+
* function used to be `value > -1 && value < 1 ? value * 100 : value`, a guess
|
|
77
|
+
* from the magnitude that read neither the field nor its `max`. A fraction-
|
|
78
|
+
* stored `1` (100%) therefore read `1%` in the list cell while the read-only
|
|
79
|
+
* form, which reads the declaration, read `100%`; a whole-stored `0.5` read
|
|
80
|
+
* `50%`. objectui#3136 was the same guess on a dashboard measure. The
|
|
81
|
+
* parameter is REQUIRED, so every caller says which storage it holds, and a
|
|
82
|
+
* value outside the union throws rather than falling to a branch silently.
|
|
83
|
+
*
|
|
84
|
+
* ⭐ The contract a third percent surface still owes is BOTH halves: this
|
|
85
|
+
* scaling, at the stored scale, AND the locale's percent convention
|
|
86
|
+
* (`style: 'percentPoints'`, see {@link formatMeasure} and `formatPercent` in
|
|
87
|
+
* `@object-ui/fields`). Between objectui#4553 and objectui#4576 the two ends
|
|
88
|
+
* shared the scaling and not the convention, so a German session read
|
|
89
|
+
* `1.234,5 %` in a list cell and `1.234,5%` in a dashboard measure.
|
|
82
90
|
*/
|
|
83
|
-
export declare function percentDisplayValue(value: number): number;
|
|
91
|
+
export declare function percentDisplayValue(value: number, percentScale: PercentScale): number;
|
|
84
92
|
/**
|
|
85
93
|
* Format a MEASURE value. Currency comes from the field's declared `currency`
|
|
86
94
|
* (locale-correct symbol via `Intl`), NOT from a "$" baked into the format
|
|
@@ -118,8 +126,54 @@ export declare function percentDisplayValue(value: number): number;
|
|
|
118
126
|
* is pure, so it moved DOWN into this package (`./number-display.ts`) and
|
|
119
127
|
* `@object-ui/i18n` re-exports it. The duplicate implementation this file used
|
|
120
128
|
* to carry is gone, and with it the drift that duplicate produced — see the
|
|
121
|
-
* percent note in the body, and
|
|
122
|
-
*
|
|
129
|
+
* percent note in the body, and the both-halves contract
|
|
130
|
+
* {@link percentDisplayValue}'s doc comment states.
|
|
131
|
+
*
|
|
132
|
+
* ── Date-valued measures (objectui#7178) ──
|
|
133
|
+
* `min` / `max` over a date or datetime field is a legitimate measure, and it
|
|
134
|
+
* used to render its stored value verbatim: the guard above returned
|
|
135
|
+
* `String(v)` for anything non-numeric BEFORE `format` was read, so a KPI tile
|
|
136
|
+
* showed `2026-07-04T07:00:00.000Z` in `text-2xl` and wrapped it over two
|
|
137
|
+
* lines. A date-shaped value now routes through {@link formatMeasureDate} to
|
|
138
|
+
* the display path list cells use; nothing about the numeric path moved.
|
|
139
|
+
*
|
|
140
|
+
* ⚠️ What `format` can and cannot say for those values, measured rather than
|
|
141
|
+
* assumed, and stated PER ARM — a date-shaped measure takes one of two arms in
|
|
142
|
+
* {@link formatMeasureDate}, and this paragraph used to describe only one of
|
|
143
|
+
* them while reading as though it described both. That cost a card: an
|
|
144
|
+
* objectui triage pass read the undifferentiated claim, concluded the defect
|
|
145
|
+
* was already fixed, and downgraded objectui#8352, which a driven browser run
|
|
146
|
+
* then refuted. The two arms agree TODAY, but only because #8352 made them; a
|
|
147
|
+
* universal sentence here is what hid the disagreement before, so it is not
|
|
148
|
+
* coming back even now that it would be true.
|
|
149
|
+
*
|
|
150
|
+
* The shared date path takes a named STYLE, not a date pattern. On BOTH arms
|
|
151
|
+
* the honoured vocabulary is `'short'` and `'relative'`:
|
|
152
|
+
*
|
|
153
|
+
* - `Field.date` (`2026-09-08`) — honoured by threading `format` into
|
|
154
|
+
* `formatDate`'s style parameter. The same words `DateCellRenderer`
|
|
155
|
+
* honours from `field.format` for the same field.
|
|
156
|
+
* - `Field.datetime` (`2026-09-01T00:00:00.000Z`) — honoured by SELECTING a
|
|
157
|
+
* formatter, because `formatDateTime` has no style parameter to thread
|
|
158
|
+
* into (objectui#8352). Before that card this arm honoured NOTHING: it
|
|
159
|
+
* dropped `format` entirely, so `'relative'` rendered the absolute face.
|
|
160
|
+
*
|
|
161
|
+
* Every other string falls to that arm's default face — a PATTERN like
|
|
162
|
+
* `format: 'YYYY-MM-DD'` is accepted by the schema, reaches this function, and
|
|
163
|
+
* renders the locale default, and so does `'compact'`, which is
|
|
164
|
+
* `formatDateTime`'s own vocabulary but not this path's. The pattern part is
|
|
165
|
+
* not new behaviour introduced here: `plugin-dashboard`'s `recordFields`
|
|
166
|
+
* already routes a date-shaped `format` into the same style slot and gets the
|
|
167
|
+
* same locale default. Closing it would mean teaching the shared path a
|
|
168
|
+
* pattern grammar, which is a change to the path itself and to every list cell
|
|
169
|
+
* that reads it — not a measure concern, and not either card.
|
|
170
|
+
*
|
|
171
|
+
* ⚠️ `'relative'` renders the ABSOLUTE face for a value more than ±7 days from
|
|
172
|
+
* today, on both arms, by `formatRelativeDate`'s own design. That is correct
|
|
173
|
+
* output, not a regression — but it also means an out-of-window value is
|
|
174
|
+
* useless for testing this path, because it renders identically whether
|
|
175
|
+
* `format` is honoured or dropped. Both readings that missed #8352 were taken
|
|
176
|
+
* on 40–60-day-old data.
|
|
123
177
|
*/
|
|
124
178
|
export declare function formatMeasure(v: unknown, format?: string, currency?: string, percentScale?: PercentScale, locale?: string): string;
|
|
125
179
|
/**
|
|
@@ -140,13 +194,31 @@ export declare function formatDimensionValue(v: unknown, locale?: string): strin
|
|
|
140
194
|
*
|
|
141
195
|
* `fieldLabel` is injected (rather than imported) so this stays React/i18n-free;
|
|
142
196
|
* callers pass `useSafeFieldLabel().fieldLabel`.
|
|
197
|
+
*
|
|
198
|
+
* ## `builtinAggregateLabels` (objectui#7534, sibling of #7258)
|
|
199
|
+
*
|
|
200
|
+
* A result field the analytics service minted as a BUILT-IN default measure
|
|
201
|
+
* carries `builtinAggregate` (objectstack#14492) and a hard-coded English
|
|
202
|
+
* `label` ('Count'). {@link resolveMeasureLabel} is the ONE resolver for that —
|
|
203
|
+
* #7258 wired it into `buildChartSeries()`, so a chart legend already read
|
|
204
|
+
* `计数` on a zh console while the table underneath it, the KPI caption, the
|
|
205
|
+
* pivot header and the dataset preview all still said `Count`, because they
|
|
206
|
+
* resolve their titles through here instead.
|
|
207
|
+
*
|
|
208
|
+
* Passing the caller's resolved labels closes that gap without a second
|
|
209
|
+
* resolution order: the discriminator is consulted FIRST (exactly as on the
|
|
210
|
+
* chart seam), the existing `fieldLabel` convention still applies on top, and
|
|
211
|
+
* the raw name is still the floor. Omitting the argument reproduces the
|
|
212
|
+
* previous output byte for byte — an author-declared measure carries no
|
|
213
|
+
* discriminator and keeps its wire `label` verbatim (objectui#4106), and a
|
|
214
|
+
* provider-less host resolves no labels and so changes nothing.
|
|
143
215
|
*/
|
|
144
|
-
export declare function buildDatasetFieldHelpers(fields: DatasetResultField[] | undefined, object: string | undefined, fieldLabel?: (objectName: string, fieldName: string, fallback: string) => string): {
|
|
216
|
+
export declare function buildDatasetFieldHelpers(fields: DatasetResultField[] | undefined, object: string | undefined, fieldLabel?: (objectName: string, fieldName: string, fallback: string) => string, builtinAggregateLabels?: BuiltinAggregateLabels): {
|
|
145
217
|
measureField: (name: string) => DatasetResultField | undefined;
|
|
146
218
|
headerLabel: (name: string) => string;
|
|
147
219
|
};
|
|
148
220
|
/**
|
|
149
|
-
* A half-open date-range drill scope for one time-bucketed dimension (#1752):
|
|
221
|
+
* A half-open date-range drill scope for one time-bucketed dimension (objectstack-ai/objectstack#1752):
|
|
150
222
|
* the object FIELD to filter and its inclusive `gte` / exclusive `lt` bounds
|
|
151
223
|
* (the server's `drillRanges` sidecar entry).
|
|
152
224
|
*/
|
|
@@ -161,18 +233,127 @@ export interface DatasetDrillRange {
|
|
|
161
233
|
* Each drillable dimension maps to its underlying object field, filtered by the
|
|
162
234
|
* dimension's RAW grouped value (from the server's parallel `drillRawRows`, NOT
|
|
163
235
|
* the visible row which carries the display LABEL — a select/lookup label would
|
|
164
|
-
* mis-filter).
|
|
165
|
-
*
|
|
166
|
-
*
|
|
236
|
+
* mis-filter). The render-time `runtimeFilter` is ANDed in so the drilled list
|
|
237
|
+
* stays within the same slice the aggregate was computed over.
|
|
238
|
+
*
|
|
239
|
+
* ## What the EMPTY bucket means, and why it is spelled `{ $null: true }`
|
|
167
240
|
*
|
|
168
|
-
* A
|
|
241
|
+
* A drill must return exactly the rows the clicked bucket COUNTED. The empty
|
|
242
|
+
* bucket is the one the aggregate grouped rows with NO VALUE for that dimension
|
|
243
|
+
* into, so the drill means "this dimension has no value" — NOT "this dimension
|
|
244
|
+
* holds the literal value null". Those are different row sets, and the
|
|
245
|
+
* difference is measurable rather than theoretical: against `ValueDataSource`'s
|
|
246
|
+
* matcher over rows `{owner:'alice'}` / `{owner:null}` / `{}` (no `owner` key),
|
|
247
|
+
* `['owner','is_null',true]` selects the explicit-null row AND the missing-key
|
|
248
|
+
* row, while an equality test against `null` selects only the explicit-null one
|
|
249
|
+
* and drops the row that has no `owner` at all. The bucket counted both, so
|
|
250
|
+
* is-null is the one that agrees with the number the user clicked.
|
|
251
|
+
*
|
|
252
|
+
* ⇒ every empty authoring — `''`, `null` and `undefined` alike — becomes ONE
|
|
253
|
+
* spelling, `{ [field]: { $null: true } }`, which `convertFiltersToAST` lowers
|
|
254
|
+
* to `[field, 'is_null', true]`. The three are not told apart because the
|
|
255
|
+
* server already merged them into the single bucket the user clicked; there is
|
|
256
|
+
* no second bucket here for a second spelling to address.
|
|
257
|
+
*
|
|
258
|
+
* ⚠️ This value used to be a bare `null`, and that is the defect objectui#9085
|
|
259
|
+
* records: `convertFiltersToAST`'s OLDEST pinned behaviour is to SKIP a key
|
|
260
|
+
* whose value is `null` / `undefined` (pinned as "should skip null and
|
|
261
|
+
* undefined values"), so the constraint was dropped on the way to the wire and
|
|
262
|
+
* the empty-bucket drill answered with a SUPERSET — every row, silently. The
|
|
263
|
+
* repair is here at the PRODUCER and ⛔ not at that converter: the skip is
|
|
264
|
+
* relied on by other producers, and changing it would move every caller's
|
|
265
|
+
* meaning at once (objectui#9020 ruled it stays). `$null` is not a new
|
|
266
|
+
* vocabulary either — it is a declared spec operator this dialect already
|
|
267
|
+
* lowers, and the one spelling that says the same thing on BOTH of
|
|
268
|
+
* `ValueDataSource`'s routes, where a bare `null` said "only an explicit null"
|
|
269
|
+
* on the plain-object route and "no constraint at all" on the AST route.
|
|
270
|
+
*
|
|
271
|
+
* ⚠️ Known BOUNDARY, unchanged by this and deliberately not widened: the drill
|
|
272
|
+
* "escape hatch" (the host's `openRecordList`, which serializes a drill filter
|
|
273
|
+
* into `filter[...]` URL params) has NO spelling for is-null — its operator
|
|
274
|
+
* vocabulary is equality plus four range bounds — so it drops this condition
|
|
275
|
+
* exactly as it already dropped the bare `null`, byte-for-byte the same query
|
|
276
|
+
* string. That surface is a superset today and stays one; closing it needs a
|
|
277
|
+
* URL-dialect operator on both the write and the read side, which is its own
|
|
278
|
+
* card.
|
|
279
|
+
*
|
|
280
|
+
* A time-bucketed date dimension (objectstack-ai/objectstack#1752) drills by RANGE, not equality — a
|
|
169
281
|
* humanized bucket ("2026-Q2") can't be exact-matched, so the server sends a
|
|
170
282
|
* half-open `[gte, lt)` per date dim in `rawRanges` instead of a raw value.
|
|
171
283
|
* Each becomes an ObjectQL range operator object (`{ $gte, $lt }`) so the drill
|
|
172
284
|
* scopes the list to the clicked time bucket instead of every bucket (which the
|
|
173
285
|
* old date-dim skip degraded to — a superset).
|
|
174
286
|
*
|
|
287
|
+
* ## The widget filter is CONJOINED, not spread (objectui#9137)
|
|
288
|
+
*
|
|
289
|
+
* ⛔ `runtimeFilter` is composed through {@link composeDrillFilter}, never by
|
|
290
|
+
* spreading it into the object literal below. Spreading is correct only for the
|
|
291
|
+
* OBJECT arm; this parameter's declared producer sends the ARRAY arm. The
|
|
292
|
+
* dashboard's `DashboardWidgetSchema.filter` docblock states it outright —
|
|
293
|
+
* "objectui passes an ObjectQL FilterNode array here, not the spec's
|
|
294
|
+
* `FilterCondition` envelope" — and `DatasetWidget`'s guard admits it
|
|
295
|
+
* (`typeof [] === 'object'` and `Object.keys(['x']).length === 1` are both
|
|
296
|
+
* true), so the array arm reached this line unfiltered. Spreading an ARRAY
|
|
297
|
+
* yields INDEX keys: `[['region','=','emea']]` became
|
|
298
|
+
* `{ '0': ['region','=','emea'] }`, which is not a condition anyone can honour.
|
|
299
|
+
*
|
|
300
|
+
* ⚠️ MEASURED, and it is NOT the silent superset the sibling sites had. An
|
|
301
|
+
* index key is not dropped on the way to the wire — every sink in circulation
|
|
302
|
+
* refuses it LOUDLY, because a bare array is not a legal equality comparand
|
|
303
|
+
* (objectui#8530 / objectui#8514): `convertFiltersToAST` THROWS
|
|
304
|
+
* `FilterOperatorError`, `driver-sql` answers `400 INVALID_FILTER`, and
|
|
305
|
+
* `ValueDataSource`'s matcher selects NOTHING. Against rows scoped
|
|
306
|
+
* `region = 'emea'`, drilling the `stage = 'won'` bucket returned `(none)`
|
|
307
|
+
* where the widget alone returned three rows and the conjunction returns the
|
|
308
|
+
* right two. So the widget's conditions were not merely lost — the drill was
|
|
309
|
+
* dead in the array arm. The failure is fail-CLOSED here, and this is the third
|
|
310
|
+
* site of the class objectui#8944 (chart) and objectui#9024 (pivot) removed.
|
|
311
|
+
*
|
|
312
|
+
* Three properties of THIS site, each confirmed rather than assumed — it is not
|
|
313
|
+
* a copy-paste of the other two:
|
|
314
|
+
*
|
|
315
|
+
* 1. The return stays a NON-optional `Record<string, unknown>`, because callers
|
|
316
|
+
* do not expect `undefined`. `composeDrillFilter` answers `undefined` when
|
|
317
|
+
* both sources are empty, so that one case is spelled `?? {}` — the same
|
|
318
|
+
* empty object this function already returned for a bucket with no dims.
|
|
319
|
+
* 2. Range operators SURVIVE. A time-bucketed dim contributes
|
|
320
|
+
* `{ $gte, $lt }`, and the composition re-spells it as two `$and` children
|
|
321
|
+
* on the same field (`[{d:{$gte}},{d:{$lt}}]`) rather than losing a bound;
|
|
322
|
+
* the selected rows are identical, and `serializeDrillFilterParams` walks
|
|
323
|
+
* `$and` (objectui#8944) so both bounds still reach `filter[d][gte|lt]`.
|
|
324
|
+
* 3. `runtimeFilter` is declared `unknown`, which is what the two consumers
|
|
325
|
+
* actually hand it — `any` from the dashboard, `Record<string, unknown>`
|
|
326
|
+
* from `DatasetReportRenderer`. Widening the PARAMETER is what admits both
|
|
327
|
+
* without a cast; ⛔ neither consumer's own type is changed by this.
|
|
328
|
+
*
|
|
329
|
+
* ⚠️ Conjunction changes one behaviour deliberately: a field named by BOTH the
|
|
330
|
+
* widget filter and the clicked bucket used to be overwritten by the bucket,
|
|
331
|
+
* and now both conditions apply. That is the point — a drill may narrow the
|
|
332
|
+
* widget's scope and may never widen it — and it is the posture the chart and
|
|
333
|
+
* pivot sites already ship.
|
|
334
|
+
*
|
|
335
|
+
* ## ⭐ Only the COMPOSING leg moves; the identity leg is left byte-identical
|
|
336
|
+
*
|
|
337
|
+
* The sink is asked only when there IS a second source. That is this site's
|
|
338
|
+
* one departure from `ObjectChart` / `ObjectPivotTable`, which call it
|
|
339
|
+
* unconditionally, and it is deliberate rather than a shortcut: the spread
|
|
340
|
+
* this replaces only ever ran when `runtimeFilter` was present, so the
|
|
341
|
+
* no-widget-filter path carries no defect to repair.
|
|
342
|
+
*
|
|
343
|
+
* ⚠️ Routing it anyway was MEASURED and rejected. `composeDrillFilter`'s own
|
|
344
|
+
* note — "a lone surviving source lowers back to exactly the flat object the
|
|
345
|
+
* spread produced" — holds only for a source of ONE condition; with two it
|
|
346
|
+
* lowers to `$and`, because `convertFiltersToAST` emits one node per condition
|
|
347
|
+
* and `parseFilterAST` cannot fold two nodes back onto one key. A `{ $gte,
|
|
348
|
+
* $lt }` range is two conditions on its own, so even a single date bucket
|
|
349
|
+
* re-spells. Sending the identity leg through the sink therefore re-shaped
|
|
350
|
+
* every multi-condition dataset drill — including the controls objectui#9085,
|
|
351
|
+
* objectui#4056, objectstack#5473 and objectstack-ai/objectstack#1752 pin on paths this card does not
|
|
352
|
+
* touch — for ZERO change in the rows selected or the `filter[...]` params
|
|
353
|
+
* emitted. ⛔ Do not "simplify" this to an unconditional call without redoing
|
|
354
|
+
* that measurement.
|
|
355
|
+
*
|
|
175
356
|
* Shared by the dashboard `DatasetWidget` and the report renderer so a drill
|
|
176
357
|
* filters identically (and correctly, including lookups) on both surfaces.
|
|
177
358
|
*/
|
|
178
|
-
export declare function buildDatasetDrillFilter(rawRow: Record<string, unknown> | undefined, drillDims: string[], dimensionFields: Record<string, string>, runtimeFilter?:
|
|
359
|
+
export declare function buildDatasetDrillFilter(rawRow: Record<string, unknown> | undefined, drillDims: string[], dimensionFields: Record<string, string>, runtimeFilter?: unknown, rawRanges?: Record<string, DatasetDrillRange>): Record<string, unknown>;
|