@finos/legend-application-studio 28.21.6 → 28.21.8
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/__lib__/LegendStudioUserDataHelper.d.ts +23 -1
- package/lib/__lib__/LegendStudioUserDataHelper.d.ts.map +1 -1
- package/lib/__lib__/LegendStudioUserDataHelper.js +66 -1
- package/lib/__lib__/LegendStudioUserDataHelper.js.map +1 -1
- package/lib/components/editor/editor-group/accessor/AccessorQueryBuilderHelper.js +1 -1
- package/lib/components/editor/editor-group/accessor/AccessorQueryBuilderHelper.js.map +1 -1
- package/lib/components/editor/editor-group/dataProduct/DataProductEditor.d.ts.map +1 -1
- package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js +7 -20
- package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js.map +1 -1
- package/lib/components/workspace-setup/RecentWorkspacesPanel.d.ts.map +1 -1
- package/lib/components/workspace-setup/RecentWorkspacesPanel.js +11 -5
- package/lib/components/workspace-setup/RecentWorkspacesPanel.js.map +1 -1
- package/lib/components/workspace-setup/WorkspaceSetup.d.ts.map +1 -1
- package/lib/components/workspace-setup/WorkspaceSetup.js +5 -6
- 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/package.json +1 -1
- package/lib/stores/editor/EditorStore.d.ts.map +1 -1
- package/lib/stores/editor/EditorStore.js +3 -0
- package/lib/stores/editor/EditorStore.js.map +1 -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 +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js.map +1 -1
- package/lib/stores/workspace-setup/WorkspaceSetupStore.d.ts +6 -2
- package/lib/stores/workspace-setup/WorkspaceSetupStore.d.ts.map +1 -1
- package/lib/stores/workspace-setup/WorkspaceSetupStore.js +60 -8
- package/lib/stores/workspace-setup/WorkspaceSetupStore.js.map +1 -1
- package/package.json +9 -9
- package/src/__lib__/LegendStudioUserDataHelper.ts +107 -2
- package/src/components/editor/editor-group/accessor/AccessorQueryBuilderHelper.tsx +1 -1
- package/src/components/editor/editor-group/dataProduct/DataProductEditor.tsx +8 -26
- package/src/components/workspace-setup/RecentWorkspacesPanel.tsx +29 -9
- package/src/components/workspace-setup/WorkspaceSetup.tsx +5 -6
- package/src/stores/editor/EditorStore.ts +3 -0
- package/src/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.ts +6 -5
- package/src/stores/workspace-setup/WorkspaceSetupStore.ts +79 -9
|
@@ -847,7 +847,7 @@ export const LakehouseDataProductAccessPointEditor = observer(
|
|
|
847
847
|
applicationStore.config.options.queryBuilderConfig,
|
|
848
848
|
editorStore.editorMode.getSourceInfo(),
|
|
849
849
|
);
|
|
850
|
-
queryBuilderState.changeAccessorOwner(ingestDefinition);
|
|
850
|
+
await queryBuilderState.changeAccessorOwner(ingestDefinition);
|
|
851
851
|
const compatibleRuntimes = getCompatibleRuntimesFromAccessorOwner(
|
|
852
852
|
ingestDefinition,
|
|
853
853
|
editorStore.graphManagerState,
|
|
@@ -2232,9 +2232,6 @@ const AccessPointGroupTab = observer(
|
|
|
2232
2232
|
const DataProductSidebar = observer(
|
|
2233
2233
|
(props: { dataProductEditorState: DataProductEditorState }) => {
|
|
2234
2234
|
const { dataProductEditorState } = props;
|
|
2235
|
-
const showTestingTab =
|
|
2236
|
-
dataProductEditorState.editorStore.applicationStore.config.options
|
|
2237
|
-
.NonProductionFeatureFlag;
|
|
2238
2235
|
const sidebarTabs = [
|
|
2239
2236
|
{
|
|
2240
2237
|
label: DATA_PRODUCT_TAB.HOME,
|
|
@@ -2250,15 +2247,11 @@ const DataProductSidebar = observer(
|
|
|
2250
2247
|
title: 'Operational Metadata',
|
|
2251
2248
|
icon: <GearSuggestIcon />,
|
|
2252
2249
|
},
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
icon: <FlaskIcon />,
|
|
2259
|
-
},
|
|
2260
|
-
]
|
|
2261
|
-
: []),
|
|
2250
|
+
{
|
|
2251
|
+
label: DATA_PRODUCT_TAB.TESTING,
|
|
2252
|
+
title: 'Testing',
|
|
2253
|
+
icon: <FlaskIcon />,
|
|
2254
|
+
},
|
|
2262
2255
|
{
|
|
2263
2256
|
label: DATA_PRODUCT_TAB.SUPPORT,
|
|
2264
2257
|
icon: <QuestionCircleIcon />,
|
|
@@ -3367,8 +3360,6 @@ export const DataProductEditor = observer(() => {
|
|
|
3367
3360
|
const [showPreview, setShowPreview] = useState(false);
|
|
3368
3361
|
const [dataProductViewerState, setDataProductViewerState] =
|
|
3369
3362
|
useState<DataProductViewerState>();
|
|
3370
|
-
const showTestingTab =
|
|
3371
|
-
editorStore.applicationStore.config.options.NonProductionFeatureFlag;
|
|
3372
3363
|
|
|
3373
3364
|
const selectedActivity = dataProductEditorState.selectedTab;
|
|
3374
3365
|
const renderActivivtyBarTab = (): React.ReactNode => {
|
|
@@ -3402,12 +3393,12 @@ export const DataProductEditor = observer(() => {
|
|
|
3402
3393
|
/>
|
|
3403
3394
|
);
|
|
3404
3395
|
case DATA_PRODUCT_TAB.TESTING:
|
|
3405
|
-
return
|
|
3396
|
+
return (
|
|
3406
3397
|
<DataProductTestableEditor
|
|
3407
3398
|
dataProductEditorState={dataProductEditorState}
|
|
3408
3399
|
isReadOnly={isReadOnly}
|
|
3409
3400
|
/>
|
|
3410
|
-
)
|
|
3401
|
+
);
|
|
3411
3402
|
default:
|
|
3412
3403
|
return null;
|
|
3413
3404
|
}
|
|
@@ -3423,15 +3414,6 @@ export const DataProductEditor = observer(() => {
|
|
|
3423
3414
|
);
|
|
3424
3415
|
}, [dataProductEditorState]);
|
|
3425
3416
|
|
|
3426
|
-
useEffect(() => {
|
|
3427
|
-
if (
|
|
3428
|
-
!showTestingTab &&
|
|
3429
|
-
dataProductEditorState.selectedTab === DATA_PRODUCT_TAB.TESTING
|
|
3430
|
-
) {
|
|
3431
|
-
dataProductEditorState.setSelectedTab(DATA_PRODUCT_TAB.HOME);
|
|
3432
|
-
}
|
|
3433
|
-
}, [dataProductEditorState, showTestingTab]);
|
|
3434
|
-
|
|
3435
3417
|
useEffect(
|
|
3436
3418
|
() =>
|
|
3437
3419
|
autorun(
|
|
@@ -65,15 +65,15 @@ export const RecentWorkspacesPanel = observer(
|
|
|
65
65
|
const applicationStore = setupStore.applicationStore;
|
|
66
66
|
|
|
67
67
|
// Recents are stored in LRU order (most-recent first). Show the top N
|
|
68
|
-
// workspaces;
|
|
69
|
-
//
|
|
68
|
+
// workspaces; look up each one's cached project entry for richer tile
|
|
69
|
+
// metadata (name, description tooltip, tag badges).
|
|
70
70
|
const tiles = setupStore.recentWorkspaces.slice(0, MAX_TILES);
|
|
71
71
|
if (tiles.length === 0) {
|
|
72
72
|
return null;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
const
|
|
76
|
-
setupStore.recentProjects.map((p) => [p.projectId, p
|
|
75
|
+
const projectById = new Map(
|
|
76
|
+
setupStore.recentProjects.map((p) => [p.projectId, p]),
|
|
77
77
|
);
|
|
78
78
|
|
|
79
79
|
const openWorkspace = (
|
|
@@ -96,8 +96,14 @@ export const RecentWorkspacesPanel = observer(
|
|
|
96
96
|
</div>
|
|
97
97
|
<div className="workspace-setup__recents__grid">
|
|
98
98
|
{tiles.map((entry) => {
|
|
99
|
-
const
|
|
100
|
-
|
|
99
|
+
const cachedProject = projectById.get(entry.projectId);
|
|
100
|
+
const projectName = cachedProject?.name ?? entry.projectId;
|
|
101
|
+
const projectDescription = cachedProject?.description ?? '';
|
|
102
|
+
const projectTags = cachedProject?.tags ?? [];
|
|
103
|
+
const tileTitle =
|
|
104
|
+
projectDescription.trim().length > 0
|
|
105
|
+
? `${projectName} / ${entry.workspaceId}\n${projectDescription}`
|
|
106
|
+
: `Open ${projectName} / ${entry.workspaceId}`;
|
|
101
107
|
const key = `${entry.projectId}::${entry.workspaceType}::${entry.workspaceId}`;
|
|
102
108
|
const handleRemove = (
|
|
103
109
|
event: React.MouseEvent<HTMLButtonElement>,
|
|
@@ -114,7 +120,7 @@ export const RecentWorkspacesPanel = observer(
|
|
|
114
120
|
key={key}
|
|
115
121
|
type="button"
|
|
116
122
|
className="workspace-setup__recents__tile"
|
|
117
|
-
title={
|
|
123
|
+
title={tileTitle}
|
|
118
124
|
onClick={() =>
|
|
119
125
|
openWorkspace(
|
|
120
126
|
entry.projectId,
|
|
@@ -148,8 +154,22 @@ export const RecentWorkspacesPanel = observer(
|
|
|
148
154
|
{entry.workspaceId}
|
|
149
155
|
</span>
|
|
150
156
|
</div>
|
|
151
|
-
<div className="workspace-
|
|
152
|
-
{
|
|
157
|
+
<div className="workspace-setup__recents__tile__meta">
|
|
158
|
+
{projectTags.length > 0 && (
|
|
159
|
+
<div className="workspace-setup__recents__tile__tags">
|
|
160
|
+
{projectTags.slice(0, 2).map((tag) => (
|
|
161
|
+
<span
|
|
162
|
+
key={tag}
|
|
163
|
+
className="workspace-setup__recents__tile__tag"
|
|
164
|
+
>
|
|
165
|
+
{tag}
|
|
166
|
+
</span>
|
|
167
|
+
))}
|
|
168
|
+
</div>
|
|
169
|
+
)}
|
|
170
|
+
<div className="workspace-setup__recents__tile__time">
|
|
171
|
+
{formatRelativeTime(entry.lastOpenedAt)}
|
|
172
|
+
</div>
|
|
153
173
|
</div>
|
|
154
174
|
</button>
|
|
155
175
|
);
|
|
@@ -378,15 +378,14 @@ export const WorkspaceSetup = withWorkspaceSetupStore(
|
|
|
378
378
|
const recentProjectOptions: ProjectOption[] = setupStore.recentProjects
|
|
379
379
|
.filter((r) => !loadedProjectIds.has(r.projectId))
|
|
380
380
|
.map((r) => {
|
|
381
|
-
//
|
|
382
|
-
//
|
|
383
|
-
// via `selectRecentProject`.
|
|
381
|
+
// Rebuild a real Project from the cached metadata; no synthetic
|
|
382
|
+
// fields needed since we persist everything the schema requires.
|
|
384
383
|
const stub = Project.serialization.fromJson({
|
|
385
384
|
projectId: r.projectId,
|
|
386
385
|
name: r.name,
|
|
387
|
-
description:
|
|
388
|
-
webUrl:
|
|
389
|
-
tags:
|
|
386
|
+
description: r.description,
|
|
387
|
+
webUrl: r.webUrl,
|
|
388
|
+
tags: r.tags,
|
|
390
389
|
} as PlainObject<Project>);
|
|
391
390
|
return { label: stub.name, value: stub };
|
|
392
391
|
});
|
|
@@ -842,6 +842,9 @@ export class EditorStore implements CommandRegistrar {
|
|
|
842
842
|
{
|
|
843
843
|
projectId: this.sdlcState.currentProject.projectId,
|
|
844
844
|
name: this.sdlcState.currentProject.name,
|
|
845
|
+
description: this.sdlcState.currentProject.description,
|
|
846
|
+
webUrl: this.sdlcState.currentProject.webUrl,
|
|
847
|
+
tags: this.sdlcState.currentProject.tags,
|
|
845
848
|
},
|
|
846
849
|
);
|
|
847
850
|
LegendStudioUserDataHelper.workspaceSetup_recordRecentWorkspace(
|
|
@@ -322,11 +322,12 @@ export class DataProductElementTestDataState {
|
|
|
322
322
|
let columns: string[] = [];
|
|
323
323
|
try {
|
|
324
324
|
if (element instanceof IngestDefinition) {
|
|
325
|
-
const accessor =
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
325
|
+
const accessor =
|
|
326
|
+
await graphManager.createAccessorFromPackageableElement(
|
|
327
|
+
element,
|
|
328
|
+
graph,
|
|
329
|
+
{ schemaName: undefined, tableName: item.id },
|
|
330
|
+
);
|
|
330
331
|
if (accessor) {
|
|
331
332
|
columns = accessor.relationType.columns.map((c) => c.name);
|
|
332
333
|
}
|
|
@@ -93,7 +93,6 @@ export class WorkspaceSetupStore {
|
|
|
93
93
|
loadWorkspacesState = ActionState.create();
|
|
94
94
|
createWorkspaceState = ActionState.create();
|
|
95
95
|
showCreateWorkspaceModal = false;
|
|
96
|
-
showAdvancedWorkspaceFilterOptions = false;
|
|
97
96
|
|
|
98
97
|
graphManagerState: GraphManagerState;
|
|
99
98
|
|
|
@@ -115,7 +114,6 @@ export class WorkspaceSetupStore {
|
|
|
115
114
|
showCreateProjectModal: observable,
|
|
116
115
|
workspaces: observable,
|
|
117
116
|
currentWorkspace: observable,
|
|
118
|
-
showAdvancedWorkspaceFilterOptions: observable,
|
|
119
117
|
loadSandboxState: observable,
|
|
120
118
|
showCreateWorkspaceModal: observable,
|
|
121
119
|
sandboxProject: observable,
|
|
@@ -128,7 +126,6 @@ export class WorkspaceSetupStore {
|
|
|
128
126
|
recentWorkspaces: observable,
|
|
129
127
|
setShowCreateProjectModal: action,
|
|
130
128
|
setShowCreateWorkspaceModal: action,
|
|
131
|
-
setShowAdvancedWorkspaceFilterOptions: action,
|
|
132
129
|
setImportProjectSuccessReport: action,
|
|
133
130
|
setSandboxModal: action,
|
|
134
131
|
changeWorkspace: action,
|
|
@@ -183,10 +180,6 @@ export class WorkspaceSetupStore {
|
|
|
183
180
|
this.showCreateWorkspaceModal = val;
|
|
184
181
|
}
|
|
185
182
|
|
|
186
|
-
setShowAdvancedWorkspaceFilterOptions(val: boolean): void {
|
|
187
|
-
this.showAdvancedWorkspaceFilterOptions = val;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
183
|
setImportProjectSuccessReport(
|
|
191
184
|
importProjectSuccessReport: ImportProjectSuccessReport | undefined,
|
|
192
185
|
): void {
|
|
@@ -262,6 +255,12 @@ export class WorkspaceSetupStore {
|
|
|
262
255
|
* Fetches a project by id (used when the user picks a cached "recent"
|
|
263
256
|
* project that may not be in the current search results) and switches to
|
|
264
257
|
* it. If the project no longer exists, the entry is pruned from recents.
|
|
258
|
+
*
|
|
259
|
+
* NOTE: we deliberately don't short-circuit using the cached recent entry
|
|
260
|
+
* here. Going through `getProject` keeps the prune-on-404 path intact, and
|
|
261
|
+
* the cached metadata is already used elsewhere to make the UI feel fast
|
|
262
|
+
* (dropdown stubs in `WorkspaceSetup.tsx` and tile labels in
|
|
263
|
+
* `RecentWorkspacesPanel.tsx`).
|
|
265
264
|
*/
|
|
266
265
|
*selectRecentProject(projectId: string): GeneratorFn<void> {
|
|
267
266
|
this.selectRecentProjectState.inProgress();
|
|
@@ -304,6 +303,12 @@ export class WorkspaceSetupStore {
|
|
|
304
303
|
message: `Sandbox project ${sandboxProject.projectId} created. Creating default workspace...`,
|
|
305
304
|
showLoading: true,
|
|
306
305
|
});
|
|
306
|
+
// Invalidate the cached sandbox info so loadSandboxProject re-fetches
|
|
307
|
+
// and persists the newly-created project id instead of reusing the
|
|
308
|
+
// stale "no sandbox yet" cache entry.
|
|
309
|
+
LegendStudioUserDataHelper.workspaceSetup_clearSandboxInfo(
|
|
310
|
+
this.applicationStore.userDataService,
|
|
311
|
+
);
|
|
307
312
|
yield flowResult(this.loadSandboxProject());
|
|
308
313
|
const sandbox = guaranteeType(
|
|
309
314
|
this.sandboxProject,
|
|
@@ -460,10 +465,58 @@ export class WorkspaceSetupStore {
|
|
|
460
465
|
if (this.enginePromise) {
|
|
461
466
|
yield this.enginePromise;
|
|
462
467
|
}
|
|
468
|
+
|
|
469
|
+
const userId = this.sdlcServerClient.currentUser?.userId;
|
|
470
|
+
|
|
471
|
+
// Fast path — if we have a recent, user-matching cache entry, use it to
|
|
472
|
+
// avoid the `userHasPrototypeProjectAccess` graph manager call and the
|
|
473
|
+
// sandbox-tag project search. We still hit SDLC once to confirm the
|
|
474
|
+
// cached projectId is alive, but `getProject(id)` is cheaper than the
|
|
475
|
+
// tagged search and self-invalidates on 404.
|
|
476
|
+
if (userId) {
|
|
477
|
+
const cached =
|
|
478
|
+
LegendStudioUserDataHelper.workspaceSetup_getCachedSandboxInfo(
|
|
479
|
+
this.applicationStore.userDataService,
|
|
480
|
+
userId,
|
|
481
|
+
);
|
|
482
|
+
if (cached) {
|
|
483
|
+
this.hasSandboxAccess = cached.hasAccess;
|
|
484
|
+
if (!cached.hasAccess) {
|
|
485
|
+
// No access, no project — nothing else to do.
|
|
486
|
+
this.sandboxProject = true;
|
|
487
|
+
this.loadSandboxState.pass();
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
if (cached.projectId) {
|
|
491
|
+
try {
|
|
492
|
+
this.sandboxProject = Project.serialization.fromJson(
|
|
493
|
+
(yield this.sdlcServerClient.getProject(
|
|
494
|
+
cached.projectId,
|
|
495
|
+
)) as PlainObject<Project>,
|
|
496
|
+
);
|
|
497
|
+
this.loadSandboxState.pass();
|
|
498
|
+
return;
|
|
499
|
+
} catch {
|
|
500
|
+
// Cached sandbox project no longer exists on the server; drop
|
|
501
|
+
// the cache and fall through to the full refresh.
|
|
502
|
+
LegendStudioUserDataHelper.workspaceSetup_clearSandboxInfo(
|
|
503
|
+
this.applicationStore.userDataService,
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
} else {
|
|
507
|
+
// User has access but hasn't created a sandbox yet.
|
|
508
|
+
this.sandboxProject = true;
|
|
509
|
+
this.loadSandboxState.pass();
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Slow path — original flow.
|
|
463
516
|
const sandboxProject = (
|
|
464
517
|
(yield this.sdlcServerClient.getProjects(
|
|
465
518
|
undefined,
|
|
466
|
-
|
|
519
|
+
userId,
|
|
467
520
|
[SANDBOX_SDLC_TAG],
|
|
468
521
|
1,
|
|
469
522
|
)) as PlainObject<Project>[]
|
|
@@ -471,7 +524,7 @@ export class WorkspaceSetupStore {
|
|
|
471
524
|
if (this.hasSandboxAccess === undefined) {
|
|
472
525
|
this.hasSandboxAccess =
|
|
473
526
|
(yield this.graphManagerState.graphManager.userHasPrototypeProjectAccess(
|
|
474
|
-
|
|
527
|
+
userId ?? '',
|
|
475
528
|
)) as boolean;
|
|
476
529
|
}
|
|
477
530
|
this.sandboxProject = true;
|
|
@@ -482,6 +535,23 @@ export class WorkspaceSetupStore {
|
|
|
482
535
|
} else if (sandboxProject.length === 1) {
|
|
483
536
|
this.sandboxProject = guaranteeNonNullable(sandboxProject[0]);
|
|
484
537
|
}
|
|
538
|
+
|
|
539
|
+
// Persist the fresh result for next time. We only cache when we have
|
|
540
|
+
// a userId (cache is scoped per user); anonymous sessions skip this.
|
|
541
|
+
if (userId) {
|
|
542
|
+
LegendStudioUserDataHelper.workspaceSetup_recordSandboxInfo(
|
|
543
|
+
this.applicationStore.userDataService,
|
|
544
|
+
{
|
|
545
|
+
userId,
|
|
546
|
+
hasAccess: this.hasSandboxAccess,
|
|
547
|
+
projectId:
|
|
548
|
+
this.sandboxProject instanceof Project
|
|
549
|
+
? this.sandboxProject.projectId
|
|
550
|
+
: undefined,
|
|
551
|
+
},
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
|
|
485
555
|
this.loadSandboxState.pass();
|
|
486
556
|
} catch (error) {
|
|
487
557
|
this.sandboxProject = true;
|