@platforma-sdk/model 1.25.0 → 1.27.6
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 +868 -591
- package/dist/index.mjs.map +1 -1
- package/dist/render/api.d.ts +54 -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/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 +1 -0
- package/dist/render/util/index.d.ts.map +1 -1
- package/dist/render/util/pcolumn_data.d.ts +11 -1
- package/dist/render/util/pcolumn_data.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +2 -2
- package/src/components/PlDataTable.ts +6 -5
- package/src/render/api.ts +349 -160
- package/src/render/internal.ts +4 -2
- package/src/render/util/axis_filtering.ts +120 -0
- package/src/render/util/index.ts +1 -0
- package/src/render/util/pcolumn_data.ts +191 -12
package/src/render/api.ts
CHANGED
|
@@ -20,12 +20,19 @@ import type {
|
|
|
20
20
|
AxisFilter,
|
|
21
21
|
PValue,
|
|
22
22
|
SUniversalPColumnId,
|
|
23
|
-
|
|
23
|
+
AnyFunction,
|
|
24
|
+
DataInfo,
|
|
25
|
+
BinaryPartitionedDataInfoEntries,
|
|
26
|
+
JsonPartitionedDataInfoEntries,
|
|
27
|
+
PObjectId } from '@milaboratories/pl-model-common';
|
|
24
28
|
import {
|
|
25
29
|
AnchoredIdDeriver,
|
|
26
30
|
getAxisId,
|
|
27
|
-
|
|
31
|
+
isDataInfo,
|
|
32
|
+
mapDataInfo,
|
|
28
33
|
resolveAnchors,
|
|
34
|
+
canonicalizeAxisId,
|
|
35
|
+
entriesToDataInfo,
|
|
29
36
|
} from '@milaboratories/pl-model-common';
|
|
30
37
|
import {
|
|
31
38
|
ensurePColumn,
|
|
@@ -42,14 +49,15 @@ import type { Optional } from 'utility-types';
|
|
|
42
49
|
import { getCfgRenderCtx } from '../internal';
|
|
43
50
|
import { TreeNodeAccessor, ifDef } from './accessor';
|
|
44
51
|
import type { FutureRef } from './future';
|
|
45
|
-
import type { GlobalCfgRenderCtx } from './internal';
|
|
52
|
+
import type { AccessorHandle, GlobalCfgRenderCtx } from './internal';
|
|
46
53
|
import { MainAccessorName, StagingAccessorName } from './internal';
|
|
47
54
|
import type { LabelDerivationOps } from './util/label';
|
|
48
55
|
import { deriveLabels } from './util/label';
|
|
49
56
|
import type { APColumnSelectorWithSplit } from './split_selectors';
|
|
50
|
-
import { getUniquePartitionKeys } from './util/pcolumn_data';
|
|
57
|
+
import { getUniquePartitionKeys, parsePColumnData } from './util/pcolumn_data';
|
|
51
58
|
import type { TraceEntry } from './util/label';
|
|
52
|
-
import {
|
|
59
|
+
import { filterDataInfoEntries } from './util/axis_filtering';
|
|
60
|
+
|
|
53
61
|
/**
|
|
54
62
|
* Helper function to match domain objects
|
|
55
63
|
* @param query Optional domain to match against
|
|
@@ -65,7 +73,74 @@ function matchDomain(query?: Record<string, string>, target?: Record<string, str
|
|
|
65
73
|
return true;
|
|
66
74
|
}
|
|
67
75
|
|
|
68
|
-
export type
|
|
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
|
+
};
|
|
69
144
|
|
|
70
145
|
export class ResultPool {
|
|
71
146
|
private readonly ctx: GlobalCfgRenderCtx = getCfgRenderCtx();
|
|
@@ -77,10 +152,6 @@ export class ResultPool {
|
|
|
77
152
|
return this.ctx.calculateOptions(predicate);
|
|
78
153
|
}
|
|
79
154
|
|
|
80
|
-
// @TODO: unused, what is this for?
|
|
81
|
-
private defaultLabelFn = (spec: PObjectSpec, _ref: PlRef) =>
|
|
82
|
-
spec.annotations?.['pl7.app/label'] ?? `Unlabelled`;
|
|
83
|
-
|
|
84
155
|
public getOptions(
|
|
85
156
|
predicateOrSelector: ((spec: PObjectSpec) => boolean) | PColumnSelector | PColumnSelector[],
|
|
86
157
|
label?: ((spec: PObjectSpec, ref: PlRef) => string) | LabelDerivationOps,
|
|
@@ -102,58 +173,14 @@ export class ResultPool {
|
|
|
102
173
|
}
|
|
103
174
|
|
|
104
175
|
/**
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
* This function filters column specifications from the result pool that match the provided predicate,
|
|
109
|
-
* creates a standardized AnchorCtx from the provided anchors, and generates a list of label-value
|
|
110
|
-
* pairs for UI components (like dropdowns).
|
|
111
|
-
*
|
|
112
|
-
* @param anchorsOrCtx - Either:
|
|
113
|
-
* - An existing AnchorCtx instance
|
|
114
|
-
* - A record mapping anchor IDs to PColumnSpec objects
|
|
115
|
-
* - A record mapping anchor IDs to PlRef objects (which will be resolved to PColumnSpec)
|
|
116
|
-
* @param predicateOrSelector - Either:
|
|
117
|
-
* - A predicate function that takes a PColumnSpec and returns a boolean.
|
|
118
|
-
* Only specs that return true will be included.
|
|
119
|
-
* - An APColumnSelector object for declarative filtering, which will be
|
|
120
|
-
* resolved against the provided anchors and matched using matchPColumn.
|
|
121
|
-
* - An array of APColumnSelector objects - columns matching ANY selector
|
|
122
|
-
* in the array will be included (OR operation).
|
|
123
|
-
* @param labelOps - Optional configuration for label generation:
|
|
124
|
-
* - includeNativeLabel: Whether to include native column labels
|
|
125
|
-
* - separator: String to use between label parts (defaults to " / ")
|
|
126
|
-
* - addLabelAsSuffix: Whether to add labels as suffix instead of prefix
|
|
127
|
-
* @returns An array of objects with `label` (display text) and `value` (anchored ID string) properties,
|
|
128
|
-
* or undefined if any PlRef resolution fails.
|
|
176
|
+
* Internal implementation that generates UniversalPColumnEntry objects from the provided
|
|
177
|
+
* anchors and selectors.
|
|
129
178
|
*/
|
|
130
|
-
|
|
131
|
-
getCanonicalOptions(
|
|
132
|
-
anchorsOrCtx: AnchoredIdDeriver,
|
|
133
|
-
predicateOrSelectors: ((spec: PColumnSpec) => boolean) | APColumnSelectorWithSplit | AnchoredPColumnSelector[],
|
|
134
|
-
labelOps?: LabelDerivationOps,
|
|
135
|
-
): { label: string; value: SUniversalPColumnId }[];
|
|
136
|
-
|
|
137
|
-
// Overload for Record<string, PColumnSpec> - guaranteed to never return undefined
|
|
138
|
-
getCanonicalOptions(
|
|
139
|
-
anchorsOrCtx: Record<string, PColumnSpec>,
|
|
140
|
-
predicateOrSelectors: ((spec: PColumnSpec) => boolean) | APColumnSelectorWithSplit | AnchoredPColumnSelector[],
|
|
141
|
-
labelOps?: LabelDerivationOps,
|
|
142
|
-
): { label: string; value: SUniversalPColumnId }[];
|
|
143
|
-
|
|
144
|
-
// Overload for Record<string, PColumnSpec | PlRef> - may return undefined if PlRef resolution fails
|
|
145
|
-
getCanonicalOptions(
|
|
146
|
-
anchorsOrCtx: Record<string, PColumnSpec | PlRef>,
|
|
147
|
-
predicateOrSelectors: ((spec: PColumnSpec) => boolean) | APColumnSelectorWithSplit | AnchoredPColumnSelector[],
|
|
148
|
-
labelOps?: LabelDerivationOps,
|
|
149
|
-
): { label: string; value: SUniversalPColumnId }[] | undefined;
|
|
150
|
-
|
|
151
|
-
// Implementation
|
|
152
|
-
getCanonicalOptions(
|
|
179
|
+
public getUniversalPColumnEntries(
|
|
153
180
|
anchorsOrCtx: AnchoredIdDeriver | Record<string, PColumnSpec | PlRef>,
|
|
154
|
-
predicateOrSelectors: ((spec: PColumnSpec) => boolean) | APColumnSelectorWithSplit |
|
|
155
|
-
|
|
156
|
-
):
|
|
181
|
+
predicateOrSelectors: ((spec: PColumnSpec) => boolean) | APColumnSelectorWithSplit | APColumnSelectorWithSplit[],
|
|
182
|
+
opts?: UniversalPColumnOpts,
|
|
183
|
+
): UniversalPColumnEntry[] | undefined {
|
|
157
184
|
// Handle PlRef objects by resolving them to PColumnSpec
|
|
158
185
|
const resolvedAnchors: Record<string, PColumnSpec> = {};
|
|
159
186
|
|
|
@@ -171,113 +198,272 @@ export class ResultPool {
|
|
|
171
198
|
}
|
|
172
199
|
}
|
|
173
200
|
|
|
174
|
-
const
|
|
175
|
-
? predicateOrSelectors
|
|
176
|
-
:
|
|
177
|
-
? predicateOrSelectors
|
|
178
|
-
:
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
const filtered = this.getSpecs().entries.filter(({ obj: spec }) => {
|
|
182
|
-
if (!isPColumnSpec(spec)) return false;
|
|
183
|
-
return predicate(spec);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
if (filtered.length === 0)
|
|
187
|
-
return [];
|
|
201
|
+
const selectorsArray = typeof predicateOrSelectors === 'function'
|
|
202
|
+
? [predicateOrSelectors]
|
|
203
|
+
: Array.isArray(predicateOrSelectors)
|
|
204
|
+
? predicateOrSelectors
|
|
205
|
+
: [predicateOrSelectors];
|
|
188
206
|
|
|
189
207
|
const anchorIdDeriver = anchorsOrCtx instanceof AnchoredIdDeriver
|
|
190
208
|
? anchorsOrCtx
|
|
191
209
|
: new AnchoredIdDeriver(resolvedAnchors);
|
|
192
210
|
|
|
193
|
-
const
|
|
194
|
-
&& !Array.isArray(predicateOrSelectors)
|
|
195
|
-
&& 'axes' in predicateOrSelectors
|
|
196
|
-
&& predicateOrSelectors.axes !== undefined
|
|
197
|
-
&& predicateOrSelectors.partialAxesMatch === undefined
|
|
198
|
-
? predicateOrSelectors.axes
|
|
199
|
-
.map((axis, index) => ('split' in axis && axis.split === true) ? index : -1)
|
|
200
|
-
.filter((index) => index !== -1)
|
|
201
|
-
: [];
|
|
202
|
-
splitAxisIdxs.sort((a, b) => a - b);
|
|
211
|
+
const result: Omit<UniversalPColumnEntry, 'id' | 'label'>[] = [];
|
|
203
212
|
|
|
204
|
-
|
|
205
|
-
|
|
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));
|
|
206
219
|
|
|
207
|
-
|
|
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
|
+
});
|
|
208
225
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const columnData = this.getDataByRef(ref);
|
|
213
|
-
if (!columnData || !isPColumn(columnData)) continue;
|
|
226
|
+
if (filtered.length === 0)
|
|
227
|
+
continue;
|
|
214
228
|
|
|
215
|
-
|
|
216
|
-
|
|
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}`);
|
|
217
252
|
|
|
218
|
-
|
|
219
|
-
|
|
253
|
+
const uniqueKeys = getUniquePartitionKeys(columnData.data);
|
|
254
|
+
if (!uniqueKeys) {
|
|
255
|
+
if (opts?.dontWaitAllData) continue;
|
|
256
|
+
return undefined;
|
|
257
|
+
}
|
|
220
258
|
|
|
221
|
-
|
|
222
|
-
|
|
259
|
+
if (maxSplitIdx >= uniqueKeys.length)
|
|
260
|
+
throw new Error(`Not enough partition keys for the requested split axes in column ${spec.name}`);
|
|
223
261
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
keyCombinations.push([...currentCombo]);
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
const axisIdx = splitAxisIdxs[sAxisIdx];
|
|
231
|
-
const axisValues = uniqueKeys[axisIdx];
|
|
232
|
-
for (const val of axisValues) {
|
|
233
|
-
currentCombo.push(val);
|
|
234
|
-
generateCombinations(currentCombo, sAxisIdx + 1);
|
|
235
|
-
currentCombo.pop();
|
|
236
|
-
}
|
|
237
|
-
};
|
|
238
|
-
generateCombinations([], 0);
|
|
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])));
|
|
239
265
|
|
|
240
|
-
|
|
241
|
-
const
|
|
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
|
+
}
|
|
242
272
|
const axisIdx = splitAxisIdxs[sAxisIdx];
|
|
243
|
-
const
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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;
|
|
254
303
|
result.push({
|
|
255
304
|
obj: spec,
|
|
256
305
|
ref,
|
|
257
|
-
|
|
258
|
-
filters,
|
|
306
|
+
// No splitFilters needed here
|
|
259
307
|
});
|
|
260
308
|
}
|
|
261
309
|
}
|
|
310
|
+
}
|
|
262
311
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
+
});
|
|
276
422
|
}
|
|
277
423
|
|
|
278
|
-
return
|
|
279
|
-
|
|
280
|
-
|
|
424
|
+
return result;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Calculates anchored identifier options for columns matching a given predicate and returns their
|
|
429
|
+
* canonicalized representations.
|
|
430
|
+
*
|
|
431
|
+
* This function filters column specifications from the result pool that match the provided predicate,
|
|
432
|
+
* creates a standardized AnchorCtx from the provided anchors, and generates a list of label-value
|
|
433
|
+
* pairs for UI components (like dropdowns).
|
|
434
|
+
*
|
|
435
|
+
* @param anchorsOrCtx - Either:
|
|
436
|
+
* - An existing AnchorCtx instance
|
|
437
|
+
* - A record mapping anchor IDs to PColumnSpec objects
|
|
438
|
+
* - A record mapping anchor IDs to PlRef objects (which will be resolved to PColumnSpec)
|
|
439
|
+
* @param predicateOrSelectors - Either:
|
|
440
|
+
* - A predicate function that takes a PColumnSpec and returns a boolean.
|
|
441
|
+
* Only specs that return true will be included.
|
|
442
|
+
* - An APColumnSelector object for declarative filtering, which will be
|
|
443
|
+
* resolved against the provided anchors and matched using matchPColumn.
|
|
444
|
+
* - An array of APColumnSelector objects - columns matching ANY selector
|
|
445
|
+
* in the array will be included (OR operation).
|
|
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)
|
|
453
|
+
* @returns An array of objects with `label` (display text) and `value` (anchored ID string) properties,
|
|
454
|
+
* or undefined if any PlRef resolution fails.
|
|
455
|
+
*/
|
|
456
|
+
getCanonicalOptions(
|
|
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,
|
|
281
467
|
}));
|
|
282
468
|
}
|
|
283
469
|
|
|
@@ -351,8 +537,11 @@ export class ResultPool {
|
|
|
351
537
|
return this.getData().entries.find(
|
|
352
538
|
(f) => f.ref.blockId === ref.blockId && f.ref.name === ref.name,
|
|
353
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;
|
|
354
543
|
return mapPObjectData(
|
|
355
|
-
|
|
544
|
+
data,
|
|
356
545
|
(handle) => new TreeNodeAccessor(handle, [ref.blockId, ref.name]),
|
|
357
546
|
);
|
|
358
547
|
}
|
|
@@ -532,37 +721,39 @@ export class RenderCtx<Args, UiState> {
|
|
|
532
721
|
return this.resultPool.findLabels(axis);
|
|
533
722
|
}
|
|
534
723
|
|
|
535
|
-
private
|
|
536
|
-
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
|
|
537
726
|
const inlineColumnsSupport = this.ctx.featureFlags?.inlineColumnsSupport === true;
|
|
538
|
-
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
|
|
539
730
|
}
|
|
540
731
|
|
|
541
|
-
public createPFrame(def: PFrameDef<TreeNodeAccessor | PColumnValues
|
|
542
|
-
this.
|
|
732
|
+
public createPFrame(def: PFrameDef<TreeNodeAccessor | PColumnValues | DataInfo<TreeNodeAccessor>>): PFrameHandle {
|
|
733
|
+
this.verifyInlineAndExplicitColumnsSupport(def);
|
|
543
734
|
return this.ctx.createPFrame(
|
|
544
|
-
def.map((c) =>
|
|
735
|
+
def.map((c) => transformPColumnData(c)),
|
|
545
736
|
);
|
|
546
737
|
}
|
|
547
738
|
|
|
548
|
-
public createPTable(def: PTableDef<PColumn<TreeNodeAccessor | PColumnValues
|
|
739
|
+
public createPTable(def: PTableDef<PColumn<TreeNodeAccessor | PColumnValues | DataInfo<TreeNodeAccessor>>>): PTableHandle;
|
|
549
740
|
public createPTable(def: {
|
|
550
|
-
columns: PColumn<TreeNodeAccessor | PColumnValues
|
|
741
|
+
columns: PColumn<TreeNodeAccessor | PColumnValues | DataInfo<TreeNodeAccessor>>[];
|
|
551
742
|
filters?: PTableRecordFilter[];
|
|
552
743
|
/** Table sorting */
|
|
553
744
|
sorting?: PTableSorting[];
|
|
554
745
|
}): PTableHandle;
|
|
555
746
|
public createPTable(
|
|
556
747
|
def:
|
|
557
|
-
| PTableDef<PColumn<TreeNodeAccessor | PColumnValues
|
|
748
|
+
| PTableDef<PColumn<TreeNodeAccessor | PColumnValues | DataInfo<TreeNodeAccessor>>>
|
|
558
749
|
| {
|
|
559
|
-
columns: PColumn<TreeNodeAccessor | PColumnValues
|
|
750
|
+
columns: PColumn<TreeNodeAccessor | PColumnValues | DataInfo<TreeNodeAccessor>>[];
|
|
560
751
|
filters?: PTableRecordFilter[];
|
|
561
752
|
/** Table sorting */
|
|
562
753
|
sorting?: PTableSorting[];
|
|
563
754
|
},
|
|
564
755
|
): PTableHandle {
|
|
565
|
-
let rawDef: PTableDef<PColumn<TreeNodeAccessor | PColumnValues
|
|
756
|
+
let rawDef: PTableDef<PColumn<TreeNodeAccessor | PColumnValues | DataInfo<TreeNodeAccessor>>>;
|
|
566
757
|
if ('columns' in def) {
|
|
567
758
|
rawDef = {
|
|
568
759
|
src: {
|
|
@@ -575,11 +766,9 @@ export class RenderCtx<Args, UiState> {
|
|
|
575
766
|
} else {
|
|
576
767
|
rawDef = def;
|
|
577
768
|
}
|
|
578
|
-
this.
|
|
769
|
+
this.verifyInlineAndExplicitColumnsSupport(extractAllColumns(rawDef.src));
|
|
579
770
|
return this.ctx.createPTable(
|
|
580
|
-
mapPTableDef(rawDef, (po) =>
|
|
581
|
-
mapPObjectData(po, (d) => (d instanceof TreeNodeAccessor ? d.handle : d)),
|
|
582
|
-
),
|
|
771
|
+
mapPTableDef(rawDef, (po) => transformPColumnData(po)),
|
|
583
772
|
);
|
|
584
773
|
}
|
|
585
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
|
};
|