@finos/legend-application-studio 28.21.4 → 28.21.6

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 (100) hide show
  1. package/lib/__lib__/LegendStudioEvent.d.ts +4 -1
  2. package/lib/__lib__/LegendStudioEvent.d.ts.map +1 -1
  3. package/lib/__lib__/LegendStudioEvent.js +3 -0
  4. package/lib/__lib__/LegendStudioEvent.js.map +1 -1
  5. package/lib/__lib__/LegendStudioTelemetryHelper.d.ts +2 -1
  6. package/lib/__lib__/LegendStudioTelemetryHelper.d.ts.map +1 -1
  7. package/lib/__lib__/LegendStudioTelemetryHelper.js +11 -3
  8. package/lib/__lib__/LegendStudioTelemetryHelper.js.map +1 -1
  9. package/lib/__lib__/LegendStudioUserDataHelper.d.ts +41 -1
  10. package/lib/__lib__/LegendStudioUserDataHelper.d.ts.map +1 -1
  11. package/lib/__lib__/LegendStudioUserDataHelper.js +120 -1
  12. package/lib/__lib__/LegendStudioUserDataHelper.js.map +1 -1
  13. package/lib/components/editor/editor-group/data-editor/EmbeddedDataEditor.d.ts +1 -1
  14. package/lib/components/editor/editor-group/data-editor/EmbeddedDataEditor.d.ts.map +1 -1
  15. package/lib/components/editor/editor-group/data-editor/EmbeddedDataEditor.js +3 -3
  16. package/lib/components/editor/editor-group/data-editor/EmbeddedDataEditor.js.map +1 -1
  17. package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.d.ts +3 -0
  18. package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.d.ts.map +1 -1
  19. package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.js +13 -35
  20. package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.js.map +1 -1
  21. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.d.ts.map +1 -1
  22. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js +20 -7
  23. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js.map +1 -1
  24. package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.d.ts.map +1 -1
  25. package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.js +59 -22
  26. package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.js.map +1 -1
  27. package/lib/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.d.ts.map +1 -1
  28. package/lib/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.js +113 -75
  29. package/lib/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.js.map +1 -1
  30. package/lib/components/editor/editor-group/testable/TestableSharedComponents.d.ts.map +1 -1
  31. package/lib/components/editor/editor-group/testable/TestableSharedComponents.js +2 -2
  32. package/lib/components/editor/editor-group/testable/TestableSharedComponents.js.map +1 -1
  33. package/lib/components/editor/side-bar/DevMetadataPanel.d.ts.map +1 -1
  34. package/lib/components/editor/side-bar/DevMetadataPanel.js +37 -6
  35. package/lib/components/editor/side-bar/DevMetadataPanel.js.map +1 -1
  36. package/lib/components/workspace-setup/RecentWorkspacesPanel.d.ts +22 -0
  37. package/lib/components/workspace-setup/RecentWorkspacesPanel.d.ts.map +1 -0
  38. package/lib/components/workspace-setup/RecentWorkspacesPanel.js +80 -0
  39. package/lib/components/workspace-setup/RecentWorkspacesPanel.js.map +1 -0
  40. package/lib/components/workspace-setup/WorkspaceSetup.d.ts.map +1 -1
  41. package/lib/components/workspace-setup/WorkspaceSetup.js +61 -6
  42. package/lib/components/workspace-setup/WorkspaceSetup.js.map +1 -1
  43. package/lib/index.css +2 -2
  44. package/lib/index.css.map +1 -1
  45. package/lib/package.json +1 -1
  46. package/lib/stores/editor/EditorStore.d.ts.map +1 -1
  47. package/lib/stores/editor/EditorStore.js +31 -0
  48. package/lib/stores/editor/EditorStore.js.map +1 -1
  49. package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.d.ts +1 -1
  50. package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.d.ts.map +1 -1
  51. package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.js +20 -48
  52. package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.js.map +1 -1
  53. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.d.ts +9 -14
  54. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.d.ts.map +1 -1
  55. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js +125 -78
  56. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js.map +1 -1
  57. package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.d.ts +18 -4
  58. package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.d.ts.map +1 -1
  59. package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.js +216 -53
  60. package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.js.map +1 -1
  61. package/lib/stores/editor/sidebar-state/ProjectOverviewState.d.ts.map +1 -1
  62. package/lib/stores/editor/sidebar-state/ProjectOverviewState.js +11 -0
  63. package/lib/stores/editor/sidebar-state/ProjectOverviewState.js.map +1 -1
  64. package/lib/stores/editor/sidebar-state/WorkspaceReviewState.d.ts.map +1 -1
  65. package/lib/stores/editor/sidebar-state/WorkspaceReviewState.js +11 -0
  66. package/lib/stores/editor/sidebar-state/WorkspaceReviewState.js.map +1 -1
  67. package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.d.ts +9 -0
  68. package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.d.ts.map +1 -1
  69. package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.js +57 -1
  70. package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.js.map +1 -1
  71. package/lib/stores/project-reviewer/ProjectReviewerStore.d.ts.map +1 -1
  72. package/lib/stores/project-reviewer/ProjectReviewerStore.js +12 -0
  73. package/lib/stores/project-reviewer/ProjectReviewerStore.js.map +1 -1
  74. package/lib/stores/workspace-setup/WorkspaceSetupStore.d.ts +17 -0
  75. package/lib/stores/workspace-setup/WorkspaceSetupStore.d.ts.map +1 -1
  76. package/lib/stores/workspace-setup/WorkspaceSetupStore.js +61 -0
  77. package/lib/stores/workspace-setup/WorkspaceSetupStore.js.map +1 -1
  78. package/package.json +16 -16
  79. package/src/__lib__/LegendStudioEvent.ts +3 -0
  80. package/src/__lib__/LegendStudioTelemetryHelper.ts +35 -11
  81. package/src/__lib__/LegendStudioUserDataHelper.ts +204 -1
  82. package/src/components/editor/editor-group/data-editor/EmbeddedDataEditor.tsx +4 -0
  83. package/src/components/editor/editor-group/data-editor/RelationElementsDataEditor.tsx +209 -187
  84. package/src/components/editor/editor-group/dataProduct/DataProductEditor.tsx +26 -7
  85. package/src/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.tsx +149 -86
  86. package/src/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.tsx +425 -308
  87. package/src/components/editor/editor-group/testable/TestableSharedComponents.tsx +3 -11
  88. package/src/components/editor/side-bar/DevMetadataPanel.tsx +194 -10
  89. package/src/components/workspace-setup/RecentWorkspacesPanel.tsx +161 -0
  90. package/src/components/workspace-setup/WorkspaceSetup.tsx +97 -8
  91. package/src/stores/editor/EditorStore.ts +44 -0
  92. package/src/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.ts +28 -50
  93. package/src/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.ts +164 -100
  94. package/src/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.ts +307 -72
  95. package/src/stores/editor/sidebar-state/ProjectOverviewState.ts +14 -0
  96. package/src/stores/editor/sidebar-state/WorkspaceReviewState.ts +14 -0
  97. package/src/stores/editor/sidebar-state/dev-metadata/DevMetadataState.ts +84 -1
  98. package/src/stores/project-reviewer/ProjectReviewerStore.ts +15 -0
  99. package/src/stores/workspace-setup/WorkspaceSetupStore.ts +93 -0
  100. package/tsconfig.json +1 -0
