@finos/legend-application-studio 28.21.19 → 28.21.21

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 (42) hide show
  1. package/lib/__lib__/LegendStudioEvent.d.ts +6 -0
  2. package/lib/__lib__/LegendStudioEvent.d.ts.map +1 -1
  3. package/lib/__lib__/LegendStudioEvent.js +7 -0
  4. package/lib/__lib__/LegendStudioEvent.js.map +1 -1
  5. package/lib/__lib__/LegendStudioTelemetryHelper.d.ts +6 -0
  6. package/lib/__lib__/LegendStudioTelemetryHelper.d.ts.map +1 -1
  7. package/lib/__lib__/LegendStudioTelemetryHelper.js +19 -0
  8. package/lib/__lib__/LegendStudioTelemetryHelper.js.map +1 -1
  9. package/lib/components/editor/ActivityBar.d.ts +2 -4
  10. package/lib/components/editor/ActivityBar.d.ts.map +1 -1
  11. package/lib/components/editor/ActivityBar.js +3 -17
  12. package/lib/components/editor/ActivityBar.js.map +1 -1
  13. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.d.ts +1 -1
  14. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.d.ts.map +1 -1
  15. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js +56 -39
  16. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js.map +1 -1
  17. package/lib/components/editor/editor-group/service-editor/ServiceEditor.d.ts.map +1 -1
  18. package/lib/components/editor/editor-group/service-editor/ServiceEditor.js +8 -1
  19. package/lib/components/editor/editor-group/service-editor/ServiceEditor.js.map +1 -1
  20. package/lib/index.css +2 -2
  21. package/lib/index.css.map +1 -1
  22. package/lib/package.json +1 -1
  23. package/lib/stores/editor/EditorGraphState.d.ts.map +1 -1
  24. package/lib/stores/editor/EditorGraphState.js +4 -1
  25. package/lib/stores/editor/EditorGraphState.js.map +1 -1
  26. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.d.ts +6 -2
  27. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.d.ts.map +1 -1
  28. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js +43 -17
  29. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js.map +1 -1
  30. package/lib/stores/editor/utils/ModelClassifierUtils.d.ts +1 -0
  31. package/lib/stores/editor/utils/ModelClassifierUtils.d.ts.map +1 -1
  32. package/lib/stores/editor/utils/ModelClassifierUtils.js +1 -0
  33. package/lib/stores/editor/utils/ModelClassifierUtils.js.map +1 -1
  34. package/package.json +12 -12
  35. package/src/__lib__/LegendStudioEvent.ts +8 -0
  36. package/src/__lib__/LegendStudioTelemetryHelper.ts +61 -0
  37. package/src/components/editor/ActivityBar.tsx +3 -22
  38. package/src/components/editor/editor-group/dataProduct/DataProductEditor.tsx +96 -42
  39. package/src/components/editor/editor-group/service-editor/ServiceEditor.tsx +17 -1
  40. package/src/stores/editor/EditorGraphState.ts +3 -0
  41. package/src/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.ts +98 -67
  42. package/src/stores/editor/utils/ModelClassifierUtils.ts +1 -0
@@ -19,10 +19,10 @@ import { useEditorStore } from '../../EditorStoreProvider.js';
19
19
  import {
20
20
  type AccessPointGroupState,
21
21
  type AccessPointState,
22
+ type LakehouseAccessPointState,
22
23
  DATA_PRODUCT_TAB,
23
24
  DATA_PRODUCT_TYPE,
24
25
  DataProductEditorState,
25
- LakehouseAccessPointState,
26
26
  ModelAccessPointGroupState,
27
27
  } from '../../../../stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js';
