@finos/legend-application-studio 28.21.4 → 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 (54) 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 +12 -35
  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 +19 -6
  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.map +1 -1
  13. package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.js +59 -22
  14. package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.js.map +1 -1
  15. package/lib/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.d.ts.map +1 -1
  16. package/lib/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.js +113 -75
  17. package/lib/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.js.map +1 -1
  18. package/lib/components/editor/editor-group/testable/TestableSharedComponents.d.ts.map +1 -1
  19. package/lib/components/editor/editor-group/testable/TestableSharedComponents.js +1 -1
  20. package/lib/components/editor/editor-group/testable/TestableSharedComponents.js.map +1 -1
  21. package/lib/components/editor/side-bar/DevMetadataPanel.d.ts.map +1 -1
  22. package/lib/components/editor/side-bar/DevMetadataPanel.js +37 -6
  23. package/lib/components/editor/side-bar/DevMetadataPanel.js.map +1 -1
  24. package/lib/index.css +2 -2
  25. package/lib/index.css.map +1 -1
  26. package/lib/package.json +1 -1
  27. package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.d.ts +1 -1
  28. package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.d.ts.map +1 -1
  29. package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.js +20 -48
  30. package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.js.map +1 -1
  31. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.d.ts +9 -14
  32. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.d.ts.map +1 -1
  33. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js +125 -78
  34. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js.map +1 -1
  35. package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.d.ts +18 -4
  36. package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.d.ts.map +1 -1
  37. package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.js +214 -53
  38. package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.js.map +1 -1
  39. package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.d.ts +9 -0
  40. package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.d.ts.map +1 -1
  41. package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.js +55 -0
  42. package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.js.map +1 -1
  43. package/package.json +16 -16
  44. package/src/components/editor/editor-group/data-editor/EmbeddedDataEditor.tsx +3 -0
  45. package/src/components/editor/editor-group/data-editor/RelationElementsDataEditor.tsx +200 -186
  46. package/src/components/editor/editor-group/dataProduct/DataProductEditor.tsx +25 -7
  47. package/src/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.tsx +149 -86
  48. package/src/components/editor/editor-group/function-activator/testable/FunctionTestableEditor.tsx +425 -308
  49. package/src/components/editor/editor-group/testable/TestableSharedComponents.tsx +2 -11
  50. package/src/components/editor/side-bar/DevMetadataPanel.tsx +194 -10
  51. package/src/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.ts +28 -50
  52. package/src/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.ts +164 -100
  53. package/src/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.ts +303 -72
  54. package/src/stores/editor/sidebar-state/dev-metadata/DevMetadataState.ts +76 -0
