@platforma-sdk/model 1.68.8 → 1.70.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.
Files changed (75) hide show
  1. package/dist/columns/column_collection_builder.cjs +1 -4
  2. package/dist/columns/column_collection_builder.cjs.map +1 -1
  3. package/dist/columns/column_collection_builder.d.ts +0 -2
  4. package/dist/columns/column_collection_builder.d.ts.map +1 -1
  5. package/dist/columns/column_collection_builder.js +1 -4
  6. package/dist/columns/column_collection_builder.js.map +1 -1
  7. package/dist/columns/ctx_column_sources.cjs +5 -8
  8. package/dist/columns/ctx_column_sources.cjs.map +1 -1
  9. package/dist/columns/ctx_column_sources.d.ts +4 -7
  10. package/dist/columns/ctx_column_sources.d.ts.map +1 -1
  11. package/dist/columns/ctx_column_sources.js +5 -8
  12. package/dist/columns/ctx_column_sources.js.map +1 -1
  13. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.cjs +23 -44
  14. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.cjs.map +1 -1
  15. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.d.ts.map +1 -1
  16. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.js +23 -44
  17. package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.js.map +1 -1
  18. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.cjs +2 -7
  19. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.cjs.map +1 -1
  20. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.d.ts.map +1 -1
  21. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.js +2 -7
  22. package/dist/components/PlDataTable/createPlDataTable/discoverColumns.js.map +1 -1
  23. package/dist/components/PlDataTable/createPlDataTable/utils.cjs +3 -18
  24. package/dist/components/PlDataTable/createPlDataTable/utils.cjs.map +1 -1
  25. package/dist/components/PlDataTable/createPlDataTable/utils.d.ts.map +1 -1
  26. package/dist/components/PlDataTable/createPlDataTable/utils.js +4 -19
  27. package/dist/components/PlDataTable/createPlDataTable/utils.js.map +1 -1
  28. package/dist/components/PlDataTable/labels.cjs +0 -25
  29. package/dist/components/PlDataTable/labels.cjs.map +1 -1
  30. package/dist/components/PlDataTable/labels.js +2 -26
  31. package/dist/components/PlDataTable/labels.js.map +1 -1
  32. package/dist/components/PlDatasetSelector/build_dataset_options.cjs +23 -11
  33. package/dist/components/PlDatasetSelector/build_dataset_options.cjs.map +1 -1
  34. package/dist/components/PlDatasetSelector/build_dataset_options.d.ts +9 -2
  35. package/dist/components/PlDatasetSelector/build_dataset_options.d.ts.map +1 -1
  36. package/dist/components/PlDatasetSelector/build_dataset_options.js +22 -11
  37. package/dist/components/PlDatasetSelector/build_dataset_options.js.map +1 -1
  38. package/dist/components/PlDatasetSelector/dataset_selection.cjs +20 -0
  39. package/dist/components/PlDatasetSelector/dataset_selection.cjs.map +1 -0
  40. package/dist/components/PlDatasetSelector/dataset_selection.d.ts +23 -0
  41. package/dist/components/PlDatasetSelector/dataset_selection.d.ts.map +1 -0
  42. package/dist/components/PlDatasetSelector/dataset_selection.js +19 -0
  43. package/dist/components/PlDatasetSelector/dataset_selection.js.map +1 -0
  44. package/dist/components/PlDatasetSelector/enrichment_discovery.cjs +75 -0
  45. package/dist/components/PlDatasetSelector/enrichment_discovery.cjs.map +1 -0
  46. package/dist/components/PlDatasetSelector/enrichment_discovery.js +73 -0
  47. package/dist/components/PlDatasetSelector/enrichment_discovery.js.map +1 -0
  48. package/dist/components/PlDatasetSelector/index.cjs +1 -0
  49. package/dist/components/PlDatasetSelector/index.d.ts +1 -0
  50. package/dist/components/PlDatasetSelector/index.js +1 -0
  51. package/dist/components/index.cjs +1 -0
  52. package/dist/components/index.d.ts +1 -0
  53. package/dist/components/index.js +1 -0
  54. package/dist/index.cjs +3 -0
  55. package/dist/index.d.ts +2 -1
  56. package/dist/index.js +2 -1
  57. package/dist/labels/derive_distinct_tooltips.cjs +0 -3
  58. package/dist/labels/derive_distinct_tooltips.cjs.map +1 -1
  59. package/dist/labels/derive_distinct_tooltips.js +0 -3
  60. package/dist/labels/derive_distinct_tooltips.js.map +1 -1
  61. package/dist/package.cjs +1 -1
  62. package/dist/package.js +1 -1
  63. package/package.json +8 -8
  64. package/src/columns/column_collection_builder.ts +0 -3
  65. package/src/columns/ctx_column_sources.ts +6 -12
  66. package/src/components/PlDataTable/createPlDataTable/createPlDataTableV3.ts +3 -43
  67. package/src/components/PlDataTable/createPlDataTable/discoverColumns.ts +0 -10
  68. package/src/components/PlDataTable/createPlDataTable/utils.test.ts +5 -131
  69. package/src/components/PlDataTable/createPlDataTable/utils.ts +2 -26
  70. package/src/components/PlDatasetSelector/build_dataset_options.ts +48 -17
  71. package/src/components/PlDatasetSelector/dataset_selection.ts +37 -0
  72. package/src/components/PlDatasetSelector/enrichment_discovery.ts +111 -0
  73. package/src/components/PlDatasetSelector/index.ts +1 -0
  74. package/src/labels/derive_distinct_tooltips.test.ts +6 -16
  75. package/src/labels/derive_distinct_tooltips.ts +0 -3
