Skip to content

Commit

Permalink
Feat/comments (#534)
Browse files Browse the repository at this point in the history
* Feat: add methods for retrieving comments from github

* Chore: add types

* Feat: add comments methods to reviewRequestService

* Feat: add comments routes

* fix: check for properly formatted comments

* Chore: remove incorrect comments

* Fix: remove error return type

* Fix: add logging if site not found

* Feat: swap use of email in github commit to userid

* Fix: response type

* Fix: rename method and add github comment type

* fix: compute the number of new comments to show (#549)

* fix: compute the number of new comments to show

* chore: adjust naming of variable and structure of code

* chore: split getting number of new comments into 2 lines

Co-authored-by: Hsu Zhong Jun <[email protected]>
  • Loading branch information
alexanderleegs and dcshzj committed Mar 16, 2023
1 parent 6d882c9 commit 07bbb3f
Show file tree
Hide file tree
Showing 6 changed files with 235 additions and 4 deletions.
66 changes: 65 additions & 1 deletion src/routes/v2/authenticated/review.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import UsersService from "@root/services/identity/UsersService"
import { isIsomerError, RequestHandler } from "@root/types"
import { ResponseErrorBody } from "@root/types/dto/error"
import {
CommentItem,
DashboardReviewRequestDto,
EditedItemDto,
UpdateReviewRequestDto,
Expand Down Expand Up @@ -698,6 +699,61 @@ export class ReviewsRouter {
return res.status(200).send()
}

getComments: RequestHandler<
{ siteName: string; requestId: number },
CommentItem[] | ResponseErrorBody,
never,
unknown,
{ userWithSiteSessionData: UserWithSiteSessionData }
> = async (req, res) => {
const { siteName, requestId } = req.params
const { userWithSiteSessionData } = res.locals
// Step 1: Check that the site exists
const site = await this.sitesService.getBySiteName(siteName)
if (!site) {
logger.error({
message: "Invalid site requested",
method: "getComments",
meta: {
userId: userWithSiteSessionData.isomerUserId,
email: userWithSiteSessionData.email,
siteName,
},
})
return res.status(404).send({
message: "Please ensure that the site exists!",
})
}

// Step 2: Retrieve comments
const comments = await this.reviewRequestService.getComments(
userWithSiteSessionData,
site,
requestId
)

return res.status(200).json(comments)
}

createComment: RequestHandler<
{ siteName: string; requestId: number },
string,
{ message: string },
unknown,
{ userWithSiteSessionData: UserWithSiteSessionData }
> = async (req, res) => {
const { requestId } = req.params
const { message } = req.body
const { userWithSiteSessionData } = res.locals
await this.reviewRequestService.createComment(
userWithSiteSessionData,
requestId,
message
)

return res.status(200).send("OK")
}

markReviewRequestCommentsAsViewed: RequestHandler<
{ siteName: string; requestId: number },
string | ResponseErrorBody,
Expand Down Expand Up @@ -862,8 +918,16 @@ export class ReviewsRouter {
"/:requestId/approve",
attachReadRouteHandlerWrapper(this.approveReviewRequest)
)
router.get(
"/:requestId/comments",
attachWriteRouteHandlerWrapper(this.getComments)
)
router.post(
"/:requestId/comments",
attachWriteRouteHandlerWrapper(this.createComment)
)
router.post(
"/:requestId/viewedComments",
"/:requestId/comments/viewedComments",
attachWriteRouteHandlerWrapper(this.markReviewRequestCommentsAsViewed)
)
router.post(
Expand Down
8 changes: 8 additions & 0 deletions src/services/db/GitHubService.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ class GitHubService {
return ReviewApi.approvePullRequest(siteName, pullRequestNumber)
}

async getComments(siteName, pullRequestNumber) {
return ReviewApi.getComments(siteName, pullRequestNumber)
}

async createComment(siteName, pullRequestNumber, user, message) {
return ReviewApi.createComment(siteName, pullRequestNumber, user, message)
}

getFilePath({ siteName, fileName, directoryName }) {
if (!directoryName)
return `${siteName}/contents/${encodeURIComponent(fileName)}`
Expand Down
48 changes: 47 additions & 1 deletion src/services/db/review.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import { RawFileChangeInfo, Commit, RawPullRequest } from "@root/types/github"
import _ from "lodash"

import {
RawFileChangeInfo,
Commit,
RawPullRequest,
RawComment,
fromGithubCommitMessage,
} from "@root/types/github"

import { isomerRepoAxiosInstance as axiosInstance } from "../api/AxiosInstance"

Expand Down Expand Up @@ -83,3 +91,41 @@ export const approvePullRequest = (
},
}
)

export const getComments = async (
siteName: string,
pullRequestNumber: number
) => {
const rawComments = await axiosInstance
.get<RawComment[]>(`${siteName}/issues/${pullRequestNumber}/comments`)
.then(({ data }) => data)
return _.compact(
rawComments.map((rawComment) => {
const commentData = fromGithubCommitMessage(rawComment.body)
if (_.isEmpty(commentData)) return null // Will be filtered out by _.compact
const { userId, message } = commentData
if (!userId || !message) return null // Will be filtered out by _.compact
return {
userId,
message,
createdAt: rawComment.created_at,
}
})
)
}

export const createComment = async (
siteName: string,
pullRequestNumber: number,
userId: string,
message: string
) => {
const stringifiedMessage = JSON.stringify({
userId,
message,
})
return axiosInstance.post<void>(
`${siteName}/issues/${pullRequestNumber}/comments`,
{ body: stringifiedMessage }
)
}
99 changes: 97 additions & 2 deletions src/services/review/ReviewRequestService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import { Site } from "@root/database/models/Site"
import { User } from "@root/database/models/User"
import RequestNotFoundError from "@root/errors/RequestNotFoundError"
import {
CommentItem,
DashboardReviewRequestDto,
EditedItemDto,
FileType,
GithubCommentData,
ReviewRequestDto,
} from "@root/types/dto/review"
import { isIsomerError } from "@root/types/error"
Expand Down Expand Up @@ -125,6 +127,25 @@ export default class ReviewRequestService {
return mappings
}

computeCommentData = async (
comments: GithubCommentData[],
viewedTime: Date | null
) => {
const mappings = await Promise.all(
comments.map(async ({ userId, message, createdAt }) => {
const createdTime = new Date(createdAt)
const author = await this.users.findByPk(userId)
return {
user: author?.email || "",
message,
createdAt: createdTime.getTime(),
isRead: viewedTime ? createdTime < viewedTime : false,
}
})
)
return mappings
}

createReviewRequest = async (
sessionData: UserWithSiteSessionData,
reviewers: User[],
Expand Down Expand Up @@ -213,6 +234,27 @@ export default class ReviewRequestService {
},
}))

// It is a new comment to the user if any of the following
// conditions satisfy:
// 1. The review request views table does not contain a record
// for the user and the review request.
// 2. The review request views table contains a record for that
// user and review request, but the lastViewedAt entry is NULL.
// 3. The review request views table contains a record in the
// lastViewedAt entry, and the comment has a timestamp greater
// than the one stored in the database.
const allComments = await this.getComments(
sessionData,
site,
pullRequestNumber
)
const countNewComments = await Promise.all(
allComments.map(async (value) => value.isRead)
).then((arr) => {
const readComments = arr.filter((isRead) => !!isRead)
return readComments.length
})

return {
id: pullRequestNumber,
author: req.requestor.email || "Unknown user",
Expand All @@ -221,8 +263,7 @@ export default class ReviewRequestService {
description: body || "",
changedFiles: changed_files,
createdAt: new Date(created_at).getTime(),
// TODO!
newComments: 0,
newComments: countNewComments,
firstView: isFirstView,
}
})
Expand Down Expand Up @@ -478,4 +519,58 @@ export default class ReviewRequestService {
reviewRequest.reviewStatus = ReviewRequestStatus.Merged
return reviewRequest.save()
}

