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

BC-4375 - add list of students #4411

Merged
merged 12 commits into from
Sep 21, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,7 @@ describe('submission create (api)', () => {
expect(result.id).toBeDefined();
expect(result.timestamps.createdAt).toBeDefined();
expect(result.timestamps.lastUpdatedAt).toBeDefined();
expect(result.userData.userId).toBe(studentUser.id);
expect(result.userData.firstName).toBe('John');
expect(result.userData.lastName).toBe('Mr Doe');
expect(result.userId).toBe(studentUser.id);
});

it('should actually create the submission item', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
userFactory,
} from '@shared/testing';
import { ServerTestModule } from '@src/modules/server';
import { SubmissionItemResponse } from '../dto';
import { SubmissionsResponse } from '../dto';

const baseRouteName = '/board-submissions';
describe('submission item lookup (api)', () => {
Expand All @@ -38,7 +38,7 @@ describe('submission item lookup (api)', () => {
await app.close();
});

describe('with teacher of two submission containers filled with submission items of 2 students', () => {
describe('when user is teacher and we have 2 submission containers filled with submission items from 2 students', () => {
const setup = async () => {
await cleanupCollections(em);

Expand Down Expand Up @@ -110,6 +110,8 @@ describe('submission item lookup (api)', () => {
item12,
item21,
item22,
studentUser1,
studentUser2,
};
};
it('should return status 200', async () => {
Expand All @@ -123,24 +125,38 @@ describe('submission item lookup (api)', () => {
const { loggedInClient, submissionContainerNode1, item11, item12 } = await setup();

const response = await loggedInClient.get(`${submissionContainerNode1.id}`);
const body = response.body as SubmissionItemResponse[];
expect(body.length).toBe(2);
expect(body.map((item) => item.id)).toContain(item11.id);
expect(body.map((item) => item.id)).toContain(item12.id);
const body = response.body as SubmissionsResponse;
const { submissionItemsResponse } = body;
expect(submissionItemsResponse.length).toBe(2);
expect(submissionItemsResponse.map((item) => item.id)).toContain(item11.id);
expect(submissionItemsResponse.map((item) => item.id)).toContain(item12.id);
});

it('should return all items from container 2 as teacher', async () => {
const { loggedInClient, submissionContainerNode2, item21, item22 } = await setup();

const response = await loggedInClient.get(`${submissionContainerNode2.id}`);
const body = response.body as SubmissionItemResponse[];
expect(body.length).toBe(2);
expect(body.map((item) => item.id)).toContain(item21.id);
expect(body.map((item) => item.id)).toContain(item22.id);
const body = response.body as SubmissionsResponse;
const { submissionItemsResponse } = body;
expect(submissionItemsResponse.length).toBe(2);
expect(submissionItemsResponse.map((item) => item.id)).toContain(item21.id);
expect(submissionItemsResponse.map((item) => item.id)).toContain(item22.id);
});

it('should return list of students', async () => {
const { loggedInClient, submissionContainerNode1, studentUser1, studentUser2 } = await setup();

const response = await loggedInClient.get(`${submissionContainerNode1.id}`);
const body = response.body as SubmissionsResponse;
const { users } = body;
expect(users.length).toBe(2);
const userIds = users.map((user) => user.userId);
expect(userIds).toContain(studentUser1.id);
expect(userIds).toContain(studentUser2.id);
});
});

describe('with student of a submission container filled with 2 items', () => {
describe('when user is student and we have a submission container element filled with 2 submission items', () => {
const setup = async () => {
await cleanupCollections(em);

Expand Down Expand Up @@ -194,13 +210,14 @@ describe('submission item lookup (api)', () => {
const { loggedInClient, submissionContainerNode, item1 } = await setup();

const response = await loggedInClient.get(`${submissionContainerNode.id}`);
const body = response.body as SubmissionItemResponse[];
expect(body.length).toBe(1);
expect(body[0].id).toBe(item1.id);
const body = response.body as SubmissionsResponse;
const { submissionItemsResponse } = body;
expect(submissionItemsResponse.length).toBe(1);
expect(submissionItemsResponse[0].id).toBe(item1.id);
});
});

describe('with invalid user', () => {
describe('when user is invalid', () => {
const setup = async () => {
await cleanupCollections(em);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,11 @@ import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { ApiValidationError } from '@shared/common';
import { ICurrentUser } from '@src/modules/authentication';
import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator';
import { SubmissionsResponse } from '@src/modules/board/controller/dto/submission-item/submissions.response';
import { CardUc } from '../uc';
import { ElementUc } from '../uc/element.uc';
import { SubmissionItemUc } from '../uc/submission-item.uc';
import {
SubmissionContainerUrlParams,
SubmissionItemResponse,
SubmissionItemUrlParams,
UpdateSubmissionItemBodyParams,
} from './dto';
import { SubmissionContainerUrlParams, SubmissionItemUrlParams, UpdateSubmissionItemBodyParams } from './dto';
import { SubmissionItemResponseMapper } from './mapper';

@ApiTags('Board Submission')
Expand All @@ -25,17 +21,22 @@ export class BoardSubmissionController {
) {}

@ApiOperation({ summary: 'Get a list of submission items by their parent container.' })
@ApiResponse({ status: 200, type: [SubmissionItemResponse] })
@ApiResponse({ status: 200, type: SubmissionsResponse })
@ApiResponse({ status: 400, type: ApiValidationError })
@ApiResponse({ status: 403, type: ForbiddenException })
@Get(':submissionContainerId')
async getSubmissionItems(
@CurrentUser() currentUser: ICurrentUser,
@Param() urlParams: SubmissionContainerUrlParams
): Promise<SubmissionItemResponse[]> {
const items = await this.submissionItemUc.findSubmissionItems(currentUser.userId, urlParams.submissionContainerId);
): Promise<SubmissionsResponse> {
const { submissionItems, users } = await this.submissionItemUc.findSubmissionItems(
currentUser.userId,
urlParams.submissionContainerId
);
const mapper = SubmissionItemResponseMapper.getInstance();
return items.map((item) => mapper.mapToResponse(item));
const response = mapper.mapToResponse(submissionItems, users);

return response;
}

@ApiOperation({ summary: 'Update a single submission item.' })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export * from './submission-container.url.params';
export * from './create-submission-item.body.params';
export * from './submission-item.response';
export * from './submission-item.url.params';
export * from './submissions.response';
export * from './update-submission-item.body.params';
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { ApiProperty } from '@nestjs/swagger';
import { TimestampsResponse } from '../timestamps.response';
import { UserDataResponse } from '../user-data.response';

export class SubmissionItemResponse {
constructor({ id, timestamps, completed, userData }: SubmissionItemResponse) {
constructor({ id, timestamps, completed, userId }: SubmissionItemResponse) {
this.id = id;
this.timestamps = timestamps;
this.completed = completed;
this.userData = userData;
this.userId = userId;
}

@ApiProperty({ pattern: '[a-f0-9]{24}' })
Expand All @@ -19,6 +18,6 @@ export class SubmissionItemResponse {
@ApiProperty()
completed: boolean;

@ApiProperty()
userData: UserDataResponse;
@ApiProperty({ pattern: '[a-f0-9]{24}' })
userId: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { SubmissionItemResponse, UserDataResponse } from '@src/modules/board/controller/dto';
import { ApiProperty } from '@nestjs/swagger';

export class SubmissionsResponse {
constructor(submissionItemsResponse: SubmissionItemResponse[], users: UserDataResponse[]) {
this.submissionItemsResponse = submissionItemsResponse;
this.users = users;
}

@ApiProperty({
type: [SubmissionItemResponse],
})
submissionItemsResponse: SubmissionItemResponse[];

@ApiProperty({
type: [UserDataResponse],
})
users: UserDataResponse[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export class ElementController {
bodyParams.completed
);
const mapper = SubmissionItemResponseMapper.getInstance();
const response = mapper.mapToResponse(submissionItem);
const response = mapper.mapSubmissionsToResponse(submissionItem);

return response;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { SubmissionItem } from '@shared/domain';
import { SubmissionItemResponse, TimestampsResponse, UserDataResponse } from '../dto';
import { SubmissionItem, UserBoardRoles } from '@shared/domain';
import { SubmissionItemResponse, SubmissionsResponse, TimestampsResponse, UserDataResponse } from '../dto';

export class SubmissionItemResponseMapper {
private static instance: SubmissionItemResponseMapper;
Expand All @@ -12,22 +12,37 @@ export class SubmissionItemResponseMapper {
return SubmissionItemResponseMapper.instance;
}

public mapToResponse(submissionItem: SubmissionItem): SubmissionItemResponse {
public mapToResponse(submissionItems: SubmissionItem[], users: UserBoardRoles[]): SubmissionsResponse {
const submissionItemsResponse: SubmissionItemResponse[] = submissionItems.map((item) =>
this.mapSubmissionsToResponse(item)
);
const usersResponse: UserDataResponse[] = users.map((user) => this.mapUsersToResponse(user));

const response = new SubmissionsResponse(submissionItemsResponse, usersResponse);

return response;
}

public mapSubmissionsToResponse(submissionItem: SubmissionItem): SubmissionItemResponse {
const result = new SubmissionItemResponse({
completed: submissionItem.completed,
id: submissionItem.id,
timestamps: new TimestampsResponse({
lastUpdatedAt: submissionItem.updatedAt,
createdAt: submissionItem.createdAt,
}),
userData: new UserDataResponse({
// TODO: put valid user info here which comes from the submission owner
firstName: 'John',
lastName: 'Mr Doe',
userId: submissionItem.userId,
}),
userId: submissionItem.userId,
});

return result;
}

private mapUsersToResponse(user: UserBoardRoles) {
const result = new UserDataResponse({
userId: user.userId,
firstName: user.firstName || '',
lastName: user.lastName || '',
});
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ describe(BoardDoAuthorizableService.name, () => {
teacherId: teacher.id,
substitutionTeacherId: substitutionTeacher.id,
studentIds: students.map((s) => s.id),
teacher,
substitutionTeacher,
students,
};
};

Expand Down Expand Up @@ -131,6 +134,33 @@ describe(BoardDoAuthorizableService.name, () => {
expect(userPermissions[studentIds[2]]).toEqual([BoardRoles.READER]);
expect(userRoleEnums[studentIds[2]]).toEqual(UserRoleEnum.STUDENT);
});

it('should return the users with their names', async () => {
const { board, teacher, substitutionTeacher, students } = setup();

const boardDoAuthorizable = await service.getBoardAuthorizable(board);
const firstNames = boardDoAuthorizable.users.reduce((map, user) => {
map[user.userId] = user.firstName;
return map;
}, {});

const lastNames = boardDoAuthorizable.users.reduce((map, user) => {
map[user.userId] = user.lastName;
return map;
}, {});

expect(boardDoAuthorizable.users).toHaveLength(5);
expect(firstNames[teacher.id]).toEqual(teacher.firstName);
expect(lastNames[teacher.id]).toEqual(teacher.lastName);
expect(firstNames[substitutionTeacher.id]).toEqual(substitutionTeacher.firstName);
expect(lastNames[substitutionTeacher.id]).toEqual(substitutionTeacher.lastName);
expect(firstNames[students[0].id]).toEqual(students[0].firstName);
expect(lastNames[students[0].id]).toEqual(students[0].lastName);
expect(firstNames[students[1].id]).toEqual(students[1].firstName);
expect(lastNames[students[1].id]).toEqual(students[1].lastName);
expect(firstNames[students[2].id]).toEqual(students[2].firstName);
expect(lastNames[students[2].id]).toEqual(students[2].lastName);
});
});

describe('when trying to create a boardDoAuthorizable on a column without a columnboard as root', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,32 @@ export class BoardDoAuthorizableService implements AuthorizationLoaderService {

private mapCourseUsersToUsergroup(course: Course): UserBoardRoles[] {
const users = [
...course.getTeacherIds().map((userId) => {
return { userId, roles: [BoardRoles.EDITOR], userRoleEnum: UserRoleEnum.TEACHER };
...course.getTeachersList().map((user) => {
return {
userId: user.id,
firstName: user.firstName,
lastName: user.lastName,
roles: [BoardRoles.EDITOR],
userRoleEnum: UserRoleEnum.TEACHER,
};
}),
...course.getSubstitutionTeacherIds().map((userId) => {
return { userId, roles: [BoardRoles.EDITOR], userRoleEnum: UserRoleEnum.SUBSTITUTION_TEACHER };
...course.getSubstitutionTeachersList().map((user) => {
return {
userId: user.id,
firstName: user.firstName,
lastName: user.lastName,
roles: [BoardRoles.EDITOR],
userRoleEnum: UserRoleEnum.SUBSTITUTION_TEACHER,
};
}),
...course.getStudentIds().map((userId) => {
return { userId, roles: [BoardRoles.READER], userRoleEnum: UserRoleEnum.STUDENT };
...course.getStudentsList().map((user) => {
return {
userId: user.id,
firstName: user.firstName,
lastName: user.lastName,
roles: [BoardRoles.READER],
userRoleEnum: UserRoleEnum.STUDENT,
};
}),
];
return users;
Expand Down
28 changes: 17 additions & 11 deletions apps/server/src/modules/board/uc/submission-item.uc.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { ObjectId } from 'bson';
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { Test, TestingModule } from '@nestjs/testing';
import { BoardDoAuthorizable, BoardRoles, UserRoleEnum } from '@shared/domain';
Expand Down Expand Up @@ -54,9 +53,6 @@ describe(SubmissionItemUc.name, () => {
authorizationService = module.get(AuthorizationService);
authorizationService.checkPermission.mockImplementation(() => {});
boardDoAuthorizableService = module.get(BoardDoAuthorizableService);
boardDoAuthorizableService.getBoardAuthorizable.mockResolvedValue(
new BoardDoAuthorizable({ users: [], id: new ObjectId().toHexString() })
);
elementService = module.get(ContentElementService);
submissionItemService = module.get(SubmissionItemService);
await setupEntities();
Expand Down Expand Up @@ -102,9 +98,14 @@ describe(SubmissionItemUc.name, () => {

it('student1 should only get their own submission item', async () => {
const { user1, submissionContainerEl, submissionItem1 } = setup();
const items = await uc.findSubmissionItems(user1.id, submissionContainerEl.id);
expect(items.length).toBe(1);
expect(items[0]).toStrictEqual(submissionItem1);
const { submissionItems } = await uc.findSubmissionItems(user1.id, submissionContainerEl.id);
expect(submissionItems.length).toBe(1);
expect(submissionItems[0]).toStrictEqual(submissionItem1);
});
it('student should not get a list of users', async () => {
const { user1, submissionContainerEl } = setup();
const { users } = await uc.findSubmissionItems(user1.id, submissionContainerEl.id);
expect(users.length).toBe(0);
});
});
describe('when user is a teacher', () => {
Expand Down Expand Up @@ -140,10 +141,15 @@ describe(SubmissionItemUc.name, () => {

it('teacher should get all submission items', async () => {
const { teacher, submissionContainerEl, submissionItem1, submissionItem2 } = setup();
const items = await uc.findSubmissionItems(teacher.id, submissionContainerEl.id);
expect(items.length).toBe(2);
expect(items.map((item) => item.id)).toContain(submissionItem1.id);
expect(items.map((item) => item.id)).toContain(submissionItem2.id);
const { submissionItems } = await uc.findSubmissionItems(teacher.id, submissionContainerEl.id);
expect(submissionItems.length).toBe(2);
expect(submissionItems.map((item) => item.id)).toContain(submissionItem1.id);
expect(submissionItems.map((item) => item.id)).toContain(submissionItem2.id);
});
it('teacher should get list of students', async () => {
const { teacher, submissionContainerEl } = setup();
const { users } = await uc.findSubmissionItems(teacher.id, submissionContainerEl.id);
expect(users.length).toBe(2);
});
});
describe('when user has not an authorized role', () => {
Expand Down
Loading
Loading