Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Programming exercises: Improve error messages for connection issues in online code editor #6888

Merged
merged 5 commits into from
Jul 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
#editor
id="ace-code-editor"
[mode]="editorMode ? editorMode : 'java'"
[readOnly]="isLoading || disableActions"
[readOnly]="isLoading || disableActions || !!fileSession[selectedFile]?.loadingError"
[hidden]="!selectedFile || isLoading"
[autoUpdateContent]="true"
[durationBeforeCallback]="200"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,10 @@ import {
ViewChildren,
ViewEncapsulation,
} from '@angular/core';
import { Subscription, fromEvent, of } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import { Subscription, fromEvent } from 'rxjs';
import { CommitState, CreateFileChange, DeleteFileChange, EditorState, FileChange, RenameFileChange } from 'app/exercises/programming/shared/code-editor/model/code-editor.model';
import { CodeEditorFileService } from 'app/exercises/programming/shared/code-editor/service/code-editor-file.service';
import { CodeEditorRepositoryFileService } from 'app/exercises/programming/shared/code-editor/service/code-editor-repository.service';
import { CodeEditorRepositoryFileService, ConnectionError } from 'app/exercises/programming/shared/code-editor/service/code-editor-repository.service';
import { RepositoryFileService } from 'app/exercises/shared/result/repository.service';
import { TextChange } from 'app/entities/text-change.model';
import { LocalStorageService } from 'ngx-webstorage';
Expand All @@ -46,6 +45,7 @@ import { faCircleNotch, faPlusSquare } from '@fortawesome/free-solid-svg-icons';
import { CodeEditorTutorAssessmentInlineFeedbackComponent } from 'app/exercises/programming/assess/code-editor-tutor-assessment-inline-feedback.component';

export type Annotation = { fileName: string; row: number; column: number; text: string; type: string; timestamp: number; hash?: string };
export type FileSession = { [fileName: string]: { code: string; cursor: { column: number; row: number }; loadingError: boolean } };

