Skip to content
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 @@ -56,6 +56,24 @@
</ion-row>
</ion-grid>
</ion-item>

<ion-item class="ion-no-padding" *ngIf="review?.projectBrief?.id">
<ion-grid class="ion-padding-horizontal">
<ion-row size="12" class="ion-justify-content-center ion-align-items-center">
<ion-col size="12" class="ion-align-self-center">
<ion-button fill="clear" size="small" class="project-brief-btn"
(click)="showProjectBrief()"
(keydown.enter)="showProjectBrief()"
(keydown.space)="showProjectBrief(); $event.preventDefault()"
aria-label="View Project Brief" i18n-aria-label
tabindex="0">
<ion-icon name="document-text-outline" slot="start" aria-hidden="true"></ion-icon>
<span i18n>Project Brief</span>
</ion-button>
</ion-col>
</ion-row>
</ion-grid>
</ion-item>
</ion-item-group>
</ion-list>
</ng-container>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -101,6 +102,7 @@ describe('AssessmentComponent', () => {
let shared: SharedService;
let utils: UtilsService;
let apolloSpy: jasmine.SpyObj<ApolloService>;
let modalSpy: jasmine.SpyObj<ModalController>;

const mockQuestions = [
{
Expand Down Expand Up @@ -236,6 +238,10 @@ describe('AssessmentComponent', () => {
provide: Router,
useClass: MockRouter,
},
{
provide: ModalController,
useValue: jasmine.createSpyObj('ModalController', ['create', 'dismiss']),
},
]
}).compileComponents();

Expand All @@ -256,6 +262,7 @@ describe('AssessmentComponent', () => {
apolloSpy = TestBed.inject(ApolloService) as jasmine.SpyObj<ApolloService>;
shared = TestBed.inject(SharedService);
utils = TestBed.inject(UtilsService);
modalSpy = TestBed.inject(ModalController) as jasmine.SpyObj<ModalController>;

// initialise service calls
/* assessmentSpy.getAssessment.and.returnValue(of({
Expand All @@ -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();
Expand Down
18 changes: 18 additions & 0 deletions projects/v3/src/app/components/assessment/assessment.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

const MIN_SCROLLING_PAGES = 8; // minimum number of pages to show pagination scrolling
const MAX_QUESTIONS_PER_PAGE = 8; // maximum number of questions to display per paginated view (controls pagination granularity)
Expand Down Expand Up @@ -149,6 +151,7 @@ export class AssessmentComponent implements OnInit, OnChanges, OnDestroy {
private assessmentService: AssessmentService,
private activityService: ActivityService,
private cdr: ChangeDetectorRef,
private modalController: ModalController,
) {
this.resubscribe$.pipe(
takeUntil(this.unsubscribe$),
Expand Down Expand Up @@ -1347,4 +1350,19 @@ Best regards`;
shouldShowRequiredIndicator(question: Question): boolean {
return this._isRequired(question) && (this.doAssessment || this.isPendingReview);
}

/**
* open the project brief modal for the submitter's team
*/
async showProjectBrief(): Promise<void> {
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();
}
}
15 changes: 14 additions & 1 deletion projects/v3/src/app/services/assessment.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
{
Expand Down Expand Up @@ -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();
Expand Down
26 changes: 25 additions & 1 deletion projects/v3/src/app/services/assessment.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -89,6 +90,7 @@ export interface AssessmentReview {
status: string;
modified: string;
teamName?: string;
projectBrief?: ProjectBrief;
}

@Injectable({
Expand Down Expand Up @@ -161,7 +163,7 @@ export class AssessmentService {
submitter {
name image
team {
name
id name projectBrief
}
}
answers {
Expand Down Expand Up @@ -437,6 +439,7 @@ export class AssessmentService {
status: firstSubmissionReview.status,
modified: firstSubmissionReview.modified,
teamName: firstSubmission.submitter.team?.name,
projectBrief: this._parseProjectBrief(firstSubmission.submitter.team?.projectBrief),
answers: {},
};

Expand All @@ -459,6 +462,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
*/
Expand Down