@platforma-sdk/model 1.69.0 → 1.71.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/columns/column_collection_builder.cjs +1 -4
- package/dist/columns/column_collection_builder.cjs.map +1 -1
- package/dist/columns/column_collection_builder.d.ts +0 -2
- package/dist/columns/column_collection_builder.d.ts.map +1 -1
- package/dist/columns/column_collection_builder.js +1 -4
- package/dist/columns/column_collection_builder.js.map +1 -1
- package/dist/columns/column_snapshot_provider.cjs +26 -2
- package/dist/columns/column_snapshot_provider.cjs.map +1 -1
- package/dist/columns/column_snapshot_provider.d.ts +2 -1
- package/dist/columns/column_snapshot_provider.d.ts.map +1 -1
- package/dist/columns/column_snapshot_provider.js +25 -2
- package/dist/columns/column_snapshot_provider.js.map +1 -1
- package/dist/columns/ctx_column_sources.cjs +5 -8
- package/dist/columns/ctx_column_sources.cjs.map +1 -1
- package/dist/columns/ctx_column_sources.d.ts +4 -7
- package/dist/columns/ctx_column_sources.d.ts.map +1 -1
- package/dist/columns/ctx_column_sources.js +5 -8
- package/dist/columns/ctx_column_sources.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.cjs +41 -24
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.d.ts.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.js +42 -25
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.cjs +1 -2
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.js +1 -2
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/utils.cjs +16 -4
- package/dist/components/PlDataTable/createPlDataTable/utils.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/utils.js +16 -5
- package/dist/components/PlDataTable/createPlDataTable/utils.js.map +1 -1
- package/dist/components/PlDatasetSelector/build_dataset_options.cjs +23 -11
- package/dist/components/PlDatasetSelector/build_dataset_options.cjs.map +1 -1
- package/dist/components/PlDatasetSelector/build_dataset_options.d.ts +9 -2
- package/dist/components/PlDatasetSelector/build_dataset_options.d.ts.map +1 -1
- package/dist/components/PlDatasetSelector/build_dataset_options.js +22 -11
- package/dist/components/PlDatasetSelector/build_dataset_options.js.map +1 -1
- package/dist/components/PlDatasetSelector/dataset_selection.cjs +20 -0
- package/dist/components/PlDatasetSelector/dataset_selection.cjs.map +1 -0
- package/dist/components/PlDatasetSelector/dataset_selection.d.ts +23 -0
- package/dist/components/PlDatasetSelector/dataset_selection.d.ts.map +1 -0
- package/dist/components/PlDatasetSelector/dataset_selection.js +19 -0
- package/dist/components/PlDatasetSelector/dataset_selection.js.map +1 -0
- package/dist/components/PlDatasetSelector/enrichment_discovery.cjs +75 -0
- package/dist/components/PlDatasetSelector/enrichment_discovery.cjs.map +1 -0
- package/dist/components/PlDatasetSelector/enrichment_discovery.js +73 -0
- package/dist/components/PlDatasetSelector/enrichment_discovery.js.map +1 -0
- package/dist/components/PlDatasetSelector/index.cjs +1 -0
- package/dist/components/PlDatasetSelector/index.d.ts +1 -0
- package/dist/components/PlDatasetSelector/index.js +1 -0
- package/dist/components/index.cjs +1 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/index.cjs +3 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/labels/derive_distinct_tooltips.cjs +0 -3
- package/dist/labels/derive_distinct_tooltips.cjs.map +1 -1
- package/dist/labels/derive_distinct_tooltips.js +0 -3
- package/dist/labels/derive_distinct_tooltips.js.map +1 -1
- package/dist/package.cjs +1 -1
- package/dist/package.js +1 -1
- package/dist/render/api.cjs +3 -14
- package/dist/render/api.cjs.map +1 -1
- package/dist/render/api.d.ts.map +1 -1
- package/dist/render/api.js +4 -15
- package/dist/render/api.js.map +1 -1
- package/dist/render/index.cjs +1 -1
- package/dist/render/index.js +1 -1
- package/package.json +9 -9
- package/src/columns/column_collection_builder.ts +0 -3
- package/src/columns/column_snapshot_provider.ts +30 -14
- package/src/columns/ctx_column_sources.ts +6 -12
- package/src/components/PlDataTable/createPlDataTable/createPlDataTableV3.ts +58 -44
- package/src/components/PlDataTable/createPlDataTable/discoverColumns.ts +0 -1
- package/src/components/PlDataTable/createPlDataTable/utils.ts +25 -3
- package/src/components/PlDatasetSelector/build_dataset_options.ts +48 -17
- package/src/components/PlDatasetSelector/dataset_selection.ts +37 -0
- package/src/components/PlDatasetSelector/enrichment_discovery.ts +111 -0
- package/src/components/PlDatasetSelector/index.ts +1 -0
- package/src/labels/derive_distinct_tooltips.test.ts +6 -16
- package/src/labels/derive_distinct_tooltips.ts +0 -3
- package/src/render/api.ts +5 -17
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import type { PObjectId } from "@milaboratories/pl-model-common";
|
|
2
|
-
import { PColumn } from "@milaboratories/pl-model-common";
|
|
2
|
+
import { isDataInfo, PColumn, visitDataInfo } from "@milaboratories/pl-model-common";
|
|
3
3
|
import { TreeNodeAccessor } from "../render/accessor";
|
|
4
4
|
import type { PColumnDataUniversal } from "../render/internal";
|
|
5
5
|
import type { ColumnDataStatus, ColumnSnapshot } from "./column_snapshot";
|
|
6
6
|
|
|
7
|
-
// --- ColumnProvider ---
|
|
8
|
-
|
|
9
7
|
/**
|
|
10
8
|
* Data source interface for column enumeration.
|
|
11
9
|
*
|
|
@@ -22,8 +20,6 @@ export interface ColumnSnapshotProvider {
|
|
|
22
20
|
isColumnListComplete(): boolean;
|
|
23
21
|
}
|
|
24
22
|
|
|
25
|
-
// --- ColumnSource ---
|
|
26
|
-
|
|
27
23
|
/**
|
|
28
24
|
* Union of types that can serve as column sources for helpers and builders.
|
|
29
25
|
* Does NOT include TreeNodeAccessor — call `.toColumnSource()` on it first.
|
|
@@ -33,8 +29,6 @@ export type ColumnSource =
|
|
|
33
29
|
| ColumnSnapshot<PObjectId>[]
|
|
34
30
|
| PColumn<PColumnDataUniversal | undefined>[];
|
|
35
31
|
|
|
36
|
-
// --- ArrayColumnProvider ---
|
|
37
|
-
|
|
38
32
|
/**
|
|
39
33
|
* Simple provider wrapping an array of PColumns.
|
|
40
34
|
* Always complete, data status always 'ready'.
|
|
@@ -46,8 +40,8 @@ export class ArrayColumnProvider implements ColumnSnapshotProvider {
|
|
|
46
40
|
this.columns = columns.map((col) => ({
|
|
47
41
|
id: col.id,
|
|
48
42
|
spec: col.spec,
|
|
49
|
-
dataStatus: "ready" as const,
|
|
50
43
|
data: { get: () => col.data },
|
|
44
|
+
dataStatus: this.getStatus(col.data),
|
|
51
45
|
}));
|
|
52
46
|
}
|
|
53
47
|
|
|
@@ -58,9 +52,35 @@ export class ArrayColumnProvider implements ColumnSnapshotProvider {
|
|
|
58
52
|
isColumnListComplete(): boolean {
|
|
59
53
|
return true;
|
|
60
54
|
}
|
|
61
|
-
}
|
|
62
55
|
|
|
63
|
-
|
|
56
|
+
protected getStatus(
|
|
57
|
+
d: undefined | PColumnDataUniversal | (() => undefined | PColumnDataUniversal),
|
|
58
|
+
): ColumnDataStatus {
|
|
59
|
+
if (d == null) {
|
|
60
|
+
return "absent";
|
|
61
|
+
}
|
|
62
|
+
if (typeof d === "function") {
|
|
63
|
+
return this.getStatus(d());
|
|
64
|
+
}
|
|
65
|
+
if (d instanceof TreeNodeAccessor) {
|
|
66
|
+
if (d.getIsReadyOrError()) return "ready";
|
|
67
|
+
if (d.getIsFinal()) return "absent";
|
|
68
|
+
return "computing";
|
|
69
|
+
}
|
|
70
|
+
if (isDataInfo(d)) {
|
|
71
|
+
let ready = true;
|
|
72
|
+
let final = true;
|
|
73
|
+
visitDataInfo(d, (v) => {
|
|
74
|
+
ready &&= v.getIsReadyOrError();
|
|
75
|
+
final &&= v.getIsFinal();
|
|
76
|
+
});
|
|
77
|
+
if (ready) return "ready";
|
|
78
|
+
if (final) return "absent";
|
|
79
|
+
return "computing";
|
|
80
|
+
}
|
|
81
|
+
return "ready";
|
|
82
|
+
}
|
|
83
|
+
}
|
|
64
84
|
|
|
65
85
|
/**
|
|
66
86
|
* Provider wrapping an array of ColumnSnapshots.
|
|
@@ -78,8 +98,6 @@ export class SnapshotColumnProvider implements ColumnSnapshotProvider {
|
|
|
78
98
|
}
|
|
79
99
|
}
|
|
80
100
|
|
|
81
|
-
// --- OutputColumnProvider ---
|
|
82
|
-
|
|
83
101
|
export interface OutputColumnProviderOpts {
|
|
84
102
|
/** When true and the accessor is final, columns with no ready data get status 'absent'. */
|
|
85
103
|
allowPermanentAbsence?: boolean;
|
|
@@ -133,8 +151,6 @@ export class OutputColumnProvider implements ColumnSnapshotProvider {
|
|
|
133
151
|
}
|
|
134
152
|
}
|
|
135
153
|
|
|
136
|
-
// --- Source normalization ---
|
|
137
|
-
|
|
138
154
|
/** Checks if a value is a ColumnSnapshotProvider (duck-typing). */
|
|
139
155
|
export function isColumnSnapshotProvider(source: unknown): source is ColumnSnapshotProvider {
|
|
140
156
|
return (
|
|
@@ -9,32 +9,26 @@ import { ResourceTypeName } from "@milaboratories/pl-model-common";
|
|
|
9
9
|
import type { ValueOf } from "@milaboratories/helpers";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Collect ColumnSnapshotProviders from
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* - **prerun** — PFrame fields from prerun/staging results
|
|
17
|
-
*
|
|
18
|
-
* Returns an array of providers suitable for `ColumnCollectionBuilder.addSource()`.
|
|
12
|
+
* Collect ColumnSnapshotProviders from `outputs`, `prerun`, and
|
|
13
|
+
* `resultPool` in that order. Dedup keeps the first occurrence per
|
|
14
|
+
* `NativePObjectId`, so a block re-publishing its own columns keeps
|
|
15
|
+
* the `outputs`-rooted canonical id instead of the result-pool variant.
|
|
19
16
|
*/
|
|
20
17
|
export function collectCtxColumnSnapshotProviders(ctx: RenderCtxBase): ColumnSnapshotProvider[] {
|
|
21
18
|
const providers: ColumnSnapshotProvider[] = [];
|
|
22
19
|
|
|
23
|
-
// ResultPool — all upstream columns
|
|
24
|
-
providers.push(new ResultPoolColumnSnapshotProvider(ctx.resultPool));
|
|
25
|
-
|
|
26
|
-
// Outputs — each PFrame-like output field becomes a provider
|
|
27
20
|
const outputs = ctx.outputs;
|
|
28
21
|
if (outputs) {
|
|
29
22
|
providers.push(...collectPFrameProviders(outputs));
|
|
30
23
|
}
|
|
31
24
|
|
|
32
|
-
// Prerun — same treatment as outputs
|
|
33
25
|
const prerun = ctx.prerun;
|
|
34
26
|
if (prerun) {
|
|
35
27
|
providers.push(...collectPFrameProviders(prerun));
|
|
36
28
|
}
|
|
37
29
|
|
|
30
|
+
providers.push(new ResultPoolColumnSnapshotProvider(ctx.resultPool));
|
|
31
|
+
|
|
38
32
|
return providers;
|
|
39
33
|
}
|
|
40
34
|
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
withLabelAnnotations,
|
|
33
33
|
withTableVisualAnnotations,
|
|
34
34
|
withInfoAnnotations,
|
|
35
|
+
withDataStatusAnnotations,
|
|
35
36
|
} from "./utils";
|
|
36
37
|
import type { PrimaryEntry, SecondaryGroup } from "./createPTableDefV3";
|
|
37
38
|
import { createPTableDefV3 } from "./createPTableDefV3";
|
|
@@ -79,8 +80,6 @@ export type ColumnVisibilityRule = {
|
|
|
79
80
|
|
|
80
81
|
export type ColumnMatcher = (spec: PColumnSpec) => boolean;
|
|
81
82
|
|
|
82
|
-
// Main Function
|
|
83
|
-
|
|
84
83
|
export function createPlDataTableV3<A, U>(
|
|
85
84
|
ctx: RenderCtxBase<A, U>,
|
|
86
85
|
options: createPlDataTableOptionsV3,
|
|
@@ -138,17 +137,18 @@ export function createPlDataTableV3<A, U>(
|
|
|
138
137
|
]);
|
|
139
138
|
|
|
140
139
|
const remapedDefaultFilters = remapFilterColumnIds(options.filters, discovered);
|
|
141
|
-
const filters =
|
|
142
|
-
|
|
143
|
-
|
|
140
|
+
const filters = filterFilters(
|
|
141
|
+
concatFilters(
|
|
142
|
+
state.pTableParams.filters,
|
|
143
|
+
state.pTableParams.defaultFilters ?? remapedDefaultFilters,
|
|
144
|
+
),
|
|
145
|
+
columnIsAvailable,
|
|
144
146
|
);
|
|
145
|
-
validateFilters(filters, columnIsAvailable);
|
|
146
147
|
|
|
147
|
-
const sorting =
|
|
148
|
-
state.pTableParams.sorting,
|
|
149
|
-
|
|
148
|
+
const sorting = filterSorting(
|
|
149
|
+
resolveSorting(state.pTableParams.sorting, remapSortingColumnIds(options.sorting, discovered)),
|
|
150
|
+
columnIsAvailable,
|
|
150
151
|
);
|
|
151
|
-
validateSorting(sorting, columnIsAvailable);
|
|
152
152
|
|
|
153
153
|
const primaryEntries: PrimaryEntry<undefined | PColumnDataUniversal>[] = primarySnapshots.map(
|
|
154
154
|
(v) => ({ column: resolveSnapshot(v.column) }),
|
|
@@ -274,6 +274,7 @@ function annotateColumnGroups(params: {
|
|
|
274
274
|
const directAnnotated = liftToVariantColumns(
|
|
275
275
|
direct,
|
|
276
276
|
flow(
|
|
277
|
+
(cols) => withDataStatusAnnotations(cols),
|
|
277
278
|
(cols) => withLabelAnnotations(derivedLabels, cols),
|
|
278
279
|
(cols) => withInfoAnnotations(derivedTooltips, cols),
|
|
279
280
|
(cols) => withTableVisualAnnotations(visibilityByColId, orderByColId, cols),
|
|
@@ -283,6 +284,7 @@ function annotateColumnGroups(params: {
|
|
|
283
284
|
const linkedAnnotated = liftToVariantColumns(
|
|
284
285
|
linked,
|
|
285
286
|
flow(
|
|
287
|
+
(cols) => withDataStatusAnnotations(cols),
|
|
286
288
|
(cols) => withHidenAxesAnnotations(cols),
|
|
287
289
|
(cols) => withLabelAnnotations(derivedLabels, cols),
|
|
288
290
|
(cols) => withInfoAnnotations(derivedTooltips, cols),
|
|
@@ -344,19 +346,35 @@ function createColumnValidationById(
|
|
|
344
346
|
};
|
|
345
347
|
}
|
|
346
348
|
|
|
347
|
-
/**
|
|
348
|
-
function
|
|
349
|
+
/** Drop filter leaves whose column references are not available in the table. */
|
|
350
|
+
function filterFilters(
|
|
349
351
|
filters: Nil | PlDataTableFilters,
|
|
350
352
|
isValidColumnId: (id: string) => boolean,
|
|
351
|
-
):
|
|
352
|
-
if (filters
|
|
353
|
-
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
}
|
|
353
|
+
): Nil | PlDataTableFilters {
|
|
354
|
+
if (isNil(filters)) return filters;
|
|
355
|
+
|
|
356
|
+
const isLeafValid = (leaf: PlDataTableFilterSpecLeaf): boolean => {
|
|
357
|
+
if (leaf.type === undefined) return true;
|
|
358
|
+
if ("column" in leaf && !isValidColumnId(leaf.column)) return false;
|
|
359
|
+
if ("rhs" in leaf && !isValidColumnId(leaf.rhs)) return false;
|
|
360
|
+
return true;
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const prune = (node: PlDataTableFilterNode): Nil | PlDataTableFilterNode => {
|
|
364
|
+
if (node.type === "and" || node.type === "or") {
|
|
365
|
+
const kept = node.filters
|
|
366
|
+
.map((f) => prune(f))
|
|
367
|
+
.filter((f): f is PlDataTableFilterNode => !isNil(f));
|
|
368
|
+
return { type: node.type, filters: kept };
|
|
369
|
+
}
|
|
370
|
+
if (node.type === "not") {
|
|
371
|
+
const inner = prune(node.filter);
|
|
372
|
+
return isNil(inner) ? undefined : { type: "not", filter: inner };
|
|
373
|
+
}
|
|
374
|
+
return isLeafValid(node) ? node : undefined;
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
return prune(filters) as Nil | PlDataTableFilters;
|
|
360
378
|
}
|
|
361
379
|
|
|
362
380
|
/** Merge two filter trees into one AND-combined tree. Returns the non-nil one if the other is nil. */
|
|
@@ -377,16 +395,12 @@ function resolveSorting(
|
|
|
377
395
|
return (isEmpty(userSorting) ? defaultSorting : userSorting) ?? [];
|
|
378
396
|
}
|
|
379
397
|
|
|
380
|
-
/**
|
|
381
|
-
function
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
throw new Error(
|
|
387
|
-
`Invalid sorting column ${JSON.stringify(firstInvalid.column)}: column reference does not match the table columns`,
|
|
388
|
-
);
|
|
389
|
-
}
|
|
398
|
+
/** Drop sorting entries whose column is not available in the table. */
|
|
399
|
+
function filterSorting(
|
|
400
|
+
sorting: PTableSorting[],
|
|
401
|
+
isValidColumnId: (id: string) => boolean,
|
|
402
|
+
): PTableSorting[] {
|
|
403
|
+
return sorting.filter((s) => isValidColumnId(canonicalizeJson<PTableColumnId>(s.column)));
|
|
390
404
|
}
|
|
391
405
|
|
|
392
406
|
function buildSecondaryGroups(
|
|
@@ -405,7 +419,6 @@ function buildSecondaryGroups(
|
|
|
405
419
|
entries: [
|
|
406
420
|
...lc.path.map((s) => ({
|
|
407
421
|
column: resolveSnapshot(s.linker),
|
|
408
|
-
qualifications: s.qualifications,
|
|
409
422
|
})),
|
|
410
423
|
{ column: resolveSnapshot(lc.column), qualifications: lc.qualifications.forHit },
|
|
411
424
|
],
|
|
@@ -474,21 +487,22 @@ function remapSortingColumnIds(
|
|
|
474
487
|
sorting: Nil | PTableSorting[],
|
|
475
488
|
columns: TableColumnVariant[],
|
|
476
489
|
): Nil | PTableSorting[] {
|
|
477
|
-
return sorting?.
|
|
478
|
-
if (s.column.type === "axis") return s; // Axis references are unaffected by column ID remapping
|
|
490
|
+
return sorting?.flatMap((s) => {
|
|
491
|
+
if (s.column.type === "axis") return [s]; // Axis references are unaffected by column ID remapping
|
|
479
492
|
|
|
480
493
|
const id = s.column.id;
|
|
481
|
-
const column =
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
494
|
+
const column = columns.find((c) => (c.originalId ?? c.column.id) === id);
|
|
495
|
+
if (column === undefined) return [];
|
|
496
|
+
|
|
497
|
+
return [
|
|
498
|
+
{
|
|
499
|
+
...s,
|
|
500
|
+
column: {
|
|
501
|
+
type: "column" as const,
|
|
502
|
+
id: column.column.id,
|
|
503
|
+
},
|
|
490
504
|
},
|
|
491
|
-
|
|
505
|
+
];
|
|
492
506
|
});
|
|
493
507
|
}
|
|
494
508
|
|
|
@@ -102,7 +102,6 @@ function mapToTableColumnVariants(
|
|
|
102
102
|
path: variant.path.map((p) => ({
|
|
103
103
|
type: "linker",
|
|
104
104
|
column: p.linker.id,
|
|
105
|
-
qualifications: p.qualifications,
|
|
106
105
|
})),
|
|
107
106
|
columnQualifications: variant.qualifications.forHit,
|
|
108
107
|
queriesQualifications: variant.qualifications.forQueries,
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
deriveDistinctTooltips,
|
|
18
18
|
type TooltipEntry,
|
|
19
19
|
} from "../../../labels/derive_distinct_tooltips";
|
|
20
|
-
import type { MatchQualifications, MatchVariant } from "../../../columns";
|
|
20
|
+
import type { ColumnDataStatus, MatchQualifications, MatchVariant } from "../../../columns";
|
|
21
21
|
import type { ColumnMatcher, ColumnOrderRule, ColumnVisibilityRule } from "./createPlDataTableV3";
|
|
22
22
|
import type { ColumnSelector } from "../../../columns";
|
|
23
23
|
import { ArrayColumnProvider, ColumnCollectionBuilder } from "../../../columns";
|
|
@@ -130,7 +130,10 @@ function dedupeById(columns: RuleColumn[]): RuleColumn[] {
|
|
|
130
130
|
* For each axis in column specs: writes derived axis label into AxisSpec annotations.
|
|
131
131
|
*/
|
|
132
132
|
export function withLabelAnnotations<
|
|
133
|
-
T extends {
|
|
133
|
+
T extends {
|
|
134
|
+
readonly id: PObjectId;
|
|
135
|
+
readonly spec: PColumnSpec;
|
|
136
|
+
},
|
|
134
137
|
>(derivedLabels: undefined | Record<string, string>, columns: T[]): T[] {
|
|
135
138
|
if (derivedLabels === undefined) return columns;
|
|
136
139
|
return columns.map((col) => {
|
|
@@ -153,6 +156,26 @@ export function withLabelAnnotations<
|
|
|
153
156
|
});
|
|
154
157
|
}
|
|
155
158
|
|
|
159
|
+
export function withDataStatusAnnotations<
|
|
160
|
+
T extends {
|
|
161
|
+
readonly spec: PColumnSpec;
|
|
162
|
+
readonly dataStatus: ColumnDataStatus;
|
|
163
|
+
},
|
|
164
|
+
>(columns: T[]): T[] {
|
|
165
|
+
return columns.map((col) => {
|
|
166
|
+
return {
|
|
167
|
+
...col,
|
|
168
|
+
spec: {
|
|
169
|
+
...col.spec,
|
|
170
|
+
annotations: {
|
|
171
|
+
...col.spec.annotations,
|
|
172
|
+
[Annotation.DataStatus]: col.dataStatus,
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
} as T;
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
156
179
|
/**
|
|
157
180
|
* Writes effective display properties (OrderPriority, Visibility) from precomputed rule maps
|
|
158
181
|
* into column annotations. Returns new column objects — originals are not mutated.
|
|
@@ -241,7 +264,6 @@ export function deriveAllLabels(options: {
|
|
|
241
264
|
spec: c.spec,
|
|
242
265
|
linkerPath: c.linkerPath?.map((step) => ({
|
|
243
266
|
spec: step.linker.spec,
|
|
244
|
-
qualifications: step.qualifications,
|
|
245
267
|
})),
|
|
246
268
|
qualifications: c.qualifications,
|
|
247
269
|
}));
|
|
@@ -1,20 +1,25 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
Option,
|
|
4
|
-
PColumnSelector,
|
|
5
|
-
PObjectSpec,
|
|
6
|
-
} from "@milaboratories/pl-model-common";
|
|
1
|
+
import type { MultiColumnSelector, Option, PObjectSpec } from "@milaboratories/pl-model-common";
|
|
2
|
+
import { multiColumnSelectorsToPredicate } from "@milaboratories/pl-model-common";
|
|
7
3
|
import type { DeriveLabelsOptions } from "../../labels/derive_distinct_labels";
|
|
8
4
|
import type { RenderCtxBase } from "../../render";
|
|
9
5
|
import { ColumnCollectionBuilder } from "../../columns/column_collection_builder";
|
|
10
6
|
import { collectCtxColumnSnapshotProviders } from "../../columns/ctx_column_sources";
|
|
7
|
+
import type { DatasetOption } from "./dataset_selection";
|
|
11
8
|
import { buildRefMap, filterMatchesToOptions, findFilterColumns } from "./filter_discovery";
|
|
9
|
+
import { enrichmentVariantsToRefs, findEnrichmentColumns } from "./enrichment_discovery";
|
|
12
10
|
|
|
13
11
|
export type BuildDatasetOptions = {
|
|
14
12
|
/** Which result pool columns qualify as datasets. Defaults to all. */
|
|
15
|
-
|
|
13
|
+
primary?: MultiColumnSelector | MultiColumnSelector[] | ((spec: PObjectSpec) => boolean);
|
|
16
14
|
/** Formatting options for filter labels. */
|
|
17
15
|
labelOptions?: DeriveLabelsOptions;
|
|
16
|
+
/**
|
|
17
|
+
* Enables enrichment discovery and filters hits attached to
|
|
18
|
+
* `DatasetOption.enrichments`. Use `() => true` to accept all; omit to disable.
|
|
19
|
+
*/
|
|
20
|
+
withEnrichments?: MultiColumnSelector | MultiColumnSelector[] | ((spec: PObjectSpec) => boolean);
|
|
21
|
+
/** Maximum linker hops considered. Only used when `withEnrichments` is set. */
|
|
22
|
+
enrichmentMaxHops?: number;
|
|
18
23
|
};
|
|
19
24
|
|
|
20
25
|
/**
|
|
@@ -27,28 +32,54 @@ export function buildDatasetOptions(
|
|
|
27
32
|
ctx: RenderCtxBase,
|
|
28
33
|
opts?: BuildDatasetOptions,
|
|
29
34
|
): DatasetOption[] | undefined {
|
|
30
|
-
const
|
|
31
|
-
const
|
|
35
|
+
const primary = opts?.primary;
|
|
36
|
+
const primaryPredicate =
|
|
37
|
+
primary === undefined
|
|
38
|
+
? () => true
|
|
39
|
+
: typeof primary === "function"
|
|
40
|
+
? primary
|
|
41
|
+
: multiColumnSelectorsToPredicate(primary);
|
|
42
|
+
const options = ctx.resultPool.getOptions(primaryPredicate, { refsWithEnrichments: true });
|
|
32
43
|
if (options.length === 0) return [];
|
|
33
44
|
|
|
34
45
|
const columnSources = collectCtxColumnSnapshotProviders(ctx);
|
|
35
46
|
const refMap = buildRefMap(ctx.resultPool.getSpecs().entries);
|
|
36
47
|
const pframeSpec = ctx.getService("pframeSpec");
|
|
37
48
|
|
|
38
|
-
return options.map((
|
|
39
|
-
const datasetSpec = ctx.resultPool.getPColumnSpecByRef(
|
|
40
|
-
if (!datasetSpec) return
|
|
49
|
+
return options.map((primary: Option): DatasetOption => {
|
|
50
|
+
const datasetSpec = ctx.resultPool.getPColumnSpecByRef(primary.ref);
|
|
51
|
+
if (!datasetSpec) return { primary };
|
|
41
52
|
|
|
42
53
|
const builder = new ColumnCollectionBuilder(pframeSpec);
|
|
43
54
|
for (const src of columnSources) builder.addSource(src);
|
|
44
55
|
const collection = builder.build({ anchors: { main: datasetSpec } });
|
|
45
|
-
if (!collection) return
|
|
56
|
+
if (!collection) return { primary };
|
|
46
57
|
|
|
47
58
|
try {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
59
|
+
const filterMatches = findFilterColumns(collection);
|
|
60
|
+
const filters =
|
|
61
|
+
filterMatches.length === 0
|
|
62
|
+
? undefined
|
|
63
|
+
: filterMatchesToOptions(filterMatches, refMap, opts?.labelOptions);
|
|
64
|
+
|
|
65
|
+
let enrichments;
|
|
66
|
+
if (opts?.withEnrichments !== undefined) {
|
|
67
|
+
const enrichmentVariants = findEnrichmentColumns(collection, {
|
|
68
|
+
maxHops: opts.enrichmentMaxHops,
|
|
69
|
+
...(typeof opts.withEnrichments === "function"
|
|
70
|
+
? { predicate: opts.withEnrichments }
|
|
71
|
+
: { include: opts.withEnrichments }),
|
|
72
|
+
});
|
|
73
|
+
if (enrichmentVariants.length > 0) {
|
|
74
|
+
enrichments = enrichmentVariantsToRefs(enrichmentVariants, opts.labelOptions);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
primary,
|
|
80
|
+
...(filters !== undefined && filters.length > 0 ? { filters } : {}),
|
|
81
|
+
...(enrichments !== undefined && enrichments.length > 0 ? { enrichments } : {}),
|
|
82
|
+
};
|
|
52
83
|
} finally {
|
|
53
84
|
collection.dispose();
|
|
54
85
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { LabeledEnrichmentRefs, Option, PrimaryRef } from "@milaboratories/pl-model-common";
|
|
2
|
+
|
|
3
|
+
/** Dataset picker entry: user picks {@link primary}, gets {@link enrichments} attached. */
|
|
4
|
+
export type DatasetOption = {
|
|
5
|
+
readonly primary: Option;
|
|
6
|
+
readonly filters?: readonly Option[];
|
|
7
|
+
readonly enrichments?: LabeledEnrichmentRefs;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Picked dataset bundle emitted by `PlDatasetSelector`. Stored opaquely in
|
|
12
|
+
* block data; block authors unbundle inside their args resolver.
|
|
13
|
+
*/
|
|
14
|
+
export type DatasetSelection = {
|
|
15
|
+
readonly __isDatasetSelection: "v1";
|
|
16
|
+
readonly primary: PrimaryRef;
|
|
17
|
+
readonly enrichments?: LabeledEnrichmentRefs;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function isDatasetSelection(value: unknown): value is DatasetSelection {
|
|
21
|
+
return (
|
|
22
|
+
typeof value === "object" &&
|
|
23
|
+
value !== null &&
|
|
24
|
+
(value as { __isDatasetSelection?: unknown }).__isDatasetSelection === "v1" &&
|
|
25
|
+
"primary" in value
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function createDatasetSelection(
|
|
30
|
+
primary: PrimaryRef,
|
|
31
|
+
enrichments?: LabeledEnrichmentRefs,
|
|
32
|
+
): DatasetSelection {
|
|
33
|
+
if (enrichments !== undefined && enrichments.length > 0) {
|
|
34
|
+
return { __isDatasetSelection: "v1", primary, enrichments };
|
|
35
|
+
}
|
|
36
|
+
return { __isDatasetSelection: "v1", primary };
|
|
37
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Annotation, createEnrichmentRef } from "@milaboratories/pl-model-common";
|
|
2
|
+
import type {
|
|
3
|
+
EnrichmentStep,
|
|
4
|
+
LabeledEnrichmentRef,
|
|
5
|
+
LabeledEnrichmentRefs,
|
|
6
|
+
MultiColumnSelector,
|
|
7
|
+
PObjectId,
|
|
8
|
+
PObjectSpec,
|
|
9
|
+
} from "@milaboratories/pl-model-common";
|
|
10
|
+
import type {
|
|
11
|
+
AnchoredColumnCollection,
|
|
12
|
+
ColumnVariant,
|
|
13
|
+
} from "../../columns/column_collection_builder";
|
|
14
|
+
import {
|
|
15
|
+
deriveDistinctLabels,
|
|
16
|
+
type DeriveLabelsOptions,
|
|
17
|
+
type Entry,
|
|
18
|
+
} from "../../labels/derive_distinct_labels";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* True for global-form ids — `canonicalize({__isRef: true, blockId, name})` —
|
|
22
|
+
* which the workflow can resolve via bquery. Local-form ids (`resolvePath`)
|
|
23
|
+
* fail this check and are excluded from auto-discovery; prerun/outputs hops
|
|
24
|
+
* must be supplied as resolved `{spec, data}` instead.
|
|
25
|
+
*/
|
|
26
|
+
function isGloballyAddressable(id: PObjectId): boolean {
|
|
27
|
+
try {
|
|
28
|
+
const decoded = JSON.parse(id);
|
|
29
|
+
return (
|
|
30
|
+
typeof decoded === "object" &&
|
|
31
|
+
decoded !== null &&
|
|
32
|
+
decoded.__isRef === true &&
|
|
33
|
+
typeof decoded.blockId === "string" &&
|
|
34
|
+
typeof decoded.name === "string"
|
|
35
|
+
);
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Linker-reached hits attached to the anchor primary. Drops zero-hop variants
|
|
43
|
+
* (filters / the primary itself) and structural hits (subset, linker, label
|
|
44
|
+
* columns). Narrow further with `include` selectors or a `predicate`.
|
|
45
|
+
*/
|
|
46
|
+
export function findEnrichmentColumns(
|
|
47
|
+
collection: AnchoredColumnCollection,
|
|
48
|
+
options?: {
|
|
49
|
+
maxHops?: number;
|
|
50
|
+
include?: MultiColumnSelector | MultiColumnSelector[];
|
|
51
|
+
predicate?: (spec: PObjectSpec) => boolean;
|
|
52
|
+
},
|
|
53
|
+
): ColumnVariant[] {
|
|
54
|
+
const include =
|
|
55
|
+
options?.include === undefined
|
|
56
|
+
? undefined
|
|
57
|
+
: Array.isArray(options.include)
|
|
58
|
+
? options.include
|
|
59
|
+
: [options.include];
|
|
60
|
+
const variants = collection.findColumnVariants({
|
|
61
|
+
mode: "enrichment",
|
|
62
|
+
maxHops: options?.maxHops ?? 4,
|
|
63
|
+
include,
|
|
64
|
+
exclude: [
|
|
65
|
+
{ annotations: { [Annotation.IsSubset]: "true" } },
|
|
66
|
+
{ annotations: { [Annotation.IsLinkerColumn]: "true" } },
|
|
67
|
+
{ name: Annotation.Label },
|
|
68
|
+
],
|
|
69
|
+
});
|
|
70
|
+
const predicate = options?.predicate;
|
|
71
|
+
return variants.filter((v) => {
|
|
72
|
+
if (v.path.length === 0) return false;
|
|
73
|
+
if (predicate !== undefined && !predicate(v.column.spec)) return false;
|
|
74
|
+
if (!isGloballyAddressable(v.column.id)) return false;
|
|
75
|
+
if (v.path.some((p) => !isGloballyAddressable(p.linker.id))) return false;
|
|
76
|
+
return true;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Pair each variant with a path-disambiguated label (so export headers stay
|
|
82
|
+
* unique) and carry hit/linker `PObjectId`s through verbatim. Propagates
|
|
83
|
+
* `qualifications.forHit`; `forQueries` is re-derived by the table builder.
|
|
84
|
+
*/
|
|
85
|
+
export function enrichmentVariantsToRefs(
|
|
86
|
+
variants: ColumnVariant[],
|
|
87
|
+
labelOptions?: DeriveLabelsOptions,
|
|
88
|
+
): LabeledEnrichmentRefs {
|
|
89
|
+
if (variants.length === 0) return [];
|
|
90
|
+
|
|
91
|
+
const entries: Entry[] = variants.map((variant) => ({
|
|
92
|
+
spec: variant.column.spec,
|
|
93
|
+
linkerPath: variant.path.map((p) => ({ spec: p.linker.spec })),
|
|
94
|
+
qualifications: variant.qualifications,
|
|
95
|
+
}));
|
|
96
|
+
const labels = deriveDistinctLabels(entries, labelOptions);
|
|
97
|
+
|
|
98
|
+
return variants.map((variant, i): LabeledEnrichmentRef => {
|
|
99
|
+
const path: EnrichmentStep[] = variant.path.map((step) => ({
|
|
100
|
+
type: "linker",
|
|
101
|
+
linker: step.linker.id,
|
|
102
|
+
}));
|
|
103
|
+
return {
|
|
104
|
+
ref: createEnrichmentRef(variant.column.id, {
|
|
105
|
+
path,
|
|
106
|
+
qualifications: variant.qualifications.forHit,
|
|
107
|
+
}),
|
|
108
|
+
label: labels[i],
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
}
|