UNPKG

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

Version:
417 lines (384 loc) 14.6 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 { type ExportDataInfo, buildExecutionParameterValues, ExecutionPlanState, QUERY_BUILDER_EVENT, QueryBuilderTelemetryHelper, PARAMETER_SUBMIT_ACTION, } from '@finos/legend-query-builder'; import { action, flow, flowResult, makeObservable, observable } from 'mobx'; import { type GeneratorFn, assertErrorThrown, LogEvent, StopWatch, getContentTypeFileExtension, ActionState, ContentType, UnsupportedOperationError, } from '@finos/legend-shared'; import { type ExecutionResult, type RawExecutionPlan, GRAPH_MANAGER_EVENT, V1_DELEGATED_EXPORT_HEADER, EXECUTION_SERIALIZATION_FORMAT, } from '@finos/legend-graph'; import { getDataQualityPureGraphManagerExtension } from '../../graph-manager/protocol/pure/DSL_DataQuality_PureGraphManagerExtension.js'; import type { DataQualityRelationValidationConfigurationState } from './DataQualityRelationValidationConfigurationState.js'; import type { DataQualityRelationValidation } from '../../graph/metamodel/pure/packageableElements/data-quality/DataQualityValidationConfiguration.js'; import { downloadStream } from '@finos/legend-application'; export type DataQualityRelationResultCellDataType = | string | number | boolean | null | undefined; export interface DataQualityRelationRowDataType { [key: string]: DataQualityRelationResultCellDataType; } export interface DataQualityRelationResultCellCoordinate { rowIndex: number; colIndex: number; } export const DEFAULT_LIMIT = 1000; export class DataQualityRelationResultState { readonly dataQualityRelationValidationConfigurationState: DataQualityRelationValidationConfigurationState; readonly executionPlanState: ExecutionPlanState; readonly exportState = ActionState.create(); previewLimit = DEFAULT_LIMIT; isRunningValidation = false; isGeneratingPlan = false; allValidationsChecked = false; executionResult?: ExecutionResult | undefined; executionDuration?: number | undefined; latestRunHashCode?: string | undefined; validationRunPromise: Promise<ExecutionResult> | undefined = undefined; validationToRun: DataQualityRelationValidation | undefined; constructor( dataQualityRelationValidationState: DataQualityRelationValidationConfigurationState, ) { makeObservable(this, { executionResult: observable, previewLimit: observable, executionDuration: observable, latestRunHashCode: observable, validationRunPromise: observable, isGeneratingPlan: observable, isRunningValidation: observable, validationToRun: observable, allValidationsChecked: observable, setIsRunningValidation: action, setExecutionResult: action, setExecutionDuration: action, setPreviewLimit: action, setValidationRunPromise: action, setValidationToRun: action, handleRunValidation: action, handleExport: action, onToggleAllValidationsChecked: action, runValidation: flow, cancelValidation: flow, generatePlan: flow, exportData: flow, }); this.dataQualityRelationValidationConfigurationState = dataQualityRelationValidationState; this.executionPlanState = new ExecutionPlanState( this.dataQualityRelationValidationConfigurationState.editorStore.applicationStore, this.dataQualityRelationValidationConfigurationState.editorStore.graphManagerState, ); } setIsRunningValidation(val: boolean): void { this.isRunningValidation = val; } setExecutionResult(val: ExecutionResult | undefined): void { this.executionResult = val; } setExecutionDuration(val: number | undefined): void { this.executionDuration = val; } onToggleAllValidationsChecked(val: boolean): void { this.allValidationsChecked = !val; } setPreviewLimit(val: number): void { this.previewLimit = Math.max(1, val); } setValidationRunPromise(promise: Promise<ExecutionResult> | undefined): void { this.validationRunPromise = promise; } setValidationToRun(validation: DataQualityRelationValidation): void { this.validationToRun = validation; } get checkForStaleResults(): boolean { if ( this.latestRunHashCode !== this.dataQualityRelationValidationConfigurationState.hashCode ) { return true; } return false; } handleRunValidation() { const queryLambda = this.dataQualityRelationValidationConfigurationState .bodyExpressionSequence; const parameters = (queryLambda.parameters ?? []) as object[]; if (parameters.length) { this.dataQualityRelationValidationConfigurationState.parametersState.openModal( queryLambda, () => this.runValidation(), ); } else { flowResult(this.runValidation()).catch( this.dataQualityRelationValidationConfigurationState.editorStore .applicationStore.alertUnhandledError, ); } } handleExport(format: string) { const queryLambda = this.dataQualityRelationValidationConfigurationState .bodyExpressionSequence; const parameters = (queryLambda.parameters ?? []) as object[]; if (parameters.length) { this.dataQualityRelationValidationConfigurationState.parametersState.parameterStates = this.dataQualityRelationValidationConfigurationState.parametersState.build( queryLambda, ); this.dataQualityRelationValidationConfigurationState.parametersState.parameterValuesEditorState.open( (): Promise<void> => flowResult(this.exportData(format)).catch( this.dataQualityRelationValidationConfigurationState.editorStore .applicationStore.alertUnhandledError, ), PARAMETER_SUBMIT_ACTION.EXPORT, ); } else { flowResult(this.exportData(format)).catch( this.dataQualityRelationValidationConfigurationState.editorStore .applicationStore.alertUnhandledError, ); } } *runValidation(): GeneratorFn<void> { let promise; try { this.setIsRunningValidation(true); const currentHashCode = this.dataQualityRelationValidationConfigurationState.hashCode; const packagePath = this.dataQualityRelationValidationConfigurationState.validationElement .path; const model = this.dataQualityRelationValidationConfigurationState.editorStore .graphManagerState.graph; const stopWatch = new StopWatch(); promise = getDataQualityPureGraphManagerExtension( this.dataQualityRelationValidationConfigurationState.editorStore .graphManagerState.graphManager, ).execute(model, packagePath, { previewLimit: this.previewLimit, allValidationsChecked: this.allValidationsChecked, validationName: this.validationToRun?.name, lambdaParameterValues: buildExecutionParameterValues( this.dataQualityRelationValidationConfigurationState.parametersState .parameterStates, this.dataQualityRelationValidationConfigurationState.editorStore .graphManagerState, ), }); this.setValidationRunPromise(promise); const result = (yield promise) as ExecutionResult; if (this.validationRunPromise === promise) { this.setExecutionResult(result); this.latestRunHashCode = currentHashCode; this.setExecutionDuration(stopWatch.elapsed); } } catch (error) { if (this.validationRunPromise === promise) { assertErrorThrown(error); this.setExecutionResult(undefined); this.dataQualityRelationValidationConfigurationState.editorStore.applicationStore.logService.error( LogEvent.create(GRAPH_MANAGER_EVENT.EXECUTION_FAILURE), error, ); this.dataQualityRelationValidationConfigurationState.editorStore.applicationStore.notificationService.notifyError( error, ); } } finally { this.setIsRunningValidation(false); } } *cancelValidation(): GeneratorFn<void> { this.setIsRunningValidation(false); this.setValidationRunPromise(undefined); try { yield this.dataQualityRelationValidationConfigurationState.editorStore.graphManagerState.graphManager.cancelUserExecutions( true, ); } catch (error) { // Don't notify users about success or failure this.dataQualityRelationValidationConfigurationState.editorStore.applicationStore.logService.error( LogEvent.create(GRAPH_MANAGER_EVENT.EXECUTION_FAILURE), error, ); } } *generatePlan(debug: boolean): GeneratorFn<void> { const packagePath = this.dataQualityRelationValidationConfigurationState.validationElement .path; const model = this.dataQualityRelationValidationConfigurationState.editorStore .graphManagerState.graph; try { this.isGeneratingPlan = true; let rawPlan: RawExecutionPlan; const stopWatch = new StopWatch(); if (debug) { const debugResult = (yield getDataQualityPureGraphManagerExtension( this.dataQualityRelationValidationConfigurationState.editorStore .graphManagerState.graphManager, ).debugExecutionPlanGeneration(model, packagePath, { validationName: this.validationToRun?.name, allValidationsChecked: this.allValidationsChecked, previewLimit: this.previewLimit, })) as { plan: RawExecutionPlan; debug: string; }; rawPlan = debugResult.plan; this.executionPlanState.setDebugText(debugResult.debug); } else { QueryBuilderTelemetryHelper.logEvent_ExecutionPlanGenerationLaunched( this.dataQualityRelationValidationConfigurationState.editorStore .applicationStore.telemetryService, ); rawPlan = (yield getDataQualityPureGraphManagerExtension( this.dataQualityRelationValidationConfigurationState.editorStore .graphManagerState.graphManager, ).generatePlan(model, packagePath, { validationName: this.validationToRun?.name, allValidationsChecked: this.allValidationsChecked, previewLimit: this.previewLimit, })) as RawExecutionPlan; } stopWatch.record(); try { this.executionPlanState.setRawPlan(rawPlan); const plan = this.dataQualityRelationValidationConfigurationState.editorStore.graphManagerState.graphManager.buildExecutionPlan( rawPlan, this.dataQualityRelationValidationConfigurationState.editorStore .graphManagerState.graph, ); this.executionPlanState.initialize(plan); } catch { //do nothing } stopWatch.record(QUERY_BUILDER_EVENT.BUILD_EXECUTION_PLAN__SUCCESS); } catch (error) { assertErrorThrown(error); this.dataQualityRelationValidationConfigurationState.editorStore.applicationStore.logService.error( LogEvent.create(GRAPH_MANAGER_EVENT.EXECUTION_FAILURE), error, ); this.dataQualityRelationValidationConfigurationState.editorStore.applicationStore.notificationService.notifyError( error, ); } finally { this.isGeneratingPlan = false; } } getExportDataInfo(format: string): ExportDataInfo { switch (format) { case EXECUTION_SERIALIZATION_FORMAT.CSV: return { contentType: ContentType.TEXT_CSV, serializationFormat: EXECUTION_SERIALIZATION_FORMAT.CSV, }; default: throw new UnsupportedOperationError( `Unsupported TDS export type ${format}`, ); } } get exportDataFormatOptions(): string[] { return [EXECUTION_SERIALIZATION_FORMAT.CSV]; } *exportData(format: string): GeneratorFn<void> { try { this.exportState.inProgress(); const packagePath = this.dataQualityRelationValidationConfigurationState.validationElement .path; const model = this.dataQualityRelationValidationConfigurationState.editorStore .graphManagerState.graph; this.dataQualityRelationValidationConfigurationState.editorStore.applicationStore.notificationService.notifySuccess( `Export ${format} will run in background`, ); const exportData = this.getExportDataInfo(format); const contentType = exportData.contentType; const serializationFormat = exportData.serializationFormat; const result = (yield getDataQualityPureGraphManagerExtension( this.dataQualityRelationValidationConfigurationState.editorStore .graphManagerState.graphManager, ).exportData(model, packagePath, { serializationFormat, previewLimit: this.previewLimit, validationName: this.validationToRun?.name, allValidationsChecked: this.allValidationsChecked, lambdaParameterValues: buildExecutionParameterValues( this.dataQualityRelationValidationConfigurationState.parametersState .parameterStates, this.dataQualityRelationValidationConfigurationState.editorStore .graphManagerState, ), })) as Response; if (result.headers.get(V1_DELEGATED_EXPORT_HEADER) === 'true') { if (result.status === 200) { this.exportState.pass(); } else { this.exportState.fail(); } return; } downloadStream( result, `result.${getContentTypeFileExtension(contentType)}`, exportData.contentType, ) .then(() => { this.exportState.pass(); }) .catch((error) => { assertErrorThrown(error); }); } catch (error) { this.exportState.fail(); assertErrorThrown(error); this.dataQualityRelationValidationConfigurationState.editorStore.applicationStore.notificationService.notifyError( error, ); this.exportState.complete(); } } }