@finos/legend-application-studio 28.21.4 → 28.21.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/__lib__/LegendStudioEvent.d.ts +4 -1
- package/lib/__lib__/LegendStudioEvent.d.ts.map +1 -1
- package/lib/__lib__/LegendStudioEvent.js +3 -0
- package/lib/__lib__/LegendStudioEvent.js.map +1 -1
- package/lib/__lib__/LegendStudioTelemetryHelper.d.ts +2 -1
- package/lib/__lib__/LegendStudioTelemetryHelper.d.ts.map +1 -1
- package/lib/__lib__/LegendStudioTelemetryHelper.js +11 -3
- package/lib/__lib__/LegendStudioTelemetryHelper.js.map +1 -1
- package/lib/__lib__/LegendStudioUserDataHelper.d.ts +41 -1
- package/lib/__lib__/LegendStudioUserDataHelper.d.ts.map +1 -1
- package/lib/__lib__/LegendStudioUserDataHelper.js +120 -1
- package/lib/__lib__/LegendStudioUserDataHelper.js.map +1 -1
- package/lib/components/editor/editor-group/data-editor/EmbeddedDataEditor.d.ts +1 -1
- package/lib/components/editor/editor-group/data-editor/EmbeddedDataEditor.d.ts.map +1 -1
- package/lib/components/editor/editor-group/data-editor/EmbeddedDataEditor.js +3 -3
- package/lib/components/editor/editor-group/data-editor/EmbeddedDataEditor.js.map +1 -1
- package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.d.ts +3 -0
- package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.d.ts.map +1 -1
- package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.js +13 -35
- package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.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 +20 -7
- 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 +59 -22
- package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.js.map +1 -1
- package/lib/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.d.ts.map +1 -1
- package/lib/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.js +113 -75
- package/lib/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.js.map +1 -1
- package/lib/components/editor/editor-group/testable/TestableSharedComponents.d.ts.map +1 -1
- package/lib/components/editor/editor-group/testable/TestableSharedComponents.js +2 -2
- package/lib/components/editor/editor-group/testable/TestableSharedComponents.js.map +1 -1
- package/lib/components/editor/side-bar/DevMetadataPanel.d.ts.map +1 -1
- package/lib/components/editor/side-bar/DevMetadataPanel.js +37 -6
- package/lib/components/editor/side-bar/DevMetadataPanel.js.map +1 -1
- package/lib/components/workspace-setup/RecentWorkspacesPanel.d.ts +22 -0
- package/lib/components/workspace-setup/RecentWorkspacesPanel.d.ts.map +1 -0
- package/lib/components/workspace-setup/RecentWorkspacesPanel.js +80 -0
- package/lib/components/workspace-setup/RecentWorkspacesPanel.js.map +1 -0
- package/lib/components/workspace-setup/WorkspaceSetup.d.ts.map +1 -1
- package/lib/components/workspace-setup/WorkspaceSetup.js +61 -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 +31 -0
- package/lib/stores/editor/EditorStore.js.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.d.ts +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.d.ts.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.js +20 -48
- package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.js.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.d.ts +9 -14
- 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 +125 -78
- package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.d.ts +18 -4
- package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.d.ts.map +1 -1
- package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.js +216 -53
- package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.js.map +1 -1
- package/lib/stores/editor/sidebar-state/ProjectOverviewState.d.ts.map +1 -1
- package/lib/stores/editor/sidebar-state/ProjectOverviewState.js +11 -0
- package/lib/stores/editor/sidebar-state/ProjectOverviewState.js.map +1 -1
- package/lib/stores/editor/sidebar-state/WorkspaceReviewState.d.ts.map +1 -1
- package/lib/stores/editor/sidebar-state/WorkspaceReviewState.js +11 -0
- package/lib/stores/editor/sidebar-state/WorkspaceReviewState.js.map +1 -1
- package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.d.ts +9 -0
- package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.d.ts.map +1 -1
- package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.js +57 -1
- package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.js.map +1 -1
- package/lib/stores/project-reviewer/ProjectReviewerStore.d.ts.map +1 -1
- package/lib/stores/project-reviewer/ProjectReviewerStore.js +12 -0
- package/lib/stores/project-reviewer/ProjectReviewerStore.js.map +1 -1
- package/lib/stores/workspace-setup/WorkspaceSetupStore.d.ts +17 -0
- package/lib/stores/workspace-setup/WorkspaceSetupStore.d.ts.map +1 -1
- package/lib/stores/workspace-setup/WorkspaceSetupStore.js +61 -0
- package/lib/stores/workspace-setup/WorkspaceSetupStore.js.map +1 -1
- package/package.json +16 -16
- package/src/__lib__/LegendStudioEvent.ts +3 -0
- package/src/__lib__/LegendStudioTelemetryHelper.ts +35 -11
- package/src/__lib__/LegendStudioUserDataHelper.ts +204 -1
- package/src/components/editor/editor-group/data-editor/EmbeddedDataEditor.tsx +4 -0
- package/src/components/editor/editor-group/data-editor/RelationElementsDataEditor.tsx +209 -187
- package/src/components/editor/editor-group/dataProduct/DataProductEditor.tsx +26 -7
- package/src/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.tsx +149 -86
- package/src/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.tsx +425 -308
- package/src/components/editor/editor-group/testable/TestableSharedComponents.tsx +3 -11
- package/src/components/editor/side-bar/DevMetadataPanel.tsx +194 -10
- package/src/components/workspace-setup/RecentWorkspacesPanel.tsx +161 -0
- package/src/components/workspace-setup/WorkspaceSetup.tsx +97 -8
- package/src/stores/editor/EditorStore.ts +44 -0
- package/src/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.ts +28 -50
- package/src/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.ts +164 -100
- package/src/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.ts +307 -72
- package/src/stores/editor/sidebar-state/ProjectOverviewState.ts +14 -0
- package/src/stores/editor/sidebar-state/WorkspaceReviewState.ts +14 -0
- package/src/stores/editor/sidebar-state/dev-metadata/DevMetadataState.ts +84 -1
- package/src/stores/project-reviewer/ProjectReviewerStore.ts +15 -0
- package/src/stores/workspace-setup/WorkspaceSetupStore.ts +93 -0
- package/tsconfig.json +1 -0
|
@@ -595,6 +595,7 @@ const EqualToRelationAsssertionEditor = observer(
|
|
|
595
595
|
equalToRelationAssertionState.expectedRelationElementState
|
|
596
596
|
}
|
|
597
597
|
isReadOnly={isReadOnly}
|
|
598
|
+
hideColumnDefinitions={true}
|
|
598
599
|
/>
|
|
599
600
|
);
|
|
600
601
|
},
|
|
@@ -610,25 +611,16 @@ const DataProductEqualToRelationAssertionEditor = observer(
|
|
|
610
611
|
|
|
611
612
|
return (
|
|
612
613
|
<div className="service-test-data-editor panel">
|
|
613
|
-
<div className="function-testable-editor__header">
|
|
614
|
-
<div className="function-testable-editor__header__title">
|
|
615
|
-
<div className="function-testable-editor__header__title__label">
|
|
616
|
-
expected
|
|
617
|
-
</div>
|
|
618
|
-
</div>
|
|
619
|
-
</div>
|
|
620
614
|
<div className="panel__content__form__section">
|
|
621
615
|
<div className="panel__content__form__section__header__label">
|
|
622
|
-
Access Point
|
|
623
|
-
</div>
|
|
624
|
-
<div className="panel__content__form__section__header__prompt">
|
|
625
|
-
{testState.accessPointLabel}
|
|
616
|
+
Access Point: {testState.accessPointLabel}
|
|
626
617
|
</div>
|
|
627
618
|
</div>
|
|
628
619
|
{relationElementState ? (
|
|
629
620
|
<RelationElementEditor
|
|
630
621
|
relationElementState={relationElementState}
|
|
631
622
|
isReadOnly={isReadOnly}
|
|
623
|
+
hideColumnDefinitions={true}
|
|
632
624
|
/>
|
|
633
625
|
) : (
|
|
634
626
|
<BlankPanelPlaceholder
|
|
@@ -46,9 +46,13 @@ import {
|
|
|
46
46
|
TrashIcon,
|
|
47
47
|
PlusIcon,
|
|
48
48
|
CogIcon,
|
|
49
|
+
InfoCircleIcon,
|
|
50
|
+
CompareIcon,
|
|
51
|
+
ChevronDownIcon,
|
|
52
|
+
ChevronRightIcon,
|
|
49
53
|
} from '@finos/legend-art';
|
|
50
54
|
import { CODE_EDITOR_LANGUAGE } from '@finos/legend-code-editor';
|
|
51
|
-
import { CodeEditor } from '@finos/legend-lego/code-editor';
|
|
55
|
+
import { CodeDiffView, CodeEditor } from '@finos/legend-lego/code-editor';
|
|
52
56
|
import {
|
|
53
57
|
type BuildLog,
|
|
54
58
|
type BuildPhaseActionState,
|
|
@@ -338,6 +342,103 @@ const getPhaseStatusIcon = (status: BuildPhaseStatus): ReactNode => {
|
|
|
338
342
|
}
|
|
339
343
|
};
|
|
340
344
|
|
|
345
|
+
const DevMetadataCompareModal = observer(() => {
|
|
346
|
+
const editorStore = useEditorStore();
|
|
347
|
+
const applicationStore = editorStore.applicationStore;
|
|
348
|
+
const devMetadataState = editorStore.devMetadataState;
|
|
349
|
+
const isOpen = devMetadataState.isCompareModalOpen;
|
|
350
|
+
const isLoading = devMetadataState.compareState.isInProgress;
|
|
351
|
+
|
|
352
|
+
if (!isOpen) {
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return (
|
|
357
|
+
<Dialog
|
|
358
|
+
open={isOpen}
|
|
359
|
+
onClose={() => devMetadataState.closeCompareModal()}
|
|
360
|
+
classes={{
|
|
361
|
+
root: 'editor-modal__root-container',
|
|
362
|
+
container: 'editor-modal__container',
|
|
363
|
+
paper: 'editor-modal__content',
|
|
364
|
+
}}
|
|
365
|
+
>
|
|
366
|
+
<Modal
|
|
367
|
+
darkMode={
|
|
368
|
+
!applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled
|
|
369
|
+
}
|
|
370
|
+
className="editor-modal dev-metadata-compare-modal"
|
|
371
|
+
>
|
|
372
|
+
<ModalHeader>
|
|
373
|
+
<ModalTitle title="Compare Workspace with Dev Snapshot" />
|
|
374
|
+
</ModalHeader>
|
|
375
|
+
<ModalBody className="dev-metadata-compare-modal__body">
|
|
376
|
+
{isLoading && (
|
|
377
|
+
<div className="dev-metadata-compare-modal__loading">
|
|
378
|
+
<CircleNotchIcon className="dev-metadata-compare-modal__loading__spinner" />
|
|
379
|
+
<span>Loading diff…</span>
|
|
380
|
+
</div>
|
|
381
|
+
)}
|
|
382
|
+
{!isLoading && devMetadataState.snapshotNotAvailable && (
|
|
383
|
+
<div className="dev-metadata-compare-modal__empty">
|
|
384
|
+
<InfoCircleIcon />
|
|
385
|
+
<div>
|
|
386
|
+
<div className="dev-metadata-compare-modal__empty__title">
|
|
387
|
+
Dev snapshot not available
|
|
388
|
+
</div>
|
|
389
|
+
<div className="dev-metadata-compare-modal__empty__body">
|
|
390
|
+
No deployed metadata was found for this project at version{' '}
|
|
391
|
+
<code>1.0.0-SNAPSHOT</code>. Push to dev first to create a
|
|
392
|
+
snapshot to compare against.
|
|
393
|
+
</div>
|
|
394
|
+
</div>
|
|
395
|
+
</div>
|
|
396
|
+
)}
|
|
397
|
+
{!isLoading && !devMetadataState.snapshotNotAvailable && (
|
|
398
|
+
<div className="dev-metadata-compare-modal__diff">
|
|
399
|
+
<div className="dev-metadata-compare-modal__diff__legend">
|
|
400
|
+
<div className="dev-metadata-compare-modal__diff__legend__side dev-metadata-compare-modal__diff__legend__side--from">
|
|
401
|
+
<span className="dev-metadata-compare-modal__diff__legend__badge dev-metadata-compare-modal__diff__legend__badge--from">
|
|
402
|
+
Deployed
|
|
403
|
+
</span>
|
|
404
|
+
<span className="dev-metadata-compare-modal__diff__legend__label">
|
|
405
|
+
Dev Snapshot
|
|
406
|
+
</span>
|
|
407
|
+
<code className="dev-metadata-compare-modal__diff__legend__version">
|
|
408
|
+
1.0.0-SNAPSHOT
|
|
409
|
+
</code>
|
|
410
|
+
</div>
|
|
411
|
+
<div className="dev-metadata-compare-modal__diff__legend__side dev-metadata-compare-modal__diff__legend__side--to">
|
|
412
|
+
<span className="dev-metadata-compare-modal__diff__legend__badge dev-metadata-compare-modal__diff__legend__badge--to">
|
|
413
|
+
Local
|
|
414
|
+
</span>
|
|
415
|
+
<span className="dev-metadata-compare-modal__diff__legend__label">
|
|
416
|
+
Current Workspace
|
|
417
|
+
</span>
|
|
418
|
+
</div>
|
|
419
|
+
</div>
|
|
420
|
+
<div className="dev-metadata-compare-modal__diff__view">
|
|
421
|
+
<CodeDiffView
|
|
422
|
+
language={CODE_EDITOR_LANGUAGE.PURE}
|
|
423
|
+
from={devMetadataState.snapshotCode ?? ''}
|
|
424
|
+
to={devMetadataState.currentWorkspaceCode ?? ''}
|
|
425
|
+
/>
|
|
426
|
+
</div>
|
|
427
|
+
</div>
|
|
428
|
+
)}
|
|
429
|
+
</ModalBody>
|
|
430
|
+
<ModalFooter>
|
|
431
|
+
<ModalFooterButton
|
|
432
|
+
text="Close"
|
|
433
|
+
onClick={() => devMetadataState.closeCompareModal()}
|
|
434
|
+
type="secondary"
|
|
435
|
+
/>
|
|
436
|
+
</ModalFooter>
|
|
437
|
+
</Modal>
|
|
438
|
+
</Dialog>
|
|
439
|
+
);
|
|
440
|
+
});
|
|
441
|
+
|
|
341
442
|
const PhaseLogsViewer = observer(
|
|
342
443
|
(props: { phase: BuildPhaseActionState; onClose: () => void }) => {
|
|
343
444
|
const { phase, onClose } = props;
|
|
@@ -356,6 +457,17 @@ const PhaseLogsViewer = observer(
|
|
|
356
457
|
|
|
357
458
|
const logs = phase.logs ? formatLogs(phase.logs) : 'No logs available';
|
|
358
459
|
|
|
460
|
+
const handleCopyLogs = (): void => {
|
|
461
|
+
applicationStore.clipboardService
|
|
462
|
+
.copyTextToClipboard(logs)
|
|
463
|
+
.then(() =>
|
|
464
|
+
applicationStore.notificationService.notifySuccess(
|
|
465
|
+
'Logs copied to clipboard',
|
|
466
|
+
),
|
|
467
|
+
)
|
|
468
|
+
.catch(applicationStore.alertUnhandledError);
|
|
469
|
+
};
|
|
470
|
+
|
|
359
471
|
return (
|
|
360
472
|
<Dialog
|
|
361
473
|
open={true}
|
|
@@ -386,6 +498,11 @@ const PhaseLogsViewer = observer(
|
|
|
386
498
|
/>
|
|
387
499
|
</ModalBody>
|
|
388
500
|
<ModalFooter>
|
|
501
|
+
<ModalFooterButton
|
|
502
|
+
text="Copy Logs"
|
|
503
|
+
onClick={handleCopyLogs}
|
|
504
|
+
type="secondary"
|
|
505
|
+
/>
|
|
389
506
|
<ModalFooterButton
|
|
390
507
|
text="Close"
|
|
391
508
|
onClick={onClose}
|
|
@@ -444,7 +561,13 @@ const DeploymentPhaseNode = observer(
|
|
|
444
561
|
}
|
|
445
562
|
menuProps={{ elevation: 7 }}
|
|
446
563
|
>
|
|
447
|
-
<div
|
|
564
|
+
<div
|
|
565
|
+
className={clsx('deployment-phase__node', {
|
|
566
|
+
'deployment-phase__node--clickable': hasLogs,
|
|
567
|
+
})}
|
|
568
|
+
onClick={hasLogs ? () => onViewLogs(phase) : undefined}
|
|
569
|
+
title={hasLogs ? 'Click to view logs' : undefined}
|
|
570
|
+
>
|
|
448
571
|
<div className="deployment-phase__node__icon">{statusIcon}</div>
|
|
449
572
|
<div className="deployment-phase__node__content">
|
|
450
573
|
<div className="deployment-phase__node__title">{phase.phase}</div>
|
|
@@ -578,6 +701,7 @@ export const DevMetadataPanel = observer(() => {
|
|
|
578
701
|
const editorStore = useEditorStore();
|
|
579
702
|
const devMetadataState = editorStore.devMetadataState;
|
|
580
703
|
const [isOptionsModalOpen, setIsOptionsModalOpen] = useState(false);
|
|
704
|
+
const [isInfoExpanded, setIsInfoExpanded] = useState(false);
|
|
581
705
|
|
|
582
706
|
const handlePush = (): void => {
|
|
583
707
|
flowResult(devMetadataState.push()).catch(
|
|
@@ -589,7 +713,15 @@ export const DevMetadataPanel = observer(() => {
|
|
|
589
713
|
devMetadataState.setOptions(newOptions);
|
|
590
714
|
};
|
|
591
715
|
|
|
716
|
+
const handleCompare = (): void => {
|
|
717
|
+
devMetadataState.openCompareModal();
|
|
718
|
+
flowResult(devMetadataState.compareWithSnapshot()).catch(
|
|
719
|
+
editorStore.applicationStore.alertUnhandledError,
|
|
720
|
+
);
|
|
721
|
+
};
|
|
722
|
+
|
|
592
723
|
const isPushing = devMetadataState.pushState.isInProgress;
|
|
724
|
+
const isComparing = devMetadataState.compareState.isInProgress;
|
|
593
725
|
|
|
594
726
|
return (
|
|
595
727
|
<Panel>
|
|
@@ -628,6 +760,46 @@ export const DevMetadataPanel = observer(() => {
|
|
|
628
760
|
</PanelFormSection>
|
|
629
761
|
|
|
630
762
|
<PanelDivider />
|
|
763
|
+
<PanelFormSection>
|
|
764
|
+
<div
|
|
765
|
+
className={clsx('dev-metadata-panel__info-callout', {
|
|
766
|
+
'dev-metadata-panel__info-callout--collapsed': !isInfoExpanded,
|
|
767
|
+
})}
|
|
768
|
+
>
|
|
769
|
+
<button
|
|
770
|
+
type="button"
|
|
771
|
+
className="dev-metadata-panel__info-callout__header"
|
|
772
|
+
onClick={() => setIsInfoExpanded(!isInfoExpanded)}
|
|
773
|
+
title={isInfoExpanded ? 'Hide details' : 'Show details'}
|
|
774
|
+
>
|
|
775
|
+
<span className="dev-metadata-panel__info-callout__header__chevron">
|
|
776
|
+
{isInfoExpanded ? <ChevronDownIcon /> : <ChevronRightIcon />}
|
|
777
|
+
</span>
|
|
778
|
+
<InfoCircleIcon />
|
|
779
|
+
<span className="dev-metadata-panel__info-callout__header__title">
|
|
780
|
+
Heads up — you're using Dev Mode
|
|
781
|
+
</span>
|
|
782
|
+
</button>
|
|
783
|
+
{isInfoExpanded && (
|
|
784
|
+
<div className="dev-metadata-panel__info-callout__content">
|
|
785
|
+
<div className="dev-metadata-panel__info-callout__body">
|
|
786
|
+
This pushes your current workspace straight to your dev branch
|
|
787
|
+
(
|
|
788
|
+
<code className="dev-metadata-panel__info-callout__code">
|
|
789
|
+
1.0.0-SNAPSHOT
|
|
790
|
+
</code>
|
|
791
|
+
), bypassing the GitLab build pipeline so you can iterate and
|
|
792
|
+
test changes faster.
|
|
793
|
+
</div>
|
|
794
|
+
<div className="dev-metadata-panel__info-callout__body">
|
|
795
|
+
Any Lakehouse elements in your workspace — including ingests,
|
|
796
|
+
materialized views, and data products — will be deployed by
|
|
797
|
+
default.
|
|
798
|
+
</div>
|
|
799
|
+
</div>
|
|
800
|
+
)}
|
|
801
|
+
</div>
|
|
802
|
+
</PanelFormSection>
|
|
631
803
|
<PanelFormSection>
|
|
632
804
|
<div className="dev-metadata-panel__push-section">
|
|
633
805
|
<div className="dev-metadata-panel__push-header">
|
|
@@ -635,14 +807,25 @@ export const DevMetadataPanel = observer(() => {
|
|
|
635
807
|
<div className="panel__content__form__section__header__label">
|
|
636
808
|
Deploy Metadata
|
|
637
809
|
</div>
|
|
638
|
-
<
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
810
|
+
<div className="dev-metadata-panel__push-title-row__actions">
|
|
811
|
+
<button
|
|
812
|
+
className="dev-metadata-panel__compare-btn"
|
|
813
|
+
onClick={handleCompare}
|
|
814
|
+
title="Compare current workspace with the deployed dev snapshot"
|
|
815
|
+
disabled={isPushing || isComparing}
|
|
816
|
+
>
|
|
817
|
+
<CompareIcon />
|
|
818
|
+
<span>Compare with Dev</span>
|
|
819
|
+
</button>
|
|
820
|
+
<button
|
|
821
|
+
className="dev-metadata-panel__settings-btn"
|
|
822
|
+
onClick={() => setIsOptionsModalOpen(true)}
|
|
823
|
+
title="Configure deployment options"
|
|
824
|
+
disabled={isPushing}
|
|
825
|
+
>
|
|
826
|
+
<CogIcon />
|
|
827
|
+
</button>
|
|
828
|
+
</div>
|
|
646
829
|
</div>
|
|
647
830
|
<div className="dev-metadata-panel__push-description">
|
|
648
831
|
{isPushing
|
|
@@ -702,6 +885,7 @@ export const DevMetadataPanel = observer(() => {
|
|
|
702
885
|
options={devMetadataState.options}
|
|
703
886
|
onSave={handleSaveOptions}
|
|
704
887
|
/>
|
|
888
|
+
<DevMetadataCompareModal />
|
|
705
889
|
</PanelContent>
|
|
706
890
|
</Panel>
|
|
707
891
|
);
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2020-present, Goldman Sachs
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { observer } from 'mobx-react-lite';
|
|
18
|
+
import {
|
|
19
|
+
FolderIcon,
|
|
20
|
+
HistoryIcon,
|
|
21
|
+
TimesIcon,
|
|
22
|
+
UserIcon,
|
|
23
|
+
UsersIcon,
|
|
24
|
+
} from '@finos/legend-art';
|
|
25
|
+
import { WorkspaceType } from '@finos/legend-server-sdlc';
|
|
26
|
+
import type { WorkspaceSetupStore } from '../../stores/workspace-setup/WorkspaceSetupStore.js';
|
|
27
|
+
import { generateEditorRoute } from '../../__lib__/LegendStudioNavigation.js';
|
|
28
|
+
|
|
29
|
+
const MAX_TILES = 6;
|
|
30
|
+
|
|
31
|
+
const formatRelativeTime = (timestamp: number): string => {
|
|
32
|
+
const diffMs = Date.now() - timestamp;
|
|
33
|
+
if (diffMs < 0) {
|
|
34
|
+
return 'just now';
|
|
35
|
+
}
|
|
36
|
+
const minutes = Math.floor(diffMs / 60_000);
|
|
37
|
+
if (minutes < 1) {
|
|
38
|
+
return 'just now';
|
|
39
|
+
}
|
|
40
|
+
if (minutes < 60) {
|
|
41
|
+
return `${minutes}m ago`;
|
|
42
|
+
}
|
|
43
|
+
const hours = Math.floor(minutes / 60);
|
|
44
|
+
if (hours < 24) {
|
|
45
|
+
return `${hours}h ago`;
|
|
46
|
+
}
|
|
47
|
+
const days = Math.floor(hours / 24);
|
|
48
|
+
if (days < 7) {
|
|
49
|
+
return `${days}d ago`;
|
|
50
|
+
}
|
|
51
|
+
const weeks = Math.floor(days / 7);
|
|
52
|
+
if (weeks < 4) {
|
|
53
|
+
return `${weeks}w ago`;
|
|
54
|
+
}
|
|
55
|
+
const months = Math.floor(days / 30);
|
|
56
|
+
if (months < 12) {
|
|
57
|
+
return `${months}mo ago`;
|
|
58
|
+
}
|
|
59
|
+
return `${Math.floor(days / 365)}y ago`;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const RecentWorkspacesPanel = observer(
|
|
63
|
+
(props: { setupStore: WorkspaceSetupStore }) => {
|
|
64
|
+
const { setupStore } = props;
|
|
65
|
+
const applicationStore = setupStore.applicationStore;
|
|
66
|
+
|
|
67
|
+
// Recents are stored in LRU order (most-recent first). Show the top N
|
|
68
|
+
// workspaces; map each to its project name (or fall back to projectId
|
|
69
|
+
// if the matching project entry was evicted independently).
|
|
70
|
+
const tiles = setupStore.recentWorkspaces.slice(0, MAX_TILES);
|
|
71
|
+
if (tiles.length === 0) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const projectNameById = new Map(
|
|
76
|
+
setupStore.recentProjects.map((p) => [p.projectId, p.name]),
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const openWorkspace = (
|
|
80
|
+
projectId: string,
|
|
81
|
+
workspaceId: string,
|
|
82
|
+
workspaceType: WorkspaceType,
|
|
83
|
+
): void => {
|
|
84
|
+
applicationStore.navigationService.navigator.goToLocation(
|
|
85
|
+
generateEditorRoute(projectId, undefined, workspaceId, workspaceType),
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<div className="workspace-setup__recents">
|
|
91
|
+
<div className="workspace-setup__recents__header">
|
|
92
|
+
<div className="workspace-setup__recents__header__title">
|
|
93
|
+
<HistoryIcon />
|
|
94
|
+
<span>Recent workspaces</span>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
<div className="workspace-setup__recents__grid">
|
|
98
|
+
{tiles.map((entry) => {
|
|
99
|
+
const projectName =
|
|
100
|
+
projectNameById.get(entry.projectId) ?? entry.projectId;
|
|
101
|
+
const key = `${entry.projectId}::${entry.workspaceType}::${entry.workspaceId}`;
|
|
102
|
+
const handleRemove = (
|
|
103
|
+
event: React.MouseEvent<HTMLButtonElement>,
|
|
104
|
+
): void => {
|
|
105
|
+
event.stopPropagation();
|
|
106
|
+
setupStore.removeRecentWorkspace({
|
|
107
|
+
projectId: entry.projectId,
|
|
108
|
+
workspaceId: entry.workspaceId,
|
|
109
|
+
workspaceType: entry.workspaceType,
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
return (
|
|
113
|
+
<button
|
|
114
|
+
key={key}
|
|
115
|
+
type="button"
|
|
116
|
+
className="workspace-setup__recents__tile"
|
|
117
|
+
title={`Open ${projectName} / ${entry.workspaceId}`}
|
|
118
|
+
onClick={() =>
|
|
119
|
+
openWorkspace(
|
|
120
|
+
entry.projectId,
|
|
121
|
+
entry.workspaceId,
|
|
122
|
+
entry.workspaceType,
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
>
|
|
126
|
+
<button
|
|
127
|
+
type="button"
|
|
128
|
+
tabIndex={-1}
|
|
129
|
+
className="workspace-setup__recents__tile__remove"
|
|
130
|
+
title="Remove from recents"
|
|
131
|
+
onClick={handleRemove}
|
|
132
|
+
>
|
|
133
|
+
<TimesIcon />
|
|
134
|
+
</button>
|
|
135
|
+
<div className="workspace-setup__recents__tile__project">
|
|
136
|
+
<FolderIcon />
|
|
137
|
+
<span className="workspace-setup__recents__tile__project__name">
|
|
138
|
+
{projectName}
|
|
139
|
+
</span>
|
|
140
|
+
</div>
|
|
141
|
+
<div className="workspace-setup__recents__tile__workspace">
|
|
142
|
+
{entry.workspaceType === WorkspaceType.GROUP ? (
|
|
143
|
+
<UsersIcon />
|
|
144
|
+
) : (
|
|
145
|
+
<UserIcon />
|
|
146
|
+
)}
|
|
147
|
+
<span className="workspace-setup__recents__tile__workspace__name">
|
|
148
|
+
{entry.workspaceId}
|
|
149
|
+
</span>
|
|
150
|
+
</div>
|
|
151
|
+
<div className="workspace-setup__recents__tile__time">
|
|
152
|
+
{formatRelativeTime(entry.lastOpenedAt)}
|
|
153
|
+
</div>
|
|
154
|
+
</button>
|
|
155
|
+
);
|
|
156
|
+
})}
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
},
|
|
161
|
+
);
|
|
@@ -52,6 +52,7 @@ import { CreateProjectModal } from './CreateProjectModal.js';
|
|
|
52
52
|
import { ActivityBarMenu } from '../editor/ActivityBar.js';
|
|
53
53
|
import { LEGEND_STUDIO_APPLICATION_NAVIGATION_CONTEXT_KEY } from '../../__lib__/LegendStudioApplicationNavigationContext.js';
|
|
54
54
|
import { CreateWorkspaceModal } from './CreateWorkspaceModal.js';
|
|
55
|
+
import { RecentWorkspacesPanel } from './RecentWorkspacesPanel.js';
|
|
55
56
|
import {
|
|
56
57
|
useLegendStudioApplicationStore,
|
|
57
58
|
useLegendStudioBaseStore,
|
|
@@ -66,7 +67,12 @@ import {
|
|
|
66
67
|
buildWorkspaceOption,
|
|
67
68
|
formatWorkspaceOptionLabel,
|
|
68
69
|
} from './WorkspaceSelectorUtils.js';
|
|
69
|
-
import {
|
|
70
|
+
import {
|
|
71
|
+
debounce,
|
|
72
|
+
guaranteeNonNullable,
|
|
73
|
+
type PlainObject,
|
|
74
|
+
} from '@finos/legend-shared';
|
|
75
|
+
import { Project } from '@finos/legend-server-sdlc';
|
|
70
76
|
import { WorkspaceSetupStore } from '../../stores/workspace-setup/WorkspaceSetupStore.js';
|
|
71
77
|
import { openShowcaseManager } from '../../stores/ShowcaseManagerState.js';
|
|
72
78
|
|
|
@@ -360,18 +366,57 @@ export const WorkspaceSetup = withWorkspaceSetupStore(
|
|
|
360
366
|
applicationStore.assistantService.toggleAssistant();
|
|
361
367
|
|
|
362
368
|
// projects
|
|
363
|
-
|
|
369
|
+
// Build a unified option list: recent projects (that aren't already in the
|
|
370
|
+
// loaded set) are prepended so users can instantly re-open common work
|
|
371
|
+
// without waiting for the SDLC search to round-trip.
|
|
372
|
+
const loadedProjectOptions = setupStore.projects
|
|
364
373
|
.map(buildProjectOption)
|
|
365
374
|
.sort(compareLabelFn);
|
|
375
|
+
const loadedProjectIds = new Set(
|
|
376
|
+
setupStore.projects.map((p) => p.projectId),
|
|
377
|
+
);
|
|
378
|
+
const recentProjectOptions: ProjectOption[] = setupStore.recentProjects
|
|
379
|
+
.filter((r) => !loadedProjectIds.has(r.projectId))
|
|
380
|
+
.map((r) => {
|
|
381
|
+
// Construct a lightweight Project stand-in so the existing selector
|
|
382
|
+
// contract is preserved. The full project will be fetched on click
|
|
383
|
+
// via `selectRecentProject`.
|
|
384
|
+
const stub = Project.serialization.fromJson({
|
|
385
|
+
projectId: r.projectId,
|
|
386
|
+
name: r.name,
|
|
387
|
+
description: '',
|
|
388
|
+
webUrl: '',
|
|
389
|
+
tags: [],
|
|
390
|
+
} as PlainObject<Project>);
|
|
391
|
+
return { label: stub.name, value: stub };
|
|
392
|
+
});
|
|
393
|
+
const projectOptions: ProjectOption[] = [
|
|
394
|
+
...recentProjectOptions,
|
|
395
|
+
...loadedProjectOptions,
|
|
396
|
+
];
|
|
397
|
+
const recentProjectIdSet = new Set(
|
|
398
|
+
setupStore.recentProjects.map((p) => p.projectId),
|
|
399
|
+
);
|
|
366
400
|
const selectedProjectOption = setupStore.currentProject
|
|
367
401
|
? buildProjectOption(setupStore.currentProject)
|
|
368
402
|
: null;
|
|
369
403
|
|
|
370
404
|
const onProjectChange = (val: ProjectOption | null): void => {
|
|
371
405
|
if (val) {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
406
|
+
// If the selection corresponds to a recent that hasn't been loaded
|
|
407
|
+
// from search yet, fetch the project before switching.
|
|
408
|
+
const isUnloadedRecent =
|
|
409
|
+
!loadedProjectIds.has(val.value.projectId) &&
|
|
410
|
+
recentProjectIdSet.has(val.value.projectId);
|
|
411
|
+
if (isUnloadedRecent) {
|
|
412
|
+
flowResult(setupStore.selectRecentProject(val.value.projectId)).catch(
|
|
413
|
+
applicationStore.alertUnhandledError,
|
|
414
|
+
);
|
|
415
|
+
} else {
|
|
416
|
+
flowResult(setupStore.changeProject(val.value)).catch(
|
|
417
|
+
applicationStore.alertUnhandledError,
|
|
418
|
+
);
|
|
419
|
+
}
|
|
375
420
|
} else {
|
|
376
421
|
setupStore.resetProject();
|
|
377
422
|
}
|
|
@@ -404,9 +449,40 @@ export const WorkspaceSetup = withWorkspaceSetupStore(
|
|
|
404
449
|
};
|
|
405
450
|
|
|
406
451
|
// workspaces
|
|
407
|
-
|
|
408
|
-
|
|
452
|
+
// Recent (non-patch) workspaces for the currently selected project are
|
|
453
|
+
// floated to the top of the dropdown so users can re-enter their
|
|
454
|
+
// typical work without scanning the full list.
|
|
455
|
+
const projectIdForRecents = setupStore.currentProject?.projectId;
|
|
456
|
+
const recentWorkspaceKeys = new Set(
|
|
457
|
+
projectIdForRecents
|
|
458
|
+
? setupStore.recentWorkspaces
|
|
459
|
+
.filter((w) => w.projectId === projectIdForRecents)
|
|
460
|
+
.map((w) => `${w.workspaceType}::${w.workspaceId}`)
|
|
461
|
+
: [],
|
|
462
|
+
);
|
|
463
|
+
const allWorkspaceOptions = setupStore.workspaces.map(buildWorkspaceOption);
|
|
464
|
+
const recentWorkspaceOptions = allWorkspaceOptions
|
|
465
|
+
.filter(
|
|
466
|
+
(o) =>
|
|
467
|
+
o.value.source === undefined &&
|
|
468
|
+
recentWorkspaceKeys.has(
|
|
469
|
+
`${o.value.workspaceType}::${o.value.workspaceId}`,
|
|
470
|
+
),
|
|
471
|
+
)
|
|
472
|
+
.sort(compareLabelFn);
|
|
473
|
+
const otherWorkspaceOptions = allWorkspaceOptions
|
|
474
|
+
.filter(
|
|
475
|
+
(o) =>
|
|
476
|
+
o.value.source !== undefined ||
|
|
477
|
+
!recentWorkspaceKeys.has(
|
|
478
|
+
`${o.value.workspaceType}::${o.value.workspaceId}`,
|
|
479
|
+
),
|
|
480
|
+
)
|
|
409
481
|
.sort(compareLabelFn);
|
|
482
|
+
const workspaceOptions = [
|
|
483
|
+
...recentWorkspaceOptions,
|
|
484
|
+
...otherWorkspaceOptions,
|
|
485
|
+
];
|
|
410
486
|
const selectedWorkspaceOption = setupStore.currentWorkspace
|
|
411
487
|
? buildWorkspaceOption(setupStore.currentWorkspace)
|
|
412
488
|
: null;
|
|
@@ -489,11 +565,24 @@ export const WorkspaceSetup = withWorkspaceSetupStore(
|
|
|
489
565
|
Welcome to Legend Studio
|
|
490
566
|
</div>
|
|
491
567
|
</div>
|
|
568
|
+
<RecentWorkspacesPanel setupStore={setupStore} />
|
|
492
569
|
<div className="workspace-setup__selectors">
|
|
493
570
|
<div className="workspace-setup__selectors__container">
|
|
494
571
|
<div className="workspace-setup__selector">
|
|
495
572
|
<div className="workspace-setup__selector__header">
|
|
496
|
-
Search for an existing project
|
|
573
|
+
<span>Search for an existing project</span>
|
|
574
|
+
{(setupStore.recentProjects.length > 0 ||
|
|
575
|
+
setupStore.recentWorkspaces.length > 0) && (
|
|
576
|
+
<button
|
|
577
|
+
type="button"
|
|
578
|
+
className="workspace-setup__selector__header__clear-recents"
|
|
579
|
+
tabIndex={-1}
|
|
580
|
+
onClick={() => setupStore.clearRecents()}
|
|
581
|
+
title="Clear recently-opened projects and workspaces"
|
|
582
|
+
>
|
|
583
|
+
Clear recents
|
|
584
|
+
</button>
|
|
585
|
+
)}
|
|
497
586
|
</div>
|
|
498
587
|
<div className="workspace-setup__selector__content">
|
|
499
588
|
<div
|