@@ -21,7 +21,6 @@ import type { PlDataTableFilters, PlDataTableFilterSpecLeaf, PlDataTableModel }
21
21
  import { upgradePlDataTableStateV2 } from "../state-migration";
22
22
  import type { PlDataTableStateV2 } from "../state-migration";
23
23
  import type { ColumnSelector, ColumnSnapshot, ColumnVariant, MatchingMode } from "../../../columns";
24
- import { getAllLabelColumns, getMatchingLabelColumns } from "../labels";
25
24
  import type { DeriveLabelsOptions } from "../../../labels/derive_distinct_labels";
26
25
  import {
27
26
  deriveAllLabels,
@@ -97,11 +96,6 @@ export function createPlDataTableV3<A, U>(
97
96
 
98
97
  const splited = splitDiscoveredColumns(discovered);
99
98
 
100
- const labelColumns = getMatchingLabelColumns(
101
- [...splited.direct, ...splited.linked].map((v) => v.column),
102
- getAllLabelColumns(ctx),
103
- );
104
-
105
99
  const derivedLabels = deriveAllLabels({
106
100
  columns: discovered.map((dc) => ({
107
101
  id: dc.column.id,
@@ -109,7 +103,6 @@ export function createPlDataTableV3<A, U>(
109
103
  linkerPath: dc.path,
110
104
  qualifications: dc.qualifications,
111
105
  })),
112
- labelColumns,
113
106
  deriveLabelsOptions: {
114
107
  includeNativeLabel: true,
115
108
  ...options.labelsOptions,
@@ -129,7 +122,6 @@ export function createPlDataTableV3<A, U>(
129
122
  const annotated = annotateColumnGroups({
130
123
  pframeSpec,
131
124
  ...splited,
132
- labelColumns,
133
125
  derivedLabels,
134
126
  derivedTooltips,
135
127
  displayOptions: options.displayOptions,
@@ -143,7 +135,6 @@ export function createPlDataTableV3<A, U>(
143
135
  const columnIsAvailable = createColumnValidationById([
144
136
  ...annotated.direct.map((v) => v.column),
145
137
  ...annotated.linked.flatMap((lc) => [...lc.path.map((s) => s.linker), lc.column]),
146
- ...annotated.labels,
147
138
  ]);
148
139
 
149
140
  const remapedDefaultFilters = remapFilterColumnIds(options.filters, discovered);
@@ -165,7 +156,6 @@ export function createPlDataTableV3<A, U>(
165
156
  const secondaryGroups: SecondaryGroup<undefined | PColumnDataUniversal>[] = buildSecondaryGroups(
166
157
  secondarySnapshots,
167
158
  annotated.linked,
168
- annotated.labels,
169
159
  );
170
160
  const fullDef = createPTableDefV3({
171
161
  primaryJoinType,
@@ -181,7 +171,6 @@ export function createPlDataTableV3<A, U>(
181
171
  const pframeHandle = ctx.createPFrame([
182
172
  ...annotated.direct.map((v) => resolveSnapshot(v.column)),
183
173
  ...annotated.linked.map((v) => resolveSnapshot(v.column)),
184
- ...annotated.labels,
185
174
  ...collectLinkerSnapshots(annotated.linked).map(resolveSnapshot),
186
175
  ]);
187
176
 
@@ -193,14 +182,13 @@ export function createPlDataTableV3<A, U>(
193
182
  hiddenSpecs,
194
183
  );
195
184
 
196
- const visible = buildVisibleColumns(annotated, hiddenColumnIds, labelColumns);
185
+ const visible = buildVisibleColumns(annotated, hiddenColumnIds);
197
186
  const visibleDef = createPTableDefV3({
198
187
  primaryJoinType,
199
188
  primary: primaryEntries,
200
189
  secondary: buildSecondaryGroups(
201
190
  visible.direct.filter((c) => !c.isPrimary),
202
191
  visible.linked,
203
- visible.labels,
204
192
  ),
205
193
  filters,
206
194
  sorting,
@@ -229,13 +217,11 @@ type SplitDiscoveredColumns = {
229
217
  type AnnotatedColumnGroups = {
230
218
  readonly direct: TableColumnVariant[];
231
219
  readonly linked: TableColumnVariant[];
232
- readonly labels: PColumn<PColumnDataUniversal>[];
233
220
  };
234
221
 
235
222
  type VisibleColumns = {
236
223
  readonly direct: TableColumnVariant[];
237
224
  readonly linked: TableColumnVariant[];
238
- readonly labels: PColumn<PColumnDataUniversal>[];
239
225
  };
240
226
 
241
227
  /** Split discovered columns into direct (no linker path) and linked (with linker path). */
@@ -262,26 +248,16 @@ function collectLinkerSnapshots(linked: TableColumnVariant[]): ColumnSnapshot<PO
262
248
  function annotateColumnGroups(params: {
263
249
  direct: TableColumnVariant[];
264
250
  linked: TableColumnVariant[];
265
- labelColumns: PColumn<PColumnDataUniversal>[];
266
251
  derivedLabels: Record<string, string>;
267
252
  derivedTooltips: Record<string, string>;
268
253
  displayOptions?: ColumnsDisplayOptions;
269
254
  pframeSpec: PFrameSpecDriver;
270
255
  }): AnnotatedColumnGroups {
271
- const {
272
- direct,
273
- linked,
274
- labelColumns,
275
- derivedLabels,
276
- derivedTooltips,
277
- displayOptions,
278
- pframeSpec,
279
- } = params;
256
+ const { direct, linked, derivedLabels, derivedTooltips, displayOptions, pframeSpec } = params;
280
257
 
281
258
  const allColumnsForRules = [
282
259
  ...direct.map((v) => v.column),
283
260
  ...linked.map((v) => v.column),
284
- ...labelColumns,
285
261
  ...collectLinkerSnapshots(linked),
286
262
  ];
287
263
  const visibilityByColId = evaluateRules(
@@ -314,15 +290,9 @@ function annotateColumnGroups(params: {
314
290
  ),
315
291
  ).map((lc) => ({ ...lc, path: annotateLinkerPath(derivedLabels, lc.path) }));
316
292
 
317
- const labelColumnsAnnotated = flow(
318
- (cols: PColumn<PColumnDataUniversal>[]) => withHidenAxesAnnotations(cols),
319
- (cols) => withLabelAnnotations(derivedLabels, cols),
320
- )(labelColumns);
321
-
322
293
  return {
323
294
  direct: directAnnotated,
324
295
  linked: linkedAnnotated,
325
- labels: labelColumnsAnnotated,
326
296
  };
327
297
  }
328
298
 
@@ -422,7 +392,6 @@ function validateSorting(sorting: PTableSorting[], isValidColumnId: (id: string)
422
392
  function buildSecondaryGroups(
423
393
  direct: TableColumnVariant[],
424
394
  linked: TableColumnVariant[],
425
- labels: PColumn<PColumnDataUniversal>[],
426
395
  ): SecondaryGroup<undefined | PColumnDataUniversal>[] {
427
396
  return [
428
397
  ...direct.map(
@@ -436,16 +405,12 @@ function buildSecondaryGroups(
436
405
  entries: [
437
406
  ...lc.path.map((s) => ({
438
407
  column: resolveSnapshot(s.linker),
439
- qualifications: s.qualifications,
440
408
  })),
441
409
  { column: resolveSnapshot(lc.column), qualifications: lc.qualifications.forHit },
442
410
  ],
443
411
  primaryQualifications: lc.qualifications.forQueries,
444
412
  }),
445
413
  ),
446
- ...labels.map(
447
- (c): SecondaryGroup<undefined | PColumnDataUniversal> => ({ entries: [{ column: c }] }),
448
- ),
449
414
  ];
450
415
  }
451
416
 
@@ -490,15 +455,10 @@ function collectPreservedColumnIds(
490
455
  function buildVisibleColumns(
491
456
  annotated: AnnotatedColumnGroups,
492
457
  hiddenColumns: Set<PObjectId>,
493
- originalLabelColumns: PColumn<PColumnDataUniversal>[],
494
458
  ): VisibleColumns {
495
459
  const direct = annotated.direct.filter((c) => !hiddenColumns.has(c.column.id));
496
460
  const linked = annotated.linked.filter((c) => !hiddenColumns.has(c.column.id));
497
- const labels = getMatchingLabelColumns(
498
- [...direct, ...linked].map((v) => v.column),
499
- originalLabelColumns,
500
- );
501
- return { direct, linked, labels };
461
+ return { direct, linked };
502
462
  }
503
463
 
504
464
  /** Resolve a ColumnSnapshot to a PColumn with lazily-evaluated data. */
@@ -13,7 +13,6 @@ import { toColumnSnapshotProvider } from "../../../columns/column_snapshot_provi
13
13
  import { collectCtxColumnSnapshotProviders } from "../../../columns/ctx_column_sources";
14
14
  import { throwError } from "@milaboratories/helpers";
15
15
  import type { ColumnsSelectorConfig, TableColumnVariant } from "./createPlDataTableV3";
16
- import { isNil } from "es-toolkit";
17
16
 
18
17
  export type DiscoverTableColumnOptions = {
19
18
  sources?: ColumnSource[];
@@ -45,14 +44,6 @@ export function discoverTableColumnSnaphots(
45
44
  try {
46
45
  const variants = collection.findColumnVariants({
47
46
  ...(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
47
  });
57
48
  const anchors = collection.getAnchors();
58
49
  return mapToTableColumnVariants(variants, anchors);
@@ -111,7 +102,6 @@ function mapToTableColumnVariants(
111
102
  path: variant.path.map((p) => ({
112
103
  type: "linker",
113
104
  column: p.linker.id,
114
- qualifications: p.qualifications,
115
105
  })),
116
106
  columnQualifications: variant.qualifications.forHit,
117
107
  queriesQualifications: variant.qualifications.forQueries,
@@ -1,11 +1,4 @@
1
- import {
2
- Annotation,
3
- canonicalizeJson,
4
- getAxisId,
5
- type AxisId,
6
- type PColumnSpec,
7
- type PObjectId,
8
- } from "@milaboratories/pl-model-common";
1
+ import { Annotation, type PColumnSpec, type PObjectId } from "@milaboratories/pl-model-common";
9
2
  import { SpecDriver } from "@milaboratories/pf-spec-driver";
10
3
  import { describe, expect, test } from "vitest";
11
4
  import { deriveAllLabels, evaluateRules, type LabelableColumn, type RuleColumn } from "./utils";
@@ -38,24 +31,6 @@ function makeLabelableColumn(
38
31
  };
39
32
  }
40
33
 
41
- function makeLabelColumn(
42
- axisSpec: PColumnSpec["axesSpec"][0],
43
- label: string,
44
- ): { readonly spec: PColumnSpec } {
45
- return {
46
- spec: makeSpec({
47
- name: "label-col",
48
- valueType: "String",
49
- annotations: { [Annotation.Label]: label },
50
- axesSpec: [axisSpec],
51
- }),
52
- };
53
- }
54
-
55
- function axisKey(axis: PColumnSpec["axesSpec"][0]): string {
56
- return canonicalizeJson<AxisId>(getAxisId(axis));
57
- }
58
-
59
34
  // ---------------------------------------------------------------------------
60
35
  // deriveAllLabels
61
36
  // ---------------------------------------------------------------------------
@@ -75,7 +50,6 @@ describe("deriveAllLabels", () => {
75
50
 
76
51
  const result = deriveAllLabels({
77
52
  columns,
78
- labelColumns: [],
79
53
  deriveLabelsOptions: { includeNativeLabel: true },
80
54
  });
81
55
 
@@ -83,7 +57,7 @@ describe("deriveAllLabels", () => {
83
57
  expect(result["c2"]).toBe("Beta");
84
58
  });
85
59
 
86
- test("returns axis labels from label columns when available", () => {
60
+ test("does not produce axis labels", () => {
87
61
  const axis = { type: "String", name: "sample" } as const;
88
62
  const columns: LabelableColumn[] = [
89
63
  makeLabelableColumn("c1", {
@@ -92,56 +66,17 @@ describe("deriveAllLabels", () => {
92
66
  annotations: { [Annotation.Label]: "Score" },
93
67
  }),
94
68
  ];
95
- const labelColumns = [makeLabelColumn(axis, "Sample Name")];
96
-
97
- const result = deriveAllLabels({
98
- columns,
99
- labelColumns,
100
- deriveLabelsOptions: { includeNativeLabel: true },
101
- });
102
-
103
- expect(result[axisKey(axis)]).toBe("Sample Name");
104
- });
105
-
106
- test("falls back to axis annotation when no label column matches", () => {
107
- const axis = { type: "String", name: "gene" } as const;
108
- const columns: LabelableColumn[] = [
109
- makeLabelableColumn("c1", {
110
- name: "expr",
111
- axesSpec: [{ ...axis, annotations: { [Annotation.Label]: "Gene ID" } }],
112
- annotations: { [Annotation.Label]: "Expression" },
113
- }),
114
- ];
115
-
116
- const result = deriveAllLabels({
117
- columns,
118
- labelColumns: [],
119
- deriveLabelsOptions: { includeNativeLabel: true },
120
- });
121
-
122
- expect(result[axisKey(axis)]).toBe("Gene ID");
123
- });
124
-
125
- test("axis with no label annotation and no label column gets 'Unlabeled'", () => {
126
- const axis = { type: "Int", name: "idx" } as const;
127
- const columns: LabelableColumn[] = [
128
- makeLabelableColumn("c1", {
129
- name: "val",
130
- axesSpec: [axis],
131
- annotations: { [Annotation.Label]: "Value" },
132
- }),
133
- ];
134
69
 
135
70
  const result = deriveAllLabels({
136
71
  columns,
137
- labelColumns: [],
138
72
  deriveLabelsOptions: { includeNativeLabel: true },
139
73
  });
140
74
 
141
- expect(result[axisKey(axis)]).toBe("Unlabeled");
75
+ expect(Object.keys(result)).toEqual(["c1"]);
76
+ expect(result["c1"]).toBe("Score");
142
77
  });
143
78
 
144
- test("deduplicates axes shared across columns", () => {
79
+ test("distinct labels for columns sharing an axis", () => {
145
80
  const sharedAxis = { type: "String", name: "sample" } as const;
146
81
  const columns: LabelableColumn[] = [
147
82
  makeLabelableColumn("c1", {
@@ -155,75 +90,15 @@ describe("deriveAllLabels", () => {
155
90
  annotations: { [Annotation.Label]: "Score 2" },
156
91
  }),
157
92
  ];
158
- const labelColumns = [makeLabelColumn(sharedAxis, "Sample")];
159
93
 
160
94
  const result = deriveAllLabels({
161
95
  columns,
162
- labelColumns,
163
96
  deriveLabelsOptions: { includeNativeLabel: true },
164
97
  });
165
98
 
166
- // axis label appears once
167
- expect(result[axisKey(sharedAxis)]).toBe("Sample");
168
- // column labels are distinct
169
99
  expect(result["c1"]).toBe("Score 1");
170
100
  expect(result["c2"]).toBe("Score 2");
171
101
  });
172
- });
173
-
174
- // ---------------------------------------------------------------------------
175
- // deriveAxisLabels (tested through deriveAllLabels)
176
- // ---------------------------------------------------------------------------
177
-
178
- describe("deriveAxisLabels via deriveAllLabels", () => {
179
- test("multiple axes each get their own label", () => {
180
- const axis1 = { type: "String", name: "sample" } as const;
181
- const axis2 = { type: "String", name: "chain" } as const;
182
- const columns: LabelableColumn[] = [
183
- makeLabelableColumn("c1", {
184
- name: "value",
185
- axesSpec: [axis1, axis2],
186
- annotations: { [Annotation.Label]: "Value" },
187
- }),
188
- ];
189
- const labelColumns = [
190
- makeLabelColumn(axis1, "Sample ID"),
191
- makeLabelColumn(axis2, "Chain Type"),
192
- ];
193
-
194
- const result = deriveAllLabels({
195
- columns,
196
- labelColumns,
197
- deriveLabelsOptions: { includeNativeLabel: true },
198
- });
199
-
200
- expect(result[axisKey(axis1)]).toBe("Sample ID");
201
- expect(result[axisKey(axis2)]).toBe("Chain Type");
202
- });
203
-
204
- test("label column overrides axis annotation", () => {
205
- const axis = {
206
- type: "String",
207
- name: "sample",
208
- annotations: { [Annotation.Label]: "Axis Annotation Label" },
209
- } as const;
210
- const columns: LabelableColumn[] = [
211
- makeLabelableColumn("c1", {
212
- name: "v",
213
- axesSpec: [axis],
214
- annotations: { [Annotation.Label]: "V" },
215
- }),
216
- ];
217
- const labelColumns = [makeLabelColumn({ type: "String", name: "sample" }, "Label Col Label")];
218
-
219
- const result = deriveAllLabels({
220
- columns,
221
- labelColumns,
222
- deriveLabelsOptions: { includeNativeLabel: true },
223
- });
224
-
225
- expect(result[axisKey(axis)]).toBe("Label Col Label");
226
- });
227
102
 
228
103
  // Regression: LabelableColumn.linkerPath used to be `linkerPath` while
229
104
  // deriveDistinctLabels.Entry expected `linkersPath`. Structural typing +
@@ -249,7 +124,6 @@ describe("deriveAxisLabels via deriveAllLabels", () => {
249
124
 
250
125
  const result = deriveAllLabels({
251
126
  columns,
252
- labelColumns: [],
253
127
  deriveLabelsOptions: { includeNativeLabel: true },
254
128
  });
255
129
 
@@ -5,9 +5,7 @@ import {
5
5
  type PObjectId,
6
6
  Annotation,
7
7
  canonicalizeAxisId,
8
- canonicalizeJson,
9
8
  DiscoveredPColumnId,
10
- getAxisId,
11
9
  readAnnotation,
12
10
  readAnnotationJson,
13
11
  } from "@milaboratories/pl-model-common";
@@ -236,16 +234,13 @@ export type LabelableColumn = {
236
234
  /** Derive labels for all table elements: columns via deriveDistinctLabels, axes from label columns. */
237
235
  export function deriveAllLabels(options: {
238
236
  columns: LabelableColumn[];
239
- labelColumns: { readonly spec: PColumnSpec }[];
240
237
  deriveLabelsOptions?: DeriveLabelsOptions;
241
238
  }): Record<string, string> {
242
- const { columns, labelColumns, deriveLabelsOptions } = options;
243
- const axisLabels = deriveAxisLabels(columns, labelColumns);
239
+ const { columns, deriveLabelsOptions } = options;
244
240
  const entries = columns.map((c) => ({
245
241
  spec: c.spec,
246
242
  linkerPath: c.linkerPath?.map((step) => ({
247
243
  spec: step.linker.spec,
248
- qualifications: step.qualifications,
249
244
  })),
250
245
  qualifications: c.qualifications,
251
246
  }));
@@ -253,26 +248,7 @@ export function deriveAllLabels(options: {
253
248
  (acc, label, index) => ((acc[columns[index].id] = label), acc),
254
249
  {} as Record<string, string>,
255
250
  );
256
- return { ...axisLabels, ...columnLabels };
257
- }
258
-
259
- /** Derive axis labels from matching label columns, falling back to axis spec annotations. */
260
- function deriveAxisLabels(
261
- columns: { readonly spec: PColumnSpec }[],
262
- labelColumns: { readonly spec: PColumnSpec }[],
263
- ): Record<string, string> {
264
- const axisSpecMap = new Map(
265
- columns.flatMap((c) => c.spec.axesSpec).map((a) => [canonicalizeJson(getAxisId(a)), a]),
266
- );
267
- const result: Record<string, string> = {};
268
- for (const axisKey of axisSpecMap.keys()) {
269
- const labelCol = labelColumns.find(
270
- (lc) => canonicalizeJson(getAxisId(lc.spec.axesSpec[0])) === axisKey,
271
- );
272
- const source = labelCol?.spec ?? axisSpecMap.get(axisKey);
273
- result[axisKey] = readAnnotation(source ?? {}, Annotation.Label)?.trim() ?? "Unlabeled";
274
- }
275
- return result;
251
+ return columnLabels;
276
252
  }
277
253
 
278
254
  /** Column shape required by tooltip derivation. */
@@ -1,20 +1,25 @@
1
- import type {
2
- DatasetOption,
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
- selector?: PColumnSelector | PColumnSelector[] | ((spec: PObjectSpec) => boolean);
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 predicate = opts?.selector ?? (() => true);
31
- const options = ctx.resultPool.getOptions(predicate, { refsWithEnrichments: true });
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((o: Option): DatasetOption => {
39
- const datasetSpec = ctx.resultPool.getPColumnSpecByRef(o.ref);
40
- if (!datasetSpec) return o;
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 o;
56
+ if (!collection) return { primary };
46
57
 
47
58
  try {
48
- const matches = findFilterColumns(collection);
49
- if (matches.length === 0) return o;
50
- const filters = filterMatchesToOptions(matches, refMap, opts?.labelOptions);
51
- return { ...o, filters };
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
+ }