@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.
- package/lib/components/editor/ActivityBar.d.ts +13 -0
- package/lib/components/editor/ActivityBar.d.ts.map +1 -1
- package/lib/components/editor/ActivityBar.js +36 -15
- package/lib/components/editor/ActivityBar.js.map +1 -1
- package/lib/components/editor/editor-group/dataProduct/DataProductEditor.d.ts +2 -0
- package/lib/components/editor/editor-group/dataProduct/DataProductEditor.d.ts.map +1 -1
- package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js +81 -19
- package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js.map +1 -1
- package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.d.ts.map +1 -1
- package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.js +109 -15
- package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.js.map +1 -1
- package/lib/components/editor/editor-group/service-editor/ServiceEditor.d.ts.map +1 -1
- package/lib/components/editor/editor-group/service-editor/ServiceEditor.js +4 -1
- package/lib/components/editor/editor-group/service-editor/ServiceEditor.js.map +1 -1
- package/lib/components/workspace-setup/WorkspaceSetup.d.ts.map +1 -1
- package/lib/components/workspace-setup/WorkspaceSetup.js +20 -3
- package/lib/components/workspace-setup/WorkspaceSetup.js.map +1 -1
- package/lib/index.css +2 -2
- package/lib/index.css.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/package.json +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.d.ts +34 -1
- package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.d.ts.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js +203 -2
- package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js.map +1 -1
- package/lib/stores/extensions/DSL_DataProduct_LegendStudioApplicationPlugin_Extension.d.ts +61 -0
- package/lib/stores/extensions/DSL_DataProduct_LegendStudioApplicationPlugin_Extension.d.ts.map +1 -0
- package/lib/stores/extensions/DSL_DataProduct_LegendStudioApplicationPlugin_Extension.js +17 -0
- package/lib/stores/extensions/DSL_DataProduct_LegendStudioApplicationPlugin_Extension.js.map +1 -0
- package/lib/stores/extensions/DSL_Service_LegendStudioApplicationPlugin_Extension.d.ts +2 -2
- package/lib/stores/extensions/DSL_Service_LegendStudioApplicationPlugin_Extension.d.ts.map +1 -1
- package/lib/stores/workspace-setup/WorkspaceSetupStore.d.ts.map +1 -1
- package/lib/stores/workspace-setup/WorkspaceSetupStore.js +6 -2
- package/lib/stores/workspace-setup/WorkspaceSetupStore.js.map +1 -1
- package/package.json +13 -13
- package/src/components/editor/ActivityBar.tsx +61 -38
- package/src/components/editor/editor-group/dataProduct/DataProductEditor.tsx +207 -29
- package/src/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.tsx +370 -22
- package/src/components/editor/editor-group/service-editor/ServiceEditor.tsx +5 -1
- package/src/components/workspace-setup/WorkspaceSetup.tsx +27 -2
- package/src/index.ts +1 -0
- package/src/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.ts +317 -0
- package/src/stores/extensions/DSL_DataProduct_LegendStudioApplicationPlugin_Extension.ts +77 -0
- package/src/stores/extensions/DSL_Service_LegendStudioApplicationPlugin_Extension.ts +1 -2
- package/src/stores/workspace-setup/WorkspaceSetupStore.ts +5 -0
- package/tsconfig.json +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@finos/legend-application-studio",
|
|
3
|
-
"version": "28.21.
|
|
3
|
+
"version": "28.21.14",
|
|
4
4
|
"description": "Legend Studio application core",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"legend",
|
|
@@ -47,18 +47,18 @@
|
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@dagrejs/dagre": "1.1.4",
|
|
50
|
-
"@finos/legend-application": "16.0.
|
|
51
|
-
"@finos/legend-art": "7.1.
|
|
52
|
-
"@finos/legend-code-editor": "2.0.
|
|
53
|
-
"@finos/legend-data-cube": "0.3.
|
|
54
|
-
"@finos/legend-extension-dsl-data-product": "0.0.
|
|
55
|
-
"@finos/legend-extension-dsl-diagram": "8.1.
|
|
56
|
-
"@finos/legend-graph": "32.6.
|
|
57
|
-
"@finos/legend-lego": "2.0.
|
|
58
|
-
"@finos/legend-query-builder": "4.18.
|
|
59
|
-
"@finos/legend-server-depot": "6.1.
|
|
60
|
-
"@finos/legend-server-lakehouse": "0.3.
|
|
61
|
-
"@finos/legend-server-sdlc": "5.4.
|
|
50
|
+
"@finos/legend-application": "16.0.112",
|
|
51
|
+
"@finos/legend-art": "7.1.153",
|
|
52
|
+
"@finos/legend-code-editor": "2.0.179",
|
|
53
|
+
"@finos/legend-data-cube": "0.3.96",
|
|
54
|
+
"@finos/legend-extension-dsl-data-product": "0.0.89",
|
|
55
|
+
"@finos/legend-extension-dsl-diagram": "8.1.243",
|
|
56
|
+
"@finos/legend-graph": "32.6.10",
|
|
57
|
+
"@finos/legend-lego": "2.0.199",
|
|
58
|
+
"@finos/legend-query-builder": "4.18.18",
|
|
59
|
+
"@finos/legend-server-depot": "6.1.13",
|
|
60
|
+
"@finos/legend-server-lakehouse": "0.3.59",
|
|
61
|
+
"@finos/legend-server-sdlc": "5.4.4",
|
|
62
62
|
"@finos/legend-server-showcase": "0.2.66",
|
|
63
63
|
"@finos/legend-shared": "11.0.25",
|
|
64
64
|
"@finos/legend-storage": "3.0.146",
|
|
@@ -24,13 +24,11 @@ import {
|
|
|
24
24
|
import { LEGEND_STUDIO_TEST_ID } from '../../__lib__/LegendStudioTesting.js';
|
|
25
25
|
import {
|
|
26
26
|
clsx,
|
|
27
|
-
CheckIcon,
|
|
28
27
|
ControlledDropdownMenu,
|
|
29
28
|
RepoIcon,
|
|
30
29
|
MenuContent,
|
|
31
30
|
MenuContentItem,
|
|
32
31
|
MenuContentItemBlankIcon,
|
|
33
|
-
MenuContentItemIcon,
|
|
34
32
|
MenuContentItemLabel,
|
|
35
33
|
GitPullRequestIcon,
|
|
36
34
|
GitMergeIcon,
|
|
@@ -47,6 +45,8 @@ import {
|
|
|
47
45
|
WorkflowIcon,
|
|
48
46
|
ReadMeIcon,
|
|
49
47
|
DevIcon,
|
|
48
|
+
MoonIcon,
|
|
49
|
+
SunIcon,
|
|
50
50
|
} from '@finos/legend-art';
|
|
51
51
|
import { useEditorStore } from './EditorStoreProvider.js';
|
|
52
52
|
import { forwardRef, useEffect, useState } from 'react';
|
|
@@ -97,19 +97,10 @@ import { useAuth, type AuthContextProps } from 'react-oidc-context';
|
|
|
97
97
|
const SettingsMenu = observer(
|
|
98
98
|
forwardRef<HTMLDivElement, unknown>(function SettingsMenu(props, ref) {
|
|
99
99
|
const editorStore = useEditorStore();
|
|
100
|
-
const applicationStore = useLegendStudioApplicationStore();
|
|
101
|
-
const { layoutService } = applicationStore;
|
|
102
100
|
const showDeveloperTool = (): void => {
|
|
103
101
|
editorStore.panelGroupDisplayState.open();
|
|
104
102
|
editorStore.setActivePanelMode(PANEL_MODE.DEV_TOOL);
|
|
105
103
|
};
|
|
106
|
-
const selectColorTheme = (key: string): void => {
|
|
107
|
-
layoutService.setColorTheme(key, { persist: true });
|
|
108
|
-
};
|
|
109
|
-
// Non-production themes (e.g. the in-progress light theme) are only exposed
|
|
110
|
-
// when the `NonProductionFeatureFlag` config option is on.
|
|
111
|
-
const showNonProductionThemes =
|
|
112
|
-
applicationStore.config.options.NonProductionFeatureFlag;
|
|
113
104
|
|
|
114
105
|
return (
|
|
115
106
|
<MenuContent ref={ref} className="activity-bar__setting__menu">
|
|
@@ -117,33 +108,6 @@ const SettingsMenu = observer(
|
|
|
117
108
|
<MenuContentItemBlankIcon />
|
|
118
109
|
<MenuContentItemLabel>Show Developer Tool</MenuContentItemLabel>
|
|
119
110
|
</MenuContentItem>
|
|
120
|
-
<MenuContentDivider />
|
|
121
|
-
<MenuContentItem disabled={true}>
|
|
122
|
-
<MenuContentItemBlankIcon />
|
|
123
|
-
<MenuContentItemLabel>Color Theme</MenuContentItemLabel>
|
|
124
|
-
</MenuContentItem>
|
|
125
|
-
{layoutService.availableColorThemes
|
|
126
|
-
.filter(
|
|
127
|
-
(theme) =>
|
|
128
|
-
STUDIO_SUPPORTED_COLOR_THEMES.has(theme.key) &&
|
|
129
|
-
(showNonProductionThemes ||
|
|
130
|
-
!STUDIO_NON_PRODUCTION_COLOR_THEMES.has(theme.key)),
|
|
131
|
-
)
|
|
132
|
-
.map((theme) => (
|
|
133
|
-
<MenuContentItem
|
|
134
|
-
key={theme.key}
|
|
135
|
-
onClick={() => selectColorTheme(theme.key)}
|
|
136
|
-
>
|
|
137
|
-
{theme.key === layoutService.currentColorTheme.key ? (
|
|
138
|
-
<MenuContentItemIcon>
|
|
139
|
-
<CheckIcon />
|
|
140
|
-
</MenuContentItemIcon>
|
|
141
|
-
) : (
|
|
142
|
-
<MenuContentItemBlankIcon />
|
|
143
|
-
)}
|
|
144
|
-
<MenuContentItemLabel>{theme.name}</MenuContentItemLabel>
|
|
145
|
-
</MenuContentItem>
|
|
146
|
-
))}
|
|
147
111
|
</MenuContent>
|
|
148
112
|
);
|
|
149
113
|
}),
|
|
@@ -157,6 +121,64 @@ const useOptionalAuth = (): AuthContextProps | undefined => {
|
|
|
157
121
|
}
|
|
158
122
|
};
|
|
159
123
|
|
|
124
|
+
/**
|
|
125
|
+
* One-click toggle between the default dark and default light color themes,
|
|
126
|
+
* surfaced directly in the activity bar so users don't have to dig through the
|
|
127
|
+
* Settings menu to flip themes.
|
|
128
|
+
*
|
|
129
|
+
* The toggle only renders when both themes are actually exposed in the current
|
|
130
|
+
* environment (i.e. it respects the same `STUDIO_SUPPORTED_COLOR_THEMES` /
|
|
131
|
+
* `NonProductionFeatureFlag` gating as the Settings menu picker). This keeps
|
|
132
|
+
* the in-progress light theme hidden in production until it's stabilized.
|
|
133
|
+
*/
|
|
134
|
+
export const ColorThemeToggle = observer(() => {
|
|
135
|
+
const applicationStore = useLegendStudioApplicationStore();
|
|
136
|
+
const { layoutService } = applicationStore;
|
|
137
|
+
const showNonProductionThemes =
|
|
138
|
+
applicationStore.config.options.NonProductionFeatureFlag;
|
|
139
|
+
|
|
140
|
+
const exposedThemeKeys = new Set(
|
|
141
|
+
layoutService.availableColorThemes
|
|
142
|
+
.filter(
|
|
143
|
+
(theme) =>
|
|
144
|
+
STUDIO_SUPPORTED_COLOR_THEMES.has(theme.key) &&
|
|
145
|
+
(showNonProductionThemes ||
|
|
146
|
+
!STUDIO_NON_PRODUCTION_COLOR_THEMES.has(theme.key)),
|
|
147
|
+
)
|
|
148
|
+
.map((theme) => theme.key),
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// Only show the toggle when both endpoints of the dark<->light flip are
|
|
152
|
+
// actually available; otherwise it would be a no-op control.
|
|
153
|
+
if (
|
|
154
|
+
!exposedThemeKeys.has(LEGEND_APPLICATION_COLOR_THEME.DEFAULT_DARK) ||
|
|
155
|
+
!exposedThemeKeys.has(LEGEND_APPLICATION_COLOR_THEME.DEFAULT_LIGHT)
|
|
156
|
+
) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const isLight = layoutService.TEMPORARY__isLightColorThemeEnabled;
|
|
161
|
+
const toggleTheme = (): void => {
|
|
162
|
+
layoutService.setColorTheme(
|
|
163
|
+
isLight
|
|
164
|
+
? LEGEND_APPLICATION_COLOR_THEME.DEFAULT_DARK
|
|
165
|
+
: LEGEND_APPLICATION_COLOR_THEME.DEFAULT_LIGHT,
|
|
166
|
+
{ persist: true },
|
|
167
|
+
);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
return (
|
|
171
|
+
<button
|
|
172
|
+
className="activity-bar__item"
|
|
173
|
+
onClick={toggleTheme}
|
|
174
|
+
tabIndex={-1}
|
|
175
|
+
title={isLight ? 'Switch to dark theme' : 'Switch to light theme'}
|
|
176
|
+
>
|
|
177
|
+
{isLight ? <MoonIcon /> : <SunIcon />}
|
|
178
|
+
</button>
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
|
|
160
182
|
export const ActivityBarMenu: React.FC<{
|
|
161
183
|
openShowcasePanel?: () => void;
|
|
162
184
|
}> = ({ openShowcasePanel }) => {
|
|
@@ -570,6 +592,7 @@ export const ActivityBar = observer(() => {
|
|
|
570
592
|
>
|
|
571
593
|
<ReadMeIcon />
|
|
572
594
|
</button>
|
|
595
|
+
<ColorThemeToggle />
|
|
573
596
|
<ControlledDropdownMenu
|
|
574
597
|
className="activity-bar__item"
|
|
575
598
|
title="Settings"
|
|
@@ -80,6 +80,7 @@ import {
|
|
|
80
80
|
PanelLoadingIndicator,
|
|
81
81
|
GearSuggestIcon,
|
|
82
82
|
FlaskIcon,
|
|
83
|
+
SparkleIcon,
|
|
83
84
|
} from '@finos/legend-art';
|
|
84
85
|
import {
|
|
85
86
|
type ChangeEventHandler,
|
|
@@ -106,7 +107,7 @@ import {
|
|
|
106
107
|
QueryBuilderAdvancedWorkflowState,
|
|
107
108
|
type QueryBuilderState,
|
|
108
109
|
} from '@finos/legend-query-builder';
|
|
109
|
-
import { action, autorun, flowResult } from 'mobx';
|
|
110
|
+
import { action, autorun, flowResult, runInAction } from 'mobx';
|
|
110
111
|
import { DataProductTestableEditor } from './testable/DataProductTestableEditor.js';
|
|
111
112
|
import { CODE_EDITOR_LANGUAGE } from '@finos/legend-code-editor';
|
|
112
113
|
import { CodeEditor } from '@finos/legend-lego/code-editor';
|
|
@@ -202,6 +203,11 @@ import {
|
|
|
202
203
|
import type { LegendStudioApplicationStore } from '../../../../stores/LegendStudioBaseStore.js';
|
|
203
204
|
import type { DepotServerClient } from '@finos/legend-server-depot';
|
|
204
205
|
import { RelationElementEditor } from '../data-editor/RelationElementsDataEditor.js';
|
|
206
|
+
import type {
|
|
207
|
+
AccessPointMeta,
|
|
208
|
+
DataProductDocResponse,
|
|
209
|
+
DSL_DataProduct_LegendStudioApplicationPlugin_Extension,
|
|
210
|
+
} from '../../../../stores/extensions/DSL_DataProduct_LegendStudioApplicationPlugin_Extension.js';
|
|
205
211
|
|
|
206
212
|
export enum AP_GROUP_MODAL_ERRORS {
|
|
207
213
|
GROUP_NAME_EMPTY = 'Group Name is empty',
|
|
@@ -262,8 +268,11 @@ const hoverIcon = () => {
|
|
|
262
268
|
};
|
|
263
269
|
|
|
264
270
|
const AccessPointTitle = observer(
|
|
265
|
-
(props: {
|
|
266
|
-
|
|
271
|
+
(props: {
|
|
272
|
+
accessPointState: LakehouseAccessPointState;
|
|
273
|
+
aiSuggestionMeta?: AccessPointMeta | undefined;
|
|
274
|
+
}) => {
|
|
275
|
+
const { accessPointState, aiSuggestionMeta } = props;
|
|
267
276
|
const accessPoint = accessPointState.accessPoint;
|
|
268
277
|
const [editingName, setEditingName] = useState(
|
|
269
278
|
accessPoint.id === newNamePlaceholder,
|
|
@@ -286,29 +295,37 @@ const AccessPointTitle = observer(
|
|
|
286
295
|
}
|
|
287
296
|
});
|
|
288
297
|
|
|
289
|
-
return
|
|
290
|
-
<
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
298
|
+
return (
|
|
299
|
+
<div style={{ flex: '1 1 auto', minWidth: 0 }}>
|
|
300
|
+
{editingName ? (
|
|
301
|
+
<textarea
|
|
302
|
+
className="access-point-editor__name"
|
|
303
|
+
spellCheck={false}
|
|
304
|
+
value={accessPoint.id}
|
|
305
|
+
onChange={updateAccessPointName}
|
|
306
|
+
placeholder={'Access Point Name'}
|
|
307
|
+
onBlur={handleNameBlur}
|
|
308
|
+
style={{
|
|
309
|
+
borderColor:
|
|
310
|
+
accessPoint.id === newNamePlaceholder
|
|
311
|
+
? 'var(--color-red-300)'
|
|
312
|
+
: 'transparent',
|
|
313
|
+
}}
|
|
314
|
+
/>
|
|
315
|
+
) : (
|
|
316
|
+
<div
|
|
317
|
+
className="access-point-editor__name__label"
|
|
318
|
+
onClick={handleNameEdit}
|
|
319
|
+
title="Click to edit access point name"
|
|
320
|
+
>
|
|
321
|
+
{accessPoint.id}
|
|
322
|
+
</div>
|
|
323
|
+
)}
|
|
324
|
+
{aiSuggestionMeta && (
|
|
325
|
+
<div className="data-product-editor__ai-suggestion-inline">
|
|
326
|
+
{aiSuggestionMeta.name}
|
|
327
|
+
</div>
|
|
328
|
+
)}
|
|
312
329
|
</div>
|
|
313
330
|
);
|
|
314
331
|
},
|
|
@@ -697,8 +714,9 @@ export const LakehouseDataProductAccessPointEditor = observer(
|
|
|
697
714
|
(props: {
|
|
698
715
|
accessPointState: LakehouseAccessPointState;
|
|
699
716
|
isReadOnly: boolean;
|
|
717
|
+
aiSuggestionMeta?: AccessPointMeta | undefined;
|
|
700
718
|
}) => {
|
|
701
|
-
const { accessPointState } = props;
|
|
719
|
+
const { accessPointState, aiSuggestionMeta } = props;
|
|
702
720
|
const editorStore = useEditorStore();
|
|
703
721
|
const accessPoint = accessPointState.accessPoint;
|
|
704
722
|
const groupState = accessPointState.state;
|
|
@@ -925,7 +943,10 @@ export const LakehouseDataProductAccessPointEditor = observer(
|
|
|
925
943
|
/>
|
|
926
944
|
<div style={{ flex: 1 }}>
|
|
927
945
|
<div className="access-point-editor__metadata">
|
|
928
|
-
<AccessPointTitle
|
|
946
|
+
<AccessPointTitle
|
|
947
|
+
accessPointState={accessPointState}
|
|
948
|
+
aiSuggestionMeta={aiSuggestionMeta}
|
|
949
|
+
/>
|
|
929
950
|
<div className="access-point-editor__info">
|
|
930
951
|
<div className="access-point-editor__reproducible">
|
|
931
952
|
<Checkbox
|
|
@@ -1166,6 +1187,11 @@ export const LakehouseDataProductAccessPointEditor = observer(
|
|
|
1166
1187
|
{isHoveringTitle && hoverIcon()}
|
|
1167
1188
|
</div>
|
|
1168
1189
|
)}
|
|
1190
|
+
{aiSuggestionMeta && (
|
|
1191
|
+
<div className="data-product-editor__ai-suggestion-inline">
|
|
1192
|
+
{aiSuggestionMeta.title}
|
|
1193
|
+
</div>
|
|
1194
|
+
)}
|
|
1169
1195
|
{editingDescription ? (
|
|
1170
1196
|
<textarea
|
|
1171
1197
|
className="panel__content__form__section__input"
|
|
@@ -1209,6 +1235,11 @@ export const LakehouseDataProductAccessPointEditor = observer(
|
|
|
1209
1235
|
{isHoveringDesc && hoverIcon()}
|
|
1210
1236
|
</div>
|
|
1211
1237
|
)}
|
|
1238
|
+
{aiSuggestionMeta && (
|
|
1239
|
+
<div className="data-product-editor__ai-suggestion-inline">
|
|
1240
|
+
{aiSuggestionMeta.description}
|
|
1241
|
+
</div>
|
|
1242
|
+
)}
|
|
1212
1243
|
<div className="access-point-editor__content">
|
|
1213
1244
|
<div className="access-point-editor__generic-entry">
|
|
1214
1245
|
<div className="access-point-editor__entry__container">
|
|
@@ -1712,6 +1743,10 @@ const AccessPointGroupEditor = observer(
|
|
|
1712
1743
|
const [isHoveringName, setIsHoveringName] = useState(false);
|
|
1713
1744
|
const [editingTitle, setEditingTitle] = useState(false);
|
|
1714
1745
|
const [isHoveringTitle, setIsHoveringTitle] = useState(false);
|
|
1746
|
+
const [isSuggestingWithAI, setIsSuggestingWithAI] = useState(false);
|
|
1747
|
+
const [aiSuggestion, setAISuggestion] = useState<
|
|
1748
|
+
DataProductDocResponse | undefined
|
|
1749
|
+
>(undefined);
|
|
1715
1750
|
const handleDescriptionEdit = () => setEditingDescription(true);
|
|
1716
1751
|
const handleDescriptionBlur = () => {
|
|
1717
1752
|
setEditingDescription(false);
|
|
@@ -1767,6 +1802,96 @@ const AccessPointGroupEditor = observer(
|
|
|
1767
1802
|
accessPointGroup_setTitle(groupState.value, val);
|
|
1768
1803
|
};
|
|
1769
1804
|
|
|
1805
|
+
// AI suggestion for APG
|
|
1806
|
+
const legendAIUrl = editorStore.applicationStore.config.legendAIUrl;
|
|
1807
|
+
const aiDocSuggester = legendAIUrl
|
|
1808
|
+
? editorStore.pluginManager
|
|
1809
|
+
.getApplicationPlugins()
|
|
1810
|
+
.map((p) =>
|
|
1811
|
+
(
|
|
1812
|
+
p as DSL_DataProduct_LegendStudioApplicationPlugin_Extension
|
|
1813
|
+
).getExtraDataProductDocumentationAISuggester?.bind(p),
|
|
1814
|
+
)
|
|
1815
|
+
.find(Boolean)
|
|
1816
|
+
: undefined;
|
|
1817
|
+
const suggestWithAI = async (): Promise<void> => {
|
|
1818
|
+
if (!aiDocSuggester || !legendAIUrl) {
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
setIsSuggestingWithAI(true);
|
|
1822
|
+
setAISuggestion(undefined);
|
|
1823
|
+
try {
|
|
1824
|
+
const definitions =
|
|
1825
|
+
await editorStore.graphManagerState.graphManager.graphToPureCode(
|
|
1826
|
+
editorStore.graphManagerState.graph,
|
|
1827
|
+
);
|
|
1828
|
+
const product = productEditorState.product;
|
|
1829
|
+
const suggestion = await aiDocSuggester(
|
|
1830
|
+
{ definitions, data_product_name: product.path },
|
|
1831
|
+
legendAIUrl,
|
|
1832
|
+
);
|
|
1833
|
+
runInAction(() => {
|
|
1834
|
+
setAISuggestion(suggestion);
|
|
1835
|
+
});
|
|
1836
|
+
} finally {
|
|
1837
|
+
runInAction(() => {
|
|
1838
|
+
setIsSuggestingWithAI(false);
|
|
1839
|
+
});
|
|
1840
|
+
}
|
|
1841
|
+
};
|
|
1842
|
+
const applyAISuggestion = (): void => {
|
|
1843
|
+
if (!aiSuggestion) {
|
|
1844
|
+
return;
|
|
1845
|
+
}
|
|
1846
|
+
// Find matching group in the response
|
|
1847
|
+
const gIdx =
|
|
1848
|
+
productEditorState.accessPointGroupStates.indexOf(groupState);
|
|
1849
|
+
const groupMeta =
|
|
1850
|
+
aiSuggestion.access_point_groups.find(
|
|
1851
|
+
(g) => g.name === groupState.value.id,
|
|
1852
|
+
) ?? aiSuggestion.access_point_groups[gIdx];
|
|
1853
|
+
if (groupMeta) {
|
|
1854
|
+
accessPointGroup_setTitle(groupState.value, groupMeta.title);
|
|
1855
|
+
accessPointGroup_setDescription(
|
|
1856
|
+
groupState.value,
|
|
1857
|
+
groupMeta.description,
|
|
1858
|
+
);
|
|
1859
|
+
}
|
|
1860
|
+
// Apply access point metadata for this group
|
|
1861
|
+
const apMetas = aiSuggestion.access_points.filter(
|
|
1862
|
+
(ap) => ap.group === (groupMeta?.name ?? groupState.value.id),
|
|
1863
|
+
);
|
|
1864
|
+
for (
|
|
1865
|
+
let j = 0;
|
|
1866
|
+
j < apMetas.length && j < groupState.value.accessPoints.length;
|
|
1867
|
+
j++
|
|
1868
|
+
) {
|
|
1869
|
+
const apMeta = apMetas[j] as (typeof apMetas)[number];
|
|
1870
|
+
const ap = groupState.value.accessPoints[
|
|
1871
|
+
j
|
|
1872
|
+
] as (typeof groupState.value.accessPoints)[number];
|
|
1873
|
+
// Convert suggested name to valid identifier (alphanumeric + underscore)
|
|
1874
|
+
const sanitizedName = apMeta.name.replace(/[^0-9a-zA-Z_]/g, '_');
|
|
1875
|
+
if (sanitizedName) {
|
|
1876
|
+
ap.id = sanitizedName;
|
|
1877
|
+
}
|
|
1878
|
+
accessPoint_setTitle(ap, apMeta.title);
|
|
1879
|
+
accessPoint_setDescription(ap, apMeta.description);
|
|
1880
|
+
}
|
|
1881
|
+
setAISuggestion(undefined);
|
|
1882
|
+
};
|
|
1883
|
+
|
|
1884
|
+
// Computed AI suggestion for current group
|
|
1885
|
+
const groupIndex =
|
|
1886
|
+
productEditorState.accessPointGroupStates.indexOf(groupState);
|
|
1887
|
+
const aiGroupMeta =
|
|
1888
|
+
aiSuggestion?.access_point_groups.find(
|
|
1889
|
+
(g) => g.name === groupState.value.id,
|
|
1890
|
+
) ?? aiSuggestion?.access_point_groups[groupIndex];
|
|
1891
|
+
const aiAccessPointMetas = aiSuggestion?.access_points.filter(
|
|
1892
|
+
(ap) => ap.group === (aiGroupMeta?.name ?? groupState.value.id),
|
|
1893
|
+
);
|
|
1894
|
+
|
|
1770
1895
|
const handleRemoveAccessPointGroup = (): void => {
|
|
1771
1896
|
editorStore.applicationStore.alertService.setActionAlertInfo({
|
|
1772
1897
|
message:
|
|
@@ -1852,6 +1977,48 @@ const AccessPointGroupEditor = observer(
|
|
|
1852
1977
|
<TimesIcon />
|
|
1853
1978
|
</button>
|
|
1854
1979
|
</div>
|
|
1980
|
+
{aiDocSuggester && (
|
|
1981
|
+
<div style={{ padding: '0.25rem 0.5rem' }}>
|
|
1982
|
+
<PanelLoadingIndicator isLoading={isSuggestingWithAI} />
|
|
1983
|
+
{aiSuggestion ? (
|
|
1984
|
+
<div className="data-product-editor__ai-suggestion__actions">
|
|
1985
|
+
<button
|
|
1986
|
+
className="btn btn--dark data-product-editor__ai-suggestion__apply-btn"
|
|
1987
|
+
onClick={applyAISuggestion}
|
|
1988
|
+
title="Apply AI-generated title, description, and access point metadata for this group"
|
|
1989
|
+
>
|
|
1990
|
+
Apply
|
|
1991
|
+
</button>
|
|
1992
|
+
<button
|
|
1993
|
+
className="btn data-product-editor__ai-suggestion__dismiss-btn"
|
|
1994
|
+
onClick={(): void => setAISuggestion(undefined)}
|
|
1995
|
+
>
|
|
1996
|
+
Dismiss
|
|
1997
|
+
</button>
|
|
1998
|
+
<span className="data-product-editor__ai-suggestion-badge">
|
|
1999
|
+
<SparkleIcon />
|
|
2000
|
+
AI Suggestion
|
|
2001
|
+
</span>
|
|
2002
|
+
</div>
|
|
2003
|
+
) : (
|
|
2004
|
+
<button
|
|
2005
|
+
className="data-product-editor__ai-suggest-btn"
|
|
2006
|
+
onClick={(): void => {
|
|
2007
|
+
suggestWithAI().catch(
|
|
2008
|
+
editorStore.applicationStore.alertUnhandledError,
|
|
2009
|
+
);
|
|
2010
|
+
}}
|
|
2011
|
+
disabled={isSuggestingWithAI || isReadOnly}
|
|
2012
|
+
title="Use AI to suggest title and descriptions for this access point group"
|
|
2013
|
+
>
|
|
2014
|
+
<SparkleIcon />
|
|
2015
|
+
<span>
|
|
2016
|
+
{isSuggestingWithAI ? 'Suggesting...' : 'Suggest with AI'}
|
|
2017
|
+
</span>
|
|
2018
|
+
</button>
|
|
2019
|
+
)}
|
|
2020
|
+
</div>
|
|
2021
|
+
)}
|
|
1855
2022
|
<div className="access-point-editor__group-container__name-editor">
|
|
1856
2023
|
{editingTitle ? (
|
|
1857
2024
|
<textarea
|
|
@@ -1896,6 +2063,11 @@ const AccessPointGroupEditor = observer(
|
|
|
1896
2063
|
</div>
|
|
1897
2064
|
)}
|
|
1898
2065
|
</div>
|
|
2066
|
+
{aiGroupMeta && (
|
|
2067
|
+
<div className="data-product-editor__ai-suggestion-inline">
|
|
2068
|
+
{aiGroupMeta.title}
|
|
2069
|
+
</div>
|
|
2070
|
+
)}
|
|
1899
2071
|
<div className="access-point-editor__group-container__description-editor">
|
|
1900
2072
|
{editingDescription ? (
|
|
1901
2073
|
<textarea
|
|
@@ -1938,6 +2110,11 @@ const AccessPointGroupEditor = observer(
|
|
|
1938
2110
|
{isHoveringDescription && hoverIcon()}
|
|
1939
2111
|
</div>
|
|
1940
2112
|
)}
|
|
2113
|
+
{aiGroupMeta && (
|
|
2114
|
+
<div className="data-product-editor__ai-suggestion-inline">
|
|
2115
|
+
{aiGroupMeta.description}
|
|
2116
|
+
</div>
|
|
2117
|
+
)}
|
|
1941
2118
|
</div>
|
|
1942
2119
|
{editorStore.applicationStore.config.options.dataProductConfig && (
|
|
1943
2120
|
<AccessPointGroupPublicToggle groupState={groupState} />
|
|
@@ -1975,11 +2152,12 @@ const AccessPointGroupEditor = observer(
|
|
|
1975
2152
|
>
|
|
1976
2153
|
{groupState.accessPointStates
|
|
1977
2154
|
.filter(filterByType(LakehouseAccessPointState))
|
|
1978
|
-
.map((apState) => (
|
|
2155
|
+
.map((apState, idx) => (
|
|
1979
2156
|
<LakehouseDataProductAccessPointEditor
|
|
1980
2157
|
key={apState.uuid}
|
|
1981
2158
|
isReadOnly={isReadOnly}
|
|
1982
2159
|
accessPointState={apState}
|
|
2160
|
+
aiSuggestionMeta={aiAccessPointMetas?.[idx]}
|
|
1983
2161
|
/>
|
|
1984
2162
|
))}
|
|
1985
2163
|
</div>
|