@finos/legend-application-studio 28.21.22 → 28.21.24

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 (92) hide show
  1. package/lib/__lib__/LegendStudioDocumentation.d.ts +2 -2
  2. package/lib/__lib__/LegendStudioDocumentation.d.ts.map +1 -1
  3. package/lib/__lib__/LegendStudioDocumentation.js +1 -1
  4. package/lib/__lib__/LegendStudioDocumentation.js.map +1 -1
  5. package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.d.ts.map +1 -1
  6. package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.js +2 -5
  7. package/lib/components/editor/editor-group/data-editor/RelationElementsDataEditor.js.map +1 -1
  8. package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.d.ts.map +1 -1
  9. package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.js +13 -239
  10. package/lib/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.js.map +1 -1
  11. package/lib/components/editor/editor-group/ingest-editor/IngestDefinitionEditor.d.ts.map +1 -1
  12. package/lib/components/editor/editor-group/ingest-editor/IngestDefinitionEditor.js +17 -3
  13. package/lib/components/editor/editor-group/ingest-editor/IngestDefinitionEditor.js.map +1 -1
  14. package/lib/components/editor/editor-group/ingest-editor/testable/IngestTestableEditor.d.ts +22 -0
  15. package/lib/components/editor/editor-group/ingest-editor/testable/IngestTestableEditor.d.ts.map +1 -0
  16. package/lib/components/editor/editor-group/ingest-editor/testable/IngestTestableEditor.js +134 -0
  17. package/lib/components/editor/editor-group/ingest-editor/testable/IngestTestableEditor.js.map +1 -0
  18. package/lib/components/editor/editor-group/testable/LakehouseTestableEditor.d.ts +89 -0
  19. package/lib/components/editor/editor-group/testable/LakehouseTestableEditor.d.ts.map +1 -0
  20. package/lib/components/editor/editor-group/testable/LakehouseTestableEditor.js +144 -0
  21. package/lib/components/editor/editor-group/testable/LakehouseTestableEditor.js.map +1 -0
  22. package/lib/components/editor/editor-group/testable/TestableSharedComponents.d.ts.map +1 -1
  23. package/lib/components/editor/editor-group/testable/TestableSharedComponents.js +14 -8
  24. package/lib/components/editor/editor-group/testable/TestableSharedComponents.js.map +1 -1
  25. package/lib/components/editor/ingest-definition-editor.css +1 -1
  26. package/lib/components/editor/ingest-definition-editor.css.map +1 -1
  27. package/lib/components/project-reviewer/ProjectReviewer.d.ts.map +1 -1
  28. package/lib/components/project-reviewer/ProjectReviewer.js +2 -1
  29. package/lib/components/project-reviewer/ProjectReviewer.js.map +1 -1
  30. package/lib/components/project-view/ProjectViewer.d.ts.map +1 -1
  31. package/lib/components/project-view/ProjectViewer.js +2 -2
  32. package/lib/components/project-view/ProjectViewer.js.map +1 -1
  33. package/lib/components/showcase/ShowcaseViewer.d.ts.map +1 -1
  34. package/lib/components/showcase/ShowcaseViewer.js +2 -2
  35. package/lib/components/showcase/ShowcaseViewer.js.map +1 -1
  36. package/lib/components/workspace-setup/WorkspaceSetup.d.ts +1 -1
  37. package/lib/components/workspace-setup/WorkspaceSetup.d.ts.map +1 -1
  38. package/lib/components/workspace-setup/WorkspaceSetup.js +98 -43
  39. package/lib/components/workspace-setup/WorkspaceSetup.js.map +1 -1
  40. package/lib/index.css +2 -2
  41. package/lib/index.css.map +1 -1
  42. package/lib/package.json +1 -1
  43. package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.d.ts.map +1 -1
  44. package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.js +1 -2
  45. package/lib/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.js.map +1 -1
  46. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.d.ts.map +1 -1
  47. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js +1 -0
  48. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js.map +1 -1
  49. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.d.ts +8 -30
  50. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.d.ts.map +1 -1
  51. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js +7 -147
  52. package/lib/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js.map +1 -1
  53. package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.d.ts.map +1 -1
  54. package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.js +32 -28
  55. package/lib/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.js.map +1 -1
  56. package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestDefinitionEditorState.d.ts +8 -0
  57. package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestDefinitionEditorState.d.ts.map +1 -1
  58. package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestDefinitionEditorState.js +15 -0
  59. package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestDefinitionEditorState.js.map +1 -1
  60. package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestTestableState.d.ts +86 -0
  61. package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestTestableState.d.ts.map +1 -0
  62. package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestTestableState.js +420 -0
  63. package/lib/stores/editor/editor-state/element-editor-state/ingest/IngestTestableState.js.map +1 -0
  64. package/lib/stores/editor/editor-state/element-editor-state/testable/LakehouseTestableUtils.d.ts +62 -0
  65. package/lib/stores/editor/editor-state/element-editor-state/testable/LakehouseTestableUtils.d.ts.map +1 -0
  66. package/lib/stores/editor/editor-state/element-editor-state/testable/LakehouseTestableUtils.js +138 -0
  67. package/lib/stores/editor/editor-state/element-editor-state/testable/LakehouseTestableUtils.js.map +1 -0
  68. package/lib/stores/workspace-setup/WorkspaceSetupStore.d.ts +10 -1
  69. package/lib/stores/workspace-setup/WorkspaceSetupStore.d.ts.map +1 -1
  70. package/lib/stores/workspace-setup/WorkspaceSetupStore.js +105 -12
  71. package/lib/stores/workspace-setup/WorkspaceSetupStore.js.map +1 -1
  72. package/package.json +11 -11
  73. package/src/__lib__/LegendStudioDocumentation.ts +1 -1
  74. package/src/components/editor/editor-group/data-editor/RelationElementsDataEditor.tsx +2 -5
  75. package/src/components/editor/editor-group/dataProduct/testable/DataProductTestableEditor.tsx +19 -869
  76. package/src/components/editor/editor-group/ingest-editor/IngestDefinitionEditor.tsx +90 -34
  77. package/src/components/editor/editor-group/ingest-editor/testable/IngestTestableEditor.tsx +418 -0
  78. package/src/components/editor/editor-group/testable/LakehouseTestableEditor.tsx +564 -0
  79. package/src/components/editor/editor-group/testable/TestableSharedComponents.tsx +17 -9
  80. package/src/components/project-reviewer/ProjectReviewer.tsx +2 -0
  81. package/src/components/project-view/ProjectViewer.tsx +2 -1
  82. package/src/components/showcase/ShowcaseViewer.tsx +2 -1
  83. package/src/components/workspace-setup/WorkspaceSetup.tsx +169 -75
  84. package/src/stores/editor/editor-state/element-editor-state/data/EmbeddedDataState.ts +1 -2
  85. package/src/stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.ts +1 -0
  86. package/src/stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.ts +18 -204
  87. package/src/stores/editor/editor-state/element-editor-state/function-activator/testable/FunctionTestableState.ts +41 -36
  88. package/src/stores/editor/editor-state/element-editor-state/ingest/IngestDefinitionEditorState.ts +16 -0
  89. package/src/stores/editor/editor-state/element-editor-state/ingest/IngestTestableState.ts +654 -0
  90. package/src/stores/editor/editor-state/element-editor-state/testable/LakehouseTestableUtils.ts +224 -0
  91. package/src/stores/workspace-setup/WorkspaceSetupStore.ts +125 -8
  92. package/tsconfig.json +4 -0
