-
Notifications
You must be signed in to change notification settings - Fork 6
fic(PM-1585): Search scorecard modifications #14
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
Changes from 2 commits
d598e22
7db8787
278a90c
dd01d79
ccefae6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,8 @@ import { | |
Query, | ||
NotFoundException, | ||
InternalServerErrorException, | ||
Res, | ||
UseInterceptors, | ||
} from '@nestjs/common'; | ||
import { | ||
ApiTags, | ||
|
@@ -24,18 +26,24 @@ import { UserRole } from 'src/shared/enums/userRole.enum'; | |
import { Scopes } from 'src/shared/decorators/scopes.decorator'; | ||
import { Scope } from 'src/shared/enums/scopes.enum'; | ||
import { | ||
ScorecardPaginatedResponseDto, | ||
ScorecardRequestDto, | ||
ScorecardResponseDto, | ||
ScorecardWithGroupResponseDto, | ||
mapScorecardRequestToDto, | ||
} from 'src/dto/scorecard.dto'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The import |
||
import { ChallengeTrack } from 'src/shared/enums/challengeTrack.enum'; | ||
import { PrismaService } from '../../shared/modules/global/prisma.service'; | ||
import { ScoreCardService } from './scorecard.service'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The import statement for |
||
import { OkResponse } from 'src/dto/common.dto'; | ||
import { Response } from 'express'; | ||
import { PaginationHeaderInterceptor } from 'src/interceptors/PaginationHeaderInterceptor'; | ||
|
||
@ApiTags('Scorecard') | ||
@ApiBearerAuth() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
@Controller('/scorecards') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
export class ScorecardController { | ||
constructor(private readonly prisma: PrismaService) {} | ||
constructor(private readonly prisma: PrismaService, private readonly scorecardService: ScoreCardService) {} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The constructor parameter |
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
@Post() | ||
@Roles(UserRole.Admin) | ||
|
@@ -48,12 +56,12 @@ export class ScorecardController { | |
@ApiResponse({ | ||
status: 201, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are really returning 201 here? Probably change to 200... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @kkartunov This is for adding scorecard, normally for adding we return 201 so this looks like its returning correct, for search api it returns 200. |
||
description: 'Scorecard added successfully.', | ||
type: ScorecardResponseDto, | ||
type: ScorecardWithGroupResponseDto, | ||
}) | ||
@ApiResponse({ status: 403, description: 'Forbidden.' }) | ||
async addScorecard( | ||
@Body() body: ScorecardRequestDto, | ||
): Promise<ScorecardResponseDto> { | ||
): Promise<ScorecardWithGroupResponseDto> { | ||
const data = await this.prisma.scorecard.create({ | ||
hentrymartin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
data: mapScorecardRequestToDto(body), | ||
include: { | ||
|
@@ -68,7 +76,7 @@ export class ScorecardController { | |
}, | ||
}, | ||
}); | ||
return data as ScorecardResponseDto; | ||
return data as ScorecardWithGroupResponseDto; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The return type has been changed from |
||
} | ||
|
||
@Put('/:id') | ||
|
@@ -87,14 +95,14 @@ export class ScorecardController { | |
@ApiResponse({ | ||
status: 200, | ||
description: 'Scorecard updated successfully.', | ||
type: ScorecardResponseDto, | ||
type: ScorecardWithGroupResponseDto, | ||
}) | ||
@ApiResponse({ status: 403, description: 'Forbidden.' }) | ||
@ApiResponse({ status: 404, description: 'Scorecard not found.' }) | ||
async editScorecard( | ||
@Param('id') id: string, | ||
@Body() body: ScorecardRequestDto, | ||
): Promise<ScorecardResponseDto> { | ||
@Body() body: ScorecardWithGroupResponseDto, | ||
): Promise<ScorecardWithGroupResponseDto> { | ||
console.log(JSON.stringify(body)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't want this. Please always use logged.debug for that purpose. |
||
|
||
const data = await this.prisma.scorecard | ||
hentrymartin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
@@ -121,7 +129,7 @@ export class ScorecardController { | |
message: `Error: ${error.code}`, | ||
}); | ||
}); | ||
return data as ScorecardResponseDto; | ||
return data as ScorecardWithGroupResponseDto; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The return type has been changed from |
||
} | ||
|
||
@Delete(':id') | ||
|
@@ -139,7 +147,7 @@ export class ScorecardController { | |
@ApiResponse({ | ||
status: 200, | ||
description: 'Scorecard deleted successfully.', | ||
type: ScorecardResponseDto, | ||
type: ScorecardWithGroupResponseDto, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The type for the API response has been changed from |
||
}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
@ApiResponse({ status: 403, description: 'Forbidden.' }) | ||
@ApiResponse({ status: 404, description: 'Scorecard not found.' }) | ||
|
@@ -173,10 +181,10 @@ export class ScorecardController { | |
@ApiResponse({ | ||
status: 200, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error handling logic for |
||
description: 'Scorecard retrieved successfully.', | ||
type: ScorecardResponseDto, | ||
type: ScorecardWithGroupResponseDto, | ||
}) | ||
@ApiResponse({ status: 404, description: 'Scorecard not found.' }) | ||
async viewScorecard(@Param('id') id: string): Promise<ScorecardResponseDto> { | ||
async viewScorecard(@Param('id') id: string): Promise<ScorecardWithGroupResponseDto> { | ||
const data = await this.prisma.scorecard | ||
hentrymartin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
.findUniqueOrThrow({ | ||
where: { id }, | ||
|
@@ -200,11 +208,11 @@ export class ScorecardController { | |
message: `Error: ${error.code}`, | ||
}); | ||
}); | ||
return data as ScorecardResponseDto; | ||
return data as ScorecardWithGroupResponseDto; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SuggestionEnsure that |
||
} | ||
|
||
@Get() | ||
@Roles(UserRole.Admin, UserRole.Copilot) | ||
@Roles(UserRole.Admin) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SuggestionConsider the implications of removing the |
||
@Scopes(Scope.ReadScorecard) | ||
@ApiOperation({ | ||
summary: 'Search scorecards', | ||
|
@@ -249,34 +257,31 @@ export class ScorecardController { | |
description: 'List of matching scorecards', | ||
type: [ScorecardResponseDto], | ||
}) | ||
@UseInterceptors(PaginationHeaderInterceptor) | ||
async searchScorecards( | ||
@Query('challengeTrack') challengeTrack?: ChallengeTrack, | ||
@Query('challengeType') challengeType?: string, | ||
@Query('challengeTrack') challengeTrack?: ChallengeTrack | ChallengeTrack[], | ||
@Query('challengeType') challengeType?: string | string[], | ||
@Query('name') name?: string, | ||
@Query('page') page: number = 1, | ||
@Query('perPage') perPage: number = 10, | ||
) { | ||
const skip = (page - 1) * perPage; | ||
const data = await this.prisma.scorecard.findMany({ | ||
where: { | ||
...(challengeTrack && { challengeTrack }), | ||
...(challengeType && { challengeType }), | ||
...(name && { name: { contains: name, mode: 'insensitive' } }), | ||
}, | ||
include: { | ||
scorecardGroups: { | ||
include: { | ||
sections: { | ||
include: { | ||
questions: true, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
skip, | ||
take: perPage, | ||
): Promise<ScorecardPaginatedResponseDto> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The return type of the |
||
const challengeTrackArray = Array.isArray(challengeTrack) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The logic for converting |
||
? challengeTrack | ||
: challengeTrack | ||
? [challengeTrack] | ||
: []; | ||
const challengeTypeArray = Array.isArray(challengeType) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The logic for converting |
||
? challengeType | ||
: challengeType | ||
? [challengeType] | ||
: []; | ||
const result = await this.scorecardService.getScoreCards({ | ||
challengeTrack: challengeTrackArray, | ||
challengeType: challengeTypeArray, | ||
name, | ||
page, | ||
perPage, | ||
}); | ||
return data as ScorecardResponseDto[]; | ||
return result; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { Injectable } from "@nestjs/common"; | ||
import { Prisma } from "@prisma/client"; | ||
import { ScorecardPaginatedResponseDto, ScorecardQueryDto, ScorecardResponseDto } from "src/dto/scorecard.dto"; | ||
import { PrismaService } from "src/shared/modules/global/prisma.service"; | ||
|
||
@Injectable() | ||
export class ScoreCardService { | ||
constructor( | ||
private readonly prisma: PrismaService, | ||
) {} | ||
|
||
/** | ||
* Get list of score cards and send it in paginated way | ||
* @param query query params | ||
* @returns response dto | ||
*/ | ||
async getScoreCards( | ||
query: ScorecardQueryDto | ||
): Promise<ScorecardPaginatedResponseDto> { | ||
const { page = 1, perPage = 10, challengeTrack, challengeType, name } = query; | ||
const skip = (page - 1) * perPage; | ||
const where: Prisma.scorecardWhereInput = { | ||
...(challengeTrack?.length && { | ||
challengeTrack: { | ||
in: challengeTrack, | ||
}, | ||
}), | ||
...(challengeType?.length && { | ||
challengeType: { | ||
in: challengeType, | ||
}, | ||
}), | ||
...(name && { name: { contains: name, mode: 'insensitive' } }), | ||
}; | ||
const data = await this.prisma.scorecard.findMany({ | ||
where, | ||
skip, | ||
take: perPage, | ||
orderBy: { | ||
name: 'asc', | ||
}, | ||
}); | ||
|
||
const totalCount = await this.prisma.scorecard.count({ | ||
where, | ||
}); | ||
|
||
return { | ||
metadata: { | ||
total: totalCount, | ||
page, | ||
perPage, | ||
totalPages: Math.ceil(totalCount/perPage), | ||
}, | ||
scoreCards: data as ScorecardResponseDto[], | ||
}; | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure that there is a newline at the end of the file to adhere to POSIX standards and improve compatibility with various tools. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -203,11 +203,13 @@ export class ScorecardBaseDto { | |
example: 'user456', | ||
}) | ||
updatedBy: string; | ||
} | ||
|
||
export class ScorecardBaseWithGroupsDto extends ScorecardBaseDto { | ||
scorecardGroups: any[]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider specifying a more specific type for |
||
} | ||
|
||
export class ScorecardRequestDto extends ScorecardBaseDto { | ||
export class ScorecardRequestDto extends ScorecardBaseWithGroupsDto { | ||
@ApiProperty({ description: 'The ID of the scorecard', example: 'abc123' }) | ||
id: string; | ||
|
||
|
@@ -221,6 +223,11 @@ export class ScorecardRequestDto extends ScorecardBaseDto { | |
export class ScorecardResponseDto extends ScorecardBaseDto { | ||
@ApiProperty({ description: 'The ID of the scorecard', example: 'abc123' }) | ||
id: string; | ||
} | ||
|
||
export class ScorecardWithGroupResponseDto extends ScorecardBaseDto { | ||
@ApiProperty({ description: 'The ID of the scorecard', example: 'abc123' }) | ||
id: string; | ||
|
||
@ApiProperty({ | ||
description: 'The list of groups associated with the scorecard', | ||
|
@@ -229,6 +236,39 @@ export class ScorecardResponseDto extends ScorecardBaseDto { | |
scorecardGroups: ScorecardGroupResponseDto[]; | ||
} | ||
|
||
export class PaginationMetaDto { | ||
@ApiProperty({ example: 100 }) | ||
total: number; | ||
|
||
@ApiProperty({ example: 1 }) | ||
page: number; | ||
|
||
@ApiProperty({ example: 10 }) | ||
perPage: number; | ||
|
||
@ApiProperty({ example: 10 }) | ||
totalPages: number; | ||
} | ||
|
||
export class ScorecardPaginatedResponseDto { | ||
@ApiProperty({ description: 'This contains pagination metadata' }) | ||
metadata: PaginationMetaDto; | ||
|
||
@ApiProperty({ | ||
description: 'The list of score cards', | ||
type: [ScorecardGroupResponseDto], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider specifying the type of elements in the |
||
}) | ||
scoreCards: ScorecardResponseDto[]; | ||
} | ||
|
||
export class ScorecardQueryDto { | ||
challengeTrack?: ChallengeTrack[]; | ||
challengeType?: string[]; | ||
name?: string; | ||
page?: number; | ||
perPage?: number; | ||
} | ||
|
||
export function mapScorecardRequestToDto(request: ScorecardRequestDto) { | ||
const userFields = { | ||
createdBy: request.createdBy, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice one @hentrymartin! |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from "@nestjs/common"; | ||
import { Observable, tap } from "rxjs"; | ||
import { Response } from "express"; | ||
|
||
@Injectable() | ||
export class PaginationHeaderInterceptor implements NestInterceptor { | ||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> { | ||
const res = context.switchToHttp().getResponse<Response>(); | ||
return next.handle().pipe( | ||
tap((response) => { | ||
if (response?.metadata) { | ||
const { total, page, perPage, totalPages } = response.metadata; | ||
res.setHeader('X-Total-Count', total); | ||
res.setHeader('X-Page', page); | ||
res.setHeader('X-Per-Page', perPage); | ||
res.setHeader('X-Total-Pages', totalPages); | ||
} | ||
}), | ||
); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ScoreCardService
is added to the providers array, but ensure that it is properly imported at the top of the file. If it's already imported, verify that the import path is correct and matches the file structure.