@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.
- 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 +12 -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 +19 -6
- 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 +1 -1
- 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/index.css +2 -2
- package/lib/index.css.map +1 -1
- package/lib/package.json +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 +214 -53
- package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.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 +55 -0
- package/lib/stores/editor/sidebar-state/dev-metadata/DevMetadataState.js.map +1 -1
- package/package.json +16 -16
- package/src/components/editor/editor-group/data-editor/EmbeddedDataEditor.tsx +3 -0
- package/src/components/editor/editor-group/data-editor/RelationElementsDataEditor.tsx +200 -186
- package/src/components/editor/editor-group/dataProduct/DataProductEditor.tsx +25 -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 +2 -11
- package/src/components/editor/side-bar/DevMetadataPanel.tsx +194 -10
- 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 +303 -72
- 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
|
|
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
|
);
|
|
@@ -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] =
|
|
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
|
-
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
|
366
|
-
if (
|
|
347
|
+
const parsed = parseCSVContent(csvContent);
|
|
348
|
+
if (parsed.length === 0) {
|
|
367
349
|
return;
|
|
368
350
|
}
|
|
369
351
|
|
|
370
|
-
const
|
|
371
|
-
if (!
|
|
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
|
-
|
|
383
|
-
|
|
384
|
-
});
|
|
360
|
+
row.values = headers.map((_, index) =>
|
|
361
|
+
csvEncodeValue(values[index] ?? ''),
|
|
362
|
+
);
|
|
385
363
|
return observe_RelationRowTestData(row);
|
|
386
364
|
});
|
|
387
365
|
}
|