@platforma-sdk/model 1.20.27 → 1.21.10

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/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const PlatformaSDKVersion = "1.20.27";
1
+ export declare const PlatformaSDKVersion = "1.21.10";
2
2
  //# sourceMappingURL=version.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platforma-sdk/model",
3
- "version": "1.20.27",
3
+ "version": "1.21.10",
4
4
  "description": "Platforma.bio SDK / Block Model",
5
5
  "types": "./dist/index.d.ts",
6
6
  "main": "./dist/index.js",
@@ -0,0 +1,141 @@
1
+ import {test, expect, describe} from '@jest/globals';
2
+ import {getAdditionalColumns} from './PFrameForGraphs';
3
+ import {PColumn, PColumnSpec, PColumnValues, PObjectId} from "@milaboratories/pl-model-common";
4
+ import {TreeNodeAccessor} from "../render";
5
+
6
+
7
+ describe('PFrameForGraph', () => {
8
+ test('columns are compatible, no additional columns', () => {
9
+ const columnSpec1:PColumnSpec = {
10
+ kind: 'PColumn',
11
+ name: 'column1',
12
+ valueType: 'Int',
13
+ axesSpec: [
14
+ {type: 'String', name: 'axis1', domain: {}},
15
+ {type: 'Int', name: 'axis2', domain: {}}
16
+ ]
17
+ }
18
+ const columnSpec2:PColumnSpec = {
19
+ kind: 'PColumn',
20
+ name: 'column2',
21
+ valueType: 'Int',
22
+ axesSpec: [{type: 'String', name: 'axis1', domain: {}}]
23
+ }
24
+ const columns:PColumn<TreeNodeAccessor | PColumnValues>[] = [{
25
+ id: 'id1' as PObjectId,
26
+ spec: columnSpec1,
27
+ data: []
28
+ }, {
29
+ id: 'id2' as PObjectId,
30
+ spec: columnSpec2,
31
+ data: []
32
+ }
33
+ ] as PColumn<PColumnValues>[]
34
+
35
+ expect(getAdditionalColumns(columns).length).toEqual(0);
36
+ });
37
+
38
+ test('columns are not compatible, 1 additional column', () => {
39
+ const columnSpec1:PColumnSpec = {
40
+ kind: 'PColumn',
41
+ name: 'column1',
42
+ valueType: 'Int',
43
+ axesSpec: [{type: 'String', name: 'axis1', domain: {key1: 'a'}}]
44
+ }
45
+ const columnSpec2:PColumnSpec = {
46
+ kind: 'PColumn',
47
+ name: 'column2',
48
+ valueType: 'Int',
49
+ axesSpec: [{type: 'String', name: 'axis1', domain: {}}]
50
+ }
51
+ const columns:PColumn<TreeNodeAccessor | PColumnValues>[] = [
52
+ {id: 'id1' as PObjectId, spec: columnSpec1, data: []},
53
+ {id: 'id2' as PObjectId, spec: columnSpec2, data: []}
54
+ ] as PColumn<PColumnValues>[]
55
+
56
+ const additionalColumns = getAdditionalColumns(columns);
57
+ expect(additionalColumns.length).toEqual(1);
58
+ expect(additionalColumns[0].id === columns[0].id).toEqual(false);
59
+ expect(additionalColumns[0].id === columns[1].id).toEqual(false);
60
+ expect(additionalColumns[0].spec).toEqual({
61
+ kind: 'PColumn',
62
+ name: 'column2',
63
+ valueType: 'Int',
64
+ axesSpec: [{type: 'String', name: 'axis1', domain: {key1: 'a'}}]
65
+ });
66
+ });
67
+ test('columns are not compatible, additional columns are impossible', () => {
68
+ const columnSpec1:PColumnSpec = {
69
+ kind: 'PColumn',
70
+ name: 'column1',
71
+ valueType: 'Int',
72
+ axesSpec: [{type: 'String', name: 'axis1', domain: {key1: 'a'}}]
73
+ }
74
+ const columnSpec2:PColumnSpec = {
75
+ kind: 'PColumn',
76
+ name: 'column2',
77
+ valueType: 'Int',
78
+ axesSpec: [{type: 'String', name: 'axis1', domain: {key2: 'b'}}]
79
+ }
80
+ const columns:PColumn<TreeNodeAccessor | PColumnValues>[] = [
81
+ {id: 'id1' as PObjectId, spec: columnSpec1, data: []},
82
+ {id: 'id2' as PObjectId, spec: columnSpec2, data: []}
83
+ ] as PColumn<PColumnValues>[]
84
+
85
+ const additionalColumns = getAdditionalColumns(columns);
86
+ expect(additionalColumns.length).toEqual(0);
87
+ });
88
+ test('columns are not compatible, 2 additional columns', () => {
89
+ const columnSpec1:PColumnSpec = {
90
+ kind: 'PColumn',
91
+ name: 'column1',
92
+ valueType: 'Int',
93
+ axesSpec: [
94
+ {type: 'String', name: 'axis1', domain: {key1: 'a'}},
95
+ {type: 'String', name: 'axis1', domain: {key1: 'b'}},
96
+ ]
97
+ }
98
+ const columnSpec2:PColumnSpec = {
99
+ kind: 'PColumn',
100
+ name: 'column2',
101
+ valueType: 'Int',
102
+ axesSpec: [{type: 'String', name: 'axis1', domain: {}}]
103
+ }
104
+ const columns:PColumn<TreeNodeAccessor | PColumnValues>[] = [
105
+ {id: 'id1' as PObjectId, spec: columnSpec1, data: []},
106
+ {id: 'id2' as PObjectId, spec: columnSpec2, data: []}
107
+ ] as PColumn<PColumnValues>[]
108
+
109
+ const additionalColumns = getAdditionalColumns(columns);
110
+ expect(additionalColumns.length).toEqual(2);
111
+ });
112
+ test('columns are not compatible, 4 additional columns - by 2 axes', () => {
113
+ const columnSpec1:PColumnSpec = {
114
+ kind: 'PColumn',
115
+ name: 'column1',
116
+ valueType: 'Int',
117
+ axesSpec: [
118
+ {type: 'String', name: 'axis1', domain: {key1: 'a'}},
119
+ {type: 'String', name: 'axis1', domain: {key1: 'b'}},
120
+ {type: 'String', name: 'axis2', domain: {key1: 'a'}},
121
+ {type: 'String', name: 'axis2', domain: {key1: 'b'}},
122
+ ]
123
+ }
124
+ const columnSpec2:PColumnSpec = {
125
+ kind: 'PColumn',
126
+ name: 'column2',
127
+ valueType: 'Int',
128
+ axesSpec: [
129
+ {type: 'String', name: 'axis1', domain: {}},
130
+ {type: 'String', name: 'axis2', domain: {}}
131
+ ]
132
+ }
133
+ const columns:PColumn<TreeNodeAccessor | PColumnValues>[] = [
134
+ {id: 'id1' as PObjectId, spec: columnSpec1, data: []},
135
+ {id: 'id2' as PObjectId, spec: columnSpec2, data: []}
136
+ ] as PColumn<PColumnValues>[]
137
+
138
+ const additionalColumns = getAdditionalColumns(columns);
139
+ expect(additionalColumns.length).toEqual(4);
140
+ });
141
+ })
@@ -0,0 +1,112 @@
1
+ import {
2
+ AxisId,
3
+ getAxisId,
4
+ matchAxisId,
5
+ PColumn,
6
+ PColumnValues, PFrameHandle,
7
+ PObjectId,
8
+ } from '@milaboratories/pl-model-common';
9
+ import { RenderCtx, TreeNodeAccessor } from '../render';
10
+
11
+ /** Create id for column copy with added keys in axes domains */
12
+ const colId = (id: PObjectId, domains: (Record<string, string> | undefined)[]) => {
13
+ let wid = id.toString();
14
+ domains?.forEach(domain => {
15
+ if (domain) {
16
+ for (const [k, v] of Object.entries(domain)) {
17
+ wid += k;
18
+ wid += v;
19
+ }
20
+ }
21
+ });
22
+ return wid;
23
+ };
24
+
25
+ /** All combinations with 1 key from each list */
26
+ function getKeysCombinations(idsLists: AxisId[][]) {
27
+ if (!idsLists.length) {
28
+ return [];
29
+ }
30
+ let result: AxisId[][] = [[]];
31
+ idsLists.forEach(list => {
32
+ const nextResult: AxisId[][] = [];
33
+ list.forEach(key => {
34
+ nextResult.push(...result.map(resultItem => [...resultItem, key]));
35
+ });
36
+ result = nextResult;
37
+ });
38
+ return result;
39
+ }
40
+
41
+ /** Main column can have additional domains, if secondary column (meta-column) has all axes match main column axes
42
+ we can add its copy with missed domain fields for compatibility */
43
+ function getAdditionalColumnsForPair(
44
+ mainColumn: PColumn<TreeNodeAccessor | PColumnValues>,
45
+ secondaryColumn: PColumn<TreeNodeAccessor | PColumnValues>
46
+ ): PColumn<TreeNodeAccessor | PColumnValues>[] {
47
+ const mainAxesIds = mainColumn.spec.axesSpec.map(getAxisId);
48
+ const secondaryAxesIds = secondaryColumn.spec.axesSpec.map(getAxisId);
49
+
50
+ const isFullCompatible = secondaryAxesIds.every(id => mainAxesIds.some(mainId => matchAxisId(mainId, id) && matchAxisId(id, mainId)));
51
+ if (isFullCompatible) { // in this case it isn't necessary to add more columns
52
+ return [];
53
+ }
54
+ const isCompatible = secondaryAxesIds.every(id => mainAxesIds.some(mainId => matchAxisId(mainId, id)));
55
+ if (!isCompatible) { // in this case it is impossible to add some compatible column
56
+ return [];
57
+ }
58
+ // options with different possible domains for every axis of secondary column
59
+ const secondaryIdsOptions = secondaryAxesIds.map(id => {
60
+ return mainAxesIds.filter(mainId => matchAxisId(mainId, id));
61
+ });
62
+ // all possible combinations of axes with added domains
63
+ const secondaryIdsVariants = getKeysCombinations(secondaryIdsOptions);
64
+
65
+ return secondaryIdsVariants.map(idsList => {
66
+ const id = colId(secondaryColumn.id, idsList.map(id => id.domain));
67
+ return {
68
+ id: id as PObjectId,
69
+ spec: {
70
+ ...secondaryColumn.spec,
71
+ axesSpec: idsList.map((axisId, idx) => ({
72
+ ...axisId,
73
+ annotations: secondaryColumn.spec.axesSpec[idx].annotations
74
+ }))
75
+ },
76
+ data: secondaryColumn.data
77
+ };
78
+ });
79
+ }
80
+
81
+ export function getAdditionalColumns(columns: PColumn<TreeNodeAccessor | PColumnValues>[]):PColumn<TreeNodeAccessor | PColumnValues>[] {
82
+ const additionalColumns: PColumn<TreeNodeAccessor | PColumnValues>[] = [];
83
+ for (let i = 0; i < columns.length; i++) {
84
+ for (let j = i + 1; j < columns.length; j++) {
85
+ const column1 = columns[i];
86
+ const column2 = columns[j];
87
+
88
+ // check if column 1 is meta for column 2 or backward
89
+ additionalColumns.push(
90
+ ...getAdditionalColumnsForPair(column1, column2),
91
+ ...getAdditionalColumnsForPair(column2, column1)
92
+ );
93
+ }
94
+ }
95
+ return additionalColumns;
96
+ }
97
+
98
+ export function createPFrameForGraphs<A, U>(
99
+ ctx: RenderCtx<A, U>,
100
+ columns: PColumn<TreeNodeAccessor | PColumnValues>[]
101
+ ): PFrameHandle | undefined {
102
+ const extendedColumns = [...columns, ...getAdditionalColumns(columns)];
103
+ // if at least one column is not yet ready, we can't show the table
104
+ if (
105
+ extendedColumns.some(
106
+ (a) => a.data instanceof TreeNodeAccessor && !a.data.getIsReadyOrError()
107
+ )
108
+ )
109
+ return undefined;
110
+
111
+ return ctx.createPFrame(extendedColumns);
112
+ }
@@ -6,6 +6,7 @@ import {
6
6
  matchAxisId,
7
7
  PColumn,
8
8
  PColumnIdAndSpec,
9
+ PColumnSpec,
9
10
  PColumnValues,
10
11
  PObjectId,
11
12
  PTableHandle,
@@ -282,6 +283,31 @@ export type PlTableFiltersModel = {
282
283
  filters?: PTableRecordFilter[];
283
284
  };
284
285
 
286
+ export type CreatePlDataTableOps = {
287
+ /** Table filters, should contain */
288
+ filters?: PTableRecordFilter[];
289
+
290
+ /**
291
+ * Selects columns for which will be inner-joined to the table.
292
+ *
293
+ * Default behaviour: all columns are considered to be core
294
+ */
295
+ coreColumnPredicate?: (spec: PColumnSpec) => boolean;
296
+
297
+ /**
298
+ * Determines how core columns should be joined together:
299
+ * inner - so user will only see records present in all core columns
300
+ * full - so user will only see records present in any of the core columns
301
+ *
302
+ * All non-core columns will be left joined to the table produced by the core
303
+ * columns, in other words records form the pool of non-core columns will only
304
+ * make their way into the final table if core table contins corresponding key.
305
+ *
306
+ * Default: 'full'
307
+ */
308
+ coreJoinType?: 'inner' | 'full';
309
+ };
310
+
285
311
  /**
286
312
  * Create p-table handle given ui table state
287
313
  *
@@ -293,9 +319,32 @@ export type PlTableFiltersModel = {
293
319
  export function createPlDataTable<A, U>(
294
320
  ctx: RenderCtx<A, U>,
295
321
  columns: PColumn<TreeNodeAccessor | PColumnValues>[],
296
- tableState?: PlDataTableState,
297
- filters?: PTableRecordFilter[]
322
+ tableState: PlDataTableState | undefined
323
+ ): PTableHandle | undefined;
324
+ export function createPlDataTable<A, U>(
325
+ ctx: RenderCtx<A, U>,
326
+ columns: PColumn<TreeNodeAccessor | PColumnValues>[],
327
+ tableState: PlDataTableState | undefined,
328
+ ops: CreatePlDataTableOps
329
+ ): PTableHandle | undefined;
330
+ /** @deprecated use method with extended ops as the last argument */
331
+ export function createPlDataTable<A, U>(
332
+ ctx: RenderCtx<A, U>,
333
+ columns: PColumn<TreeNodeAccessor | PColumnValues>[],
334
+ tableState: PlDataTableState | undefined,
335
+ filters: PTableRecordFilter[]
336
+ ): PTableHandle | undefined;
337
+ export function createPlDataTable<A, U>(
338
+ ctx: RenderCtx<A, U>,
339
+ columns: PColumn<TreeNodeAccessor | PColumnValues>[],
340
+ tableState: PlDataTableState | undefined,
341
+ ops?: PTableRecordFilter[] | CreatePlDataTableOps
298
342
  ): PTableHandle | undefined {
343
+ // ops migration for backward compatibility with previous deprecated API
344
+ if (Array.isArray(ops)) {
345
+ ops = { filters: ops };
346
+ }
347
+
299
348
  const allLabelCols = ctx.resultPool
300
349
  .getData()
301
350
  .entries.map((d) => d.obj)
@@ -351,16 +400,28 @@ export function createPlDataTable<A, U>(
351
400
  )
352
401
  return undefined;
353
402
 
403
+ let coreColumns = columns;
404
+ const secondaryColumns: typeof columns = [];
405
+
406
+ if (ops?.coreColumnPredicate) {
407
+ coreColumns = [];
408
+ for (const c of columns)
409
+ if (ops.coreColumnPredicate(c.spec)) coreColumns.push(c);
410
+ else secondaryColumns.push(c);
411
+ }
412
+
413
+ secondaryColumns.push(...labelColumns.values());
414
+
354
415
  return ctx.createPTable({
355
416
  src: {
356
417
  type: 'outer',
357
418
  primary: {
358
- type: 'full',
359
- entries: columns.map((c) => ({ type: 'column', column: c }))
419
+ type: ops?.coreJoinType ?? 'full',
420
+ entries: coreColumns.map((c) => ({ type: 'column', column: c }))
360
421
  },
361
- secondary: [...labelColumns.values()].map((c) => ({ type: 'column', column: c }))
422
+ secondary: secondaryColumns.map((c) => ({ type: 'column', column: c }))
362
423
  },
363
- filters: [...(tableState?.pTableParams?.filters ?? []), ...(filters ?? [])],
424
+ filters: [...(ops?.filters ?? []), ...(tableState?.pTableParams?.filters ?? [])],
364
425
  sorting: tableState?.pTableParams?.sorting ?? []
365
426
  });
366
427
  }
@@ -1 +1,2 @@
1
1
  export * from './PlDataTable';
2
+ export * from './PFrameForGraphs';