@finos/legend-application-studio 28.21.16 → 28.21.18

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 (37) hide show
  1. package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.d.ts.map +1 -1
  2. package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.js +5 -3
  3. package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.js.map +1 -1
  4. package/lib/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.d.ts.map +1 -1
  5. package/lib/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.js +1 -19
  6. package/lib/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.js.map +1 -1
  7. package/lib/components/editor/editor-group/project-configuration-editor/ProjectConfigurationEditor.d.ts.map +1 -1
  8. package/lib/components/editor/editor-group/project-configuration-editor/ProjectConfigurationEditor.js +3 -1
  9. package/lib/components/editor/editor-group/project-configuration-editor/ProjectConfigurationEditor.js.map +1 -1
  10. package/lib/components/editor/editor-group/project-configuration-editor/ProjectDependencyEditor.d.ts.map +1 -1
  11. package/lib/components/editor/editor-group/project-configuration-editor/ProjectDependencyEditor.js +11 -1
  12. package/lib/components/editor/editor-group/project-configuration-editor/ProjectDependencyEditor.js.map +1 -1
  13. package/lib/components/editor/editor-group/uml-editor/ClassEditor.d.ts.map +1 -1
  14. package/lib/components/editor/editor-group/uml-editor/ClassEditor.js +14 -5
  15. package/lib/components/editor/editor-group/uml-editor/ClassEditor.js.map +1 -1
  16. package/lib/components/editor/editor-group/uml-editor/PropertyEditor.d.ts.map +1 -1
  17. package/lib/components/editor/editor-group/uml-editor/PropertyEditor.js +4 -1
  18. package/lib/components/editor/editor-group/uml-editor/PropertyEditor.js.map +1 -1
  19. package/lib/index.css +2 -2
  20. package/lib/index.css.map +1 -1
  21. package/lib/package.json +1 -1
  22. package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.d.ts +2 -4
  23. package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.d.ts.map +1 -1
  24. package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.js +98 -64
  25. package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.js.map +1 -1
  26. package/lib/stores/editor/editor-state/project-configuration-editor-state/ProjectDependencyEditorState.d.ts.map +1 -1
  27. package/lib/stores/editor/editor-state/project-configuration-editor-state/ProjectDependencyEditorState.js +6 -0
  28. package/lib/stores/editor/editor-state/project-configuration-editor-state/ProjectDependencyEditorState.js.map +1 -1
  29. package/package.json +9 -9
  30. package/src/components/editor/editor-group/data-editor/RelationElementsDataEditor.tsx +5 -3
  31. package/src/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.tsx +3 -32
  32. package/src/components/editor/editor-group/project-configuration-editor/ProjectConfigurationEditor.tsx +11 -1
  33. package/src/components/editor/editor-group/project-configuration-editor/ProjectDependencyEditor.tsx +19 -2
  34. package/src/components/editor/editor-group/uml-editor/ClassEditor.tsx +24 -0
  35. package/src/components/editor/editor-group/uml-editor/PropertyEditor.tsx +7 -0
  36. package/src/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.ts +145 -85
  37. package/src/stores/editor/editor-state/project-configuration-editor-state/ProjectDependencyEditorState.ts +17 -0