createComment = async (
sessionData: UserWithSiteSessionData,
pullRequestNumber: number,
message: string
) => {
const { siteName, isomerUserId } = sessionData

return this.apiService.createComment(
siteName,
pullRequestNumber,
isomerUserId,
message
)
}

getComments = async (
sessionData: UserWithSiteSessionData,
site: Site,
pullRequestNumber: number
): Promise<CommentItem[]> => {
const { siteName, isomerUserId: userId } = sessionData

const comments = await this.apiService.getComments(
siteName,
pullRequestNumber
)

const requestsView = await this.reviewRequestView.findOne({
where: {
siteId: site.id,
userId,
},
include: [
{
model: ReviewRequest,
required: true,
include: [
{
model: ReviewMeta,
required: true,
where: {
pullRequestNumber,
},
},
],
},
],
})

const viewedTime = requestsView ? new Date(requestsView.lastViewedAt) : null

return this.computeCommentData(comments, viewedTime)
}
}
13 changes: 13 additions & 0 deletions src/types/dto/review.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,16 @@ export interface ReviewRequestDto {
export interface UpdateReviewRequestDto {
reviewers: string[]
}

export interface CommentItem {
user: string
createdAt: number
message: string
isRead: boolean
}

export interface GithubCommentData {
userId: string
message: string
createdAt: string
}
5 changes: 5 additions & 0 deletions src/types/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,8 @@ export interface RawPullRequest {
changed_files: number
created_at: string
}

export interface RawComment {
body: string
created_at: string
}

0 comments on commit 07bbb3f

Please sign in to comment.