/** * Copyright (c) 2026-present, Goldman Sachs * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { observer } from 'mobx-react-lite'; import { flowResult } from 'mobx'; import { forwardRef, useRef, useState } from 'react'; import { BlankPanelPlaceholder, clsx, ContextMenu, CustomSelectorInput, Dialog, MenuContent, MenuContentItem, Modal, ModalBody, ModalFooter, ModalFooterButton, ModalHeader, ModalTitle, Panel, PanelContent, PanelFormTextField, PanelHeader, PanelHeaderActionItem, PanelHeaderActions, PlayIcon, PlusIcon, ResizablePanel, ResizablePanelGroup, ResizablePanelSplitter, ResizablePanelSplitterLine, RunAllIcon, RunErrorsIcon, } from '@finos/legend-art'; import { CodeEditor } from '@finos/legend-lego/code-editor'; import { CODE_EDITOR_LANGUAGE } from '@finos/legend-code-editor'; import { type AvailabilityBarrierTest, type AvailabilityTestSuite, } from '@finos/legend-graph'; import { TESTABLE_RESULT, getTestableResultFromTestResult, } from '../../../../../stores/editor/sidebar-state/testable/GlobalTestRunnerState.js'; import { getTestableResultIcon } from '../../../side-bar/testable/GlobalTestRunner.js'; import { AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT, type AvailabilityTestState, type AvailabilityTestSuiteState, type AvailabilityTestableState, } from '../../../../../stores/editor/editor-state/element-editor-state/availability/testable/AvailabilityTestableState.js'; import { atomicTest_setId, testAssertion_setId, testSuite_setId, } from '../../../../../stores/graph-modifier/Testable_GraphModifierHelper.js'; import { RenameModal, TestAssertionResultViewer, } from '../../testable/TestableSharedComponents.js'; import { TEST_ASSERTION_TAB } from '../../../../../stores/editor/editor-state/element-editor-state/testable/TestAssertionState.js'; import { RelationElementEditor } from '../../data-editor/RelationElementsDataEditor.js'; import { validateTestableId } from '../../../../../stores/editor/utils/TestableUtils.js'; // ─── Format helpers ───────────────────────────────────────────────────────── interface FormatOption { label: string; value: AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT; } const FORMAT_OPTIONS: FormatOption[] = Object.values( AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT, ).map((format) => ({ label: format, value: format })); const FormatSelector = observer( (props: { value: AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT; onChange: (v: AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT) => void; disabled?: boolean; darkMode?: boolean; }) => { const { value, onChange, disabled, darkMode } = props; const selectedOption = FORMAT_OPTIONS.find((option) => option.value === value) ?? null; return (
{ if (option) { onChange(option.value); } }} value={selectedOption} placeholder="Select watermark format" isClearable={false} darkMode={darkMode ?? true} disabled={disabled} />
); }, ); // ─── Create Suite Modal ───────────────────────────────────────────────────── const CreateSuiteModal = observer( (props: { testableState: AvailabilityTestableState; onClose: () => void; }) => { const { testableState, onClose } = props; const editorStore = testableState.editorState.editorStore; const applicationStore = editorStore.applicationStore; const inputRef = useRef(null); const existingSuiteIds = testableState.availability.tests.map( (suite) => suite.id, ); const [suiteName, setSuiteName] = useState(undefined); const [testName, setTestName] = useState(undefined); const [format, setFormat] = useState( AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT.DEFAULT, ); const suiteError = validateTestableId(suiteName, existingSuiteIds); const testError = validateTestableId(testName, undefined); const isValid = Boolean(suiteName && !suiteError && testName && !testError); const create = (): void => { if (!suiteName || !testName) { return; } try { testableState.addSuite(format, suiteName, testName); onClose(); } catch (err) { applicationStore.notificationService.notifyError( err instanceof Error ? err.message : String(err), ); } }; return ( inputRef.current?.focus() }, paper: { classes: { root: 'search-modal__inner-container' } }, }} > setSuiteName(v ?? '')} errorMessage={suiteError} /> setTestName(v ?? '')} errorMessage={testError} />
Watermark Serialization Format
This format is fixed for the life of the test.
); }, ); // ─── Create Test Modal ────────────────────────────────────────────────────── const CreateTestModal = observer( (props: { suiteState: AvailabilityTestSuiteState; onClose: () => void }) => { const { suiteState, onClose } = props; const applicationStore = suiteState.editorStore.applicationStore; const inputRef = useRef(null); const existingIds = suiteState.suite.tests.map((test) => test.id); const [testName, setTestName] = useState(undefined); const [format, setFormat] = useState( AVAILABILITY_WATERMARK_SERIALIZATION_FORMAT.DEFAULT, ); const testError = validateTestableId(testName, existingIds); const isValid = Boolean(testName && !testError); const create = (): void => { if (!testName) { return; } try { suiteState.addTest(format, testName); onClose(); } catch (err) { applicationStore.notificationService.notifyError( err instanceof Error ? err.message : String(err), ); } }; return ( inputRef.current?.focus() }, paper: { classes: { root: 'search-modal__inner-container' } }, }} > setTestName(v ?? '')} errorMessage={testError} />
Watermark Serialization Format
This format is fixed for the life of the test.
); }, ); // ─── Availability expected-JSON editor ────────────────────────────────────── const ExpectedJsonEditor = observer( (props: { testState: AvailabilityTestState }) => { const { testState } = props; const isReadOnly = testState.isReadOnly; const canAdd = testState.parsedExpected !== undefined; const [editorRefreshNonce, setEditorRefreshNonce] = useState(0); const handleAdd = (): void => { testState.addExpectedEntry(); // Force a lightweight rerender/remount so Monaco reflects external // state updates immediately (without tab switching). setEditorRefreshNonce((val) => val + 1); }; return (
{ testState.setExpectedValue(val); }} hideGutter={true} rightActions={ } key={`availability-expected-json-${String(editorRefreshNonce)}`} />
); }, ); const AvailabilityAssertionEditor = observer( (props: { testState: AvailabilityTestState }) => { const { testState } = props; const assertionEditorState = testState.assertionEditorStates[0]; const selectedTab = assertionEditorState?.selectedTab ?? TEST_ASSERTION_TAB.EXPECTED; const changeTab = (tab: TEST_ASSERTION_TAB): void => assertionEditorState?.setSelectedTab(tab); return (
{testState.test.id}
Format
{testState.format}
{Object.values(TEST_ASSERTION_TAB).map((tab) => (
changeTab(tab)} className={clsx('testable-test-assertion-editor__header__tab', { 'testable-test-assertion-editor__header__tab--active': tab === selectedTab, })} > {tab === TEST_ASSERTION_TAB.EXPECTED ? 'Expected' : 'Result'}
))}
{selectedTab === TEST_ASSERTION_TAB.EXPECTED && ( )} {selectedTab === TEST_ASSERTION_TAB.RESULT && assertionEditorState && ( )}
); }, ); // ─── Test list item ───────────────────────────────────────────────────────── const AvailabilityTestContextMenu = observer( forwardRef< HTMLDivElement, { suiteState: AvailabilityTestSuiteState; test: AvailabilityBarrierTest; } >(function AvailabilityTestContextMenu(props, ref) { const { suiteState, test } = props; return ( suiteState.setTestToRename(test)}> Rename suiteState.deleteTest(test)}> Delete ); }), ); const AvailabilityTestItem = observer( (props: { suiteState: AvailabilityTestSuiteState; testState: AvailabilityTestState; }) => { const { suiteState, testState } = props; const isActive = suiteState.selectTestState === testState; const testResult = getTestableResultFromTestResult( testState.testResultState.result, ); const icon = getTestableResultIcon(testResult); return ( } >
suiteState.changeTest(testState.test)} tabIndex={-1} >
{icon}
{testState.test.id}
); }, ); // ─── Tests panel (right side) ─────────────────────────────────────────────── const AvailabilityTestsEditor = observer( (props: { suiteState: AvailabilityTestSuiteState; testableState: AvailabilityTestableState; }) => { const { suiteState, testableState } = props; const selectedTestState = suiteState.selectTestState; const isReadOnly = testableState.editorState.isReadOnly; return (
Tests
{!isReadOnly && ( )}
{suiteState.testStates.map((testState) => ( ))}
{selectedTestState ? ( ) : ( )}
); }, ); // ─── Suite editor (horizontal split) ──────────────────────────────────────── const AvailabilitySuiteEditor = observer( (props: { suiteState: AvailabilityTestSuiteState; testableState: AvailabilityTestableState; }) => { const { suiteState, testableState } = props; const isReadOnly = testableState.editorState.isReadOnly; return (
); }, ); // ─── Suite tab context menu ───────────────────────────────────────────────── const AvailabilitySuiteContextMenu = observer( forwardRef< HTMLDivElement, { suite: AvailabilityTestSuite; testableState: AvailabilityTestableState; } >(function AvailabilitySuiteContextMenu(props, ref) { const { suite, testableState } = props; return ( testableState.setSuiteToRename(suite)} > Rename testableState.deleteSuite(suite)}> Delete ); }), ); // ─── Top-level testable editor ────────────────────────────────────────────── export const AvailabilityTestableEditor = observer( (props: { testableState: AvailabilityTestableState }) => { const { testableState } = props; const availability = testableState.availability; const selectedSuiteState = testableState.selectedSuiteState; const isReadOnly = testableState.editorState.isReadOnly; const renameSuite = (val: string): void => { if (testableState.suiteToRename) { testSuite_setId(testableState.suiteToRename, val); } }; const renameTest = (val: string): void => { const testToRename = testableState.selectedSuiteState?.testToRename; if (testToRename) { atomicTest_setId(testToRename, val); } }; const renameAssertion = (val: string): void => { const assertionToRename = testableState.selectedSuiteState?.selectTestState?.assertionToRename; if (assertionToRename) { testAssertion_setId(assertionToRename, val); } }; return ( {availability.tests.length ? (
{availability.tests.map((suite) => { const isActive = selectedSuiteState?.suite === suite; const suiteResult = selectedSuiteState ? isActive ? selectedSuiteState.result : TESTABLE_RESULT.DID_NOT_RUN : TESTABLE_RESULT.DID_NOT_RUN; return (
testableState.changeSuite(suite)} className={clsx('service-test-suite-editor__tab', { 'service-test-suite-editor__tab--active': isActive, })} > } >
{getTestableResultIcon(suiteResult)}
{suite.id}
); })}
) : (
)} {!isReadOnly && ( testableState.setShowCreateSuiteModal(true) } title="Add Suite" > )}
{selectedSuiteState ? ( ) : ( testableState.setShowCreateSuiteModal(true)} clickActionType="add" tooltipText="Click to add availability test suite" /> )} {testableState.showCreateSuiteModal && ( testableState.setShowCreateSuiteModal(false)} /> )} {testableState.showCreateTestModal && selectedSuiteState && ( testableState.setShowCreateTestModal(false)} /> )} {testableState.suiteToRename && ( testableState.setSuiteToRename(undefined)} setValue={renameSuite} /> )} {selectedSuiteState?.testToRename && ( selectedSuiteState.setTestToRename(undefined) } setValue={renameTest} /> )} {selectedSuiteState?.selectTestState?.assertionToRename && ( selectedSuiteState.selectTestState?.setAssertionToRename( undefined, ) } setValue={renameAssertion} /> )}
); }, );