@platforma-sdk/model 1.76.4 → 1.77.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 +6 -3
- package/dist/columns/column_collection_builder.cjs.map +1 -1
- package/dist/columns/column_collection_builder.js +6 -3
- package/dist/columns/column_collection_builder.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV2.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV2.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV3.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPTableDefV3.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.d.ts +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV2.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.d.ts +1 -1
- package/dist/components/PlDataTable/createPlDataTable/createPlDataTableV3.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/index.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTable/index.js.map +1 -1
- package/dist/components/PlDataTable/createPlDataTableSheet.cjs.map +1 -1
- package/dist/components/PlDataTable/createPlDataTableSheet.d.ts +1 -1
- package/dist/components/PlDataTable/createPlDataTableSheet.js.map +1 -1
- package/dist/components/PlDataTable/index.d.ts +1 -1
- package/dist/components/PlDataTable/state-migration.cjs +32 -1
- package/dist/components/PlDataTable/state-migration.cjs.map +1 -1
- package/dist/components/PlDataTable/state-migration.d.ts +4 -3
- package/dist/components/PlDataTable/state-migration.d.ts.map +1 -1
- package/dist/components/PlDataTable/state-migration.js +33 -2
- package/dist/components/PlDataTable/state-migration.js.map +1 -1
- package/dist/components/PlDataTable/typesV6.d.ts +26 -84
- package/dist/components/PlDataTable/typesV6.d.ts.map +1 -1
- package/dist/components/PlDataTable/typesV7.d.ts +98 -0
- package/dist/components/PlDataTable/typesV7.d.ts.map +1 -0
- package/dist/components/PlDatasetSelector/build_dataset_options.cjs +5 -1
- package/dist/components/PlDatasetSelector/build_dataset_options.cjs.map +1 -1
- package/dist/components/PlDatasetSelector/build_dataset_options.d.ts +6 -1
- package/dist/components/PlDatasetSelector/build_dataset_options.d.ts.map +1 -1
- package/dist/components/PlDatasetSelector/build_dataset_options.js +5 -1
- package/dist/components/PlDatasetSelector/build_dataset_options.js.map +1 -1
- package/dist/components/PlDatasetSelector/filter_discovery.cjs +21 -30
- package/dist/components/PlDatasetSelector/filter_discovery.cjs.map +1 -1
- package/dist/components/PlDatasetSelector/filter_discovery.d.ts +20 -20
- package/dist/components/PlDatasetSelector/filter_discovery.d.ts.map +1 -1
- package/dist/components/PlDatasetSelector/filter_discovery.js +21 -30
- package/dist/components/PlDatasetSelector/filter_discovery.js.map +1 -1
- package/dist/components/PlDatasetSelector/index.d.ts +1 -1
- package/dist/components/index.d.ts +2 -2
- package/dist/index.d.ts +3 -3
- package/dist/package.cjs +1 -1
- package/dist/package.js +1 -1
- package/package.json +5 -5
- package/src/columns/column_collection_builder.ts +12 -3
- package/src/components/PlDataTable/createPlDataTable/createPTableDefV2.ts +1 -1
- package/src/components/PlDataTable/createPlDataTable/createPTableDefV3.ts +1 -1
- package/src/components/PlDataTable/createPlDataTable/createPlDataTableV2.ts +1 -1
- package/src/components/PlDataTable/createPlDataTable/createPlDataTableV3.ts +1 -1
- package/src/components/PlDataTable/createPlDataTable/index.ts +1 -1
- package/src/components/PlDataTable/createPlDataTableSheet.ts +1 -1
- package/src/components/PlDataTable/index.ts +1 -1
- package/src/components/PlDataTable/state-migration.ts +71 -13
- package/src/components/PlDataTable/typesV6.ts +16 -138
- package/src/components/PlDataTable/typesV7.ts +151 -0
- package/src/components/PlDatasetSelector/build_dataset_options.ts +10 -2
- package/src/components/PlDatasetSelector/filter_discovery.test.ts +116 -19
- package/src/components/PlDatasetSelector/filter_discovery.ts +46 -39
|
@@ -1,152 +1,30 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
AxisSpec,
|
|
4
|
-
CanonicalizedJson,
|
|
5
|
-
ListOptionBase,
|
|
6
|
-
PTableColumnSpec,
|
|
7
|
-
PTableSorting,
|
|
8
|
-
PColumnIdAndSpec,
|
|
9
|
-
PTableHandle,
|
|
10
|
-
RootFilterSpec,
|
|
11
|
-
PTableColumnId,
|
|
12
|
-
PFrameHandle,
|
|
13
|
-
} from "@milaboratories/pl-model-common";
|
|
14
|
-
import type { FilterSpecLeaf } from "../../filters";
|
|
15
|
-
import { Nil } from "@milaboratories/helpers";
|
|
1
|
+
import type { CanonicalizedJson, PTableColumnSpec } from "@milaboratories/pl-model-common";
|
|
2
|
+
import type { PlDataTableFiltersWithMeta, PlDataTableSheetState, PTableParamsV2 } from "./typesV7";
|
|
16
3
|
|
|
17
|
-
|
|
4
|
+
/**
|
|
5
|
+
* v6 colId scheme: bare canonicalized `PTableColumnSpec` (full spec including
|
|
6
|
+
* all annotations and `pl7.app/trace`). v7 dropped the spec body and now uses
|
|
7
|
+
* `CanonicalizedJson<PTableColumnId>` — orders of magnitude smaller.
|
|
8
|
+
*/
|
|
9
|
+
export type PlDataTableV6ColIdJson = CanonicalizedJson<PTableColumnSpec>;
|
|
18
10
|
|
|
19
|
-
export type
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
orderedColIds: PlTableColumnIdJson[];
|
|
24
|
-
};
|
|
25
|
-
/** Includes current sort columns and direction */
|
|
26
|
-
sort?: {
|
|
27
|
-
/** Sorted columns and directions in order */
|
|
28
|
-
sortModel: {
|
|
29
|
-
/** Column Id to apply the sort to. */
|
|
30
|
-
colId: PlTableColumnIdJson;
|
|
31
|
-
/** Sort direction */
|
|
32
|
-
sort: "asc" | "desc";
|
|
33
|
-
}[];
|
|
34
|
-
};
|
|
35
|
-
/** Includes column visibility */
|
|
36
|
-
columnVisibility?: {
|
|
37
|
-
/** All colIds which were hidden */
|
|
38
|
-
hiddenColIds: PlTableColumnIdJson[];
|
|
39
|
-
};
|
|
11
|
+
export type PlDataTableGridStateV6 = {
|
|
12
|
+
columnOrder?: { orderedColIds: PlDataTableV6ColIdJson[] };
|
|
13
|
+
sort?: { sortModel: { colId: PlDataTableV6ColIdJson; sort: "asc" | "desc" }[] };
|
|
14
|
+
columnVisibility?: { hiddenColIds: PlDataTableV6ColIdJson[] };
|
|
40
15
|
};
|
|
41
16
|
|
|
42
|
-
export type
|
|
43
|
-
/** spec of the axis to use */
|
|
44
|
-
axis: AxisSpec;
|
|
45
|
-
/** options to show in the filter dropdown */
|
|
46
|
-
options: ListOptionBase<string | number>[];
|
|
47
|
-
/** default (selected) value */
|
|
48
|
-
defaultValue?: string | number;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
export type PlDataTableSheetState = {
|
|
52
|
-
/** id of the axis */
|
|
53
|
-
axisId: AxisId;
|
|
54
|
-
/** selected value */
|
|
55
|
-
value: string | number;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
/** Tree-based filter state compatible with PlAdvancedFilter's RootFilter */
|
|
59
|
-
export type PlDataTableFilterMeta = {
|
|
60
|
-
id: number;
|
|
61
|
-
source?: "table-filter" | "table-search";
|
|
62
|
-
isExpanded?: boolean;
|
|
63
|
-
isSuppressed?: boolean;
|
|
64
|
-
};
|
|
65
|
-
export type PlDataTableFilterSpecLeaf = FilterSpecLeaf<CanonicalizedJson<PTableColumnId>>;
|
|
66
|
-
export type PlDataTableFilters = RootFilterSpec<PlDataTableFilterSpecLeaf>;
|
|
67
|
-
export type PlDataTableFiltersWithMeta = RootFilterSpec<
|
|
68
|
-
PlDataTableFilterSpecLeaf,
|
|
69
|
-
PlDataTableFilterMeta
|
|
70
|
-
>;
|
|
71
|
-
|
|
72
|
-
export type PlDataTableStateV2CacheEntry = {
|
|
73
|
-
/** DataSource identifier for state management */
|
|
17
|
+
export type PlDataTableStateV2V6CacheEntry = {
|
|
74
18
|
sourceId: string;
|
|
75
|
-
|
|
76
|
-
gridState: PlDataTableGridStateCore;
|
|
77
|
-
/** Sheets state */
|
|
19
|
+
gridState: PlDataTableGridStateV6;
|
|
78
20
|
sheetsState: PlDataTableSheetState[];
|
|
79
|
-
/** User filters state (tree-based, compatible with PlAdvancedFilter) */
|
|
80
21
|
filtersState: null | PlDataTableFiltersWithMeta;
|
|
81
|
-
/** Default filters state from model (snapshot of defaults) */
|
|
82
22
|
defaultFiltersState: null | PlDataTableFiltersWithMeta;
|
|
83
|
-
/** Fast search string */
|
|
84
23
|
searchString?: string;
|
|
85
24
|
};
|
|
86
25
|
|
|
87
|
-
export type
|
|
88
|
-
| {
|
|
89
|
-
sourceId: null;
|
|
90
|
-
hiddenColIds: null;
|
|
91
|
-
sorting: [];
|
|
92
|
-
filters: null;
|
|
93
|
-
defaultFilters: null;
|
|
94
|
-
}
|
|
95
|
-
| {
|
|
96
|
-
sourceId: string;
|
|
97
|
-
hiddenColIds: null | PTableColumnId[];
|
|
98
|
-
sorting: PTableSorting[];
|
|
99
|
-
filters: null | PlDataTableFilters;
|
|
100
|
-
defaultFilters: null | PlDataTableFilters;
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
export type PlDataTableStateV2Normalized = {
|
|
104
|
-
/** Version for upgrades */
|
|
26
|
+
export type PlDataTableStateV2V6 = {
|
|
105
27
|
version: 6;
|
|
106
|
-
|
|
107
|
-
stateCache: PlDataTableStateV2CacheEntry[];
|
|
108
|
-
/** PTable params derived from the cache state for the current sourceId */
|
|
28
|
+
stateCache: PlDataTableStateV2V6CacheEntry[];
|
|
109
29
|
pTableParams: PTableParamsV2;
|
|
110
30
|
};
|
|
111
|
-
|
|
112
|
-
/** PlAgDataTable model */
|
|
113
|
-
export type PlDataTableModel = {
|
|
114
|
-
/** DataSource identifier for state management */
|
|
115
|
-
sourceId: null | string;
|
|
116
|
-
/** p-table including all columns, used to show the full specification of the table */
|
|
117
|
-
fullTableHandle?: PTableHandle;
|
|
118
|
-
/** p-frame handle */
|
|
119
|
-
fullPframeHandle?: PFrameHandle;
|
|
120
|
-
/** p-table including only visible columns, used to get the data */
|
|
121
|
-
visibleTableHandle?: PTableHandle;
|
|
122
|
-
/** Default filters from model options, surfaced for UI display */
|
|
123
|
-
defaultFilters?: Nil | PlDataTableFilters;
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
export type CreatePlDataTableOps = {
|
|
127
|
-
/** Filters for columns and non-partitioned axes */
|
|
128
|
-
filters?: PlDataTableFilters;
|
|
129
|
-
|
|
130
|
-
/** Sorting to columns hidden from user */
|
|
131
|
-
sorting?: PTableSorting[];
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Selects columns for which will be inner-joined to the table.
|
|
135
|
-
*
|
|
136
|
-
* Default behaviour: all columns are considered to be core
|
|
137
|
-
*/
|
|
138
|
-
coreColumnPredicate?: (spec: PColumnIdAndSpec) => boolean;
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Determines how core columns should be joined together:
|
|
142
|
-
* inner - so user will only see records present in all core columns
|
|
143
|
-
* full - so user will only see records present in any of the core columns
|
|
144
|
-
*
|
|
145
|
-
* All non-core columns will be left joined to the table produced by the core
|
|
146
|
-
* columns, in other words records form the pool of non-core columns will only
|
|
147
|
-
* make their way into the final table if core table contains corresponding key.
|
|
148
|
-
*
|
|
149
|
-
* Default: 'full'
|
|
150
|
-
*/
|
|
151
|
-
coreJoinType?: "inner" | "full";
|
|
152
|
-
};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AxisId,
|
|
3
|
+
AxisSpec,
|
|
4
|
+
CanonicalizedJson,
|
|
5
|
+
ListOptionBase,
|
|
6
|
+
PTableSorting,
|
|
7
|
+
PColumnIdAndSpec,
|
|
8
|
+
PTableHandle,
|
|
9
|
+
RootFilterSpec,
|
|
10
|
+
PTableColumnId,
|
|
11
|
+
PFrameHandle,
|
|
12
|
+
} from "@milaboratories/pl-model-common";
|
|
13
|
+
import type { FilterSpecLeaf } from "../../filters";
|
|
14
|
+
import { Nil } from "@milaboratories/helpers";
|
|
15
|
+
|
|
16
|
+
export type PlTableColumnIdJson = CanonicalizedJson<PTableColumnId>;
|
|
17
|
+
|
|
18
|
+
export type PlDataTableGridStateCore = {
|
|
19
|
+
/** Includes column ordering */
|
|
20
|
+
columnOrder?: {
|
|
21
|
+
/** All colIds in order */
|
|
22
|
+
orderedColIds: PlTableColumnIdJson[];
|
|
23
|
+
};
|
|
24
|
+
/** Includes current sort columns and direction */
|
|
25
|
+
sort?: {
|
|
26
|
+
/** Sorted columns and directions in order */
|
|
27
|
+
sortModel: {
|
|
28
|
+
/** Column Id to apply the sort to. */
|
|
29
|
+
colId: PlTableColumnIdJson;
|
|
30
|
+
/** Sort direction */
|
|
31
|
+
sort: "asc" | "desc";
|
|
32
|
+
}[];
|
|
33
|
+
};
|
|
34
|
+
/** Includes column visibility */
|
|
35
|
+
columnVisibility?: {
|
|
36
|
+
/** All colIds which were hidden */
|
|
37
|
+
hiddenColIds: PlTableColumnIdJson[];
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type PlDataTableSheet = {
|
|
42
|
+
/** spec of the axis to use */
|
|
43
|
+
axis: AxisSpec;
|
|
44
|
+
/** options to show in the filter dropdown */
|
|
45
|
+
options: ListOptionBase<string | number>[];
|
|
46
|
+
/** default (selected) value */
|
|
47
|
+
defaultValue?: string | number;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type PlDataTableSheetState = {
|
|
51
|
+
/** id of the axis */
|
|
52
|
+
axisId: AxisId;
|
|
53
|
+
/** selected value */
|
|
54
|
+
value: string | number;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** Tree-based filter state compatible with PlAdvancedFilter's RootFilter */
|
|
58
|
+
export type PlDataTableFilterMeta = {
|
|
59
|
+
id: number;
|
|
60
|
+
source?: "table-filter" | "table-search";
|
|
61
|
+
isExpanded?: boolean;
|
|
62
|
+
isSuppressed?: boolean;
|
|
63
|
+
};
|
|
64
|
+
export type PlDataTableFilterSpecLeaf = FilterSpecLeaf<CanonicalizedJson<PTableColumnId>>;
|
|
65
|
+
export type PlDataTableFilters = RootFilterSpec<PlDataTableFilterSpecLeaf>;
|
|
66
|
+
export type PlDataTableFiltersWithMeta = RootFilterSpec<
|
|
67
|
+
PlDataTableFilterSpecLeaf,
|
|
68
|
+
PlDataTableFilterMeta
|
|
69
|
+
>;
|
|
70
|
+
|
|
71
|
+
export type PlDataTableStateV2CacheEntry = {
|
|
72
|
+
/** DataSource identifier for state management */
|
|
73
|
+
sourceId: string;
|
|
74
|
+
/** Internal ag-grid state */
|
|
75
|
+
gridState: PlDataTableGridStateCore;
|
|
76
|
+
/** Sheets state */
|
|
77
|
+
sheetsState: PlDataTableSheetState[];
|
|
78
|
+
/** User filters state (tree-based, compatible with PlAdvancedFilter) */
|
|
79
|
+
filtersState: null | PlDataTableFiltersWithMeta;
|
|
80
|
+
/** Default filters state from model (snapshot of defaults) */
|
|
81
|
+
defaultFiltersState: null | PlDataTableFiltersWithMeta;
|
|
82
|
+
/** Fast search string */
|
|
83
|
+
searchString?: string;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export type PTableParamsV2 =
|
|
87
|
+
| {
|
|
88
|
+
sourceId: null;
|
|
89
|
+
hiddenColIds: null;
|
|
90
|
+
sorting: [];
|
|
91
|
+
filters: null;
|
|
92
|
+
defaultFilters: null;
|
|
93
|
+
}
|
|
94
|
+
| {
|
|
95
|
+
sourceId: string;
|
|
96
|
+
hiddenColIds: null | PTableColumnId[];
|
|
97
|
+
sorting: PTableSorting[];
|
|
98
|
+
filters: null | PlDataTableFilters;
|
|
99
|
+
defaultFilters: null | PlDataTableFilters;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export type PlDataTableStateV2Normalized = {
|
|
103
|
+
/** Version for upgrades */
|
|
104
|
+
version: 7;
|
|
105
|
+
/** Internal states, LRU cache for 5 sourceId-s */
|
|
106
|
+
stateCache: PlDataTableStateV2CacheEntry[];
|
|
107
|
+
/** PTable params derived from the cache state for the current sourceId */
|
|
108
|
+
pTableParams: PTableParamsV2;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/** PlAgDataTable model */
|
|
112
|
+
export type PlDataTableModel = {
|
|
113
|
+
/** DataSource identifier for state management */
|
|
114
|
+
sourceId: null | string;
|
|
115
|
+
/** p-table including all columns, used to show the full specification of the table */
|
|
116
|
+
fullTableHandle?: PTableHandle;
|
|
117
|
+
/** p-frame handle */
|
|
118
|
+
fullPframeHandle?: PFrameHandle;
|
|
119
|
+
/** p-table including only visible columns, used to get the data */
|
|
120
|
+
visibleTableHandle?: PTableHandle;
|
|
121
|
+
/** Default filters from model options, surfaced for UI display */
|
|
122
|
+
defaultFilters?: Nil | PlDataTableFilters;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export type CreatePlDataTableOps = {
|
|
126
|
+
/** Filters for columns and non-partitioned axes */
|
|
127
|
+
filters?: PlDataTableFilters;
|
|
128
|
+
|
|
129
|
+
/** Sorting to columns hidden from user */
|
|
130
|
+
sorting?: PTableSorting[];
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Selects columns for which will be inner-joined to the table.
|
|
134
|
+
*
|
|
135
|
+
* Default behaviour: all columns are considered to be core
|
|
136
|
+
*/
|
|
137
|
+
coreColumnPredicate?: (spec: PColumnIdAndSpec) => boolean;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Determines how core columns should be joined together:
|
|
141
|
+
* inner - so user will only see records present in all core columns
|
|
142
|
+
* full - so user will only see records present in any of the core columns
|
|
143
|
+
*
|
|
144
|
+
* All non-core columns will be left joined to the table produced by the core
|
|
145
|
+
* columns, in other words records form the pool of non-core columns will only
|
|
146
|
+
* make their way into the final table if core table contains corresponding key.
|
|
147
|
+
*
|
|
148
|
+
* Default: 'full'
|
|
149
|
+
*/
|
|
150
|
+
coreJoinType?: "inner" | "full";
|
|
151
|
+
};
|
|
@@ -31,7 +31,11 @@ export type BuildDatasetOptions = {
|
|
|
31
31
|
* accept-all.
|
|
32
32
|
*/
|
|
33
33
|
filter?: SpecPredicateOption;
|
|
34
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Formatting options forwarded to label derivation for both filter and
|
|
36
|
+
* enrichment rows. `formatters.native` on the filter path is overridden
|
|
37
|
+
* — see `FilterMatchOptions.labelOptions`.
|
|
38
|
+
*/
|
|
35
39
|
labelOptions?: DeriveLabelsOptions;
|
|
36
40
|
/**
|
|
37
41
|
* Enables enrichment discovery and filters hits attached to
|
|
@@ -96,7 +100,11 @@ export function buildDatasetOptions(
|
|
|
96
100
|
const filters =
|
|
97
101
|
filterMatches.length === 0
|
|
98
102
|
? undefined
|
|
99
|
-
: filterMatchesToOptions(filterMatches,
|
|
103
|
+
: filterMatchesToOptions(filterMatches, {
|
|
104
|
+
refsByObjectId: refMap,
|
|
105
|
+
datasetSpec,
|
|
106
|
+
labelOptions: opts?.labelOptions,
|
|
107
|
+
});
|
|
100
108
|
|
|
101
109
|
let enrichments;
|
|
102
110
|
if (enrichmentCollection && withEnrichments) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Annotation, createPlRef } from "@milaboratories/pl-model-common";
|
|
2
|
-
import type { AxisSpec, PColumnSpec,
|
|
2
|
+
import type { AxisSpec, PColumnSpec, PObjectId } from "@milaboratories/pl-model-common";
|
|
3
3
|
import { SpecDriver } from "@milaboratories/pf-spec-driver";
|
|
4
4
|
import canonicalize from "canonicalize";
|
|
5
5
|
import { afterEach, describe, expect, test } from "vitest";
|
|
@@ -85,10 +85,7 @@ describe("buildRefMap", () => {
|
|
|
85
85
|
test("maps canonicalized PlRef to original ref", () => {
|
|
86
86
|
const ref1 = createPlRef("b1", "out1");
|
|
87
87
|
const ref2 = createPlRef("b2", "out2", true);
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
const map = buildRefMap(entries);
|
|
91
|
-
|
|
88
|
+
const map = buildRefMap([{ ref: ref1 }, { ref: ref2 }]);
|
|
92
89
|
expect(map.get(canonicalize(ref1)! as PObjectId)).toBe(ref1);
|
|
93
90
|
expect(map.get(canonicalize(ref2)! as PObjectId)).toBe(ref2);
|
|
94
91
|
expect(map.size).toBe(2);
|
|
@@ -104,18 +101,10 @@ describe("filterMatchesToOptions", () => {
|
|
|
104
101
|
const filterRef1 = createPlRef("b1", "filter-top1000");
|
|
105
102
|
const filterRef2 = createPlRef("b1", "filter-highconf");
|
|
106
103
|
|
|
107
|
-
|
|
108
|
-
const refMap = buildRefMap([
|
|
109
|
-
{ ref: anchorSnap.id as unknown as PlRef }, // anchor — won't be looked up
|
|
110
|
-
{ ref: filterRef1 },
|
|
111
|
-
{ ref: filterRef2 },
|
|
112
|
-
]);
|
|
113
|
-
|
|
114
|
-
// Build filter specs with isSubset annotation
|
|
104
|
+
const refMap = buildRefMap([{ ref: filterRef1 }, { ref: filterRef2 }]);
|
|
115
105
|
const filterSpec1 = spec("filter1", [axis("sample")], { [Annotation.IsSubset]: "true" });
|
|
116
106
|
const filterSpec2 = spec("filter2", [axis("sample")], { [Annotation.IsSubset]: "true" });
|
|
117
107
|
|
|
118
|
-
// Use the canonical PlRef as the PObjectId (matches how result pool works)
|
|
119
108
|
const f1Snap = snap(canonicalize(filterRef1)! as string, filterSpec1);
|
|
120
109
|
const f2Snap = snap(canonicalize(filterRef2)! as string, filterSpec2);
|
|
121
110
|
|
|
@@ -126,7 +115,10 @@ describe("filterMatchesToOptions", () => {
|
|
|
126
115
|
const matches = findFilterColumns(collection);
|
|
127
116
|
expect(matches.length).toBe(2);
|
|
128
117
|
|
|
129
|
-
const options = filterMatchesToOptions(matches,
|
|
118
|
+
const options = filterMatchesToOptions(matches, {
|
|
119
|
+
refsByObjectId: refMap,
|
|
120
|
+
datasetSpec: anchorSpec,
|
|
121
|
+
});
|
|
130
122
|
expect(options).toHaveLength(2);
|
|
131
123
|
// Each option has a ref and label
|
|
132
124
|
for (const opt of options) {
|
|
@@ -136,11 +128,114 @@ describe("filterMatchesToOptions", () => {
|
|
|
136
128
|
}
|
|
137
129
|
});
|
|
138
130
|
|
|
131
|
+
test("single filter: dataset name prefixes the discriminating trace step", () => {
|
|
132
|
+
// Regression: without the dataset in the input, deriveDistinctLabels
|
|
133
|
+
// picks the highest-importance step it sees — the dataset's own
|
|
134
|
+
// `samples-and-data/dataset` step (importance 100) — and the filter
|
|
135
|
+
// shows "Bulk" instead of "Top 10". Appending the dataset forces the
|
|
136
|
+
// algorithm to include the lower-importance lead-selection step too.
|
|
137
|
+
const datasetSpec = spec("dataset", [axis("sample"), axis("gene")], {
|
|
138
|
+
[Annotation.Label]: "Bulk",
|
|
139
|
+
[Annotation.Trace]: JSON.stringify([
|
|
140
|
+
{ type: "milaboratories.samples-and-data/dataset", label: "Bulk", importance: 100 },
|
|
141
|
+
{ type: "milaboratories.mixcr-clonotyping", label: "MiXCR", importance: 20 },
|
|
142
|
+
]),
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const filterRef = createPlRef("b1", "filter");
|
|
146
|
+
const refMap = buildRefMap([{ ref: filterRef }]);
|
|
147
|
+
const filterSpec = spec("filter", [axis("sample")], {
|
|
148
|
+
[Annotation.IsSubset]: "true",
|
|
149
|
+
[Annotation.Label]: "Selected Leads",
|
|
150
|
+
[Annotation.Trace]: JSON.stringify([
|
|
151
|
+
{ type: "milaboratories.samples-and-data/dataset", label: "Bulk", importance: 100 },
|
|
152
|
+
{ type: "milaboratories.mixcr-clonotyping", label: "MiXCR", importance: 20 },
|
|
153
|
+
{ type: "milaboratories.antibody-tcr-lead-selection", label: "Top 10", importance: 30 },
|
|
154
|
+
]),
|
|
155
|
+
});
|
|
156
|
+
const fSnap = snap(canonicalize(filterRef)! as string, filterSpec);
|
|
157
|
+
|
|
158
|
+
const builder = new ColumnCollectionBuilder(createSpecFrameCtx());
|
|
159
|
+
builder.addSource([fSnap, anchorSnap]);
|
|
160
|
+
const collection = builder.build({ anchors: { main: anchorSpec } })!;
|
|
161
|
+
|
|
162
|
+
const matches = findFilterColumns(collection);
|
|
163
|
+
expect(matches).toHaveLength(1);
|
|
164
|
+
|
|
165
|
+
const options = filterMatchesToOptions(matches, { refsByObjectId: refMap, datasetSpec });
|
|
166
|
+
expect(options).toHaveLength(1);
|
|
167
|
+
expect(options[0].label).toBe("Bulk / Top 10");
|
|
168
|
+
|
|
169
|
+
// Caller's `formatters.native` must NOT override the internal
|
|
170
|
+
// suppression — otherwise the algorithm short-circuits on the
|
|
171
|
+
// inherited "Selected Leads" label.
|
|
172
|
+
const withCustomNative = filterMatchesToOptions(matches, {
|
|
173
|
+
refsByObjectId: refMap,
|
|
174
|
+
datasetSpec,
|
|
175
|
+
labelOptions: { formatters: { native: (l) => `<<${l}>>` } },
|
|
176
|
+
});
|
|
177
|
+
expect(withCustomNative[0].label).toBe("Bulk / Top 10");
|
|
178
|
+
|
|
179
|
+
// Non-native label options (here `separator`) flow through.
|
|
180
|
+
const withSeparator = filterMatchesToOptions(matches, {
|
|
181
|
+
refsByObjectId: refMap,
|
|
182
|
+
datasetSpec,
|
|
183
|
+
labelOptions: { separator: " :: " },
|
|
184
|
+
});
|
|
185
|
+
expect(withSeparator[0].label).toBe("Bulk :: Top 10");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("multiple filters with shared dataset trace disambiguate by filter-specific steps", () => {
|
|
189
|
+
const datasetSpec = spec("dataset", [axis("sample"), axis("gene")], {
|
|
190
|
+
[Annotation.Label]: "Bulk",
|
|
191
|
+
[Annotation.Trace]: JSON.stringify([
|
|
192
|
+
{ type: "milaboratories.samples-and-data/dataset", label: "Bulk", importance: 100 },
|
|
193
|
+
{ type: "milaboratories.mixcr-clonotyping", label: "MiXCR", importance: 20 },
|
|
194
|
+
]),
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const ref1 = createPlRef("b1", "f1");
|
|
198
|
+
const ref2 = createPlRef("b2", "f2");
|
|
199
|
+
const refMap = buildRefMap([{ ref: ref1 }, { ref: ref2 }]);
|
|
200
|
+
const filterSpec1 = spec("filter1", [axis("sample")], {
|
|
201
|
+
[Annotation.IsSubset]: "true",
|
|
202
|
+
[Annotation.Label]: "Selected Leads",
|
|
203
|
+
[Annotation.Trace]: JSON.stringify([
|
|
204
|
+
{ type: "milaboratories.samples-and-data/dataset", label: "Bulk", importance: 100 },
|
|
205
|
+
{ type: "milaboratories.mixcr-clonotyping", label: "MiXCR", importance: 20 },
|
|
206
|
+
{ type: "milaboratories.antibody-tcr-lead-selection", label: "Top 10", importance: 30 },
|
|
207
|
+
]),
|
|
208
|
+
});
|
|
209
|
+
const filterSpec2 = spec("filter2", [axis("sample")], {
|
|
210
|
+
[Annotation.IsSubset]: "true",
|
|
211
|
+
[Annotation.Label]: "Selected Leads",
|
|
212
|
+
[Annotation.Trace]: JSON.stringify([
|
|
213
|
+
{ type: "milaboratories.samples-and-data/dataset", label: "Bulk", importance: 100 },
|
|
214
|
+
{ type: "milaboratories.mixcr-clonotyping", label: "MiXCR", importance: 20 },
|
|
215
|
+
{ type: "milaboratories.antibody-tcr-lead-selection", label: "Top 11", importance: 30 },
|
|
216
|
+
]),
|
|
217
|
+
});
|
|
218
|
+
const f1Snap = snap(canonicalize(ref1)! as string, filterSpec1);
|
|
219
|
+
const f2Snap = snap(canonicalize(ref2)! as string, filterSpec2);
|
|
220
|
+
|
|
221
|
+
const builder = new ColumnCollectionBuilder(createSpecFrameCtx());
|
|
222
|
+
builder.addSource([f1Snap, f2Snap, anchorSnap]);
|
|
223
|
+
const collection = builder.build({ anchors: { main: anchorSpec } })!;
|
|
224
|
+
|
|
225
|
+
const matches = findFilterColumns(collection);
|
|
226
|
+
expect(matches).toHaveLength(2);
|
|
227
|
+
|
|
228
|
+
const options = filterMatchesToOptions(matches, { refsByObjectId: refMap, datasetSpec });
|
|
229
|
+
expect(options.map((o) => o.label).sort()).toEqual(["Bulk / Top 10", "Bulk / Top 11"]);
|
|
230
|
+
});
|
|
231
|
+
|
|
139
232
|
test("returns empty array for empty matches", () => {
|
|
140
|
-
expect(
|
|
233
|
+
expect(
|
|
234
|
+
filterMatchesToOptions([], { refsByObjectId: new Map(), datasetSpec: anchorSpec }),
|
|
235
|
+
).toEqual([]);
|
|
141
236
|
});
|
|
142
237
|
|
|
143
|
-
test("skips entries whose
|
|
238
|
+
test("skips entries whose id is not in refsByObjectId", () => {
|
|
144
239
|
const knownRef = createPlRef("b1", "known");
|
|
145
240
|
const knownSpec = spec("known", [axis("sample")], { [Annotation.IsSubset]: "true" });
|
|
146
241
|
const orphanSpec = spec("orphan", [axis("sample")], { [Annotation.IsSubset]: "true" });
|
|
@@ -153,9 +248,11 @@ describe("filterMatchesToOptions", () => {
|
|
|
153
248
|
|
|
154
249
|
const matches = findFilterColumns(collection);
|
|
155
250
|
expect(matches.length).toBe(2);
|
|
156
|
-
|
|
157
251
|
const refMap = buildRefMap([{ ref: knownRef }]);
|
|
158
|
-
const options = filterMatchesToOptions(matches,
|
|
252
|
+
const options = filterMatchesToOptions(matches, {
|
|
253
|
+
refsByObjectId: refMap,
|
|
254
|
+
datasetSpec: anchorSpec,
|
|
255
|
+
});
|
|
159
256
|
expect(options).toHaveLength(1);
|
|
160
257
|
expect(options[0].ref).toBe(knownRef);
|
|
161
258
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Annotation } from "@milaboratories/pl-model-common";
|
|
2
|
-
import type { Option, PlRef, PObjectId } from "@milaboratories/pl-model-common";
|
|
2
|
+
import type { Option, PlRef, PObjectId, PObjectSpec } from "@milaboratories/pl-model-common";
|
|
3
3
|
import canonicalize from "canonicalize";
|
|
4
4
|
import type {
|
|
5
5
|
AnchoredColumnCollection,
|
|
@@ -12,12 +12,8 @@ import {
|
|
|
12
12
|
} from "../../labels/derive_distinct_labels";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* The axes-subset constraint is enforced by `mode: "enrichment"`, which sets
|
|
18
|
-
* `allowFloatingHitAxes: false` — every axis of the matched column must be
|
|
19
|
-
* present in the anchor's axes. See `matchingModeToConstraints()` in
|
|
20
|
-
* `column_collection_builder.ts`.
|
|
15
|
+
* Columns annotated `pl7.app/isSubset: "true"` whose axes ⊆ anchor axes.
|
|
16
|
+
* The axes-subset constraint comes from `mode: "enrichment"`.
|
|
21
17
|
*/
|
|
22
18
|
export function findFilterColumns(collection: AnchoredColumnCollection): ColumnMatch[] {
|
|
23
19
|
return collection.findColumns({
|
|
@@ -28,51 +24,62 @@ export function findFilterColumns(collection: AnchoredColumnCollection): ColumnM
|
|
|
28
24
|
});
|
|
29
25
|
}
|
|
30
26
|
|
|
27
|
+
export type FilterMatchOptions = {
|
|
28
|
+
/** Maps result-pool column id back to its source PlRef (see {@link buildRefMap}). */
|
|
29
|
+
refsByObjectId: ReadonlyMap<PObjectId, PlRef>;
|
|
30
|
+
/** Spec of the dataset the filters are subsets of. */
|
|
31
|
+
datasetSpec: PObjectSpec;
|
|
32
|
+
/**
|
|
33
|
+
* Forwarded to `deriveDistinctLabels`. Any `formatters.native` caller
|
|
34
|
+
* sets is silently overridden — the function relies on a no-op native
|
|
35
|
+
* formatter to keep the algorithm from short-circuiting on filters'
|
|
36
|
+
* inherited `pl7.app/label`.
|
|
37
|
+
*/
|
|
38
|
+
labelOptions?: DeriveLabelsOptions;
|
|
39
|
+
};
|
|
40
|
+
|
|
31
41
|
/**
|
|
32
|
-
* Derive
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* skipped — they cannot be exposed as user-selectable options.
|
|
36
|
-
*
|
|
37
|
-
* @param matches - from findFilterColumns()
|
|
38
|
-
* @param refsByObjectId - from {@link buildRefMap}
|
|
39
|
-
* @param labelOptions - forwarded to deriveDistinctLabels()
|
|
42
|
+
* Derive labels for filter column matches (for `DatasetOption.filters`).
|
|
43
|
+
* Matches whose column id is missing from `refsByObjectId` are silently
|
|
44
|
+
* dropped — they cannot be exposed as selectable options.
|
|
40
45
|
*/
|
|
41
46
|
export function filterMatchesToOptions(
|
|
42
47
|
matches: ColumnMatch[],
|
|
43
|
-
|
|
44
|
-
labelOptions?: DeriveLabelsOptions,
|
|
48
|
+
options: FilterMatchOptions,
|
|
45
49
|
): Option[] {
|
|
46
50
|
if (matches.length === 0) return [];
|
|
47
51
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
|
|
52
|
+
const { refsByObjectId, datasetSpec, labelOptions } = options;
|
|
53
|
+
|
|
54
|
+
// One entry per match-variant (different paths to the same column are
|
|
55
|
+
// exposed as separate Options). The `ref` field rides along on the
|
|
56
|
+
// Entry-shaped objects via structural typing; `deriveDistinctLabels`
|
|
57
|
+
// ignores extra fields.
|
|
58
|
+
const entries = matches.flatMap((match): (Entry & { ref: PlRef })[] => {
|
|
54
59
|
const ref = refsByObjectId.get(match.column.id);
|
|
55
60
|
if (ref === undefined) return [];
|
|
56
|
-
return match.variants.map((variant) => ({
|
|
61
|
+
return match.variants.map((variant) => ({
|
|
62
|
+
ref,
|
|
63
|
+
spec: match.column.spec,
|
|
64
|
+
linkerPath: variant.path.map((p) => ({ spec: p.linker.spec })),
|
|
65
|
+
}));
|
|
57
66
|
});
|
|
58
67
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
// Appending the dataset forces a discriminating trace step into every
|
|
69
|
+
// filter label (yielding e.g. "Bulk / Top 10"); the dataset's own label
|
|
70
|
+
// is dropped because we only zip `entries`. Native label is force-
|
|
71
|
+
// suppressed (the override sits after caller's spread) — filters
|
|
72
|
+
// inherit the dataset's `pl7.app/label` and would otherwise satisfy
|
|
73
|
+
// uniqueness before any trace step is consulted.
|
|
74
|
+
const labels = deriveDistinctLabels([...entries, { spec: datasetSpec }], {
|
|
75
|
+
...labelOptions,
|
|
76
|
+
formatters: { ...labelOptions?.formatters, native: () => undefined },
|
|
77
|
+
});
|
|
65
78
|
|
|
66
|
-
return
|
|
79
|
+
return entries.map(({ ref }, i) => ({ ref, label: labels[i] }));
|
|
67
80
|
}
|
|
68
81
|
|
|
69
|
-
/**
|
|
70
|
-
* Usage: `buildRefMap(ctx.resultPool.getSpecs().entries)`
|
|
71
|
-
*/
|
|
82
|
+
/** Build the `refsByObjectId` map from `ctx.resultPool.getSpecs().entries`. */
|
|
72
83
|
export function buildRefMap(entries: readonly { readonly ref: PlRef }[]): Map<PObjectId, PlRef> {
|
|
73
|
-
|
|
74
|
-
for (const entry of entries) {
|
|
75
|
-
map.set(canonicalize(entry.ref)! as PObjectId, entry.ref);
|
|
76
|
-
}
|
|
77
|
-
return map;
|
|
84
|
+
return new Map(entries.map((e) => [canonicalize(e.ref)! as PObjectId, e.ref]));
|
|
78
85
|
}
|