@@ -18,13 +18,10 @@ import { observer } from 'mobx-react-lite';
18
18
  import { flowResult } from 'mobx';
19
19
  import {
20
20
  BlankPanelPlaceholder,
21
- BlankPanelContent,
22
21
  clsx,
23
22
  ContextMenu,
24
23
  CustomSelectorInput,
25
24
  Dialog,
26
- ErrorWarnIcon,
27
- FilledWindowMaximizeIcon,
28
25
  MenuContent,
29
26
  MenuContentItem,
30
27
  Modal,
@@ -39,64 +36,23 @@ import {
39
36
  PanelHeaderActionItem,
40
37
  PanelHeaderActions,
41
38
  PanelLoadingIndicator,
42
- PlayIcon,
43
39
  PlusIcon,
44
- RefreshIcon,
45
- ResizablePanel,
46
- ResizablePanelGroup,
47
- ResizablePanelSplitter,
48
- ResizablePanelSplitterLine,
49
- RunAllIcon,
50
- TimesIcon,
51
40
  } from '@finos/legend-art';
52
- import {
53
- FunctionAccessPoint,
54
- LakehouseAccessPoint,
55
- PrimitiveInstanceValue,
56
- PrimitiveType,
57
- type RawLambda,
58
- type DataProductTestSuite,
59
- type ValueSpecification,
60
- } from '@finos/legend-graph';
41
+ import type { DataProductTestSuite } from '@finos/legend-graph';
61
42
  import type { DataProductEditorState } from '../../../../../stores/editor/editor-state/element-editor-state/dataProduct/DataProductEditorState.js';
62
43
  import {
63
- DataProductValueSpecificationTestParameterState,
64
- type DataProductTestParameterState,
65
44
  type DataProductTestableState,
66
45
  type DataProductTestSuiteState,
67
- type DataProductTestState,
68
- type DataProductTestDataState,
69
- type DataProductElementTestDataState,
70
46
  } from '../../../../../stores/editor/editor-state/element-editor-state/dataProduct/testable/DataProductTestableState.js';
71
- import { forwardRef, useEffect, useRef, useState } from 'react';
72
- import { getTestableResultIcon } from '../../../side-bar/testable/GlobalTestRunner.js';
73
- import {
74
- TESTABLE_RESULT,
75
- getTestableResultFromTestResult,
76
- } from '../../../../../stores/editor/sidebar-state/testable/GlobalTestRunnerState.js';
77
- import { RelationElementsDataEditor } from '../../data-editor/RelationElementsDataEditor.js';
78
- import {
79
- getContentTypeWithParamFromQuery,
80
- type TestParamContentType,
81
- validateTestableId,
82
- } from '../../../../../stores/editor/utils/TestableUtils.js';
47
+ import { forwardRef, useRef, useState } from 'react';
48
+ import { validateTestableId } from '../../../../../stores/editor/utils/TestableUtils.js';
83
49
  import { useEditorStore } from '../../../EditorStoreProvider.js';
84
50
  import { guaranteeNonNullable } from '@finos/legend-shared';
85
- import {
86
- ExternalFormatParameterEditorModal,
87
- RenameModal,
88
- TestAssertionEditor,
89
- } from '../../testable/TestableSharedComponents.js';
51
+ import { RenameModal } from '../../testable/TestableSharedComponents.js';
90
52
  import { testSuite_setId } from '../../../../../stores/graph-modifier/Testable_GraphModifierHelper.js';
91
- import { TESTABLE_TEST_TAB } from '../../../../../stores/editor/editor-state/element-editor-state/testable/TestableEditorState.js';
92
- import {
93
- BasicValueSpecificationEditor,
94
- instanceValue_setValue,
95
- } from '@finos/legend-query-builder';
53
+ import { LakehouseTestSuiteEditor } from '../../testable/LakehouseTestableEditor.js';
96
54
 
97
- // ──────────────────────────────────────────────────────────────────────────────
98
- // Create Suite Modal — test name + access point (datasets are auto-inferred)
99
- // ──────────────────────────────────────────────────────────────────────────────
55
+ // ─── Create Suite Modal ───────────────────────────────────────────────────────
100
56
 
101
57
  interface ItemOption {
102
58
  value: string;
@@ -115,7 +71,6 @@ const CreateSuiteModal = observer(
115
71
  string | undefined
116
72
  >(undefined);
117
73
 
118
- // Auto-generate suite name
119
74
  const existingIds = testableState.dataProduct.tests.map((s) => s.id);
120
75
  const generateSuiteName = (): string => {
121
76
  let idx = 1;
@@ -127,7 +82,6 @@ const CreateSuiteModal = observer(
127
82
 
128
83
  const testError = validateTestableId(testName, undefined);
129
84
 
130
- // Access points on the current DataProduct (for the test target)
131
85
  const accessPointOptions: ItemOption[] = testableState.ownAccessPoints.map(
132
86
  (ap) => ({
133
87
  value: ap.id,
@@ -228,9 +182,7 @@ const CreateSuiteModal = observer(
228
182
  },
229
183
  );
230
184
 
231
- // ──────────────────────────────────────────────────────────────────────────────
232
- // Create Test Modal — asks for test name + access point to test
233
- // ──────────────────────────────────────────────────────────────────────────────
185
+ // ─── Create Test Modal ────────────────────────────────────────────────────────
234
186
 
235
187
  const CreateTestModal = observer(
236
188
  (props: { suiteState: DataProductTestSuiteState; onClose: () => void }) => {
@@ -339,807 +291,7 @@ const CreateTestModal = observer(
339
291
  },
340
292
  );
341
293
 
342
- // ──────────────────────────────────────────────────────────────────────────────
343
- // Element Test Data Item (sidebar list row in test data panel)
344
- // ──────────────────────────────────────────────────────────────────────────────
345
-
346
- const ElementTestDataItem = observer(
347
- (props: {
348
- elementState: DataProductElementTestDataState;
349
- testDataState: DataProductTestDataState;
350
- isReadOnly: boolean;
351
- }) => {
352
- const { elementState, testDataState, isReadOnly } = props;
353
- const isActive =
354
- testDataState.selectedElementTestDataState === elementState;
355
-
356
- const select = (): void =>
357
- testDataState.setSelectedElementTestDataState(elementState);
358
-
359
- return (
360
- <div
361
- className={clsx('testable-test-explorer__item', {
362
- 'testable-test-explorer__item--active': isActive,
363
- })}
364
- >
365
- <div
366
- className="testable-test-explorer__item__label"
367
- onClick={select}
368
- tabIndex={-1}
369
- >
370
- <div className="testable-test-explorer__item__label__text">
371
- <span title={elementState.element.path}>
372
- {elementState.element.name}
373
- </span>
374
- </div>
375
- {!isReadOnly && (
376
- <div className="mapping-test-explorer__item__actions">
377
- <button
378
- className="mapping-test-explorer__item__action"
379
- onClick={(e): void => {
380
- e.stopPropagation();
381
- testDataState.deleteElement(elementState);
382
- }}
383
- tabIndex={-1}
384
- title="Delete"
385
- >
386
- <TimesIcon />
387
- </button>
388
- </div>
389
- )}
390
- </div>
391
- </div>
392
- );
393
- },
394
- );
395
-
396
- const ElementTestDataEditor = observer(
397
- (props: {
398
- elementState: DataProductElementTestDataState;
399
- isReadOnly: boolean;
400
- }) => {
401
- const { elementState, isReadOnly } = props;
402
- const dataState = elementState.relationElementsDataState;
403
-
404
- if (!dataState) {
405
- return (
406
- <BlankPanelContent>No relation data for this element</BlankPanelContent>
407
- );
408
- }
409
-
410
- return (
411
- <RelationElementsDataEditor
412
- dataState={dataState}
413
- isReadOnly={isReadOnly}
414
- hideColumnDefinitions={true}
415
- />
416
- );
417
- },
418
- );
419
-
420
- // ──────────────────────────────────────────────────────────────────────────────
421
- // Test Data Editor (top panel) — always-visible elements sidebar + per-element editor
422
- // ──────────────────────────────────────────────────────────────────────────────
423
-
424
- const AddElementModal = observer(
425
- (props: { testDataState: DataProductTestDataState }) => {
426
- const { testDataState } = props;
427
- const applicationStore = testDataState.editorStore.applicationStore;
428
- const options = testDataState.availableElementsToAdd.map((e) => ({
429
- value: e.path,
430
- label: e.path,
431
- }));
432
- const [selectedPath, setSelectedPath] = useState<string | undefined>(
433
- options[0]?.value,
434
- );
435
- const close = (): void => testDataState.setShowAddElementModal(false);
436
- const add = (): void => {
437
- if (selectedPath) {
438
- testDataState.addElement(selectedPath);
439
- close();
440
- }
441
- };
442
- const onChange = (val: { label: string; value: string } | null): void => {
443
- setSelectedPath(val?.value);
444
- };
445
-
446
- return (
447
- <Dialog
448
- open={testDataState.showAddElementModal}
449
- onClose={close}
450
- classes={{ container: 'search-modal__container' }}
451
- slotProps={{
452
- paper: {
453
- classes: { root: 'search-modal__inner-container' },
454
- },
455
- }}
456
- >
457
- <Modal
458
- darkMode={
459
- !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled
460
- }
461
- >
462
- <ModalHeader>
463
- <ModalTitle title="Add Element" />
464
- </ModalHeader>
465
- <ModalBody>
466
- <CustomSelectorInput
467
- className="panel__content__form__section__dropdown"
468
- options={options}
469
- onChange={onChange}
470
- value={
471
- selectedPath
472
- ? { value: selectedPath, label: selectedPath }
473
- : null
474
- }
475
- placeholder="Select element..."
476
- darkMode={
477
- !applicationStore.layoutService
478
- .TEMPORARY__isLightColorThemeEnabled
479
- }
480
- />
481
- </ModalBody>
482
- <ModalFooter>
483
- <ModalFooterButton
484
- disabled={!selectedPath}
485
- onClick={add}
486
- text="Add"
487
- />
488
- <ModalFooterButton onClick={close} text="Close" type="secondary" />
489
- </ModalFooter>
490
- </Modal>
491
- </Dialog>
492
- );
493
- },
494
- );
495
-
496
- const DataProductTestDataEditor = observer(
497
- (props: { testDataState: DataProductTestDataState; isReadOnly: boolean }) => {
498
- const { testDataState, isReadOnly } = props;
499
-
500
- const addElement = (): void => {
501
- if (testDataState.availableElementsToAdd.length === 0) {
502
- testDataState.editorStore.applicationStore.notificationService.notifyWarning(
503
- 'No elements available to add',
504
- );
505
- return;
506
- }
507
- testDataState.setShowAddElementModal(true);
508
- };
509
-
510
- const hasTestData = testDataState.elementTestDataStates.length > 0;
511
-
512
- return (
513
- <div
514
- className={clsx('data-product-test-data-editor panel', {
515
- 'data-product-test-data-editor--no-data': !hasTestData,
516
- })}
517
- >
518
- <div className="data-product-test-data-editor__data">
519
- <ResizablePanelGroup orientation="vertical">
520
- {/* Left: elements list — always visible */}
521
- <ResizablePanel minSize={100} size={180}>
522
- <div className="binding-editor__header">
523
- <div className="binding-editor__header__title">
524
- <div className="panel__header__title__content">Test Data</div>
525
- </div>
526
- {!isReadOnly && (
527
- <div className="panel__header__actions">
528
- <button
529
- className="panel__header__action"
530
- tabIndex={-1}
531
- onClick={addElement}
532
- title="Add Element"
533
- >
534
- <PlusIcon />
535
- </button>
536
- </div>
537
- )}
538
- </div>
539
- {!hasTestData ? (
540
- <div className="data-product-test-data-editor__warning">
541
- <ErrorWarnIcon />
542
- <span>Add an element to configure test data</span>
543
- </div>
544
- ) : (
545
- <div>
546
- {testDataState.elementTestDataStates.map((elementState) => (
547
- <ElementTestDataItem
548
- key={elementState.element.path}
549
- elementState={elementState}
550
- testDataState={testDataState}
551
- isReadOnly={isReadOnly}
552
- />
553
- ))}
554
- </div>
555
- )}
556
- </ResizablePanel>
557
- <ResizablePanelSplitter>
558
- <ResizablePanelSplitterLine color="var(--color-dark-grey-200)" />
559
- </ResizablePanelSplitter>
560
- {/* Right: per-element dataset tabs + CSV editor */}
561
- <ResizablePanel minSize={200}>
562
- {testDataState.selectedElementTestDataState ? (
563
- <ElementTestDataEditor
564
- elementState={testDataState.selectedElementTestDataState}
565
- isReadOnly={isReadOnly}
566
- />
567
- ) : (
568
- <BlankPanelContent>
569
- Select an element to configure its test data
570
- </BlankPanelContent>
571
- )}
572
- </ResizablePanel>
573
- </ResizablePanelGroup>
574
- </div>
575
- {testDataState.showAddElementModal && (
576
- <AddElementModal testDataState={testDataState} />
577
- )}
578
- </div>
579
- );
580
- },
581
- );
582
-
583
- // ──────────────────────────────────────────────────────────────────────────────
584
- // Test Item (inside tests panel)
585
- // ──────────────────────────────────────────────────────────────────────────────
586
-
587
- const TestItem = observer(
588
- (props: {
589
- testState: DataProductTestState;
590
- suiteState: DataProductTestSuiteState;
591
- isReadOnly: boolean;
592
- }) => {
593
- const { testState, suiteState, isReadOnly } = props;
594
- const isActive = suiteState.selectTestState === testState;
595
- const isRunning = testState.runningTestAction.isInProgress;
596
- const _testableResult = getTestableResultFromTestResult(
597
- testState.testResultState.result,
598
- );
599
- const testResult = isRunning
600
- ? TESTABLE_RESULT.IN_PROGRESS
601
- : _testableResult;
602
-
603
- const select = (): void => suiteState.changeTest(testState.test);
604
- const runTest = (): void => {
605
- flowResult(testState.runTest()).catch(
606
- testState.editorStore.applicationStore.alertUnhandledError,
607
- );
608
- };
609
- const deleteTest = (): void => {
610
- if (!isReadOnly) {
611
- suiteState.deleteTest(testState.test);
612
- }
613
- };
614
-
615
- return (
616
- <div
617
- className={clsx('testable-test-explorer__item', {
618
- 'testable-test-explorer__item--active': isActive,
619
- })}
620
- >
621
- <div
622
- className="testable-test-explorer__item__label"
623
- onClick={select}
624
- tabIndex={-1}
625
- >
626
- <div className="testable-test-explorer__item__label__icon">
627
- {getTestableResultIcon(testResult)}
628
- </div>
629
- <div className="testable-test-explorer__item__label__text">
630
- {testState.test.id}
631
- </div>
632
- </div>
633
- <div className="mapping-test-explorer__item__actions">
634
- <button
635
- className="mapping-test-explorer__item__action mapping-test-explorer__run-test-btn"
636
- onClick={runTest}
637
- disabled={isRunning}
638
- tabIndex={-1}
639
- title={`Run test ${testState.test.id}`}
640
- >
641
- <PlayIcon />
642
- </button>
643
- {!isReadOnly && (
644
- <button
645
- className="mapping-test-explorer__item__action mapping-test-explorer__run-test-btn"
646
- onClick={deleteTest}
647
- tabIndex={-1}
648
- title={`Delete test ${testState.test.id}`}
649
- >
650
- <TimesIcon />
651
- </button>
652
- )}
653
- </div>
654
- </div>
655
- );
656
- },
657
- );
658
-
659
- // ──────────────────────────────────────────────────────────────────────────────
660
- // Test Editor — shows SETUP (input data) and ASSERTION (expected + result) tabs
661
- // ──────────────────────────────────────────────────────────────────────────────
662
-
663
- const DataProductTestParameterEditor = observer(
664
- (props: {
665
- isReadOnly: boolean;
666
- paramState: DataProductTestParameterState;
667
- testState: DataProductTestState;
668
- contentTypeParamPair: TestParamContentType | undefined;
669
- }) => {
670
- const { testState, paramState, isReadOnly, contentTypeParamPair } = props;
671
- const [showPopUp, setShowPopUp] = useState(false);
672
- const valueSpecificationParamState =
673
- paramState instanceof DataProductValueSpecificationTestParameterState
674
- ? paramState
675
- : undefined;
676
- const paramExpression = valueSpecificationParamState?.varExpression;
677
- const paramIsRequired = (paramExpression?.multiplicity.lowerBound ?? 0) > 0;
678
- const type = contentTypeParamPair
679
- ? contentTypeParamPair.contentType
680
- : (paramExpression?.genericType?.value.rawType.name ?? 'unknown');
681
- const primitiveValueSpecification =
682
- valueSpecificationParamState?.valueSpec instanceof PrimitiveInstanceValue
683
- ? valueSpecificationParamState.valueSpec
684
- : undefined;
685
- const primitiveValue = primitiveValueSpecification?.values[0] as
686
- | string
687
- | undefined;
688
- const paramValue =
689
- paramExpression?.genericType?.value.rawType === PrimitiveType.BYTE &&
690
- primitiveValue !== undefined
691
- ? atob(primitiveValue)
692
- : (primitiveValue ?? '');
693
-
694
- const openInPopUp = (): void => setShowPopUp(!showPopUp);
695
- const closePopUp = (): void => setShowPopUp(false);
696
- const updateParamValue = (val: string): void => {
697
- if (
698
- primitiveValueSpecification &&
699
- valueSpecificationParamState &&
700
- paramExpression
701
- ) {
702
- instanceValue_setValue(
703
- primitiveValueSpecification,
704
- paramExpression.genericType?.value.rawType === PrimitiveType.BYTE
705
- ? btoa(val)
706
- : val,
707
- 0,
708
- testState.editorStore.changeDetectionState.observerContext,
709
- );
710
- valueSpecificationParamState.updateValueSpecification(
711
- primitiveValueSpecification,
712
- );
713
- }
714
- };
715
-
716
- return (
717
- <div
718
- key={paramState.parameterValue.name}
719
- className="panel__content__form__section"
720
- >
721
- <div className="panel__content__form__section__header__label">
722
- {paramState.parameterValue.name}
723
- <button
724
- className={clsx('type-tree__node__type__label', {})}
725
- tabIndex={-1}
726
- title={type}
727
- >
728
- {type}
729
- </button>
730
- </div>
731
- {!valueSpecificationParamState || !paramExpression ? (
732
- <BlankPanelPlaceholder
733
- text="Unsupported parameter value"
734
- tooltipText="This parameter was preserved but cannot currently be edited in form mode"
735
- />
736
- ) : contentTypeParamPair && primitiveValueSpecification ? (
737
- <div className="data-product-test-editor__parameter__code-editor">
738
- <textarea
739
- className="panel__content__form__section__textarea value-spec-editor__input"
740
- spellCheck={false}
741
- value={paramValue}
742
- placeholder={primitiveValue === '' ? '(empty)' : undefined}
743
- onChange={(event) => {
744
- updateParamValue(event.target.value);
745
- }}
746
- />
747
- {showPopUp && (
748
- <ExternalFormatParameterEditorModal
749
- valueSpec={valueSpecificationParamState.valueSpec}
750
- varExpression={paramExpression}
751
- isReadOnly={isReadOnly}
752
- onClose={closePopUp}
753
- updateParamValue={updateParamValue}
754
- contentTypeParamPair={contentTypeParamPair}
755
- />
756
- )}
757
- <div className="data-product-test-editor__parameter__value__actions">
758
- <button
759
- className="data-product-test-editor__parameter__code-editor__expand-btn"
760
- onClick={openInPopUp}
761
- tabIndex={-1}
762
- title="Open in a popup..."
763
- >
764
- <FilledWindowMaximizeIcon />
765
- </button>
766
- <button
767
- className="btn--icon btn--dark btn--sm data-product-test-editor__parameter__code-editor__expand-btn"
768
- disabled={isReadOnly || paramIsRequired}
769
- onClick={(): void =>
770
- testState.removeParamValueState(paramState)
771
- }
772
- tabIndex={-1}
773
- title={
774
- paramIsRequired ? 'Parameter Required' : 'Remove Parameter'
775
- }
776
- >
777
- <TimesIcon />
778
- </button>
779
- </div>
780
- </div>
781
- ) : (
782
- <div className="data-product-test-editor__parameter__value">
783
- <BasicValueSpecificationEditor
784
- valueSpecification={valueSpecificationParamState.valueSpec}
785
- setValueSpecification={(val: ValueSpecification): void => {
786
- valueSpecificationParamState.updateValueSpecification(val);
787
- }}
788
- graph={testState.editorStore.graphManagerState.graph}
789
- observerContext={
790
- testState.editorStore.changeDetectionState.observerContext
791
- }
792
- typeCheckOption={{
793
- expectedType:
794
- paramExpression.genericType?.value.rawType ??
795
- PrimitiveType.STRING,
796
- }}
797
- className="query-builder__parameters__value__editor"
798
- resetValue={(): void => {
799
- valueSpecificationParamState.resetValueSpec();
800
- }}
801
- />
802
- <div className="data-product-test-editor__parameter__value__actions">
803
- <button
804
- className="btn--icon btn--dark btn--sm"
805
- disabled={isReadOnly || paramIsRequired}
806
- onClick={(): void =>
807
- testState.removeParamValueState(paramState)
808
- }
809
- tabIndex={-1}
810
- title={
811
- paramIsRequired ? 'Parameter Required' : 'Remove Parameter'
812
- }
813
- >
814
- <TimesIcon />
815
- </button>
816
- </div>
817
- </div>
818
- )}
819
- </div>
820
- );
821
- },
822
- );
823
-
824
- const NewDataProductParameterModal = observer(
825
- (props: { testState: DataProductTestState; isReadOnly: boolean }) => {
826
- const { testState, isReadOnly } = props;
827
- const applicationStore = testState.editorStore.applicationStore;
828
- const currentOption = {
829
- value: testState.newParameterValueName,
830
- label: testState.newParameterValueName,
831
- };
832
- const options = testState.newParamOptions;
833
- const closeModal = (): void => testState.setShowNewParameterModal(false);
834
- const onChange = (val: { label: string; value: string } | null): void => {
835
- if (val === null) {
836
- testState.setNewParameterValueName('');
837
- } else if (val.value !== testState.newParameterValueName) {
838
- testState.setNewParameterValueName(val.value);
839
- }
840
- };
841
- return (
842
- <Dialog
843
- open={testState.showNewParameterModal}
844
- onClose={closeModal}
845
- classes={{ container: 'search-modal__container' }}
846
- slotProps={{
847
- paper: {
848
- classes: { root: 'search-modal__inner-container' },
849
- },
850
- }}
851
- >
852
- <form
853
- onSubmit={(event) => {
854
- event.preventDefault();
855
- testState.addParameterValue();
856
- }}
857
- className="modal modal--dark search-modal"
858
- >
859
- <div className="modal__title">New Test Parameter Value</div>
860
- <CustomSelectorInput
861
- className="panel__content__form__section__dropdown"
862
- options={options}
863
- onChange={onChange}
864
- value={currentOption}
865
- escapeClearsValue={true}
866
- darkMode={
867
- !applicationStore.layoutService
868
- .TEMPORARY__isLightColorThemeEnabled
869
- }
870
- disabled={isReadOnly}
871
- />
872
- <div className="search-modal__actions">
873
- <button className="btn btn--dark" disabled={isReadOnly}>
874
- Add
875
- </button>
876
- </div>
877
- </form>
878
- </Dialog>
879
- );
880
- },
881
- );
882
-
883
- const DataProductTestEditor = observer(
884
- (props: { testState: DataProductTestState; isReadOnly: boolean }) => {
885
- const { testState, isReadOnly } = props;
886
- const selectedTab = testState.selectedTab;
887
- const selectedAssertion = testState.selectedAsertionState;
888
-
889
- useEffect(() => {
890
- testState.syncWithQuery();
891
- }, [testState]);
892
-
893
- const addParameter = (): void => {
894
- testState.openNewParamModal();
895
- };
896
-
897
- const generateParameterValues = (): void => {
898
- testState.generateTestParameterValues();
899
- };
900
-
901
- const accessPoint = testState.suiteState.testableState.ownAccessPoints.find(
902
- (ap) => ap.id === testState.test.accessPointId,
903
- );
904
- let accessPointQuery: RawLambda | undefined;
905
- if (accessPoint instanceof LakehouseAccessPoint) {
906
- accessPointQuery = accessPoint.func;
907
- } else if (accessPoint instanceof FunctionAccessPoint) {
908
- accessPointQuery = accessPoint.query;
909
- }
910
- const hasQueryParameters = Boolean(
911
- testState.queryVariableExpressions.length,
912
- );
913
-
914
- return (
915
- <div className="function-test-editor panel">
916
- <div className="panel__header">
917
- <div className="panel__header data-product-test-editor__header--with-tabs">
918
- <div className="panel__header__title__content">Assertion</div>
919
- </div>
920
- </div>
921
- <div className="panel">
922
- {selectedTab === TESTABLE_TEST_TAB.ASSERTION && (
923
- <>
924
- {selectedAssertion ? (
925
- <div className="data-product-test-editor__assertion">
926
- {hasQueryParameters && (
927
- <div className="data-product-test-editor__parameters-panel-container">
928
- <div className="panel data-product-test-editor__parameters-panel">
929
- <div className="data-product-test-editor__parameters-header">
930
- <div className="data-product-test-editor__parameters-header__title">
931
- <div className="data-product-test-editor__parameters-header__title__label">
932
- Parameters
933
- </div>
934
- </div>
935
- <div className="panel__header__actions data-product-test-editor__parameters-header__actions">
936
- <button
937
- className="panel__header__action data-product-test-editor__generate-btn"
938
- onClick={generateParameterValues}
939
- disabled={!testState.newParamOptions.length}
940
- title="Generate test parameter values"
941
- tabIndex={-1}
942
- >
943
- <div className="data-product-test-editor__generate-btn__label">
944
- <RefreshIcon className="data-product-test-editor__generate-btn__label__icon" />
945
- <div className="data-product-test-editor__generate-btn__label__title">
946
- Generate
947
- </div>
948
- </div>
949
- </button>
950
- <button
951
- className="panel__header__action data-product-test-editor__parameters-header__action"
952
- tabIndex={-1}
953
- disabled={!testState.newParamOptions.length}
954
- onClick={addParameter}
955
- title="Add Parameter Value"
956
- >
957
- <PlusIcon />
958
- </button>
959
- </div>
960
- </div>
961
- <div className="data-product-test-editor__parameters">
962
- {testState.parameterValueStates.map((paramState) => (
963
- <DataProductTestParameterEditor
964
- key={paramState.uuid}
965
- isReadOnly={isReadOnly}
966
- paramState={paramState}
967
- testState={testState}
968
- contentTypeParamPair={getContentTypeWithParamFromQuery(
969
- accessPointQuery,
970
- testState.editorStore,
971
- ).find(
972
- (pair) =>
973
- pair.param === paramState.parameterValue.name,
974
- )}
975
- />
976
- ))}
977
- </div>
978
- </div>
979
- </div>
980
- )}
981
- <div
982
- className={clsx(
983
- 'data-product-test-editor__assertion-result',
984
- {
985
- 'data-product-test-editor__assertion-result--full':
986
- !hasQueryParameters,
987
- },
988
- )}
989
- >
990
- <TestAssertionEditor
991
- testAssertionState={selectedAssertion}
992
- />
993
- </div>
994
- </div>
995
- ) : null}
996
- </>
997
- )}
998
- {selectedTab === TESTABLE_TEST_TAB.ASSERTION &&
999
- !selectedAssertion && (
1000
- <BlankPanelPlaceholder
1001
- text="No assertion"
1002
- tooltipText="No assertion configured for this test"
1003
- />
1004
- )}
1005
- {testState.showNewParameterModal && (
1006
- <NewDataProductParameterModal
1007
- testState={testState}
1008
- isReadOnly={isReadOnly}
1009
- />
1010
- )}
1011
- </div>
1012
- </div>
1013
- );
1014
- },
1015
- );
1016
-
1017
- // ──────────────────────────────────────────────────────────────────────────────
1018
- // Tests Editor (bottom panel) — tests list + test detail
1019
- // ──────────────────────────────────────────────────────────────────────────────
1020
-
1021
- const DataProductTestsEditor = observer(
1022
- (props: {
1023
- suiteState: DataProductTestSuiteState;
1024
- testableState: DataProductTestableState;
1025
- isReadOnly: boolean;
1026
- }) => {
1027
- const { suiteState, testableState, isReadOnly } = props;
1028
- const selectedTest = suiteState.selectTestState;
1029
-
1030
- return (
1031
- <div className="panel data-product-test-editor__tests">
1032
- <div className="data-product-test-editor__tests__content">
1033
- <ResizablePanelGroup orientation="vertical">
1034
- <ResizablePanel minSize={100} size={200}>
1035
- <div className="binding-editor__header">
1036
- <div className="binding-editor__header__title">
1037
- <div className="panel__header__title__content">Tests</div>
1038
- </div>
1039
- <div className="panel__header__actions">
1040
- <button
1041
- className="panel__header__action testable-test-explorer__play__all__icon"
1042
- tabIndex={-1}
1043
- onClick={(): void => {
1044
- flowResult(suiteState.runSuite()).catch(
1045
- testableState.editorStore.applicationStore
1046
- .alertUnhandledError,
1047
- );
1048
- }}
1049
- disabled={
1050
- suiteState.runningSuiteState.isInProgress ||
1051
- suiteState.suite.tests.length === 0
1052
- }
1053
- title="Run all tests in this suite"
1054
- >
1055
- <RunAllIcon />
1056
- </button>
1057
- {!isReadOnly && (
1058
- <button
1059
- className="panel__header__action"
1060
- tabIndex={-1}
1061
- onClick={(): void =>
1062
- testableState.setShowCreateTestModal(true)
1063
- }
1064
- title="Add test to this suite"
1065
- >
1066
- <PlusIcon />
1067
- </button>
1068
- )}
1069
- </div>
1070
- </div>
1071
- <div>
1072
- {suiteState.testStates.map((ts) => (
1073
- <TestItem
1074
- key={ts.test.id}
1075
- testState={ts}
1076
- suiteState={suiteState}
1077
- isReadOnly={isReadOnly}
1078
- />
1079
- ))}
1080
- </div>
1081
- </ResizablePanel>
1082
- <ResizablePanelSplitter>
1083
- <ResizablePanelSplitterLine color="var(--color-dark-grey-200)" />
1084
- </ResizablePanelSplitter>
1085
- <ResizablePanel minSize={56}>
1086
- {selectedTest ? (
1087
- <DataProductTestEditor
1088
- testState={selectedTest}
1089
- isReadOnly={isReadOnly}
1090
- />
1091
- ) : (
1092
- <BlankPanelPlaceholder
1093
- text="Select a test"
1094
- tooltipText="Select a test from the list above"
1095
- />
1096
- )}
1097
- </ResizablePanel>
1098
- </ResizablePanelGroup>
1099
- </div>
1100
- </div>
1101
- );
1102
- },
1103
- );
1104
-
1105
- // ──────────────────────────────────────────────────────────────────────────────
1106
- // Suite Editor — horizontal split: test data (top) + tests (bottom)
1107
- // ──────────────────────────────────────────────────────────────────────────────
1108
-
1109
- const DataProductTestSuiteEditor = observer(
1110
- (props: { suiteState: DataProductTestSuiteState }) => {
1111
- const { suiteState } = props;
1112
- const testableState = suiteState.testableState;
1113
- const isReadOnly = testableState.dataProductEditorState.isReadOnly;
1114
-
1115
- return (
1116
- <div className="data-product-test-suite-editor">
1117
- <ResizablePanelGroup orientation="horizontal">
1118
- <ResizablePanel size={580} minSize={28}>
1119
- <DataProductTestDataEditor
1120
- testDataState={suiteState.testDataState}
1121
- isReadOnly={isReadOnly}
1122
- />
1123
- </ResizablePanel>
1124
- <ResizablePanelSplitter>
1125
- <ResizablePanelSplitterLine color="var(--color-dark-grey-200)" />
1126
- </ResizablePanelSplitter>
1127
- <ResizablePanel minSize={56}>
1128
- <DataProductTestsEditor
1129
- suiteState={suiteState}
1130
- testableState={testableState}
1131
- isReadOnly={isReadOnly}
1132
- />
1133
- </ResizablePanel>
1134
- </ResizablePanelGroup>
1135
- </div>
1136
- );
1137
- },
1138
- );
1139
-
1140
- // ──────────────────────────────────────────────────────────────────────────────
1141
- // Suite Tab Context Menu (rename/delete)
1142
- // ──────────────────────────────────────────────────────────────────────────────
294
+ // ─── Suite Tab Context Menu ───────────────────────────────────────────────────
1143
295
 
1144
296
  const SuiteHeaderTabContextMenu = observer(
1145
297
  forwardRef<
@@ -1169,9 +321,7 @@ const SuiteHeaderTabContextMenu = observer(
1169
321
  }),
1170
322
  );
1171
323
 
1172
- // ──────────────────────────────────────────────────────────────────────────────
1173
- // Main Testing Tab — suite tabs at top, suite editor below
1174
- // ──────────────────────────────────────────────────────────────────────────────
324
+ // ─── Main Testing Tab ─────────────────────────────────────────────────────────
1175
325
 
1176
326
  export const DataProductTestableEditor = observer(
1177
327
  (props: {
@@ -1183,10 +333,6 @@ export const DataProductTestableEditor = observer(
1183
333
  const selectedSuiteState = testableState.selectedSuiteState;
1184
334
  const dp = testableState.dataProduct;
1185
335
 
1186
- useEffect(() => {
1187
- testableState.init();
1188
- }, [testableState]);
1189
-
1190
336
  const addSuite = (): void => {
1191
337
  testableState.setShowCreateSuiteModal(true);
1192
338
  };
@@ -1199,7 +345,7 @@ export const DataProductTestableEditor = observer(
1199
345
  testSuite_setId(guaranteeNonNullable(testableState.suiteToRename), val);
1200
346
 
1201
347
  return (
1202
- <Panel className="data-product-test-suite-editor">
348
+ <Panel className="service-test-suite-editor">
1203
349
  <PanelLoadingIndicator
1204
350
  isLoading={testableState.runningAllTestsState.isInProgress}
1205
351
  />
@@ -1220,14 +366,14 @@ export const DataProductTestableEditor = observer(
1220
366
 
1221
367
  <PanelHeader>
1222
368
  {dp.tests.length ? (
1223
- <PanelHeader className="data-product-test-suite-editor__header data-product-test-suite-editor__header--with-tabs">
369
+ <PanelHeader className="service-test-suite-editor__header service-test-suite-editor__header--with-tabs">
1224
370
  <div className="uml-element-editor__tabs">
1225
371
  {dp.tests.map((suite) => (
1226
372
  <div
1227
373
  key={suite.id}
1228
374
  onClick={(): void => changeSuite(suite)}
1229
- className={clsx('data-product-test-suite-editor__tab', {
1230
- 'data-product-test-suite-editor__tab--active':
375
+ className={clsx('service-test-suite-editor__tab', {
376
+ 'service-test-suite-editor__tab--active':
1231
377
  selectedSuiteState?.suite === suite,
1232
378
  })}
1233
379
  >
@@ -1255,9 +401,13 @@ export const DataProductTestableEditor = observer(
1255
401
  </PanelHeaderActionItem>
1256
402
  </PanelHeaderActions>
1257
403
  </PanelHeader>
1258
- <Panel className="data-product-test-suite-editor">
404
+ <Panel className="service-test-suite-editor">
1259
405
  {selectedSuiteState && (
1260
- <DataProductTestSuiteEditor suiteState={selectedSuiteState} />
406
+ <LakehouseTestSuiteEditor
407
+ suiteState={selectedSuiteState}
408
+ testableState={testableState}
409
+ isReadOnly={isReadOnly}
410
+ />
1261
411
  )}
1262
412
  {!dp.tests.length && (
1263
413
  <BlankPanelPlaceholder