UNPKG

@finos/legend-extension-dsl-data-quality

Version:
469 lines (437 loc) 15.7 kB
/** * 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 { action, computed, flow, makeObservable, observable } from 'mobx'; import { type Class, type GraphFetchTree, type GraphManagerState, type PackageableElement, type RawLambda, type RootGraphFetchTree, type Runtime, CORE_PURE_PATH, FunctionType, GenericType, GenericTypeExplicitReference, getMilestoneTemporalStereotype, LambdaFunction, LambdaFunctionInstanceValue, MILESTONING_STEREOTYPE, Multiplicity, observe_ValueSpecification, PackageableElementExplicitReference, ParameterValue, PRIMITIVE_TYPE, buildRawLambdaFromLambdaFunction, VariableExpression, PROCESSING_DATE_MILESTONING_PROPERTY_NAME, BUSINESS_DATE_MILESTONING_PROPERTY_NAME, V1_RawValueSpecificationType, } from '@finos/legend-graph'; import { type EditorExtensionState, type EditorStore, ElementEditorState, } from '@finos/legend-application-studio'; import type { DataSpaceExecutionContext } from '@finos/legend-extension-dsl-data-space/graph'; import { DataQualityGraphFetchTreeState } from './DataQualityGraphFetchTreeState.js'; import { type DataQualityRootGraphFetchTree, DataQualityPropertyGraphFetchTree, } from '../../graph/metamodel/pure/packageableElements/data-quality/DataQualityGraphFetchTree.js'; import { buildDefaultDataQualityRootGraphFetchTree, buildGraphFetchTreeData, } from '../utils/DataQualityGraphFetchTreeUtil.js'; import { type GeneratorFn, ActionState, guaranteeNonNullable, guaranteeType, hashArray, isNonNullable, assertType, at, } from '@finos/legend-shared'; import { type GenericLegendApplicationStore } from '@finos/legend-application'; import { DataQualityResultState } from './DataQualityResultState.js'; import { DATA_QUALITY_HASH_STRUCTURE } from '../../graph/metamodel/DSL_DataQuality_HashUtils.js'; import { buildFilterConditionExpression, generateDefaultValueForPrimitiveType, processFilterLambda, QueryBuilderAdvancedWorkflowState, } from '@finos/legend-query-builder'; import { DataQualityQueryBuilderState } from './DataQualityQueryBuilderState.js'; import { getDataQualityPureGraphManagerExtension } from '../../graph-manager/protocol/pure/DSL_DataQuality_PureGraphManagerExtension.js'; import { dataQualityClassValidation_setDataQualityGraphFetchTree, dataQualityClassValidation_setFilter, } from '../../graph-manager/DSL_DataQuality_GraphModifierHelper.js'; import type { DataQualityClassValidationsConfiguration } from '../../graph/metamodel/pure/packageableElements/data-quality/DataQualityValidationConfiguration.js'; import { type DSL_DataQuality_LegendStudioPlugin_Extension } from '../DSL_DataQuality_LegendStudioPlugin_Extension.js'; export enum DATA_QUALITY_TAB { FILTER = 'Filter ', CONSTRAINTS_SELECTION = 'Constraints Selection', TRIAL_RUNS = 'Trial Runs', } export function buildExtensionState( editorStore: EditorStore, dataQualityState: DataQualityState, ): EditorExtensionState | undefined { const extensionStateBuilders = editorStore.pluginManager .getApplicationPlugins() .flatMap( (plugin) => ( plugin as DSL_DataQuality_LegendStudioPlugin_Extension ).getExtensionStates?.() ?? [], ); for (const stateBuilder of extensionStateBuilders) { const state = stateBuilder(editorStore, dataQualityState); if (state) { return state; } } return undefined; } export abstract class DataQualityState extends ElementEditorState { readonly applicationStore: GenericLegendApplicationStore; readonly graphManagerState: GraphManagerState; readonly loadDataSpacesState = ActionState.create(); readonly onExecutionContextChange?: | ((val: DataSpaceExecutionContext) => void) | undefined; readonly onRuntimeChange?: ((val: Runtime) => void) | undefined; readonly extensionState: EditorExtensionState | undefined; selectedTab: string; executionContext!: DataSpaceExecutionContext; dataQualityGraphFetchTreeState: DataQualityGraphFetchTreeState; structuralValidationsGraphFetchTreeState: DataQualityGraphFetchTreeState; showRuntimeSelector = false; dataQualityQueryBuilderState: DataQualityQueryBuilderState; resultState: DataQualityResultState; showStructuralValidations = false; showDateSelection = false; processingDate = generateDefaultValueForPrimitiveType( PRIMITIVE_TYPE.STRICTDATE, ) as string; businessDate = generateDefaultValueForPrimitiveType( PRIMITIVE_TYPE.STRICTDATE, ) as string; constructor(editorStore: EditorStore, element: PackageableElement) { super(editorStore, element); makeObservable(this, { selectedTab: observable, showRuntimeSelector: observable, dataQualityGraphFetchTreeState: observable, structuralValidationsGraphFetchTreeState: observable, dataQualityQueryBuilderState: observable, showStructuralValidations: observable, showDateSelection: observable, processingDate: observable, businessDate: observable, constraintsConfigurationElement: computed, setSelectedTab: action, setExecutionContext: action, setShowRuntimeSelector: action, initializeGraphFetchTreeState: action, initializeStructuralValidationsGraphFetchTreeState: action, setShowStructuralValidations: action, setShowDateSelection: action, updateElementOnClassChange: action, checkConstraintsSelectedAtNode: action, changeSourceElement: action, setProcessingDate: action, setBusinessDate: action, fetchStructuralValidations: flow, tabsToShow: computed, areNestedConstraintsSelected: computed, hashCode: computed, }); this.applicationStore = this.editorStore.applicationStore; this.graphManagerState = this.editorStore.graphManagerState; this.dataQualityQueryBuilderState = new DataQualityQueryBuilderState( this.editorStore.applicationStore, this.editorStore.graphManagerState, QueryBuilderAdvancedWorkflowState.INSTANCE, this.editorStore.applicationStore.config.options.queryBuilderConfig, undefined, ); this.selectedTab = DATA_QUALITY_TAB.CONSTRAINTS_SELECTION; this.dataQualityGraphFetchTreeState = new DataQualityGraphFetchTreeState( this, ); this.structuralValidationsGraphFetchTreeState = new DataQualityGraphFetchTreeState(this); this.resultState = new DataQualityResultState(this); this.extensionState = buildExtensionState(this.editorStore, this); } abstract get constraintsConfigurationElement(): PackageableElement; setShowStructuralValidations(val: boolean): void { this.showStructuralValidations = val; } setShowDateSelection(val: boolean): void { this.showDateSelection = val; } setProcessingDate(val: string): void { this.processingDate = val; } setBusinessDate(val: string): void { this.businessDate = val; } get tabsToShow(): string[] { const tabs: string[] = Object.values(DATA_QUALITY_TAB); const extensionTabGetters = this.editorStore.pluginManager .getApplicationPlugins() .flatMap( (plugin) => ( plugin as DSL_DataQuality_LegendStudioPlugin_Extension ).getAllTabs?.() ?? [], ); for (const tabGetter of extensionTabGetters) { tabs.push(...tabGetter()); } return tabs; } get currentClassMilestoningStrategy(): MILESTONING_STEREOTYPE | undefined { const currentclass = this.dataQualityQueryBuilderState.sourceClass; if (currentclass !== undefined) { return getMilestoneTemporalStereotype( currentclass, this.graphManagerState.graph, ); } return undefined; } get isCurrentClassMilestoned(): boolean { return this.currentClassMilestoningStrategy !== undefined; } get lambdaParameterValues(): ParameterValue[] { const parameters: ParameterValue[] = []; const currentClassMilestoningStrategy = this.currentClassMilestoningStrategy; if ( currentClassMilestoningStrategy === MILESTONING_STEREOTYPE.PROCESSING_TEMPORAL || currentClassMilestoningStrategy === MILESTONING_STEREOTYPE.BITEMPORAL ) { const parameterValue = new ParameterValue(); parameterValue.name = PROCESSING_DATE_MILESTONING_PROPERTY_NAME; parameterValue.value = { _type: V1_RawValueSpecificationType.CSTRICTDATE, value: this.processingDate, }; parameters.push(parameterValue); } if ( currentClassMilestoningStrategy === MILESTONING_STEREOTYPE.BUSINESS_TEMPORAL || currentClassMilestoningStrategy === MILESTONING_STEREOTYPE.BITEMPORAL ) { const parameterValue = new ParameterValue(); parameterValue.name = BUSINESS_DATE_MILESTONING_PROPERTY_NAME; parameterValue.value = { _type: V1_RawValueSpecificationType.CSTRICTDATE, value: this.businessDate, }; parameters.push(parameterValue); } return parameters; } *fetchStructuralValidations(): GeneratorFn<void> { const packagePath = this.constraintsConfigurationElement.path; const model = this.graphManagerState.graph; const rootGraphFetchTree = (yield getDataQualityPureGraphManagerExtension( this.graphManagerState.graphManager, ).fetchStructuralValidations(model, packagePath, {})) as RootGraphFetchTree; this.initializeStructuralValidationsGraphFetchTreeState( rootGraphFetchTree as DataQualityRootGraphFetchTree, ); } initializeFilterState(filterLambda: RawLambda | undefined) { if (!filterLambda) { return; } const filterSpec = observe_ValueSpecification( this.graphManagerState.graphManager.buildValueSpecification( this.graphManagerState.graphManager.serializeRawValueSpecification( filterLambda, ), this.graphManagerState.graph, ), this.dataQualityQueryBuilderState.observerContext, ); const compiledValueSpecification = guaranteeType( filterSpec, LambdaFunctionInstanceValue, `Can't build filter state: data quality filter only support lambda`, ); processFilterLambda( at(compiledValueSpecification.values, 0), this.dataQualityQueryBuilderState, ); } initializeGraphFetchTreeState( tree: DataQualityRootGraphFetchTree | undefined, ) { if (!tree) { return; } this.dataQualityGraphFetchTreeState.treeData = buildGraphFetchTreeData( this.editorStore, tree, true, true, false, ); } get areNestedConstraintsSelected(): boolean { const treeData = this.dataQualityGraphFetchTreeState.treeData; if (!treeData) { return false; } let constraintSelectedAtChildLevel = false; treeData.tree.subTrees.forEach((subtree) => { constraintSelectedAtChildLevel = constraintSelectedAtChildLevel || this.checkConstraintsSelectedAtNode(subtree); }); return constraintSelectedAtChildLevel; } checkConstraintsSelectedAtNode(tree: GraphFetchTree): boolean { assertType(tree, DataQualityPropertyGraphFetchTree); if (tree.constraints.length > 0) { return true; } let constraintSelectedAtChildLevel = false; tree.subTrees.forEach((subtree) => { constraintSelectedAtChildLevel = constraintSelectedAtChildLevel || this.checkConstraintsSelectedAtNode(subtree); }); return constraintSelectedAtChildLevel; } updateFilterElement = (): void => { const { filterState } = this.dataQualityQueryBuilderState; const filterConditionExpressions = filterState.rootIds .map((e) => guaranteeNonNullable(filterState.nodes.get(e))) .map((e) => buildFilterConditionExpression(filterState, e)) .filter(isNonNullable); if (!filterConditionExpressions.length) { dataQualityClassValidation_setFilter( this .constraintsConfigurationElement as DataQualityClassValidationsConfiguration, undefined, ); return; } const genericType = new GenericType( guaranteeNonNullable(this.dataQualityQueryBuilderState.sourceClass), ); const genericTypeReference = GenericTypeExplicitReference.create(genericType); const functionType = new FunctionType( PackageableElementExplicitReference.create( this.dataQualityQueryBuilderState.graphManagerState.graph.getType( CORE_PURE_PATH.ANY, ), ), Multiplicity.ONE, ); functionType.parameters.push( new VariableExpression( filterState.lambdaParameterName, Multiplicity.ONE, genericTypeReference, ), ); const lambdaFunction = new LambdaFunction(functionType); lambdaFunction.expressionSequence = filterConditionExpressions; dataQualityClassValidation_setFilter( this .constraintsConfigurationElement as DataQualityClassValidationsConfiguration, buildRawLambdaFromLambdaFunction( lambdaFunction, this.dataQualityQueryBuilderState.graphManagerState, ), ); }; initializeStructuralValidationsGraphFetchTreeState( tree: DataQualityRootGraphFetchTree | undefined, ) { if (!tree) { return; } this.structuralValidationsGraphFetchTreeState.treeData = buildGraphFetchTreeData(this.editorStore, tree, true, true, true); } get sideBarClassName(): string | undefined { return this.showRuntimeSelector ? 'data-quality-validation__setup__data-space--with-runtime' : 'data-quality-validation__setup__data-space'; } changeSourceElement(val: Class): void { this.dataQualityQueryBuilderState.changeSourceElement(val); this.structuralValidationsGraphFetchTreeState = new DataQualityGraphFetchTreeState(this); this.resultState = new DataQualityResultState(this); this.initializeGraphFetchTreeState( buildDefaultDataQualityRootGraphFetchTree( guaranteeNonNullable(this.dataQualityQueryBuilderState.sourceClass), ), ); this.updateElementOnClassChange(); } updateElementOnClassChange() { dataQualityClassValidation_setDataQualityGraphFetchTree( this .constraintsConfigurationElement as DataQualityClassValidationsConfiguration, guaranteeNonNullable(this.dataQualityGraphFetchTreeState.treeData).tree, ); dataQualityClassValidation_setFilter( this .constraintsConfigurationElement as DataQualityClassValidationsConfiguration, undefined, ); } setExecutionContext(val: DataSpaceExecutionContext): void { this.executionContext = val; } get isMappingReadOnly(): boolean { return false; } get isRuntimeReadOnly(): boolean { return false; } get isQuerySupported(): boolean { return true; } setSelectedTab(tab: string): void { this.selectedTab = tab; } setShowRuntimeSelector(val: boolean): void { this.showRuntimeSelector = val; } get hashCode(): string { return hashArray([ DATA_QUALITY_HASH_STRUCTURE.DATA_QUALITY_STATE, this.dataQualityGraphFetchTreeState, this.dataQualityQueryBuilderState.filterState, ]); } }