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

Calculate mean ratings across stages #63

Merged
merged 12 commits into from
Apr 12, 2024
18 changes: 12 additions & 6 deletions apps/backend/src/applications/application.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,13 @@ export class Application {
@IsObject({ each: true })
reviews: Review[];

toGetAllApplicationResponseDTO(): GetAllApplicationResponseDTO {
const meanRatingAllStages = 0; // TODO: calculate this
const meanRatingSingleStages = 0; // TODO: calculate this (should be an object)

toGetAllApplicationResponseDTO(
meanRatingAllReviews,
meanRatingResume,
meanRatingChallenge,
meanRatingTechnicalChallenge,
meanRatingInterview,
): GetAllApplicationResponseDTO {
return {
userId: this.user.id,
firstName: this.user.firstName,
Expand All @@ -75,8 +78,11 @@ export class Application {
step: this.step,
position: this.position,
createdAt: this.createdAt,
meanRatingAllStages: meanRatingAllStages,
meanRatingSingleStages: meanRatingSingleStages,
meanRatingAllReviews,
meanRatingResume,
meanRatingChallenge,
meanRatingTechnicalChallenge,
meanRatingInterview,
};
}

Expand Down
77 changes: 71 additions & 6 deletions apps/backend/src/applications/applications.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ export class ApplicationsService {
where: { user: { id: userId } },
relations: ['user'],
});

return apps;
}

Expand All @@ -109,14 +108,80 @@ export class ApplicationsService {
year: getCurrentYear(),
semester: getCurrentSemester(),
},
relations: ['user'],
relations: ['reviews'],
});

const dtos: GetAllApplicationResponseDTO[] = applications.map((app) =>
app.toGetAllApplicationResponseDTO(),
);
const allApplicationsDto = applications.map((app) => {
// Initialize variables for storing mean ratings
let meanRatingAllReviews = null;
let meanRatingResume = null;
let meanRatingChallenge = null; // Default to null for DESIGNERS
let meanRatingTechnicalChallenge = null;
let meanRatingInterview = null;

// Calculate mean rating of all reviews
if (app.reviews.length > 0) {
meanRatingAllReviews =
app.reviews.reduce((acc, review) => acc + review.rating, 0) /
app.reviews.length;
}

// Filter reviews by stage and calculate mean ratings accordingly
const resumeReviews = app.reviews.filter(
(review) => review.stage === ApplicationStage.RESUME,
);
const challengeReviews = app.reviews.filter(
(review) =>
review.stage === ApplicationStage.TECHNICAL_CHALLENGE ||
review.stage === ApplicationStage.PM_CHALLENGE,
);
const technicalChallengeReviews = app.reviews.filter(
(review) => review.stage === ApplicationStage.TECHNICAL_CHALLENGE,
);
const interviewReviews = app.reviews.filter(
(review) => review.stage === ApplicationStage.INTERVIEW,
);

// Mean rating for RESUME stage
if (resumeReviews.length > 0) {
meanRatingResume =
resumeReviews.reduce((acc, review) => acc + review.rating, 0) /
resumeReviews.length;
}

// Mean rating for CHALLENGE stage (for DEVS and PMS)
if (challengeReviews.length > 0) {
meanRatingChallenge =
challengeReviews.reduce((acc, review) => acc + review.rating, 0) /
challengeReviews.length;
}

// Mean rating for TECHNICAL_CHALLENGE stage (specifically for DEVS)
if (technicalChallengeReviews.length > 0) {
meanRatingTechnicalChallenge =
technicalChallengeReviews.reduce(
(acc, review) => acc + review.rating,
0,
) / technicalChallengeReviews.length;
}

// Mean rating for INTERVIEW stage
if (interviewReviews.length > 0) {
meanRatingInterview =
interviewReviews.reduce((acc, review) => acc + review.rating, 0) /
interviewReviews.length;
}

return app.toGetAllApplicationResponseDTO(
meanRatingAllReviews,
meanRatingResume,
meanRatingChallenge,
meanRatingTechnicalChallenge,
meanRatingInterview,
);
});