@@ -861,20 +861,9 @@ const FunctionTestDataPanel = observer(
861
861
  (props: { functionTestSuiteState: FunctionTestSuiteState }) => {
862
862
  const { functionTestSuiteState } = props;
863
863
  const dataState = functionTestSuiteState.dataState;
864
- const hasIngestOrDataProductAccessors =
865
- functionTestSuiteState.functionTestableState
866
- .resolvedIngestOrDataProductAccessors.length > 0;
867
864
  const hasTestData = Boolean(dataState.dataHolder.testData?.length);
868
865
 
869
866
  const addStoreTestData = (): void => {
870
- if (!dataState.availableElementsToAdd.length) {
871
- functionTestSuiteState.editorStore.applicationStore.notificationService.notifyWarning(
872
- hasIngestOrDataProductAccessors
873
- ? `All referenced elements' data is already present`
874
- : `No elements available to add`,
875
- );
876
- return;
877
- }
878
867
  dataState.setShowAddElementModal(true);
879
868
  };
880
869
 
@@ -923,7 +912,9 @@ const FunctionTestDataPanel = observer(
923
912
  tabIndex={-1}
924
913
  >
925
914
  <div className="testable-test-explorer__item__label__text">
926
- {td.element.value.path}
915
+ <span title={td.element.value.path}>
916
+ {td.element.value.name}
917
+ </span>
927
918
  </div>
928
919
  <div className="mapping-test-explorer__item__actions">
929
920
  <button
@@ -1076,26 +1067,6 @@ const FunctionTestsPanel = observer(
1076
1067
  const FunctionTestSuiteEditor = observer(
1077
1068
  (props: { functionTestSuiteState: FunctionTestSuiteState }) => {
1078
1069
  const { functionTestSuiteState } = props;
1079
- const hasTestData = Boolean(
1080
- functionTestSuiteState.dataState.dataHolder.testData?.length,
1081
- );
1082
- const hasIngestOrDataProductAccessors =
1083
- functionTestSuiteState.functionTestableState
1084
- .resolvedIngestOrDataProductAccessors.length > 0;
1085
- const showTestDataPanel =
1086
- functionTestSuiteState.functionTestableState.containsRuntime ||
1087
- hasIngestOrDataProductAccessors ||
1088
- hasTestData;
1089
-
1090
- if (!showTestDataPanel) {
1091
- // No test data — just show the tests panel full width
1092
- return (
1093
- <div className="service-test-suite-editor">
1094
- <FunctionTestsPanel functionTestSuiteState={functionTestSuiteState} />
1095
- </div>
1096
- );
1097
- }
1098
-
1099
1070
  return (
1100
1071
  <div className="service-test-suite-editor">
1101
1072
  <ResizablePanelGroup orientation="horizontal">
@@ -67,6 +67,7 @@ import {
67
67
  DocumentationPreview,
68
68
  } from '@finos/legend-lego/application';
69
69
  import type { StoreProjectData } from '@finos/legend-server-depot';
70
+ import { generateGAVCoordinates } from '@finos/legend-storage';
70
71
 
71
72
  const isProjectConfigurationVersionOutdated = (
72
73
  projectConfig: ProjectConfiguration,
@@ -715,7 +716,16 @@ export const ProjectConfigurationEditor = observer(() => {
715
716
  async (): Promise<void> => {
716
717
  if (!isReadOnly) {
717
718
  if (selectedTab === CONFIGURATION_EDITOR_TAB.PROJECT_DEPENDENCIES) {
718
- const currentProjects = Array.from(configState.projects.values());
719
+ const currentProjectCoordinates = generateGAVCoordinates(
720
+ currentProjectConfiguration.groupId,
721
+ currentProjectConfiguration.artifactId,
722
+ undefined,
723
+ );
724
+ const currentProjects = Array.from(
725
+ configState.projects.values(),
726
+ ).filter(
727
+ (project) => project.coordinates !== currentProjectCoordinates,
728
+ );
719
729
  if (currentProjects.length) {
720
730
  const projectToAdd = currentProjects[0] as StoreProjectData;
721
731
  const dependencyToAdd = new ProjectDependency(
@@ -1068,6 +1068,13 @@ const ProjectDependencyInlineExclusionsSelector = observer(
1068
1068
  );
1069
1069
 
1070
1070
  const visitedNodes = new Set<string>();
1071
+ const currentProjectCoordinate = generateGAVCoordinates(
1072
+ editorStore.projectConfigurationEditorState
1073
+ .currentProjectConfiguration.groupId,
1074
+ editorStore.projectConfigurationEditorState
1075
+ .currentProjectConfiguration.artifactId,
1076
+ undefined,
1077
+ );
1071
1078
  const traverseNode = (nodeId: string) => {
1072
1079
  if (visitedNodes.has(nodeId)) {
1073
1080
  return;
@@ -1090,7 +1097,8 @@ const ProjectDependencyInlineExclusionsSelector = observer(
1090
1097
  if (
1091
1098
  existingExclusionCoordinates.indexOf(coordinate) === -1 &&
1092
1099
  coordinate !==
1093
- `${projectDependency.groupId}:${projectDependency.artifactId}`
1100
+ `${projectDependency.groupId}:${projectDependency.artifactId}` &&
1101
+ coordinate !== currentProjectCoordinate
1094
1102
  ) {
1095
1103
  transitiveDeps.set(coordinate, {
1096
1104
  label: generateGAVCoordinates(
@@ -1125,6 +1133,10 @@ const ProjectDependencyInlineExclusionsSelector = observer(
1125
1133
  projectDependency.groupId,
1126
1134
  projectDependency.artifactId,
1127
1135
  projectDependency.versionId,
1136
+ editorStore.projectConfigurationEditorState.currentProjectConfiguration
1137
+ .groupId,
1138
+ editorStore.projectConfigurationEditorState.currentProjectConfiguration
1139
+ .artifactId,
1128
1140
  ]);
1129
1141
 
1130
1142
  useEffect(() => {
@@ -1141,7 +1153,6 @@ const ProjectDependencyInlineExclusionsSelector = observer(
1141
1153
  if (!option) {
1142
1154
  return;
1143
1155
  }
1144
-
1145
1156
  try {
1146
1157
  dependencyEditorState.addExclusionByCoordinate(
1147
1158
  projectDependency.projectId,
@@ -1310,7 +1321,13 @@ const ProjectVersionDependencyEditor = observer(
1310
1321
  const projectDisabled =
1311
1322
  !configState.associatedProjectsAndVersionsFetched ||
1312
1323
  configState.isReadOnly;
1324
+ const currentProjectCoordinates = generateGAVCoordinates(
1325
+ configState.currentProjectConfiguration.groupId,
1326
+ configState.currentProjectConfiguration.artifactId,
1327
+ undefined,
1328
+ );
1313
1329
  const projectsOptions = Array.from(configState.projects.values())
1330
+ .filter((project) => project.coordinates !== currentProjectCoordinates)
1314
1331
  .map(buildProjectOption)
1315
1332
  .sort(compareLabelFn);
1316
1333
  const onProjectSelectionChange = async (
@@ -159,6 +159,7 @@ const PropertyBasicEditor = observer(
159
159
  const { property, _class, editorState, deleteProperty, isReadOnly } = props;
160
160
 
161
161
  const editorStore = useEditorStore();
162
+ const applicationStore = useApplicationStore();
162
163
  const isInheritedProperty =
163
164
  property._OWNER instanceof Class && property._OWNER !== _class;
164
165
  const isPropertyFromAssociation = property._OWNER instanceof Association;
@@ -340,6 +341,10 @@ const PropertyBasicEditor = observer(
340
341
  placeholder="Choose a type..."
341
342
  filterOption={filterOption}
342
343
  formatOptionLabel={getPackageableElementOptionFormatter({})}
344
+ darkMode={
345
+ !applicationStore.layoutService
346
+ .TEMPORARY__isLightColorThemeEnabled
347
+ }
343
348
  />
344
349
  )}
345
350
  {!isIndirectProperty && !isReadOnly && !isEditingType && (
@@ -713,6 +718,10 @@ const DerivedPropertyBasicEditor = observer(
713
718
  placeholder="Choose a type..."
714
719
  filterOption={filterOption}
715
720
  formatOptionLabel={getPackageableElementOptionFormatter({})}
721
+ darkMode={
722
+ !applicationStore.layoutService
723
+ .TEMPORARY__isLightColorThemeEnabled
724
+ }
716
725
  />
717
726
  )}
718
727
  {!isInheritedProperty && !isReadOnly && !isEditingType && (
@@ -1044,6 +1053,7 @@ const SuperTypeEditor = observer(
1044
1053
 
1045
1054
  const { superType, _class, deleteSuperType, isReadOnly } = props;
1046
1055
  const editorStore = useEditorStore();
1056
+ const applicationStore = useApplicationStore();
1047
1057
  // Type
1048
1058
  const superTypeOptions = editorStore.graphManagerState.usableClasses
1049
1059
  .filter(
@@ -1136,6 +1146,10 @@ const SuperTypeEditor = observer(
1136
1146
  placeholder="Choose a class"
1137
1147
  filterOption={filterOption}
1138
1148
  formatOptionLabel={getPackageableElementOptionFormatter({})}
1149
+ darkMode={
1150
+ !applicationStore.layoutService
1151
+ .TEMPORARY__isLightColorThemeEnabled
1152
+ }
1139
1153
  />
1140
1154
  <button
1141
1155
  className="uml-element-editor__basic__detail-btn"
@@ -1472,6 +1486,7 @@ const SupertypesEditor = observer(
1472
1486
  const TaggedValuesEditor = observer(
1473
1487
  (props: { _class: Class; editorState: ClassEditorState }) => {
1474
1488
  const { _class, editorState } = props;
1489
+ const applicationStore = useApplicationStore();
1475
1490
  const isReadOnly = editorState.isReadOnly;
1476
1491
 
1477
1492
  const deleteTaggedValue =
@@ -1519,6 +1534,10 @@ const TaggedValuesEditor = observer(
1519
1534
  taggedValue={taggedValue}
1520
1535
  deleteValue={deleteTaggedValue(taggedValue)}
1521
1536
  isReadOnly={isReadOnly}
1537
+ darkTheme={
1538
+ !applicationStore.layoutService
1539
+ .TEMPORARY__isLightColorThemeEnabled
1540
+ }
1522
1541
  />
1523
1542
  ))}
1524
1543
  </PanelContentLists>
@@ -1530,6 +1549,7 @@ const TaggedValuesEditor = observer(
1530
1549
  const StereotypesEditor = observer(
1531
1550
  (props: { _class: Class; editorState: ClassEditorState }) => {
1532
1551
  const { _class, editorState } = props;
1552
+ const applicationStore = useApplicationStore();
1533
1553
  const isReadOnly = editorState.isReadOnly;
1534
1554
 
1535
1555
  const deleteStereotype =
@@ -1579,6 +1599,10 @@ const StereotypesEditor = observer(
1579
1599
  stereotype={stereotype}
1580
1600
  deleteStereotype={deleteStereotype(stereotype)}
1581
1601
  isReadOnly={isReadOnly}
1602
+ darkTheme={
1603
+ !applicationStore.layoutService
1604
+ .TEMPORARY__isLightColorThemeEnabled
1605
+ }
1582
1606
  />
1583
1607
  ))}
1584
1608
  </PanelContentLists>
@@ -45,6 +45,7 @@ import {
45
45
  import { LEGEND_STUDIO_TEST_ID } from '../../../../__lib__/LegendStudioTesting.js';
46
46
  import { isNonNullable, prettyCONSTName } from '@finos/legend-shared';
47
47
  import { UML_EDITOR_TAB } from '../../../../stores/editor/editor-state/element-editor-state/UMLEditorState.js';
48
+ import { useApplicationStore } from '@finos/legend-application';
48
49
  import {
49
50
  type AbstractProperty,
50
51
  type StereotypeReference,
@@ -81,6 +82,9 @@ export const PropertyEditor = observer(
81
82
  isReadOnly: boolean;
82
83
  }) => {
83
84
  const { property, deselectProperty, isReadOnly } = props;
85
+ const applicationStore = useApplicationStore();
86
+ const darkMode =
87
+ !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled;
84
88
  // Tab
85
89
  const [selectedTab, setSelectedTab] = useState<UML_EDITOR_TAB>(
86
90
  property instanceof Property
@@ -272,6 +276,7 @@ export const PropertyEditor = observer(
272
276
  escapeClearsValue={true}
273
277
  isClearable={true}
274
278
  disabled={isReadOnly}
279
+ darkMode={darkMode}
275
280
  />
276
281
  </PanelFormSection>
277
282
  </PanelForm>
@@ -290,6 +295,7 @@ export const PropertyEditor = observer(
290
295
  taggedValue={taggedValue}
291
296
  deleteValue={_deleteTaggedValue(taggedValue)}
292
297
  isReadOnly={isReadOnly}
298
+ darkTheme={darkMode}
293
299
  />
294
300
  ))}
295
301
  </PanelContentLists>
@@ -309,6 +315,7 @@ export const PropertyEditor = observer(
309
315
  stereotype={stereotype}
310
316
  deleteStereotype={_deleteStereotype(stereotype)}
311
317
  isReadOnly={isReadOnly}
318
+ darkTheme={darkMode}
312
319
  />
313
320
  ))}
314
321
  </PanelContentLists>
@@ -14,7 +14,14 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
- import { action, flow, flowResult, makeObservable, observable } from 'mobx';
17
+ import {
18
+ action,
19
+ flow,
20
+ flowResult,
21
+ makeObservable,
22
+ observable,
23
+ runInAction,
24
+ } from 'mobx';
18
25
  import type { EditorStore } from '../../../../EditorStore.js';
19
26
  import type { FunctionEditorState } from '../../FunctionEditorState.js';
20
27
  import {
@@ -43,6 +50,8 @@ import {
43
50
  type ValueSpecification,
44
51
  type TestAssertion,
45
52
  type AccessorOwner,
53
+ type AbstractPureGraphManager,
54
+ type PackageableElement,
46
55
  FunctionParameterValue,
47
56
  VariableExpression,
48
57
  FunctionTest,
@@ -57,8 +66,11 @@ import {
57
66
  InstanceValue,
58
67
  PackageableElementReference,
59
68
  Database,
69
+ DataProduct,
70
+ IngestDefinition,
60
71
  PackageableElementExplicitReference,
61
72
  observe_ValueSpecification,
73
+ observe_RelationElementsData,
62
74
  buildLambdaVariableExpressions,
63
75
  EqualTo,
64
76
  EqualToRelation,
@@ -72,7 +84,7 @@ import {
72
84
  type Accessor,
73
85
  DataProductAccessor,
74
86
  IngestionAccessor,
75
- RelationalStoreAccessor,
87
+ getAccessorItemLabelForElement,
76
88
  V1_buildRelationElementsDataFromAccessors,
77
89
  } from '@finos/legend-graph';
78
90
  import {
@@ -191,6 +203,28 @@ const resolveRuntimesFromQuery = (
191
203
  }
192
204
  };
193
205
 
206
+ interface ElementDataItem {
207
+ id: string;
208
+ label: string;
209
+ }
210
+
211
+ const getElementDataItems = (
212
+ element: PackageableElement,
213
+ graphManager: AbstractPureGraphManager,
214
+ ): ElementDataItem[] => {
215
+ if (element instanceof DataProduct) {
216
+ return element.accessPointGroups
217
+ .flatMap((g) => g.accessPoints)
218
+ .map((ap) => ({ id: ap.id, label: ap.id }));
219
+ }
220
+ if (element instanceof IngestDefinition) {
221
+ return graphManager
222
+ .getIngestDefinitionDatasetNames(element)
223
+ .map((name) => ({ id: name, label: name }));
224
+ }
225
+ return [];
226
+ };
227
+
194
228
  export class FunctionStoreTestDataState {
195
229
  readonly editorStore: EditorStore;
196
230
  readonly testDataState: FunctionTestDataState;
@@ -239,51 +273,69 @@ export class FunctionStoreTestDataState {
239
273
  embeddedDataState: RelationElementsDataState,
240
274
  ): Promise<void> {
241
275
  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) {
276
+ const graphManager = this.editorStore.graphManagerState.graphManager;
277
+ const graph = this.editorStore.graphManagerState.graph;
278
+
279
+ // For Database parents, fall back to the schema+table form
280
+ if (parentElement instanceof Database) {
249
281
  embeddedDataState.setAccessorOptions(undefined, undefined);
250
282
  return;
251
283
  }
252
- const firstAccessor = parentAccessors[0];
253
- if (!firstAccessor) {
284
+
285
+ const items = getElementDataItems(parentElement, graphManager);
286
+ if (items.length === 0) {
254
287
  embeddedDataState.setAccessorOptions(undefined, undefined);
255
288
  return;
256
289
  }
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) {
290
+ const typeLabel = getAccessorItemLabelForElement(
291
+ parentElement as AccessorOwner,
292
+ );
293
+ const options = await Promise.all(
294
+ items.map(async (item) => {
295
+ let columns: string[] = [];
296
+ try {
297
+ if (parentElement instanceof IngestDefinition) {
298
+ const accessor =
299
+ await graphManager.createAccessorFromPackageableElement(
300
+ parentElement,
301
+ graph,
302
+ { schemaName: undefined, tableName: item.id },
303
+ );
304
+ if (accessor) {
305
+ columns = accessor.relationType.columns.map((c) => c.name);
306
+ }
307
+ } else if (parentElement instanceof DataProduct) {
308
+ const accessor = await graphManager.buildDataProductAccessor(
309
+ parentElement,
310
+ graph,
311
+ { tableName: item.id },
312
+ );
313
+ if (accessor) {
314
+ columns = accessor.relationType.columns.map((c) => c.name);
315
+ }
316
+ }
317
+ } catch {}
318
+ return { label: item.label, value: item.id, columns };
319
+ }),
320
+ );
321
+ runInAction(() => {
322
+ embeddedDataState.setAccessorOptions(options, typeLabel);
323
+ const columnsByItem = new Map(
324
+ options
325
+ .filter((o) => o.columns.length > 0)
326
+ .map((o) => [o.value, o.columns]),
327
+ );
274
328
  for (const relState of embeddedDataState.relationElementStates) {
275
329
  const rel = relState.relationElement;
276
330
  if (rel.columns.length === 0) {
277
331
  const key = rel.paths[rel.paths.length - 1];
278
- const cols = key ? columnOverrides.get(key) : undefined;
332
+ const cols = key ? columnsByItem.get(key) : undefined;
279
333
  if (cols) {
280
334
  rel.columns = cols;
281
335
  }
282
336
  }
283
337
  }
284
- }
285
-
286
- embeddedDataState.setAccessorOptions(options, typeLabel);
338
+ });
287
339
  }
288
340
 
289
341
  setDataElementModal(val: boolean): void {
@@ -655,34 +707,38 @@ class FunctionTestDataState {
655
707
  return (this.dataHolder.testData ?? []).map((td) => td.element.value.path);
656
708
  }
657
709
 
658
- get availableElementsToAdd(): { path: string }[] {
659
- const accessors =
660
- this.functionTestableState.resolvedIngestOrDataProductAccessors;
710
+ get availableElementsToAdd(): PackageableElement[] {
711
+ const graph = this.editorStore.graphManagerState.graph;
661
712
  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 }));
713
+ const candidates: PackageableElement[] = [
714
+ ...graph.ingests,
715
+ ...graph.dataProducts,
716
+ ...graph.databases,
717
+ ];
718
+ return candidates.filter((e) => !existingPaths.has(e.path));
670
719
  }
671
720
 
672
721
  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) {
722
+ const element =
723
+ this.editorStore.graphManagerState.graph.getNullableElement(elementPath);
724
+ if (!element) {
677
725
  return;
678
726
  }
679
- const element =
680
- this.editorStore.graphManagerState.graph.getElement(elementPath);
681
727
  const data = new FunctionTestData();
682
728
  data.element = PackageableElementExplicitReference.create(
683
729
  element as AccessorOwner,
684
730
  );
685
- data.data = V1_buildRelationElementsDataFromAccessors(group);
731
+ const matchingAccessors = this.functionTestableState.cachedAccessors.filter(
732
+ (a) => a.accessorOwner === elementPath,
733
+ );
734
+ if (matchingAccessors.length) {
735
+ data.data = V1_buildRelationElementsDataFromAccessors(matchingAccessors);
736
+ } else {
737
+ const relData = new RelationElementsData();
738
+ relData.relationElements = [];
739
+ data.data = relData;
740
+ }
741
+ observe_RelationElementsData(data.data as RelationElementsData);
686
742
  if (!this.dataHolder.testData) {
687
743
  this.dataHolder.testData = [];
688
744
  }
@@ -966,36 +1022,9 @@ export class FunctionTestableState extends TestablePackageableElementEditorState
966
1022
 
967
1023
  const ingestOrDataProductAccessors =
968
1024
  this.resolvedIngestOrDataProductAccessors;
969
- const databaseAccessors = this.cachedAccessors.filter(
970
- (a) => a instanceof RelationalStoreAccessor,
971
- );
972
1025
 
973
1026
  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
- },
996
- );
997
- } else {
998
- // No ingest/data product accessors found — try runtime-based approach
1027
+ if (!ingestOrDataProductAccessors.length) {
999
1028
  const engineRuntimes = this.associatedRuntimes;
1000
1029
  if (engineRuntimes?.length) {
1001
1030
  assertTrue(
@@ -1047,10 +1076,44 @@ export class FunctionTestableState extends TestablePackageableElementEditorState
1047
1076
  type.path === CORE_PURE_PATH.RELATION ||
1048
1077
  type.path === CORE_PURE_PATH.TABULAR_DATASET
1049
1078
  ) {
1050
- this.editorStore.applicationStore.notificationService.notifyError(
1051
- `Unable to create function test suite: no runtime or accessors found, or they could not be resolved`,
1079
+ this.editorStore.applicationStore.notificationService.notifyWarning(
1080
+ `No runtime or accessors found, or they could not be resolved. For Relational (non-Lakehouse) function testing, please ensure that your query is bound to a runtime`,
1081
+ );
1082
+ // continue: still allow the user to create the suite/test and
1083
+ // add test data manually
1084
+ }
1085
+ }
1086
+ }
1087
+
1088
+ if (this.cachedAccessors.length) {
1089
+ const accessorsByOwner = new Map<string, Accessor[]>();
1090
+ for (const accessor of this.cachedAccessors) {
1091
+ const key = accessor.accessorOwner;
1092
+ if (key) {
1093
+ const group = accessorsByOwner.get(key) ?? [];
1094
+ group.push(accessor);
1095
+ accessorsByOwner.set(key, group);
1096
+ }
1097
+ }
1098
+ if (!functionSuite.testData) {
1099
+ functionSuite.testData = [];
1100
+ }
1101
+ for (const [parentPath, group] of accessorsByOwner.entries()) {
1102
+ const builtData = V1_buildRelationElementsDataFromAccessors(group);
1103
+ const existing = functionSuite.testData.find(
1104
+ (td) => td.element.value.path === parentPath,
1105
+ );
1106
+ if (existing) {
1107
+ existing.data = builtData;
1108
+ } else {
1109
+ const element =
1110
+ this.editorStore.graphManagerState.graph.getElement(parentPath);
1111
+ const data = new FunctionTestData();
1112
+ data.element = PackageableElementExplicitReference.create(
1113
+ element as AccessorOwner,
1052
1114
  );
1053
- return;
1115
+ data.data = builtData;
1116
+ functionSuite.testData.push(data);
1054
1117
  }
1055
1118
  }
1056
1119
  }
@@ -1062,10 +1125,7 @@ export class FunctionTestableState extends TestablePackageableElementEditorState
1062
1125
  return;
1063
1126
  }
1064
1127
 
1065
- const hasTestData =
1066
- this.containsRuntime ||
1067
- ingestOrDataProductAccessors.length > 0 ||
1068
- databaseAccessors.length > 0;
1128
+ const hasTestData = this.containsRuntime || this.cachedAccessors.length > 0;
1069
1129
  yield createFunctionTest(
1070
1130
  testName,
1071
1131
  this.editorStore.changeDetectionState.observerContext,
@@ -444,6 +444,23 @@ export class ProjectDependencyEditorState {
444
444
  return;
445
445
  }
446
446
 
447
+ const currentProjectCoordinate = generateGAVCoordinates(
448
+ this.configState.currentProjectConfiguration.groupId,
449
+ this.configState.currentProjectConfiguration.artifactId,
450
+ undefined,
451
+ );
452
+ const exclusionCoordinate = generateGAVCoordinates(
453
+ guaranteeNonNullable(exclusion.groupId),
454
+ guaranteeNonNullable(exclusion.artifactId),
455
+ undefined,
456
+ );
457
+ if (exclusionCoordinate === currentProjectCoordinate) {
458
+ this.editorStore.applicationStore.notificationService.notifyWarning(
459
+ 'You cannot exclude your own project from a dependency',
460
+ );
461
+ return;
462
+ }
463
+
447
464
  const existingExclusion = this.findExistingExclusion(
448
465
  dependencyId,
449
466
  generateGAVCoordinates(