@platforma-sdk/model 1.45.35 → 1.45.45

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 (67) hide show
  1. package/dist/annotations/converter.cjs +30 -14
  2. package/dist/annotations/converter.cjs.map +1 -1
  3. package/dist/annotations/converter.d.ts.map +1 -1
  4. package/dist/annotations/converter.js +30 -14
  5. package/dist/annotations/converter.js.map +1 -1
  6. package/dist/components/PFrameForGraphs.cjs +12 -27
  7. package/dist/components/PFrameForGraphs.cjs.map +1 -1
  8. package/dist/components/PFrameForGraphs.d.ts +4 -3
  9. package/dist/components/PFrameForGraphs.d.ts.map +1 -1
  10. package/dist/components/PFrameForGraphs.js +12 -28
  11. package/dist/components/PFrameForGraphs.js.map +1 -1
  12. package/dist/filters/converter.cjs +4 -2
  13. package/dist/filters/converter.cjs.map +1 -1
  14. package/dist/filters/converter.d.ts.map +1 -1
  15. package/dist/filters/converter.js +4 -2
  16. package/dist/filters/converter.js.map +1 -1
  17. package/dist/filters/types.d.ts +1 -1
  18. package/dist/filters/types.d.ts.map +1 -1
  19. package/dist/index.cjs +10 -0
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.ts +1 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +2 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/internal.cjs.map +1 -1
  26. package/dist/internal.js.map +1 -1
  27. package/dist/package.json.cjs +1 -1
  28. package/dist/package.json.js +1 -1
  29. package/dist/pframe.cjs +11 -5
  30. package/dist/pframe.cjs.map +1 -1
  31. package/dist/pframe.d.ts +2 -1
  32. package/dist/pframe.d.ts.map +1 -1
  33. package/dist/pframe.js +11 -5
  34. package/dist/pframe.js.map +1 -1
  35. package/dist/pframe_utils/index.cjs +294 -0
  36. package/dist/pframe_utils/index.cjs.map +1 -0
  37. package/dist/pframe_utils/index.d.ts +48 -0
  38. package/dist/pframe_utils/index.d.ts.map +1 -0
  39. package/dist/pframe_utils/index.js +285 -0
  40. package/dist/pframe_utils/index.js.map +1 -0
  41. package/dist/render/api.cjs.map +1 -1
  42. package/dist/render/api.d.ts +2 -2
  43. package/dist/render/api.d.ts.map +1 -1
  44. package/dist/render/api.js.map +1 -1
  45. package/dist/render/util/column_collection.cjs.map +1 -1
  46. package/dist/render/util/column_collection.d.ts +11 -6
  47. package/dist/render/util/column_collection.d.ts.map +1 -1
  48. package/dist/render/util/column_collection.js.map +1 -1
  49. package/dist/render/util/pcolumn_data.cjs +10 -6
  50. package/dist/render/util/pcolumn_data.cjs.map +1 -1
  51. package/dist/render/util/pcolumn_data.d.ts +3 -3
  52. package/dist/render/util/pcolumn_data.d.ts.map +1 -1
  53. package/dist/render/util/pcolumn_data.js +10 -6
  54. package/dist/render/util/pcolumn_data.js.map +1 -1
  55. package/package.json +11 -10
  56. package/src/annotations/converter.ts +35 -15
  57. package/src/components/PFrameForGraphs.ts +19 -37
  58. package/src/filters/converter.ts +4 -1
  59. package/src/filters/types.ts +1 -1
  60. package/src/global.d.ts +2 -2
  61. package/src/index.ts +1 -0
  62. package/src/internal.ts +2 -2
  63. package/src/pframe.ts +13 -6
  64. package/src/pframe_utils/index.ts +442 -0
  65. package/src/render/api.ts +6 -4
  66. package/src/render/util/column_collection.ts +13 -5
  67. package/src/render/util/pcolumn_data.ts +15 -10
@@ -23,8 +23,8 @@ import {
23
23
  readAnnotationJson,
24
24
  stringifyJson,
25
25
  } from '@milaboratories/pl-model-common';
26
- import type { PColumnDataUniversal, RenderCtx } from '../render';
27
- import { allPColumnsReady, PColumnCollection } from '../render';
26
+ import type { PColumnDataUniversal, PColumnEntryUniversal, PColumnEntryWithLabel, RenderCtx } from '../render';
27
+ import { PColumnCollection } from '../render';
28
28
 
29
29
  /** Create id for column copy with added keys in axes domains */
