/** * 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 { observer } from 'mobx-react-lite'; import type { PostValidationAssertionState, PostValidationParameterState, PostValidationState, ServicePostValidationsState, } from '../../../../stores/editor/editor-state/element-editor-state/service/ServicePostValidationState.js'; import { BlankPanelPlaceholder, ContextMenu, MenuContent, MenuContentItem, PURE_FunctionIcon, PanelContent, PanelFormTextField, PlusIcon, ResizablePanel, ResizablePanelGroup, ResizablePanelSplitter, ResizablePanelSplitterLine, TimesIcon, clsx, Panel, PanelHeader, PanelHeaderActions, PanelHeaderActionItem, PlayIcon, PanelTabs, } from '@finos/legend-art'; import type { PostValidation, PostValidationAssertionResult, PostValidationViolationsResultRow, } from '@finos/legend-graph'; import { forwardRef, useEffect, useState } from 'react'; import { serviceValidation_setASsertionId, serviceValidation_setDescription, } from '../../../../stores/graph-modifier/DSL_Service_GraphModifierHelper.js'; import { InlineLambdaEditor } from '@finos/legend-query-builder'; import { flowResult } from 'mobx'; import { useApplicationStore } from '@finos/legend-application'; const ServicePostValidationAssertionEditor = observer( (props: { assertionState: PostValidationAssertionState }) => { const { assertionState } = props; const servicePostValidationState = assertionState.postValidationState.servicePostValidationState; const serviceEditorState = servicePostValidationState.serviceEditorState; const postValState = assertionState.postValidationState; const hasParserError = postValState.hasParserError; const isReadOnly = serviceEditorState.isReadOnly; const assertion = assertionState.assertion; const errorMessage = assertion.id === '' ? `assertion ID can't be empty` : assertion.id.includes(' ') ? `assertion ID can't include spaced` : undefined; return (
Assertion
serviceValidation_setASsertionId(assertion, value ?? '') } errorMessage={errorMessage} />
); }, ); const ServicePostValidationParameterEditor = observer( (props: { paramState: PostValidationParameterState }) => { const { paramState } = props; const serviceEditorState = paramState.postValidationState.servicePostValidationState .serviceEditorState; const postValState = paramState.postValidationState; const hasParserError = postValState.hasParserError; const isReadOnly = serviceEditorState.isReadOnly; return (
Parameter
); }, ); const ServicePostValidationEditor = observer( (props: { postValidationState: PostValidationState }) => { const { postValidationState } = props; const serviceEditorState = postValidationState.servicePostValidationState.serviceEditorState; const editorStore = serviceEditorState.editorStore; const applicationStore = editorStore.applicationStore; const isReadOnly = serviceEditorState.isReadOnly; const validation = postValidationState.validation; const addParameter = (): void => { flowResult(postValidationState.addParameter()).catch( applicationStore.alertUnhandledError, ); }; const addAssertion = (): void => { flowResult(postValidationState.addAssertion()).catch( applicationStore.alertUnhandledError, ); }; useEffect(() => { flowResult(postValidationState.convertAssertionsLambdas()).catch( applicationStore.alertUnhandledError, ); flowResult(postValidationState.convertParameterLambdas()).catch( applicationStore.alertUnhandledError, ); }, [applicationStore, postValidationState]); return (
General
serviceValidation_setDescription(validation, value ?? '') } />
Parameters
{postValidationState.parametersState.map((aState) => ( ))} {!validation.parameters.length && ( )}
Assertions
{postValidationState.assertionStates.map((aState) => ( ))} {!validation.assertions.length && ( )}
); }, ); const PostValidationContextMenu = observer( forwardRef< HTMLDivElement, { _delete: () => void; add: () => void; } >(function TestContainerContextMenu(props, ref) { const { add, _delete } = props; return ( Delete Add Post Validation ); }), ); export const PostValidationItem = observer( (props: { idx: number; postValidation: PostValidation; validationState: ServicePostValidationsState; }) => { const { postValidation, validationState, idx } = props; const serviceEditorState = validationState.serviceEditorState; const applicationStore = useApplicationStore(); const isReadOnly = serviceEditorState.isReadOnly; const [isSelectedFromContextMenu, setIsSelectedFromContextMenu] = useState(false); const isActive = validationState.selectedPostValidationState?.validation === postValidation; const openVal = (): void => validationState.changeValidation(postValidation); const add = (): void => validationState.addValidation(); const _delete = (): void => validationState.deleteValidation(postValidation); const onContextMenuOpen = (): void => setIsSelectedFromContextMenu(true); const onContextMenuClose = (): void => setIsSelectedFromContextMenu(false); const runVal = (): void => { flowResult(validationState.runVal()).catch( applicationStore.alertUnhandledError, ); }; return ( } menuProps={{ elevation: 7 }} onOpen={onContextMenuOpen} onClose={onContextMenuClose} >
); }, ); const ServicePostValidationResultViewer = observer( (props: { result: PostValidationAssertionResult }) => { const { result } = props; const summaryType = result.violations.result.rows.length === 0 ? 'success' : 'fail'; return (
Assertion Result Summary ({summaryType})
Id: {result.id}
Message: {result.message}
Violations: {result.violations.result.rows.length}
{result.violations.result.rows.map( (row: PostValidationViolationsResultRow) => (
{row.values.map((value, valueIndex) => (
{result.violations.result.columns[valueIndex]}: {value}
))}
), )}
); }, ); const ServicePostValidationResultsViewer = observer( (props: { isInProgress: boolean; results: PostValidationAssertionResult[] | undefined; }) => { const { isInProgress, results } = props; return (
{results ? results.map((result) => ( )) : isInProgress ? 'Post validation(s) running...' : 'Run post validation(s) to see results'}
); }, ); // TOD: Currently editor is a very basic editor. Can improve with the following: // 1. generate parameters using the query parameters (verify that those parameter values are used for execution) // most likely the parameter values should be consistent with other parameter value shapes such as service tests // 2. wire up engine api calls to generate validation as well as run the validations export const ServicePostValidationsEditor = observer( (props: { validationState: ServicePostValidationsState }) => { const { validationState } = props; const serviceEditorState = validationState.serviceEditorState; const service = serviceEditorState.service; const isReadOnly = serviceEditorState.isReadOnly; enum POST_VALIDATION_TAB { SETUP = 'SETUP', ASSERTIONS = 'ASSERTIONS', } const [selectedTab, setSelectedTab] = useState(POST_VALIDATION_TAB.SETUP); const addPostValidation = (): void => validationState.addValidation(); useEffect(() => { validationState.init(); }, [validationState]); useEffect(() => { if (validationState.runningPostValidationAction.isInProgress) { setSelectedTab(POST_VALIDATION_TAB.ASSERTIONS); } }, [ validationState.runningPostValidationAction.isInProgress, POST_VALIDATION_TAB.ASSERTIONS, ]); return (
Post Validations
{service.postValidations.map((postValidation, _idx) => ( ))} {!service.postValidations.length && ( )}
() => setSelectedTab( POST_VALIDATION_TAB[ tab as keyof typeof POST_VALIDATION_TAB ], ) } tabClassName="panel__header__tab" /> {selectedTab === POST_VALIDATION_TAB.SETUP ? ( {validationState.selectedPostValidationState && ( )} {!service.postValidations.length && ( )} ) : null} {selectedTab === POST_VALIDATION_TAB.ASSERTIONS ? ( ) : null}
); }, );