@finos/legend-application-studio 28.21.12 → 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.
Files changed (49) hide show
  1. package/lib/components/editor/ActivityBar.d.ts +13 -0
  2. package/lib/components/editor/ActivityBar.d.ts.map +1 -1
  3. package/lib/components/editor/ActivityBar.js +36 -15
  4. package/lib/components/editor/ActivityBar.js.map +1 -1
  5. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.d.ts +2 -0
  6. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.d.ts.map +1 -1
  7. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js +81 -19
  8. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js.map +1 -1
  9. package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.d.ts.map +1 -1
  10. package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.js +109 -15
  11. package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.js.map +1 -1
  12. package/lib/components/editor/editor-group/service-editor/ServiceEditor.d.ts.map +1 -1
  13. package/lib/components/editor/editor-group/service-editor/ServiceEditor.js +4 -1
  14. package/lib/components/editor/editor-group/service-editor/ServiceEditor.js.map +1 -1
  15. package/lib/components/workspace-setup/WorkspaceSetup.d.ts.map +1 -1
  16. package/lib/components/workspace-setup/WorkspaceSetup.js +20 -3
  17. package/lib/components/workspace-setup/WorkspaceSetup.js.map +1 -1
  18. package/lib/index.css +2 -2
  19. package/lib/index.css.map +1 -1
  20. package/lib/index.d.ts +1 -0
  21. package/lib/index.d.ts.map +1 -1
  22. package/lib/index.js +1 -0
  23. package/lib/index.js.map +1 -1
  24. package/lib/package.json +1 -1
  25. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.d.ts +34 -1
  26. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.d.ts.map +1 -1
  27. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js +203 -2
  28. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js.map +1 -1
  29. package/lib/stores/extensions/DSL_DataProduct_LegendStudioApplicationPlugin_Extension.d.ts +61 -0
  30. package/lib/stores/extensions/DSL_DataProduct_LegendStudioApplicationPlugin_Extension.d.ts.map +1 -0
  31. package/lib/stores/extensions/DSL_DataProduct_LegendStudioApplicationPlugin_Extension.js +17 -0
  32. package/lib/stores/extensions/DSL_DataProduct_LegendStudioApplicationPlugin_Extension.js.map +1 -0
  33. package/lib/stores/extensions/DSL_Service_LegendStudioApplicationPlugin_Extension.d.ts +2 -2
  34. package/lib/stores/extensions/DSL_Service_LegendStudioApplicationPlugin_Extension.d.ts.map +1 -1
  35. package/lib/stores/workspace-setup/WorkspaceSetupStore.d.ts.map +1 -1
  36. package/lib/stores/workspace-setup/WorkspaceSetupStore.js +6 -2
  37. package/lib/stores/workspace-setup/WorkspaceSetupStore.js.map +1 -1
  38. package/package.json +13 -13
  39. package/src/components/editor/ActivityBar.tsx +61 -38
  40. package/src/components/editor/editor-group/dataProduct/DataProductEditor.tsx +207 -29
  41. package/src/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.tsx +370 -22
  42. package/src/components/editor/editor-group/service-editor/ServiceEditor.tsx +5 -1
  43. package/src/components/workspace-setup/WorkspaceSetup.tsx +27 -2
  44. package/src/index.ts +1 -0
  45. package/src/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.ts +317 -0
  46. package/src/stores/extensions/DSL_DataProduct_LegendStudioApplicationPlugin_Extension.ts +77 -0
  47. package/src/stores/extensions/DSL_Service_LegendStudioApplicationPlugin_Extension.ts +1 -2
  48. package/src/stores/workspace-setup/WorkspaceSetupStore.ts +5 -0
  49. package/tsconfig.json +1 -0
