@finos/legend-application-studio 28.21.13 → 28.21.14

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.
@@ -21,6 +21,7 @@ import {
21
21
  type TestSuite,
22
22
  type RawLambda,
23
23
  type AccessorOwner,
24
+ type ValueSpecification,
24
25
  DataProduct,
25
26
  FunctionAccessPoint,
26
27
  LakehouseAccessPoint,
@@ -35,21 +36,30 @@ import {
35
36
  TestError,
36
37
  TestExecutionStatus,
37
38
  EqualToRelation,
39
+ FunctionParameterValue,
38
40
  RelationRowTestData,
41
+ VariableExpression,
42
+ observe_FunctionParameterValue,
39
43
  observe_RelationElement,
40
44
  observe_RelationRowTestData,
41
45
  observe_RelationElementsData,
42
46
  observe_DataProductTestSuite,
47
+ observe_ValueSpecification,
48
+ buildLambdaVariableExpressions,
43
49
  IngestDefinition,
44
50
  getAccessorItemLabelForElement,
45
51
  type AbstractPureGraphManager,
46
52
  } from '@finos/legend-graph';
47
53
  import {
48
54
  type GeneratorFn,
55
+ type PlainObject,
49
56
  ActionState,
50
57
  assertErrorThrown,
58
+ filterByType,
59
+ guaranteeNonNullable,
51
60
  deleteEntry,
52
61
  addUniqueEntry,
62
+ returnUndefOnError,
53
63
  uuid,
54
64
  noop,
55
65
  } from '@finos/legend-shared';
@@ -73,6 +83,7 @@ import {
73
83
  TestableTestEditorState,
74
84
  TestableTestSuiteEditorState,
75
85
  } from '../../testable/TestableEditorState.js';
86
+ import { generateVariableExpressionMockValue } from '@finos/legend-query-builder';
76
87
 
77
88
  const createEmptyRelationElement = (
78
89
  itemId: string,
@@ -178,6 +189,73 @@ const inferDataProductItemColumns = async (
178
189
 
179
190
  // ─── Per-test state ──────────────────────────────────────────────────────────
180
191
 
192
+ export class DataProductTestParameterState {
193
+ readonly uuid = uuid();
194
+ readonly editorStore: EditorStore;
195
+ readonly testState: DataProductTestState;
196
+ parameterValue: FunctionParameterValue;
197
+
198
+ constructor(
199
+ parameterValue: FunctionParameterValue,
200
+ editorStore: EditorStore,
201
+ testState: DataProductTestState,
202
+ ) {
203
+ this.editorStore = editorStore;
204
+ this.testState = testState;
205
+ this.parameterValue = parameterValue;
206
+ }
207
+ }
208
+
209
+ export class DataProductValueSpecificationTestParameterState extends DataProductTestParameterState {
210
+ valueSpec: ValueSpecification;
211
+ varExpression: VariableExpression;
212
+
213
+ constructor(
214
+ parameterValue: FunctionParameterValue,
215
+ editorStore: EditorStore,
216
+ testState: DataProductTestState,
217
+ valueSpec: ValueSpecification,
218
+ varExpression: VariableExpression,
219
+ ) {
220
+ super(parameterValue, editorStore, testState);
221
+ makeObservable(this, {
222
+ valueSpec: observable,
223
+ updateValueSpecification: action,
224
+ updateParameterValue: action,
225
+ resetValueSpec: action,
226
+ });
227
+ this.valueSpec = valueSpec;
228
+ this.varExpression = varExpression;
229
+ }
230
+
231
+ updateValueSpecification(val: ValueSpecification): void {
232
+ this.valueSpec = observe_ValueSpecification(
233
+ val,
234
+ this.editorStore.changeDetectionState.observerContext,
235
+ );
236
+ this.updateParameterValue();
237
+ }
238
+
239
+ updateParameterValue(): void {
240
+ const updatedValueSpec =
241
+ this.editorStore.graphManagerState.graphManager.serializeValueSpecification(
242
+ this.valueSpec,
243
+ );
244
+ this.parameterValue.value = updatedValueSpec;
245
+ }
246
+
247
+ resetValueSpec(): void {
248
+ const mockValue = generateVariableExpressionMockValue(
249
+ this.varExpression,
250
+ this.editorStore.graphManagerState.graph,
251
+ this.editorStore.changeDetectionState.observerContext,
252
+ );
253
+ if (mockValue) {
254
+ this.updateValueSpecification(mockValue);
255
+ }
256
+ }
257
+ }
258
+
181
259
  export class DataProductTestState extends TestableTestEditorState {
182
260
  readonly suiteState: DataProductTestSuiteState;
183
261
  override test: DataProductAccessPointTest;
@@ -185,6 +263,9 @@ export class DataProductTestState extends TestableTestEditorState {
185
263
 
186
264
  /** Wraps assertion.expected — drives both column definitions and test data rows. */
187
265
  testDataRelationState: RelationElementState | undefined;
266
+ parameterValueStates: DataProductTestParameterState[] = [];
267
+ newParameterValueName = '';
268
+ showNewParameterModal = false;
188
269
 
189
270
  constructor(
190
271
  suiteState: DataProductTestSuiteState,
@@ -206,6 +287,9 @@ export class DataProductTestState extends TestableTestEditorState {
206
287
  runningTestAction: observable,
207
288
  // own observable
208
289
  testDataRelationState: observable,
290
+ parameterValueStates: observable,
291
+ newParameterValueName: observable,
292
+ showNewParameterModal: observable,
209
293
  // actions from base class
210
294
  setSelectedTab: action,
211
295
  setAssertionToRename: action,
@@ -214,14 +298,247 @@ export class DataProductTestState extends TestableTestEditorState {
214
298
  openAssertion: action,
215
299
  resetResult: action,
216
300
  handleTestResult: action,
301
+ setNewParameterValueName: action,
302
+ setShowNewParameterModal: action,
303
+ openNewParamModal: action,
304
+ addParameterValue: action,
305
+ addExpressionParameterValue: action,
306
+ syncWithQuery: action,
307
+ generateTestParameterValues: action,
308
+ removeParamValueState: action,
217
309
  // flow from base class
218
310
  runTest: flow,
219
311
  });
220
312
  this.suiteState = suiteState;
221
313
  this.test = test;
314
+ this.normalizePositionalParameters();
315
+ this.parameterValueStates = this.buildParameterStates();
222
316
  this.buildTestDataRelationState().catch(noop());
223
317
  }
224
318
 
319
+ private normalizePositionalParameters(): void {
320
+ const params = this.test.parameters ?? [];
321
+ if (!params.length) {
322
+ return;
323
+ }
324
+
325
+ const expressions = this.queryVariableExpressions;
326
+ if (!expressions.length) {
327
+ return;
328
+ }
329
+
330
+ const unnamedParams = params.filter((p) => !p.name);
331
+ if (!unnamedParams.length) {
332
+ return;
333
+ }
334
+
335
+ const takenNames = new Set(
336
+ params.map((p) => p.name).filter((name) => Boolean(name)),
337
+ );
338
+
339
+ expressions.forEach((expr) => {
340
+ if (!takenNames.has(expr.name)) {
341
+ const nextUnnamed = unnamedParams.shift();
342
+ if (nextUnnamed) {
343
+ nextUnnamed.name = expr.name;
344
+ takenNames.add(expr.name);
345
+ }
346
+ }
347
+ });
348
+ }
349
+
350
+ get queryVariableExpressions(): VariableExpression[] {
351
+ const accessPoint = this.suiteState.testableState.ownAccessPoints.find(
352
+ (ap) => ap.id === this.test.accessPointId,
353
+ );
354
+ const query = accessPoint ? getAccessPointLambda(accessPoint) : undefined;
355
+ if (!query) {
356
+ return [];
357
+ }
358
+ return buildLambdaVariableExpressions(
359
+ query,
360
+ this.editorStore.graphManagerState,
361
+ ).filter(filterByType(VariableExpression));
362
+ }
363
+
364
+ get newParamOptions(): { value: string; label: string }[] {
365
+ const queryVarExpressions = this.queryVariableExpressions;
366
+ const currentParams = this.test.parameters ?? [];
367
+ return queryVarExpressions
368
+ .filter((v) => !currentParams.find((i) => i.name === v.name))
369
+ .map((e) => ({ value: e.name, label: e.name }));
370
+ }
371
+
372
+ setNewParameterValueName(val: string): void {
373
+ this.newParameterValueName = val;
374
+ }
375
+
376
+ setShowNewParameterModal(val: boolean): void {
377
+ this.showNewParameterModal = val;
378
+ }
379
+
380
+ openNewParamModal(): void {
381
+ this.setShowNewParameterModal(true);
382
+ const option = this.newParamOptions[0];
383
+ if (option) {
384
+ this.newParameterValueName = option.value;
385
+ }
386
+ }
387
+
388
+ addParameterValue(): void {
389
+ try {
390
+ const expressions = this.queryVariableExpressions;
391
+ const expression = guaranteeNonNullable(
392
+ expressions.find((v) => v.name === this.newParameterValueName),
393
+ );
394
+ this.addExpressionParameterValue(expression);
395
+ } catch (error) {
396
+ assertErrorThrown(error);
397
+ this.editorStore.applicationStore.notificationService.notifyError(error);
398
+ } finally {
399
+ this.setShowNewParameterModal(false);
400
+ }
401
+ }
402
+
403
+ syncWithQuery(): void {
404
+ this.normalizePositionalParameters();
405
+
406
+ this.parameterValueStates.forEach((paramState) => {
407
+ const expression = this.queryVariableExpressions.find(
408
+ (v) => v.name === paramState.parameterValue.name,
409
+ );
410
+ if (!expression) {
411
+ deleteEntry(this.parameterValueStates, paramState);
412
+ deleteEntry(this.test.parameters ?? [], paramState.parameterValue);
413
+ }
414
+ });
415
+
416
+ this.queryVariableExpressions.forEach((v) => {
417
+ const multiplicity = v.multiplicity;
418
+ const isRequired = multiplicity.lowerBound > 0;
419
+ const paramState = this.parameterValueStates.find(
420
+ (p) => p.parameterValue.name === v.name,
421
+ );
422
+ if (!paramState && isRequired) {
423
+ this.addExpressionParameterValue(v);
424
+ }
425
+ });
426
+ }
427
+
428
+ addExpressionParameterValue(expression: VariableExpression): void {
429
+ try {
430
+ const mockValue = guaranteeNonNullable(
431
+ generateVariableExpressionMockValue(
432
+ expression,
433
+ this.editorStore.graphManagerState.graph,
434
+ this.editorStore.changeDetectionState.observerContext,
435
+ ),
436
+ );
437
+ const paramValue = observe_FunctionParameterValue(
438
+ new FunctionParameterValue(),
439
+ );
440
+ paramValue.name = expression.name;
441
+ paramValue.value =
442
+ this.editorStore.graphManagerState.graphManager.serializeValueSpecification(
443
+ mockValue,
444
+ );
445
+ if (this.test.parameters) {
446
+ this.test.parameters.push(paramValue);
447
+ } else {
448
+ this.test.parameters = [paramValue];
449
+ }
450
+ const paramValueState =
451
+ new DataProductValueSpecificationTestParameterState(
452
+ paramValue,
453
+ this.editorStore,
454
+ this,
455
+ observe_ValueSpecification(
456
+ mockValue,
457
+ this.editorStore.changeDetectionState.observerContext,
458
+ ),
459
+ expression,
460
+ );
461
+ this.parameterValueStates.push(paramValueState);
462
+ } catch (error) {
463
+ assertErrorThrown(error);
464
+ this.editorStore.applicationStore.notificationService.notifyError(error);
465
+ }
466
+ }
467
+
468
+ generateTestParameterValues(): void {
469
+ try {
470
+ const varExpressions = this.queryVariableExpressions;
471
+ const parameterValueStates = varExpressions
472
+ .map((varExpression) => {
473
+ const mockValue = generateVariableExpressionMockValue(
474
+ varExpression,
475
+ this.editorStore.graphManagerState.graph,
476
+ this.editorStore.changeDetectionState.observerContext,
477
+ );
478
+ if (mockValue) {
479
+ const paramValue = observe_FunctionParameterValue(
480
+ new FunctionParameterValue(),
481
+ );
482
+ paramValue.name = varExpression.name;
483
+ paramValue.value =
484
+ this.editorStore.graphManagerState.graphManager.serializeValueSpecification(
485
+ mockValue,
486
+ );
487
+ return new DataProductValueSpecificationTestParameterState(
488
+ paramValue,
489
+ this.editorStore,
490
+ this,
491
+ mockValue,
492
+ varExpression,
493
+ );
494
+ }
495
+ return undefined;
496
+ })
497
+ .filter(
498
+ (value): value is DataProductValueSpecificationTestParameterState =>
499
+ value !== undefined,
500
+ );
501
+ this.test.parameters = parameterValueStates.map((s) => s.parameterValue);
502
+ this.parameterValueStates = parameterValueStates;
503
+ } catch (error) {
504
+ assertErrorThrown(error);
505
+ this.editorStore.applicationStore.notificationService.notifyError(
506
+ `Unable to generate parameter values: ${error.message}`,
507
+ );
508
+ }
509
+ }
510
+
511
+ buildParameterStates(): DataProductTestParameterState[] {
512
+ const varExpressions = this.queryVariableExpressions;
513
+ const paramValues = this.test.parameters ?? [];
514
+ return paramValues.map((paramValue) => {
515
+ const spec = returnUndefOnError(() =>
516
+ this.editorStore.graphManagerState.graphManager.buildValueSpecification(
517
+ paramValue.value as PlainObject,
518
+ this.editorStore.graphManagerState.graph,
519
+ ),
520
+ );
521
+ const expression = varExpressions.find((e) => e.name === paramValue.name);
522
+ return spec && expression
523
+ ? new DataProductValueSpecificationTestParameterState(
524
+ paramValue,
525
+ this.editorStore,
526
+ this,
527
+ observe_ValueSpecification(
528
+ spec,
529
+ this.editorStore.changeDetectionState.observerContext,
530
+ ),
531
+ expression,
532
+ )
533
+ : new DataProductTestParameterState(paramValue, this.editorStore, this);
534
+ });
535
+ }
536
+
537
+ removeParamValueState(paramState: DataProductTestParameterState): void {
538
+ deleteEntry(this.parameterValueStates, paramState);
539
+ deleteEntry(this.test.parameters ?? [], paramState.parameterValue);
540
+ }
541
+
225
542
  private async buildTestDataRelationState(): Promise<void> {
226
543
  const assertion = this.test.assertions.find(
227
544
  (a): a is EqualToRelation => a instanceof EqualToRelation,