@platforma-sdk/model 1.71.0 → 1.73.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/block_model.cjs +3 -0
- package/dist/block_model.cjs.map +1 -1
- package/dist/block_model.d.ts +2 -0
- package/dist/block_model.d.ts.map +1 -1
- package/dist/block_model.js +2 -0
- package/dist/block_model.js.map +1 -1
- package/dist/block_model_legacy.cjs +4 -1
- package/dist/block_model_legacy.cjs.map +1 -1
- package/dist/block_model_legacy.d.ts.map +1 -1
- package/dist/block_model_legacy.js +3 -1
- package/dist/block_model_legacy.js.map +1 -1
- package/dist/components/PlDatasetSelector/build_dataset_options.cjs +24 -14
- 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 +25 -15
- package/dist/components/PlDatasetSelector/build_dataset_options.js.map +1 -1
- package/dist/components/PlDatasetSelector/filter_discovery.cjs +16 -12
- package/dist/components/PlDatasetSelector/filter_discovery.cjs.map +1 -1
- package/dist/components/PlDatasetSelector/filter_discovery.d.ts +3 -0
- package/dist/components/PlDatasetSelector/filter_discovery.d.ts.map +1 -1
- package/dist/components/PlDatasetSelector/filter_discovery.js +16 -12
- package/dist/components/PlDatasetSelector/filter_discovery.js.map +1 -1
- package/dist/package.cjs +1 -1
- package/dist/package.js +1 -1
- package/package.json +8 -8
- package/src/block_model.ts +2 -0
- package/src/block_model_legacy.ts +2 -0
- package/src/components/PlDatasetSelector/build_dataset_options.ts +62 -25
- package/src/components/PlDatasetSelector/filter_discovery.test.ts +12 -6
- package/src/components/PlDatasetSelector/filter_discovery.ts +11 -10
|
@@ -2,22 +2,42 @@ import type { MultiColumnSelector, Option, PObjectSpec } from "@milaboratories/p
|
|
|
2
2
|
import { multiColumnSelectorsToPredicate } from "@milaboratories/pl-model-common";
|
|
3
3
|
import type { DeriveLabelsOptions } from "../../labels/derive_distinct_labels";
|
|
4
4
|
import type { RenderCtxBase } from "../../render";
|
|
5
|
+
import type { AnchoredColumnCollection } from "../../columns/column_collection_builder";
|
|
5
6
|
import { ColumnCollectionBuilder } from "../../columns/column_collection_builder";
|
|
6
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
ResultPoolColumnSnapshotProvider,
|
|
9
|
+
collectCtxColumnSnapshotProviders,
|
|
10
|
+
} from "../../columns/ctx_column_sources";
|
|
7
11
|
import type { DatasetOption } from "./dataset_selection";
|
|
8
12
|
import { buildRefMap, filterMatchesToOptions, findFilterColumns } from "./filter_discovery";
|
|
9
13
|
import { enrichmentVariantsToRefs, findEnrichmentColumns } from "./enrichment_discovery";
|
|
10
14
|
|
|
15
|
+
type SpecPredicateOption =
|
|
16
|
+
| MultiColumnSelector
|
|
17
|
+
| MultiColumnSelector[]
|
|
18
|
+
| ((spec: PObjectSpec) => boolean);
|
|
19
|
+
|
|
20
|
+
function toPredicate(opt: SpecPredicateOption | undefined): (spec: PObjectSpec) => boolean {
|
|
21
|
+
if (opt === undefined) return () => true;
|
|
22
|
+
return typeof opt === "function" ? opt : multiColumnSelectorsToPredicate(opt);
|
|
23
|
+
}
|
|
24
|
+
|
|
11
25
|
export type BuildDatasetOptions = {
|
|
12
26
|
/** Which result pool columns qualify as datasets. Defaults to all. */
|
|
13
|
-
primary?:
|
|
27
|
+
primary?: SpecPredicateOption;
|
|
28
|
+
/**
|
|
29
|
+
* Restricts which result pool columns are considered as filters. Intersected
|
|
30
|
+
* with the built-in `pl7.app/isSubset: "true"` constraint. Defaults to
|
|
31
|
+
* accept-all.
|
|
32
|
+
*/
|
|
33
|
+
filter?: SpecPredicateOption;
|
|
14
34
|
/** Formatting options for filter labels. */
|
|
15
35
|
labelOptions?: DeriveLabelsOptions;
|
|
16
36
|
/**
|
|
17
37
|
* Enables enrichment discovery and filters hits attached to
|
|
18
38
|
* `DatasetOption.enrichments`. Use `() => true` to accept all; omit to disable.
|
|
19
39
|
*/
|
|
20
|
-
withEnrichments?:
|
|
40
|
+
withEnrichments?: SpecPredicateOption;
|
|
21
41
|
/** Maximum linker hops considered. Only used when `withEnrichments` is set. */
|
|
22
42
|
enrichmentMaxHops?: number;
|
|
23
43
|
};
|
|
@@ -32,46 +52,62 @@ export function buildDatasetOptions(
|
|
|
32
52
|
ctx: RenderCtxBase,
|
|
33
53
|
opts?: BuildDatasetOptions,
|
|
34
54
|
): DatasetOption[] | undefined {
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
? () => true
|
|
39
|
-
: typeof primary === "function"
|
|
40
|
-
? primary
|
|
41
|
-
: multiColumnSelectorsToPredicate(primary);
|
|
55
|
+
const primaryPredicate = toPredicate(opts?.primary);
|
|
56
|
+
const filterPredicate = toPredicate(opts?.filter);
|
|
57
|
+
|
|
42
58
|
const options = ctx.resultPool.getOptions(primaryPredicate, { refsWithEnrichments: true });
|
|
43
59
|
if (options.length === 0) return [];
|
|
44
60
|
|
|
45
|
-
const columnSources = collectCtxColumnSnapshotProviders(ctx);
|
|
46
61
|
const refMap = buildRefMap(ctx.resultPool.getSpecs().entries);
|
|
47
62
|
const pframeSpec = ctx.getService("pframeSpec");
|
|
48
63
|
|
|
64
|
+
const withEnrichments = opts?.withEnrichments ?? false;
|
|
65
|
+
const filterSource = new ResultPoolColumnSnapshotProvider(ctx.resultPool);
|
|
66
|
+
// Hoisted out of the per-option loop: collectCtxColumnSnapshotProviders
|
|
67
|
+
// walks the entire output tree, so calling it once per dataset option would
|
|
68
|
+
// be O(N × tree).
|
|
69
|
+
const enrichmentSources = withEnrichments ? collectCtxColumnSnapshotProviders(ctx) : undefined;
|
|
70
|
+
|
|
49
71
|
return options.map((primary: Option): DatasetOption => {
|
|
50
72
|
const datasetSpec = ctx.resultPool.getPColumnSpecByRef(primary.ref);
|
|
51
73
|
if (!datasetSpec) return { primary };
|
|
52
74
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
75
|
+
// Allocations happen inside try so a throw on the second build()
|
|
76
|
+
// still disposes the first collection.
|
|
77
|
+
let filterCollection: AnchoredColumnCollection | undefined;
|
|
78
|
+
let enrichmentCollection: AnchoredColumnCollection | undefined;
|
|
58
79
|
try {
|
|
59
|
-
|
|
80
|
+
// ResultPoolColumnSnapshotProvider is always complete;
|
|
81
|
+
// allowPartialColumnList narrows the return type to non-undefined.
|
|
82
|
+
filterCollection = new ColumnCollectionBuilder(pframeSpec)
|
|
83
|
+
.addSource(filterSource)
|
|
84
|
+
.build({ anchors: { main: datasetSpec }, allowPartialColumnList: true });
|
|
85
|
+
|
|
86
|
+
enrichmentCollection =
|
|
87
|
+
enrichmentSources !== undefined
|
|
88
|
+
? new ColumnCollectionBuilder(pframeSpec)
|
|
89
|
+
.addSources(enrichmentSources)
|
|
90
|
+
.build({ anchors: { main: datasetSpec } })
|
|
91
|
+
: undefined;
|
|
92
|
+
|
|
93
|
+
const filterMatches = findFilterColumns(filterCollection).filter((m) =>
|
|
94
|
+
filterPredicate(m.column.spec),
|
|
95
|
+
);
|
|
60
96
|
const filters =
|
|
61
97
|
filterMatches.length === 0
|
|
62
98
|
? undefined
|
|
63
99
|
: filterMatchesToOptions(filterMatches, refMap, opts?.labelOptions);
|
|
64
100
|
|
|
65
101
|
let enrichments;
|
|
66
|
-
if (
|
|
67
|
-
const enrichmentVariants = findEnrichmentColumns(
|
|
68
|
-
maxHops: opts
|
|
69
|
-
...(typeof
|
|
70
|
-
? { predicate:
|
|
71
|
-
: { include:
|
|
102
|
+
if (enrichmentCollection && withEnrichments) {
|
|
103
|
+
const enrichmentVariants = findEnrichmentColumns(enrichmentCollection, {
|
|
104
|
+
maxHops: opts?.enrichmentMaxHops,
|
|
105
|
+
...(typeof withEnrichments === "function"
|
|
106
|
+
? { predicate: withEnrichments }
|
|
107
|
+
: { include: withEnrichments }),
|
|
72
108
|
});
|
|
73
109
|
if (enrichmentVariants.length > 0) {
|
|
74
|
-
enrichments = enrichmentVariantsToRefs(enrichmentVariants, opts
|
|
110
|
+
enrichments = enrichmentVariantsToRefs(enrichmentVariants, opts?.labelOptions);
|
|
75
111
|
}
|
|
76
112
|
}
|
|
77
113
|
|
|
@@ -81,7 +117,8 @@ export function buildDatasetOptions(
|
|
|
81
117
|
...(enrichments !== undefined && enrichments.length > 0 ? { enrichments } : {}),
|
|
82
118
|
};
|
|
83
119
|
} finally {
|
|
84
|
-
|
|
120
|
+
filterCollection?.dispose();
|
|
121
|
+
enrichmentCollection?.dispose();
|
|
85
122
|
}
|
|
86
123
|
});
|
|
87
124
|
}
|
|
@@ -140,17 +140,23 @@ describe("filterMatchesToOptions", () => {
|
|
|
140
140
|
expect(filterMatchesToOptions([], new Map())).toEqual([]);
|
|
141
141
|
});
|
|
142
142
|
|
|
143
|
-
test("
|
|
144
|
-
const
|
|
145
|
-
const
|
|
143
|
+
test("skips entries whose ref is not found in map", () => {
|
|
144
|
+
const knownRef = createPlRef("b1", "known");
|
|
145
|
+
const knownSpec = spec("known", [axis("sample")], { [Annotation.IsSubset]: "true" });
|
|
146
|
+
const orphanSpec = spec("orphan", [axis("sample")], { [Annotation.IsSubset]: "true" });
|
|
147
|
+
const knownSnap = snap(canonicalize(knownRef)! as string, knownSpec);
|
|
148
|
+
const orphanSnap = snap("orphan-id", orphanSpec);
|
|
146
149
|
|
|
147
150
|
const builder = new ColumnCollectionBuilder(createSpecFrameCtx());
|
|
148
|
-
builder.addSource([
|
|
151
|
+
builder.addSource([knownSnap, orphanSnap, anchorSnap]);
|
|
149
152
|
const collection = builder.build({ anchors: { main: anchorSpec } })!;
|
|
150
153
|
|
|
151
154
|
const matches = findFilterColumns(collection);
|
|
152
|
-
expect(matches.length).toBe(
|
|
155
|
+
expect(matches.length).toBe(2);
|
|
153
156
|
|
|
154
|
-
|
|
157
|
+
const refMap = buildRefMap([{ ref: knownRef }]);
|
|
158
|
+
const options = filterMatchesToOptions(matches, refMap);
|
|
159
|
+
expect(options).toHaveLength(1);
|
|
160
|
+
expect(options[0].ref).toBe(knownRef);
|
|
155
161
|
});
|
|
156
162
|
});
|
|
@@ -31,6 +31,9 @@ export function findFilterColumns(collection: AnchoredColumnCollection): ColumnM
|
|
|
31
31
|
/**
|
|
32
32
|
* Derive labeled options from filter column matches, for use in DatasetOption.filters.
|
|
33
33
|
*
|
|
34
|
+
* Entries whose column id has no PlRef in `refsByObjectId` are silently
|
|
35
|
+
* skipped — they cannot be exposed as user-selectable options.
|
|
36
|
+
*
|
|
34
37
|
* @param matches - from findFilterColumns()
|
|
35
38
|
* @param refsByObjectId - from {@link buildRefMap}
|
|
36
39
|
* @param labelOptions - forwarded to deriveDistinctLabels()
|
|
@@ -45,8 +48,13 @@ export function filterMatchesToOptions(
|
|
|
45
48
|
// Each ColumnMatch can be reached via multiple variants (different linker
|
|
46
49
|
// paths / qualifications). We emit one Option per variant so the user can
|
|
47
50
|
// pick a specific path — `deriveDistinctLabels` disambiguates labels by
|
|
48
|
-
// path.
|
|
49
|
-
|
|
51
|
+
// path. All variants of a match share a column id, so the ref lookup
|
|
52
|
+
// happens once per match.
|
|
53
|
+
const flattened = matches.flatMap((match) => {
|
|
54
|
+
const ref = refsByObjectId.get(match.column.id);
|
|
55
|
+
if (ref === undefined) return [];
|
|
56
|
+
return match.variants.map((variant) => ({ match, variant, ref }));
|
|
57
|
+
});
|
|
50
58
|
|
|
51
59
|
const entries: Entry[] = flattened.map(({ match, variant }) => ({
|
|
52
60
|
spec: match.column.spec,
|
|
@@ -55,14 +63,7 @@ export function filterMatchesToOptions(
|
|
|
55
63
|
|
|
56
64
|
const labels = deriveDistinctLabels(entries, labelOptions);
|
|
57
65
|
|
|
58
|
-
return flattened.map(({
|
|
59
|
-
const ref = refsByObjectId.get(match.column.id);
|
|
60
|
-
if (ref === undefined)
|
|
61
|
-
throw new Error(
|
|
62
|
-
`no PlRef found for filter column ${match.column.spec.name} (id: ${match.column.id})`,
|
|
63
|
-
);
|
|
64
|
-
return { ref, label: labels[i] };
|
|
65
|
-
});
|
|
66
|
+
return flattened.map(({ ref }, i) => ({ ref, label: labels[i] }));
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
/**
|