@Component({
selector: 'jhi-code-editor-ace',
Expand Down Expand Up @@ -107,7 +107,7 @@ export class CodeEditorAceComponent implements AfterViewInit, OnChanges, OnDestr
isLoading = false;
annotationsArray: Array<Annotation> = [];
annotationChange: Subscription;
fileSession: { [fileName: string]: { code: string; cursor: { column: number; row: number } } } = {};
fileSession: FileSession = {};
// Inline feedback variables
fileFeedbacks: Feedback[];
fileFeedbackPerLine: { [line: number]: Feedback } = {};
Expand Down Expand Up @@ -175,7 +175,7 @@ export class CodeEditorAceComponent implements AfterViewInit, OnChanges, OnDestr
) {
// Current file has changed
// Only load the file from server if there is nothing stored in the editorFileSessions
if (this.selectedFile && !this.fileSession[this.selectedFile]) {
if ((this.selectedFile && !this.fileSession[this.selectedFile]) || this.fileSession[this.selectedFile].loadingError) {
this.loadFile(this.selectedFile);
} else {
this.initEditorAfterFileChange();
Expand Down Expand Up @@ -261,24 +261,29 @@ export class CodeEditorAceComponent implements AfterViewInit, OnChanges, OnDestr
*/
loadFile(fileName: string) {
this.isLoading = true;
/** Query the repositoryFileService for the specified file in the repository */
this.repositoryFileService
.getFile(fileName)
.pipe(
tap((fileObj) => {
this.fileSession[fileName] = { code: fileObj.fileContent, cursor: { column: 0, row: 0 } };
// It is possible that the selected file has changed - in this case don't update the editor.
if (this.selectedFile === fileName) {
this.initEditorAfterFileChange();
}
}),
catchError(() => {
return of(undefined);
}),
)
.subscribe(() => {
this.isLoading = false;
});
this.repositoryFileService.getFile(fileName).subscribe({
next: (fileObj) => {
this.fileSession[fileName] = { code: fileObj.fileContent, cursor: { column: 0, row: 0 }, loadingError: false };
this.finalizeLoading(fileName);
},
error: (error) => {
this.fileSession[fileName] = { code: '', cursor: { column: 0, row: 0 }, loadingError: true };
if (error.message === ConnectionError.message) {
this.onError.emit('loadingFailed' + error.message);
} else {
this.onError.emit('loadingFailed');
}
this.finalizeLoading(fileName);
},
});
}

finalizeLoading(fileName: string) {
// It is possible that the selected file has changed - in this case don't update the editor.
if (this.selectedFile === fileName) {
this.initEditorAfterFileChange();
}
this.isLoading = false;
}

/**
Expand All @@ -300,7 +305,7 @@ export class CodeEditorAceComponent implements AfterViewInit, OnChanges, OnDestr
if (this.selectedFile && this.fileSession[this.selectedFile]) {
if (this.fileSession[this.selectedFile].code !== code) {
const cursor = this.editor.getEditor().getCursorPosition();
this.fileSession[this.selectedFile] = { code, cursor };
this.fileSession[this.selectedFile] = { code, cursor, loadingError: false };
this.onFileContentChange.emit({ file: this.selectedFile, fileContent: code });
}
}
Expand Down Expand Up @@ -413,7 +418,7 @@ export class CodeEditorAceComponent implements AfterViewInit, OnChanges, OnDestr
this.annotationsArray = this.annotationsArray.filter((a) => a.fileName === fileChange.fileName);
this.storeAnnotations([fileChange.fileName]);
} else if (fileChange instanceof CreateFileChange && this.selectedFile === fileChange.fileName) {
this.fileSession = { ...this.fileSession, [fileChange.fileName]: { code: '', cursor: { row: 0, column: 0 } } };
this.fileSession = { ...this.fileSession, [fileChange.fileName]: { code: '', cursor: { row: 0, column: 0 }, loadingError: false } };
this.initEditorAfterFileChange();
}
this.displayAnnotations();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { Participation } from 'app/entities/participation/participation.model';
import { CodeEditorInstructionsComponent } from 'app/exercises/programming/shared/code-editor/instructions/code-editor-instructions.component';
import { Feedback } from 'app/entities/feedback.model';
import { Course } from 'app/entities/course.model';
import { ConnectionError } from 'app/exercises/programming/shared/code-editor/service/code-editor-repository.service';

export enum CollapsableCodeEditorElement {
FileBrowser,
Expand Down Expand Up @@ -216,10 +217,18 @@ export class CodeEditorContainerComponent implements ComponentCanDeactivate {
/**
* Show an error as an alert in the top of the editor html.
* Used by other components to display errors.
* The error must already be provided translated by the emitting component.
* @param error the translation key of the error that should be displayed
*/
onError(error: any) {
this.alertService.error(`artemisApp.editor.errors.${error as string}`);
let errorTranslationKey: string;
const translationParams = { connectionIssue: '' };
if (!error.includes(ConnectionError.message)) {
errorTranslationKey = error;
} else {
translationParams.connectionIssue = this.translateService.instant(`artemisApp.editor.errors.${ConnectionError.message}`);
errorTranslationKey = error.replaceAll(ConnectionError.message, '');
}
this.alertService.error(`artemisApp.editor.errors.${errorTranslationKey}`, translationParams);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const handleErrorResponse = <T>(conflictService: CodeEditorConflictStateService)
if (err.status === 409) {
conflictService.notifyConflictState(GitConflictState.CHECKOUT_CONFLICT);
}
if (err.status === 0) {
if (err.status === 0 || err.status === 504) {
return throwError(() => new ConnectionError());
}
return throwError(() => err);
Expand Down
13 changes: 6 additions & 7 deletions src/main/webapp/i18n/de/editor.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,10 @@
"errors": {
"participationNotFound": "Deine Teilnahme konnte nicht gefunden werden.",
"exerciseNotFound": "Die Übung konnte nicht gefunden werden.",
"saveFailed": "Eine oder mehrere Dateien konnte nicht gespeichert werden.",
"submitFailed": "Eine oder mehrere Dateien konnte nicht abgesendet werden.",
"refreshFailed": "Die Aktualisierung ist fehlgeschlagen",
"saveFailed": "Speichern ist fehlgeschlagen. {{ connectionIssue }}",
"submitFailed": "Absenden ist fehlgeschlagen. {{ connectionIssue }}",
"refreshFailed": "Aktualisieren ist fehlgeschlagen. {{ connectionIssue }}",
"loadingFailed": "Das Laden der Datei ist fehlgeschlagen. {{ connectionIssue }}",
"noPermissions": "Du verfügst nicht über die notwendigen Berechtigungen.",
"checkoutFailed": "Dein Git-Repository konnte nicht ausgecheckt werden.",
"fileExists": "Datei/Verzeichnis Name existiert bereits. Bitte wähle einen anderen Namen.",
Expand All @@ -92,13 +93,11 @@
"failedToLoadBuildLogs": "Die Buildlogs konnten nicht geladen werden.",
"repositoryInConflict": "Dein Repository befindet sich in einem Konfliktzustand.",
"notAllowedExam": "Du kannst (nicht mehr) abgeben",
"saveFailedInternetDisconnected": "Speichern ist fehlgeschlagen. Bitte stelle eine gute Internetverbindung sicher und versuche es nochmal.",
"submitFailedInternetDisconnected": "Absenden ist fehlgeschlagen. Bitte stelle eine gute Internetverbindung sicher und versuche es nochmal.",
"refreshFailedInternetDisconnected": "Aktualisieren ist fehlgeschlagen. Bitte stelle eine gute Internetverbindung sicher und versuche es nochmal.",
"resetFailed": "Dein Repository konnte nicht zurückgesetzt werden.",
"submitBeforeStartDate": "Du kannst vor dem Startdatum keine Abgaben einreichen.",
"submitAfterDueDate": "Du kannst nach der Abgabefrist keine weiteren Abgaben einreichen.",
"submitAfterReachingSubmissionLimit": "Du hast das Abgabelimit erreicht und kannst keine weiteren Abgaben einreichen."
"submitAfterReachingSubmissionLimit": "Du hast das Abgabelimit erreicht und kannst keine weiteren Abgaben einreichen.",
"InternetDisconnected": "Bitte stelle eine stabile Internetverbindung sicher und versuche es nochmal."
},
"testStatusLabels": {
"noResult": "Keine Ergebnisse",
Expand Down
13 changes: 6 additions & 7 deletions src/main/webapp/i18n/en/editor.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,10 @@
"errors": {
"participationNotFound": "Your participation could not be found.",
"exerciseNotFound": "The exercise could not be found.",
"saveFailed": "One or more files could not be updated.",
"submitFailed": "One or more files could not be submitted.",
"refreshFailed": "Refresh has failed",
"saveFailed": "Saving failed. {{ connectionIssue }}",
"submitFailed": "Submitting failed. {{ connectionIssue }}",
"refreshFailed": "Refresh failed. {{ connectionIssue }}",
"loadingFailed": "Loading file failed. {{ connectionIssue }}",
"noPermissions": "You don't have the necessary permissions.",
"checkoutFailed": "The checkout of your git repository failed.",
"fileExists": "File/Directory name already exists. Please choose a different name.",
Expand All @@ -93,13 +94,11 @@
"failedToLoadBuildLogs": "The build logs could not be retrieved.",
"repositoryInConflict": "Your repository has entered a conflict state.",
"notAllowedExam": "You may not submit (anymore)",
"saveFailedInternetDisconnected": "Saving failed. Please make sure you have a stable internet connection and try again.",
"submitFailedInternetDisconnected": "Submit failed. Please make sure you have a stable internet connection and try again.",
"refreshFailedInternetDisconnected": "Refresh failed. Please make sure you have a stable internet connection and try again.",
"resetFailed": "Your repository could not be reset.",
"submitBeforeStartDate": "You cannot submit before the start date of the exercise.",
"submitAfterDueDate": "You cannot submit after the due date of the exercise.",
"submitAfterReachingSubmissionLimit": "You reached the submission limit and cannot participate anymore."
"submitAfterReachingSubmissionLimit": "You reached the submission limit and cannot participate anymore.",
"InternetDisconnected": "Please make sure you have a stable internet connection and try again."
},
"testStatusLabels": {
"noResult": "No results",
Expand Down
Loading
Loading