Skip to content

Commit a242536

Browse files
balzdurgh-zoe-cade
andauthored
feat(validation): better evaluation errors (#226)
* feat: better evaluation errors (#211) * feat: disable activate button if errors (#214) * feat(ScenarioValidationError): aggregate errors (#213) * feat(ScenarioValidation): remove error code from error message * refactor(EvaluationError): use code * feat(ScenarioValidationError): aggregate errors * refactor(evaluation): refactor evaluation errors (#216) * refactor(evaluation): refactor evaluation errors * misc fixes --------- Co-authored-by: Zoé Cadé <[email protected]> --------- Co-authored-by: Zoé Cadé <[email protected]>
1 parent 23f526a commit a242536

File tree

19 files changed

+379
-82
lines changed

19 files changed

+379
-82
lines changed

packages/app-builder/public/locales/en/common.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"errors.data.duplicate_field_name": "A field with this name already exist",
1313
"errors.data.duplicate_table_name": "A table with this name already exist",
1414
"errors.data.duplicate_link_name": "A link with this name already exist",
15-
"errors.draft.invalid": "Invalid draft. Please check your draft for error messages, fix them and try again.",
15+
"errors.draft.invalid": "This draft can't be published because it contains errors.",
1616
"cancel": "Cancel",
1717
"save": "Save",
1818
"delete": "Delete",

packages/app-builder/public/locales/en/scenarios.json

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -99,18 +99,37 @@
9999
"validation.decision.score_review_threshold_required": "Required",
100100
"validation.decision.score_reject_threshold_required": "Required",
101101
"validation.decision.score_reject_review_thresholds_missmatch": "Reject threshold must be greater than review threshold",
102-
"validation.evaluation_error.unknown_function": "required",
102+
"validation.evaluation_error.undefined_function_one": "required",
103+
"validation.evaluation_error.undefined_function_other": "{{count}} required",
103104
"validation.evaluation_error.wrong_number_of_arguments": "wrong number of arguments",
104-
"validation.evaluation_error.missing_named_argument": "missing named argument",
105-
"validation.evaluation_error.arguments_must_be_int_or_float": "arguments must be an integer or a float",
106-
"validation.evaluation_error.argument_must_be_integer": "argument must be an integer",
107-
"validation.evaluation_error.argument_must_be_string": "argument must be a string",
108-
"validation.evaluation_error.argument_must_be_boolean": "argument must be a boolean",
109-
"validation.evaluation_error.argument_must_be_list": "argument must be a list",
110-
"validation.evaluation_error.argument_must_be_convertible_to_duration": "argument must be a duration",
111-
"validation.evaluation_error.argument_must_be_time": "argument must be a time",
112-
"validation.evaluation_error.argument_required": "argument is required",
113-
"validation.evaluation_error.function_error": "function is incorrect",
105+
"validation.evaluation_error.missing_named_argument_one": "missing named argument",
106+
"validation.evaluation_error.missing_named_argument_other": "{{count}} missing named arguments",
107+
"validation.evaluation_error.arguments_must_be_int_or_float_one": "argument must be an integer or a float",
108+
"validation.evaluation_error.arguments_must_be_int_or_float_other": "{{count}} arguments must be integers or floats",
109+
"validation.evaluation_error.arguments_must_be_int_float_or_time_one": "argument must be an integer, a float or a time",
110+
"validation.evaluation_error.arguments_must_be_int_float_or_time_other": "{{count}} arguments must be integers, floats or times",
111+
"validation.evaluation_error.argument_must_be_integer_one": "argument must be an integer",
112+
"validation.evaluation_error.argument_must_be_integer_other": "{{count}} arguments must be integers",
113+
"validation.evaluation_error.argument_must_be_string_one": "argument must be a string",
114+
"validation.evaluation_error.argument_must_be_string_other": "{{count}} arguments must be strings",
115+
"validation.evaluation_error.argument_must_be_boolean_one": "argument must be a boolean",
116+
"validation.evaluation_error.argument_must_be_boolean_other": "{{count}} arguments must be booleans",
117+
"validation.evaluation_error.argument_must_be_list_one": "argument must be a list",
118+
"validation.evaluation_error.argument_must_be_list_other": "{{count}} arguments must be lists",
119+
"validation.evaluation_error.argument_must_be_convertible_to_duration_one": "argument must be a duration",
120+
"validation.evaluation_error.argument_must_be_convertible_to_duration_other": "{{count}} arguments must be durations",
121+
"validation.evaluation_error.argument_must_be_time_one": "argument must be a time",
122+
"validation.evaluation_error.argument_must_be_time_other": "{{count}} arguments must be times",
123+
"validation.evaluation_error.argument_required_one": "argument is required",
124+
"validation.evaluation_error.argument_required_other": "{{count}} arguments are required",
125+
"validation.evaluation_error.argument_invalid_type_one": "argument has an invalid type",
126+
"validation.evaluation_error.argument_invalid_type_other": "{{count}} arguments have invalid types",
127+
"validation.evaluation_error.list_not_found_one": "list not found",
128+
"validation.evaluation_error.list_not_found_other": "{{count}} lists not found",
129+
"validation.evaluation_error.field_not_found_one": "field not found",
130+
"validation.evaluation_error.field_not_found_other": "{{count}} fields not found",
131+
"validation.evaluation_error.function_error_one": "function contains errors",
132+
"validation.evaluation_error.function_error_other": "{{count}} functions contain error",
114133
"edit_aggregation.title": "Create a variable",
115134
"edit_aggregation.subtitle": "From Marble database",
116135
"edit_aggregation.label_title": "Variable name",

packages/app-builder/src/components/Scenario/AstBuilder/AstBuilderNode/AggregationEdit/EditFilters.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import { scenarioI18n } from '@app-builder/components/Scenario';
2+
import { ScenarioValidationError } from '@app-builder/components/Scenario/ScenarioValidationError';
23
import { NewUndefinedAstNode } from '@app-builder/models';
34
import {
45
adaptEditorNodeViewModel,
56
type AstBuilder,
67
} from '@app-builder/services/editor/ast-editor';
8+
import {
9+
adaptEvaluationErrorViewModels,
10+
useGetNodeEvaluationErrorMessage,
11+
} from '@app-builder/services/validation';
712
import { Button } from '@ui-design-system';
813
import { Plus } from '@ui-icons';
914
import { useTranslation } from 'react-i18next';
@@ -36,6 +41,7 @@ export const EditFilters = ({
3641
value: FilterViewModel[];
3742
}) => {
3843
const { t } = useTranslation(scenarioI18n);
44+
const getNodeEvaluationErrorMessage = useGetNodeEvaluationErrorMessage();
3945

4046
const filteredDataModalFieldOptions = aggregatedField?.tableName
4147
? dataModelFieldOptions.filter(
@@ -80,6 +86,9 @@ export const EditFilters = ({
8086
<div>
8187
<div className="flex flex-col gap-2">
8288
{value.map((filter, filterIndex) => {
89+
const valueErrorMessages = adaptEvaluationErrorViewModels(
90+
filter.value.errors
91+
).map((error) => getNodeEvaluationErrorMessage(error));
8392
return (
8493
<div key={filterIndex}>
8594
<div className="flex flex-row items-center gap-1">
@@ -120,6 +129,13 @@ export const EditFilters = ({
120129
{filter.errors.filter.length > 0 && (
121130
<ErrorMessage errors={filter.errors.filter} />
122131
)}
132+
<div className="mt-2 flex flex-row flex-wrap gap-2">
133+
{valueErrorMessages.map((error) => (
134+
<ScenarioValidationError key={error}>
135+
{error}
136+
</ScenarioValidationError>
137+
))}
138+
</div>
123139
</div>
124140
);
125141
})}

packages/app-builder/src/components/Scenario/AstBuilder/AstBuilderNode/Operand/Operand.tsx

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@ export const computeOperandErrors = (
2626
viewModel: EditorNodeViewModel
2727
): EvaluationError[] => {
2828
if (viewModel.funcName && functionNodeNames.includes(viewModel.funcName)) {
29-
return hasNestedErrors(viewModel)
30-
? [{ error: 'FUNCTION_ERROR', message: 'function has error' }]
31-
: [];
29+
return viewModel.errors.filter((error) => error.argumentName === undefined);
3230
} else {
3331
return [
3432
...viewModel.errors,
@@ -38,13 +36,6 @@ export const computeOperandErrors = (
3836
}
3937
};
4038

41-
function hasNestedErrors(viewModel: EditorNodeViewModel): boolean {
42-
if (viewModel.errors.length > 0) return true;
43-
if (viewModel.children.some(hasNestedErrors)) return true;
44-
if (Object.values(viewModel.namedChildren).some(hasNestedErrors)) return true;
45-
return false;
46-
}
47-
4839
export function isEditableOperand(node: AstNode): boolean {
4940
return (
5041
isConstant(node) ||

packages/app-builder/src/components/Scenario/AstBuilder/AstBuilderNode/TwoOperandsLine/TwoOperandsLine.tsx

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
import { ScenarioValidationError } from '@app-builder/components/Scenario/ScenarioValidatioError';
2-
import { type EvaluationError } from '@app-builder/models';
1+
import { ScenarioValidationError } from '@app-builder/components/Scenario/ScenarioValidationError';
32
import {
43
type AstBuilder,
54
type EditorNodeViewModel,
65
findArgumentIndexErrorsFromParent,
76
} from '@app-builder/services/editor/ast-editor';
8-
import { useGetNodeEvaluationErrorMessage } from '@app-builder/services/validation';
7+
import {
8+
adaptEvaluationErrorViewModels,
9+
type EvaluationErrorViewModel,
10+
useGetNodeEvaluationErrorMessage,
11+
} from '@app-builder/services/validation';
912

1013
import {
1114
computeOperandErrors,
@@ -22,7 +25,7 @@ interface TwoOperandsLineViewModel {
2225
left: OperandViewModel;
2326
operator: OperatorViewModel;
2427
right: OperandViewModel;
25-
errors: EvaluationError[];
28+
errors: EvaluationErrorViewModel[];
2629
}
2730

2831
export function TwoOperandsLine({
@@ -36,6 +39,10 @@ export function TwoOperandsLine({
3639
}) {
3740
const getNodeEvaluationErrorMessage = useGetNodeEvaluationErrorMessage();
3841

42+
const errorMessages = twoOperandsViewModel.errors.map((error) =>
43+
getNodeEvaluationErrorMessage(error)
44+
);
45+
3946
return (
4047
<div className="flex flex-col gap-2">
4148
<div className="flex flex-row gap-2">
@@ -67,11 +74,8 @@ export function TwoOperandsLine({
6774
/>
6875
</div>
6976
<div className="flex flex-row flex-wrap gap-2">
70-
{twoOperandsViewModel.errors.map((error, index) => (
71-
// TODO: find a better way to compute error key (flatten errors make it hard)
72-
<ScenarioValidationError key={index}>
73-
{getNodeEvaluationErrorMessage(error)}
74-
</ScenarioValidationError>
77+
{errorMessages.map((error) => (
78+
<ScenarioValidationError key={error}>{error}</ScenarioValidationError>
7579
))}
7680
</div>
7781
</div>
@@ -93,11 +97,11 @@ export function adaptTwoOperandsLineViewModel(
9397
left,
9498
operator: operatorVm,
9599
right,
96-
errors: [
100+
errors: adaptEvaluationErrorViewModels([
97101
...computeOperandErrors(left),
98102
...vm.errors,
99103
...computeOperandErrors(right),
100104
...findArgumentIndexErrorsFromParent(vm),
101-
],
105+
]),
102106
};
103107
}
Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,28 @@
11
import { type EvaluationError } from '@app-builder/models';
2-
import { useGetNodeEvaluationErrorMessage } from '@app-builder/services/validation';
2+
import {
3+
adaptEvaluationErrorViewModels,
4+
useGetNodeEvaluationErrorMessage,
5+
} from '@app-builder/services/validation';
36

47
export interface ErrorMessageProps {
58
errors?: EvaluationError[];
69
}
710

11+
/**
12+
* @deprecated Use ScenarioValidationError instead
13+
*/
814
export function ErrorMessage({ errors }: ErrorMessageProps) {
915
const getNodeEvaluationErrorMessage = useGetNodeEvaluationErrorMessage();
1016

1117
const firstError = errors?.[0];
1218

1319
return (
1420
<p className="text-s font-medium text-red-100 transition-opacity duration-200 ease-in-out">
15-
{firstError && getNodeEvaluationErrorMessage(firstError)}
21+
{firstError &&
22+
getNodeEvaluationErrorMessage(
23+
// glitch for ISO compatibility with former code
24+
adaptEvaluationErrorViewModels([firstError])[0]
25+
)}
1626
</p>
1727
);
1828
}

packages/app-builder/src/components/Scenario/AstBuilder/RootAstBuilderNode/RootAnd.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@ import {
99
type EditorNodeViewModel,
1010
hasArgumentIndexErrorsFromParent,
1111
} from '@app-builder/services/editor/ast-editor';
12-
import { useGetOrAndNodeEvaluationErrorMessage } from '@app-builder/services/validation';
12+
import {
13+
adaptEvaluationErrorViewModels,
14+
useGetOrAndNodeEvaluationErrorMessage,
15+
} from '@app-builder/services/validation';
1316
import clsx from 'clsx';
1417
import { Fragment } from 'react';
1518

16-
import { ScenarioValidationError } from '../../ScenarioValidatioError';
19+
import { ScenarioValidationError } from '../../ScenarioValidationError';
1720
import { AstBuilderNode } from '../AstBuilderNode/AstBuilderNode';
1821
import { RemoveButton } from '../RemoveButton';
1922
import { AddLogicalOperatorButton } from './AddLogicalOperatorButton';
@@ -60,6 +63,10 @@ export function RootAnd({
6063
rootAndViewModel.errors
6164
);
6265

66+
const andErrorMessages = adaptEvaluationErrorViewModels(
67+
andNonChildrenErrors
68+
).map(getEvaluationErrorMessage);
69+
6370
function appendAndChild() {
6471
builder.appendChild(rootAndViewModel.nodeId, NewAndChild());
6572
}
@@ -143,10 +150,8 @@ export function RootAnd({
143150
{!viewOnly && (
144151
<AddLogicalOperatorButton onClick={appendAndChild} operator="and" />
145152
)}
146-
{andNonChildrenErrors.map((error, index) => (
147-
<ScenarioValidationError key={index}>
148-
{getEvaluationErrorMessage(error)}
149-
</ScenarioValidationError>
153+
{andErrorMessages.map((error) => (
154+
<ScenarioValidationError key={error}>{error}</ScenarioValidationError>
150155
))}
151156
</div>
152157
</>

packages/app-builder/src/components/Scenario/AstBuilder/RootAstBuilderNode/RootOrWithAnd.tsx

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@ import {
1010
type EditorNodeViewModel,
1111
hasArgumentIndexErrorsFromParent,
1212
} from '@app-builder/services/editor/ast-editor';
13-
import { useGetOrAndNodeEvaluationErrorMessage } from '@app-builder/services/validation';
13+
import {
14+
adaptEvaluationErrorViewModels,
15+
useGetOrAndNodeEvaluationErrorMessage,
16+
} from '@app-builder/services/validation';
1417
import clsx from 'clsx';
1518
import React from 'react';
1619

17-
import { ScenarioValidationError } from '../../ScenarioValidatioError';
20+
import { ScenarioValidationError } from '../../ScenarioValidationError';
1821
import { AstBuilderNode } from '../AstBuilderNode/AstBuilderNode';
1922
import { RemoveButton } from '../RemoveButton';
2023
import { AddLogicalOperatorButton } from './AddLogicalOperatorButton';
@@ -82,6 +85,10 @@ export function RootOrWithAnd({
8285
rootOrWithAndViewModel.orErrors
8386
);
8487

88+
const rootOrErrorMessages = adaptEvaluationErrorViewModels(
89+
rootOrNonChildrenErrors
90+
).map(getEvaluationErrorMessage);
91+
8592
return (
8693
<div className="flex flex-col gap-4">
8794
{rootOrWithAndViewModel.ands.map((andChild, childIndex) => {
@@ -90,6 +97,10 @@ export function RootOrWithAnd({
9097
andChild.errors
9198
);
9299

100+
const andErrorMessages = adaptEvaluationErrorViewModels(
101+
andNonChildrenErrors
102+
).map(getEvaluationErrorMessage);
103+
93104
function appendAndChild() {
94105
builder.appendChild(andChild.nodeId, NewAndChild());
95106
}
@@ -157,9 +168,9 @@ export function RootOrWithAnd({
157168
</div>
158169
)}
159170

160-
{andNonChildrenErrors.map((error, index) => (
161-
<ScenarioValidationError key={index}>
162-
{getEvaluationErrorMessage(error)}
171+
{andErrorMessages.map((error) => (
172+
<ScenarioValidationError key={error}>
173+
{error}
163174
</ScenarioValidationError>
164175
))}
165176
</div>
@@ -172,10 +183,8 @@ export function RootOrWithAnd({
172183
<AddLogicalOperatorButton onClick={appendOrChild} operator="or" />
173184
)}
174185

175-
{rootOrNonChildrenErrors.map((error, index) => (
176-
<ScenarioValidationError key={index}>
177-
{getEvaluationErrorMessage(error)}
178-
</ScenarioValidationError>
186+
{rootOrErrorMessages.map((error) => (
187+
<ScenarioValidationError key={error}>{error}</ScenarioValidationError>
179188
))}
180189
</div>
181190
</div>

packages/app-builder/src/routes/__builder/scenarios/$scenarioId/i/$iterationId/__edit-view.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { VersionSelect } from '@app-builder/components/Scenario/Iteration/Versio
99
import { sortScenarioIterations } from '@app-builder/models/scenario-iteration';
1010
import { useCurrentScenario } from '@app-builder/routes/__builder/scenarios/$scenarioId';
1111
import { CreateDraftIteration } from '@app-builder/routes/ressources/scenarios/$scenarioId/$iterationId/create_draft';
12-
import { DeploymentModal } from '@app-builder/routes/ressources/scenarios/deployment';
12+
import { DeploymentActions } from '@app-builder/routes/ressources/scenarios/deployment';
1313
import { useEditorMode } from '@app-builder/services/editor';
1414
import { serverServices } from '@app-builder/services/init.server';
1515
import {
@@ -76,7 +76,7 @@ export default function ScenarioEditLayout() {
7676
const withEditTag = editorMode === 'edit';
7777
const withCreateDraftIteration =
7878
canManageScenario && currentIteration.type !== 'draft';
79-
const withDeploymentModal = canPublishScenario;
79+
const withDeploymentActions = canPublishScenario;
8080

8181
return (
8282
<ScenarioPage.Container>
@@ -104,11 +104,16 @@ export default function ScenarioEditLayout() {
104104
draftId={draftIteration?.id}
105105
/>
106106
)}
107-
{withDeploymentModal && (
108-
<DeploymentModal
107+
{withDeploymentActions && (
108+
<DeploymentActions
109109
scenarioId={currentScenario.id}
110110
liveVersionId={currentScenario.liveVersionId}
111111
currentIteration={currentIteration}
112+
hasScenarioErrors={
113+
hasTriggerErrors(scenarioValidation) ||
114+
hasRulesErrors(scenarioValidation) ||
115+
hasDecisionErrors(scenarioValidation)
116+
}
112117
/>
113118
)}
114119
</div>

packages/app-builder/src/routes/__builder/scenarios/$scenarioId/i/$iterationId/__edit-view/decision.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
FormLabel,
1414
} from '@app-builder/components/Form';
1515
import { setToastMessage } from '@app-builder/components/MarbleToaster';
16-
import { ScenarioValidationError } from '@app-builder/components/Scenario/ScenarioValidatioError';
16+
import { ScenarioValidationError } from '@app-builder/components/Scenario/ScenarioValidationError';
1717
import {
1818
useCurrentScenarioIteration,
1919
useEditorMode,

packages/app-builder/src/routes/__builder/scenarios/$scenarioId/i/$iterationId/__edit-view/rules.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Ping } from '@app-builder/components/Ping';
2-
import { ScenarioValidationError } from '@app-builder/components/Scenario/ScenarioValidatioError';
2+
import { ScenarioValidationError } from '@app-builder/components/Scenario/ScenarioValidationError';
33
import { CreateRule } from '@app-builder/routes/ressources/scenarios/$scenarioId/$iterationId/rules/create';
44
import { useEditorMode } from '@app-builder/services/editor';
55
import { serverServices } from '@app-builder/services/init.server';

packages/app-builder/src/routes/__builder/scenarios/$scenarioId/i/$iterationId/__edit-view/trigger.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
} from '@app-builder/components';
77
import { setToastMessage } from '@app-builder/components/MarbleToaster';
88
import { AstBuilder } from '@app-builder/components/Scenario/AstBuilder';
9-
import { ScenarioValidationError } from '@app-builder/components/Scenario/ScenarioValidatioError';
9+
import { ScenarioValidationError } from '@app-builder/components/Scenario/ScenarioValidationError';
1010
import { ScheduleOption } from '@app-builder/components/Scenario/Trigger';
1111
import {
1212
adaptDataModelDto,

0 commit comments

Comments
 (0)