@finos/legend-application-studio 28.21.3 → 28.21.5

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 (66) hide show
  1. package/lib/components/editor/editor-group/data-editor/EmbeddedDataEditor.d.ts +1 -1
  2. package/lib/components/editor/editor-group/data-editor/EmbeddedDataEditor.d.ts.map +1 -1
  3. package/lib/components/editor/editor-group/data-editor/EmbeddedDataEditor.js +3 -3
  4. package/lib/components/editor/editor-group/data-editor/EmbeddedDataEditor.js.map +1 -1
  5. package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.d.ts +3 -0
  6. package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.d.ts.map +1 -1
  7. package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.js +72 -52
  8. package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.js.map +1 -1
  9. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.d.ts.map +1 -1
  10. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js +22 -1
  11. package/lib/components/editor/editor-group/dataProduct/DataProductEditor.js.map +1 -1
  12. package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.d.ts +23 -0
  13. package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.d.ts.map +1 -0
  14. package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.js +267 -0
  15. package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.js.map +1 -0
  16. package/lib/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.d.ts.map +1 -1
  17. package/lib/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.js +113 -75
  18. package/lib/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.js.map +1 -1
  19. package/lib/components/editor/editor-group/testable/TestableSharedComponents.d.ts.map +1 -1
  20. package/lib/components/editor/editor-group/testable/TestableSharedComponents.js +39 -5
  21. package/lib/components/editor/editor-group/testable/TestableSharedComponents.js.map +1 -1
  22. package/lib/components/editor/side-bar/DevMetadataPanel.d.ts.map +1 -1
  23. package/lib/components/editor/side-bar/DevMetadataPanel.js +37 -6
  24. package/lib/components/editor/side-bar/DevMetadataPanel.js.map +1 -1
  25. package/lib/index.css +2 -2
  26. package/lib/index.css.map +1 -1
  27. package/lib/package.json +1 -1
  28. package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.d.ts +17 -2
  29. package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.d.ts.map +1 -1
  30. package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.js +56 -49
  31. package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.js.map +1 -1
  32. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.d.ts +4 -1
  33. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.d.ts.map +1 -1
  34. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js +4 -0
  35. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js.map +1 -1
  36. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.d.ts +113 -0
  37. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.d.ts.map +1 -0
  38. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js +647 -0
  39. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js.map +1 -0
  40. package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.d.ts +18 -4
  41. package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.d.ts.map +1 -1
  42. package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.js +214 -53
  43. package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.js.map +1 -1
  44. package/lib/stores/editor/editor-state/element-editor-state/testable/TestAssertionState.d.ts +17 -1
  45. package/lib/stores/editor/editor-state/element-editor-state/testable/TestAssertionState.d.ts.map +1 -1
  46. package/lib/stores/editor/editor-state/element-editor-state/testable/TestAssertionState.js +46 -1
  47. package/lib/stores/editor/editor-state/element-editor-state/testable/TestAssertionState.js.map +1 -1
  48. package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.d.ts +9 -0
  49. package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.d.ts.map +1 -1
  50. package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.js +55 -0
  51. package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.js.map +1 -1
  52. package/package.json +16 -16
  53. package/src/components/editor/editor-group/data-editor/EmbeddedDataEditor.tsx +3 -0
  54. package/src/components/editor/editor-group/data-editor/RelationElementsDataEditor.tsx +331 -231
  55. package/src/components/editor/editor-group/dataProduct/DataProductEditor.tsx +32 -0
  56. package/src/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.tsx +935 -0
  57. package/src/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.tsx +425 -308
  58. package/src/components/editor/editor-group/testable/TestableSharedComponents.tsx +160 -15
  59. package/src/components/editor/side-bar/DevMetadataPanel.tsx +194 -10
  60. package/src/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.ts +82 -51
  61. package/src/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.ts +4 -0
  62. package/src/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.ts +927 -0
  63. package/src/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.ts +303 -72
  64. package/src/stores/editor/editor-state/element-editor-state/testable/TestAssertionState.ts +66 -0
  65. package/src/stores/editor/sidebar-state/dev-metadata/DevMetadataState.ts +76 -0
  66. package/tsconfig.json +2 -0
@@ -15,6 +15,7 @@
15
15
  */
16
16
 
