diff --git a/app/src/api/scaleTeam/scaleTeam.cache.service.ts b/app/src/api/scaleTeam/scaleTeam.cache.service.ts index 2c5452a6..e3895f62 100644 --- a/app/src/api/scaleTeam/scaleTeam.cache.service.ts +++ b/app/src/api/scaleTeam/scaleTeam.cache.service.ts @@ -9,11 +9,14 @@ import { import { CacheUtilService } from 'src/cache/cache.util.service'; import { DateTemplate } from 'src/dateRange/dtos/dateRange.dto'; import { ScaleTeamService } from './scaleTeam.service'; + export const EVAL_COUNT_RANKING = 'evalCountRanking'; -export const COMMENT_RANKING = 'commentRanking'; + export const AVERAGE_FEEDBACK_LENGTH = 'averageFeedbackLength'; export const AVERAGE_COMMENT_LENGTH = 'averageCommentLength'; +export const COMMENT_RANKING = 'commentRanking'; + export type EvalCountRankingSupportedDateTemplate = Extract< RankingSupportedDateTemplate, DateTemplate.TOTAL | DateTemplate.CURR_MONTH | DateTemplate.CURR_WEEK @@ -27,6 +30,11 @@ type AverageReviewLengthCache = Awaited< ReturnType | undefined >; +export type AverageReviewLengthRankingSupportedDateTemplate = Extract< + RankingSupportedDateTemplate, + DateTemplate.TOTAL | DateTemplate.CURR_MONTH | DateTemplate.CURR_WEEK +>; + @Injectable() export class ScaleTeamCacheService { constructor( @@ -79,21 +87,25 @@ export class ScaleTeamCacheService { } async getCommentRank( + dateTemplate: AverageReviewLengthRankingSupportedDateTemplate, userId: number, promo?: number, ): Promise { - return await this.cacheUtilRankingService.getRawRank({ + return await this.cacheUtilRankingService.getRank({ keyBase: COMMENT_RANKING, userId, - dateTemplate: DateTemplate.TOTAL, + dateTemplate, promo, }); } - async getCommentRanking(promo?: number): Promise { - return await this.cacheUtilRankingService.getRawRanking({ + async getCommentRanking( + dateTemplate: AverageReviewLengthRankingSupportedDateTemplate, + promo?: number, + ): Promise { + return await this.cacheUtilRankingService.getRanking({ keyBase: COMMENT_RANKING, - dateTemplate: DateTemplate.TOTAL, + dateTemplate, promo, }); } diff --git a/app/src/api/scaleTeam/scaleTeam.service.ts b/app/src/api/scaleTeam/scaleTeam.service.ts index 279dbc2c..7b71dc69 100644 --- a/app/src/api/scaleTeam/scaleTeam.service.ts +++ b/app/src/api/scaleTeam/scaleTeam.service.ts @@ -10,6 +10,7 @@ import { findAllAndLean, type QueryArgs, } from 'src/database/mongoose/database.mongoose.query'; +import type { DateRange } from 'src/dateRange/dtos/dateRange.dto'; import { EvalLogSortOrder } from 'src/page/evalLog/dtos/evalLog.dto.getEvalLog'; import type { EvalLog } from 'src/page/evalLog/models/evalLog.model'; import { CursusUserService } from '../cursusUser/cursusUser.service'; @@ -18,7 +19,10 @@ import { conditionalProjectPreview, } from '../project/db/project.database.aggregate'; import { addUserPreview, lookupUser } from '../user/db/user.database.aggregate'; -import { lookupScaleTeams } from './db/scaleTeam.database.aggregate'; +import { + evalCountDateRangeFilter, + lookupScaleTeams, +} from './db/scaleTeam.database.aggregate'; import { scale_team } from './db/scaleTeam.database.schema'; export const OUTSTANDING_FLAG_ID = 9; @@ -112,11 +116,13 @@ export class ScaleTeamService { return reviewAggr?.value ?? 0; } - async averageReviewLengthRanking( + async averageReviewLengthRankingByDateRange( field: 'comment' | 'feedback', - filter?: FilterQuery, + dateRange?: DateRange, ): Promise { - const aggregate = this.cursusUserService.aggregate(); + const aggregate = this.cursusUserService.aggregate< + Omit & { count: number } + >(); const fieldMap = { comment: 'corrector', @@ -125,18 +131,19 @@ export class ScaleTeamService { const target = fieldMap[field]; - return await aggregate + const scaleTeamFilter = { + [field]: { $ne: null }, + ...(dateRange ? evalCountDateRangeFilter(dateRange) : undefined), + }; + + const rawRanking = await aggregate .append( lookupScaleTeams('user.id', `${target}.id`, [ - { - $match: { - ...filter, - [`${field}`]: { $ne: null }, - }, - }, + { $match: scaleTeamFilter }, { $group: { _id: 'result', + count: { $count: {} }, value: { $avg: { $strLenCP: `$${field}` } }, }, }, @@ -147,14 +154,54 @@ export class ScaleTeamService { $ifNull: [{ $round: { $first: '$scale_teams.value' } }, 0], }, }) - .append(addRank()) + .sort({ value: -1 }) .append(addUserPreview('user')) .project({ _id: 0, userPreview: 1, value: 1, - rank: 1, + count: { $first: '$scale_teams.count' }, }); + + const { ranking, zeroValueRanking } = rawRanking.reduce( + ( + { ranking, prevRank, prevValue, zeroValueRanking }, + { userPreview, value, count }, + ) => { + if (dateRange || count >= 20) { + const rank = prevValue === value ? prevRank : ranking.length + 1; + + ranking.push({ + userPreview, + value, + rank, + }); + + return { + ranking, + prevRank: rank, + prevValue: value, + zeroValueRanking, + }; + } + + zeroValueRanking.push({ + userPreview, + value: 0, + rank: Number.MAX_SAFE_INTEGER, + }); + + return { ranking, prevRank, prevValue, zeroValueRanking }; + }, + { + ranking: new Array(), + prevRank: 0, + prevValue: Number.MIN_SAFE_INTEGER, + zeroValueRanking: new Array(), + }, + ); + + return [...ranking, ...zeroValueRanking]; } /** diff --git a/app/src/lambda/lambda.service.ts b/app/src/lambda/lambda.service.ts index 9e26dd62..b525ce49 100644 --- a/app/src/lambda/lambda.service.ts +++ b/app/src/lambda/lambda.service.ts @@ -190,17 +190,44 @@ export class LambdaService { DateTemplate.CURR_WEEK, ); - const commentRank = await this.scaleTeamService.averageReviewLengthRanking( - 'comment', - ); + const totalCommentRanking = + await this.scaleTeamService.averageReviewLengthRankingByDateRange( + 'comment', + ); await this.cacheUtilRankingService.setRanking( - commentRank, + totalCommentRanking, updatedAt, COMMENT_RANKING, DateTemplate.TOTAL, ); + const currMonthCommentRanking = + await this.scaleTeamService.averageReviewLengthRankingByDateRange( + 'comment', + currMonth, + ); + + await this.cacheUtilRankingService.setRanking( + currMonthCommentRanking, + updatedAt, + COMMENT_RANKING, + DateTemplate.CURR_MONTH, + ); + + const currWeekCommentRanking = + await this.scaleTeamService.averageReviewLengthRankingByDateRange( + 'comment', + currWeek, + ); + + await this.cacheUtilRankingService.setRanking( + currWeekCommentRanking, + updatedAt, + COMMENT_RANKING, + DateTemplate.CURR_WEEK, + ); + const totalLog = await this.locationService.logtimeRanking(total); await this.cacheUtilRankingService.setRanking( diff --git a/app/src/page/leaderboard/comment/leaderboard.comment.resolver.ts b/app/src/page/leaderboard/comment/leaderboard.comment.resolver.ts index 3f66e956..0ccfa470 100644 --- a/app/src/page/leaderboard/comment/leaderboard.comment.resolver.ts +++ b/app/src/page/leaderboard/comment/leaderboard.comment.resolver.ts @@ -32,7 +32,13 @@ export class LeaderboardCommentResolver { @Args() { pageNumber, pageSize, promo }: GetLeaderboardElementArgs, @Args() { dateTemplate }: DateTemplateArgs, ): Promise { - if (dateTemplate !== DateTemplate.TOTAL) { + if ( + !( + dateTemplate === DateTemplate.TOTAL || + dateTemplate === DateTemplate.CURR_MONTH || + dateTemplate === DateTemplate.CURR_WEEK + ) + ) { throw new UnsupportedDateTemplate(); } diff --git a/app/src/page/leaderboard/comment/leaderboard.comment.service.ts b/app/src/page/leaderboard/comment/leaderboard.comment.service.ts index 0f88a386..6dde26d2 100644 --- a/app/src/page/leaderboard/comment/leaderboard.comment.service.ts +++ b/app/src/page/leaderboard/comment/leaderboard.comment.service.ts @@ -1,7 +1,9 @@ import { Injectable } from '@nestjs/common'; -import { ScaleTeamCacheService } from 'src/api/scaleTeam/scaleTeam.cache.service'; +import { + AverageReviewLengthRankingSupportedDateTemplate, + ScaleTeamCacheService, +} from 'src/api/scaleTeam/scaleTeam.cache.service'; import { assertExist } from 'src/common/assertExist'; -import { DateTemplate } from 'src/dateRange/dtos/dateRange.dto'; import { LeaderboardElementDateRanged } from '../common/models/leaderboard.model'; import type { RankingByDateTemplateArgs } from '../common/types/leaderboard.rankingByDateTemplateArgs'; import { LeaderboardUtilService } from '../util/leaderboard.util.service'; @@ -18,9 +20,17 @@ export class LeaderboardCommentService { userId, paginationIndexArgs, promo, - }: RankingByDateTemplateArgs): Promise { - const rank = await this.scaleTeamCacheService.getCommentRank(userId, promo); - const ranking = await this.scaleTeamCacheService.getCommentRanking(promo); + }: RankingByDateTemplateArgs): Promise { + const rank = await this.scaleTeamCacheService.getCommentRank( + dateTemplate, + userId, + promo, + ); + + const ranking = await this.scaleTeamCacheService.getCommentRanking( + dateTemplate, + promo, + ); assertExist(ranking); diff --git a/app/src/page/leaderboard/comment/models/leaderboard.comment.model.ts b/app/src/page/leaderboard/comment/models/leaderboard.comment.model.ts index 029c48d3..a26f77a3 100644 --- a/app/src/page/leaderboard/comment/models/leaderboard.comment.model.ts +++ b/app/src/page/leaderboard/comment/models/leaderboard.comment.model.ts @@ -3,6 +3,6 @@ import { LeaderboardElementDateRanged } from '../../common/models/leaderboard.mo @ObjectType() export class LeaderboardComment { - @Field({ description: 'TOTAL 만 가능합니다' }) + @Field({ description: 'TOTAL, CURR_MONTH, CURR_WEEK 만 가능합니다' }) byDateTemplate: LeaderboardElementDateRanged; } diff --git a/app/src/schema.gql b/app/src/schema.gql index aaae8f77..2b2f7b95 100644 --- a/app/src/schema.gql +++ b/app/src/schema.gql @@ -272,7 +272,7 @@ type LeaderboardElementDateRanged { } type LeaderboardComment { - """TOTAL 만 가능합니다""" + """TOTAL, CURR_MONTH, CURR_WEEK 만 가능합니다""" byDateTemplate(pageSize: Int! = 10, pageNumber: Int! = 1, promo: Int, dateTemplate: DateTemplate!): LeaderboardElementDateRanged! }