@@ -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,
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Copyright (c) 2020-present, Goldman Sachs
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import type { DSL_LegendStudioApplicationPlugin_Extension } from '../LegendStudioApplicationPlugin.js';
18
+
19
+ // ── Response types ─────────────────────────────────────────────────────────────
20
+
21
+ export type DataProductMeta = {
22
+ title: string;
23
+ description: string;
24
+ confidence: number;
25
+ };
26
+
27
+ export type AccessPointGroupMeta = {
28
+ name: string;
29
+ title: string;
30
+ description: string;
31
+ confidence: number;
32
+ };
33
+
34
+ export type AccessPointMeta = {
35
+ group: string;
36
+ name: string;
37
+ title: string;
38
+ description: string;
39
+ confidence: number;
40
+ };
41
+
42
+ export type DataProductDocResponse = {
43
+ data_product: DataProductMeta;
44
+ access_point_groups: AccessPointGroupMeta[];
45
+ access_points: AccessPointMeta[];
46
+ };
47
+
48
+ // ── Request type ───────────────────────────────────────────────────────────────
49
+
50
+ export type DataProductDocRequest = {
51
+ /**
52
+ * Raw Pure grammar text containing the data product definitions.
53
+ */
54
+ definitions: string;
55
+ /**
56
+ * Fully-qualified name of the data product (e.g. dataProduct::MY_PRODUCT).
57
+ */
58
+ data_product_name: string;
59
+ /**
60
+ * LLM model override (uses server default if omitted).
61
+ */
62
+ model?: string;
63
+ };
64
+
65
+ // ── Plugin extension interface ─────────────────────────────────────────────────
66
+
67
+ export interface DSL_DataProduct_LegendStudioApplicationPlugin_Extension
68
+ extends DSL_LegendStudioApplicationPlugin_Extension {
69
+ /**
70
+ * Generate AI-suggested documentation for a data product
71
+ * (title, description, access point groups, access points).
72
+ */
73
+ getExtraDataProductDocumentationAISuggester?(
74
+ request: DataProductDocRequest,
75
+ legendAIUrl: string,
76
+ ): Promise<DataProductDocResponse>;
77
+ }
@@ -23,7 +23,6 @@ import type {
23
23
  ObserverContext,
24
24
  PostDeploymentProperties,
25
25
  HostedService,
26
- Service,
27
26
  } from '@finos/legend-graph';
28
27
 
29
28
  export type ServiceTestRuntimeConnectionBuilder = (
@@ -50,7 +49,7 @@ export type PostDeploymentActionTypeGetter = (
50
49
  ) => string | undefined;
51
50
 
52
51
  export type ServiceDocumentationAISuggester = (
53
- service: Service,
52
+ serviceGrammar: string,
54
53
  legendAIUrl: string,
55
54
  ) => Promise<string>;
56
55
 
@@ -444,6 +444,10 @@ export class WorkspaceSetupStore {
444
444
  // custom selector. This avoids losing some results with the additional filtering.
445
445
  isValidSearchString ? exactSearch(searchText) : undefined,
446
446
  undefined,
447
+ // Sandbox projects are loaded via a separate, dedicated call (see
448
+ // `loadSandboxProject`); exclude them here so they do not appear
449
+ // in the main workspace setup project picker.
450
+ [SANDBOX_SDLC_TAG],
447
451
  DEFAULT_TYPEAHEAD_SEARCH_LIMIT,
448
452
  )) as PlainObject<Project>[]
449
453
  ).map((v) => Project.serialization.fromJson(v));
@@ -518,6 +522,7 @@ export class WorkspaceSetupStore {
518
522
  undefined,
519
523
  userId,
520
524
  [SANDBOX_SDLC_TAG],
525
+ undefined,
521
526
  1,
522
527
  )) as PlainObject<Project>[]
523
528
  ).map((v) => Project.serialization.fromJson(v));
package/tsconfig.json CHANGED
@@ -197,6 +197,7 @@
197
197
  "./src/stores/editor/utils/PackageTreeUtils.ts",
198
198
  "./src/stores/editor/utils/TestableUtils.ts",
199
199
  "./src/stores/editor/utils/TreeUtils.ts",
200
+ "./src/stores/extensions/DSL_DataProduct_LegendStudioApplicationPlugin_Extension.ts",
200
201
  "./src/stores/extensions/DSL_Data_LegendStudioApplicationPlugin_Extension.ts",
201
202
  "./src/stores/extensions/DSL_Generation_LegendStudioApplicationPlugin_Extension.ts",
202
203
  "./src/stores/extensions/DSL_Mapping_LegendStudioApplicationPlugin_Extension.ts",