17
17
  import {
18
+ BlankPanelPlaceholder,
18
19
  clsx,
19
20
  CompareIcon,
20
21
  ContextMenu,
@@ -38,6 +39,7 @@ import {
38
39
  } from '@finos/legend-art';
39
40
  import {
40
41
  type DataElement,
42
+ DataProduct,
41
43
  type ValueSpecification,
42
44
  type VariableExpression,
43
45
  type PrimitiveInstanceValue,
@@ -61,6 +63,8 @@ import {
61
63
  type TestAssertionState,
62
64
  EqualToAssertionState,
63
65
  EqualToAssertFailState,
66
+ EqualToRelationAssertionState,
67
+ EqualToRelationAssertFailState,
64
68
  } from '../../../../stores/editor/editor-state/element-editor-state/testable/TestAssertionState.js';
65
69
  import { externalFormatData_setData } from '../../../../stores/graph-modifier/DSL_Data_GraphModifierHelper.js';
66
70
  import { TESTABLE_RESULT } from '../../../../stores/editor/sidebar-state/testable/GlobalTestRunnerState.js';
@@ -76,11 +80,13 @@ import {
76
80
  getPackageableElementOptionFormatter,
77
81
  } from '@finos/legend-lego/graph-editor';
78
82
  import type { TestParamContentType } from '../../../../stores/editor/utils/TestableUtils.js';
83
+ import { RelationElementEditor } from '../data-editor/RelationElementsDataEditor.js';
79
84
  import {
80
85
  BasicValueSpecificationEditor,
81
86
  buildDefaultInstanceValue,
82
87
  } from '@finos/legend-query-builder';
83
88
  import { useApplicationStore } from '@finos/legend-application';
89
+ import type { DataProductTestState } from '../../../../stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js';
84
90
 
85
91
  export const SharedDataElementModal = observer(
86
92
  (props: {
@@ -575,6 +581,123 @@ const TestErrorViewer = observer((props: { testError: TestError }) => {
575
581
  );
576
582
  });
577
583
 
584
+ const EqualToRelationAsssertionEditor = observer(
585
+ (props: {
586
+ testAssertionEditorState: TestAssertionEditorState;
587
+ equalToRelationAssertionState: EqualToRelationAssertionState;
588
+ }) => {
589
+ const { equalToRelationAssertionState, testAssertionEditorState } = props;
590
+ const isReadOnly = testAssertionEditorState.testState.isReadOnly;
591
+
592
+ return (
593
+ <RelationElementEditor
594
+ relationElementState={
595
+ equalToRelationAssertionState.expectedRelationElementState
596
+ }
597
+ isReadOnly={isReadOnly}
598
+ />
599
+ );
600
+ },
601
+ );
602
+
603
+ const DataProductEqualToRelationAssertionEditor = observer(
604
+ (props: { testAssertionEditorState: TestAssertionEditorState }) => {
605
+ const { testAssertionEditorState } = props;
606
+ const testState =
607
+ testAssertionEditorState.testState as DataProductTestState;
608
+ const isReadOnly = testAssertionEditorState.testState.isReadOnly;
609
+ const relationElementState = testState.testDataRelationState;
610
+
611
+ return (
612
+ <div className="service-test-data-editor panel">
613
+ <div className="panel__content__form__section">
614
+ <div className="panel__content__form__section__header__label">
615
+ Access Point: {testState.accessPointLabel}
616
+ </div>
617
+ </div>
618
+ {relationElementState ? (
619
+ <RelationElementEditor
620
+ relationElementState={relationElementState}
621
+ isReadOnly={isReadOnly}
622
+ hideColumnDefinitions={true}
623
+ />
624
+ ) : (
625
+ <BlankPanelPlaceholder
626
+ text="No expected columns"
627
+ tooltipText="No expected columns configured for this test"
628
+ />
629
+ )}
630
+ </div>
631
+ );
632
+ },
633
+ );
634
+
635
+ const EqualToRelationAssertFailViewer = observer(
636
+ (props: {
637
+ equalToRelationAssertFailState: EqualToRelationAssertFailState;
638
+ }) => {
639
+ const { equalToRelationAssertFailState } = props;
640
+ const applicationStore =
641
+ equalToRelationAssertFailState.resultState.editorStore.applicationStore;
642
+ const open = (): void => equalToRelationAssertFailState.setDiffModal(true);
643
+ const close = (): void =>
644
+ equalToRelationAssertFailState.setDiffModal(false);
645
+ const expected = equalToRelationAssertFailState.status.expected;
646
+ const actual = equalToRelationAssertFailState.status.actual;
647
+
648
+ return (
649
+ <>
650
+ <div className="equal-to-json-editor__message" onClick={open}>
651
+ {`<Click to see difference>`}
652
+ </div>
653
+ {equalToRelationAssertFailState.diffModal && (
654
+ <Dialog
655
+ open={Boolean(equalToRelationAssertFailState.diffModal)}
656
+ onClose={close}
657
+ classes={{
658
+ root: 'editor-modal__root-container',
659
+ container: 'editor-modal__container',
660
+ paper: 'editor-modal__content',
661
+ }}
662
+ >
663
+ <Modal
664
+ darkMode={
665
+ !applicationStore.layoutService
666
+ .TEMPORARY__isLightColorThemeEnabled
667
+ }
668
+ className="editor-modal"
669
+ >
670
+ <ModalHeader>
671
+ <div className="equal-to-json-result__diff__summary">
672
+ <div className="equal-to-json-result__diff__header__label">
673
+ expected
674
+ </div>
675
+ <div className="equal-to-json-result__diff__icon">
676
+ <CompareIcon />
677
+ </div>
678
+ <div className="equal-to-json-result__diff__header__label">
679
+ actual
680
+ </div>
681
+ </div>
682
+ </ModalHeader>
683
+ <ModalBody>
684
+ <JSONDiffView from={expected} to={actual} lossless={false} />
685
+ </ModalBody>
686
+ <ModalFooter>
687
+ <ModalFooterButton
688
+ text="Close"
689
+ onClick={close}
690
+ type="secondary"
691
+ />
692
+ </ModalFooter>
693
+ </Modal>
694
+ </Dialog>
695
+ )}
696
+ </>
697
+ );
698
+ },
699
+ );
700
+
578
701
  const AssertFailViewer = observer(
579
702
  (props: { assertFailState: AssertFailState }) => {
580
703
  const { assertFailState } = props;
@@ -606,6 +729,10 @@ const AssertFailViewer = observer(
606
729
  <EqualToJsonAssertFailViewer
607
730
  equalToJsonAssertFailState={assertFailState}
608
731
  />
732
+ ) : assertFailState instanceof EqualToRelationAssertFailState ? (
733
+ <EqualToRelationAssertFailViewer
734
+ equalToRelationAssertFailState={assertFailState}
735
+ />
609
736
  ) : assertFailState instanceof EqualToAssertFailState ? (
610
737
  <EqualToAssertFailViewer equalToAssertFailState={assertFailState} />
611
738
  ) : isGenericEqualToFail ? (
@@ -849,6 +976,8 @@ export const TestAssertionEditor = observer(
849
976
  const { testAssertionState } = props;
850
977
  const selectedTab = testAssertionState.selectedTab;
851
978
  const isReadOnly = testAssertionState.testState.isReadOnly;
979
+ const isDataProductTest =
980
+ testAssertionState.testState.testable instanceof DataProduct;
852
981
  const isDisabled =
853
982
  isReadOnly ||
854
983
  !testAssertionState.assertionState.supportsGeneratingAssertion ||
@@ -870,6 +999,20 @@ export const TestAssertionEditor = observer(
870
999
  testAssertionEditorState={testAssertionState}
871
1000
  />
872
1001
  );
1002
+ } else if (state instanceof EqualToRelationAssertionState) {
1003
+ if (isDataProductTest) {
1004
+ return (
1005
+ <DataProductEqualToRelationAssertionEditor
1006
+ testAssertionEditorState={testAssertionState}
1007
+ />
1008
+ );
1009
+ }
1010
+ return (
1011
+ <EqualToRelationAsssertionEditor
1012
+ equalToRelationAssertionState={state}
1013
+ testAssertionEditorState={testAssertionState}
1014
+ />
1015
+ );
873
1016
  }
874
1017
  return (
875
1018
  <UnsupportedEditorPanel
@@ -901,22 +1044,24 @@ export const TestAssertionEditor = observer(
901
1044
  </div>
902
1045
  ))}
903
1046
  </div>
904
- <div className="testable-test-assertion-editor__header__actions">
905
- <button
906
- className="panel__header__action service-execution-editor__test-data__generate-btn"
907
- onClick={generate}
908
- title="Generate expected result if possible"
909
- disabled={isDisabled}
910
- tabIndex={-1}
911
- >
912
- <div className="service-execution-editor__test-data__generate-btn__label">
913
- <RefreshIcon className="service-execution-editor__test-data__generate-btn__label__icon" />
914
- <div className="service-execution-editor__test-data__generate-btn__label__title">
915
- Generate
1047
+ {!isDataProductTest && (
1048
+ <div className="testable-test-assertion-editor__header__actions">
1049
+ <button
1050
+ className="panel__header__action service-execution-editor__test-data__generate-btn"
1051
+ onClick={generate}
1052
+ title="Generate expected result if possible"
1053
+ disabled={isDisabled}
1054
+ tabIndex={-1}
1055
+ >
1056
+ <div className="service-execution-editor__test-data__generate-btn__label">
1057
+ <RefreshIcon className="service-execution-editor__test-data__generate-btn__label__icon" />
1058
+ <div className="service-execution-editor__test-data__generate-btn__label__title">
1059
+ Generate
1060
+ </div>
916
1061
  </div>
917
- </div>
918
- </button>
919
- </div>
1062
+ </button>
1063
+ </div>
1064
+ )}
920
1065
  </div>
921
1066
  <div className="testable-test-assertion-editor__content">
922
1067
  {selectedTab === TEST_ASSERTION_TAB.EXPECTED && (
@@ -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 className="deployment-phase__node">
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&apos;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
- <button
639
- className="dev-metadata-panel__settings-btn"
640
- onClick={() => setIsOptionsModalOpen(true)}
641
- title="Configure deployment options"
642
- disabled={isPushing}
643
- >
644
- <CogIcon />
645
- </button>
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
  );