diff --git a/frontend/src/assets/styles/teacher_components.css b/frontend/src/assets/styles/teacher_components.css index bd660d4d..bdbba503 100644 --- a/frontend/src/assets/styles/teacher_components.css +++ b/frontend/src/assets/styles/teacher_components.css @@ -56,4 +56,19 @@ display: flex; flex-direction: column; gap: 5px; +} + +.error-row { + display: flex; + flex-direction: row; + gap: 5px; +} + +.error-fail { + font-weight: bold; + color: black; +} + +.error-success { + color: rgb(201, 255, 201); } \ No newline at end of file diff --git a/frontend/src/components/ProjectStudentComponent.tsx b/frontend/src/components/ProjectStudentComponent.tsx index f70cd66b..9eaf1a68 100644 --- a/frontend/src/components/ProjectStudentComponent.tsx +++ b/frontend/src/components/ProjectStudentComponent.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment, no-inner-declarations */ import {ChangeEvent, JSX, useEffect, useRef, useState} from "react"; import FieldWithLabel from "./FieldWithLabel.tsx"; import {FaCheck, FaUpload} from "react-icons/fa"; @@ -14,6 +15,7 @@ import {joinGroup, leaveGroup} from "../utils/api/Groups.ts"; import {getGroupInfo, loadGroupMembers} from "../dataloaders/loader_helpers/SharedFunctions.ts"; import SimpleTests from "./SimpleTests/SimpleTests.tsx"; import {TeacherOrStudent} from "./SimpleTests/TeacherOrStudentEnum.tsx"; +import getID from "./SimpleTests/IDProvider.tsx"; function ProjectInfo(props: { project: ProjectStudent }): JSX.Element { const {t} = useTranslation(); @@ -62,7 +64,7 @@ export default function ProjectStudentComponent(props: { project: ProjectStudent const [newSelectedFile, setNewSelectedFile] = useState(false); const [file, setFile] = useState(undefined) - const [error, setError] = useState('') + const [error, setError] = useState(undefined) const [success, setSuccess] = useState('') const [hasGroup, setHasGroup] = useState((props.project.groupMembers || false) && props.project.groupMembers.length > 0) @@ -97,7 +99,7 @@ export default function ProjectStudentComponent(props: { project: ProjectStudent setSubmission('') setHasGroup(false) setSuccess('') - setError('') + setError(undefined) setFile(undefined) setNewSelectedFile(false) } @@ -215,12 +217,12 @@ export default function ProjectStudentComponent(props: { project: ProjectStudent if (file !== undefined) { const submission: string | Submission = await make_submission(groupId, file) if (typeof submission === 'string') { - setError(submission) // TODO translation + setError(submission) setSuccess('') } else { setNewSelectedFile(false) setSuccess(t("project.submitted")) - setError('') + setError(undefined) } setFile(undefined); setNewSelectedFile(false); @@ -255,6 +257,120 @@ export default function ProjectStudentComponent(props: { project: ProjectStudent document.body.removeChild(a); } + class ParsedError { + success: boolean; + local_rows: ErrorRow[] | undefined; + global_rows: ErrorRow[] | undefined; + unparsed_error: string; + constructor( + success: boolean, + local_rows: ErrorRow[] | undefined, + global_rows: ErrorRow[] | undefined, + unparsed_error: string + ) { + this.success = success; + this.local_rows = local_rows; + this.global_rows = global_rows; + this.unparsed_error = unparsed_error; + } + } + + class ErrorRow { + type: string; + value: string | undefined; + depth: number; + success: boolean; + constructor( + type: string, + value: string | undefined, + depth: number, + success: boolean, + ) { + this.type = type; + this.value = value; + this.depth = depth; + this.success = success; + } + } + + interface StringDictionary { + [key: string]: (string | boolean | StringDictionary | StringDictionary[] | null); + } + + function tryParseError(error: string): ParsedError { + try { + const obj: StringDictionary = JSON.parse(error); + const row_list_local: ErrorRow[] = []; + const row_list_global: ErrorRow[] = []; + + function dfs( + json: StringDictionary, + depth: number, + local: boolean = true + ) { + const _type = json['type'] as string; + const _depth = depth; + const _success = json['is_ok'] as boolean; + let _value = undefined; + if ('file_name' in json) {_value = json['file_name'] as string} + if ('zip_name' in json) {_value = json['zip_name'] as string} + if ('directory_name' in json) {_value = json['directory_name'] as string} + if ('file_or_directory_name' in json) {_value = json['file_or_directory_name'] as string} + if ('not_present_extension' in json) {_value = json['not_present_extension'] as string} + if ('extension' in json) {_value = json['extension'] as string} + + const row = new ErrorRow(_type, _value, _depth, _success); + if (local) { + if (row.type !== "SUBMISSION") + row_list_local.push(row); + } else { + row_list_global.push(row); + } + + if ('root_constraint_result' in json) { + if (json['root_constraint_result'] !== null) { + const sub_obj = json['root_constraint_result'] as StringDictionary; + dfs(sub_obj, depth); + } + } + + if ('sub_constraint_results' in json) { + if (json['sub_constraint_results'] !== null) { + const sub_objs = json['sub_constraint_results'] as StringDictionary[]; + for (const sub_obj of sub_objs) { + dfs(sub_obj, depth+1); + } + } + } + + if ('global_constraint_result' in json) { + if (json['global_constraint_result'] !== null) { + const sub_obj = json['global_constraint_result'] as StringDictionary; + dfs(sub_obj, 0, false); + } + } + + if ('global_constraint_results' in json) { + if (json['global_constraint_results'] !== null) { + const sub_obj = json['global_constraint_results'] as StringDictionary; + for (const x of Object.keys(sub_obj).map(function(key){return sub_obj[key]})) { + dfs(x as StringDictionary, 0, false); + } + } + } + + } + + dfs(obj, 0); + + return new ParsedError(true, row_list_local, row_list_global, error); + + } catch (e) { + console.log(e) + return new ParsedError(false, undefined, undefined, error); + } + } + return (
@@ -263,7 +379,38 @@ export default function ProjectStudentComponent(props: { project: ProjectStudent
{t('project.failed-submission')}

- {error} + {tryParseError(error).success + ?
+ { tryParseError(error).global_rows?.length !== undefined && (tryParseError(error).global_rows?.length as number) > 1 && +
+
+ {tryParseError(error).global_rows?.map(row => +
+ {"\u00A0".repeat(5 * row.depth)} + {row.success + ?
{row.type.toLocaleLowerCase()}: {row.value}
+ :
{row.type.toLocaleLowerCase()}: {row.value}
+ } +
+ )} +
+
+
+ } +
+ {tryParseError(error).local_rows?.map(row => +
+ {"\u00A0".repeat(5 * row.depth)} + {row.success + ?
{row.type.toLocaleLowerCase()}: {row.value}
+ :
{row.type.toLocaleLowerCase()}: {row.value}
+ } +
+ )} +
+
+ :
{error}
+ }
}