Skip to content

Commit

Permalink
Vivien/refactor evaluation (#91)
Browse files Browse the repository at this point in the history
  • Loading branch information
balzdur authored Aug 31, 2023
2 parents 2ff90c5 + 18e06f0 commit ec3b42a
Show file tree
Hide file tree
Showing 12 changed files with 88 additions and 99 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { LogicalOperatorLabel } from '@app-builder/components/Scenario/LogicalOp
import {
NewAstNode,
NewUndefinedAstNode,
type NodeEvaluation,
type Validation,
} from '@app-builder/models';
import {
type AstBuilder,
Expand All @@ -18,29 +18,29 @@ import { AstBuilderNode } from '../AstBuilderNode';

export interface RootOrWithAndViewModel {
orNodeId: string;
orValidation: NodeEvaluation;
orValidation: Validation;
ands: {
nodeId: string;
validation: NodeEvaluation;
validation: Validation;
children: EditorNodeViewModel[];
}[];
}

export function adaptRootOrWithAndViewModel(
astNode: EditorNodeViewModel
viewModel: EditorNodeViewModel
): RootOrWithAndViewModel | null {
if (astNode.funcName !== 'Or') {
if (viewModel.funcName !== 'Or') {
return null;
}
for (const child of astNode.children) {
for (const child of viewModel.children) {
if (child.funcName !== 'And') {
return null;
}
}
return {
orNodeId: astNode.nodeId,
orValidation: astNode.validation,
ands: astNode.children.map((andNode) => ({
orNodeId: viewModel.nodeId,
orValidation: viewModel.validation,
ands: viewModel.children.map((andNode) => ({
nodeId: andNode.nodeId,
validation: andNode.validation,
children: andNode.children,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
adaptAstNodeToViewModel,
adaptEditorIdentifierToViewModel,
type AstViewModel,
adaptLabelledAst,
adaptLabelledAstFromIdentifier,
type LabelledAst,
NewAstNode,
} from '@app-builder/models';
import {
Expand All @@ -21,16 +21,16 @@ export function OperandEditor({
builder: AstBuilder;
operandViewModel: OperandViewModel;
}) {
const identifiersOptions: AstViewModel[] = useMemo(
const identifiersOptions: LabelledAst[] = useMemo(
() => [
...builder.identifiers.databaseAccessors.map(
adaptEditorIdentifierToViewModel
adaptLabelledAstFromIdentifier
),
...builder.identifiers.payloadAccessors.map(
adaptEditorIdentifierToViewModel
adaptLabelledAstFromIdentifier
),
...builder.identifiers.customListAccessors.map(
adaptEditorIdentifierToViewModel
adaptLabelledAstFromIdentifier
),
],
[builder.identifiers]
Expand All @@ -39,21 +39,20 @@ export function OperandEditor({
(search: string) => {
if (!search) return identifiersOptions;
const constantNode = coerceToConstant(search);
return [...identifiersOptions, adaptAstNodeToViewModel(constantNode)];
return [...identifiersOptions, adaptLabelledAst(constantNode)];
},
[identifiersOptions]
);

const [inputValue, setInputValue] = useState(
adaptAstNodeToViewModel(adaptAstNodeFromEditorViewModel(operandViewModel))
.label
adaptLabelledAst(adaptAstNodeFromEditorViewModel(operandViewModel)).label
);

const items = getIdentifierOptions(inputValue);

const filteredItems = items.filter((item) => item.label.includes(inputValue));

const [selectedItem, setSelectedItem] = useState<AstViewModel | null>(null);
const [selectedItem, setSelectedItem] = useState<LabelledAst | null>(null);

return (
<Combobox.Root<(typeof items)[0]>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ const operatorEditorFunctions = [
'>',
'*',
'/',
'/',
'IsInList',
] as const;
type OperatorEditorFunctions = (typeof operatorEditorFunctions)[number];
Expand Down Expand Up @@ -95,7 +94,6 @@ export function useGetOperatorName() {
if (operatorName === '*') return '×';
if (operatorName === '/') return '÷';

if (operatorName === '/') return '÷';
if (operatorName === 'IsInList') return t('scenarios:operator.is_in');

// eslint-disable-next-line no-restricted-properties
Expand Down
4 changes: 2 additions & 2 deletions packages/app-builder/src/components/Edit/EditAstNode.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
adaptAstNodeToViewModelFromIdentifier,
adaptLabelledAstFromAllIdentifiers,
type AstNode,
NewUndefinedAstNode,
} from '@app-builder/models';
Expand Down Expand Up @@ -104,7 +104,7 @@ const EditOperand = forwardRef<
const editorIdentifier = useEditorIdentifiers();
const getIdentifierOptions = useGetIdentifierOptions();
const selectedItem = value
? adaptAstNodeToViewModelFromIdentifier(value, editorIdentifier)
? adaptLabelledAstFromAllIdentifiers(value, editorIdentifier)
: null;

const [inputValue, setInputValue] = useState(selectedItem?.label ?? '');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
adaptAstNodeToViewModelFromIdentifier,
adaptLabelledAstFromAllIdentifiers,
type AstNode,
} from '@app-builder/models';
import { useEditorIdentifiers } from '@app-builder/services/editor';
Expand All @@ -13,11 +13,8 @@ interface CustomListProps {
}

export function Identifier({ node, isRoot }: CustomListProps) {
const editorIdentifier = useEditorIdentifiers();
const viewModel = adaptAstNodeToViewModelFromIdentifier(
node,
editorIdentifier
);
const allIdentifiers = useEditorIdentifiers();
const viewModel = adaptLabelledAstFromAllIdentifiers(node, allIdentifiers);
return (
<Condition.Container isRoot={isRoot}>
<Condition.Item isRoot={isRoot}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
adaptAstNodeToViewModelFromIdentifier,
adaptLabelledAstFromAllIdentifiers,
type AstNode,
} from '@app-builder/models';
import { useEditorIdentifiers } from '@app-builder/services/editor';
Expand All @@ -21,10 +21,7 @@ function format(label: string) {

export function Payload({ node, isRoot }: PayloadProps) {
const editorIdentifier = useEditorIdentifiers();
const viewModel = adaptAstNodeToViewModelFromIdentifier(
node,
editorIdentifier
);
const viewModel = adaptLabelledAstFromAllIdentifiers(node, editorIdentifier);
const { tooltip, inline } = format(viewModel.label);
return (
<Condition.Container isRoot={isRoot}>
Expand Down
19 changes: 7 additions & 12 deletions packages/app-builder/src/models/ast-view-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,27 @@ import {
getIdentifiersFromAstNode,
} from './identifier';

export interface AstViewModel {
export interface LabelledAst {
label: string;
tooltip: string;
astNode: AstNode;
}

export function adaptAstNodeToViewModel(astNode: AstNode): AstViewModel {
export function adaptLabelledAst(astNode: AstNode): LabelledAst {
return {
label: getAstNodeDisplayName(astNode),
tooltip: '',
astNode,
};
}

export function adaptAstNodeToViewModelFromIdentifier(
export function adaptLabelledAstFromAllIdentifiers(
astNode: AstNode,
identifiers: EditorIdentifiersByType
): AstViewModel {
): LabelledAst {
const identifier = getIdentifiersFromAstNode(astNode, identifiers);
if (identifier) {
return adaptEditorIdentifierToViewModel(identifier);
return adaptLabelledAstFromIdentifier(identifier);
}
return {
label: getAstNodeDisplayName(astNode),
Expand All @@ -40,20 +40,15 @@ export function adaptAstNodeToViewModelFromIdentifier(
};
}

export function adaptEditorIdentifierToViewModel(
export function adaptLabelledAstFromIdentifier(
identifier: EditorIdentifier
): AstViewModel {
): LabelledAst {
return {
label: identifier.name,
tooltip: identifier.description,
astNode: identifier.node,
};
}
export function adaptAstViewModelToAstNode(
astViewModel: AstViewModel
): AstNode {
return astViewModel.astNode;
}

function getConstantDisplayName(constant: ConstantType) {
if (constant === null) return '';
Expand Down
62 changes: 29 additions & 33 deletions packages/app-builder/src/models/scenario-validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
} from '@marble-api';
import * as R from 'remeda';

import type { AstNode, ConstantType } from './ast-node';
import type { ConstantType } from './ast-node';

export type EvaluationErrorCode =
| 'UNEXPECTED_ERROR'
Expand Down Expand Up @@ -34,37 +34,30 @@ export function isUndefinedFunctionError(evaluationError: {
return evaluationError.error === 'UNDEFINED_FUNCTION';
}

interface CommonNodeEvaluation {
returnValue?: ConstantType;
export interface NodeEvaluation {
returnValue: ConstantType;
errors: EvaluationError[] | null;
children: NodeEvaluation[];
namedChildren: Record<string, NodeEvaluation>;
}

interface ValidNodeEvaluation extends CommonNodeEvaluation {
interface ValidationSuccess {
state: 'valid';
}

interface PendingNodeEvaluation extends CommonNodeEvaluation {
interface PendingValidation {
state: 'pending';
}

interface InvalidNodeEvaluation extends CommonNodeEvaluation {
state: 'invalid';
interface ValidationFailure {
state: 'fail';
errors: EvaluationError[];
}

export function NewPendingNodeEvaluation(ast: AstNode): PendingNodeEvaluation {
return {
state: 'pending',
children: ast.children.map(NewPendingNodeEvaluation),
namedChildren: R.mapValues(ast.namedChildren, NewPendingNodeEvaluation),
};
}

export type NodeEvaluation =
| ValidNodeEvaluation
| PendingNodeEvaluation
| InvalidNodeEvaluation;
export type Validation =
| ValidationSuccess
| PendingValidation
| ValidationFailure;

export interface ScenarioValidation {
errors: string[];
Expand All @@ -82,22 +75,25 @@ function adaptEvaluationError(dto: EvaluationErrorDto): EvaluationError {
}

function adaptNodeEvaluation(dto: NodeEvaluationDto): NodeEvaluation {
const commonNodeEvaluation = {
return {
returnValue: dto.return_value,
errors: dto.errors ? dto.errors.map(adaptEvaluationError) : null,
children: dto.children ? dto.children.map(adaptNodeEvaluation) : [],
namedChildren: dto.named_children
? R.mapValues(dto.named_children, adaptNodeEvaluation)
: {},
};
if (dto.errors === null) {
return { ...commonNodeEvaluation, state: 'pending' };
} else if (dto.errors.length === 0) {
return { ...commonNodeEvaluation, state: 'valid' };
}

export function adaptValidation(evaluation: NodeEvaluation): Validation {
if (evaluation.errors === null) {
return { state: 'pending' };
} else if (evaluation.errors.length === 0) {
return { state: 'valid' };
} else {
return {
...commonNodeEvaluation,
state: 'invalid',
errors: dto.errors.map(adaptEvaluationError),
state: 'fail',
errors: evaluation.errors,
};
}
}
Expand All @@ -112,29 +108,29 @@ export function adaptScenarioValidation(
};
}

type NodeEvaluationErrors = NodeEvaluation & { path: string };
type ValidationErrors = Validation & { path: string };

export function adaptNodeEvaluationErrors(
export function adaptValidationErrors(
path: string,
evaluation: NodeEvaluation
): NodeEvaluationErrors[] {
): ValidationErrors[] {
const childrenPathErrors = R.pipe(
evaluation.children,
R.map.indexed((child, index) => {
return adaptNodeEvaluationErrors(`${path}.children.${index}`, child);
return adaptValidationErrors(`${path}.children.${index}`, child);
}),
R.flatten()
);

const namedChildrenPathErrors = R.pipe(
R.toPairs(evaluation.namedChildren),
R.flatMap(([key, child]) => {
return adaptNodeEvaluationErrors(`${path}.namedChildren.${key}`, child);
return adaptValidationErrors(`${path}.namedChildren.${key}`, child);
})
);

return [
{ path, ...evaluation },
{ ...adaptValidation(evaluation), path },
...childrenPathErrors,
...namedChildrenPathErrors,
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
} from '@app-builder/components';
import { EditAstNode, RootOrOperator } from '@app-builder/components/Edit';
import { setToastMessage } from '@app-builder/components/MarbleToaster';
import { adaptNodeEvaluationErrors, type AstNode } from '@app-builder/models';
import { adaptValidationErrors, type AstNode } from '@app-builder/models';
import { EditRule } from '@app-builder/routes/ressources/scenarios/$scenarioId/$iterationId/rules/$ruleId/edit';
import { DeleteRule } from '@app-builder/routes/ressources/scenarios/$scenarioId/$iterationId/rules/delete';
import {
Expand Down Expand Up @@ -154,12 +154,12 @@ export default function RuleEdit() {
const { setError } = formMethods;
const getNodeEvaluationErrorMessage = useGetNodeEvaluationErrorMessage();
useEffect(() => {
const allEvaluationErrors = adaptNodeEvaluationErrors(
const allEvaluationErrors = adaptValidationErrors(
'astNode',
ruleValidation
);
allEvaluationErrors.forEach((flattenNodeEvaluationErrors) => {
if (flattenNodeEvaluationErrors.state === 'invalid') {
if (flattenNodeEvaluationErrors.state === 'fail') {
const firstError = flattenNodeEvaluationErrors.errors[0];
//@ts-expect-error path is a string
setError(flattenNodeEvaluationErrors.path, {
Expand Down
Loading

0 comments on commit ec3b42a

Please sign in to comment.