@finos/legend-application-studio 28.11.3 → 28.12.0

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 (24) hide show
  1. package/lib/__lib__/LegendStudioUserDataHelper.d.ts +24 -0
  2. package/lib/__lib__/LegendStudioUserDataHelper.d.ts.map +1 -0
  3. package/lib/__lib__/LegendStudioUserDataHelper.js +29 -0
  4. package/lib/__lib__/LegendStudioUserDataHelper.js.map +1 -0
  5. package/lib/application/LegendStudioApplicationConfig.d.ts.map +1 -1
  6. package/lib/application/LegendStudioApplicationConfig.js +0 -1
  7. package/lib/application/LegendStudioApplicationConfig.js.map +1 -1
  8. package/lib/components/editor/side-bar/testable/GlobalTestRunner.d.ts +6 -0
  9. package/lib/components/editor/side-bar/testable/GlobalTestRunner.d.ts.map +1 -1
  10. package/lib/components/editor/side-bar/testable/GlobalTestRunner.js +87 -44
  11. package/lib/components/editor/side-bar/testable/GlobalTestRunner.js.map +1 -1
  12. package/lib/index.css +2 -2
  13. package/lib/index.css.map +1 -1
  14. package/lib/package.json +1 -1
  15. package/lib/stores/editor/sidebar-state/testable/GlobalTestRunnerState.d.ts +17 -8
  16. package/lib/stores/editor/sidebar-state/testable/GlobalTestRunnerState.d.ts.map +1 -1
  17. package/lib/stores/editor/sidebar-state/testable/GlobalTestRunnerState.js +70 -22
  18. package/lib/stores/editor/sidebar-state/testable/GlobalTestRunnerState.js.map +1 -1
  19. package/package.json +4 -4
  20. package/src/__lib__/LegendStudioUserDataHelper.ts +43 -0
  21. package/src/application/LegendStudioApplicationConfig.ts +0 -1
  22. package/src/components/editor/side-bar/testable/GlobalTestRunner.tsx +278 -143
  23. package/src/stores/editor/sidebar-state/testable/GlobalTestRunnerState.ts +102 -28
  24. package/tsconfig.json +1 -0
