From d2df7e7f046936fc18e42de94fe89a3ece3117f0 Mon Sep 17 00:00:00 2001 From: Respirayson Date: Sun, 16 Jun 2024 12:30:20 +0800 Subject: [PATCH 01/11] Add autosave functionality to localstorage --- .../question-submission-form.component.ts | 5 +++ .../session-submission-page.component.html | 3 ++ .../session-submission-page.component.ts | 38 +++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/src/web/app/components/question-submission-form/question-submission-form.component.ts b/src/web/app/components/question-submission-form/question-submission-form.component.ts index 21ff6f00d99..3891e635e98 100644 --- a/src/web/app/components/question-submission-form/question-submission-form.component.ts +++ b/src/web/app/components/question-submission-form/question-submission-form.component.ts @@ -118,6 +118,9 @@ export class QuestionSubmissionFormComponent implements DoCheck { @Output() responsesSave: EventEmitter = new EventEmitter(); + @Output() + autoSave: EventEmitter<{ id: string, model: QuestionSubmissionFormModel }> = new EventEmitter(); + @ViewChild(ContributionQuestionConstraintComponent) private contributionQuestionConstraint!: ContributionQuestionConstraintComponent; @@ -362,6 +365,8 @@ export class QuestionSubmissionFormComponent implements DoCheck { this.updateIsValidByQuestionConstraint(); this.formModelChange.emit(this.model); + + this.autoSave.emit({ id: this.model.feedbackQuestionId, model: this.model }); } } diff --git a/src/web/app/pages-session/session-submission-page/session-submission-page.component.html b/src/web/app/pages-session/session-submission-page/session-submission-page.component.html index 85cbd840f19..f0c065388c6 100644 --- a/src/web/app/pages-session/session-submission-page/session-submission-page.component.html +++ b/src/web/app/pages-session/session-submission-page/session-submission-page.component.html @@ -123,6 +123,7 @@

{{ getRecipientName(entry.key) }}

[(isSubmitAllClicked)]="isSubmitAllClicked" [currentSelectedSessionView]="currentSelectedSessionView" [recipientId]="entry.key" + (autoSave)="handleAutoSave($event)" > @@ -151,6 +152,7 @@

There are no ungroupable questions

(deleteCommentEvent)="deleteParticipantComment(i, $event)" [isQuestionCountOne]="isQuestionCountOne" [(isSubmitAllClicked)]="isSubmitAllClicked" + (autoSave)="handleAutoSave($event)" > @@ -166,6 +168,7 @@

There are no ungroupable questions

