@finos/legend-extension-dsl-data-quality
Version:
Legend extension for Data Quality
232 lines (202 loc) • 7.65 kB
text/typescript
/**
* 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 { PanelDisplayState } from '@finos/legend-art';
import type { DataQualityRelationValidationConfigurationState } from './DataQualityRelationValidationConfigurationState.js';
import { DataQualityRelationValidationState } from './DataQualityRelationValidationState.js';
import {
ActionState,
assertErrorThrown,
hashValue,
type GeneratorFn,
} from '@finos/legend-shared';
import { action, computed, flow, makeObservable, observable } from 'mobx';
import { getDataQualityPureGraphManagerExtension } from '../../graph-manager/protocol/pure/DSL_DataQuality_PureGraphManagerExtension.js';
import { buildExecutionParameterValues } from '@finos/legend-query-builder';
import { DataQualityRelationValidation } from '../../graph-manager/index.js';
import type { RawLambda } from '@finos/legend-graph';
export enum SuggestedValidationsFilter {
ALL,
NEW,
MODIFICATIONS,
}
export enum SuggestionType {
NEW = 'NEW',
EDIT = 'EDIT',
APPLIED = 'APPLIED',
}
export class SuggestedValidationsState {
readonly parentState: DataQualityRelationValidationConfigurationState;
readonly suggestionPanelState: PanelDisplayState;
readonly fetchState = ActionState.create();
suggestedValidations: DataQualityRelationValidationState[] = [];
filter: SuggestedValidationsFilter = SuggestedValidationsFilter.ALL;
constructor(parentState: DataQualityRelationValidationConfigurationState) {
makeObservable(this, {
suggestedValidations: observable,
filter: observable,
onClickSuggestValidations: action,
onFilterChange: action,
fetchValidationSuggestions: flow,
existingValidationsByName: computed,
selectedSuggestions: computed,
filteredSuggestions: computed,
});
this.parentState = parentState;
this.suggestionPanelState = new PanelDisplayState({
initial: 0,
default: 300,
snap: 100,
});
}
get existingValidationsByName(): Map<string, string> {
return new Map(
this.parentState.validationStates.map((s) => [
s.relationValidation.name,
hashValue(s.lambdaString),
]),
);
}
get filteredSuggestions(): DataQualityRelationValidationState[] {
return this.suggestedValidations.filter((s) => {
const type = this.getSuggestionType(s);
switch (this.filter) {
case SuggestedValidationsFilter.NEW:
return type === SuggestionType.NEW;
case SuggestedValidationsFilter.MODIFICATIONS:
return type === SuggestionType.EDIT;
default:
return type === SuggestionType.NEW || type === SuggestionType.EDIT;
}
});
}
get selectedSuggestions(): DataQualityRelationValidationState[] {
return this.filteredSuggestions.filter((e) => e.isSelected);
}
getSuggestionType(
suggestion: DataQualityRelationValidationState,
): SuggestionType {
const name = suggestion.relationValidation.name;
const existingHash = this.existingValidationsByName.get(name);
// TODO: name does not exist already treated as new rule probably needs to change if exact assertion exist, because then the suggestion is unecessary?
if (existingHash === undefined) {
return SuggestionType.NEW;
}
return existingHash !== hashValue(suggestion.lambdaString)
? SuggestionType.EDIT
: SuggestionType.APPLIED;
}
onClickSuggestValidations = () => {
if (!this.suggestionPanelState.isOpen) {
this.suggestionPanelState.toggle();
}
if (!this.fetchState.isInProgress) {
this.fetchValidationSuggestions();
}
};
onFilterChange = (newFilter: SuggestedValidationsFilter) => {
this.filter = newFilter;
};
*fetchValidationSuggestions(): GeneratorFn<void> {
this.fetchState.inProgress();
try {
// Parse the response into proper RawLambda objects
const packagePath = this.parentState.validationElement.path;
const model = this.parentState.editorStore.graphManagerState.graph;
const extension = getDataQualityPureGraphManagerExtension(
this.parentState.editorStore.graphManagerState.graphManager,
);
const options = {
lambdaParameterValues: buildExecutionParameterValues(
this.parentState.parametersState.parameterStates,
this.parentState.editorStore.graphManagerState,
),
};
const result = (yield extension.fetchValidationSuggestions(
model,
packagePath,
options,
)) as DataQualityRelationValidation[];
const suggestions: DataQualityRelationValidation[] = result.map(
(suggestion) => {
const lambda =
this.parentState.editorStore.graphManagerState.graphManager.buildRawValueSpecification(
suggestion.assertion,
this.parentState.editorStore.graphManagerState.graph,
) as RawLambda;
const validation = new DataQualityRelationValidation(
suggestion.name,
lambda,
);
if (suggestion.type) {
validation.type = suggestion.type;
}
return validation;
},
);
// Create validation states
const suggestedValidations: DataQualityRelationValidationState[] =
suggestions.map(
(validation) =>
new DataQualityRelationValidationState(
validation,
this.parentState.editorStore,
),
);
// Convert to grammar strings for display
const lambdas = new Map<string, RawLambda>();
suggestedValidations.forEach((validationState) => {
lambdas.set(
validationState.lambdaId,
validationState.relationValidation.assertion,
);
});
suggestedValidations.forEach((validationState) => {
validationState.initializeWithColumns(
this.parentState.relationTypeMetadata.columns,
);
});
const isolatedLambdas =
(yield this.parentState.editorStore.graphManagerState.graphManager.lambdasToPureCode(
lambdas,
)) as Map<string, string>;
isolatedLambdas.forEach((grammarText, key) => {
const validationState = suggestedValidations.find(
(state) => state.lambdaId === key,
);
if (validationState) {
validationState.setLambdaString(
validationState.extractLambdaString(grammarText),
);
}
});
suggestedValidations.forEach((validationState) => {
if (validationState.dataQualityValidationLambdaFormState) {
const description =
validationState.dataQualityValidationLambdaFormState.getDescription();
validationState.relationValidation.description = description;
}
});
this.suggestedValidations = suggestedValidations;
this.fetchState.pass();
} catch (error) {
assertErrorThrown(error);
this.fetchState.fail();
this.parentState.editorStore.applicationStore.notificationService.notifyError(
error,
);
}
}
}