@@ -14,7 +14,7 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
- import { action, flow, makeObservable, observable } from 'mobx';
17
+ import { action, flow, flowResult, makeObservable, observable } from 'mobx';
18
18
  import type { EditorStore } from '../../../../EditorStore.js';
19
19
  import type { FunctionEditorState } from '../../FunctionEditorState.js';
20
20
  import {
@@ -31,6 +31,8 @@ import {
31
31
  filterByType,
32
32
  deleteEntry,
33
33
  UnsupportedOperationError,
34
+ noop,
35
+ type GeneratorFn,
34
36
  } from '@finos/legend-shared';
35
37
  import {
36
38
  type ConcreteFunctionDefinition,
@@ -40,6 +42,7 @@ import {
40
42
  type ObserverContext,
41
43
  type ValueSpecification,
42
44
  type TestAssertion,
45
+ type AccessorOwner,
43
46
  FunctionParameterValue,
44
47
  VariableExpression,
45
48
  FunctionTest,
@@ -58,9 +61,19 @@ import {
58
61
  observe_ValueSpecification,
59
62
  buildLambdaVariableExpressions,
60
63
  EqualTo,
64
+ EqualToRelation,
65
+ RelationElement,
66
+ RelationRowTestData,
67
+ observe_RelationElement,
68
+ observe_RelationRowTestData,
61
69
  ModelStore,
62
70
  RelationElementsData,
63
71
  CORE_PURE_PATH,
72
+ type Accessor,
73
+ DataProductAccessor,
74
+ IngestionAccessor,
75
+ RelationalStoreAccessor,
76
+ V1_buildRelationElementsDataFromAccessors,
64
77
  } from '@finos/legend-graph';
65
78
  import {
66
79
  TestablePackageableElementEditorState,
@@ -68,6 +81,7 @@ import {
68
81
  TestableTestSuiteEditorState,
69
82
  } from '../../testable/TestableEditorState.js';
70
83
  import { EmbeddedDataEditorState } from '../../data/DataEditorState.js';
84
+ import { RelationElementsDataState } from '../../data/EmbeddedDataState.js';
71
85
  import {
72
86
  functionTestable_deleteDataStore,
73
87
  functionTestable_setEmbeddedData,
@@ -207,6 +221,69 @@ export class FunctionStoreTestDataState {
207
221
  hideSource: true,
208
222
  },
209
223
  );
224
+ this.initAccessorOptions();
225
+ }
226
+
227
+ private initAccessorOptions(): void {
228
+ const embeddedDataState = this.embeddedEditorState.embeddedDataState;
229
+ if (!(embeddedDataState instanceof RelationElementsDataState)) {
230
+ return;
231
+ }
232
+ this.refreshAccessorOptions(embeddedDataState).catch(noop());
233
+ embeddedDataState.setRefreshAccessorOptions(() =>
234
+ this.refreshAccessorOptions(embeddedDataState),
235
+ );
236
+ }
237
+
238
+ private async refreshAccessorOptions(
239
+ embeddedDataState: RelationElementsDataState,
240
+ ): Promise<void> {
241
+ const parentElement = this.storeTestData.element.value;
242
+ const accessors =
243
+ this.testDataState.functionTestableState
244
+ .resolvedIngestOrDataProductAccessors;
245
+ const parentAccessors = accessors.filter(
246
+ (a) => a.accessorOwner === parentElement.path,
247
+ );
248
+ if (!parentAccessors.length) {
249
+ embeddedDataState.setAccessorOptions(undefined, undefined);
250
+ return;
251
+ }
252
+ const firstAccessor = parentAccessors[0];
253
+ if (!firstAccessor) {
254
+ embeddedDataState.setAccessorOptions(undefined, undefined);
255
+ return;
256
+ }
257
+
258
+ const columnOverrides = new Map<string, string[]>();
259
+ for (const accessor of parentAccessors) {
260
+ const cols = accessor.relationType.columns.map((col) => col.name);
261
+ if (cols.length > 0) {
262
+ columnOverrides.set(accessor.accessor, cols);
263
+ }
264
+ }
265
+
266
+ const typeLabel = firstAccessor.accessorLabel;
267
+ const options = parentAccessors.map((a) => ({
268
+ label: a.accessor,
269
+ value: a.accessor,
270
+ columns: columnOverrides.get(a.accessor) ?? [],
271
+ }));
272
+
273
+ if (columnOverrides.size > 0) {
274
+ for (const relState of embeddedDataState.relationElementStates) {
275
+ const rel = relState.relationElement;
276
+ if (rel.columns.length === 0) {
277
+ const key = rel.paths[rel.paths.length - 1];
278
+ const cols = key ? columnOverrides.get(key) : undefined;
279
+ if (cols) {
280
+ rel.columns = cols;
281
+ }
282
+ }
283
+ }
284
+ }
285
+
286
+ embeddedDataState.setAccessorOptions(options, typeLabel);
210
287
  }
211
288
 
212
289
  setDataElementModal(val: boolean): void {
@@ -532,6 +609,7 @@ class FunctionTestDataState {
532
609
  selectedDataState: FunctionStoreTestDataState | undefined;
533
610
  dataHolder: FunctionTestSuite;
534
611
  showNewModal = false;
612
+ showAddElementModal = false;
535
613
 
536
614
  constructor(
537
615
  editorStore: EditorStore,
@@ -542,9 +620,12 @@ class FunctionTestDataState {
542
620
  selectedDataState: observable,
543
621
  dataHolder: observable,
544
622
  showNewModal: observable,
623
+ showAddElementModal: observable,
545
624
  initDefaultStore: action,
546
625
  setShowModal: action,
626
+ setShowAddElementModal: action,
547
627
  deleteStoreTestData: action,
628
+ addDataElement: action,
548
629
  openStoreTestData: action,
549
630
  });
550
631
  this.editorStore = editorStore;
@@ -566,6 +647,49 @@ class FunctionTestDataState {
566
647
  this.showNewModal = val;
567
648
  }
568
649
 
650
+ setShowAddElementModal(val: boolean): void {
651
+ this.showAddElementModal = val;
652
+ }
653
+
654
+ get existingElementPaths(): string[] {
655
+ return (this.dataHolder.testData ?? []).map((td) => td.element.value.path);
656
+ }
657
+
658
+ get availableElementsToAdd(): { path: string }[] {
659
+ const accessors =
660
+ this.functionTestableState.resolvedIngestOrDataProductAccessors;
661
+ const existingPaths = new Set(this.existingElementPaths);
662
+ const parentPaths = new Set<string>();
663
+ for (const accessor of accessors) {
664
+ const parentPath = accessor.path[0];
665
+ if (parentPath && !existingPaths.has(parentPath)) {
666
+ parentPaths.add(parentPath);
667
+ }
668
+ }
669
+ return Array.from(parentPaths).map((path) => ({ path }));
670
+ }
671
+
672
+ addDataElement(elementPath: string): void {
673
+ const accessors =
674
+ this.functionTestableState.resolvedIngestOrDataProductAccessors;
675
+ const group = accessors.filter((a) => a.path[0] === elementPath);
676
+ if (!group.length) {
677
+ return;
678
+ }
679
+ const element =
680
+ this.editorStore.graphManagerState.graph.getElement(elementPath);
681
+ const data = new FunctionTestData();
682
+ data.element = PackageableElementExplicitReference.create(
683
+ element as AccessorOwner,
684
+ );
685
+ data.data = V1_buildRelationElementsDataFromAccessors(group);
686
+ if (!this.dataHolder.testData) {
687
+ this.dataHolder.testData = [];
688
+ }
689
+ this.dataHolder.testData.push(data);
690
+ this.openStoreTestData(data);
691
+ }
692
+
569
693
  deleteStoreTestData(val: FunctionTestData): void {
570
694
  functionTestable_deleteDataStore(this.dataHolder, val);
571
695
  this.initDefaultStore();
@@ -580,26 +704,62 @@ class FunctionTestDataState {
580
704
  }
581
705
  }
582
706
 
583
- export const createFunctionTest = (
707
+ export const createFunctionTest = async (
584
708
  id: string,
585
709
  observerContext: ObserverContext,
586
710
  containsRuntime: boolean,
587
711
  functionDefinition: ConcreteFunctionDefinition,
588
712
  editorStore: EditorStore,
589
713
  suite?: FunctionTestSuite | undefined,
590
- ): FunctionTest => {
714
+ ): Promise<FunctionTest> => {
591
715
  const funcionTest = new FunctionTest();
592
716
  funcionTest.id = id;
593
717
  funcionTest.assertions = [];
594
718
  let _assertion: TestAssertion;
595
- if (containsRuntime) {
719
+ const type = functionDefinition.returnType.value.rawType;
720
+ if (
721
+ type.path === CORE_PURE_PATH.RELATION ||
722
+ type.path === CORE_PURE_PATH.TABULAR_DATASET
723
+ ) {
724
+ const assertion = new EqualToRelation();
725
+ assertion.id = DEFAULT_TEST_ASSERTION_ID;
726
+ const expectedRelElement = new RelationElement();
727
+ expectedRelElement.paths = [];
728
+ let inferredColumns: string[] = [];
729
+ if (type.path === CORE_PURE_PATH.RELATION) {
730
+ try {
731
+ const rawLambda = new RawLambda(
732
+ functionDefinition.parameters.map((_param) =>
733
+ editorStore.graphManagerState.graphManager.serializeRawValueSpecification(
734
+ _param,
735
+ ),
736
+ ),
737
+ functionDefinition.expressionSequence,
738
+ );
739
+ const relationTypeMetadata =
740
+ await editorStore.graphManagerState.graphManager.getLambdaRelationType(
741
+ rawLambda,
742
+ editorStore.graphManagerState.graph,
743
+ );
744
+ inferredColumns = relationTypeMetadata.columns.map((col) => col.name);
745
+ } catch {
746
+ // best-effort: leave columns empty
747
+ }
748
+ }
749
+ expectedRelElement.columns = inferredColumns;
750
+ const emptyRow = observe_RelationRowTestData(new RelationRowTestData());
751
+ emptyRow.values = inferredColumns.map(() => '');
752
+ expectedRelElement.rows = [emptyRow];
753
+ observe_RelationElement(expectedRelElement);
754
+ assertion.expected = expectedRelElement;
755
+ _assertion = assertion;
756
+ } else if (containsRuntime) {
596
757
  _assertion = createDefaultEqualToJSONTestAssertion(
597
758
  DEFAULT_TEST_ASSERTION_ID,
598
759
  );
599
760
  } else {
600
761
  const equalTo = new EqualTo();
601
762
  equalTo.id = DEFAULT_TEST_ASSERTION_ID;
602
- const type = functionDefinition.returnType.value.rawType;
603
763
  const valSpec = buildDefaultInstanceValue(
604
764
  editorStore.graphManagerState.graph,
605
765
  type,
@@ -652,7 +812,7 @@ export class FunctionTestSuiteState extends TestableTestSuiteEditorState {
652
812
  deleteTest: action,
653
813
  buildTestStates: action,
654
814
  setShowModal: action,
655
- addNewTest: action,
815
+ addNewTest: flow,
656
816
  });
657
817
  this.functionTestableState = functionTestableState;
658
818
  this.suite = suite;
@@ -682,15 +842,15 @@ export class FunctionTestSuiteState extends TestableTestSuiteEditorState {
682
842
  return undefined;
683
843
  }
684
844
 
685
- addNewTest(id: string): void {
686
- const test = createFunctionTest(
845
+ *addNewTest(id: string): GeneratorFn<void> {
846
+ const test = (yield createFunctionTest(
687
847
  id,
688
848
  this.editorStore.changeDetectionState.observerContext,
689
849
  this.functionTestableState.containsRuntime,
690
850
  this.functionTestableState.function,
691
851
  this.editorStore,
692
852
  this.suite,
693
- );
853
+ )) as FunctionTest;
694
854
  testSuite_addTest(
695
855
  this.suite,
696
856
  test,
@@ -711,6 +871,7 @@ export class FunctionTestableState extends TestablePackageableElementEditorState
711
871
  declare runningSuite: FunctionTestSuite | undefined;
712
872
 
713
873
  createSuiteModal = false;
874
+ cachedAccessors: Accessor[] = [];
714
875
 
715
876
  constructor(functionEditorState: FunctionEditorState) {
716
877
  super(functionEditorState, functionEditorState.functionElement);
@@ -722,6 +883,7 @@ export class FunctionTestableState extends TestablePackageableElementEditorState
722
883
  runningSuite: observable,
723
884
  testableComponentToRename: observable,
724
885
  createSuiteModal: observable,
886
+ cachedAccessors: observable,
725
887
  init: action,
726
888
  buildTestSuiteState: action,
727
889
  deleteTestSuite: action,
@@ -730,6 +892,8 @@ export class FunctionTestableState extends TestablePackageableElementEditorState
730
892
  setRenameComponent: action,
731
893
  clearTestResultsForSuite: action,
732
894
  setCreateSuite: action,
895
+ resolveAccessors: flow,
896
+ createSuite: flow,
733
897
  runTestable: flow,
734
898
  runSuite: flow,
735
899
  runAllFailingSuites: flow,
@@ -750,6 +914,36 @@ export class FunctionTestableState extends TestablePackageableElementEditorState
750
914
  return Boolean(this.associatedRuntimes?.length);
751
915
  }
752
916
 
917
+ get resolvedIngestOrDataProductAccessors(): Accessor[] {
918
+ return this.cachedAccessors.filter(
919
+ (a) => a instanceof DataProductAccessor || a instanceof IngestionAccessor,
920
+ );
921
+ }
922
+
923
+ private buildRawLambdaFromFunction(): RawLambda {
924
+ return new RawLambda(
925
+ this.function.parameters.map((_param) =>
926
+ this.editorStore.graphManagerState.graphManager.serializeRawValueSpecification(
927
+ _param,
928
+ ),
929
+ ),
930
+ this.function.expressionSequence,
931
+ );
932
+ }
933
+
934
+ *resolveAccessors(): GeneratorFn<void> {
935
+ try {
936
+ const rawLambda = this.buildRawLambdaFromFunction();
937
+ this.cachedAccessors =
938
+ (yield this.editorStore.graphManagerState.graphManager.collectAccessorsInRawLambda(
939
+ rawLambda,
940
+ this.editorStore.graphManagerState.graph,
941
+ )) as Accessor[];
942
+ } catch {
943
+ this.cachedAccessors = [];
944
+ }
945
+ }
946
+
753
947
  override init(): void {
754
948
  if (!this.selectedTestSuite) {
755
949
  const suite = this.function.tests[0];
@@ -757,84 +951,125 @@ export class FunctionTestableState extends TestablePackageableElementEditorState
757
951
  ? this.buildTestSuiteState(suite)
758
952
  : undefined;
759
953
  }
954
+ flowResult(this.resolveAccessors()).catch(noop);
760
955
  }
761
956
 
762
957
  setCreateSuite(val: boolean): void {
763
958
  this.createSuiteModal = val;
764
959
  }
765
960
 
766
- createSuite(suiteName: string, testName: string): void {
961
+ *createSuite(suiteName: string, testName: string): GeneratorFn<void> {
767
962
  const functionSuite = new FunctionTestSuite();
768
963
  functionSuite.id = suiteName;
769
- const engineRuntimes = this.associatedRuntimes;
770
- if (!engineRuntimes?.length) {
771
- const type = this.function.returnType.value.rawType;
772
- if (
773
- type.path === CORE_PURE_PATH.RELATION ||
774
- type.path === CORE_PURE_PATH.TABULAR_DATASET
775
- ) {
776
- this.editorStore.applicationStore.notificationService.notifyWarning(
777
- `Unable to find runtime or function contains accessors incompatible for test suite creation`,
778
- );
779
- return;
780
- }
781
- } else {
782
- try {
783
- assertTrue(
784
- engineRuntimes.length === 1,
785
- `Function Test Suite Only supports One Runtime at this time. Found ${engineRuntimes.length}`,
786
- );
787
- const engineRuntime = guaranteeNonNullable(engineRuntimes[0]);
788
- assertTrue(
789
- !(
790
- engineRuntime.connectionStores.length &&
791
- engineRuntime.connections.length
792
- ),
793
- `Runtime found has two connection types defined. Please use connection stores only`,
794
- );
795
- const stores = [
796
- ...engineRuntime.connections
797
- .map((e) =>
798
- e.storeConnections.map((s) => s.connection.store?.value).flat(),
799
- )
800
- .flat(),
801
- ...engineRuntime.connectionStores
802
- .map((e) => e.storePointers.map((sPt) => sPt.value))
803
- .flat(),
804
- ].filter(isNonNullable);
805
- assertTrue(Boolean(stores.length), 'No runtime store found');
806
- assertTrue(
807
- stores.length === 1,
808
- 'Only one store supported in runtime for function tests',
964
+
965
+ yield flowResult(this.resolveAccessors());
966
+
967
+ const ingestOrDataProductAccessors =
968
+ this.resolvedIngestOrDataProductAccessors;
969
+ const databaseAccessors = this.cachedAccessors.filter(
970
+ (a) => a instanceof RelationalStoreAccessor,
971
+ );
972
+
973
+ try {
974
+ if (ingestOrDataProductAccessors.length) {
975
+ // Group by parent element path and create test data per parent
976
+ const parentElementMap = new Map<string, Accessor[]>();
977
+ for (const accessor of ingestOrDataProductAccessors) {
978
+ const key = accessor.accessorOwner;
979
+ if (key) {
980
+ const group = parentElementMap.get(key) ?? [];
981
+ group.push(accessor);
982
+ parentElementMap.set(key, group);
983
+ }
984
+ }
985
+ functionSuite.testData = Array.from(parentElementMap.entries()).map(
986
+ ([parentPath, group]) => {
987
+ const data = new FunctionTestData();
988
+ const element =
989
+ this.editorStore.graphManagerState.graph.getElement(parentPath);
990
+ data.element = PackageableElementExplicitReference.create(
991
+ element as AccessorOwner,
992
+ );
993
+ data.data = V1_buildRelationElementsDataFromAccessors(group);
994
+ return data;
995
+ },
809
996
  );
810
- const store = guaranteeNonNullable(stores[0]);
811
- const data = new FunctionTestData();
812
- if (store instanceof Database) {
813
- const relation = new RelationElementsData();
814
- data.element = PackageableElementExplicitReference.create(store);
815
- data.data = relation;
816
- } else if (store instanceof ModelStore) {
817
- const modelStoreData = createBareExternalFormat();
818
- data.element = PackageableElementExplicitReference.create(store);
819
- data.data = modelStoreData;
820
- } else {
821
- throw new UnsupportedOperationError(
822
- `function test store data does not support store: ${store.path}`,
997
+ } else {
998
+ // No ingest/data product accessors found — try runtime-based approach
999
+ const engineRuntimes = this.associatedRuntimes;
1000
+ if (engineRuntimes?.length) {
1001
+ assertTrue(
1002
+ engineRuntimes.length === 1,
1003
+ `Function Test Suite Only supports One Runtime at this time. Found ${engineRuntimes.length}`,
1004
+ );
1005
+ const engineRuntime = guaranteeNonNullable(engineRuntimes[0]);
1006
+ assertTrue(
1007
+ !(
1008
+ engineRuntime.connectionStores.length &&
1009
+ engineRuntime.connections.length
1010
+ ),
1011
+ `Runtime found has two connection types defined. Please use connection stores only`,
823
1012
  );
1013
+ const stores = [
1014
+ ...engineRuntime.connections
1015
+ .map((e) =>
1016
+ e.storeConnections.map((s) => s.connection.store?.value).flat(),
1017
+ )
1018
+ .flat(),
1019
+ ...engineRuntime.connectionStores
1020
+ .map((e) => e.storePointers.map((sPt) => sPt.value))
1021
+ .flat(),
1022
+ ].filter(isNonNullable);
1023
+ assertTrue(Boolean(stores.length), 'No runtime store found');
1024
+ assertTrue(
1025
+ stores.length === 1,
1026
+ 'Only one store supported in runtime for function tests',
1027
+ );
1028
+ const store = guaranteeNonNullable(stores[0]);
1029
+ const data = new FunctionTestData();
1030
+ if (store instanceof Database) {
1031
+ const relation = new RelationElementsData();
1032
+ data.element = PackageableElementExplicitReference.create(store);
1033
+ data.data = relation;
1034
+ } else if (store instanceof ModelStore) {
1035
+ const modelStoreData = createBareExternalFormat();
1036
+ data.element = PackageableElementExplicitReference.create(store);
1037
+ data.data = modelStoreData;
1038
+ } else {
1039
+ throw new UnsupportedOperationError(
1040
+ `function test store data does not support store: ${store.path}`,
1041
+ );
1042
+ }
1043
+ functionSuite.testData = [data];
1044
+ } else {
1045
+ const type = this.function.returnType.value.rawType;
1046
+ if (
1047
+ type.path === CORE_PURE_PATH.RELATION ||
1048
+ type.path === CORE_PURE_PATH.TABULAR_DATASET
1049
+ ) {
1050
+ this.editorStore.applicationStore.notificationService.notifyError(
1051
+ `Unable to create function test suite: no runtime or accessors found, or they could not be resolved`,
1052
+ );
1053
+ return;
1054
+ }
824
1055
  }
825
- functionSuite.testData = [data];
826
- } catch (error) {
827
- assertErrorThrown(error);
828
- this.editorStore.applicationStore.notificationService.notifyError(
829
- `Unable to create function test suite: ${error.message}`,
830
- );
831
- return;
832
1056
  }
1057
+ } catch (error) {
1058
+ assertErrorThrown(error);
1059
+ this.editorStore.applicationStore.notificationService.notifyError(
1060
+ `Unable to create function test suite: ${error.message}`,
1061
+ );
1062
+ return;
833
1063
  }
834
- createFunctionTest(
1064
+
1065
+ const hasTestData =
1066
+ this.containsRuntime ||
1067
+ ingestOrDataProductAccessors.length > 0 ||
1068
+ databaseAccessors.length > 0;
1069
+ yield createFunctionTest(
835
1070
  testName,
836
1071
  this.editorStore.changeDetectionState.observerContext,
837
- this.containsRuntime,
1072
+ hasTestData,
838
1073
  this.function,
839
1074
  this.editorStore,
840
1075
  functionSuite,
@@ -42,6 +42,7 @@ import {
42
42
  type WorkspaceType,
43
43
  } from '@finos/legend-server-sdlc';
44
44
  import { LEGEND_STUDIO_APP_EVENT } from '../../../__lib__/LegendStudioEvent.js';
45
+ import { LegendStudioUserDataHelper } from '../../../__lib__/LegendStudioUserDataHelper.js';
45
46
 
46
47
  export enum PROJECT_OVERVIEW_ACTIVITY_MODE {
47
48
  RELEASE = 'RELEASE',
@@ -155,6 +156,19 @@ export class ProjectOverviewState {
155
156
  this.projectWorkspaces = this.projectWorkspaces.filter(
156
157
  (w) => !areWorkspacesEquivalent(workspace, w),
157
158
  );
159
+ // Drop the deleted workspace from the recents cache so the workspace
160
+ // setup screen doesn't keep offering a dead link. Patch workspaces are
161
+ // never cached, so this is a no-op for them.
162
+ if (workspace.source === undefined) {
163
+ LegendStudioUserDataHelper.workspaceSetup_removeRecentWorkspace(
164
+ this.editorStore.applicationStore.userDataService,
165
+ {
166
+ projectId: this.sdlcState.activeProject.projectId,
167
+ workspaceId: workspace.workspaceId,
168
+ workspaceType: workspace.workspaceType,
169
+ },
170
+ );
171
+ }
158
172
  // redirect to home page if current workspace is deleted
159
173
  if (
160
174
  areWorkspacesEquivalent(
@@ -25,6 +25,7 @@ import {
25
25
  import type { EditorStore } from '../EditorStore.js';
26
26
  import type { EditorSDLCState } from '../EditorSDLCState.js';
27
27
  import { LEGEND_STUDIO_APP_EVENT } from '../../../__lib__/LegendStudioEvent.js';
28
+ import { LegendStudioUserDataHelper } from '../../../__lib__/LegendStudioUserDataHelper.js';
28
29
  import {
29
30
  type GeneratorFn,
30
31
  type PlainObject,
@@ -402,6 +403,19 @@ export class WorkspaceReviewState {
402
403
  review.id,
403
404
  { message: `${review.title} [review]` },
404
405
  );
406
+ // Committing a review deletes the workspace on SDLC. Drop it from
407
+ // the recents cache so the workspace setup screen doesn't keep
408
+ // offering a dead link. Patch workspaces are never cached.
409
+ if (this.sdlcState.activePatch === undefined) {
410
+ LegendStudioUserDataHelper.workspaceSetup_removeRecentWorkspace(
411
+ this.editorStore.applicationStore.userDataService,
412
+ {
413
+ projectId: this.sdlcState.activeProject.projectId,
414
+ workspaceId: this.sdlcState.activeWorkspace.workspaceId,
415
+ workspaceType: this.sdlcState.activeWorkspace.workspaceType,
416
+ },
417
+ );
418
+ }
405
419
  this.editorStore.applicationStore.alertService.setActionAlertInfo({
406
420
  message: 'Committed review successfully',
407
421
  prompt: