From 8c617609e085c6521d714be03627fde6c1476889 Mon Sep 17 00:00:00 2001 From: trtshen Date: Thu, 12 Feb 2026 16:55:57 +0800 Subject: [PATCH] [CORE-8148] client's review projectbrief --- .../assessment/assessment.component.html | 18 +++++++ .../assessment/assessment.component.spec.ts | 48 +++++++++++++++++++ .../assessment/assessment.component.ts | 18 +++++++ .../app/services/assessment.service.spec.ts | 15 +++++- .../v3/src/app/services/assessment.service.ts | 26 +++++++++- 5 files changed, 123 insertions(+), 2 deletions(-) diff --git a/projects/v3/src/app/components/assessment/assessment.component.html b/projects/v3/src/app/components/assessment/assessment.component.html index fd4580391..653e084fe 100644 --- a/projects/v3/src/app/components/assessment/assessment.component.html +++ b/projects/v3/src/app/components/assessment/assessment.component.html @@ -53,6 +53,24 @@ + + + + + + + + Project Brief + + + + + diff --git a/projects/v3/src/app/components/assessment/assessment.component.spec.ts b/projects/v3/src/app/components/assessment/assessment.component.spec.ts index 20efd664b..b8f9985e9 100644 --- a/projects/v3/src/app/components/assessment/assessment.component.spec.ts +++ b/projects/v3/src/app/components/assessment/assessment.component.spec.ts @@ -17,6 +17,7 @@ import { BehaviorSubject, of, Subject } from 'rxjs'; import { MockRouter } from '@testingv3/mocked.service'; import { TestUtils } from '@testingv3/utils'; import { ApolloService } from '@v3/app/services/apollo.service'; +import { ModalController } from '@ionic/angular'; class Page { get savingMessage() { @@ -101,6 +102,7 @@ describe('AssessmentComponent', () => { let shared: SharedService; let utils: UtilsService; let apolloSpy: jasmine.SpyObj; + let modalSpy: jasmine.SpyObj; const mockQuestions = [ { @@ -236,6 +238,10 @@ describe('AssessmentComponent', () => { provide: Router, useClass: MockRouter, }, + { + provide: ModalController, + useValue: jasmine.createSpyObj('ModalController', ['create', 'dismiss']), + }, ] }).compileComponents(); @@ -256,6 +262,7 @@ describe('AssessmentComponent', () => { apolloSpy = TestBed.inject(ApolloService) as jasmine.SpyObj; shared = TestBed.inject(SharedService); utils = TestBed.inject(UtilsService); + modalSpy = TestBed.inject(ModalController) as jasmine.SpyObj; // initialise service calls /* assessmentSpy.getAssessment.and.returnValue(of({ @@ -273,6 +280,47 @@ describe('AssessmentComponent', () => { expect(component).toBeTruthy(); }); + describe('showProjectBrief()', () => { + it('should open project brief modal when review has projectBrief', async () => { + const mockProjectBrief = { + id: 'brief-1', + title: 'Test Brief', + description: 'Test Description', + }; + component.review = { + id: 1, + answers: {}, + status: 'pending review', + modified: '2024-01-01', + projectBrief: mockProjectBrief, + }; + const mockModal = { present: jasmine.createSpy('present') }; + modalSpy.create.and.returnValue(Promise.resolve(mockModal as any)); + + await component.showProjectBrief(); + + expect(modalSpy.create).toHaveBeenCalledWith({ + component: jasmine.any(Function), + componentProps: { projectBrief: mockProjectBrief }, + cssClass: 'project-brief-modal', + }); + expect(mockModal.present).toHaveBeenCalled(); + }); + + it('should not open modal when review has no projectBrief', async () => { + component.review = { + id: 1, + answers: {}, + status: 'pending review', + modified: '2024-01-01', + }; + + await component.showProjectBrief(); + + expect(modalSpy.create).not.toHaveBeenCalled(); + }); + }); + describe('ngOnChanges()', () => { it('should straightaway return when assessment not loaded', () => { expect(component.ngOnChanges({})).toBeFalsy(); diff --git a/projects/v3/src/app/components/assessment/assessment.component.ts b/projects/v3/src/app/components/assessment/assessment.component.ts index be7ad5278..bb6381ced 100644 --- a/projects/v3/src/app/components/assessment/assessment.component.ts +++ b/projects/v3/src/app/components/assessment/assessment.component.ts @@ -19,6 +19,8 @@ import { Task } from '@v3/app/services/activity.service'; import { ActivityService } from '@v3/app/services/activity.service'; import { FileInput, Question, SubmitActions } from '../types/assessment'; import { FileUploadComponent } from '../file-upload/file-upload.component'; +import { ProjectBriefModalComponent, ProjectBrief } from '../project-brief-modal/project-brief-modal.component'; +import { ModalController } from '@ionic/angular'; @Component({ selector: 'app-assessment', @@ -132,6 +134,7 @@ export class AssessmentComponent implements OnInit, OnChanges, OnDestroy { private sharedService: SharedService, private assessmentService: AssessmentService, private activityService: ActivityService, + private modalController: ModalController, ) { this.resubscribe$.pipe( takeUntil(this.unsubscribe$), @@ -846,4 +849,19 @@ Best regards`; this.flashBlink(element); } } + + /** + * open the project brief modal for the submitter's team + */ + async showProjectBrief(): Promise { + if (!this.review?.projectBrief) { + return; + } + const modal = await this.modalController.create({ + component: ProjectBriefModalComponent, + componentProps: { projectBrief: this.review.projectBrief }, + cssClass: 'project-brief-modal', + }); + await modal.present(); + } } diff --git a/projects/v3/src/app/services/assessment.service.spec.ts b/projects/v3/src/app/services/assessment.service.spec.ts index e43b5ece2..564ae7cba 100644 --- a/projects/v3/src/app/services/assessment.service.spec.ts +++ b/projects/v3/src/app/services/assessment.service.spec.ts @@ -695,7 +695,15 @@ describe('AssessmentService', () => { submitter: { name: 'John Doe', image: 'profile.jpg', - team: { name: 'Team Alpha' } + team: { + id: 10, + name: 'Team Alpha', + projectBrief: JSON.stringify({ + id: 'brief-1', + title: 'Team Alpha Brief', + description: 'Brief description', + }), + } }, answers: [ { @@ -799,6 +807,11 @@ describe('AssessmentService', () => { expect(result.review.id).toBe(201); expect(result.review.status).toBe('done'); expect(result.review.teamName).toBe('Team Alpha'); + expect(result.review.projectBrief).toEqual({ + id: 'brief-1', + title: 'Team Alpha Brief', + description: 'Brief description', + }); // Verify review answers normalization expect(result.review.answers[1].answer).toBeNull(); diff --git a/projects/v3/src/app/services/assessment.service.ts b/projects/v3/src/app/services/assessment.service.ts index b1bbf6c2c..1f970cd82 100644 --- a/projects/v3/src/app/services/assessment.service.ts +++ b/projects/v3/src/app/services/assessment.service.ts @@ -12,6 +12,7 @@ import { FastFeedbackService } from './fast-feedback.service'; import { RequestService } from 'request'; import { FileInput, FileResponse } from '../components/types/assessment'; import { Choice, Question } from '@v3/components/types/assessment'; +import { ProjectBrief } from '@v3/app/components/project-brief-modal/project-brief-modal.component'; /** * @name api @@ -88,6 +89,7 @@ export interface AssessmentReview { status: string; modified: string; teamName?: string; + projectBrief?: ProjectBrief; } @Injectable({ @@ -156,7 +158,7 @@ export class AssessmentService { submitter { name image team { - name + id name projectBrief } } answers { @@ -431,6 +433,7 @@ export class AssessmentService { status: firstSubmissionReview.status, modified: firstSubmissionReview.modified, teamName: firstSubmission.submitter.team?.name, + projectBrief: this._parseProjectBrief(firstSubmission.submitter.team?.projectBrief), answers: {}, }; @@ -453,6 +456,27 @@ export class AssessmentService { return review; } + /** + * parse project brief from raw string or object + */ + private _parseProjectBrief(brief: string | object | null): ProjectBrief | null { + if (!brief) { + return null; + } + if (typeof brief === 'object') { + return brief as ProjectBrief; + } + if (typeof brief === 'string') { + try { + return JSON.parse(brief); + } catch (e) { + console.error('failed to parse project brief:', e); + return null; + } + } + return null; + } + /** * For each question that has choice (oneof & multiple), show the choice explanation in the submission if it is not empty */