@@ -0,0 +1,43 @@
1
+ import type { UserDataService } from '@finos/legend-application';
2
+ import { returnUndefOnError } from '@finos/legend-shared';
3
+
4
+ /**
5
+ * Copyright (c) 2020-present, Goldman Sachs
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the "License");
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ */
19
+ export enum LEGEND_STUDIO_USER_DATA_KEY {
20
+ GLOBAL_TEST_RUNNER_SHOW_DEPENDENCIES = 'studio-editor.global-test-runner-showDependencyPanel',
21
+ }
22
+
23
+ export class LegendStudioUserDataHelper {
24
+ static globalTestRunner_getShowDependencyPanel(
25
+ service: UserDataService,
26
+ ): boolean | undefined {
27
+ return returnUndefOnError(() =>
28
+ service.getBooleanValue(
29
+ LEGEND_STUDIO_USER_DATA_KEY.GLOBAL_TEST_RUNNER_SHOW_DEPENDENCIES,
30
+ ),
31
+ );
32
+ }
33
+
34
+ static globalTestRunner_setShowDependencyPanel(
35
+ service: UserDataService,
36
+ val: boolean,
37
+ ): void {
38
+ service.persistValue(
39
+ LEGEND_STUDIO_USER_DATA_KEY.GLOBAL_TEST_RUNNER_SHOW_DEPENDENCIES,
40
+ val,
41
+ );
42
+ }
43
+ }
@@ -95,7 +95,6 @@ class LegendStudioApplicationCoreOptions {
95
95
  TEMPORARY__serviceRegistrationConfig: ServiceRegistrationEnvironmentConfig[] =
96
96
  [];
97
97
 
98
- // TODO- change it to false
99
98
  /**
100
99
  * This flag can be removed when the support for end-to-end workflow is official
101
100
  */
@@ -42,6 +42,14 @@ import {
42
42
  Panel,
43
43
  PanelHeader,
44
44
  OffIcon,
45
+ DropdownMenu,
46
+ MenuContentItemIcon,
47
+ CheckIcon,
48
+ MenuContentItemLabel,
49
+ MoreVerticalIcon,
50
+ ResizablePanelGroup,
51
+ ResizablePanel,
52
+ ResizablePanelSplitter,
45
53
  } from '@finos/legend-art';
46
54
  import {
47
55
  AssertFail,
@@ -56,7 +64,7 @@ import {
56
64
  prettyCONSTName,
57
65
  } from '@finos/legend-shared';
58
66
  import { observer } from 'mobx-react-lite';
59
- import { forwardRef, useEffect, useState } from 'react';
67
+ import React, { forwardRef, useEffect, useState } from 'react';
60
68
  import { TEST_RUNNER_TABS } from '../../../../stores/editor/EditorConfig.js';
61
69
  import {
62
70
  type TestableExplorerTreeNodeData,
@@ -195,7 +203,7 @@ const TestFailViewer = observer(
195
203
  >
196
204
  <Modal darkMode={true} className="editor-modal">
197
205
  <PanelLoadingIndicator
198
- isLoading={globalTestRunnerState.isDispatchingAction}
206
+ isLoading={globalTestRunnerState.isDispatchingOwnProjectAction}
199
207
  />
200
208
  <ModalHeader title={id} />
201
209
  <ModalBody>
@@ -256,17 +264,12 @@ const TestableExplorerContextMenu = observer(
256
264
  return (
257
265
  <MenuContent data-testid={LEGEND_STUDIO_TEST_ID.EXPLORER_CONTEXT_MENU}>
258
266
  <MenuContentItem
259
- disabled={globalTestRunnerState.isDispatchingAction}
267
+ disabled={globalTestRunnerState.isDispatchingOwnProjectAction}
260
268
  onClick={runTest}
261
269
  >
262
270
  Run
263
271
  </MenuContentItem>
264
- <MenuContentItem
265
- disabled={globalTestRunnerState.isDispatchingAction}
266
- onClick={visitTestable}
267
- >
268
- Open Testable
269
- </MenuContentItem>
272
+ <MenuContentItem onClick={visitTestable}>Open Testable</MenuContentItem>
270
273
  {error &&
271
274
  (error instanceof TestError || error instanceof AssertFail) && (
272
275
  <MenuContentItem onClick={(): void => viewError(error)}>
@@ -297,11 +300,13 @@ const TestableTreeNodeContainer: React.FC<
297
300
  globalTestRunnerState: GlobalTestRunnerState;
298
301
  testableState: TestableState;
299
302
  treeData: TreeData<TestableExplorerTreeNodeData>;
303
+ isDependency?: boolean | undefined;
300
304
  }
301
305
  >
302
306
  > = (props) => {
303
307
  const { node, level, stepPaddingInRem, onNodeSelect } = props;
304
- const { treeData, testableState, globalTestRunnerState } = props.innerProps;
308
+ const { treeData, testableState, globalTestRunnerState, isDependency } =
309
+ props.innerProps;
305
310
  const editorStore = useEditorStore();
306
311
  const results = testableState.results;
307
312
  const expandIcon =
@@ -326,7 +331,10 @@ const TestableTreeNodeContainer: React.FC<
326
331
  const resultIcon = getTestableResultIcon(
327
332
  getNodeTestableResult(
328
333
  node,
329
- testableState.globalTestRunnerState.isRunningTests.isInProgress,
334
+ isDependency
335
+ ? testableState.globalTestRunnerState.isRunningDependencyTests
336
+ .isInProgress
337
+ : testableState.globalTestRunnerState.isRunningTests.isInProgress,
330
338
  results,
331
339
  ),
332
340
  );
@@ -420,44 +428,96 @@ const TestableTreeNodeContainer: React.FC<
420
428
  );
421
429
  };
422
430
 
423
- // TODO:
424
- // - Handle Multi Execution Test Results
425
- // - Add `Visit Test` to open test editors when Testable Editors are complete
426
- export const GlobalTestRunner = observer(
431
+ export const TestablePanelRunner = observer(
427
432
  (props: { globalTestRunnerState: GlobalTestRunnerState }) => {
428
- const editorStore = useEditorStore();
429
- const globalTestRunnerState = props.globalTestRunnerState;
430
- const isDispatchingAction = globalTestRunnerState.isDispatchingAction;
431
-
432
- const [selectedTab, setSelectedTab] = useState(
433
- TEST_RUNNER_TABS.TEST_RUNNER.valueOf(),
434
- );
435
-
436
- const extractTestRunnerTabConfigurations = editorStore.pluginManager
437
- .getApplicationPlugins()
438
- .flatMap(
439
- (plugin) => plugin.getExtraTestRunnerViewConfigurations?.() ?? [],
440
- )
441
- .filter((configuration) => configuration.renderer(editorStore));
442
-
443
- const testRunnerTabs = (Object.values(TEST_RUNNER_TABS) as string[])
444
- .map((e) => ({
445
- value: e,
446
- label: prettyCONSTName(e),
447
- }))
448
- .concat(
449
- extractTestRunnerTabConfigurations.map((config) => ({
450
- value: config.key,
451
- label: config.title,
452
- })),
453
- );
454
-
455
- const changeTab = (tab: string): void => {
456
- setSelectedTab(tab);
433
+ const { globalTestRunnerState } = props;
434
+ const isDispatchingAction =
435
+ globalTestRunnerState.isDispatchingOwnProjectAction;
436
+ const showDependencyPanel = globalTestRunnerState.showDependencyPanel;
437
+ const toggleShowDependencyTests = (): void => {
438
+ globalTestRunnerState.setShowDependencyPanel(!showDependencyPanel);
457
439
  };
440
+ const runAllTests = (): GeneratorFn<void> =>
441
+ globalTestRunnerState.runAllTests();
442
+ const reset = (): void => globalTestRunnerState.initOwnTestables(true);
443
+
444
+ //
445
+ const runDependencyTests = (): GeneratorFn<void> =>
446
+ globalTestRunnerState.runDependenciesTests();
447
+ const isDispatchingDependencyAction =
448
+ globalTestRunnerState.isDispatchingDependencyAction;
458
449
 
459
450
  const renderTestables = (): React.ReactNode => (
460
451
  <>
452
+ <PanelHeader>
453
+ <div className="panel__header__title">
454
+ <div className="panel__header__title__content">TESTABLES</div>
455
+ </div>
456
+ <div className="panel__header__actions side-bar__header__actions">
457
+ <button
458
+ className={clsx(
459
+ 'panel__header__action side-bar__header__action global-test-runner__refresh-btn',
460
+ {
461
+ 'global-test-runner__refresh-btn--loading':
462
+ isDispatchingAction,
463
+ },
464
+ )}
465
+ disabled={isDispatchingAction}
466
+ onClick={reset}
467
+ tabIndex={-1}
468
+ title="Reset"
469
+ >
470
+ <RefreshIcon />
471
+ </button>
472
+ <button
473
+ className="panel__header__action side-bar__header__action global-test-runner__refresh-btn"
474
+ disabled={isDispatchingAction}
475
+ onClick={runAllTests}
476
+ tabIndex={-1}
477
+ title="Run All Tests"
478
+ >
479
+ <PlayIcon />
480
+ </button>
481
+ <div
482
+ className="global-test-runner__count"
483
+ data-testid={
484
+ LEGEND_STUDIO_TEST_ID.SIDEBAR_PANEL_HEADER__CHANGES_COUNT
485
+ }
486
+ >
487
+ {globalTestRunnerState.testableStates?.length ?? '0'}
488
+ </div>
489
+
490
+ <DropdownMenu
491
+ className="panel__header__action"
492
+ title="Show Options Menu..."
493
+ content={
494
+ <MenuContent>
495
+ <MenuContentItem onClick={toggleShowDependencyTests}>
496
+ <MenuContentItemIcon>
497
+ {showDependencyPanel ? <CheckIcon /> : null}
498
+ </MenuContentItemIcon>
499
+ <MenuContentItemLabel>
500
+ Show from dependency tests
501
+ </MenuContentItemLabel>
502
+ </MenuContentItem>
503
+ </MenuContent>
504
+ }
505
+ menuProps={{
506
+ anchorOrigin: {
507
+ vertical: 'bottom',
508
+ horizontal: 'left',
509
+ },
510
+ transformOrigin: {
511
+ vertical: 'top',
512
+ horizontal: 'left',
513
+ },
514
+ elevation: 7,
515
+ }}
516
+ >
517
+ <MoreVerticalIcon className="query-builder__icon__more-options" />
518
+ </DropdownMenu>
519
+ </div>
520
+ </PanelHeader>
461
521
  {(globalTestRunnerState.testableStates ?? []).map((testableState) => {
462
522
  const onNodeSelect = (node: TestableExplorerTreeNodeData): void => {
463
523
  testableState.onTreeNodeSelect(node, testableState.treeData);
@@ -492,116 +552,191 @@ export const GlobalTestRunner = observer(
492
552
  </>
493
553
  );
494
554
 
495
- useEffect(() => {
496
- editorStore.globalTestRunnerState.init();
497
- }, [editorStore.globalTestRunnerState]);
555
+ const renderDependenciesTestables = (): React.ReactNode => (
556
+ <>
557
+ <PanelHeader>
558
+ <div className="panel__header__title">
559
+ <div className="panel__header__title__content">
560
+ DEPENDENCY TESTABLES
561
+ </div>
562
+ </div>
563
+ <div className="panel__header__actions side-bar__header__actions">
564
+ <button
565
+ className="panel__header__action side-bar__header__action global-test-runner__refresh-btn"
566
+ disabled={isDispatchingDependencyAction}
567
+ onClick={runDependencyTests}
568
+ tabIndex={-1}
569
+ title="Run Dependency Tests"
570
+ >
571
+ <PlayIcon />
572
+ </button>
573
+ <div
574
+ className="global-test-runner__count"
575
+ data-testid={
576
+ LEGEND_STUDIO_TEST_ID.SIDEBAR_PANEL_HEADER__CHANGES_COUNT
577
+ }
578
+ >
579
+ {globalTestRunnerState.dependencyTestableStates?.length ?? '0'}
580
+ </div>
581
+ </div>
582
+ </PanelHeader>
583
+ {(globalTestRunnerState.dependencyTestableStates ?? []).map(
584
+ (testableState) => {
585
+ const onNodeSelect = (node: TestableExplorerTreeNodeData): void => {
586
+ testableState.onTreeNodeSelect(node, testableState.treeData);
587
+ };
588
+ const getChildNodes = (
589
+ node: TestableExplorerTreeNodeData,
590
+ ): TestableExplorerTreeNodeData[] => {
591
+ if (node.childrenIds) {
592
+ return node.childrenIds
593
+ .map((id) => testableState.treeData.nodes.get(id))
594
+ .filter(isNonNullable);
595
+ }
596
+ return [];
597
+ };
598
+ return (
599
+ <TreeView
600
+ components={{
601
+ TreeNodeContainer: TestableTreeNodeContainer,
602
+ }}
603
+ key={testableState.uuid}
604
+ treeData={testableState.treeData}
605
+ onNodeSelect={onNodeSelect}
606
+ getChildNodes={getChildNodes}
607
+ innerProps={{
608
+ globalTestRunnerState,
609
+ testableState,
610
+ treeData: testableState.treeData,
611
+ isDependency: true,
612
+ }}
613
+ />
614
+ );
615
+ },
616
+ )}
617
+ </>
618
+ );
498
619
 
499
- const runAllTests = (): GeneratorFn<void> =>
500
- globalTestRunnerState.runAllTests(undefined);
620
+ return (
621
+ <Panel className="side-bar__panel">
622
+ {!showDependencyPanel && (
623
+ <PanelContent>{renderTestables()}</PanelContent>
624
+ )}
625
+ {showDependencyPanel && (
626
+ <ResizablePanelGroup>
627
+ <ResizablePanel>
628
+ <PanelContent>{renderTestables()}</PanelContent>
629
+ </ResizablePanel>
630
+ <ResizablePanelSplitter />
631
+ <ResizablePanel>
632
+ <PanelContent>{renderDependenciesTestables()}</PanelContent>
633
+ </ResizablePanel>
634
+ </ResizablePanelGroup>
635
+ )}
636
+ {globalTestRunnerState.failureViewing && (
637
+ <TestFailViewer
638
+ globalTestRunnerState={globalTestRunnerState}
639
+ failure={globalTestRunnerState.failureViewing}
640
+ />
641
+ )}
642
+ </Panel>
643
+ );
644
+ },
645
+ );
646
+
647
+ // TODO:
648
+ // - Handle Multi Execution Test Results
649
+ export const GlobalTestRunner = observer(
650
+ (props: { globalTestRunnerState: GlobalTestRunnerState }) => {
651
+ const editorStore = useEditorStore();
652
+ const globalTestRunnerState = props.globalTestRunnerState;
653
+ const isDispatchingAction =
654
+ globalTestRunnerState.isDispatchingOwnProjectAction;
655
+
656
+ const [selectedTab, setSelectedTab] = useState(
657
+ TEST_RUNNER_TABS.TEST_RUNNER.valueOf(),
658
+ );
501
659
 
502
- const reset = (): void => globalTestRunnerState.init(true);
660
+ const extractTestRunnerTabConfigurations = editorStore.pluginManager
661
+ .getApplicationPlugins()
662
+ .flatMap(
663
+ (plugin) => plugin.getExtraTestRunnerViewConfigurations?.() ?? [],
664
+ )
665
+ .filter((configuration) => configuration.renderer(editorStore));
503
666
 
504
- const renderTestRunnerTab = (): React.ReactNode => {
667
+ const testRunnerTabs = (Object.values(TEST_RUNNER_TABS) as string[])
668
+ .map((e) => ({
669
+ value: e,
670
+ label: prettyCONSTName(e),
671
+ }))
672
+ .concat(
673
+ extractTestRunnerTabConfigurations.map((config) => ({
674
+ value: config.key,
675
+ label: config.title,
676
+ })),
677
+ );
678
+
679
+ const changeTab = (tab: string): void => {
680
+ setSelectedTab(tab);
681
+ };
682
+
683
+ useEffect(() => {
684
+ editorStore.globalTestRunnerState.initOwnTestables();
685
+ }, [editorStore.globalTestRunnerState]);
686
+
687
+ const renderSelectedTab = (): React.ReactNode => {
505
688
  if (selectedTab === TEST_RUNNER_TABS.TEST_RUNNER) {
506
689
  return (
507
- <div
508
- data-testid={LEGEND_STUDIO_TEST_ID.TEST_RUNNER}
509
- className="panel global-test-runner"
510
- >
511
- <PanelHeader className="panel__header side-bar__header">
512
- <div className="panel__header__title global-test-runner__header__title">
513
- <div className="panel__header__title__content side-bar__header__title__content">
514
- GLOBAL TEST RUNNER
515
- </div>
516
- </div>
517
- <div className="panel__header__actions side-bar__header__actions">
518
- <button
519
- className={clsx(
520
- 'panel__header__action side-bar__header__action global-test-runner__refresh-btn',
521
- {
522
- 'global-test-runner__refresh-btn--loading':
523
- isDispatchingAction,
524
- },
525
- )}
526
- disabled={isDispatchingAction}
527
- onClick={reset}
528
- tabIndex={-1}
529
- title="Reset"
530
- >
531
- <RefreshIcon />
532
- </button>
533
- <button
534
- className="panel__header__action side-bar__header__action global-test-runner__refresh-btn"
535
- disabled={isDispatchingAction}
536
- onClick={runAllTests}
537
- tabIndex={-1}
538
- title="Run All Tests"
539
- >
540
- <PlayIcon />
541
- </button>
542
- </div>
543
- </PanelHeader>
544
- <div className="panel__header__tabs panel__header__test__runner__tabs">
545
- {testRunnerTabs.map((tab) => (
546
- <div
547
- key={tab.value}
548
- onClick={() => changeTab(tab.value)}
549
- className={clsx('panel__header__tab', {
550
- ['panel__header__tab--active']: tab.value === selectedTab,
551
- })}
552
- >
553
- {tab.label}
554
- </div>
555
- ))}
556
- </div>
557
- <PanelContent className="side-bar__content">
558
- <PanelLoadingIndicator isLoading={isDispatchingAction} />
559
- <Panel className="side-bar__panel">
560
- <PanelHeader>
561
- <div className="panel__header__title">
562
- <div className="panel__header__title__content">
563
- TESTABLES
564
- </div>
565
- </div>
566
- <div
567
- className="side-bar__panel__header__changes-count"
568
- data-testid={
569
- LEGEND_STUDIO_TEST_ID.SIDEBAR_PANEL_HEADER__CHANGES_COUNT
570
- }
571
- >
572
- {globalTestRunnerState.testableStates?.length ?? '0'}
573
- </div>
574
- </PanelHeader>
575
- <PanelContent>{renderTestables()}</PanelContent>
576
- {globalTestRunnerState.failureViewing && (
577
- <TestFailViewer
578
- globalTestRunnerState={globalTestRunnerState}
579
- failure={globalTestRunnerState.failureViewing}
580
- />
581
- )}
582
- </Panel>
583
- </PanelContent>
584
- </div>
690
+ <>
691
+ <PanelLoadingIndicator isLoading={isDispatchingAction} />
692
+ <TestablePanelRunner
693
+ globalTestRunnerState={globalTestRunnerState}
694
+ />
695
+ </>
585
696
  );
586
- } else {
587
- for (const testRunnerTabConfiguration of extractTestRunnerTabConfigurations) {
588
- if (testRunnerTabConfiguration.key === selectedTab) {
589
- return testRunnerTabConfiguration.renderer(editorStore);
590
- }
697
+ }
698
+ for (const testRunnerTabConfiguration of extractTestRunnerTabConfigurations) {
699
+ if (testRunnerTabConfiguration.key === selectedTab) {
700
+ return testRunnerTabConfiguration.renderer(editorStore);
591
701
  }
592
- return (
593
- <UnsupportedEditorPanel
594
- text="Can't display this tab"
595
- isReadOnly={true}
596
- />
597
- );
598
702
  }
703
+ return (
704
+ <UnsupportedEditorPanel
705
+ text="Can't display this tab"
706
+ isReadOnly={true}
707
+ />
708
+ );
599
709
  };
600
710
 
601
711
  return (
602
- <Panel>
603
- <PanelContent>{renderTestRunnerTab()}</PanelContent>
604
- </Panel>
712
+ <div
713
+ data-testid={LEGEND_STUDIO_TEST_ID.TEST_RUNNER}
714
+ className="panel global-test-runner"
715
+ >
716
+ <PanelHeader className="panel__header side-bar__header">
717
+ <div className="panel__header__title global-test-runner__header__title">
718
+ <div className="panel__header__title__content side-bar__header__title__content">
719
+ GLOBAL TEST RUNNER
720
+ </div>
721
+ </div>
722
+ </PanelHeader>
723
+ <div className="panel__header__tabs panel__header__test__runner__tabs">
724
+ {testRunnerTabs.map((tab) => (
725
+ <div
726
+ key={tab.value}
727
+ onClick={() => changeTab(tab.value)}
728
+ className={clsx('panel__header__tab', {
729
+ ['panel__header__tab--active']: tab.value === selectedTab,
730
+ })}
731
+ >
732
+ {tab.label}
733
+ </div>
734
+ ))}
735
+ </div>
736
+ <PanelContent className="side-bar__content">
737
+ {renderSelectedTab()}
738
+ </PanelContent>
739
+ </div>
605
740
  );
606
741
  },
607
742
  );