30
30
  const colId = (id: PObjectId, domains: (Record<string, string> | undefined)[]) => {
@@ -60,10 +60,14 @@ export function isHiddenFromGraphColumn(column: PColumnSpec): boolean {
60
60
  return !!readAnnotationJson(column, Annotation.HideDataFromGraphs);
61
61
  }
62
62
 
63
+ export function isHiddenFromUIColumn(column: PColumnSpec): boolean {
64
+ return !!readAnnotationJson(column, Annotation.HideDataFromUi);
65
+ }
66
+
63
67
  type AxesVault = Map<CanonicalizedJson<AxisId>, AxisSpecNormalized>;
64
68
 
65
69
  export function getAvailableWithLinkersAxes(
66
- linkerColumns: PColumn<PColumnDataUniversal>[],
70
+ linkerColumns: (PColumnEntryWithLabel | PColumnEntryUniversal)[],
67
71
  blockAxes: AxesVault,
68
72
  ): AxesVault {
69
73
  const linkerMap = LinkerMap.fromColumns(linkerColumns.map(getColumnIdAndSpec));
@@ -90,18 +94,14 @@ export function getAvailableWithLinkersAxes(
90
94
  }));
91
95
  }
92
96
  /** Add columns with fully compatible axes created from partial compatible ones */
93
- export function enrichCompatible(blockAxes: AxesVault, columns: PColumn<PColumnDataUniversal>[]) {
94
- const result: PColumn<PColumnDataUniversal>[] = [];
95
- columns.forEach((column) => {
96
- result.push(...getAdditionalColumnsForColumn(blockAxes, column));
97
- });
98
- return result;
97
+ export function enrichCompatible<T extends Omit<PColumn<PColumnDataUniversal>, 'data'>>(blockAxes: AxesVault, columns: T[]): T[] {
98
+ return columns.flatMap((column) => getAdditionalColumnsForColumn(blockAxes, column));
99
99
  }
100
100
 
101
- function getAdditionalColumnsForColumn(
101
+ function getAdditionalColumnsForColumn<T extends Omit<PColumn<PColumnDataUniversal>, 'data'>>(
102
102
  blockAxes: AxesVault,
103
- column: PColumn<PColumnDataUniversal>,
104
- ): PColumn<PColumnDataUniversal>[] {
103
+ column: T,
104
+ ): T[] {
105
105
  const columnAxesIds = column.spec.axesSpec.map(getAxisId);
106
106
 
107
107
  if (columnAxesIds.every((id) => blockAxes.has(canonicalizeJson(id)))) {
@@ -168,6 +168,7 @@ function getAdditionalColumnsForColumn(
168
168
  }
169
169
 
170
170
  return {
171
+ ...column,
171
172
  id: id as PObjectId,
172
173
  spec: {
173
174
  ...column.spec,
@@ -177,7 +178,6 @@ function getAdditionalColumnsForColumn(
177
178
  })),
178
179
  annotations,
179
180
  },
180
- data: column.data,
181
181
  };
182
182
  });
183
183
 
