/**
* Copyright (c) 2020-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 {
ContextMenu,
PlusIcon,
ResizablePanel,
ResizablePanelGroup,
ResizablePanelSplitter,
ResizablePanelSplitterLine,
MenuContent,
MenuContentItem,
clsx,
FlaskIcon,
RunAllIcon,
RunErrorsIcon,
CheckCircleIcon,
TimesCircleIcon,
TestTubeIcon,
CustomSelectorInput,
Dialog,
RefreshIcon,
TimesIcon,
FilledWindowMaximizeIcon,
PlayIcon,
} from '@finos/legend-art';
import {
type ValueSpecification,
PrimitiveInstanceValue,
PrimitiveType,
PureMultiExecution,
resolveServiceQueryRawLambda,
} from '@finos/legend-graph';
import {
BasicValueSpecificationEditor,
instanceValue_setValue,
} from '@finos/legend-query-builder';
import {
filterByType,
guaranteeNonNullable,
prettyCONSTName,
} from '@finos/legend-shared';
import { flowResult } from 'mobx';
import { observer } from 'mobx-react-lite';
import { forwardRef, useEffect, useState } from 'react';
import type { ServiceTestSuiteState } from '../../../../../stores/editor/editor-state/element-editor-state/service/testable/ServiceTestableState.js';
import {
type KeyOption,
type ServiceTestSetupState,
type ServiceTestState,
ServiceValueSpecificationTestParameterState,
} from '../../../../../stores/editor/editor-state/element-editor-state/service/testable/ServiceTestEditorState.js';
import { TESTABLE_TEST_TAB } from '../../../../../stores/editor/editor-state/element-editor-state/testable/TestableEditorState.js';
import {
atomicTest_setId,
testAssertion_setId,
} from '../../../../../stores/graph-modifier/Testable_GraphModifierHelper.js';
import {
getTestableResultFromTestResult,
TESTABLE_RESULT,
} from '../../../../../stores/editor/sidebar-state/testable/GlobalTestRunnerState.js';
import { getTestableResultIcon } from '../../../side-bar/testable/GlobalTestRunner.js';
import {
ExternalFormatParameterEditorModal,
RenameModal,
TestAssertionEditor,
TestAssertionItem,
} from '../../testable/TestableSharedComponents.js';
import { LEGEND_STUDIO_TEST_ID } from '../../../../../__lib__/LegendStudioTesting.js';
import {
getContentTypeWithParamFromQuery,
type TestParamContentType,
} from '../../../../../stores/editor/utils/TestableUtils.js';
export const NewParameterModal = observer(
(props: { setupState: ServiceTestSetupState; isReadOnly: boolean }) => {
const { setupState, isReadOnly } = props;
const applicationStore = setupState.editorStore.applicationStore;
const currentOption = {
value: setupState.newParameterValueName,
label: setupState.newParameterValueName,
};
const options = setupState.newParamOptions;
const closeModal = (): void => setupState.setShowNewParameterModal(false);
const onChange = (val: { label: string; value: string } | null): void => {
if (val === null) {
setupState.setNewParameterValueName('');
} else if (val.value !== setupState.newParameterValueName) {
setupState.setNewParameterValueName(val.value);
}
};
return (
);
},
);
const ServiceTestParameterEditor = observer(
(props: {
isReadOnly: boolean;
paramState: ServiceValueSpecificationTestParameterState;
serviceTestState: ServiceTestState;
contentTypeParamPair: TestParamContentType | undefined;
}) => {
const { serviceTestState, paramState, isReadOnly, contentTypeParamPair } =
props;
const [showPopUp, setShowPopUp] = useState(false);
const setupState = serviceTestState.setupState;
const paramIsRequired =
paramState.varExpression.multiplicity.lowerBound > 0;
const type = contentTypeParamPair
? contentTypeParamPair.contentType
: (paramState.varExpression.genericType?.value.rawType.name ?? 'unknown');
const paramValue =
paramState.varExpression.genericType?.value.rawType === PrimitiveType.BYTE
? atob(
(paramState.valueSpec as PrimitiveInstanceValue)
.values[0] as string,
)
: ((paramState.valueSpec as PrimitiveInstanceValue)
.values[0] as string);
const openInPopUp = (): void => setShowPopUp(!showPopUp);
const closePopUp = (): void => setShowPopUp(false);
const updateParamValue = (val: string): void => {
if (paramState.valueSpec instanceof PrimitiveInstanceValue) {
instanceValue_setValue(
paramState.valueSpec,
paramState.varExpression.genericType?.value.rawType ===
PrimitiveType.BYTE
? btoa(val)
: val,
0,
setupState.editorStore.changeDetectionState.observerContext,
);
paramState.updateValueSpecification(paramState.valueSpec);
}
};
return (
{paramState.parameterValue.name}
<>
{contentTypeParamPair ? (
) : (
{
paramState.updateValueSpecification(val);
}}
graph={setupState.editorStore.graphManagerState.graph}
observerContext={
setupState.editorStore.changeDetectionState.observerContext
}
typeCheckOption={{
expectedType:
paramState.varExpression.genericType?.value.rawType ??
PrimitiveType.STRING,
}}
className="query-builder__parameters__value__editor"
resetValue={(): void => {
paramState.resetValueSpec();
}}
/>
)}
>
);
},
);
const ServiceTestSetupEditor = observer(
(props: { serviceTestState: ServiceTestState }) => {
const { serviceTestState } = props;
const setupState = serviceTestState.setupState;
const applicationStore = setupState.editorStore.applicationStore;
const test = serviceTestState.test;
const format = test.serializationFormat;
const selectedSerializationFormat = setupState.getSelectedFormatOption();
const options = setupState.options;
const keyOptions = setupState.keyOptions;
const selectedKeys = setupState.getSelectedKeyOptions();
const isReadOnly =
serviceTestState.suiteState.testableState.serviceEditorState.isReadOnly;
const onSerializationFormatChange = (
val: { label: string; value: string } | null,
): void => {
if (val === null) {
setupState.changeSerializationFormat(undefined);
} else if (val.value !== format) {
setupState.changeSerializationFormat(val.value);
}
};
const onKeyOptionChange = (val: KeyOption[]): void => {
setupState.addServiceTestAssertKeys(val.map((op) => op.value));
};
const addParameter = (): void => {
setupState.setShowNewParameterModal(true);
};
const generateParameterValues = (): void => {
setupState.generateTestParameterValues();
};
useEffect(() => {
setupState.syncWithQuery();
}, [setupState]);
return (
Serialization Format
Format to serialize execution result
{setupState.testState.testable.execution instanceof
PureMultiExecution && (
Keys
Specify keys for each test to run assertions against
selected env keys
)}
{setupState.parameterValueStates
.filter(
filterByType(ServiceValueSpecificationTestParameterState),
)
.map((paramState) => (
pair.param === paramState.parameterValue.name,
)}
/>
))}
{setupState.showNewParameterModal && (
)}
);
},
);
const TestAssertionsEditor = observer(
(props: { serviceTestState: ServiceTestState }) => {
const { serviceTestState } = props;
const isReadOnly =
serviceTestState.suiteState.testableState.serviceEditorState.isReadOnly;
const editorStore = serviceTestState.editorStore;
const selectedAsertionState = serviceTestState.selectedAsertionState;
const hideExplorer =
serviceTestState.test.assertions.length === 1 &&
serviceTestState.selectedAsertionState?.assertion.id ===
serviceTestState.test.assertions[0]?.id;
const addAssertion = (): void => serviceTestState.addAssertion();
const renameAssertion = (val: string): void =>
testAssertion_setId(
guaranteeNonNullable(serviceTestState.assertionToRename),
val,
);
const runTest = (): void => {
flowResult(serviceTestState.runTest()).catch(
editorStore.applicationStore.alertUnhandledError,
);
};
return (
{hideExplorer && selectedAsertionState ? (
) : (
{serviceTestState.assertionCount}
{serviceTestState.assertionPassed}
{serviceTestState.assertionFailed}
{serviceTestState.assertionEditorStates.map(
(assertionState) => (
),
)}
{serviceTestState.assertionToRename && (
serviceTestState.setAssertionToRename(undefined)
}
setValue={renameAssertion}
/>
)}
{selectedAsertionState && (
)}
)}
);
},
);
const ServiceTestEditor = observer(
(props: { serviceTestState: ServiceTestState }) => {
const { serviceTestState } = props;
const selectedTab = serviceTestState.selectedTab;
return (
{Object.values(TESTABLE_TEST_TAB).map((tab) => (
serviceTestState.setSelectedTab(tab)}
className={clsx('service-test-editor__tab', {
'service-test-editor__tab--active':
tab === serviceTestState.selectedTab,
})}
>
{prettyCONSTName(tab)}
))}
{selectedTab === TESTABLE_TEST_TAB.SETUP && (
)}
{selectedTab === TESTABLE_TEST_TAB.ASSERTION && (
)}
);
},
);
const ServiceTestDataContextMenu = observer(
forwardRef<
HTMLDivElement,
{
suiteState: ServiceTestSuiteState;
serviceTestState: ServiceTestState;
}
>(function TestContainerContextMenu(props, ref) {
const { suiteState, serviceTestState } = props;
const addTest = (): void => {
suiteState.addServiceTest();
};
const remove = (): void => suiteState.deleteTest(serviceTestState);
const rename = (): void =>
suiteState.setTestToRename(serviceTestState.test);
const runTest =
serviceTestState.editorStore.applicationStore.guardUnhandledError(() =>
flowResult(serviceTestState.runTest()),
);
return (
Run test
Rename
Delete
Add test
);
}),
);
const ServiceTestItem = observer(
(props: {
suiteState: ServiceTestSuiteState;
serviceTestState: ServiceTestState;
}) => {
const { serviceTestState, suiteState } = props;
const serviceTest = serviceTestState.test;
const isRunning = serviceTestState.runningTestAction.isInProgress;
const [isSelectedFromContextMenu, setIsSelectedFromContextMenu] =
useState(false);
const isReadOnly = suiteState.testableState.serviceEditorState.isReadOnly;
const openTest = (): void =>
suiteState.setSelectedTestState(serviceTestState);
const isActive = suiteState.selectedTestState?.test === serviceTest;
const _testableResult = getTestableResultFromTestResult(
serviceTestState.testResultState.result,
);
const testableResult = isRunning
? TESTABLE_RESULT.IN_PROGRESS
: _testableResult;
const resultIcon = getTestableResultIcon(testableResult);
const onContextMenuOpen = (): void => setIsSelectedFromContextMenu(true);
const onContextMenuClose = (): void => setIsSelectedFromContextMenu(false);
const runTest =
serviceTestState.editorStore.applicationStore.guardUnhandledError(() =>
flowResult(serviceTestState.runTest()),
);
return (
}
menuProps={{ elevation: 7 }}
onOpen={onContextMenuOpen}
onClose={onContextMenuClose}
>
);
},
);
export const ServiceTestsEditor = observer(
(props: { suiteState: ServiceTestSuiteState }) => {
const { suiteState } = props;
const editorStore = suiteState.editorStore;
const isReadOnly = suiteState.testableState.serviceEditorState.isReadOnly;
const addTest = (): void => suiteState.addServiceTest();
const runSuite = (): void => {
flowResult(suiteState.runSuite()).catch(
editorStore.applicationStore.alertUnhandledError,
);
};
const runFailingTests = (): void => {
flowResult(suiteState.runFailingTests()).catch(
editorStore.applicationStore.alertUnhandledError,
);
};
const renameTest = (val: string): void =>
atomicTest_setId(guaranteeNonNullable(suiteState.testToRename), val);
return (
{suiteState.testStates.map((testState) => (
))}
{suiteState.testToRename && (
suiteState.setTestToRename(undefined)}
setValue={renameTest}
/>
)}
{suiteState.selectedTestState && (
)}
);
},
);