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

Fix form responses #121

Merged
merged 3 commits into from
Mar 23, 2024
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Another example [here](https://co-pilot.dev/changelog)
### Fixed

- Fix failed tests in app and ideation due to the change from jwt token response to http cookies ([#98](https://github.com/chingu-x/chingu-dashboard-be/pull/98))
- Fix a bug in PATCH /meetings/{meetingId}/forms/{formId} where it's not accepting an array of responese (updated validation pipe, service, and tests) ([#121](https://github.com/chingu-x/chingu-dashboard-be/pull/121))

### Removed

Expand Down
4 changes: 2 additions & 2 deletions src/auth/guards/roles.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ export class RolesGuard implements CanActivate {
}
const { user } = context.switchToHttp().getRequest();

const canAccess = requiredRoles.some(
(role) => user.roles?.includes(role),
const canAccess = requiredRoles.some((role) =>
user.roles?.includes(role),
);
if (!canAccess) {
throw new ForbiddenException(
Expand Down
35 changes: 35 additions & 0 deletions src/global/dtos/FormResponse.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { ApiProperty } from "@nestjs/swagger";
import { IsOptional } from "class-validator";

export class FormResponseDto {
@ApiProperty({
description: "question id",
})
questionId: number;

@ApiProperty({
description: "choiceId, if it's a multiple choice questions",
})
@IsOptional()
optionChoiceId?: number;

@ApiProperty({
description: "for questions with a text response",
example: "Team member x landed a job this week.",
})
@IsOptional()
text?: string;

@ApiProperty({
description: "for question with yes/no answer",
example: true,
})
@IsOptional()
boolean?: boolean;

@ApiProperty({
description: "for numerical responses",
})
@IsOptional()
numeric?: number;
}
28 changes: 18 additions & 10 deletions src/pipes/form-input-validation.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
import { BadRequestException, Injectable, PipeTransform } from "@nestjs/common";
import { ResponseDto } from "../sprints/dto/create-meeting-form-response.dto";
import { FormResponseDto } from "../global/dtos/FormResponse.dto";

const responseIndex = ["response", "responses"];

@Injectable()
export class FormInputValidationPipe implements PipeTransform {
transform(value: ResponseDto): any {
transform(value: any): any {
for (const index in value) {
if (index !== "constructor") {
if (
!value[index].text &&
!value[index].numeric &&
!value[index].boolean &&
!value[index].optionChoiceId
)
if (responseIndex.includes(index)) {
if (!Array.isArray(value[index]))
throw new BadRequestException(
`All response fields are empty for question ID ${value[index].questionId}`,
`'responses' is not an array`,
);
value[index].forEach((v: FormResponseDto) => {
if (
!v.text &&
!v.numeric &&
!v.boolean &&
!v.optionChoiceId
)
throw new BadRequestException(
`All response fields are empty for question ID ${v.questionId}`,
);
});
}
}
return value;
Expand Down
5 changes: 5 additions & 0 deletions src/sprints/dto/create-checkin-form-response.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { FormResponseDto } from "../../global/dtos/FormResponse.dto";

export class CreateCheckinFormResponseDto {
responses: FormResponseDto[];
}
41 changes: 5 additions & 36 deletions src/sprints/dto/create-meeting-form-response.dto.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,14 @@
import { ApiProperty } from "@nestjs/swagger";
import { IsOptional } from "class-validator";

export class ResponseDto {
@ApiProperty({
description: "question id",
})
questionId: number;

@ApiProperty({
description: "choiceId, if it's a multiple choice questions",
})
@IsOptional()
optionChoiceId?: number;

@ApiProperty({
description: "for questions with a text response",
example: "Team member x landed a job this week.",
})
@IsOptional()
text?: string;

@ApiProperty({
description: "for question with yes/no answer",
example: true,
})
@IsOptional()
boolean?: boolean;

@ApiProperty({
description: "for numerical responses",
})
@IsOptional()
number?: number;
}
import { FormResponseDto } from "../../global/dtos/FormResponse.dto";
import { IsArray } from "class-validator";

export class CreateMeetingFormResponseDto {
@ApiProperty({
description:
"Meeting form responses e.g. Sprint review, sprint planning",
type: ResponseDto,
type: FormResponseDto,
isArray: true,
})
response: ResponseDto[];
@IsArray()
responses: FormResponseDto[];
}
2 changes: 1 addition & 1 deletion src/sprints/sprints.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ export class SprintsController {
@ApiResponse({
status: HttpStatus.BAD_REQUEST,
description:
"invalid meeting id, form id, question id(s) not found in form with a given formId",
"invalid meeting id, form id, question id(s) not found in form with a given formId, responses not an array",
type: BadRequestErrorResponse,
})
@ApiParam({
Expand Down
32 changes: 17 additions & 15 deletions src/sprints/sprints.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { UpdateAgendaDto } from "./dto/update-agenda.dto";
import { CreateMeetingFormResponseDto } from "./dto/create-meeting-form-response.dto";
import { FormsService } from "../forms/forms.service";
import { UpdateMeetingFormResponseDto } from "./dto/update-meeting-form-response.dto";
import { FormResponseDto } from "../global/dtos/FormResponse.dto";

@Injectable()
export class SprintsService {
Expand All @@ -24,22 +25,23 @@ export class SprintsService {
responses: CreateMeetingFormResponseDto | UpdateMeetingFormResponseDto,
) => {
const responsesArray = [];
const responseIndex = ["response", "responses"];
for (const index in responses) {
if (index !== "constructor") {
responsesArray.push({
questionId: responses[index].questionId,
...(responses[index].text
? { text: responses[index].text }
: { text: null }),
...(responses[index].numeric
? { numeric: responses[index].numeric }
: { numeric: null }),
...(responses[index].boolean
? { boolean: responses[index].boolean }
: { boolean: null }),
...(responses[index].optionChoiceId
? { optionChoiceId: responses[index].optionChoiceId }
: { optionChoiceId: null }),
if (responseIndex.includes(index)) {
responses[index].forEach((v: FormResponseDto) => {
responsesArray.push({
questionId: v.questionId,
...(v.text ? { text: v.text } : { text: null }),
...(v.numeric
? { numeric: v.numeric }
: { numeric: null }),
...(v.boolean
? { boolean: v.boolean }
: { boolean: null }),
...(v.optionChoiceId
? { optionChoiceId: v.optionChoiceId }
: { optionChoiceId: null }),
});
});
}
}
Expand Down
39 changes: 31 additions & 8 deletions test/sprints.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { seed } from "../prisma/seed/seed";
import { extractResCookieValueByKey } from "./utils";
import { CreateAgendaDto } from "src/sprints/dto/create-agenda.dto";
import { toBeOneOf } from "jest-extended";

expect.extend({ toBeOneOf });

describe("Sprints Controller (e2e)", () => {
Expand Down Expand Up @@ -648,13 +649,15 @@ describe("Sprints Controller (e2e)", () => {
.patch(`/voyages/sprints/meetings/${meetingId}/forms/${formId}`)
.set("Authorization", `Bearer ${userAccessToken}`)
.send({
response: {
questionId: 1,
optionChoiceId: 1,
text: "Team member x landed a job this week.",
boolean: true,
number: 1,
},
responses: [
{
questionId: 1,
optionChoiceId: 1,
text: "Team member x landed a job this week.",
boolean: true,
number: 1,
},
],
})
.expect(200)
.expect((res) => {
Expand Down Expand Up @@ -697,7 +700,27 @@ describe("Sprints Controller (e2e)", () => {
.patch(`/voyages/sprints/meetings/${meetingId}/forms/${formId}`)
.set("Authorization", `Bearer ${userAccessToken}`)
.send({
response: {
responses: [
{
questionId: 1,
optionChoiceId: 1,
text: "Team member x landed a job this week.",
boolean: true,
number: 1,
},
],
})
.expect(400);
});

it("should return 400 if responses in the body is not an array", async () => {
const meetingId = 1;
const formId = 1;
return request(app.getHttpServer())
.patch(`/voyages/sprints/meetings/${meetingId}/forms/${formId}`)
.set("Authorization", `Bearer ${userAccessToken}`)
.send({
responses: {
questionId: 1,
optionChoiceId: 1,
text: "Team member x landed a job this week.",
Expand Down
Loading
Loading