@@ -200,16 +200,12 @@ export function createPFrameForGraphs<A, U>(
200
200
  ctx: RenderCtx<A, U>,
201
201
  blockColumns?: PColumn<PColumnDataUniversal>[],
202
202
  ): PFrameHandle | undefined {
203
+ const suitableSpec = (spec: PColumnSpec) => !isHiddenFromUIColumn(spec) && !isHiddenFromGraphColumn(spec);
203
204
  // if current block doesn't produce own columns then use all columns from result pool
204
205
  if (!blockColumns) {
205
206
  const columns = new PColumnCollection();
206
207
  columns.addColumnProvider(ctx.resultPool);
207
-
208
- const allColumns = columns.getColumns((spec) => !isHiddenFromGraphColumn(spec), { dontWaitAllData: true, overrideLabelAnnotation: false }) ?? [];
209
- // if at least one column is not yet ready, we can't show the graph
210
- if (!allPColumnsReady(allColumns)) {
211
- return undefined;
212
- }
208
+ const allColumns = columns.getUniversalEntries(suitableSpec, { dontWaitAllData: true, overrideLabelAnnotation: false }) ?? [];
213
209
 
214
210
  const allAxes: AxesVault = new Map(allColumns
215
211
  .flatMap((column) => getNormalizedAxesList(column.spec.axesSpec))
@@ -224,10 +220,6 @@ export function createPFrameForGraphs<A, U>(
224
220
  return ctx.createPFrame(extendedColumns);
225
221
  };
226
222
 
227
- if (!allPColumnsReady(blockColumns)) {
228
- return undefined;
229
- }
230
-
231
223
  // if current block has its own columns then take from result pool only compatible with them
232
224
  const columns = new PColumnCollection();
233
225
  columns.addColumnProvider(ctx.resultPool);
@@ -246,7 +238,7 @@ export function createPFrameForGraphs<A, U>(
246
238
  }
247
239
 
248
240
  // all linker columns always go to pFrame - even it's impossible to use some of them they all are hidden
249
- const linkerColumns = columns.getColumns((spec) => isLinkerColumn(spec)) ?? [];
241
+ const linkerColumns = columns.getUniversalEntries((spec) => suitableSpec(spec) && isLinkerColumn(spec)) ?? [];
250
242
  const availableWithLinkersAxes = getAvailableWithLinkersAxes(linkerColumns, blockAxes);
251
243
 
252
244
  // all possible axes from connected linkers
@@ -257,16 +249,11 @@ export function createPFrameForGraphs<A, U>(
257
249
 
258
250
  const blockAxesArr = Array.from(blockAxes.values());
259
251
  // all compatible with block columns but without label columns
260
- let compatibleWithoutLabels = (columns.getColumns((spec) => !isHiddenFromGraphColumn(spec) && spec.axesSpec.some((axisSpec) => {
252
+ let compatibleWithoutLabels = (columns.getUniversalEntries((spec) => suitableSpec(spec) && spec.axesSpec.some((axisSpec) => {
261
253
  const axisId = getAxisId(axisSpec);
262
254
  return blockAxesArr.some((selectorAxisSpec) => matchAxisId(getAxisId(selectorAxisSpec), axisId));
263
255
  }), { dontWaitAllData: true, overrideLabelAnnotation: false }) ?? []).filter((column) => !isLabelColumn(column.spec));
264
256
 
265
- // if at least one column is not yet ready, we can't show the graph
266
- if (!allPColumnsReady(compatibleWithoutLabels)) {
267
- return undefined;
268
- }
269
-
270
257
  // extend axes set for label columns request
271
258
  for (const c of compatibleWithoutLabels) {
272
259
  for (const spec of getNormalizedAxesList(c.spec.axesSpec)) {
@@ -277,22 +264,17 @@ export function createPFrameForGraphs<A, U>(
277
264
 
278
265
  const allAxesArr = Array.from(allAxes.values());
279
266
  // extend allowed columns - add columns thad doesn't have axes from block, but have all axes in 'allAxes' list (that means all axes from linkers or from 'hanging' of other selected columns)
280
- compatibleWithoutLabels = (columns.getColumns((spec) => !isHiddenFromGraphColumn(spec) && spec.axesSpec.every((axisSpec) => {
267
+ compatibleWithoutLabels = (columns.getUniversalEntries((spec) => suitableSpec(spec) && spec.axesSpec.every((axisSpec) => {
281
268
  const axisId = getAxisId(axisSpec);
282
269
  return allAxesArr.some((selectorAxisSpec) => matchAxisId(getAxisId(selectorAxisSpec), axisId));
283
270
  }), { dontWaitAllData: true, overrideLabelAnnotation: false }) ?? []).filter((column) => !isLabelColumn(column.spec));
284
271
 
285
272
  // label columns must be compatible with full set of axes - block axes and axes from compatible columns from result pool
286
- const compatibleLabels = (columns.getColumns((spec) => !isHiddenFromGraphColumn(spec) && spec.axesSpec.some((axisSpec) => {
273
+ const compatibleLabels = (columns.getUniversalEntries((spec) => suitableSpec(spec) && spec.axesSpec.some((axisSpec) => {
287
274
  const axisId = getAxisId(axisSpec);
288
275
  return allAxesArr.some((selectorAxisSpec) => matchAxisId(getAxisId(selectorAxisSpec), axisId));
289
276
  }), { dontWaitAllData: true, overrideLabelAnnotation: false }) ?? []).filter((column) => isLabelColumn(column.spec));
290
277
 
291
- // if at least one column is not yet ready, we can't show the graph
292
- if (!allPColumnsReady(compatibleLabels)) {
293
- return undefined;
294
- }
295
-
296
278
  const compatible = [...compatibleWithoutLabels, ...compatibleLabels];
297
279
 
298
280
  // additional columns are duplicates with extra fields in domains for compatibility if there are ones with partial match
@@ -51,6 +51,10 @@ export function convertFilterUiToExpressionImpl(value: FilterSpec): ExpressionIm
51
51
  return col(value.column).eq(lit(value.x));
52
52
  }
53
53
 
54
+ if (value.type === 'notEqual') {
55
+ return col(value.column).neq(lit(value.x));
56
+ }
57
+
54
58
  if (value.type === 'lessThan') {
55
59
  return col(value.column).lt(lit(value.x));
56
60
  }
@@ -112,7 +116,6 @@ export function convertFilterUiToExpressionImpl(value: FilterSpec): ExpressionIm
112
116
  || value.type === 'patternFuzzyContainSubsequence'
113
117
  || value.type === 'inSet'
114
118
  || value.type === 'notInSet'
115
- || value.type === 'notEqual'
116
119
  ) {
117
120
  throw new Error('Not implemented filter type: ' + value.type);
118
121
  }
@@ -10,7 +10,7 @@ export type SimplifiedUniversalPColumnEntry = {
10
10
 
11
11
  export type FilterSpecNode<Leaf, CommonNode = {}, CommonLeaf = {}> =
12
12
  | CommonLeaf & Leaf
13
- | CommonNode & { type: 'not'; filter: CommonLeaf & Leaf }
13
+ | CommonNode & { type: 'not'; filter: FilterSpecNode<Leaf, CommonNode, CommonLeaf> }
14
14
  | CommonNode & { type: 'or'; filters: FilterSpecNode<Leaf, CommonNode, CommonLeaf>[] }
15
15
  | CommonNode & { type: 'and'; filters: FilterSpecNode<Leaf, CommonNode, CommonLeaf>[] };
16
16
 
package/src/global.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import type { PlatformaApiVersion } from './platforma';
1
+ import type { Platforma, PlatformaApiVersion } from './platforma';
2
2
 
3
3
  declare global {
4
4
  /** Global factory method returning platforma instance */
5
5
  var getPlatforma: PlatformaSDKVersion; // TODO: invalid type
6
- var platforma: Platforma;
6
+ var platforma: undefined | Platforma;
7
7
  var platformaApiVersion: PlatformaApiVersion;
8
8
 
9
9
  function getEnvironmentValue(name: string): string | undefined;
package/src/index.ts CHANGED
@@ -14,6 +14,7 @@ export * from './block_api_v1';
14
14
  export * from './block_api_v2';
15
15
  export * from './filters';
16
16
  export * from './annotations';
17
+ export * from './pframe_utils';
17
18
 
18
19
  // reexporting everything from SDK model
19
20
  export * from '@milaboratories/pl-model-common';
package/src/internal.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { ValueOrErrors } from '@milaboratories/pl-model-common';
2
- import {} from './global';
2
+ import { } from './global';
3
3
  import type { Platforma, PlatformaApiVersion } from './platforma';
4
4
  import type { FutureHandle, GlobalCfgRenderCtx } from './render/internal';
5
5
 
@@ -19,7 +19,7 @@ export function getPlatformaInstance<
19
19
  >(config?: { sdkVersion: string; apiVersion: PlatformaApiVersion }): Platforma<Args, Outputs, UiState, Href> {
20
20
  if (config && typeof globalThis.getPlatforma === 'function')
21
21
  return globalThis.getPlatforma(config);
22
- else if (typeof globalThis.platforma !== 'undefined') return globalThis.platforma;
22
+ else if (typeof globalThis.platforma !== 'undefined') return globalThis.platforma as Platforma<Args, Outputs, UiState, Href>;
23
23
  else throw new Error('Can\'t get platforma instance.');
24
24
  }
25
25
 
package/src/pframe.ts CHANGED
@@ -18,15 +18,15 @@ export class PFrameImpl implements PFrame {
18
18
  constructor(private readonly handle: PFrameHandle) {}
19
19
 
20
20
  public async findColumns(request: FindColumnsRequest): Promise<FindColumnsResponse> {
21
- return await platforma.pFrameDriver.findColumns(this.handle, request);
21
+ return await this.getPlatforma().pFrameDriver.findColumns(this.handle, request);
22
22
  }
23
23
 
24
- public async getColumnSpec(columnId: PObjectId): Promise<PColumnSpec> {
25
- return await platforma.pFrameDriver.getColumnSpec(this.handle, columnId);
24
+ public async getColumnSpec(columnId: PObjectId): Promise<PColumnSpec | null> {
25
+ return await this.getPlatforma().pFrameDriver.getColumnSpec(this.handle, columnId);
26
26
  }
27
27
 
28
28
  public async listColumns(): Promise<PColumnIdAndSpec[]> {
29
- return await platforma.pFrameDriver.listColumns(this.handle);
29
+ return await this.getPlatforma().pFrameDriver.listColumns(this.handle);
30
30
  }
31
31
 
32
32
  public async calculateTableData(
@@ -39,10 +39,17 @@ export class PFrameImpl implements PFrame {
39
39
  filters: patchInSetFilters(request.filters),
40
40
  };
41
41
  }
42
- return await platforma.pFrameDriver.calculateTableData(this.handle, request, range);
42
+ return await this.getPlatforma().pFrameDriver.calculateTableData(this.handle, request, range);
43
43
  }
44
44
 
45
45
  public async getUniqueValues(request: UniqueValuesRequest): Promise<UniqueValuesResponse> {
46
- return await platforma.pFrameDriver.getUniqueValues(this.handle, request);
46
+ return await this.getPlatforma().pFrameDriver.getUniqueValues(this.handle, request);
47
+ }
48
+
49
+ private getPlatforma() {
50
+ if (platforma === undefined) {
51
+ throw new Error('Platforma instance is not available in the current context.');
52
+ }
53
+ return platforma;
47
54
  }
48
55
  }
@@ -0,0 +1,442 @@
1
+ import { flatten, uniq } from 'es-toolkit';
2
+
3
+ import type {
4
+ AxisSpec,
5
+ CalculateTableDataRequest,
6
+ PColumnSpec,
7
+ PFrameHandle,
8
+ PTableVector,
9
+ UniqueValuesRequest,
10
+ AxisId,
11
+ CanonicalizedJson,
12
+ FindColumnsRequest,
13
+ FindColumnsResponse,
14
+ FullPTableColumnData,
15
+ PColumnIdAndSpec,
16
+ PObjectId,
17
+ PTableRecordSingleValueFilterV2,
18
+ ValueType,
19
+ } from '@milaboratories/pl-model-common';
20
+ import { pTableValue, Annotation, canonicalizeAxisId, getAxisId, readAnnotation } from '@milaboratories/pl-model-common';
21
+
22
+ // Types
23
+ type PValue = string | number | null;
24
+
25
+ type SuggestionResponse = {
26
+ values: {
27
+ value: string;
28
+ label: string;
29
+ }[];
30
+ overflow: boolean;
31
+ };
32
+
33
+ type SingleColumnData = {
34
+ axesData: Record<string, PValue[]>;
35
+ data: PValue[];
36
+ };
37
+
38
+ type UniqueValuesResponse = {
39
+ values: string[];
40
+ overflow: boolean;
41
+ };
42
+
43
+ type GetUniqueSourceValuesParams = {
44
+ columnId: PObjectId;
45
+ axisIdx?: number;
46
+ limit?: number;
47
+ searchQuery?: string;
48
+ searchQueryValue?: string;
49
+ };
50
+
51
+ type GetAxisUniqueValuesParams = {
52
+ axisId: AxisId;
53
+ parentColumnIds: PObjectId[];
54
+ limit?: number;
55
+ filters?: PTableRecordSingleValueFilterV2[];
56
+ };
57
+
58
+ type GetColumnsFullParams = {
59
+ selectedSources: PObjectId[];
60
+ strictlyCompatible: boolean;
61
+ types?: ValueType[];
62
+ names?: string[];
63
+ annotations?: FindColumnsRequest['columnFilter']['annotationValue'];
64
+ annotationsNotEmpty?: string[];
65
+ };
66
+
67
+ // Constants
68
+ const UNIQUE_VALUES_LIMIT = 1000000;
69
+
70
+ // Helper functions
71
+ const sortValuesPredicate = (a: { label: string }, b: { label: string }) =>
72
+ a.label.localeCompare(b.label, 'en', { numeric: true });
73
+
74
+ function convertColumnData(type: ValueType, response: PTableVector, absentValue: number | null = null): PValue[] {
75
+ if (type === 'String') {
76
+ return response.data as PValue[];
77
+ }
78
+ const res: PValue[] = new Array(response.data.length);
79
+ for (let i = 0; i < response.data.length; i++) {
80
+ res[i] = pTableValue(response, i, { absent: absentValue, na: null }) as PValue;
81
+ }
82
+ return res;
83
+ }
84
+
85
+ function createSearchFilter(
86
+ columnId: PObjectId,
87
+ substring: string,
88
+ ): PTableRecordSingleValueFilterV2 {
89
+ return {
90
+ type: 'bySingleColumnV2',
91
+ column: {
92
+ type: 'column',
93
+ id: columnId,
94
+ },
95
+ predicate: {
96
+ operator: 'StringIContains',
97
+ substring,
98
+ },
99
+ };
100
+ }
101
+
102
+ function createAxisSearchFilter(
103
+ axisSpec: AxisSpec,
104
+ substring: string,
105
+ ): PTableRecordSingleValueFilterV2 {
106
+ return {
107
+ type: 'bySingleColumnV2',
108
+ column: {
109
+ type: 'axis',
110
+ id: {
111
+ type: axisSpec.type,
112
+ name: axisSpec.name,
113
+ },
114
+ },
115
+ predicate: {
116
+ operator: 'StringIContains',
117
+ substring,
118
+ },
119
+ };
120
+ }
121
+
122
+ function mapValuesToSuggestions(values: string[]): { value: string; label: string }[] {
123
+ return values.map((v) => ({ value: String(v), label: String(v) })).sort(sortValuesPredicate);
124
+ }
125
+
126
+ function getPFrameDriver() {
127
+ if (typeof platforma === 'undefined') {
128
+ throw new Error('Platforma instance is not available');
129
+ }
130
+ if (typeof platforma.pFrameDriver === 'undefined') {
131
+ throw new Error('PFrame driver is not available in the current Platforma instance');
132
+ }
133
+ return platforma.pFrameDriver;
134
+ }
135
+
136
+ // Core functions
137
+ export async function getColumnSpecById(handle: PFrameHandle, id: PObjectId): Promise<PColumnSpec | null> {
138
+ try {
139
+ const response = await getPFrameDriver().getColumnSpec(handle, id);
140
+ return response ?? null;
141
+ } catch (err) {
142
+ console.error('PFrame: get single column error', err);
143
+ return null;
144
+ }
145
+ }
146
+
147
+ export async function getSingleColumnData(
148
+ handle: PFrameHandle,
149
+ id: PObjectId,
150
+ filters: PTableRecordSingleValueFilterV2[] = [],
151
+ ): Promise<SingleColumnData> {
152
+ try {
153
+ const response: FullPTableColumnData[] = await getPFrameDriver().calculateTableData(handle, {
154
+ src: {
155
+ type: 'column',
156
+ column: id,
157
+ },
158
+ filters,
159
+ sorting: [],
160
+ } as CalculateTableDataRequest<PObjectId>);
161
+
162
+ const axes = response.filter((item) => item.spec.type === 'axis');
163
+ const columns = response.filter((item) => item.spec.type === 'column');
164
+
165
+ return {
166
+ axesData: axes.reduce((res: Record<string, PValue[]>, item) => {
167
+ const id = getAxisId(item.spec.spec as AxisSpec);
168
+ res[canonicalizeAxisId(id)] = convertColumnData(id.type, item.data);
169
+ return res;
170
+ }, {}),
171
+ data: columns.length ? convertColumnData(columns[0].data.type, columns[0].data) : [],
172
+ };
173
+ } catch (err) {
174
+ console.error('PFrame: calculateTableData error');
175
+ throw err;
176
+ }
177
+ }
178
+
179
+ export async function getColumnUniqueValues(
180
+ handle: PFrameHandle,
181
+ id: PObjectId,
182
+ limit = UNIQUE_VALUES_LIMIT,
183
+ filters: PTableRecordSingleValueFilterV2[] = [],
184
+ ): Promise<UniqueValuesResponse> {
185
+ const request: UniqueValuesRequest = {
186
+ columnId: id,
187
+ filters,
188
+ limit,
189
+ };
190
+
191
+ try {
192
+ const response = await getPFrameDriver().getUniqueValues(handle, request);
193
+ if (response.overflow) {
194
+ console.warn(`More than ${limit} values for ${id} column`);
195
+ }
196
+ return {
197
+ values: Array.from(response.values.data as ArrayLike<unknown>).map(String),
198
+ overflow: response.overflow,
199
+ };
200
+ } catch (err) {
201
+ console.error('PFrame: getUniqueValues for column error');
202
+ throw err;
203
+ }
204
+ }
205
+
206
+ export async function getAxisUniqueValues(
207
+ handle: PFrameHandle,
208
+ params: GetAxisUniqueValuesParams,
209
+ ): Promise<UniqueValuesResponse> {
210
+ const { axisId, parentColumnIds, limit = UNIQUE_VALUES_LIMIT, filters = [] } = params;
211
+ const strAxisId = canonicalizeAxisId(axisId);
212
+
213
+ const parentsSpecs = (await Promise.all(parentColumnIds.map((p) => getColumnSpecById(handle, p))))
214
+ .flatMap((spec, i): [PObjectId, PColumnSpec][] =>
215
+ spec != null && spec.kind === 'PColumn' ? [[parentColumnIds[i], spec]] : [],
216
+ )
217
+ .filter(([_, spec]) =>
218
+ spec.axesSpec.some((axisSpec) => canonicalizeAxisId(getAxisId(axisSpec)) === strAxisId),
219
+ );
220
+
221
+ if (parentsSpecs.length === 0) {
222
+ console.warn('Axis unique values requested without parent columns');
223
+ return { values: [], overflow: false };
224
+ }
225
+
226
+ try {
227
+ const responses = await Promise.all(
228
+ parentsSpecs.map(([id]) =>
229
+ getPFrameDriver().getUniqueValues(handle, {
230
+ columnId: id,
231
+ axis: axisId,
232
+ filters,
233
+ limit,
234
+ }),
235
+ ),
236
+ );
237
+
238
+ const overflow = responses.some((r) => r.overflow);
239
+ return {
240
+ values: uniq(
241
+ flatten(responses.map((r) =>
242
+ Array.from(r.values.data as ArrayLike<unknown>).map(String),
243
+ )),
244
+ ),
245
+ overflow,
246
+ };
247
+ } catch (err) {
248
+ console.error('PFrame: getUniqueValues for axis error', err);
249
+ return { values: [], overflow: false };
250
+ }
251
+ }
252
+
253
+ export async function getRequestColumnsFromSelectedSources(
254
+ handle: PFrameHandle,
255
+ sources: PObjectId[],
256
+ ): Promise<AxisId[]> {
257
+ const result: AxisId[] = [];
258
+ for (const item of sources) {
259
+ const spec = await getColumnSpecById(handle, item);
260
+ if (spec?.kind === 'PColumn') {
261
+ result.push(...spec.axesSpec.map((spec) => getAxisId(spec)));
262
+ }
263
+ }
264
+ return result;
265
+ }
266
+
267
+ export async function getColumnsFull(
268
+ handle: PFrameHandle,
269
+ params: GetColumnsFullParams,
270
+ ): Promise<PColumnIdAndSpec[]> {
271
+ const { selectedSources, strictlyCompatible, types, names, annotations, annotationsNotEmpty } = params;
272
+
273
+ try {
274
+ const request: FindColumnsRequest = {
275
+ columnFilter: {
276
+ type: types,
277
+ name: names,
278
+ annotationValue: annotations,
279
+ annotationPattern: annotationsNotEmpty?.reduce((res, v) => {
280
+ res[v] = '.+';
281
+ return res;
282
+ }, {} as Record<string, string>),
283
+ },
284
+ compatibleWith: await getRequestColumnsFromSelectedSources(handle, selectedSources),
285
+ strictlyCompatible,
286
+ };
287
+
288
+ const response: FindColumnsResponse = await getPFrameDriver().findColumns(handle, request);
289
+ return response.hits;
290
+ } catch (err) {
291
+ console.error('PFrame: findColumns error');
292
+ throw err;
293
+ }
294
+ }
295
+
296
+ export async function getColumnOrAxisValueLabelsId(
297
+ handle: PFrameHandle,
298
+ strAxisId: CanonicalizedJson<AxisId>,
299
+ ): Promise<PObjectId | undefined> {
300
+ const labelColumns = await getColumnsFull(handle, {
301
+ selectedSources: [],
302
+ strictlyCompatible: false,
303
+ names: [Annotation.Label],
304
+ });
305
+
306
+ const labelColumn = labelColumns.find(({ spec }) => {
307
+ return spec && spec.axesSpec.length === 1 && canonicalizeAxisId(spec.axesSpec[0]) === strAxisId;
308
+ });
309
+
310
+ return labelColumn?.columnId;
311
+ }
312
+
313
+ function getDiscreteValuesFromAnnotation(columnSpec: PColumnSpec): undefined | SuggestionResponse {
314
+ const discreteValuesStr = readAnnotation(columnSpec, Annotation.DiscreteValues);
315
+ if (!discreteValuesStr) {
316
+ return undefined;
317
+ }
318
+
319
+ try {
320
+ const discreteValues: string[] = (JSON.parse(discreteValuesStr) as (string | number)[]).map((v) => String(v));
321
+ const values = discreteValues.map((v) => ({ value: v, label: v })).sort(sortValuesPredicate);
322
+ return { values, overflow: false };
323
+ } catch {
324
+ console.error(`Parsing error: discrete values annotation ${discreteValuesStr}`);
325
+ return undefined;
326
+ }
327
+ }
328
+
329
+ async function getAxisValuesWithLabels(
330
+ handle: PFrameHandle,
331
+ params: {
332
+ columnId: PObjectId;
333
+ axisSpec: AxisSpec;
334
+ labelsColumnId: PObjectId | undefined;
335
+ limit?: number;
336
+ searchQuery?: string;
337
+ searchQueryValue?: string;
338
+ },
339
+ ): Promise<SuggestionResponse> {
340
+ const { columnId, axisSpec, labelsColumnId, limit, searchQuery, searchQueryValue } = params;
341
+ const strAxisId = canonicalizeAxisId(getAxisId(axisSpec));
342
+
343
+ let filters: PTableRecordSingleValueFilterV2[] = [];
344
+
345
+ if (labelsColumnId) {
346
+ if (searchQuery) {
347
+ filters = [createSearchFilter(labelsColumnId, searchQuery)];
348
+ }
349
+ if (searchQueryValue) {
350
+ filters = [createAxisSearchFilter(axisSpec, searchQueryValue)];
351
+ }
352
+
353
+ const { data: dataValues, axesData } = await getSingleColumnData(handle, labelsColumnId, filters);
354
+ const axisKeys = axesData[strAxisId];
355
+ const values: { value: string; label: string }[] = [];
356
+
357
+ for (let i = 0; i < Math.min(axisKeys.length, limit ?? axisKeys.length); i++) {
358
+ values.push({ value: String(axisKeys[i]), label: String(dataValues[i]) });
359
+ }
360
+
361
+ values.sort(sortValuesPredicate);
362
+ return { values, overflow: !(limit === undefined || axisKeys.length < limit) };
363
+ } else {
364
+ const searchInLabelsOrValue = searchQuery ?? searchQueryValue;
365
+ if (searchInLabelsOrValue) {
366
+ filters = [createAxisSearchFilter(axisSpec, searchInLabelsOrValue)];
367
+ }
368
+
369
+ const response = await getAxisUniqueValues(handle, {
370
+ axisId: getAxisId(axisSpec),
371
+ parentColumnIds: [columnId],
372
+ limit,
373
+ filters,
374
+ });
375
+
376
+ const values = mapValuesToSuggestions(response.values);
377
+ return { values, overflow: response.overflow };
378
+ }
379
+ }
380
+
381
+ async function getColumnValuesWithLabels(
382
+ handle: PFrameHandle,
383
+ params: {
384
+ columnId: PObjectId;
385
+ limit?: number;
386
+ searchQuery?: string;
387
+ searchQueryValue?: string;
388
+ },
389
+ ): Promise<SuggestionResponse> {
390
+ const { columnId, limit, searchQuery, searchQueryValue } = params;
391
+ const searchInLabelsOrValue = searchQuery ?? searchQueryValue;
392
+
393
+ const filters: PTableRecordSingleValueFilterV2[] = searchInLabelsOrValue
394
+ ? [createSearchFilter(columnId, searchInLabelsOrValue)]
395
+ : [];
396
+
397
+ const response = await getColumnUniqueValues(handle, columnId, limit, filters);
398
+ const values = mapValuesToSuggestions(response.values);
399
+ return { values, overflow: response.overflow };
400
+ }
401
+
402
+ export async function getUniqueSourceValuesWithLabels(
403
+ handle: PFrameHandle,
404
+ params: GetUniqueSourceValuesParams,
405
+ ): Promise<SuggestionResponse> {
406
+ const { columnId, axisIdx, limit, searchQuery, searchQueryValue } = params;
407
+
408
+ const selectedSourceSpec = await getColumnSpecById(handle, columnId);
409
+ if (selectedSourceSpec == null || selectedSourceSpec.kind !== 'PColumn') {
410
+ return { values: [], overflow: false };
411
+ }
412
+
413
+ // Try to get discrete values from annotation
414
+ const discreteValues = getDiscreteValuesFromAnnotation(selectedSourceSpec);
415
+ if (discreteValues != null) {
416
+ return discreteValues;
417
+ }
418
+
419
+ // Handle axis values
420
+ if (axisIdx != null) {
421
+ const axisSpec = selectedSourceSpec.axesSpec[axisIdx];
422
+ const strAxisId = canonicalizeAxisId(getAxisId(axisSpec));
423
+ const labelsColumnId = await getColumnOrAxisValueLabelsId(handle, strAxisId);
424
+
425
+ return getAxisValuesWithLabels(handle, {
426
+ columnId,
427
+ axisSpec,
428
+ labelsColumnId,
429
+ limit,
430
+ searchQuery,
431
+ searchQueryValue,
432
+ });
433
+ }
434
+
435
+ // Handle column values
436
+ return getColumnValuesWithLabels(handle, {
437
+ columnId,
438
+ limit,
439
+ searchQuery,
440
+ searchQueryValue,
441
+ });
442
+ }