@platforma-sdk/model 1.24.11 → 1.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/PlDataTable.d.ts +4 -4
- package/dist/components/PlDataTable.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1004 -660
- package/dist/index.mjs.map +1 -1
- package/dist/render/api.d.ts +65 -21
- package/dist/render/api.d.ts.map +1 -1
- package/dist/render/internal.d.ts +4 -3
- package/dist/render/internal.d.ts.map +1 -1
- package/dist/render/split_selectors.d.ts +14 -0
- package/dist/render/split_selectors.d.ts.map +1 -0
- package/dist/render/util/axis_filtering.d.ts +14 -0
- package/dist/render/util/axis_filtering.d.ts.map +1 -0
- package/dist/render/util/index.d.ts +2 -1
- package/dist/render/util/index.d.ts.map +1 -1
- package/dist/render/util/label.d.ts +7 -1
- package/dist/render/util/label.d.ts.map +1 -1
- package/dist/render/util/{resource_map.d.ts → pcolumn_data.d.ts} +12 -2
- package/dist/render/util/pcolumn_data.d.ts.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/components/PlDataTable.ts +6 -5
- package/src/render/api.ts +425 -126
- package/src/render/internal.ts +4 -2
- package/src/render/split_selectors.ts +15 -0
- package/src/render/util/axis_filtering.ts +120 -0
- package/src/render/util/index.ts +2 -1
- package/src/render/util/label.ts +31 -3
- package/src/render/util/pcolumn_data.ts +387 -0
- package/dist/render/util/resource_map.d.ts.map +0 -1
- package/src/render/util/resource_map.ts +0 -208
package/src/render/api.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
APColumnSelector,
|
|
3
2
|
AxisId,
|
|
4
|
-
CanonicalPColumnId,
|
|
5
3
|
Option,
|
|
6
4
|
PColumn,
|
|
7
5
|
PColumnSelector,
|
|
@@ -19,11 +17,22 @@ import type {
|
|
|
19
17
|
PlRef,
|
|
20
18
|
ResultCollection,
|
|
21
19
|
ValueOrError,
|
|
20
|
+
AxisFilter,
|
|
21
|
+
PValue,
|
|
22
|
+
SUniversalPColumnId,
|
|
22
23
|
AnyFunction,
|
|
23
|
-
|
|
24
|
+
DataInfo,
|
|
25
|
+
BinaryPartitionedDataInfoEntries,
|
|
26
|
+
JsonPartitionedDataInfoEntries,
|
|
27
|
+
PObjectId } from '@milaboratories/pl-model-common';
|
|
24
28
|
import {
|
|
25
|
-
|
|
29
|
+
AnchoredIdDeriver,
|
|
30
|
+
getAxisId,
|
|
31
|
+
isDataInfo,
|
|
32
|
+
mapDataInfo,
|
|
26
33
|
resolveAnchors,
|
|
34
|
+
canonicalizeAxisId,
|
|
35
|
+
entriesToDataInfo,
|
|
27
36
|
} from '@milaboratories/pl-model-common';
|
|
28
37
|
import {
|
|
29
38
|
ensurePColumn,
|
|
@@ -40,10 +49,98 @@ import type { Optional } from 'utility-types';
|
|
|
40
49
|
import { getCfgRenderCtx } from '../internal';
|
|
41
50
|
import { TreeNodeAccessor, ifDef } from './accessor';
|
|
42
51
|
import type { FutureRef } from './future';
|
|
43
|
-
import type { GlobalCfgRenderCtx } from './internal';
|
|
52
|
+
import type { AccessorHandle, GlobalCfgRenderCtx } from './internal';
|
|
44
53
|
import { MainAccessorName, StagingAccessorName } from './internal';
|
|
45
54
|
import type { LabelDerivationOps } from './util/label';
|
|
46
55
|
import { deriveLabels } from './util/label';
|
|
56
|
+
import type { APColumnSelectorWithSplit } from './split_selectors';
|
|
57
|
+
import { getUniquePartitionKeys, parsePColumnData } from './util/pcolumn_data';
|
|
58
|
+
import type { TraceEntry } from './util/label';
|
|
59
|
+
import { filterDataInfoEntries } from './util/axis_filtering';
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Helper function to match domain objects
|
|
63
|
+
* @param query Optional domain to match against
|
|
64
|
+
* @param target Optional domain to match
|
|
65
|
+
* @returns true if domains match, false otherwise
|
|
66
|
+
*/
|
|
67
|
+
function matchDomain(query?: Record<string, string>, target?: Record<string, string>) {
|
|
68
|
+
if (query === undefined) return target === undefined;
|
|
69
|
+
if (target === undefined) return true;
|
|
70
|
+
for (const k in target) {
|
|
71
|
+
if (query[k] !== target[k]) return false;
|
|
72
|
+
}
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export type UniversalColumnOption = { label: string; value: SUniversalPColumnId };
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Transforms PColumn data into the internal representation expected by the platform
|
|
80
|
+
* @param data Data from a PColumn to transform
|
|
81
|
+
* @returns Transformed data compatible with platform API
|
|
82
|
+
*/
|
|
83
|
+
function transformPColumnData(data: PColumn<TreeNodeAccessor | PColumnValues | DataInfo<TreeNodeAccessor>>):
|
|
84
|
+
PColumn<PColumnValues | AccessorHandle | DataInfo<AccessorHandle>> {
|
|
85
|
+
return mapPObjectData(data, (d) => {
|
|
86
|
+
if (d instanceof TreeNodeAccessor) {
|
|
87
|
+
return d.handle;
|
|
88
|
+
} else if (isDataInfo(d)) {
|
|
89
|
+
return mapDataInfo(d, (accessor) => accessor.handle);
|
|
90
|
+
} else {
|
|
91
|
+
return d;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Describes a single filter applied due to a split axis.
|
|
98
|
+
*/
|
|
99
|
+
export type AxisFilterInfo = {
|
|
100
|
+
axisIdx: number;
|
|
101
|
+
axisId: AxisId;
|
|
102
|
+
value: PValue;
|
|
103
|
+
label: string;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Represents a column specification with potential split axis filtering information
|
|
108
|
+
* used in canonical options generation.
|
|
109
|
+
*/
|
|
110
|
+
export type UniversalPColumnEntry = {
|
|
111
|
+
id: SUniversalPColumnId;
|
|
112
|
+
obj: PColumnSpec;
|
|
113
|
+
ref: PlRef;
|
|
114
|
+
axisFilters?: AxisFilterInfo[];
|
|
115
|
+
label: string;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Converts an array of SplitAxisFilter objects into an array of TraceEntry objects
|
|
120
|
+
* suitable for label generation.
|
|
121
|
+
*/
|
|
122
|
+
function splitFiltersToTrace(splitFilters?: AxisFilterInfo[]): TraceEntry[] | undefined {
|
|
123
|
+
if (!splitFilters) return undefined;
|
|
124
|
+
return splitFilters.map((filter) => ({
|
|
125
|
+
type: `split:${canonicalizeAxisId(filter.axisId)}`,
|
|
126
|
+
label: filter.label,
|
|
127
|
+
importance: 1_000_000, // High importance for split filters in labels
|
|
128
|
+
}));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Converts an array of SplitAxisFilter objects into an array of AxisFilter tuples
|
|
133
|
+
* suitable for deriving anchored IDs.
|
|
134
|
+
*/
|
|
135
|
+
function splitFiltersToAxisFilter(splitFilters?: AxisFilterInfo[]): AxisFilter[] | undefined {
|
|
136
|
+
if (!splitFilters) return undefined;
|
|
137
|
+
return splitFilters.map((filter) => [filter.axisIdx, filter.value]);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
type UniversalPColumnOpts = {
|
|
141
|
+
labelOps?: LabelDerivationOps;
|
|
142
|
+
dontWaitAllData?: boolean;
|
|
143
|
+
};
|
|
47
144
|
|
|
48
145
|
export class ResultPool {
|
|
49
146
|
private readonly ctx: GlobalCfgRenderCtx = getCfgRenderCtx();
|
|
@@ -55,10 +152,6 @@ export class ResultPool {
|
|
|
55
152
|
return this.ctx.calculateOptions(predicate);
|
|
56
153
|
}
|
|
57
154
|
|
|
58
|
-
// @TODO: unused, what is this for?
|
|
59
|
-
private defaultLabelFn = (spec: PObjectSpec, _ref: PlRef) =>
|
|
60
|
-
spec.annotations?.['pl7.app/label'] ?? `Unlabelled`;
|
|
61
|
-
|
|
62
155
|
public getOptions(
|
|
63
156
|
predicateOrSelector: ((spec: PObjectSpec) => boolean) | PColumnSelector | PColumnSelector[],
|
|
64
157
|
label?: ((spec: PObjectSpec, ref: PlRef) => string) | LabelDerivationOps,
|
|
@@ -79,6 +172,258 @@ export class ResultPool {
|
|
|
79
172
|
}));
|
|
80
173
|
}
|
|
81
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Internal implementation that generates UniversalPColumnEntry objects from the provided
|
|
177
|
+
* anchors and selectors.
|
|
178
|
+
*/
|
|
179
|
+
public getUniversalPColumnEntries(
|
|
180
|
+
anchorsOrCtx: AnchoredIdDeriver | Record<string, PColumnSpec | PlRef>,
|
|
181
|
+
predicateOrSelectors: ((spec: PColumnSpec) => boolean) | APColumnSelectorWithSplit | APColumnSelectorWithSplit[],
|
|
182
|
+
opts?: UniversalPColumnOpts,
|
|
183
|
+
): UniversalPColumnEntry[] | undefined {
|
|
184
|
+
// Handle PlRef objects by resolving them to PColumnSpec
|
|
185
|
+
const resolvedAnchors: Record<string, PColumnSpec> = {};
|
|
186
|
+
|
|
187
|
+
if (!(anchorsOrCtx instanceof AnchoredIdDeriver)) {
|
|
188
|
+
for (const [key, value] of Object.entries(anchorsOrCtx)) {
|
|
189
|
+
if (isPlRef(value)) {
|
|
190
|
+
const resolvedSpec = this.getPColumnSpecByRef(value);
|
|
191
|
+
if (!resolvedSpec)
|
|
192
|
+
return undefined;
|
|
193
|
+
resolvedAnchors[key] = resolvedSpec;
|
|
194
|
+
} else {
|
|
195
|
+
// It's already a PColumnSpec
|
|
196
|
+
resolvedAnchors[key] = value;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const selectorsArray = typeof predicateOrSelectors === 'function'
|
|
202
|
+
? [predicateOrSelectors]
|
|
203
|
+
: Array.isArray(predicateOrSelectors)
|
|
204
|
+
? predicateOrSelectors
|
|
205
|
+
: [predicateOrSelectors];
|
|
206
|
+
|
|
207
|
+
const anchorIdDeriver = anchorsOrCtx instanceof AnchoredIdDeriver
|
|
208
|
+
? anchorsOrCtx
|
|
209
|
+
: new AnchoredIdDeriver(resolvedAnchors);
|
|
210
|
+
|
|
211
|
+
const result: Omit<UniversalPColumnEntry, 'id' | 'label'>[] = [];
|
|
212
|
+
|
|
213
|
+
// Process each selector individually
|
|
214
|
+
for (const selector of selectorsArray) {
|
|
215
|
+
// Create predicate for this specific selector
|
|
216
|
+
const predicate = typeof selector === 'function'
|
|
217
|
+
? selector
|
|
218
|
+
: selectorsToPredicate(resolveAnchors(resolvedAnchors, selector));
|
|
219
|
+
|
|
220
|
+
// Filter specs based on this specific predicate
|
|
221
|
+
const filtered = this.getSpecs().entries.filter(({ obj: spec }) => {
|
|
222
|
+
if (!isPColumnSpec(spec)) return false;
|
|
223
|
+
return predicate(spec);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
if (filtered.length === 0)
|
|
227
|
+
continue;
|
|
228
|
+
|
|
229
|
+
// Check if this selector has any split axes
|
|
230
|
+
const splitAxisIdxs = typeof selector === 'object'
|
|
231
|
+
&& 'axes' in selector
|
|
232
|
+
&& selector.axes !== undefined
|
|
233
|
+
&& selector.partialAxesMatch === undefined
|
|
234
|
+
? selector.axes
|
|
235
|
+
.map((axis, index) => ('split' in axis && axis.split === true) ? index : -1)
|
|
236
|
+
.filter((index) => index !== -1)
|
|
237
|
+
: [];
|
|
238
|
+
splitAxisIdxs.sort((a, b) => a - b);
|
|
239
|
+
|
|
240
|
+
if (splitAxisIdxs.length > 0) { // Handle split axes
|
|
241
|
+
const maxSplitIdx = splitAxisIdxs[splitAxisIdxs.length - 1]; // Last one is max since they're sorted
|
|
242
|
+
|
|
243
|
+
for (const { ref, obj: spec } of filtered) {
|
|
244
|
+
if (!isPColumnSpec(spec)) throw new Error(`Assertion failed: expected PColumnSpec, got ${spec.kind}`);
|
|
245
|
+
|
|
246
|
+
const columnData = this.getDataByRef(ref);
|
|
247
|
+
if (!columnData) {
|
|
248
|
+
if (opts?.dontWaitAllData) continue;
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
if (!isPColumn(columnData)) throw new Error(`Assertion failed: expected PColumn, got ${columnData.spec.kind}`);
|
|
252
|
+
|
|
253
|
+
const uniqueKeys = getUniquePartitionKeys(columnData.data);
|
|
254
|
+
if (!uniqueKeys) {
|
|
255
|
+
if (opts?.dontWaitAllData) continue;
|
|
256
|
+
return undefined;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (maxSplitIdx >= uniqueKeys.length)
|
|
260
|
+
throw new Error(`Not enough partition keys for the requested split axes in column ${spec.name}`);
|
|
261
|
+
|
|
262
|
+
// Pre-fetch labels for all involved split axes
|
|
263
|
+
const axesLabels: (Record<string | number, string> | undefined)[] = splitAxisIdxs
|
|
264
|
+
.map((idx) => this.findLabels(getAxisId(spec.axesSpec[idx])));
|
|
265
|
+
|
|
266
|
+
const keyCombinations: (string | number)[][] = [];
|
|
267
|
+
const generateCombinations = (currentCombo: (string | number)[], sAxisIdx: number) => {
|
|
268
|
+
if (sAxisIdx >= splitAxisIdxs.length) {
|
|
269
|
+
keyCombinations.push([...currentCombo]);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const axisIdx = splitAxisIdxs[sAxisIdx];
|
|
273
|
+
const axisValues = uniqueKeys[axisIdx];
|
|
274
|
+
for (const val of axisValues) {
|
|
275
|
+
currentCombo.push(val);
|
|
276
|
+
generateCombinations(currentCombo, sAxisIdx + 1);
|
|
277
|
+
currentCombo.pop();
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
generateCombinations([], 0);
|
|
281
|
+
|
|
282
|
+
// Generate entries for each key combination
|
|
283
|
+
for (const keyCombo of keyCombinations) {
|
|
284
|
+
const splitFilters: AxisFilterInfo[] = keyCombo.map((value, sAxisIdx) => {
|
|
285
|
+
const axisIdx = splitAxisIdxs[sAxisIdx];
|
|
286
|
+
const axisId = getAxisId(spec.axesSpec[axisIdx]);
|
|
287
|
+
const axisLabelMap = axesLabels[sAxisIdx];
|
|
288
|
+
const label = axisLabelMap?.[value] ?? String(value);
|
|
289
|
+
return { axisIdx, axisId, value: value as PValue, label };
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
result.push({
|
|
293
|
+
obj: spec,
|
|
294
|
+
ref,
|
|
295
|
+
axisFilters: splitFilters,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
} else {
|
|
300
|
+
// No split axes, simply add each filtered item without filters
|
|
301
|
+
for (const { ref, obj: spec } of filtered) {
|
|
302
|
+
if (!isPColumnSpec(spec)) continue;
|
|
303
|
+
result.push({
|
|
304
|
+
obj: spec,
|
|
305
|
+
ref,
|
|
306
|
+
// No splitFilters needed here
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (result.length === 0)
|
|
313
|
+
return [];
|
|
314
|
+
|
|
315
|
+
const labelResults = deriveLabels(
|
|
316
|
+
result,
|
|
317
|
+
(o) => ({
|
|
318
|
+
spec: o.obj,
|
|
319
|
+
suffixTrace: splitFiltersToTrace(o.axisFilters), // Use helper function
|
|
320
|
+
}),
|
|
321
|
+
opts?.labelOps ?? {},
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
return labelResults.map((item) => ({
|
|
325
|
+
id: anchorIdDeriver.deriveS(
|
|
326
|
+
item.value.obj,
|
|
327
|
+
splitFiltersToAxisFilter(item.value.axisFilters), // Use helper function
|
|
328
|
+
),
|
|
329
|
+
obj: item.value.obj,
|
|
330
|
+
ref: item.value.ref,
|
|
331
|
+
axisFilters: item.value.axisFilters,
|
|
332
|
+
label: item.label,
|
|
333
|
+
}));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Returns columns that match the provided anchors and selectors. It applies axis filters and label derivation.
|
|
338
|
+
*
|
|
339
|
+
* @param anchorsOrCtx - Anchor context for column selection (same as in getCanonicalOptions)
|
|
340
|
+
* @param predicateOrSelectors - Predicate or selectors for filtering columns (same as in getCanonicalOptions)
|
|
341
|
+
* @param opts - Optional configuration for label generation and data waiting
|
|
342
|
+
* @returns A PFrameHandle for the created PFrame, or undefined if any required data is missing
|
|
343
|
+
*/
|
|
344
|
+
public getAnchoredPColumns(
|
|
345
|
+
anchorsOrCtx: AnchoredIdDeriver | Record<string, PColumnSpec | PlRef>,
|
|
346
|
+
predicateOrSelectors: ((spec: PColumnSpec) => boolean) | APColumnSelectorWithSplit | APColumnSelectorWithSplit[],
|
|
347
|
+
opts?: UniversalPColumnOpts,
|
|
348
|
+
): PColumn<DataInfo<TreeNodeAccessor>>[] | undefined {
|
|
349
|
+
// Ensure includeNativeLabel is true in the labelOps
|
|
350
|
+
const enhancedOpts: UniversalPColumnOpts = {
|
|
351
|
+
...opts,
|
|
352
|
+
labelOps: {
|
|
353
|
+
includeNativeLabel: true,
|
|
354
|
+
...(opts?.labelOps || {}),
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const entries = this.getUniversalPColumnEntries(
|
|
359
|
+
anchorsOrCtx,
|
|
360
|
+
predicateOrSelectors,
|
|
361
|
+
enhancedOpts,
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
if (!entries || entries.length === 0) return undefined;
|
|
365
|
+
|
|
366
|
+
const result: PColumn<DataInfo<TreeNodeAccessor>>[] = [];
|
|
367
|
+
|
|
368
|
+
for (const entry of entries) {
|
|
369
|
+
const columnData = this.getPColumnByRef(entry.ref);
|
|
370
|
+
if (!columnData) return undefined;
|
|
371
|
+
|
|
372
|
+
const parsedData = parsePColumnData(columnData.data);
|
|
373
|
+
if (!parsedData) return undefined;
|
|
374
|
+
|
|
375
|
+
let filteredEntries: JsonPartitionedDataInfoEntries<TreeNodeAccessor> | BinaryPartitionedDataInfoEntries<TreeNodeAccessor> = parsedData;
|
|
376
|
+
let spec = { ...columnData.spec };
|
|
377
|
+
|
|
378
|
+
if (entry.axisFilters && entry.axisFilters.length > 0) {
|
|
379
|
+
const axisFiltersByIdx = entry.axisFilters.map((filter) => [
|
|
380
|
+
filter.axisIdx,
|
|
381
|
+
filter.value,
|
|
382
|
+
] as [number, PValue]);
|
|
383
|
+
|
|
384
|
+
filteredEntries = filterDataInfoEntries(parsedData, axisFiltersByIdx);
|
|
385
|
+
|
|
386
|
+
const axisIndicesToRemove = [...entry.axisFilters]
|
|
387
|
+
.map((filter) => filter.axisIdx)
|
|
388
|
+
.sort((a, b) => b - a);
|
|
389
|
+
|
|
390
|
+
const newAxesSpec = [...spec.axesSpec];
|
|
391
|
+
for (const idx of axisIndicesToRemove) {
|
|
392
|
+
newAxesSpec.splice(idx, 1);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
spec = { ...spec, axesSpec: newAxesSpec };
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const dataInfo = entriesToDataInfo(filteredEntries);
|
|
399
|
+
|
|
400
|
+
if (spec.annotations) {
|
|
401
|
+
spec = {
|
|
402
|
+
...spec,
|
|
403
|
+
annotations: {
|
|
404
|
+
...spec.annotations,
|
|
405
|
+
'pl7.app/label': entry.label,
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
} else {
|
|
409
|
+
spec = {
|
|
410
|
+
...spec,
|
|
411
|
+
annotations: {
|
|
412
|
+
'pl7.app/label': entry.label,
|
|
413
|
+
},
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
result.push({
|
|
418
|
+
id: entry.id as unknown as PObjectId,
|
|
419
|
+
spec,
|
|
420
|
+
data: dataInfo,
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return result;
|
|
425
|
+
}
|
|
426
|
+
|
|
82
427
|
/**
|
|
83
428
|
* Calculates anchored identifier options for columns matching a given predicate and returns their
|
|
84
429
|
* canonicalized representations.
|
|
@@ -91,83 +436,34 @@ export class ResultPool {
|
|
|
91
436
|
* - An existing AnchorCtx instance
|
|
92
437
|
* - A record mapping anchor IDs to PColumnSpec objects
|
|
93
438
|
* - A record mapping anchor IDs to PlRef objects (which will be resolved to PColumnSpec)
|
|
94
|
-
* @param
|
|
439
|
+
* @param predicateOrSelectors - Either:
|
|
95
440
|
* - A predicate function that takes a PColumnSpec and returns a boolean.
|
|
96
441
|
* Only specs that return true will be included.
|
|
97
442
|
* - An APColumnSelector object for declarative filtering, which will be
|
|
98
443
|
* resolved against the provided anchors and matched using matchPColumn.
|
|
99
444
|
* - An array of APColumnSelector objects - columns matching ANY selector
|
|
100
445
|
* in the array will be included (OR operation).
|
|
101
|
-
* @param
|
|
102
|
-
* -
|
|
103
|
-
*
|
|
104
|
-
*
|
|
446
|
+
* @param opts - Optional configuration for label generation:
|
|
447
|
+
* - labelOps: Optional configuration for label generation:
|
|
448
|
+
* - includeNativeLabel: Whether to include native column labels
|
|
449
|
+
* - separator: String to use between label parts (defaults to " / ")
|
|
450
|
+
* - addLabelAsSuffix: Whether to add labels as suffix instead of prefix
|
|
451
|
+
* - dontWaitAllData: Whether to skip columns that don't have all data (if not set, will return undefined,
|
|
452
|
+
* if at least one column that requires splitting is missing data)
|
|
105
453
|
* @returns An array of objects with `label` (display text) and `value` (anchored ID string) properties,
|
|
106
454
|
* or undefined if any PlRef resolution fails.
|
|
107
455
|
*/
|
|
108
|
-
// Overload for AnchorCtx - guaranteed to never return undefined
|
|
109
|
-
getCanonicalOptions(
|
|
110
|
-
anchorsOrCtx: AnchorIdDeriver,
|
|
111
|
-
predicateOrSelectors: ((spec: PColumnSpec) => boolean) | APColumnSelector | APColumnSelector[],
|
|
112
|
-
labelOps?: LabelDerivationOps,
|
|
113
|
-
): { label: string; value: CanonicalPColumnId }[];
|
|
114
|
-
|
|
115
|
-
// Overload for Record<string, PColumnSpec> - guaranteed to never return undefined
|
|
116
|
-
getCanonicalOptions(
|
|
117
|
-
anchorsOrCtx: Record<string, PColumnSpec>,
|
|
118
|
-
predicateOrSelectors: ((spec: PColumnSpec) => boolean) | APColumnSelector | APColumnSelector[],
|
|
119
|
-
labelOps?: LabelDerivationOps,
|
|
120
|
-
): { label: string; value: CanonicalPColumnId }[];
|
|
121
|
-
|
|
122
|
-
// Overload for Record<string, PColumnSpec | PlRef> - may return undefined if PlRef resolution fails
|
|
123
|
-
getCanonicalOptions(
|
|
124
|
-
anchorsOrCtx: Record<string, PColumnSpec | PlRef>,
|
|
125
|
-
predicateOrSelectors: ((spec: PColumnSpec) => boolean) | APColumnSelector | APColumnSelector[],
|
|
126
|
-
labelOps?: LabelDerivationOps,
|
|
127
|
-
): { label: string; value: CanonicalPColumnId }[] | undefined;
|
|
128
|
-
|
|
129
|
-
// Implementation
|
|
130
456
|
getCanonicalOptions(
|
|
131
|
-
anchorsOrCtx:
|
|
132
|
-
predicateOrSelectors: ((spec: PColumnSpec) => boolean) |
|
|
133
|
-
|
|
134
|
-
): { label: string; value:
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const resolvedSpec = this.getPColumnSpecByRef(value);
|
|
142
|
-
if (!resolvedSpec)
|
|
143
|
-
return undefined;
|
|
144
|
-
resolvedAnchors[key] = resolvedSpec;
|
|
145
|
-
} else {
|
|
146
|
-
// It's already a PColumnSpec
|
|
147
|
-
resolvedAnchors[key] = value;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const predicate = typeof predicateOrSelectors === 'function'
|
|
153
|
-
? predicateOrSelectors
|
|
154
|
-
: selectorsToPredicate(Array.isArray(predicateOrSelectors)
|
|
155
|
-
? predicateOrSelectors.map((selector) => resolveAnchors(resolvedAnchors, selector))
|
|
156
|
-
: resolveAnchors(resolvedAnchors, predicateOrSelectors),
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
const filtered = this.getSpecs().entries.filter(({ obj: spec }) => {
|
|
160
|
-
if (!isPColumnSpec(spec)) return false;
|
|
161
|
-
return predicate(spec);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
const anchorIdDeriver = anchorsOrCtx instanceof AnchorIdDeriver
|
|
165
|
-
? anchorsOrCtx
|
|
166
|
-
: new AnchorIdDeriver(resolvedAnchors);
|
|
167
|
-
|
|
168
|
-
return deriveLabels(filtered, (o) => o.obj, labelOps ?? {}).map(({ value: { obj: spec }, label }) => ({
|
|
169
|
-
value: anchorIdDeriver.deriveCanonical(spec as PColumnSpec),
|
|
170
|
-
label,
|
|
457
|
+
anchorsOrCtx: AnchoredIdDeriver | Record<string, PColumnSpec | PlRef>,
|
|
458
|
+
predicateOrSelectors: ((spec: PColumnSpec) => boolean) | APColumnSelectorWithSplit | APColumnSelectorWithSplit[],
|
|
459
|
+
opts?: UniversalPColumnOpts,
|
|
460
|
+
): { label: string; value: SUniversalPColumnId }[] | undefined {
|
|
461
|
+
const entries = this.getUniversalPColumnEntries(anchorsOrCtx, predicateOrSelectors, opts);
|
|
462
|
+
if (!entries) return undefined;
|
|
463
|
+
// Generate final options using the entries from the helper method
|
|
464
|
+
return entries.map((item) => ({
|
|
465
|
+
value: item.id,
|
|
466
|
+
label: item.label,
|
|
171
467
|
}));
|
|
172
468
|
}
|
|
173
469
|
|
|
@@ -241,8 +537,11 @@ export class ResultPool {
|
|
|
241
537
|
return this.getData().entries.find(
|
|
242
538
|
(f) => f.ref.blockId === ref.blockId && f.ref.name === ref.name,
|
|
243
539
|
)?.obj;
|
|
540
|
+
const data = this.ctx.getDataFromResultPoolByRef(ref.blockId, ref.name); // Keep original call
|
|
541
|
+
// Need to handle undefined case before mapping
|
|
542
|
+
if (!data) return undefined;
|
|
244
543
|
return mapPObjectData(
|
|
245
|
-
|
|
544
|
+
data,
|
|
246
545
|
(handle) => new TreeNodeAccessor(handle, [ref.blockId, ref.name]),
|
|
247
546
|
);
|
|
248
547
|
}
|
|
@@ -327,15 +626,40 @@ export class ResultPool {
|
|
|
327
626
|
}
|
|
328
627
|
return result;
|
|
329
628
|
}
|
|
330
|
-
}
|
|
331
629
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
630
|
+
/**
|
|
631
|
+
* Find labels data for a given axis id. It will search for a label column and return its data as a map.
|
|
632
|
+
* @returns a map of axis value => label
|
|
633
|
+
*/
|
|
634
|
+
public findLabels(axis: AxisId): Record<string | number, string> | undefined {
|
|
635
|
+
const dataPool = this.getData();
|
|
636
|
+
for (const column of dataPool.entries) {
|
|
637
|
+
if (!isPColumn(column.obj)) continue;
|
|
638
|
+
|
|
639
|
+
const spec = column.obj.spec;
|
|
640
|
+
if (
|
|
641
|
+
spec.name === 'pl7.app/label'
|
|
642
|
+
&& spec.axesSpec.length === 1
|
|
643
|
+
&& spec.axesSpec[0].name === axis.name
|
|
644
|
+
&& spec.axesSpec[0].type === axis.type
|
|
645
|
+
&& matchDomain(axis.domain, spec.axesSpec[0].domain)
|
|
646
|
+
) {
|
|
647
|
+
if (column.obj.data.resourceType.name !== 'PColumnData/Json') {
|
|
648
|
+
throw Error(`Expected JSON column for labels, got: ${column.obj.data.resourceType.name}`);
|
|
649
|
+
}
|
|
650
|
+
const labels: Record<string | number, string> = Object.fromEntries(
|
|
651
|
+
Object.entries(
|
|
652
|
+
column.obj.data.getDataAsJson<{
|
|
653
|
+
data: Record<string | number, string>;
|
|
654
|
+
}>().data,
|
|
655
|
+
).map((e) => [JSON.parse(e[0])[0], e[1]]),
|
|
656
|
+
);
|
|
657
|
+
|
|
658
|
+
return labels;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
return undefined;
|
|
337
662
|
}
|
|
338
|
-
return true;
|
|
339
663
|
}
|
|
340
664
|
|
|
341
665
|
/** Main entry point to the API available within model lambdas (like outputs, sections, etc..) */
|
|
@@ -391,68 +715,45 @@ export class RenderCtx<Args, UiState> {
|
|
|
391
715
|
/**
|
|
392
716
|
* Find labels data for a given axis id. It will search for a label column and return its data as a map.
|
|
393
717
|
* @returns a map of axis value => label
|
|
718
|
+
* @deprecated Use resultPool.findLabels instead
|
|
394
719
|
*/
|
|
395
720
|
public findLabels(axis: AxisId): Record<string | number, string> | undefined {
|
|
396
|
-
|
|
397
|
-
for (const column of dataPool.entries) {
|
|
398
|
-
if (!isPColumn(column.obj)) continue;
|
|
399
|
-
|
|
400
|
-
const spec = column.obj.spec;
|
|
401
|
-
if (
|
|
402
|
-
spec.name === 'pl7.app/label'
|
|
403
|
-
&& spec.axesSpec.length === 1
|
|
404
|
-
&& spec.axesSpec[0].name === axis.name
|
|
405
|
-
&& spec.axesSpec[0].type === axis.type
|
|
406
|
-
&& matchDomain(axis.domain, spec.axesSpec[0].domain)
|
|
407
|
-
) {
|
|
408
|
-
if (column.obj.data.resourceType.name !== 'PColumnData/Json') {
|
|
409
|
-
throw Error(`Expected JSON column for labels, got: ${column.obj.data.resourceType.name}`);
|
|
410
|
-
}
|
|
411
|
-
const labels: Record<string | number, string> = Object.fromEntries(
|
|
412
|
-
Object.entries(
|
|
413
|
-
column.obj.data.getDataAsJson<{
|
|
414
|
-
data: Record<string | number, string>;
|
|
415
|
-
}>().data,
|
|
416
|
-
).map((e) => [JSON.parse(e[0])[0], e[1]]),
|
|
417
|
-
);
|
|
418
|
-
|
|
419
|
-
return labels;
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
return undefined;
|
|
721
|
+
return this.resultPool.findLabels(axis);
|
|
423
722
|
}
|
|
424
723
|
|
|
425
|
-
private
|
|
426
|
-
const hasInlineColumns = columns.some((c) => !(c.data instanceof TreeNodeAccessor));
|
|
724
|
+
private verifyInlineAndExplicitColumnsSupport(columns: PColumn<TreeNodeAccessor | PColumnValues | DataInfo<TreeNodeAccessor>>[]) {
|
|
725
|
+
const hasInlineColumns = columns.some((c) => !(c.data instanceof TreeNodeAccessor) || isDataInfo(c.data)); // Updated check for DataInfo
|
|
427
726
|
const inlineColumnsSupport = this.ctx.featureFlags?.inlineColumnsSupport === true;
|
|
428
|
-
if (hasInlineColumns && !inlineColumnsSupport) throw Error(`
|
|
727
|
+
if (hasInlineColumns && !inlineColumnsSupport) throw Error(`Inline or explicit columns not supported`); // Combined check
|
|
728
|
+
|
|
729
|
+
// Removed redundant explicitColumns check
|
|
429
730
|
}
|
|
430
731
|
|
|
431
|
-
public createPFrame(def: PFrameDef<TreeNodeAccessor | PColumnValues
|
|
432
|
-
this.
|
|
732
|
+
public createPFrame(def: PFrameDef<TreeNodeAccessor | PColumnValues | DataInfo<TreeNodeAccessor>>): PFrameHandle {
|
|
733
|
+
this.verifyInlineAndExplicitColumnsSupport(def);
|
|
433
734
|
return this.ctx.createPFrame(
|
|
434
|
-
def.map((c) =>
|
|
735
|
+
def.map((c) => transformPColumnData(c)),
|
|
435
736
|
);
|
|
436
737
|
}
|
|
437
738
|
|
|
438
|
-
public createPTable(def: PTableDef<PColumn<TreeNodeAccessor | PColumnValues
|
|
739
|
+
public createPTable(def: PTableDef<PColumn<TreeNodeAccessor | PColumnValues | DataInfo<TreeNodeAccessor>>>): PTableHandle;
|
|
439
740
|
public createPTable(def: {
|
|
440
|
-
columns: PColumn<TreeNodeAccessor | PColumnValues
|
|
741
|
+
columns: PColumn<TreeNodeAccessor | PColumnValues | DataInfo<TreeNodeAccessor>>[];
|
|
441
742
|
filters?: PTableRecordFilter[];
|
|
442
743
|
/** Table sorting */
|
|
443
744
|
sorting?: PTableSorting[];
|
|
444
745
|
}): PTableHandle;
|
|
445
746
|
public createPTable(
|
|
446
747
|
def:
|
|
447
|
-
| PTableDef<PColumn<TreeNodeAccessor | PColumnValues
|
|
748
|
+
| PTableDef<PColumn<TreeNodeAccessor | PColumnValues | DataInfo<TreeNodeAccessor>>>
|
|
448
749
|
| {
|
|
449
|
-
columns: PColumn<TreeNodeAccessor | PColumnValues
|
|
750
|
+
columns: PColumn<TreeNodeAccessor | PColumnValues | DataInfo<TreeNodeAccessor>>[];
|
|
450
751
|
filters?: PTableRecordFilter[];
|
|
451
752
|
/** Table sorting */
|
|
452
753
|
sorting?: PTableSorting[];
|
|
453
754
|
},
|
|
454
755
|
): PTableHandle {
|
|
455
|
-
let rawDef: PTableDef<PColumn<TreeNodeAccessor | PColumnValues
|
|
756
|
+
let rawDef: PTableDef<PColumn<TreeNodeAccessor | PColumnValues | DataInfo<TreeNodeAccessor>>>;
|
|
456
757
|
if ('columns' in def) {
|
|
457
758
|
rawDef = {
|
|
458
759
|
src: {
|
|
@@ -465,11 +766,9 @@ export class RenderCtx<Args, UiState> {
|
|
|
465
766
|
} else {
|
|
466
767
|
rawDef = def;
|
|
467
768
|
}
|
|
468
|
-
this.
|
|
769
|
+
this.verifyInlineAndExplicitColumnsSupport(extractAllColumns(rawDef.src));
|
|
469
770
|
return this.ctx.createPTable(
|
|
470
|
-
mapPTableDef(rawDef, (po) =>
|
|
471
|
-
mapPObjectData(po, (d) => (d instanceof TreeNodeAccessor ? d.handle : d)),
|
|
472
|
-
),
|
|
771
|
+
mapPTableDef(rawDef, (po) => transformPColumnData(po)),
|
|
473
772
|
);
|
|
474
773
|
}
|
|
475
774
|
|
package/src/render/internal.ts
CHANGED
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
PTableHandle,
|
|
17
17
|
ResultCollection,
|
|
18
18
|
ValueOrError,
|
|
19
|
+
DataInfo,
|
|
19
20
|
} from '@milaboratories/pl-model-common';
|
|
20
21
|
|
|
21
22
|
export const StagingAccessorName = 'staging';
|
|
@@ -140,9 +141,9 @@ export interface GlobalCfgRenderCtxMethods<AHandle = AccessorHandle, FHandle = F
|
|
|
140
141
|
// PFrame / PTable
|
|
141
142
|
//
|
|
142
143
|
|
|
143
|
-
createPFrame(def: PFrameDef<AHandle | PColumnValues
|
|
144
|
+
createPFrame(def: PFrameDef<AHandle | PColumnValues | DataInfo<AHandle>>): PFrameHandle;
|
|
144
145
|
|
|
145
|
-
createPTable(def: PTableDef<PColumn<AHandle | PColumnValues
|
|
146
|
+
createPTable(def: PTableDef<PColumn<AHandle | PColumnValues | DataInfo<AHandle>>>): PTableHandle;
|
|
146
147
|
|
|
147
148
|
//
|
|
148
149
|
// Computable
|
|
@@ -152,6 +153,7 @@ export interface GlobalCfgRenderCtxMethods<AHandle = AccessorHandle, FHandle = F
|
|
|
152
153
|
}
|
|
153
154
|
|
|
154
155
|
export const GlobalCfgRenderCtxFeatureFlags = {
|
|
156
|
+
explicitColumnsSupport: true as const,
|
|
155
157
|
inlineColumnsSupport: true as const,
|
|
156
158
|
activeArgs: true as const,
|
|
157
159
|
};
|