return dtos;
return allApplicationsDto;
}

async findCurrent(userId: number): Promise<Application> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,17 @@ export class GetAllApplicationResponseDTO {
createdAt: Date;

@IsPositive()
meanRatingAllStages: number;
meanRatingAllReviews: number;

// TODO: Should be a JSON or similar that defines scores for each stage
@IsPositive()
meanRatingSingleStages: number;
meanRatingResume: number;

@IsPositive()
meanRatingChallenge: number;

@IsPositive()
meanRatingTechnicalChallenge: number;

@IsPositive()
meanRatingInterview: number;
}
21 changes: 15 additions & 6 deletions apps/backend/src/reviews/dto/submit-review.request.dto.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
import { IsEnum, IsPositive, IsString } from 'class-validator';
import { Rating, Stage } from '../types';
import {
IsEnum,
IsNumber,
Min,
Max,
IsPositive,
IsString,
} from 'class-validator';
import { ApplicationStage } from '../../applications/types';

export class SubmitReviewRequestDTO {
@IsPositive()
applicantId: number;

@IsEnum(Stage)
stage: Stage;
@IsEnum(ApplicationStage)
stage: ApplicationStage;

@IsEnum(Rating)
rating: Rating;
@IsNumber({}, { message: 'Rating must be a valid number' })
@Min(1, { message: 'Rating must be at least 1' })
@Max(5, { message: 'Rating must be at most 5' })
rating: number;

@IsString()
content: string;
Expand Down
7 changes: 5 additions & 2 deletions apps/backend/src/reviews/review.entity.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm';
import { Application } from '../applications/application.entity';
import { Rating } from './types';
import { ApplicationStage } from '../applications/types';

@Entity()
export class Review {
Expand All @@ -21,8 +21,11 @@ export class Review {
@Column({ nullable: false })
reviewerId: number;

@Column({ nullable: false })
rating: number;

@Column({ type: 'varchar', nullable: false })
rating: Rating;
stage: ApplicationStage;

@Column({ nullable: false })
content: string;
Expand Down
7 changes: 1 addition & 6 deletions apps/backend/src/reviews/reviews.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,6 @@ export class ReviewsController {
);
}

return this.reviewsService.createReview(
req.user,
createReviewDTO.applicantId,
createReviewDTO.rating,
createReviewDTO.content,
);
return this.reviewsService.createReview(req.user, createReviewDTO);
}
}
15 changes: 8 additions & 7 deletions apps/backend/src/reviews/reviews.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { MongoRepository } from 'typeorm';
import { Review } from './review.entity';
import { Rating } from './types';
import { ApplicationsService } from '../applications/applications.service';
import { User } from '../users/user.entity';
import { SubmitReviewRequestDTO } from './dto/submit-review.request.dto';

@Injectable()
export class ReviewsService {
Expand All @@ -19,19 +19,20 @@ export class ReviewsService {
*/
async createReview(
currentUser: User,
applicantId: number,
rating: Rating,
content: string,
createReviewDTO: SubmitReviewRequestDTO,
): Promise<Review> {
const application = await this.applicationsService.findCurrent(applicantId);
const application = await this.applicationsService.findCurrent(
createReviewDTO.applicantId,
);

const review = this.reviewsRepository.create({
reviewerId: currentUser.id,
createdAt: new Date(),
updatedAt: new Date(),
application,
rating,
content,
rating: createReviewDTO.rating,
content: createReviewDTO.content,
stage: createReviewDTO.stage,
});

return this.reviewsRepository.save(review);
Expand Down
13 changes: 0 additions & 13 deletions apps/backend/src/reviews/types.ts
Original file line number Diff line number Diff line change
@@ -1,13 +0,0 @@
export enum Stage {
ROUND_ONE = 'ROUND_ONE',
ROUND_TWO = 'ROUND_TWO',
ROUND_THREE = 'ROUND_THREE',
}

export enum Rating {
STRONG_YES = 'STRONG_YES',
YES = 'YES',
NEUTRAL = 'NEUTRAL',
NO = 'NO',
STRONG = 'STRONG',
}
Loading