28
28
  import {
@@ -87,12 +87,12 @@ import {
87
87
  useCallback,
88
88
  useEffect,
89
89
  useLayoutEffect,
90
+ useMemo,
90
91
  useRef,
91
92
  useState,
92
93
  } from 'react';
93
94
  import {
94
95
  assertErrorThrown,
95
- filterByType,
96
96
  guaranteeNonNullable,
97
97
  guaranteeType,
98
98
  isNonNullable,
@@ -208,6 +208,7 @@ import type {
208
208
  DataProductDocResponse,
209
209
  DSL_DataProduct_LegendStudioApplicationPlugin_Extension,
210
210
  } from '../../../../stores/extensions/DSL_DataProduct_LegendStudioApplicationPlugin_Extension.js';
211
+ import { LegendStudioTelemetryHelper } from '../../../../__lib__/LegendStudioTelemetryHelper.js';
211
212
 
212
213
  export enum AP_GROUP_MODAL_ERRORS {
213
214
  GROUP_NAME_EMPTY = 'Group Name is empty',
@@ -331,6 +332,17 @@ const AccessPointTitle = observer(
331
332
  },
332
333
  );
333
334
 
335
+ const AP_RENDER_CHUNK_SIZE = 50;
336
+ const AP_SENTINEL_ROOT_MARGIN = '0px 0px 400px 0px';
337
+ const DEFAULT_CLASSIFICATION_LABEL = 'Classification';
338
+
339
+ const toClassificationOption = (
340
+ classification: string,
341
+ ): { label: string; value: string } => ({
342
+ label: classification,
343
+ value: classification,
344
+ });
345
+
334
346
  const AccessPointClassification = observer(
335
347
  (props: {
336
348
  accessPoint: LakehouseAccessPoint;
@@ -338,52 +350,35 @@ const AccessPointClassification = observer(
338
350
  }) => {
339
351
  const { accessPoint, groupState } = props;
340
352
  const applicationStore = useEditorStore().applicationStore;
341
- const CHOOSE_CLASSIFICATION = 'Classification';
342
353
  const updateAccessPointClassificationTextbox: React.ChangeEventHandler<HTMLTextAreaElement> =
343
354
  action((event) => {
344
355
  accessPoint.classification = event.target.value;
345
356
  });
346
357
 
347
- const conditionalClassifications = (): string[] => {
348
- if (groupState.containsPublicStereotype) {
349
- return (
350
- applicationStore.config.options.dataProductConfig
351
- ?.publicClassifications ?? []
352
- );
353
- } else {
354
- return (
355
- applicationStore.config.options.dataProductConfig?.classifications ??
356
- []
357
- );
358
- }
359
- };
360
-
361
- const classificationOptions = [CHOOSE_CLASSIFICATION]
362
- .concat(conditionalClassifications())
363
- .map((classfication) => ({
364
- label: classfication,
365
- value: classfication,
366
- }));
358
+ const classificationOptions = useMemo(
359
+ () =>
360
+ [DEFAULT_CLASSIFICATION_LABEL]
361
+ .concat(groupState.classifications)
362
+ .map(toClassificationOption),
363
+ [groupState.classifications],
364
+ );
367
365
 
368
366
  const updateAccessPointClassificationFromDropdown = action(
369
367
  (val: { label: string; value: string } | null): void => {
370
368
  accessPoint_setClassification(
371
369
  accessPoint,
372
- val?.value === CHOOSE_CLASSIFICATION ? undefined : val?.value,
370
+ val?.value === DEFAULT_CLASSIFICATION_LABEL ? undefined : val?.value,
373
371
  );
374
372
  },
375
373
  );
376
374
 
377
- const currentClassification =
378
- accessPoint.classification !== undefined
379
- ? {
380
- label: accessPoint.classification,
381
- value: accessPoint.classification,
382
- }
383
- : {
384
- label: CHOOSE_CLASSIFICATION,
385
- value: CHOOSE_CLASSIFICATION,
386
- };
375
+ const currentClassification = useMemo(
376
+ () =>
377
+ toClassificationOption(
378
+ accessPoint.classification ?? DEFAULT_CLASSIFICATION_LABEL,
379
+ ),
380
+ [accessPoint.classification],
381
+ );
387
382
 
388
383
  const classificationDocumentationLink = (): void => {
389
384
  const docLink =
@@ -399,7 +394,7 @@ const AccessPointClassification = observer(
399
394
  style={{
400
395
  borderWidth: 'thin',
401
396
  borderColor:
402
- currentClassification.label === CHOOSE_CLASSIFICATION
397
+ currentClassification.label === DEFAULT_CLASSIFICATION_LABEL
403
398
  ? 'var(--color-red-300)'
404
399
  : 'transparent',
405
400
  }}
@@ -721,9 +716,7 @@ export const LakehouseDataProductAccessPointEditor = observer(
721
716
  const accessPoint = accessPointState.accessPoint;
722
717
  const groupState = accessPointState.state;
723
718
  const lambdaEditorState = accessPointState.lambdaState;
724
- const propertyHasParserError = groupState.accessPointStates
725
- .filter(filterByType(LakehouseAccessPointState))
726
- .find((pm) => pm.lambdaState.parserError);
719
+ const propertyHasParserError = groupState.hasParserError;
727
720
  const [editingDescription, setEditingDescription] = useState(false);
728
721
  const [isHoveringDesc, setIsHoveringDesc] = useState(false);
729
722
  const [editingTitle, setEditingTitle] = useState(false);
@@ -736,6 +729,17 @@ export const LakehouseDataProductAccessPointEditor = observer(
736
729
  );
737
730
  }, [lambdaEditorState, editorStore.applicationStore]);
738
731
 
732
+ // Load the relation columns for access points that already have sample
733
+ // values so mismatches surface on load. This only runs for rendered access
734
+ // points, keeping the number of concurrent calls bounded to the visible set.
735
+ useEffect(() => {
736
+ if (accessPointState.relationElementState !== undefined) {
737
+ flowResult(lambdaEditorState.updateLambdaRelationColumns()).catch(
738
+ editorStore.applicationStore.alertUnhandledError,
739
+ );
740
+ }
741
+ }, [accessPointState, lambdaEditorState, editorStore.applicationStore]);
742
+
739
743
  const handleDescriptionEdit = () => setEditingDescription(true);
740
744
  const handleDescriptionBlur = () => {
741
745
  setEditingDescription(false);
@@ -1019,7 +1023,9 @@ export const LakehouseDataProductAccessPointEditor = observer(
1019
1023
  <button
1020
1024
  className="access-point-editor__sample-values-btn"
1021
1025
  onClick={() => {
1022
- accessPointState.createAndaddRelationElement();
1026
+ flowResult(
1027
+ accessPointState.createAndaddRelationElement(),
1028
+ ).catch(editorStore.applicationStore.alertUnhandledError);
1023
1029
  }}
1024
1030
  disabled={props.isReadOnly}
1025
1031
  title="Add sample values"
@@ -1730,6 +1736,10 @@ const ModelAccessPointGroupEditor = observer(
1730
1736
  },
1731
1737
  );
1732
1738
 
1739
+ const AccessPointLoadingSentinel = (props: {
1740
+ ref?: React.Ref<HTMLDivElement>;
1741
+ }) => <div ref={props.ref} className="access-point-editor__sentinel" />;
1742
+
1733
1743
  const AccessPointGroupEditor = observer(
1734
1744
  (props: { groupState: AccessPointGroupState; isReadOnly: boolean }) => {
1735
1745
  const { groupState, isReadOnly } = props;
@@ -1747,6 +1757,31 @@ const AccessPointGroupEditor = observer(
1747
1757
  const [aiSuggestion, setAISuggestion] = useState<
1748
1758
  DataProductDocResponse | undefined
1749
1759
  >(undefined);
1760
+
1761
+ const lakehouseAccessPointStates =
1762
+ groupState.accessPointStates as LakehouseAccessPointState[];
1763
+ const accessPointCount = lakehouseAccessPointStates.length;
1764
+ const [renderedCount, setRenderedCount] = useState(AP_RENDER_CHUNK_SIZE);
1765
+ const accessPointsSentinelRef = useRef<HTMLDivElement>(null);
1766
+ useEffect(() => {
1767
+ const sentinel = accessPointsSentinelRef.current;
1768
+ if (!sentinel) {
1769
+ return undefined;
1770
+ }
1771
+ const intersectionObserver = new IntersectionObserver(
1772
+ (entries) => {
1773
+ if (entries[0]?.isIntersecting) {
1774
+ setRenderedCount((count) =>
1775
+ Math.min(count + AP_RENDER_CHUNK_SIZE, accessPointCount),
1776
+ );
1777
+ }
1778
+ },
1779
+ { rootMargin: AP_SENTINEL_ROOT_MARGIN },
1780
+ );
1781
+ intersectionObserver.observe(sentinel);
1782
+ return () => intersectionObserver.disconnect();
1783
+ }, [accessPointCount, renderedCount]);
1784
+
1750
1785
  const handleDescriptionEdit = () => setEditingDescription(true);
1751
1786
  const handleDescriptionBlur = () => {
1752
1787
  setEditingDescription(false);
@@ -1818,6 +1853,10 @@ const AccessPointGroupEditor = observer(
1818
1853
  if (!aiDocSuggester || !legendAIUrl) {
1819
1854
  return;
1820
1855
  }
1856
+ LegendStudioTelemetryHelper.logEvent_DataProductLegendAISuggestLaunched(
1857
+ editorStore.applicationStore.telemetryService,
1858
+ productEditorState.product.path,
1859
+ );
1821
1860
  setIsSuggestingWithAI(true);
1822
1861
  setAISuggestion(undefined);
1823
1862
  try {
@@ -1843,6 +1882,10 @@ const AccessPointGroupEditor = observer(
1843
1882
  if (!aiSuggestion) {
1844
1883
  return;
1845
1884
  }
1885
+ LegendStudioTelemetryHelper.logEvent_DataProductLegendAISuggestApplied(
1886
+ editorStore.applicationStore.telemetryService,
1887
+ productEditorState.product.path,
1888
+ );
1846
1889
  // Find matching group in the response
1847
1890
  const gIdx =
1848
1891
  productEditorState.accessPointGroupStates.indexOf(groupState);
@@ -1880,6 +1923,13 @@ const AccessPointGroupEditor = observer(
1880
1923
  }
1881
1924
  setAISuggestion(undefined);
1882
1925
  };
1926
+ const discardAISuggestion = (): void => {
1927
+ LegendStudioTelemetryHelper.logEvent_DataProductLegendAISuggestDiscarded(
1928
+ editorStore.applicationStore.telemetryService,
1929
+ productEditorState.product.path,
1930
+ );
1931
+ setAISuggestion(undefined);
1932
+ };
1883
1933
 
1884
1934
  // Computed AI suggestion for current group
1885
1935
  const groupIndex =
@@ -1922,6 +1972,7 @@ const AccessPointGroupEditor = observer(
1922
1972
  undefined,
1923
1973
  productEditorState.selectedGroupState ?? groupState,
1924
1974
  );
1975
+ setRenderedCount(groupState.accessPointStates.length);
1925
1976
  };
1926
1977
 
1927
1978
  return (
@@ -1991,7 +2042,7 @@ const AccessPointGroupEditor = observer(
1991
2042
  </button>
1992
2043
  <button
1993
2044
  className="btn data-product-editor__ai-suggestion__dismiss-btn"
1994
- onClick={(): void => setAISuggestion(undefined)}
2045
+ onClick={discardAISuggestion}
1995
2046
  >
1996
2047
  Dismiss
1997
2048
  </button>
@@ -2150,8 +2201,8 @@ const AccessPointGroupEditor = observer(
2150
2201
  <div
2151
2202
  style={{ gap: '1rem', display: 'flex', flexDirection: 'column' }}
2152
2203
  >
2153
- {groupState.accessPointStates
2154
- .filter(filterByType(LakehouseAccessPointState))
2204
+ {lakehouseAccessPointStates
2205
+ .slice(0, renderedCount)
2155
2206
  .map((apState, idx) => (
2156
2207
  <LakehouseDataProductAccessPointEditor
2157
2208
  key={apState.uuid}
@@ -2160,6 +2211,9 @@ const AccessPointGroupEditor = observer(
2160
2211
  aiSuggestionMeta={aiAccessPointMetas?.[idx]}
2161
2212
  />
2162
2213
  ))}
2214
+ {accessPointCount > renderedCount && (
2215
+ <AccessPointLoadingSentinel ref={accessPointsSentinelRef} />
2216
+ )}
2163
2217
  </div>
2164
2218
  </>
2165
2219
  )}
@@ -74,6 +74,7 @@ import { LEGEND_STUDIO_DOCUMENTATION_KEY } from '../../../../__lib__/LegendStudi
74
74
  import { DocumentationLink } from '@finos/legend-lego/application';
75
75
  import { ServicePostValidationsEditor } from './ServicePostValidationEditor.js';
76
76
  import type { DSL_Service_LegendStudioApplicationPlugin_Extension } from '../../../../stores/extensions/DSL_Service_LegendStudioApplicationPlugin_Extension.js';
77
+ import { LegendStudioTelemetryHelper } from '../../../../__lib__/LegendStudioTelemetryHelper.js';
77
78
 
78
79
  type UserOption = { label: string; value: string };
79
80
 
@@ -264,6 +265,10 @@ const ServiceGeneralEditor = observer(() => {
264
265
  if (!aiDocSuggester || !legendAIUrl) {
265
266
  return;
266
267
  }
268
+ LegendStudioTelemetryHelper.logEvent_ServiceLegendAISuggestLaunched(
269
+ applicationStore.telemetryService,
270
+ service.path,
271
+ );
267
272
  setIsSuggestingWithAI(true);
268
273
  setAIDocSuggestion(undefined);
269
274
  try {
@@ -281,9 +286,20 @@ const ServiceGeneralEditor = observer(() => {
281
286
  if (!aiDocSuggestion) {
282
287
  return;
283
288
  }
289
+ LegendStudioTelemetryHelper.logEvent_ServiceLegendAISuggestApplied(
290
+ applicationStore.telemetryService,
291
+ service.path,
292
+ );
284
293
  service_setDocumentation(service, aiDocSuggestion);
285
294
  setAIDocSuggestion(undefined);
286
295
  };
296
+ const discardAIDocSuggestion = (): void => {
297
+ LegendStudioTelemetryHelper.logEvent_ServiceLegendAISuggestDiscarded(
298
+ applicationStore.telemetryService,
299
+ service.path,
300
+ );
301
+ setAIDocSuggestion(undefined);
302
+ };
287
303
 
288
304
  const changeTitle: React.ChangeEventHandler<HTMLInputElement> = (event) => {
289
305
  if (!isReadOnly) {
@@ -498,7 +514,7 @@ const ServiceGeneralEditor = observer(() => {
498
514
  </button>
499
515
  <button
500
516
  className="btn service-editor__ai-suggestion__dismiss-btn"
501
- onClick={(): void => setAIDocSuggestion(undefined)}
517
+ onClick={discardAIDocSuggestion}
502
518
  title="Dismiss AI suggestion"
503
519
  >
504
520
  Dismiss
@@ -91,6 +91,7 @@ import {
91
91
  SnowflakeM2MUdf,
92
92
  GraphEntities,
93
93
  HostedService,
94
+ Compute,
94
95
  DataProduct,
95
96
  IngestDefinition,
96
97
  MemSQLFunction,
@@ -876,6 +877,8 @@ export class EditorGraphState {
876
877
  return PACKAGEABLE_ELEMENT_TYPE.HOSTED_SERVICE;
877
878
  } else if (element instanceof DataProduct) {
878
879
  return PACKAGEABLE_ELEMENT_TYPE._DATA_PRODUCT;
880
+ } else if (element instanceof Compute) {
881
+ return PACKAGEABLE_ELEMENT_TYPE._COMPUTE;
879
882
  } else if (element instanceof IngestDefinition) {
880
883
  return PACKAGEABLE_ELEMENT_TYPE.INGEST_DEFINITION;
881
884
  } else if (element instanceof MemSQLFunction) {
@@ -15,56 +15,56 @@
15
15
  */
16
16
 
17
17
  import {
18
- DataProduct,
19
- LakehouseAccessPoint,
20
- type PackageableElement,
18
+ type AppDirNode,
19
+ type ArtifactGenerationExtensionResult,
20
+ type DataProductElement,
21
21
  type IngestDefinition,
22
- AccessPoint,
23
- stub_RawLambda,
24
- LakehouseTargetEnv,
25
- LAMBDA_PIPE,
22
+ type Mapping,
23
+ type PackageableElement,
26
24
  type RawLambda,
27
- ParserError,
28
- GRAPH_MANAGER_EVENT,
29
- isStubbed_RawLambda,
30
- AccessPointGroup,
31
- CodeCompletionResult,
32
25
  type Stereotype,
33
- getStereotype,
34
26
  type StereotypeReference,
35
- ModelAccessPointGroup,
27
+ type V1_AccessPointGroupInfo,
28
+ type V1_AccessPointImplementation,
29
+ type V1_DataProductArtifact,
30
+ type V1_RawLineageModel,
31
+ AccessPoint,
32
+ AccessPointGroup,
33
+ AppDirOwner,
36
34
  Association,
37
35
  Class,
36
+ CodeCompletionResult,
37
+ DataElement,
38
+ DataElementReference,
39
+ DataProduct,
40
+ DataProductDiagram,
41
+ DataProductElementScope,
38
42
  Enumeration,
39
- Package,
40
- PackageableElementExplicitReference,
41
- type DataProductElement,
42
- type Mapping,
43
43
  Expertise,
44
+ getStereotype,
45
+ GRAPH_MANAGER_EVENT,
46
+ isStubbed_RawLambda,
47
+ LakehouseAccessPoint,
48
+ LakehouseTargetEnv,
49
+ LAMBDA_PIPE,
50
+ ModelAccessPointGroup,
51
+ observe_DataProductDiagram,
44
52
  observe_DataProductElementScope,
45
- DataProductElementScope,
46
- type V1_RawLineageModel,
47
- type ArtifactGenerationExtensionResult,
48
- type V1_DataProductArtifact,
49
- type V1_AccessPointGroupInfo,
50
- type V1_AccessPointImplementation,
51
- RelationElementsData,
52
- RelationElement,
53
53
  observe_RelationElement,
54
54
  observe_RelationElementsData,
55
- V1_PureGraphManager,
56
- V1_LambdaReturnTypeInput,
57
- V1_transformRawLambda,
55
+ Package,
56
+ PackageableElementExplicitReference,
57
+ ParserError,
58
+ RelationElement,
59
+ RelationElementsData,
60
+ stub_Mapping,
61
+ stub_RawLambda,
58
62
  V1_GraphTransformerContextBuilder,
63
+ V1_LambdaReturnTypeInput,
64
+ V1_PureGraphManager,
59
65
  V1_relationTypeModelSchema,
60
66
  V1_RemoteEngine,
61
- DataElementReference,
62
- DataElement,
63
- DataProductDiagram,
64
- observe_DataProductDiagram,
65
- stub_Mapping,
66
- AppDirOwner,
67
- type AppDirNode,
67
+ V1_transformRawLambda,
68
68
  } from '@finos/legend-graph';
69
69
  import type { EditorStore } from '../../../EditorStore.js';
70
70
  import { ElementEditorState } from '../ElementEditorState.js';
@@ -79,35 +79,35 @@ import {
79
79
  runInAction,
80
80
  } from 'mobx';
81
81
  import {
82
- guaranteeType,
83
- addUniqueEntry,
84
82
  type GeneratorFn,
83
+ ActionState,
84
+ addUniqueEntry,
85
85
  assertErrorThrown,
86
- LogEvent,
86
+ assertTrue,
87
87
  deleteEntry,
88
88
  filterByType,
89
- ActionState,
90
89
  guaranteeNonNullable,
91
- assertTrue,
92
- uuid,
93
- swapEntry,
90
+ guaranteeType,
91
+ LogEvent,
94
92
  returnUndefOnError,
93
+ swapEntry,
94
+ uuid,
95
95
  } from '@finos/legend-shared';
96
96
  import {
97
97
  accessPointGroup_swapAccessPoints,
98
98
  dataProduct_addAccessPoint,
99
99
  dataProduct_addAccessPointGroup,
100
- supportInfo_addExpertise,
101
- modelAccessPointGroup_addDiagram,
102
100
  dataProduct_deleteAccessPoint,
103
101
  dataProduct_deleteAccessPointGroup,
104
- modelAccessPointGroup_removeDiagram,
102
+ dataProduct_setSupportInfoIfAbsent,
105
103
  dataProduct_swapAccessPointGroups,
104
+ modelAccessPointGroup_addDiagram,
106
105
  modelAccessPointGroup_addElement,
106
+ modelAccessPointGroup_removeDiagram,
107
107
  modelAccessPointGroup_removeElement,
108
108
  modelAccessPointGroup_setElementExclude,
109
109
  modelAccessPointGroup_setMapping,
110
- dataProduct_setSupportInfoIfAbsent,
110
+ supportInfo_addExpertise,
111
111
  } from '../../../../graph-modifier/DSL_DataProduct_GraphModifierHelper.js';
112
112
  import { DataProductTestableState } from './testable/DataProductTestableState.js';
113
113
  import { LambdaEditorState, LineageState } from '@finos/legend-query-builder';
@@ -168,6 +168,8 @@ export class AccessPointLambdaEditorState extends LambdaEditorState {
168
168
  readonly val: LakehouseAccessPointState;
169
169
  lambdaRelationColumns: string[] | undefined;
170
170
  isUpdatingRelationColumns = false;
171
+ lastComputedLambdaHash: string | undefined;
172
+ lastComputedLambdaString: string | undefined;
171
173
 
172
174
  constructor(val: LakehouseAccessPointState) {
173
175
  super('', LAMBDA_PIPE, {
@@ -182,10 +184,6 @@ export class AccessPointLambdaEditorState extends LambdaEditorState {
182
184
  setLambdaRelationColumns: action,
183
185
  updateLambdaRelationColumns: flow,
184
186
  });
185
-
186
- flowResult(this.updateLambdaRelationColumns()).catch(
187
- this.editorStore.applicationStore.alertUnhandledError,
188
- );
189
187
  }
190
188
 
191
189
  setLambdaRelationColumns(columns: string[] | undefined): void {
@@ -202,11 +200,12 @@ export class AccessPointLambdaEditorState extends LambdaEditorState {
202
200
  return;
203
201
  }
204
202
 
205
- this.setIsUpdatingRelationColumns(true);
206
- const relationElement = this.val.relationElementState?.relationElement;
207
- if (relationElement) {
208
- this.val.deleteRelationElement();
203
+ const lambdaHash = this.val.accessPoint.func.hashCode;
204
+ if (lambdaHash === this.lastComputedLambdaHash) {
205
+ return;
209
206
  }
207
+
208
+ this.setIsUpdatingRelationColumns(true);
210
209
  try {
211
210
  const model = guaranteeType(
212
211
  this.editorStore.graphManagerState.graphManager,
@@ -244,9 +243,7 @@ export class AccessPointLambdaEditorState extends LambdaEditorState {
244
243
  } catch {
245
244
  this.setLambdaRelationColumns(undefined);
246
245
  } finally {
247
- if (relationElement) {
248
- this.val.addRelationElement(relationElement, false);
249
- }
246
+ this.lastComputedLambdaHash = lambdaHash;
250
247
  this.setIsUpdatingRelationColumns(false);
251
248
  }
252
249
  }
@@ -260,6 +257,9 @@ export class AccessPointLambdaEditorState extends LambdaEditorState {
260
257
  }
261
258
 
262
259
  *convertLambdaGrammarStringToObject(): GeneratorFn<void> {
260
+ if (this.lambdaString === this.lastComputedLambdaString) {
261
+ return;
262
+ }
263
263
  const emptyLambda = stub_RawLambda();
264
264
  if (this.lambdaString) {
265
265
  try {
@@ -270,6 +270,7 @@ export class AccessPointLambdaEditorState extends LambdaEditorState {
270
270
  )) as RawLambda;
271
271
  this.setParserError(undefined);
272
272
  this.val.accessPoint.func = lambda;
273
+ this.lastComputedLambdaString = this.lambdaString;
273
274
  } catch (error) {
274
275
  assertErrorThrown(error);
275
276
  if (error instanceof ParserError) {
@@ -303,11 +304,12 @@ export class AccessPointLambdaEditorState extends LambdaEditorState {
303
304
  options?.pretty,
304
305
  )) as Map<string, string>;
305
306
  const grammarText = isolatedLambdas.get(this.lambdaId);
306
- this.setLambdaString(
307
+ const nextLambdaString =
307
308
  grammarText !== undefined
308
309
  ? this.extractLambdaString(grammarText)
309
- : '',
310
- );
310
+ : '';
311
+ this.setLambdaString(nextLambdaString);
312
+ this.lastComputedLambdaString = nextLambdaString || undefined;
311
313
  this.clearErrors({
312
314
  preserveCompilationError: options?.preserveCompilationError,
313
315
  });
@@ -373,6 +375,7 @@ export class LakehouseAccessPointState extends AccessPointState {
373
375
  setShowSampleValuesModal: action,
374
376
  addRelationElement: action,
375
377
  deleteRelationElement: action,
378
+ createAndaddRelationElement: flow,
376
379
  // Add observables for lineage
377
380
  lineageState: observable,
378
381
  setArtifactContent: action,
@@ -483,7 +486,11 @@ export class LakehouseAccessPointState extends AccessPointState {
483
486
  return undefined;
484
487
  }
485
488
 
486
- createAndaddRelationElement(): void {
489
+ *createAndaddRelationElement(): GeneratorFn<void> {
490
+ if (this.lambdaState.lambdaRelationColumns === undefined) {
491
+ yield flowResult(this.lambdaState.updateLambdaRelationColumns());
492
+ }
493
+
487
494
  const newElement = new RelationElement();
488
495
  newElement.paths = [this.accessPoint.id];
489
496
 
@@ -677,6 +684,8 @@ export class AccessPointGroupState {
677
684
  deleteAccessPoint: action,
678
685
  swapAccessPoints: action,
679
686
  containsPublicStereotype: computed,
687
+ hasParserError: computed,
688
+ classifications: computed,
680
689
  });
681
690
  }
682
691
 
@@ -684,12 +693,30 @@ export class AccessPointGroupState {
684
693
  return Boolean(this.accessPointStates.find((e) => e.isRunningProcess));
685
694
  }
686
695
 
696
+ get hasParserError(): boolean {
697
+ return this.accessPointStates.some(
698
+ (apState) =>
699
+ apState instanceof LakehouseAccessPointState &&
700
+ Boolean(apState.lambdaState.parserError),
701
+ );
702
+ }
703
+
687
704
  get containsPublicStereotype(): StereotypeReference | undefined {
688
705
  return this.value.stereotypes.find(
689
706
  (stereotype) => stereotype.value === this.publicStereotype,
690
707
  );
691
708
  }
692
709
 
710
+ get classifications(): string[] {
711
+ const config =
712
+ this.state.editorStore.applicationStore.config.options.dataProductConfig;
713
+ return (
714
+ (this.containsPublicStereotype
715
+ ? config?.publicClassifications
716
+ : config?.classifications) ?? []
717
+ );
718
+ }
719
+
693
720
  get publicStereotype(): Stereotype | undefined {
694
721
  const publicStereotype =
695
722
  this.state.editorStore.applicationStore.config.options.dataProductConfig
@@ -1084,11 +1111,15 @@ export class DataProductEditorState extends ElementEditorState {
1084
1111
  purePropertyMapping?.lambdaState.lambdaPrefix &&
1085
1112
  grammarText.startsWith(purePropertyMapping.lambdaState.lambdaPrefix)
1086
1113
  ) {
1087
- purePropertyMapping.lambdaState.setLambdaString(
1088
- purePropertyMapping.lambdaState.extractLambdaString(grammarText),
1089
- );
1090
- } else {
1091
- purePropertyMapping?.lambdaState.setLambdaString(grammarText);
1114
+ const nextLambdaString =
1115
+ purePropertyMapping.lambdaState.extractLambdaString(grammarText);
1116
+ purePropertyMapping.lambdaState.setLambdaString(nextLambdaString);
1117
+ purePropertyMapping.lambdaState.lastComputedLambdaString =
1118
+ nextLambdaString || undefined;
1119
+ } else if (purePropertyMapping) {
1120
+ purePropertyMapping.lambdaState.setLambdaString(grammarText);
1121
+ purePropertyMapping.lambdaState.lastComputedLambdaString =
1122
+ grammarText || undefined;
1092
1123
  }
1093
1124
  });
1094
1125
  } catch (error) {
@@ -75,6 +75,7 @@ export enum PACKAGEABLE_ELEMENT_TYPE {
75
75
  MEM_SQL_FUNCTION = 'MEM_SQL_FUNCTION',
76
76
  // New Strategical Data Product
77
77
  _DATA_PRODUCT = 'BETA_DATA_PRODUCT',
78
+ _COMPUTE = 'BETA_COMPUTE',
78
79
 
79
80
  TEMPORARY__LOCAL_CONNECTION = 'LOCAL_CONNECTION',
80
81
  INTERNAL__UnknownElement = 'UNKNOWN',