[isQuestionCountOne]="isQuestionCountOne" [(isSubmitAllClicked)]="isSubmitAllClicked" [currentSelectedSessionView]="currentSelectedSessionView" + (autoSave)="handleAutoSave($event)" >
diff --git a/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts b/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts index 3761289f162..4a3638e91e4 100644 --- a/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts +++ b/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts @@ -130,6 +130,9 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { feedbackSessionId: string | undefined = ''; studentId: string | undefined = ''; + autoSaveTimeout: any; + autoSaveDelay = 1000; // 1 second delay + private backendUrl: string = environment.backendUrl; constructor(private route: ActivatedRoute, @@ -152,6 +155,36 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { this.timezoneService.getTzVersion(); // import timezone service to load timezone data } + handleAutoSave(event: { id: string, model: QuestionSubmissionFormModel }): void { + clearTimeout(this.autoSaveTimeout); + console.log("saving...") + this.autoSaveTimeout = setTimeout(() => { + const savedData = JSON.parse(localStorage.getItem('autosave') || '{}'); + const clonedModel = { + ...event.model, + hasResponseChangedForRecipients: Array.from(event.model.hasResponseChangedForRecipients.entries()), + isTabExpandedForRecipients: Array.from(event.model.isTabExpandedForRecipients.entries()), + }; + savedData[event.id] = clonedModel; + localStorage.setItem('autosave', JSON.stringify(savedData)); + }, this.autoSaveDelay); + console.log(event) + } + + loadAutoSavedData(questionId: string): void { + const savedData = JSON.parse(localStorage.getItem('autosave') || '{}'); + const savedModel = savedData[questionId]; + + if (savedModel) { + const index = this.questionSubmissionForms.findIndex(q => q.feedbackQuestionId === questionId); + if (index !== -1) { + savedModel.hasResponseChangedForRecipients = new Map(savedModel.hasResponseChangedForRecipients); + savedModel.isTabExpandedForRecipients = new Map(savedModel.isTabExpandedForRecipients); + this.questionSubmissionForms[index] = savedModel; + } + } + } + ngOnInit(): void { this.route.data.pipe( tap((data: any) => { @@ -819,6 +852,10 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { recipientSubmissionFormModel.commentByGiver = undefined; } }); + + const savedData = JSON.parse(localStorage.getItem('autosave') || '{}'); + delete savedData[questionSubmissionFormModel.feedbackQuestionId]; + localStorage.setItem('autosave', JSON.stringify(savedData)); }), switchMap(() => forkJoin(questionSubmissionFormModel.recipientSubmissionForms @@ -992,6 +1029,7 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { if (event && event.visible && !questionSubmissionForm.isLoaded && !questionSubmissionForm.isLoading) { questionSubmissionForm.isLoading = true; this.loadFeedbackQuestionRecipientsForQuestion(questionSubmissionForm); + this.loadAutoSavedData(questionSubmissionForm.feedbackQuestionId) } } From 42ab8854958ea8977516cf3fdaf46b1097178db7 Mon Sep 17 00:00:00 2001 From: Respirayson Date: Sun, 16 Jun 2024 23:41:12 +0800 Subject: [PATCH 02/11] Add reset functionality to reverse autosave data --- .../question-submission-form.component.html | 2 + .../question-submission-form.component.ts | 20 ++- .../session-submission-page.component.html | 6 + .../session-submission-page.component.ts | 135 +++++++++++++++++- 4 files changed, 159 insertions(+), 4 deletions(-) diff --git a/src/web/app/components/question-submission-form/question-submission-form.component.html b/src/web/app/components/question-submission-form/question-submission-form.component.html index 7c07540e8ee..e2d079b9840 100644 --- a/src/web/app/components/question-submission-form/question-submission-form.component.html +++ b/src/web/app/components/question-submission-form/question-submission-form.component.html @@ -241,6 +241,8 @@

Question {{ model.questionNumber }}: {{ mode {{ isQuestionCountOne ? "Submit Response" : "Submit Response for Question " + model.questionNumber }} +

diff --git a/src/web/app/components/question-submission-form/question-submission-form.component.ts b/src/web/app/components/question-submission-form/question-submission-form.component.ts index 3891e635e98..fa14a7dde0f 100644 --- a/src/web/app/components/question-submission-form/question-submission-form.component.ts +++ b/src/web/app/components/question-submission-form/question-submission-form.component.ts @@ -93,6 +93,7 @@ export class QuestionSubmissionFormComponent implements DoCheck { this.model.isTabExpandedForRecipients.set(recipient.recipientIdentifier, true); }); + this.hasResponseChanged = Array.from(this.model.hasResponseChangedForRecipients.values()).some(value => value) } @Input() @@ -121,6 +122,9 @@ export class QuestionSubmissionFormComponent implements DoCheck { @Output() autoSave: EventEmitter<{ id: string, model: QuestionSubmissionFormModel }> = new EventEmitter(); + @Output() + reset: EventEmitter = new EventEmitter(); + @ViewChild(ContributionQuestionConstraintComponent) private contributionQuestionConstraint!: ContributionQuestionConstraintComponent; @@ -171,6 +175,8 @@ export class QuestionSubmissionFormComponent implements DoCheck { visibilityStateMachine: VisibilityStateMachine; isEveryRecipientSorted: boolean = false; + autosaveTimeout: any; + constructor(private feedbackQuestionsService: FeedbackQuestionsService, private feedbackResponseService: FeedbackResponsesService) { this.visibilityStateMachine = @@ -224,6 +230,13 @@ export class QuestionSubmissionFormComponent implements DoCheck { }); } + resetForm(): void { + this.reset.emit(this.model); + this.isSaved = true; + this.hasResponseChanged = false; + clearTimeout(this.autosaveTimeout); + } + toggleQuestionTab(): void { if (this.currentSelectedSessionView === this.allSessionViews.DEFAULT) { this.model.isTabExpanded = !this.model.isTabExpanded; @@ -353,7 +366,7 @@ export class QuestionSubmissionFormComponent implements DoCheck { */ triggerRecipientSubmissionFormChange(index: number, field: string, data: any): void { if (!this.isFormsDisabled) { - this.hasResponseChanged = true; + this.isSubmitAllClickedChange.emit(false); this.model.hasResponseChangedForRecipients.set(this.model.recipientList[index].recipientIdentifier, true); @@ -367,6 +380,10 @@ export class QuestionSubmissionFormComponent implements DoCheck { this.formModelChange.emit(this.model); this.autoSave.emit({ id: this.model.feedbackQuestionId, model: this.model }); + clearTimeout(this.autosaveTimeout); + this.autosaveTimeout = setTimeout(() => { + this.hasResponseChanged = true; + }, 100); // 0.1 second to prevent people from trying to immediately reset before autosave kicks in } } @@ -456,6 +473,7 @@ export class QuestionSubmissionFormComponent implements DoCheck { * Triggers saving of responses for the specific question. */ saveFeedbackResponses(): void { + clearTimeout(this.autosaveTimeout); this.isSaved = true; this.hasResponseChanged = false; this.model.hasResponseChangedForRecipients.forEach( diff --git a/src/web/app/pages-session/session-submission-page/session-submission-page.component.html b/src/web/app/pages-session/session-submission-page/session-submission-page.component.html index f0c065388c6..86d9207a5ca 100644 --- a/src/web/app/pages-session/session-submission-page/session-submission-page.component.html +++ b/src/web/app/pages-session/session-submission-page/session-submission-page.component.html @@ -123,6 +123,7 @@

{{ getRecipientName(entry.key) }}

[(isSubmitAllClicked)]="isSubmitAllClicked" [currentSelectedSessionView]="currentSelectedSessionView" [recipientId]="entry.key" + (reset)="resetFeedbackResponses([$event], entry.key)" (autoSave)="handleAutoSave($event)" > @@ -134,6 +135,9 @@

{{ getRecipientName(entry.key) }}

(click)="saveResponsesForSelectedRecipientQuestions(entry.key, questionSubmissionForms)" [disabled]="isSavingResponses || isSubmissionFormsDisabled">Submit Responses for {{ getRecipientName(entry.key) }} + @@ -152,6 +156,7 @@

There are no ungroupable questions

(deleteCommentEvent)="deleteParticipantComment(i, $event)" [isQuestionCountOne]="isQuestionCountOne" [(isSubmitAllClicked)]="isSubmitAllClicked" + (reset)="resetFeedbackResponses([$event], null)" (autoSave)="handleAutoSave($event)" > @@ -168,6 +173,7 @@

There are no ungroupable questions

[isQuestionCountOne]="isQuestionCountOne" [(isSubmitAllClicked)]="isSubmitAllClicked" [currentSelectedSessionView]="currentSelectedSessionView" + (reset)="resetFeedbackResponses([$event], null)" (autoSave)="handleAutoSave($event)" > diff --git a/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts b/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts index 4a3638e91e4..96fac3a89d1 100644 --- a/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts +++ b/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts @@ -103,6 +103,7 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { intent: Intent = Intent.STUDENT_SUBMISSION; questionSubmissionForms: QuestionSubmissionFormModel[] = []; + originalQuestionSubmissionForms: QuestionSubmissionFormModel[] = []; isSavingResponses: boolean = false; isSubmissionFormsDisabled: boolean = false; @@ -131,7 +132,7 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { studentId: string | undefined = ''; autoSaveTimeout: any; - autoSaveDelay = 1000; // 1 second delay + autoSaveDelay = 100; // 0.1 second delay private backendUrl: string = environment.backendUrl; @@ -157,7 +158,6 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { handleAutoSave(event: { id: string, model: QuestionSubmissionFormModel }): void { clearTimeout(this.autoSaveTimeout); - console.log("saving...") this.autoSaveTimeout = setTimeout(() => { const savedData = JSON.parse(localStorage.getItem('autosave') || '{}'); const clonedModel = { @@ -168,7 +168,6 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { savedData[event.id] = clonedModel; localStorage.setItem('autosave', JSON.stringify(savedData)); }, this.autoSaveDelay); - console.log(event) } loadAutoSavedData(questionId: string): void { @@ -674,6 +673,20 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { }).pipe(finalize(() => { model.isLoading = false; model.isLoaded = true; + + this.originalQuestionSubmissionForms.push({ + ...model, + hasResponseChangedForRecipients: new Map(model.hasResponseChangedForRecipients), + isTabExpandedForRecipients: new Map(model.isTabExpandedForRecipients), + recipientList: model.recipientList.map(recipient => ({ ...recipient })), + recipientSubmissionForms: model.recipientSubmissionForms.map(form => ({ + ...form, + responseDetails: { ...form.responseDetails }, + commentByGiver: form.commentByGiver ? { ...form.commentByGiver } : undefined + })), + questionDetails: { ...model.questionDetails } + }); + })) .subscribe({ next: (existingResponses: FeedbackResponsesResponse) => { @@ -856,6 +869,26 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { const savedData = JSON.parse(localStorage.getItem('autosave') || '{}'); delete savedData[questionSubmissionFormModel.feedbackQuestionId]; localStorage.setItem('autosave', JSON.stringify(savedData)); + + // need to update the original model + this.originalQuestionSubmissionForms.forEach((originalModel: QuestionSubmissionFormModel) => { + if (originalModel.feedbackQuestionId === questionSubmissionFormModel.feedbackQuestionId) { + + originalModel.recipientSubmissionForms.forEach((originalRecipientSubmissionFormModel: FeedbackResponseRecipientSubmissionFormModel) => { + if (responsesMap[originalRecipientSubmissionFormModel.recipientIdentifier]) { + const correspondingResp: FeedbackResponse = + responsesMap[originalRecipientSubmissionFormModel.recipientIdentifier]; + originalRecipientSubmissionFormModel.responseId = correspondingResp.feedbackResponseId; + originalRecipientSubmissionFormModel.responseDetails = correspondingResp.responseDetails; + originalRecipientSubmissionFormModel.recipientIdentifier = correspondingResp.recipientIdentifier; + } else { + originalRecipientSubmissionFormModel.responseId = ''; + originalRecipientSubmissionFormModel.commentByGiver = undefined; + } + }); + } + + }); }), switchMap(() => forkJoin(questionSubmissionFormModel.recipientSubmissionForms @@ -1066,6 +1099,102 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { this.saveFeedbackResponses(recipientQSForms, false, recipientId); } + resetResponsesForSelectedRecipientQuestions(recipientId: string, + questionSubmissionForms: QuestionSubmissionFormModel[]): void { + + const questionsToRecipient: Set | undefined = this.recipientQuestionMap.get(recipientId); + if (!questionsToRecipient) { + this.statusMessageService.showErrorToast('Failed to reset response for this recipient. ' + + 'Please switch back to "Group by Question" view to reset responses.'); + } + const recipientQSForms = questionSubmissionForms + .filter((questionSubmissionFormModel: QuestionSubmissionFormModel) => + questionsToRecipient!.has(questionSubmissionFormModel.questionNumber)); + this.resetFeedbackResponses(recipientQSForms, recipientId); + } + + resetFeedbackResponses(questionSubmissionForms: QuestionSubmissionFormModel[], recipientId: string | null): void { + const savedData = JSON.parse(localStorage.getItem('autosave') || '{}'); + + questionSubmissionForms.forEach((questionSubmissionFormModel: QuestionSubmissionFormModel) => { + const originalSubmissionForm = this.originalQuestionSubmissionForms.find( + (originalModel: QuestionSubmissionFormModel) => originalModel.feedbackQuestionId === questionSubmissionFormModel.feedbackQuestionId + ); + + if (originalSubmissionForm) { + if (recipientId) { + // Reset only the specified recipient + questionSubmissionFormModel.recipientSubmissionForms.forEach((form, index) => { + if (form.recipientIdentifier === recipientId) { + const originalForm = originalSubmissionForm.recipientSubmissionForms.find( + (originalRecipientForm) => originalRecipientForm.recipientIdentifier === form.recipientIdentifier + ); + + if (originalForm) { + questionSubmissionFormModel.recipientSubmissionForms[index] = { + ...originalForm, + responseDetails: { ...originalForm.responseDetails }, + commentByGiver: originalForm.commentByGiver ? { ...originalForm.commentByGiver } : undefined + }; + } + } + }); + + // Update hasResponseChangedForRecipients and isTabExpandedForRecipients for the specified recipient + questionSubmissionFormModel.hasResponseChangedForRecipients.set( + recipientId, originalSubmissionForm.hasResponseChangedForRecipients.get(recipientId) ?? false + ); + questionSubmissionFormModel.isTabExpandedForRecipients.set( + recipientId, originalSubmissionForm.isTabExpandedForRecipients.get(recipientId) ?? true + ); + + // Remove autosave data for the specific recipient + if (savedData[questionSubmissionFormModel.feedbackQuestionId]) { + const recipientIndex = savedData[questionSubmissionFormModel.feedbackQuestionId].recipientSubmissionForms + .findIndex((form: FeedbackResponseRecipientSubmissionFormModel) => form.recipientIdentifier === recipientId); + + if (recipientIndex !== -1) { + savedData[questionSubmissionFormModel.feedbackQuestionId].recipientSubmissionForms.splice(recipientIndex, 1); + } + + if (savedData[questionSubmissionFormModel.feedbackQuestionId].recipientSubmissionForms.length === 0) { + delete savedData[questionSubmissionFormModel.feedbackQuestionId]; + } + } + } else { + // Reset the entire question + Object.assign(questionSubmissionFormModel, { + ...originalSubmissionForm, + recipientSubmissionForms: originalSubmissionForm.recipientSubmissionForms.map((form: FeedbackResponseRecipientSubmissionFormModel) => ({ + ...form, + responseDetails: { ...form.responseDetails }, + commentByGiver: form.commentByGiver ? { ...form.commentByGiver } : undefined + })), + hasResponseChangedForRecipients: new Map(originalSubmissionForm.hasResponseChangedForRecipients), + isTabExpandedForRecipients: new Map(originalSubmissionForm.isTabExpandedForRecipients), + questionDetails: { ...originalSubmissionForm.questionDetails }, + }); + + // Remove autosave data for the entire question + delete savedData[questionSubmissionFormModel.feedbackQuestionId]; + } + } + }); + + localStorage.setItem('autosave', JSON.stringify(savedData)); + } + + hasResponseChangedForRecipient(recipientId: string, + questionSubmissionForms: QuestionSubmissionFormModel[]): boolean { + const questionsToRecipient: Set | undefined = this.recipientQuestionMap.get(recipientId); + if (!questionsToRecipient) { + return false; + } + return questionSubmissionForms.some((questionSubmissionFormModel: QuestionSubmissionFormModel) => + questionsToRecipient.has(questionSubmissionFormModel.questionNumber) + && questionSubmissionFormModel.hasResponseChangedForRecipients.get(recipientId)); + } + private addQuestionForRecipient(recipientId: string, questionId: any): void { if (this.recipientQuestionMap.has(recipientId)) { this.recipientQuestionMap.get(recipientId)!.add(questionId); From 3a6eab66fdaa99376a00aa0bc60d28d650a958e6 Mon Sep 17 00:00:00 2001 From: Respirayson Date: Mon, 17 Jun 2024 01:59:36 +0800 Subject: [PATCH 03/11] Remove unnecessary comments --- .../session-submission-page.component.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts b/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts index 96fac3a89d1..43f8a540200 100644 --- a/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts +++ b/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts @@ -870,7 +870,6 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { delete savedData[questionSubmissionFormModel.feedbackQuestionId]; localStorage.setItem('autosave', JSON.stringify(savedData)); - // need to update the original model this.originalQuestionSubmissionForms.forEach((originalModel: QuestionSubmissionFormModel) => { if (originalModel.feedbackQuestionId === questionSubmissionFormModel.feedbackQuestionId) { @@ -1123,7 +1122,6 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { if (originalSubmissionForm) { if (recipientId) { - // Reset only the specified recipient questionSubmissionFormModel.recipientSubmissionForms.forEach((form, index) => { if (form.recipientIdentifier === recipientId) { const originalForm = originalSubmissionForm.recipientSubmissionForms.find( @@ -1140,7 +1138,6 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { } }); - // Update hasResponseChangedForRecipients and isTabExpandedForRecipients for the specified recipient questionSubmissionFormModel.hasResponseChangedForRecipients.set( recipientId, originalSubmissionForm.hasResponseChangedForRecipients.get(recipientId) ?? false ); @@ -1148,7 +1145,6 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { recipientId, originalSubmissionForm.isTabExpandedForRecipients.get(recipientId) ?? true ); - // Remove autosave data for the specific recipient if (savedData[questionSubmissionFormModel.feedbackQuestionId]) { const recipientIndex = savedData[questionSubmissionFormModel.feedbackQuestionId].recipientSubmissionForms .findIndex((form: FeedbackResponseRecipientSubmissionFormModel) => form.recipientIdentifier === recipientId); @@ -1162,7 +1158,6 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { } } } else { - // Reset the entire question Object.assign(questionSubmissionFormModel, { ...originalSubmissionForm, recipientSubmissionForms: originalSubmissionForm.recipientSubmissionForms.map((form: FeedbackResponseRecipientSubmissionFormModel) => ({ @@ -1175,7 +1170,6 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { questionDetails: { ...originalSubmissionForm.questionDetails }, }); - // Remove autosave data for the entire question delete savedData[questionSubmissionFormModel.feedbackQuestionId]; } } From 8d3c6ee985be229efbb4d2cf08f1c516dd7c2b77 Mon Sep 17 00:00:00 2001 From: Respirayson Date: Sat, 29 Jun 2024 13:18:51 +0800 Subject: [PATCH 04/11] Add new tests, fix failing tests and linting --- .../question-submission-form.component.ts | 7 +- ...ion-submission-page.component.spec.ts.snap | 160 ++++++++++++++++++ .../session-submission-page.component.html | 6 +- .../session-submission-page.component.spec.ts | 40 +++++ .../session-submission-page.component.ts | 87 +++++----- 5 files changed, 252 insertions(+), 48 deletions(-) diff --git a/src/web/app/components/question-submission-form/question-submission-form.component.ts b/src/web/app/components/question-submission-form/question-submission-form.component.ts index fa14a7dde0f..59d83d25227 100644 --- a/src/web/app/components/question-submission-form/question-submission-form.component.ts +++ b/src/web/app/components/question-submission-form/question-submission-form.component.ts @@ -93,7 +93,7 @@ export class QuestionSubmissionFormComponent implements DoCheck { this.model.isTabExpandedForRecipients.set(recipient.recipientIdentifier, true); }); - this.hasResponseChanged = Array.from(this.model.hasResponseChangedForRecipients.values()).some(value => value) + this.hasResponseChanged = Array.from(this.model.hasResponseChangedForRecipients.values()).some((value) => value); } @Input() @@ -123,7 +123,7 @@ export class QuestionSubmissionFormComponent implements DoCheck { autoSave: EventEmitter<{ id: string, model: QuestionSubmissionFormModel }> = new EventEmitter(); @Output() - reset: EventEmitter = new EventEmitter(); + resetFeedback: EventEmitter = new EventEmitter(); @ViewChild(ContributionQuestionConstraintComponent) private contributionQuestionConstraint!: ContributionQuestionConstraintComponent; @@ -231,7 +231,7 @@ export class QuestionSubmissionFormComponent implements DoCheck { } resetForm(): void { - this.reset.emit(this.model); + this.resetFeedback.emit(this.model); this.isSaved = true; this.hasResponseChanged = false; clearTimeout(this.autosaveTimeout); @@ -366,7 +366,6 @@ export class QuestionSubmissionFormComponent implements DoCheck { */ triggerRecipientSubmissionFormChange(index: number, field: string, data: any): void { if (!this.isFormsDisabled) { - this.isSubmitAllClickedChange.emit(false); this.model.hasResponseChangedForRecipients.set(this.model.recipientList[index].recipientIdentifier, true); diff --git a/src/web/app/pages-session/session-submission-page/__snapshots__/session-submission-page.component.spec.ts.snap b/src/web/app/pages-session/session-submission-page/__snapshots__/session-submission-page.component.spec.ts.snap index 5110ed14b92..5ac85d6d247 100644 --- a/src/web/app/pages-session/session-submission-page/__snapshots__/session-submission-page.component.spec.ts.snap +++ b/src/web/app/pages-session/session-submission-page/__snapshots__/session-submission-page.component.spec.ts.snap @@ -7,6 +7,7 @@ exports[`SessionSubmissionPageComponent should snap when feedback session questi Intent={[Function Object]} allSessionViews={[Function Object]} authService={[Function AuthService]} + autoSaveDelay={[Function Number]} backendUrl={[Function String]} commentService={[Function FeedbackResponseCommentService]} courseId={[Function String]} @@ -44,6 +45,7 @@ exports[`SessionSubmissionPageComponent should snap when feedback session questi moderatedQuestionId="" navigationService={[Function NavigationService]} ngbModal={[Function _NgbModal]} + originalQuestionSubmissionForms={[Function Array]} pageScrollService={[Function _PageScrollService]} personEmail="" personName="" @@ -165,6 +167,7 @@ exports[`SessionSubmissionPageComponent should snap when saving responses 1`] = Intent={[Function Object]} allSessionViews={[Function Object]} authService={[Function AuthService]} + autoSaveDelay={[Function Number]} backendUrl={[Function String]} commentService={[Function FeedbackResponseCommentService]} courseId={[Function String]} @@ -202,6 +205,7 @@ exports[`SessionSubmissionPageComponent should snap when saving responses 1`] = moderatedQuestionId="" navigationService={[Function NavigationService]} ngbModal={[Function _NgbModal]} + originalQuestionSubmissionForms={[Function Array]} pageScrollService={[Function _PageScrollService]} personEmail="" personName="" @@ -320,6 +324,7 @@ exports[`SessionSubmissionPageComponent should snap with default fields 1`] = ` Intent={[Function Object]} allSessionViews={[Function Object]} authService={[Function AuthService]} + autoSaveDelay={[Function Number]} backendUrl={[Function String]} commentService={[Function FeedbackResponseCommentService]} courseId={[Function String]} @@ -357,6 +362,7 @@ exports[`SessionSubmissionPageComponent should snap with default fields 1`] = ` moderatedQuestionId="" navigationService={[Function NavigationService]} ngbModal={[Function _NgbModal]} + originalQuestionSubmissionForms={[Function Array]} pageScrollService={[Function _PageScrollService]} personEmail="" personName="" @@ -475,6 +481,7 @@ exports[`SessionSubmissionPageComponent should snap with feedback session and us Intent={[Function Object]} allSessionViews={[Function Object]} authService={[Function AuthService]} + autoSaveDelay={[Function Number]} backendUrl={[Function String]} commentService={[Function FeedbackResponseCommentService]} courseId={[Function String]} @@ -512,6 +519,7 @@ exports[`SessionSubmissionPageComponent should snap with feedback session and us moderatedQuestionId="" navigationService={[Function NavigationService]} ngbModal={[Function _NgbModal]} + originalQuestionSubmissionForms={[Function Array]} pageScrollService={[Function _PageScrollService]} personEmail={[Function String]} personName={[Function String]} @@ -763,6 +771,8 @@ exports[`SessionSubmissionPageComponent should snap with feedback session questi Intent={[Function Object]} allSessionViews={[Function Object]} authService={[Function AuthService]} + autoSaveDelay={[Function Number]} + autoSaveTimeout={[Function Number]} backendUrl={[Function String]} commentService={[Function FeedbackResponseCommentService]} courseId={[Function String]} @@ -800,6 +810,7 @@ exports[`SessionSubmissionPageComponent should snap with feedback session questi moderatedQuestionId="" navigationService={[Function NavigationService]} ngbModal={[Function _NgbModal]} + originalQuestionSubmissionForms={[Function Array]} pageScrollService={[Function _PageScrollService]} personEmail="" personName="" @@ -1290,6 +1301,14 @@ exports[`SessionSubmissionPageComponent should snap with feedback session questi Submit Response for Question 1 + @@ -1547,6 +1566,14 @@ exports[`SessionSubmissionPageComponent should snap with feedback session questi Submit Response for Question 3 + @@ -1980,6 +2007,14 @@ exports[`SessionSubmissionPageComponent should snap with feedback session questi Submit Response for Question 4 + @@ -2195,6 +2230,14 @@ exports[`SessionSubmissionPageComponent should snap with feedback session questi Submit Response for Question 5 + @@ -2424,6 +2467,14 @@ exports[`SessionSubmissionPageComponent should snap with feedback session questi Submit Response for Question 6 + @@ -2883,6 +2934,14 @@ exports[`SessionSubmissionPageComponent should snap with feedback session questi Submit Response for Question 7 + @@ -3227,6 +3286,14 @@ exports[`SessionSubmissionPageComponent should snap with feedback session questi Submit Response for Question 8 + @@ -3509,6 +3576,14 @@ exports[`SessionSubmissionPageComponent should snap with feedback session questi Submit Response for Question 9 + @@ -3741,6 +3816,14 @@ exports[`SessionSubmissionPageComponent should snap with feedback session questi Submit Response for Question 10 + @@ -3775,6 +3858,7 @@ exports[`SessionSubmissionPageComponent should snap with feedback session questi Intent={[Function Object]} allSessionViews={[Function Object]} authService={[Function AuthService]} + autoSaveDelay={[Function Number]} backendUrl={[Function String]} commentService={[Function FeedbackResponseCommentService]} courseId={[Function String]} @@ -3812,6 +3896,7 @@ exports[`SessionSubmissionPageComponent should snap with feedback session questi moderatedQuestionId="" navigationService={[Function NavigationService]} ngbModal={[Function _NgbModal]} + originalQuestionSubmissionForms={[Function Array]} pageScrollService={[Function _PageScrollService]} personEmail="" personName="" @@ -4308,6 +4393,14 @@ exports[`SessionSubmissionPageComponent should snap with feedback session questi Submit Response for Question 1 + @@ -4563,6 +4656,13 @@ exports[`SessionSubmissionPageComponent should snap with feedback session questi Submit Response for Question 3 + @@ -5002,6 +5102,14 @@ exports[`SessionSubmissionPageComponent should snap with feedback session questi Submit Response for Question 4 + @@ -5219,6 +5327,14 @@ exports[`SessionSubmissionPageComponent should snap with feedback session questi Submit Response for Question 5 + @@ -5450,6 +5566,14 @@ exports[`SessionSubmissionPageComponent should snap with feedback session questi Submit Response for Question 6 + @@ -5911,6 +6035,14 @@ exports[`SessionSubmissionPageComponent should snap with feedback session questi Submit Response for Question 7 + @@ -6265,6 +6397,14 @@ exports[`SessionSubmissionPageComponent should snap with feedback session questi Submit Response for Question 8 + @@ -6549,6 +6689,14 @@ exports[`SessionSubmissionPageComponent should snap with feedback session questi Submit Response for Question 9 + @@ -6782,6 +6930,14 @@ exports[`SessionSubmissionPageComponent should snap with feedback session questi Submit Response for Question 10 + @@ -6817,6 +6973,7 @@ exports[`SessionSubmissionPageComponent should snap with user that is logged in Intent={[Function Object]} allSessionViews={[Function Object]} authService={[Function AuthService]} + autoSaveDelay={[Function Number]} backendUrl={[Function String]} commentService={[Function FeedbackResponseCommentService]} courseId={[Function String]} @@ -6854,6 +7011,7 @@ exports[`SessionSubmissionPageComponent should snap with user that is logged in moderatedQuestionId="" navigationService={[Function NavigationService]} ngbModal={[Function _NgbModal]} + originalQuestionSubmissionForms={[Function Array]} pageScrollService={[Function _PageScrollService]} personEmail="" personName={[Function String]} @@ -6973,6 +7131,7 @@ exports[`SessionSubmissionPageComponent should snap with user that is not logged Intent={[Function Object]} allSessionViews={[Function Object]} authService={[Function AuthService]} + autoSaveDelay={[Function Number]} backendUrl={[Function String]} commentService={[Function FeedbackResponseCommentService]} courseId={[Function String]} @@ -7010,6 +7169,7 @@ exports[`SessionSubmissionPageComponent should snap with user that is not logged moderatedQuestionId="" navigationService={[Function NavigationService]} ngbModal={[Function _NgbModal]} + originalQuestionSubmissionForms={[Function Array]} pageScrollService={[Function _PageScrollService]} personEmail="" personName={[Function String]} diff --git a/src/web/app/pages-session/session-submission-page/session-submission-page.component.html b/src/web/app/pages-session/session-submission-page/session-submission-page.component.html index 86d9207a5ca..136122847ee 100644 --- a/src/web/app/pages-session/session-submission-page/session-submission-page.component.html +++ b/src/web/app/pages-session/session-submission-page/session-submission-page.component.html @@ -123,7 +123,7 @@

{{ getRecipientName(entry.key) }}

[(isSubmitAllClicked)]="isSubmitAllClicked" [currentSelectedSessionView]="currentSelectedSessionView" [recipientId]="entry.key" - (reset)="resetFeedbackResponses([$event], entry.key)" + (resetFeedback)="resetFeedbackResponses([$event], entry.key)" (autoSave)="handleAutoSave($event)" > @@ -156,7 +156,7 @@

There are no ungroupable questions

(deleteCommentEvent)="deleteParticipantComment(i, $event)" [isQuestionCountOne]="isQuestionCountOne" [(isSubmitAllClicked)]="isSubmitAllClicked" - (reset)="resetFeedbackResponses([$event], null)" + (resetFeedback)="resetFeedbackResponses([$event], null)" (autoSave)="handleAutoSave($event)" > @@ -173,7 +173,7 @@

There are no ungroupable questions

[isQuestionCountOne]="isQuestionCountOne" [(isSubmitAllClicked)]="isSubmitAllClicked" [currentSelectedSessionView]="currentSelectedSessionView" - (reset)="resetFeedbackResponses([$event], null)" + (resetFeedback)="resetFeedbackResponses([$event], null)" (autoSave)="handleAutoSave($event)" > diff --git a/src/web/app/pages-session/session-submission-page/session-submission-page.component.spec.ts b/src/web/app/pages-session/session-submission-page/session-submission-page.component.spec.ts index e83ce3462dd..fc29b7e9788 100644 --- a/src/web/app/pages-session/session-submission-page/session-submission-page.component.spec.ts +++ b/src/web/app/pages-session/session-submission-page/session-submission-page.component.spec.ts @@ -697,6 +697,12 @@ describe('SessionSubmissionPageComponent', () => { expect(fixture).toMatchSnapshot(); }); + it('should store value in localStorage', () => { + // Use localStorage methods as usual + localStorage.setItem('myKey', 'myValue'); + expect(localStorage.getItem('myKey')).toBe('myValue'); + }); + it('should snap when feedback session questions have failed to load', () => { component.retryAttempts = 0; component.hasFeedbackSessionQuestionsLoadingFailed = true; @@ -1281,4 +1287,38 @@ describe('SessionSubmissionPageComponent', () => { expect(commentSpy).toHaveBeenLastCalledWith(expectedId, Intent.STUDENT_SUBMISSION, { key: testQueryParams.key, moderatedperson: '' }); }); + + it('should autosave data to localStorage', () => { + const questionId = 'feedback-question-id-mcq'; + const model: QuestionSubmissionFormModel = deepCopy(testMcqQuestionSubmissionForm); + model.hasResponseChangedForRecipients = new Map().set('r1', true); + model.isTabExpandedForRecipients = new Map().set('r1', true); + const event = { id: questionId, model }; + const setItemSpy = jest.spyOn(Storage.prototype, 'setItem'); + + jest.useFakeTimers(); + component.handleAutoSave(event); + jest.advanceTimersByTime(component.autoSaveDelay); + + expect(setItemSpy).toHaveBeenCalled(); + jest.useRealTimers(); + }); + + it('should load autosaved data from localStorage', () => { + const questionId = 'feedback-question-id-mcq'; + const savedModel: any = deepCopy(testMcqQuestionSubmissionForm); + savedModel.hasResponseChangedForRecipients = Array.from(new Map().set('r1', true).entries()); + savedModel.isTabExpandedForRecipients = Array.from(new Map().set('r1', true).entries()); + + const getItemSpy = jest.spyOn(Storage.prototype, 'getItem') + .mockReturnValue(JSON.stringify({ [questionId]: savedModel })); + + component.questionSubmissionForms = [deepCopy(testMcqQuestionSubmissionForm)]; + + component.loadAutoSavedData(questionId); + + expect(component.questionSubmissionForms[0].hasResponseChangedForRecipients.get('r1')).toBe(true); + expect(component.questionSubmissionForms[0].isTabExpandedForRecipients.get('r1')).toBe(true); + expect(getItemSpy).toHaveBeenCalledWith('autosave'); + }); }); diff --git a/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts b/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts index 43f8a540200..466cb5264a8 100644 --- a/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts +++ b/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts @@ -175,7 +175,7 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { const savedModel = savedData[questionId]; if (savedModel) { - const index = this.questionSubmissionForms.findIndex(q => q.feedbackQuestionId === questionId); + const index = this.questionSubmissionForms.findIndex((q) => q.feedbackQuestionId === questionId); if (index !== -1) { savedModel.hasResponseChangedForRecipients = new Map(savedModel.hasResponseChangedForRecipients); savedModel.isTabExpandedForRecipients = new Map(savedModel.isTabExpandedForRecipients); @@ -678,13 +678,13 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { ...model, hasResponseChangedForRecipients: new Map(model.hasResponseChangedForRecipients), isTabExpandedForRecipients: new Map(model.isTabExpandedForRecipients), - recipientList: model.recipientList.map(recipient => ({ ...recipient })), - recipientSubmissionForms: model.recipientSubmissionForms.map(form => ({ + recipientList: model.recipientList.map((recipient) => ({ ...recipient })), + recipientSubmissionForms: model.recipientSubmissionForms.map((form) => ({ ...form, responseDetails: { ...form.responseDetails }, - commentByGiver: form.commentByGiver ? { ...form.commentByGiver } : undefined + commentByGiver: form.commentByGiver ? { ...form.commentByGiver } : undefined, })), - questionDetails: { ...model.questionDetails } + questionDetails: { ...model.questionDetails }, }); })) @@ -872,19 +872,20 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { this.originalQuestionSubmissionForms.forEach((originalModel: QuestionSubmissionFormModel) => { if (originalModel.feedbackQuestionId === questionSubmissionFormModel.feedbackQuestionId) { - - originalModel.recipientSubmissionForms.forEach((originalRecipientSubmissionFormModel: FeedbackResponseRecipientSubmissionFormModel) => { - if (responsesMap[originalRecipientSubmissionFormModel.recipientIdentifier]) { - const correspondingResp: FeedbackResponse = - responsesMap[originalRecipientSubmissionFormModel.recipientIdentifier]; - originalRecipientSubmissionFormModel.responseId = correspondingResp.feedbackResponseId; - originalRecipientSubmissionFormModel.responseDetails = correspondingResp.responseDetails; - originalRecipientSubmissionFormModel.recipientIdentifier = correspondingResp.recipientIdentifier; - } else { - originalRecipientSubmissionFormModel.responseId = ''; - originalRecipientSubmissionFormModel.commentByGiver = undefined; - } - }); + originalModel.recipientSubmissionForms.forEach((originalRecipientSubmissionFormModel: + FeedbackResponseRecipientSubmissionFormModel) => { + if (responsesMap[originalRecipientSubmissionFormModel.recipientIdentifier]) { + const correspondingResp: FeedbackResponse = + responsesMap[originalRecipientSubmissionFormModel.recipientIdentifier]; + originalRecipientSubmissionFormModel.responseId = correspondingResp.feedbackResponseId; + originalRecipientSubmissionFormModel.responseDetails = correspondingResp.responseDetails; + originalRecipientSubmissionFormModel.recipientIdentifier = + correspondingResp.recipientIdentifier; + } else { + originalRecipientSubmissionFormModel.responseId = ''; + originalRecipientSubmissionFormModel.commentByGiver = undefined; + } + }); } }); @@ -1061,7 +1062,7 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { if (event && event.visible && !questionSubmissionForm.isLoaded && !questionSubmissionForm.isLoading) { questionSubmissionForm.isLoading = true; this.loadFeedbackQuestionRecipientsForQuestion(questionSubmissionForm); - this.loadAutoSavedData(questionSubmissionForm.feedbackQuestionId) + this.loadAutoSavedData(questionSubmissionForm.feedbackQuestionId); } } @@ -1114,45 +1115,48 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { resetFeedbackResponses(questionSubmissionForms: QuestionSubmissionFormModel[], recipientId: string | null): void { const savedData = JSON.parse(localStorage.getItem('autosave') || '{}'); - + questionSubmissionForms.forEach((questionSubmissionFormModel: QuestionSubmissionFormModel) => { const originalSubmissionForm = this.originalQuestionSubmissionForms.find( - (originalModel: QuestionSubmissionFormModel) => originalModel.feedbackQuestionId === questionSubmissionFormModel.feedbackQuestionId + (originalModel: QuestionSubmissionFormModel) => + originalModel.feedbackQuestionId === questionSubmissionFormModel.feedbackQuestionId, ); - + if (originalSubmissionForm) { if (recipientId) { questionSubmissionFormModel.recipientSubmissionForms.forEach((form, index) => { if (form.recipientIdentifier === recipientId) { const originalForm = originalSubmissionForm.recipientSubmissionForms.find( - (originalRecipientForm) => originalRecipientForm.recipientIdentifier === form.recipientIdentifier + (originalRecipientForm) => originalRecipientForm.recipientIdentifier === form.recipientIdentifier, ); - + if (originalForm) { questionSubmissionFormModel.recipientSubmissionForms[index] = { ...originalForm, responseDetails: { ...originalForm.responseDetails }, - commentByGiver: originalForm.commentByGiver ? { ...originalForm.commentByGiver } : undefined + commentByGiver: originalForm.commentByGiver ? { ...originalForm.commentByGiver } : undefined, }; } } }); - + questionSubmissionFormModel.hasResponseChangedForRecipients.set( - recipientId, originalSubmissionForm.hasResponseChangedForRecipients.get(recipientId) ?? false + recipientId, originalSubmissionForm.hasResponseChangedForRecipients.get(recipientId) ?? false, ); questionSubmissionFormModel.isTabExpandedForRecipients.set( - recipientId, originalSubmissionForm.isTabExpandedForRecipients.get(recipientId) ?? true + recipientId, originalSubmissionForm.isTabExpandedForRecipients.get(recipientId) ?? true, ); - + if (savedData[questionSubmissionFormModel.feedbackQuestionId]) { const recipientIndex = savedData[questionSubmissionFormModel.feedbackQuestionId].recipientSubmissionForms - .findIndex((form: FeedbackResponseRecipientSubmissionFormModel) => form.recipientIdentifier === recipientId); - + .findIndex((form: FeedbackResponseRecipientSubmissionFormModel) => + form.recipientIdentifier === recipientId); + if (recipientIndex !== -1) { - savedData[questionSubmissionFormModel.feedbackQuestionId].recipientSubmissionForms.splice(recipientIndex, 1); + savedData[questionSubmissionFormModel.feedbackQuestionId] + .recipientSubmissionForms.splice(recipientIndex, 1); } - + if (savedData[questionSubmissionFormModel.feedbackQuestionId].recipientSubmissionForms.length === 0) { delete savedData[questionSubmissionFormModel.feedbackQuestionId]; } @@ -1160,24 +1164,25 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { } else { Object.assign(questionSubmissionFormModel, { ...originalSubmissionForm, - recipientSubmissionForms: originalSubmissionForm.recipientSubmissionForms.map((form: FeedbackResponseRecipientSubmissionFormModel) => ({ - ...form, - responseDetails: { ...form.responseDetails }, - commentByGiver: form.commentByGiver ? { ...form.commentByGiver } : undefined - })), + recipientSubmissionForms: originalSubmissionForm.recipientSubmissionForms + .map((form: FeedbackResponseRecipientSubmissionFormModel) => ({ + ...form, + responseDetails: { ...form.responseDetails }, + commentByGiver: form.commentByGiver ? { ...form.commentByGiver } : undefined, + })), hasResponseChangedForRecipients: new Map(originalSubmissionForm.hasResponseChangedForRecipients), isTabExpandedForRecipients: new Map(originalSubmissionForm.isTabExpandedForRecipients), questionDetails: { ...originalSubmissionForm.questionDetails }, }); - + delete savedData[questionSubmissionFormModel.feedbackQuestionId]; } } }); - + localStorage.setItem('autosave', JSON.stringify(savedData)); } - + hasResponseChangedForRecipient(recipientId: string, questionSubmissionForms: QuestionSubmissionFormModel[]): boolean { const questionsToRecipient: Set | undefined = this.recipientQuestionMap.get(recipientId); From b92ff7b47eed0adc165e46001387e6fa1170cfdc Mon Sep 17 00:00:00 2001 From: Respirayson Date: Sun, 30 Jun 2024 09:50:28 +0800 Subject: [PATCH 05/11] Fix linting --- .../session-submission-page.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/web/app/pages-session/session-submission-page/session-submission-page.component.html b/src/web/app/pages-session/session-submission-page/session-submission-page.component.html index 136122847ee..5690e360bbe 100644 --- a/src/web/app/pages-session/session-submission-page/session-submission-page.component.html +++ b/src/web/app/pages-session/session-submission-page/session-submission-page.component.html @@ -135,7 +135,7 @@

{{ getRecipientName(entry.key) }}

(click)="saveResponsesForSelectedRecipientQuestions(entry.key, questionSubmissionForms)" [disabled]="isSavingResponses || isSubmissionFormsDisabled">Submit Responses for {{ getRecipientName(entry.key) }} - From f102ae10d89a6cda0e576e20101aef3ba92e720b Mon Sep 17 00:00:00 2001 From: Respirayson Date: Sun, 30 Jun 2024 10:02:01 +0800 Subject: [PATCH 06/11] Remove redundant tests --- .../session-submission-page.component.spec.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/web/app/pages-session/session-submission-page/session-submission-page.component.spec.ts b/src/web/app/pages-session/session-submission-page/session-submission-page.component.spec.ts index fc29b7e9788..389870d2cb5 100644 --- a/src/web/app/pages-session/session-submission-page/session-submission-page.component.spec.ts +++ b/src/web/app/pages-session/session-submission-page/session-submission-page.component.spec.ts @@ -697,12 +697,6 @@ describe('SessionSubmissionPageComponent', () => { expect(fixture).toMatchSnapshot(); }); - it('should store value in localStorage', () => { - // Use localStorage methods as usual - localStorage.setItem('myKey', 'myValue'); - expect(localStorage.getItem('myKey')).toBe('myValue'); - }); - it('should snap when feedback session questions have failed to load', () => { component.retryAttempts = 0; component.hasFeedbackSessionQuestionsLoadingFailed = true; From 57787f223d010764dfc856c1a1b36fb1ab4ba2cf Mon Sep 17 00:00:00 2001 From: Respirayson Date: Sat, 13 Jul 2024 20:36:52 +0800 Subject: [PATCH 07/11] Abstract localstorage methods --- .../session-submission-page.component.ts | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts b/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts index 466cb5264a8..598b9917812 100644 --- a/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts +++ b/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts @@ -159,19 +159,19 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { handleAutoSave(event: { id: string, model: QuestionSubmissionFormModel }): void { clearTimeout(this.autoSaveTimeout); this.autoSaveTimeout = setTimeout(() => { - const savedData = JSON.parse(localStorage.getItem('autosave') || '{}'); + const savedData = this.getLocalStorageItem('autosave'); const clonedModel = { ...event.model, hasResponseChangedForRecipients: Array.from(event.model.hasResponseChangedForRecipients.entries()), isTabExpandedForRecipients: Array.from(event.model.isTabExpandedForRecipients.entries()), }; savedData[event.id] = clonedModel; - localStorage.setItem('autosave', JSON.stringify(savedData)); + this.setLocalStorageItem('autosave', savedData); }, this.autoSaveDelay); } loadAutoSavedData(questionId: string): void { - const savedData = JSON.parse(localStorage.getItem('autosave') || '{}'); + const savedData = this.getLocalStorageItem('autosave'); const savedModel = savedData[questionId]; if (savedModel) { @@ -866,9 +866,9 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { } }); - const savedData = JSON.parse(localStorage.getItem('autosave') || '{}'); + const savedData = this.getLocalStorageItem('autosave'); delete savedData[questionSubmissionFormModel.feedbackQuestionId]; - localStorage.setItem('autosave', JSON.stringify(savedData)); + this.setLocalStorageItem('autosave', savedData); this.originalQuestionSubmissionForms.forEach((originalModel: QuestionSubmissionFormModel) => { if (originalModel.feedbackQuestionId === questionSubmissionFormModel.feedbackQuestionId) { @@ -1114,7 +1114,7 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { } resetFeedbackResponses(questionSubmissionForms: QuestionSubmissionFormModel[], recipientId: string | null): void { - const savedData = JSON.parse(localStorage.getItem('autosave') || '{}'); + const savedData = this.getLocalStorageItem('autosave'); questionSubmissionForms.forEach((questionSubmissionFormModel: QuestionSubmissionFormModel) => { const originalSubmissionForm = this.originalQuestionSubmissionForms.find( @@ -1180,7 +1180,7 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { } }); - localStorage.setItem('autosave', JSON.stringify(savedData)); + this.setLocalStorageItem('autosave', savedData); } hasResponseChangedForRecipient(recipientId: string, @@ -1319,4 +1319,18 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { studentId: this.studentId, }).subscribe(); } + + /** + * Utility method to get item from local storage. + */ + private getLocalStorageItem(key: string): any { + return JSON.parse(localStorage.getItem(key) || '{}'); + } + + /** + * Utility method to set item in local storage. + */ + private setLocalStorageItem(key: string, data: any): void { + localStorage.setItem(key, JSON.stringify(data)); + } } From 592e3190e9f928f36c793a90e7fb9c60caa26c65 Mon Sep 17 00:00:00 2001 From: Respirayson Date: Sun, 14 Jul 2024 01:50:02 +0800 Subject: [PATCH 08/11] Disable autosave for preview mode --- .../session-submission-page.component.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts b/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts index 598b9917812..8a8bda37aa0 100644 --- a/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts +++ b/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts @@ -157,6 +157,8 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { } handleAutoSave(event: { id: string, model: QuestionSubmissionFormModel }): void { + if (this.previewAsPerson) return; // Disable autosave in preview mode + clearTimeout(this.autoSaveTimeout); this.autoSaveTimeout = setTimeout(() => { const savedData = this.getLocalStorageItem('autosave'); @@ -171,6 +173,8 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { } loadAutoSavedData(questionId: string): void { + if (this.previewAsPerson) return; // Disable loading autosaved data in preview mode + const savedData = this.getLocalStorageItem('autosave'); const savedModel = savedData[questionId]; From e83695e513ebc43e03e5bdad088a243e48c57c5e Mon Sep 17 00:00:00 2001 From: Respirayson Date: Sun, 14 Jul 2024 01:55:42 +0800 Subject: [PATCH 09/11] Fix formatting --- .../session-submission-page.component.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts b/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts index 8a8bda37aa0..4873d857f84 100644 --- a/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts +++ b/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts @@ -157,7 +157,10 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { } handleAutoSave(event: { id: string, model: QuestionSubmissionFormModel }): void { - if (this.previewAsPerson) return; // Disable autosave in preview mode + // Disable autosave in preview mode + if (this.previewAsPerson) { + return; + } clearTimeout(this.autoSaveTimeout); this.autoSaveTimeout = setTimeout(() => { @@ -173,7 +176,10 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { } loadAutoSavedData(questionId: string): void { - if (this.previewAsPerson) return; // Disable loading autosaved data in preview mode + // Disable loading autosaved data in preview mode + if (this.previewAsPerson) { + return; + } const savedData = this.getLocalStorageItem('autosave'); const savedModel = savedData[questionId]; From b299ad3e63779ee624e8107d56cce5c6dd2df91c Mon Sep 17 00:00:00 2001 From: Respirayson Date: Tue, 16 Jul 2024 07:30:24 +0800 Subject: [PATCH 10/11] Extract autosave out into readonly field --- .../session-submission-page.component.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts b/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts index 4873d857f84..0703cd4cf40 100644 --- a/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts +++ b/src/web/app/pages-session/session-submission-page/session-submission-page.component.ts @@ -136,6 +136,8 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { private backendUrl: string = environment.backendUrl; + private readonly AUTOSAVE_KEY = 'autosave'; + constructor(private route: ActivatedRoute, private statusMessageService: StatusMessageService, private timezoneService: TimezoneService, @@ -164,14 +166,14 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { clearTimeout(this.autoSaveTimeout); this.autoSaveTimeout = setTimeout(() => { - const savedData = this.getLocalStorageItem('autosave'); + const savedData = this.getLocalStorageItem(this.AUTOSAVE_KEY); const clonedModel = { ...event.model, hasResponseChangedForRecipients: Array.from(event.model.hasResponseChangedForRecipients.entries()), isTabExpandedForRecipients: Array.from(event.model.isTabExpandedForRecipients.entries()), }; savedData[event.id] = clonedModel; - this.setLocalStorageItem('autosave', savedData); + this.setLocalStorageItem(this.AUTOSAVE_KEY, savedData); }, this.autoSaveDelay); } @@ -181,7 +183,7 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { return; } - const savedData = this.getLocalStorageItem('autosave'); + const savedData = this.getLocalStorageItem(this.AUTOSAVE_KEY); const savedModel = savedData[questionId]; if (savedModel) { @@ -876,9 +878,9 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { } }); - const savedData = this.getLocalStorageItem('autosave'); + const savedData = this.getLocalStorageItem(this.AUTOSAVE_KEY); delete savedData[questionSubmissionFormModel.feedbackQuestionId]; - this.setLocalStorageItem('autosave', savedData); + this.setLocalStorageItem(this.AUTOSAVE_KEY, savedData); this.originalQuestionSubmissionForms.forEach((originalModel: QuestionSubmissionFormModel) => { if (originalModel.feedbackQuestionId === questionSubmissionFormModel.feedbackQuestionId) { @@ -1124,7 +1126,7 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { } resetFeedbackResponses(questionSubmissionForms: QuestionSubmissionFormModel[], recipientId: string | null): void { - const savedData = this.getLocalStorageItem('autosave'); + const savedData = this.getLocalStorageItem(this.AUTOSAVE_KEY); questionSubmissionForms.forEach((questionSubmissionFormModel: QuestionSubmissionFormModel) => { const originalSubmissionForm = this.originalQuestionSubmissionForms.find( @@ -1190,7 +1192,7 @@ export class SessionSubmissionPageComponent implements OnInit, AfterViewInit { } }); - this.setLocalStorageItem('autosave', savedData); + this.setLocalStorageItem(this.AUTOSAVE_KEY, savedData); } hasResponseChangedForRecipient(recipientId: string, From 66153047deb698775c73411f0bb11bc45e295418 Mon Sep 17 00:00:00 2001 From: Respirayson Date: Tue, 16 Jul 2024 19:46:17 +0800 Subject: [PATCH 11/11] Update snapshots --- .../session-submission-page.component.spec.ts.snap | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/web/app/pages-session/session-submission-page/__snapshots__/session-submission-page.component.spec.ts.snap b/src/web/app/pages-session/session-submission-page/__snapshots__/session-submission-page.component.spec.ts.snap index 5ac85d6d247..d0f974f68b8 100644 --- a/src/web/app/pages-session/session-submission-page/__snapshots__/session-submission-page.component.spec.ts.snap +++ b/src/web/app/pages-session/session-submission-page/__snapshots__/session-submission-page.component.spec.ts.snap @@ -2,6 +2,7 @@ exports[`SessionSubmissionPageComponent should snap when feedback session questions have failed to load 1`] = `