@@ -610,25 +610,16 @@ const DataProductEqualToRelationAssertionEditor = observer(
610
610
 
611
611
  return (
612
612
  <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
613
  <div className="panel__content__form__section">
621
614
  <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}
615
+ Access Point: {testState.accessPointLabel}
626
616
  </div>
627
617
  </div>
628
618
  {relationElementState ? (
629
619
  <RelationElementEditor
630
620
  relationElementState={relationElementState}
631
621
  isReadOnly={isReadOnly}
622
+ hideColumnDefinitions={true}
632
623
  />
633
624
  ) : (
634
625
  <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 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
  );
@@ -32,6 +32,10 @@ import {
32
32
  } from '@finos/legend-graph';
33
33
  import {
34
34
  ContentType,
35
+ csvDecodeValue,
36
+ csvEncodeValue,
37
+ csvStringify,
38
+ parseCSVContent,
35
39
  guaranteeNonEmptyString,
36
40
  tryToFormatLosslessJSONString,
37
41
  UnsupportedOperationError,
@@ -273,10 +277,16 @@ export class RelationElementState {
273
277
 
274
278
  updateRow(rowIndex: number, columnIndex: number, value: string): void {
275
279
  if (this.relationElement.rows[rowIndex]) {
276
- this.relationElement.rows[rowIndex].values[columnIndex] = value;
280
+ this.relationElement.rows[rowIndex].values[columnIndex] =
281
+ csvEncodeValue(value);
277
282
  }
278
283
  }
279
284
 
285
+ getDisplayValue(rowIndex: number, columnIndex: number): string {
286
+ const value = this.relationElement.rows[rowIndex]?.values[columnIndex];
287
+ return value !== undefined ? csvDecodeValue(value) : '';
288
+ }
289
+
280
290
  clearAllData(): void {
281
291
  this.relationElement.rows.splice(0);
282
292
  }
@@ -285,7 +295,9 @@ export class RelationElementState {
285
295
  return JSON.stringify(
286
296
  {
287
297
  columns: this.relationElement.columns,
288
- data: this.relationElement.rows,
298
+ data: this.relationElement.rows.map((row) => ({
299
+ values: row.values.map((v) => csvDecodeValue(v)),
300
+ })),
289
301
  },
290
302
  null,
291
303
  2,
@@ -310,7 +322,7 @@ export class RelationElementState {
310
322
  const insertStatements = this.relationElement.rows.map((row) => {
311
323
  const values = this.relationElement.columns
312
324
  .map((col, colIndex) => {
313
- const value = row.values[colIndex] ?? '';
325
+ const value = csvDecodeValue(row.values[colIndex] ?? '');
314
326
  if (value !== '') {
315
327
  return `'${value.replace(/'/g, "''")}'`;
316
328
  }
@@ -324,64 +336,30 @@ export class RelationElementState {
324
336
  }
325
337
 
326
338
  exportCSV(): string {
327
- const headers = this.relationElement.columns.map((col) => col);
328
- const csvLines = [headers.join(',')];
329
-
330
- this.relationElement.rows.forEach((row) => {
331
- const values = headers.map((header, headerIndex) => {
332
- const value = row.values[headerIndex] ?? '';
333
- if (value.includes(',') || value.includes('"')) {
334
- return `"${value.replace(/"/g, '""')}"`;
335
- }
336
- return value;
337
- });
338
- csvLines.push(values.join(','));
339
- });
340
-
341
- return csvLines.join('\n');
342
- }
343
-
344
- private parseCSVLine(line: string): string[] {
345
- const result: string[] = [];
346
- let current = '';
347
- let inQuotes = false;
348
-
349
- for (let i = 0; i < line.length; i++) {
350
- const char = line[i];
351
- if (char === '"') {
352
- inQuotes = !inQuotes;
353
- } else if (char === ',' && !inQuotes) {
354
- result.push(current.trim());
355
- current = '';
356
- } else {
357
- current += char;
358
- }
359
- }
360
- result.push(current.trim());
361
- return result;
339
+ // decode so that csvStringify does not double encode
340
+ const data = this.relationElement.rows.map((row) =>
341
+ row.values.map((v) => csvDecodeValue(v)),
342
+ );
343
+ return csvStringify([this.relationElement.columns, ...data]);
362
344
  }
363
345
 
364
346
  importCSV(csvContent: string): void {
365
- const lines = csvContent.trim().split('\n');
366
- if (lines.length === 0) {
347
+ const parsed = parseCSVContent(csvContent);
348
+ if (parsed.length === 0) {
367
349
  return;
368
350
  }
369
351
 
370
- const firstLine = lines[0];
371
- if (!firstLine) {
352
+ const headers = parsed[0];
353
+ if (!headers) {
372
354
  return;
373
355
  }
374
356
 
375
- const headers = this.parseCSVLine(firstLine);
376
357
  this.relationElement.columns = headers;
377
-
378
- this.relationElement.rows = lines.slice(1).map((line) => {
379
- const values = this.parseCSVLine(line);
358
+ this.relationElement.rows = parsed.slice(1).map((values) => {
380
359
  const row = new RelationRowTestData();
381
- row.values = [];
382
- headers.forEach((header, index) => {
383
- row.values[index] = values[index] ?? '';
384
- });
360
+ row.values = headers.map((_, index) =>
361
+ csvEncodeValue(values[index] ?? ''),
362
+ );
385
363
  return observe_RelationRowTestData(row);
386
364
  });
387
365
  }