Skip to content

Commit d9e1c0b

Browse files
authored
feat improve user error information (#169)
* feat improve typescript checks according to TdPlayground file Signed-off-by: Ricardo M G da Silva <[email protected]> * feat errors from validation workers are display to the user Signed-off-by: Ricardo M G da Silva <[email protected]> * fix unit test of deleting key @type Signed-off-by: Ricardo M G da Silva <[email protected]> * fix revert changes on @type key Signed-off-by: Ricardo M G da Silva <[email protected]> --------- Signed-off-by: Ricardo M G da Silva <[email protected]>
1 parent 8d2fc97 commit d9e1c0b

File tree

7 files changed

+254
-75
lines changed

7 files changed

+254
-75
lines changed

src/components/Editor/JsonEditor.tsx

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import React, {
2222
import ediTDorContext from "../../context/ediTDorContext";
2323
import { changeBetweenTd } from "../../util";
2424
import { editor } from "monaco-editor";
25+
import { IValidationMessage } from "../../types/context";
2526

2627
type SchemaMapMessage = Map<string, Record<string, unknown>>;
2728

@@ -48,11 +49,6 @@ interface JsonSchemaEntry {
4849
schema: Record<string, unknown>;
4950
}
5051

51-
interface ValidationResults {
52-
valid: boolean;
53-
message?: string;
54-
}
55-
5652
const JsonEditor: React.FC<JsonEditorProps> = ({ editorRef }) => {
5753
const context = useContext(ediTDorContext);
5854

@@ -101,11 +97,10 @@ const JsonEditor: React.FC<JsonEditorProps> = ({ editorRef }) => {
10197
updateMonacoSchemas(ev.data);
10298
};
10399

104-
validationWorker.onmessage = (ev: MessageEvent<ValidationResults>) => {
100+
validationWorker.onmessage = (ev: MessageEvent<IValidationMessage>) => {
105101
console.debug("received message from validation worker");
106102

107-
/** @type {ValidationResults} */
108-
const validationResults = ev.data;
103+
const validationResults: IValidationMessage = ev.data;
109104
context.updateValidationMessage(validationResults);
110105
};
111106
}, [schemaWorker, validationWorker, proxy, context]);
@@ -172,20 +167,50 @@ const JsonEditor: React.FC<JsonEditorProps> = ({ editorRef }) => {
172167
if (!editorText) {
173168
return;
174169
}
170+
let validate: IValidationMessage = {
171+
report: {
172+
json: null,
173+
schema: null,
174+
defaults: null,
175+
jsonld: null,
176+
additional: null,
177+
},
178+
details: {
179+
enumConst: null,
180+
propItems: null,
181+
security: null,
182+
propUniqueness: null,
183+
multiLangConsistency: null,
184+
linksRelTypeCount: null,
185+
readWriteOnly: null,
186+
uriVariableSecurity: null,
187+
},
188+
detailComments: {
189+
enumConst: null,
190+
propItems: null,
191+
security: null,
192+
propUniqueness: null,
193+
multiLangConsistency: null,
194+
linksRelTypeCount: null,
195+
readWriteOnly: null,
196+
uriVariableSecurity: null,
197+
},
198+
customMessage: "",
199+
};
175200
try {
176201
JSON.parse(editorText);
177202
context.updateOfflineTD(editorText);
178-
context.updateValidationMessage(undefined);
203+
204+
context.updateValidationMessage(validate);
179205
} catch (error) {
180-
context.updateValidationMessage({
181-
valid: false,
182-
message:
183-
"Invalid JSON: " +
184-
(error instanceof Error ? error.message : String(error)),
185-
});
206+
let message: string =
207+
"Invalid JSON: " +
208+
(error instanceof Error ? error.message : String(error));
209+
validate.report.json = "failed";
210+
context.updateValidationMessage(validate);
211+
setLocalTextState(editorText);
212+
delay(messageWorkers, editorText ?? "", 500);
186213
}
187-
setLocalTextState(editorText);
188-
delay(messageWorkers, editorText ?? "", 500);
189214
};
190215

191216
useEffect(() => {

src/components/TDViewer/components/ValidationView.tsx

Lines changed: 90 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -11,68 +11,89 @@
1111
* SPDX-License-Identifier: EPL-2.0 OR W3C-20150513
1212
********************************************************************************/
1313

14-
import { useContext, useEffect, useState } from "react";
14+
import { useContext, useMemo } from "react";
1515
import ediTDorContext from "../../../context/ediTDorContext";
1616
import { ImCheckmark, ImCross } from "react-icons/im";
1717
import { RotateCcw, RotateCw } from "react-feather";
1818
import BaseButton from "../base/BaseButton";
1919

20-
type ValidationState = "passed" | "failed" | undefined;
20+
type ValidationState = "warning" | "passed" | "failed" | null;
2121

2222
type ValidationViewProps = {
2323
onUndo: () => void;
2424
onRedo: () => void;
2525
};
2626

27+
const GLOBAL_ERROR_PARSING_JSON =
28+
"JSON validation failed: Parsed object is not valid JSON.";
29+
const DEFAULT_ERROR_SCHEMA =
30+
"JSON Schema validation failed: Unknown schema validation error.";
31+
2732
const ValidationView: React.FC<ValidationViewProps> = ({
2833
onUndo: handleUndo,
2934
onRedo: handleRedo,
3035
}) => {
3136
const context = useContext(ediTDorContext);
3237

33-
const [jsonValidation, setJsonValidation] =
34-
useState<ValidationState>(undefined);
35-
const [jsonValidationError, setJsonValidationError] = useState<
36-
string | null | undefined
37-
>(undefined);
38-
39-
const [jsonSchemaValidation, setJsonSchemaValidation] =
40-
useState<ValidationState>(undefined);
41-
const [jsonSchemaValidationError, setJsonSchemaValidationError] = useState<
42-
string | null | undefined
43-
>(undefined);
44-
45-
useEffect(() => {
46-
const validationMessage = context.validationMessage;
47-
if (!validationMessage) {
48-
return;
38+
// Object json errors
39+
const jsonValidation = useMemo<ValidationState>(() => {
40+
let r: ValidationState = null;
41+
try {
42+
r =
43+
context.validationMessage.report.json === "passed" ||
44+
context.validationMessage.report.json === "warning" ||
45+
context.validationMessage.report.jsonld === "passed" ||
46+
context.validationMessage.report.jsonld === "warning"
47+
? "passed"
48+
: "failed";
49+
} catch (e) {
50+
return "failed";
4951
}
50-
51-
if (validationMessage.report) {
52-
setJsonValidation(validationMessage.report.json);
53-
setJsonSchemaValidation(validationMessage.report.schema);
52+
return r;
53+
}, [context]);
54+
55+
// Schenas validation
56+
const jsonSchemaValidation: ValidationState = useMemo<ValidationState>(() => {
57+
let s: ValidationState = null;
58+
try {
59+
s = context.validationMessage.report.schema;
60+
} catch (e) {
61+
return "failed";
5462
}
55-
56-
if (!validationMessage.validationErrors) {
57-
setJsonValidationError(undefined);
58-
setJsonSchemaValidationError(undefined);
59-
return;
63+
return s;
64+
}, [context]);
65+
66+
// Additional report validation
67+
const reportValidation = useMemo<
68+
(keyof typeof context.validationMessage.details)[]
69+
>(() => {
70+
try {
71+
if (typeof context.validationMessage.details != "object") {
72+
return [];
73+
}
74+
const details = context.validationMessage.details;
75+
76+
return (Object.keys(details) as (keyof typeof details)[]).filter(
77+
(key) => details[key] === "failed"
78+
);
79+
} catch (e) {
80+
return [];
6081
}
82+
}, [context]);
6183

62-
if (validationMessage.validationErrors) {
63-
setJsonValidationError(validationMessage.validationErrors.json);
64-
setJsonSchemaValidationError(validationMessage.validationErrors.schema);
65-
66-
console.debug(
67-
"JSON validation error",
68-
validationMessage.validationErrors.json
69-
);
70-
console.debug(
71-
"JSON Schema validation error",
72-
validationMessage.validationErrors.schema
73-
);
84+
const reportValidationError = useMemo<string[]>(() => {
85+
if (reportValidation.length === 0) {
86+
return [];
87+
}
88+
try {
89+
const comments = context.validationMessage.detailComments;
90+
return reportValidation
91+
.map((key) => comments[key])
92+
.filter((v): v is string => typeof v === "string");
93+
} catch (e) {
94+
return [];
7495
}
75-
}, [context, jsonValidationError, jsonSchemaValidationError]);
96+
}, [reportValidation]);
7697

7798
return (
7899
<>
@@ -100,38 +121,57 @@ const ValidationView: React.FC<ValidationViewProps> = ({
100121
<div className="mb-4 w-full rounded-md bg-gray-600 p-4 text-white">
101122
<div className="flex items-center">
102123
<h2 className="mr-2">JSON Validation</h2>
103-
{jsonValidation === "passed" && <ImCheckmark />}
104-
{jsonValidation === "failed" && <ImCross />}
124+
{jsonValidation === "passed" && <ImCheckmark color="#32CD32" />}
125+
{jsonValidation === "failed" && <ImCross color="Red" />}
105126
</div>
106127

107-
{jsonValidationError && (
128+
{jsonValidation === "failed" && (
108129
<div className="bg-formRed border-formRed mb-4 mt-2 flex min-h-[2.5rem] w-full rounded-md border-2 bg-opacity-75 px-4">
109130
<div className="flex h-6 w-16 justify-center self-center rounded-md bg-white">
110-
<div className="text-formRed place-self-center px-4 text-center text-xs">
131+
<div className="place-self-center px-4 text-center text-xs text-black">
111132
Error
112133
</div>
113134
</div>
114135
<div className="place-self-center overflow-hidden pl-3 text-base">
115-
{jsonValidationError}
136+
{context.validationMessage?.validationErrors?.json ??
137+
GLOBAL_ERROR_PARSING_JSON}
116138
</div>
117139
</div>
118140
)}
119141

120142
<div className="flex items-center">
121143
<h2 className="mr-2">JSON Schema Validation </h2>
122-
{jsonSchemaValidation === "passed" && <ImCheckmark />}
123-
{jsonSchemaValidation === "failed" && <ImCross />}
144+
{jsonSchemaValidation === "passed" && <ImCheckmark color="#32CD32" />}
145+
{jsonSchemaValidation === "failed" && <ImCross color="Red" />}
124146
</div>
125147

126-
{jsonSchemaValidationError && (
148+
{jsonSchemaValidation === "failed" && (
149+
<div className="bg-formRed border-formRed mt-2 flex min-h-[2.5rem] w-full rounded-md border-2 bg-opacity-75 px-4">
150+
<div className="flex h-6 w-16 justify-center self-center rounded-md bg-white">
151+
<div className="place-self-center px-4 text-center text-xs text-black">
152+
Error
153+
</div>
154+
</div>
155+
<div className="place-self-center overflow-hidden pl-3 text-base">
156+
{context.validationMessage?.validationErrors?.schema ??
157+
DEFAULT_ERROR_SCHEMA}
158+
</div>
159+
</div>
160+
)}
161+
<div className="flex items-center">
162+
<h2 className="mr-2">Additional Checks </h2>
163+
{reportValidation.length === 0 && <ImCheckmark color="#32CD32" />}
164+
{reportValidation.length > 0 && <ImCross color="Red" />}
165+
</div>
166+
{reportValidationError && reportValidationError.length > 0 && (
127167
<div className="bg-formRed border-formRed mt-2 flex min-h-[2.5rem] w-full rounded-md border-2 bg-opacity-75 px-4">
128168
<div className="flex h-6 w-16 justify-center self-center rounded-md bg-white">
129-
<div className="text-formRed place-self-center px-4 text-center text-xs">
169+
<div className="place-self-center px-4 text-center text-xs text-black">
130170
Error
131171
</div>
132172
</div>
133173
<div className="place-self-center overflow-hidden pl-3 text-base">
134-
{jsonSchemaValidationError}
174+
{reportValidationError.join(", ")}
135175
</div>
136176
</div>
137177
)}

src/context/GlobalState.tsx

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ import React, { ReactNode, useReducer } from "react";
1414

1515
import EdiTDorContext from "./ediTDorContext";
1616
import { editdorReducer } from "./editorReducers";
17+
import {
18+
IValidationMessage,
19+
INorthboundConnection,
20+
IContributeCatalog,
21+
EditorState,
22+
Action,
23+
} from "types/context";
1724

1825
export const UPDATE_OFFLINE_TD = "UPDATE_OFFLINE_TD";
1926
export const UPDATE_IS_MODIFIED = "UPDATE_IS_MODIFIED";
@@ -42,7 +49,40 @@ const GlobalState: React.FC<IGlobalStateProps> = ({ children }) => {
4249
isValidJSON: false,
4350
parsedTD: {},
4451
name: "",
45-
validationMessage: "",
52+
validationMessage: {
53+
report: {
54+
json: null,
55+
schema: null,
56+
defaults: null,
57+
jsonld: null,
58+
additional: null,
59+
},
60+
details: {
61+
enumConst: null,
62+
propItems: null,
63+
security: null,
64+
propUniqueness: null,
65+
multiLangConsistency: null,
66+
linksRelTypeCount: null,
67+
readWriteOnly: null,
68+
uriVariableSecurity: null,
69+
},
70+
detailComments: {
71+
enumConst: null,
72+
propItems: null,
73+
security: null,
74+
propUniqueness: null,
75+
multiLangConsistency: null,
76+
linksRelTypeCount: null,
77+
readWriteOnly: null,
78+
uriVariableSecurity: null,
79+
},
80+
validationErrors: {
81+
json: "",
82+
schema: "",
83+
},
84+
customMessage: "",
85+
},
4686
fileHandle: null,
4787
linkedTd: undefined,
4888
northboundConnection: {
@@ -121,7 +161,7 @@ const GlobalState: React.FC<IGlobalStateProps> = ({ children }) => {
121161
dispatch({ type: UPDATE_LINKED_TD, linkedTd: linkedTd });
122162
};
123163

124-
const updateValidationMessage = (validationMessage?: string) => {
164+
const updateValidationMessage = (validationMessage: IValidationMessage) => {
125165
dispatch({
126166
type: UPDATE_VALIDATION_MESSAGE,
127167
validationMessage: validationMessage,

src/context/ediTDorContext.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,40 @@ const defaultContext: IEdiTDorContext = {
2121
name: "",
2222
fileHandle: null,
2323
linkedTd: undefined,
24-
validationMessage: undefined,
24+
validationMessage: {
25+
report: {
26+
json: null,
27+
schema: null,
28+
defaults: null,
29+
jsonld: null,
30+
additional: null,
31+
},
32+
details: {
33+
enumConst: null,
34+
propItems: null,
35+
security: null,
36+
propUniqueness: null,
37+
multiLangConsistency: null,
38+
linksRelTypeCount: null,
39+
readWriteOnly: null,
40+
uriVariableSecurity: null,
41+
},
42+
detailComments: {
43+
enumConst: null,
44+
propItems: null,
45+
security: null,
46+
propUniqueness: null,
47+
multiLangConsistency: null,
48+
linksRelTypeCount: null,
49+
readWriteOnly: null,
50+
uriVariableSecurity: null,
51+
},
52+
validationErrors: {
53+
json: "",
54+
schema: "",
55+
},
56+
customMessage: "",
57+
},
2558
northboundConnection: {
2659
message: "",
2760
northboundTd: {},

0 commit comments

Comments
 (0)