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
*/