@platforma-sdk/model 1.65.9 → 1.66.2
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 +8 -11
- package/dist/block_model.cjs.map +1 -1
- package/dist/block_model.d.ts.map +1 -1
- package/dist/block_model.js +8 -10
- package/dist/block_model.js.map +1 -1
- package/dist/columns/column_collection_builder.cjs +61 -74
- package/dist/columns/column_collection_builder.cjs.map +1 -1
- package/dist/columns/column_collection_builder.d.ts +16 -22
- package/dist/columns/column_collection_builder.d.ts.map +1 -1
- package/dist/columns/column_collection_builder.js +62 -75
- package/dist/columns/column_collection_builder.js.map +1 -1
- package/dist/columns/column_selector.cjs.map +1 -1
- package/dist/columns/column_selector.d.ts +1 -1
- package/dist/columns/column_selector.js.map +1 -1
- package/dist/columns/column_snapshot.cjs.map +1 -1
- package/dist/columns/column_snapshot.d.ts +4 -4
- package/dist/columns/column_snapshot.d.ts.map +1 -1
- package/dist/columns/column_snapshot.js.map +1 -1
- package/dist/columns/ctx_column_sources.cjs.map +1 -1
- package/dist/columns/ctx_column_sources.d.ts +1 -1
- package/dist/columns/ctx_column_sources.d.ts.map +1 -1
- package/dist/columns/ctx_column_sources.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV2.cjs +2 -2
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV2.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV2.js +2 -2
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV2.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV3.cjs +17 -18
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV3.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV3.js +17 -18
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV3.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.cjs +99 -91
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.d.ts +16 -16
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.d.ts.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.js +102 -94
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.cjs +32 -23
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.d.ts +5 -5
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.d.ts.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.js +33 -24
- package/dist/components/PlDataTable/createPlDataTable/discoverColumns.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/index.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/index.d.ts +2 -3
- package/dist/components/PlDataTable/createPlDataTable/index.d.ts.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/index.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/utils.cjs +133 -16
- package/dist/components/PlDataTable/createPlDataTable/utils.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/utils.d.ts +8 -6
- package/dist/components/PlDataTable/createPlDataTable/utils.d.ts.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/utils.js +130 -17
- package/dist/components/PlDataTable/createPlDataTable/utils.js.map +1 -1
- package/dist/components/PlDataTable/labels.cjs +1 -2
- package/dist/components/PlDataTable/labels.cjs.map +1 -1
- package/dist/components/PlDataTable/labels.js +1 -2
- package/dist/components/PlDataTable/labels.js.map +1 -1
- package/dist/filters/distill.cjs +73 -30
- package/dist/filters/distill.cjs.map +1 -1
- package/dist/filters/distill.d.ts.map +1 -1
- package/dist/filters/distill.js +73 -30
- package/dist/filters/distill.js.map +1 -1
- package/dist/index.cjs +19 -15
- package/dist/index.d.ts +4 -2
- package/dist/index.js +6 -4
- package/dist/labels/derive_distinct_tooltips.cjs +85 -0
- package/dist/labels/derive_distinct_tooltips.cjs.map +1 -0
- package/dist/labels/derive_distinct_tooltips.d.ts +17 -0
- package/dist/labels/derive_distinct_tooltips.d.ts.map +1 -0
- package/dist/labels/derive_distinct_tooltips.js +84 -0
- package/dist/labels/derive_distinct_tooltips.js.map +1 -0
- package/dist/labels/index.cjs +1 -0
- package/dist/labels/index.d.ts +2 -1
- package/dist/labels/index.js +1 -0
- package/dist/package.cjs +1 -1
- package/dist/package.js +1 -1
- package/dist/render/api.cjs +8 -13
- package/dist/render/api.cjs.map +1 -1
- package/dist/render/api.d.ts +8 -11
- package/dist/render/api.d.ts.map +1 -1
- package/dist/render/api.js +8 -13
- package/dist/render/api.js.map +1 -1
- package/dist/services/get_services.cjs +19 -0
- package/dist/services/get_services.cjs.map +1 -0
- package/dist/services/get_services.d.ts +7 -0
- package/dist/services/get_services.d.ts.map +1 -0
- package/dist/services/get_services.js +19 -0
- package/dist/services/get_services.js.map +1 -0
- package/dist/services/index.cjs +1 -0
- package/dist/services/index.d.ts +2 -1
- package/dist/services/index.js +1 -0
- package/dist/services/service_bridge.cjs +4 -4
- package/dist/services/service_bridge.cjs.map +1 -1
- package/dist/services/service_bridge.d.ts +4 -4
- package/dist/services/service_bridge.d.ts.map +1 -1
- package/dist/services/service_bridge.js +4 -4
- package/dist/services/service_bridge.js.map +1 -1
- package/package.json +6 -6
- package/src/block_model.ts +8 -11
- package/src/columns/column_collection_builder.test.ts +75 -30
- package/src/columns/column_collection_builder.ts +96 -133
- package/src/columns/column_selector.ts +1 -1
- package/src/columns/column_snapshot.ts +7 -4
- package/src/columns/ctx_column_sources.ts +1 -3
- package/src/components/PFrameForGraphs.test.ts +4 -4
- package/src/components/PlDataTable/createPlDataTable/createPTableDefV2.ts +2 -2
- package/src/components/PlDataTable/createPlDataTable/createPTableDefV3.ts +44 -21
- package/src/components/PlDataTable/createPlDataTable/createPlDataTableV3.ts +202 -218
- package/src/components/PlDataTable/createPlDataTable/discoverColumns.ts +69 -56
- package/src/components/PlDataTable/createPlDataTable/index.ts +6 -7
- package/src/components/PlDataTable/createPlDataTable/utils.test.ts +97 -1
- package/src/components/PlDataTable/createPlDataTable/utils.ts +190 -35
- package/src/components/PlDataTable/labels.ts +3 -7
- package/src/filters/distill.test.ts +91 -0
- package/src/filters/distill.ts +102 -46
- package/src/labels/derive_distinct_tooltips.test.ts +233 -0
- package/src/labels/derive_distinct_tooltips.ts +130 -0
- package/src/labels/index.ts +1 -0
- package/src/render/api.ts +15 -50
- package/src/services/get_services.ts +28 -0
- package/src/services/index.ts +1 -0
- package/src/services/service_bridge.ts +5 -5
|
@@ -1,57 +1,61 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
PlRef,
|
|
4
|
-
PObjectId,
|
|
5
|
-
RequireServices,
|
|
6
|
-
Services,
|
|
7
|
-
SUniversalPColumnId,
|
|
8
|
-
} from "@milaboratories/pl-model-common";
|
|
9
|
-
import { isPlRef } from "@milaboratories/pl-model-common";
|
|
1
|
+
import type { PColumnSpec, PlRef, PObjectId } from "@milaboratories/pl-model-common";
|
|
2
|
+
import { createDiscoveredPColumnId, isPlRef } from "@milaboratories/pl-model-common";
|
|
10
3
|
import type { RenderCtxBase } from "../../../render";
|
|
11
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
ColumnSource,
|
|
6
|
+
ColumnMatch,
|
|
7
|
+
RelaxedColumnSelector,
|
|
8
|
+
ColumnSnapshotProvider,
|
|
9
|
+
ColumnSnapshot,
|
|
10
|
+
} from "../../../columns";
|
|
12
11
|
import { ColumnCollectionBuilder } from "../../../columns";
|
|
13
12
|
import { toColumnSnapshotProvider } from "../../../columns/column_snapshot_provider";
|
|
14
13
|
import { collectCtxColumnSnapshotProviders } from "../../../columns/ctx_column_sources";
|
|
15
14
|
import { throwError } from "@milaboratories/helpers";
|
|
16
15
|
import type { ColumnsSelectorConfig, TableColumnSnapshot } from "./createPlDataTableV3";
|
|
16
|
+
import { isNil } from "es-toolkit";
|
|
17
17
|
|
|
18
|
-
export type
|
|
18
|
+
export type DiscoverTableColumnOptions = {
|
|
19
19
|
sources?: ColumnSource[];
|
|
20
20
|
anchors: Record<string, PlRef | PObjectId | PColumnSpec | RelaxedColumnSelector>;
|
|
21
|
-
|
|
21
|
+
selector: ColumnsSelectorConfig;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
/** Discover columns from sources/anchors and normalize into a flat DiscoveredColumn list. */
|
|
25
|
-
export function discoverTableColumnSnaphots
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
>(
|
|
30
|
-
ctx: RenderCtxBase<A, U, S>,
|
|
31
|
-
options: DiscoveredTableColumnOptions,
|
|
32
|
-
): TableColumnSnapshot<SUniversalPColumnId>[] | undefined {
|
|
25
|
+
export function discoverTableColumnSnaphots(
|
|
26
|
+
ctx: RenderCtxBase,
|
|
27
|
+
options: DiscoverTableColumnOptions,
|
|
28
|
+
): TableColumnSnapshot[] | undefined {
|
|
33
29
|
// Resolve PlRef anchors to PColumnSpec
|
|
34
|
-
const resolvedOptions = {
|
|
30
|
+
const resolvedOptions = {
|
|
31
|
+
...options,
|
|
32
|
+
anchors: resolveAnchors(ctx, options.anchors),
|
|
33
|
+
};
|
|
35
34
|
|
|
36
35
|
// Resolve providers
|
|
37
36
|
const providers = resolveProviders(ctx, resolvedOptions.sources);
|
|
38
37
|
if (providers.length === 0) return undefined;
|
|
39
38
|
|
|
40
39
|
// Build collection (anchored or plain)
|
|
41
|
-
const
|
|
42
|
-
ctx.services.pframeSpec ?? throwError("PFrameSpec service is required for column discovery.");
|
|
43
|
-
const collection = new ColumnCollectionBuilder(pframeSpec)
|
|
40
|
+
const collection = new ColumnCollectionBuilder(ctx.getService("pframeSpec"))
|
|
44
41
|
.addSources(providers)
|
|
45
42
|
.build(resolvedOptions);
|
|
46
43
|
if (collection === undefined) return undefined;
|
|
47
44
|
|
|
48
45
|
try {
|
|
49
|
-
const
|
|
46
|
+
const columns = collection.findColumns({
|
|
47
|
+
...(resolvedOptions.selector ?? {}),
|
|
48
|
+
exclude: [
|
|
49
|
+
...(Array.isArray(resolvedOptions.selector?.exclude)
|
|
50
|
+
? resolvedOptions.selector.exclude
|
|
51
|
+
: !isNil(resolvedOptions.selector.exclude)
|
|
52
|
+
? [resolvedOptions.selector.exclude]
|
|
53
|
+
: []),
|
|
54
|
+
{ name: "pl7.app/label" },
|
|
55
|
+
],
|
|
56
|
+
});
|
|
50
57
|
const anchors = collection.getAnchors();
|
|
51
|
-
return mapToDiscoveredColumns(
|
|
52
|
-
matched,
|
|
53
|
-
Array.from(Array.from(anchors.values()).map((v) => v.columnId)),
|
|
54
|
-
);
|
|
58
|
+
return mapToDiscoveredColumns(columns, anchors);
|
|
55
59
|
} finally {
|
|
56
60
|
collection.dispose();
|
|
57
61
|
}
|
|
@@ -60,8 +64,8 @@ export function discoverTableColumnSnaphots<
|
|
|
60
64
|
// --- Pure helper functions ---
|
|
61
65
|
|
|
62
66
|
/** Resolve PlRef values in anchors to PColumnSpec via the result pool. */
|
|
63
|
-
function resolveAnchors
|
|
64
|
-
ctx: RenderCtxBase
|
|
67
|
+
function resolveAnchors(
|
|
68
|
+
ctx: RenderCtxBase,
|
|
65
69
|
anchors: Record<string, PlRef | PObjectId | PColumnSpec | RelaxedColumnSelector>,
|
|
66
70
|
): Record<string, PObjectId | PColumnSpec | RelaxedColumnSelector> {
|
|
67
71
|
const result: Record<string, PObjectId | PColumnSpec | RelaxedColumnSelector> = {};
|
|
@@ -80,7 +84,10 @@ function resolveAnchors<A, U>(
|
|
|
80
84
|
}
|
|
81
85
|
|
|
82
86
|
/** Resolve column snapshot providers from explicit sources or context. */
|
|
83
|
-
function resolveProviders
|
|
87
|
+
function resolveProviders(
|
|
88
|
+
ctx: RenderCtxBase,
|
|
89
|
+
sources: undefined | ColumnSource[],
|
|
90
|
+
): ColumnSnapshotProvider[] {
|
|
84
91
|
return sources !== undefined
|
|
85
92
|
? sources.map(toColumnSnapshotProvider)
|
|
86
93
|
: collectCtxColumnSnapshotProviders(ctx);
|
|
@@ -89,34 +96,40 @@ function resolveProviders<A, U>(ctx: RenderCtxBase<A, U>, sources: undefined | C
|
|
|
89
96
|
/** Map matched columns into a flat DiscoveredColumn list with deduped IDs. */
|
|
90
97
|
function mapToDiscoveredColumns(
|
|
91
98
|
matched: readonly ColumnMatch[],
|
|
92
|
-
|
|
93
|
-
): TableColumnSnapshot
|
|
94
|
-
const
|
|
95
|
-
(
|
|
96
|
-
new Map<PObjectId, number>(),
|
|
99
|
+
anchors: Map<string, ColumnSnapshot<PObjectId>>,
|
|
100
|
+
): TableColumnSnapshot[] {
|
|
101
|
+
const columnIdToAnchorName = new Map<PObjectId, string>(
|
|
102
|
+
Array.from(anchors.entries(), ([key, { id }]) => [id, key] as const),
|
|
97
103
|
);
|
|
98
104
|
|
|
99
|
-
|
|
100
|
-
return matched.map((match) => {
|
|
105
|
+
return matched.flatMap((match) => {
|
|
101
106
|
const snap = match.column;
|
|
107
|
+
const isPrimary = columnIdToAnchorName.get(match.column.id) !== undefined;
|
|
102
108
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
+
return match.variants.map((variant): TableColumnSnapshot => {
|
|
110
|
+
const discoveredId = createDiscoveredPColumnId({
|
|
111
|
+
column: snap.id,
|
|
112
|
+
path: variant.path.map((p) => ({
|
|
113
|
+
type: "linker",
|
|
114
|
+
column: p.linker.id,
|
|
115
|
+
qualifications: p.qualifications,
|
|
116
|
+
})),
|
|
117
|
+
columnQualifications: variant.qualifications.forHit,
|
|
118
|
+
queriesQualifications: variant.qualifications.forQueries,
|
|
119
|
+
});
|
|
120
|
+
return {
|
|
121
|
+
id: discoveredId,
|
|
122
|
+
isPrimary,
|
|
123
|
+
|
|
124
|
+
originalId: snap.id,
|
|
125
|
+
spec: snap.spec,
|
|
126
|
+
data: snap.data,
|
|
127
|
+
dataStatus: snap.dataStatus,
|
|
109
128
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
dataStatus: snap.dataStatus,
|
|
116
|
-
isPrimary: anchorColumnIds.some(
|
|
117
|
-
(anchor) => snap.id === anchor || match.originalId === anchor,
|
|
118
|
-
),
|
|
119
|
-
linkerPath: match.path,
|
|
120
|
-
};
|
|
129
|
+
linkerPath: variant.path,
|
|
130
|
+
qualifications: variant.qualifications,
|
|
131
|
+
distinctiveQualifications: variant.distinctiveQualifications,
|
|
132
|
+
};
|
|
133
|
+
});
|
|
121
134
|
});
|
|
122
135
|
}
|
|
@@ -1,20 +1,19 @@
|
|
|
1
|
-
import { Services, type RequireServices } from "@milaboratories/pl-model-common";
|
|
2
1
|
import type { RenderCtxBase } from "../../../render";
|
|
3
2
|
import type { PlDataTableModel } from "../typesV5";
|
|
4
3
|
import { createPlDataTableOptionsV2, createPlDataTableV2 } from "./createPlDataTableV2";
|
|
5
4
|
import { createPlDataTableV3 } from "./createPlDataTableV3";
|
|
6
5
|
import type { createPlDataTableOptionsV3 } from "./createPlDataTableV3";
|
|
7
6
|
|
|
8
|
-
export function createPlDataTable<A, U
|
|
9
|
-
ctx: RenderCtxBase<A, U
|
|
7
|
+
export function createPlDataTable<A, U>(
|
|
8
|
+
ctx: RenderCtxBase<A, U>,
|
|
10
9
|
options: { version: "v2" } & createPlDataTableOptionsV2,
|
|
11
10
|
): ReturnType<typeof createPlDataTableV2>;
|
|
12
|
-
export function createPlDataTable<A, U
|
|
13
|
-
ctx: RenderCtxBase<A, U
|
|
11
|
+
export function createPlDataTable<A, U>(
|
|
12
|
+
ctx: RenderCtxBase<A, U>,
|
|
14
13
|
options: { version?: "v3" } & createPlDataTableOptionsV3,
|
|
15
14
|
): ReturnType<typeof createPlDataTableV3>;
|
|
16
|
-
export function createPlDataTable<A, U
|
|
17
|
-
ctx: RenderCtxBase<A, U
|
|
15
|
+
export function createPlDataTable<A, U>(
|
|
16
|
+
ctx: RenderCtxBase<A, U>,
|
|
18
17
|
options:
|
|
19
18
|
| ({ version: "v2" } & createPlDataTableOptionsV2)
|
|
20
19
|
| ({ version?: "v3" } & createPlDataTableOptionsV3),
|
|
@@ -6,8 +6,10 @@ import {
|
|
|
6
6
|
type PColumnSpec,
|
|
7
7
|
type PObjectId,
|
|
8
8
|
} from "@milaboratories/pl-model-common";
|
|
9
|
+
import { SpecDriver } from "@milaboratories/pf-spec-driver";
|
|
9
10
|
import { describe, expect, test } from "vitest";
|
|
10
|
-
import { deriveAllLabels, type LabelableColumn } from "./utils";
|
|
11
|
+
import { deriveAllLabels, evaluateRules, type LabelableColumn, type RuleColumn } from "./utils";
|
|
12
|
+
import type { ColumnOrderRule, ColumnVisibilityRule } from "./createPlDataTableV3";
|
|
11
13
|
|
|
12
14
|
// ---------------------------------------------------------------------------
|
|
13
15
|
// Helpers
|
|
@@ -255,3 +257,97 @@ describe("deriveAxisLabels via deriveAllLabels", () => {
|
|
|
255
257
|
expect(result["c2"]).not.toContain("via ");
|
|
256
258
|
});
|
|
257
259
|
});
|
|
260
|
+
|
|
261
|
+
// ---------------------------------------------------------------------------
|
|
262
|
+
// evaluateRules
|
|
263
|
+
// ---------------------------------------------------------------------------
|
|
264
|
+
|
|
265
|
+
function makeRuleColumn(id: string, spec: Partial<PColumnSpec> = {}): RuleColumn {
|
|
266
|
+
return {
|
|
267
|
+
id: id as PObjectId,
|
|
268
|
+
spec: makeSpec({ axesSpec: [{ name: "id", type: "String" }], ...spec }),
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
describe("evaluateRules", () => {
|
|
273
|
+
test("returns empty map when rules or columns are empty", () => {
|
|
274
|
+
const driver = new SpecDriver();
|
|
275
|
+
expect(evaluateRules([], [makeRuleColumn("c1")], driver).size).toBe(0);
|
|
276
|
+
expect(
|
|
277
|
+
evaluateRules<ColumnVisibilityRule>([{ match: () => true, visibility: "hidden" }], [], driver)
|
|
278
|
+
.size,
|
|
279
|
+
).toBe(0);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test("evaluates predicate rules without touching the driver", () => {
|
|
283
|
+
const driver = new Proxy({} as SpecDriver, {
|
|
284
|
+
get() {
|
|
285
|
+
throw new Error("driver should not be called for predicate-only rules");
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
const rules: ColumnOrderRule[] = [
|
|
290
|
+
{ match: (spec) => spec.name === "alpha", priority: 10 },
|
|
291
|
+
{ match: (spec) => spec.name === "beta", priority: 5 },
|
|
292
|
+
];
|
|
293
|
+
const result = evaluateRules(
|
|
294
|
+
rules,
|
|
295
|
+
[
|
|
296
|
+
makeRuleColumn("a", { name: "alpha" }),
|
|
297
|
+
makeRuleColumn("b", { name: "beta" }),
|
|
298
|
+
makeRuleColumn("c", { name: "gamma" }),
|
|
299
|
+
],
|
|
300
|
+
driver,
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
expect(result.get("a" as PObjectId)?.priority).toBe(10);
|
|
304
|
+
expect(result.get("b" as PObjectId)?.priority).toBe(5);
|
|
305
|
+
expect(result.has("c" as PObjectId)).toBe(false);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test("evaluates selector rules via PFrameSpec.discoverColumns", () => {
|
|
309
|
+
const driver = new SpecDriver();
|
|
310
|
+
const rules: ColumnVisibilityRule[] = [
|
|
311
|
+
{ match: { name: "^note$" }, visibility: "hidden" },
|
|
312
|
+
{ match: { name: "^score$" }, visibility: "optional" },
|
|
313
|
+
];
|
|
314
|
+
const columns = [
|
|
315
|
+
makeRuleColumn("n", { name: "note" }),
|
|
316
|
+
makeRuleColumn("s", { name: "score" }),
|
|
317
|
+
makeRuleColumn("x", { name: "other" }),
|
|
318
|
+
];
|
|
319
|
+
|
|
320
|
+
const result = evaluateRules(rules, columns, driver);
|
|
321
|
+
|
|
322
|
+
expect(result.get("n" as PObjectId)?.visibility).toBe("hidden");
|
|
323
|
+
expect(result.get("s" as PObjectId)?.visibility).toBe("optional");
|
|
324
|
+
expect(result.has("x" as PObjectId)).toBe(false);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test("preserves original rule order when predicate and selector rules are mixed", () => {
|
|
328
|
+
const driver = new SpecDriver();
|
|
329
|
+
const rules: ColumnOrderRule[] = [
|
|
330
|
+
{ match: (spec) => spec.name === "alpha", priority: 100 },
|
|
331
|
+
{ match: { name: "^alpha$" }, priority: 1 }, // shadowed by the predicate above
|
|
332
|
+
{ match: { name: "^beta$" }, priority: 50 },
|
|
333
|
+
];
|
|
334
|
+
const result = evaluateRules(
|
|
335
|
+
rules,
|
|
336
|
+
[makeRuleColumn("a", { name: "alpha" }), makeRuleColumn("b", { name: "beta" })],
|
|
337
|
+
driver,
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
expect(result.get("a" as PObjectId)?.priority).toBe(100);
|
|
341
|
+
expect(result.get("b" as PObjectId)?.priority).toBe(50);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
test("dedupes columns by id before building spec frame (no duplicate-key crash)", () => {
|
|
345
|
+
const driver = new SpecDriver();
|
|
346
|
+
const rules: ColumnVisibilityRule[] = [{ match: { name: "^dup$" }, visibility: "hidden" }];
|
|
347
|
+
const dup = makeRuleColumn("d", { name: "dup" });
|
|
348
|
+
|
|
349
|
+
const result = evaluateRules(rules, [dup, dup, dup], driver);
|
|
350
|
+
|
|
351
|
+
expect(result.get("d" as PObjectId)?.visibility).toBe("hidden");
|
|
352
|
+
});
|
|
353
|
+
});
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type PColumn,
|
|
3
3
|
type PColumnSpec,
|
|
4
|
+
type PFrameSpecDriver,
|
|
4
5
|
type PObjectId,
|
|
5
6
|
Annotation,
|
|
6
7
|
canonicalizeAxisId,
|
|
7
8
|
canonicalizeJson,
|
|
9
|
+
DiscoveredPColumnId,
|
|
8
10
|
getAxisId,
|
|
9
11
|
readAnnotation,
|
|
10
12
|
readAnnotationJson,
|
|
@@ -13,7 +15,14 @@ import {
|
|
|
13
15
|
deriveDistinctLabels,
|
|
14
16
|
type DeriveLabelsOptions,
|
|
15
17
|
} from "../../../labels/derive_distinct_labels";
|
|
16
|
-
import
|
|
18
|
+
import {
|
|
19
|
+
deriveDistinctTooltips,
|
|
20
|
+
type TooltipEntry,
|
|
21
|
+
} from "../../../labels/derive_distinct_tooltips";
|
|
22
|
+
import type { MatchQualifications, MatchVariant } from "../../../columns";
|
|
23
|
+
import type { ColumnMatcher, ColumnOrderRule, ColumnVisibilityRule } from "./createPlDataTableV3";
|
|
24
|
+
import type { ColumnSelector } from "../../../columns";
|
|
25
|
+
import { ArrayColumnProvider, ColumnCollectionBuilder } from "../../../columns";
|
|
17
26
|
import { isNil } from "es-toolkit";
|
|
18
27
|
|
|
19
28
|
/** Check if column should be omitted from the table */
|
|
@@ -26,36 +35,93 @@ export function isColumnOptional(spec: { annotations?: Annotation }): boolean {
|
|
|
26
35
|
return readAnnotation(spec, Annotation.Table.Visibility) === "optional";
|
|
27
36
|
}
|
|
28
37
|
|
|
29
|
-
/**
|
|
38
|
+
/** Column shape consumed by rule evaluation. */
|
|
39
|
+
export type RuleColumn = Pick<PColumn<PObjectId>, "id" | "spec">;
|
|
40
|
+
|
|
41
|
+
/** Get effective visibility for a column. Rule map lookup first, then annotation fallback. */
|
|
30
42
|
export function getEffectiveVisibility(
|
|
31
|
-
|
|
32
|
-
|
|
43
|
+
col: RuleColumn,
|
|
44
|
+
visibilityByColId?: Map<PObjectId, ColumnVisibilityRule>,
|
|
33
45
|
): undefined | "default" | "optional" | "hidden" {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
if (isColumnHidden(spec)) return "hidden";
|
|
42
|
-
if (isColumnOptional(spec)) return "optional";
|
|
46
|
+
const rule = visibilityByColId?.get(col.id);
|
|
47
|
+
if (rule !== undefined) return rule.visibility;
|
|
48
|
+
if (isColumnHidden(col.spec)) return "hidden";
|
|
49
|
+
if (isColumnOptional(col.spec)) return "optional";
|
|
43
50
|
return undefined;
|
|
44
51
|
}
|
|
45
52
|
|
|
46
|
-
/** Get ordering priority for a column.
|
|
53
|
+
/** Get ordering priority for a column. Rule map lookup first, then annotation fallback. */
|
|
47
54
|
export function getOrderPriority(
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
col: RuleColumn,
|
|
56
|
+
orderByColId?: Map<PObjectId, ColumnOrderRule>,
|
|
50
57
|
): undefined | number {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
const rule = orderByColId?.get(col.id);
|
|
59
|
+
if (rule !== undefined) return rule.priority;
|
|
60
|
+
return readAnnotationJson(col.spec, Annotation.Table.OrderPriority);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Evaluate display rules against a set of columns and return a map of `colId → winning rule`
|
|
65
|
+
* (first-match-wins, preserving original rule order).
|
|
66
|
+
*
|
|
67
|
+
* Predicate-based rules (`ColumnMatcher`) are evaluated directly on the spec.
|
|
68
|
+
* Selector-based rules (`ColumnSelector`) are matched via `PFrameSpecDriver.discoverColumns`
|
|
69
|
+
* using the same engine as `ColumnCollection.findColumns` — no client-side matcher.
|
|
70
|
+
*/
|
|
71
|
+
export function evaluateRules<R extends { match: ColumnMatcher | ColumnSelector }>(
|
|
72
|
+
rules: R[],
|
|
73
|
+
columns: RuleColumn[],
|
|
74
|
+
pframeSpec: PFrameSpecDriver,
|
|
75
|
+
): Map<PObjectId, R> {
|
|
76
|
+
const result = new Map<PObjectId, R>();
|
|
77
|
+
if (rules.length === 0 || columns.length === 0) return result;
|
|
78
|
+
|
|
79
|
+
const hasSelectorRules = rules.some((rule) => typeof rule.match !== "function");
|
|
80
|
+
const selectorHitsByRule = new Map<R, Set<PObjectId>>();
|
|
81
|
+
|
|
82
|
+
if (hasSelectorRules) {
|
|
83
|
+
const dedupedColumns = dedupeById(columns);
|
|
84
|
+
const pColumns = dedupedColumns.map((c) => ({ id: c.id, spec: c.spec, data: undefined }));
|
|
85
|
+
const collection = new ColumnCollectionBuilder(pframeSpec)
|
|
86
|
+
.addSource(new ArrayColumnProvider(pColumns))
|
|
87
|
+
.build();
|
|
88
|
+
if (collection === undefined) return result;
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
for (const rule of rules) {
|
|
92
|
+
if (typeof rule.match === "function") continue;
|
|
93
|
+
const hits = collection.findColumns({ include: rule.match });
|
|
94
|
+
selectorHitsByRule.set(rule, new Set(hits.map((h) => h.id)));
|
|
55
95
|
}
|
|
96
|
+
} finally {
|
|
97
|
+
collection.dispose();
|
|
56
98
|
}
|
|
57
99
|
}
|
|
58
|
-
|
|
100
|
+
|
|
101
|
+
for (const col of columns) {
|
|
102
|
+
for (const rule of rules) {
|
|
103
|
+
const matches =
|
|
104
|
+
typeof rule.match === "function"
|
|
105
|
+
? rule.match(col.spec)
|
|
106
|
+
: (selectorHitsByRule.get(rule)?.has(col.id) ?? false);
|
|
107
|
+
if (matches) {
|
|
108
|
+
result.set(col.id, rule);
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function dedupeById(columns: RuleColumn[]): RuleColumn[] {
|
|
117
|
+
const seen = new Set<PObjectId>();
|
|
118
|
+
const result: RuleColumn[] = [];
|
|
119
|
+
for (const col of columns) {
|
|
120
|
+
if (seen.has(col.id)) continue;
|
|
121
|
+
seen.add(col.id);
|
|
122
|
+
result.push(col);
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
59
125
|
}
|
|
60
126
|
|
|
61
127
|
/**
|
|
@@ -65,10 +131,9 @@ export function getOrderPriority(
|
|
|
65
131
|
* For each column: writes derived label into Annotation.Label (if present in derivedLabels).
|
|
66
132
|
* For each axis in column specs: writes derived axis label into AxisSpec annotations.
|
|
67
133
|
*/
|
|
68
|
-
export function withLabelAnnotations<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
): PColumn<Data>[] {
|
|
134
|
+
export function withLabelAnnotations<
|
|
135
|
+
T extends { readonly id: PObjectId; readonly spec: PColumnSpec },
|
|
136
|
+
>(derivedLabels: undefined | Record<string, string>, columns: T[]): T[] {
|
|
72
137
|
if (derivedLabels === undefined) return columns;
|
|
73
138
|
return columns.map((col) => {
|
|
74
139
|
const colLabel = derivedLabels[col.id];
|
|
@@ -86,26 +151,29 @@ export function withLabelAnnotations<Data>(
|
|
|
86
151
|
: { ...axis, annotations: { ...axis.annotations, [Annotation.Label]: label } };
|
|
87
152
|
}),
|
|
88
153
|
},
|
|
89
|
-
};
|
|
154
|
+
} as T;
|
|
90
155
|
});
|
|
91
156
|
}
|
|
92
157
|
|
|
93
158
|
/**
|
|
94
|
-
* Writes effective display properties (OrderPriority, Visibility) from
|
|
159
|
+
* Writes effective display properties (OrderPriority, Visibility) from precomputed rule maps
|
|
95
160
|
* into column annotations. Returns new column objects — originals are not mutated.
|
|
96
161
|
*/
|
|
97
|
-
export function withTableVisualAnnotations<
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
162
|
+
export function withTableVisualAnnotations<
|
|
163
|
+
T extends { readonly id: PObjectId; readonly spec: PColumnSpec },
|
|
164
|
+
>(
|
|
165
|
+
visibilityByColId: undefined | Map<PObjectId, ColumnVisibilityRule>,
|
|
166
|
+
orderByColId: undefined | Map<PObjectId, ColumnOrderRule>,
|
|
167
|
+
columns: T[],
|
|
168
|
+
): T[] {
|
|
169
|
+
if (visibilityByColId === undefined && orderByColId === undefined) return columns;
|
|
102
170
|
return columns.map((col) => {
|
|
103
171
|
const annotations = { ...col.spec.annotations };
|
|
104
172
|
|
|
105
|
-
const visibility = getEffectiveVisibility(col
|
|
173
|
+
const visibility = getEffectiveVisibility(col, visibilityByColId);
|
|
106
174
|
if (!isNil(visibility)) annotations[Annotation.Table.Visibility] = visibility;
|
|
107
175
|
|
|
108
|
-
const orderPriority = getOrderPriority(col
|
|
176
|
+
const orderPriority = getOrderPriority(col, orderByColId);
|
|
109
177
|
if (!isNil(orderPriority)) annotations[Annotation.Table.OrderPriority] = String(orderPriority);
|
|
110
178
|
|
|
111
179
|
return {
|
|
@@ -114,10 +182,49 @@ export function withTableVisualAnnotations<Data>(
|
|
|
114
182
|
...col.spec,
|
|
115
183
|
annotations: annotations,
|
|
116
184
|
},
|
|
117
|
-
};
|
|
185
|
+
} as T;
|
|
118
186
|
});
|
|
119
187
|
}
|
|
120
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Writes derived info annotations into column annotations.
|
|
191
|
+
* Columns without an info entry are passed through unchanged.
|
|
192
|
+
*/
|
|
193
|
+
export function withInfoAnnotations<
|
|
194
|
+
T extends { readonly id: PObjectId; readonly spec: PColumnSpec },
|
|
195
|
+
>(infoById: undefined | Record<string, string>, columns: T[]): T[] {
|
|
196
|
+
if (isNil(infoById)) return columns;
|
|
197
|
+
return columns.map((col) => {
|
|
198
|
+
const info = infoById[col.id];
|
|
199
|
+
if (isNil(info)) return col;
|
|
200
|
+
return {
|
|
201
|
+
...col,
|
|
202
|
+
spec: {
|
|
203
|
+
...col.spec,
|
|
204
|
+
annotations: { ...col.spec.annotations, [Annotation.Table.Info]: info },
|
|
205
|
+
},
|
|
206
|
+
} as T;
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function withHidenAxesAnnotations<T extends { readonly spec: PColumnSpec }>(
|
|
211
|
+
columns: T[],
|
|
212
|
+
): T[] {
|
|
213
|
+
return columns.map(
|
|
214
|
+
(col) =>
|
|
215
|
+
({
|
|
216
|
+
...col,
|
|
217
|
+
spec: {
|
|
218
|
+
...col.spec,
|
|
219
|
+
axesSpec: col.spec.axesSpec.map((axis) => ({
|
|
220
|
+
...axis,
|
|
221
|
+
annotations: { ...axis.annotations, [Annotation.Table.Visibility]: "hidden" },
|
|
222
|
+
})),
|
|
223
|
+
},
|
|
224
|
+
}) as T,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
121
228
|
/** Column shape required by label derivation. */
|
|
122
229
|
export type LabelableColumn = {
|
|
123
230
|
readonly id: PObjectId;
|
|
@@ -158,3 +265,51 @@ function deriveAxisLabels(
|
|
|
158
265
|
}
|
|
159
266
|
return result;
|
|
160
267
|
}
|
|
268
|
+
|
|
269
|
+
/** Column shape required by tooltip derivation. */
|
|
270
|
+
export type TooltipableColumn = {
|
|
271
|
+
readonly id: DiscoveredPColumnId;
|
|
272
|
+
readonly spec: PColumnSpec;
|
|
273
|
+
readonly originalId: PObjectId;
|
|
274
|
+
readonly linkerPath?: MatchVariant["path"];
|
|
275
|
+
readonly qualifications?: MatchQualifications;
|
|
276
|
+
readonly distinctiveQualifications?: MatchQualifications;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
/** Derive origin tooltips for columns whose qualifications or linker path carry info. */
|
|
280
|
+
export function deriveAllTooltips(options: {
|
|
281
|
+
columns: TooltipableColumn[];
|
|
282
|
+
}): Record<DiscoveredPColumnId, string> {
|
|
283
|
+
const { columns } = options;
|
|
284
|
+
|
|
285
|
+
const variantCountByOriginal = columns.reduce<Map<PObjectId, number>>((acc, c) => {
|
|
286
|
+
return acc.set(c.originalId, (acc.get(c.originalId) ?? 0) + 1);
|
|
287
|
+
}, new Map());
|
|
288
|
+
|
|
289
|
+
const { entries } = columns.reduce(
|
|
290
|
+
({ entries, variantSeen }, c) => {
|
|
291
|
+
const variantCount = variantCountByOriginal.get(c.originalId);
|
|
292
|
+
const variantIndex =
|
|
293
|
+
(variantSeen.set(c.originalId, (variantSeen.get(c.originalId) ?? 0) + 1),
|
|
294
|
+
variantSeen.get(c.originalId));
|
|
295
|
+
|
|
296
|
+
entries.push({
|
|
297
|
+
spec: c.spec,
|
|
298
|
+
linkerPath: c.linkerPath,
|
|
299
|
+
qualifications: c.qualifications,
|
|
300
|
+
distinctiveQualifications: c.distinctiveQualifications,
|
|
301
|
+
variantIndex,
|
|
302
|
+
variantCount,
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
return { entries, variantSeen };
|
|
306
|
+
},
|
|
307
|
+
{ entries: [] as TooltipEntry[], variantSeen: new Map<PObjectId, number>() },
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
const tooltips = deriveDistinctTooltips(entries);
|
|
311
|
+
|
|
312
|
+
return Object.fromEntries(
|
|
313
|
+
tooltips.flatMap((t, i) => (isNil(t) ? [] : [[columns[i].id, t] as const])),
|
|
314
|
+
);
|
|
315
|
+
}
|
|
@@ -4,22 +4,18 @@ import {
|
|
|
4
4
|
isLabelColumn,
|
|
5
5
|
matchAxisId,
|
|
6
6
|
PColumnName,
|
|
7
|
-
Services,
|
|
8
|
-
type RequireServices,
|
|
9
7
|
} from "@milaboratories/pl-model-common";
|
|
10
8
|
import type { PColumnDataUniversal, RenderCtxBase } from "../../render";
|
|
11
9
|
import { ColumnCollectionBuilder, collectCtxColumnSnapshotProviders } from "../../columns";
|
|
12
|
-
import { throwError } from "@milaboratories/helpers";
|
|
13
10
|
|
|
14
11
|
/**
|
|
15
12
|
* Get all label columns visible in the current render context
|
|
16
13
|
* (result pool + block outputs + prerun).
|
|
17
14
|
*/
|
|
18
|
-
export function getAllLabelColumns<A, U
|
|
19
|
-
ctx: RenderCtxBase<A, U
|
|
15
|
+
export function getAllLabelColumns<A, U>(
|
|
16
|
+
ctx: RenderCtxBase<A, U>,
|
|
20
17
|
): PColumn<PColumnDataUniversal>[] {
|
|
21
|
-
const pframeSpec =
|
|
22
|
-
ctx.services.pframeSpec ?? throwError("PFrameSpec service is required for label discovery.");
|
|
18
|
+
const pframeSpec = ctx.getService("pframeSpec");
|
|
23
19
|
const collection = new ColumnCollectionBuilder(pframeSpec)
|
|
24
20
|
.addSources(collectCtxColumnSnapshotProviders(ctx))
|
|
25
21
|
.build({ allowPartialColumnList: true });
|