- add capability to fetch review comments and use them as part of the target template
- allow array syntax to be used as part of reviewers, approvers, assignees
This commit is contained in:
+41
-4
@@ -11,7 +11,7 @@ export interface PullRequestInfo {
|
||||
baseBranch: string
|
||||
branch?: string
|
||||
createdAt: moment.Moment
|
||||
mergedAt: moment.Moment | null
|
||||
mergedAt: moment.Moment | undefined
|
||||
mergeCommitSha: string
|
||||
author: string
|
||||
repoName: string
|
||||
@@ -21,15 +21,26 @@ export interface PullRequestInfo {
|
||||
assignees: string[]
|
||||
requestedReviewers: string[]
|
||||
approvedReviewers: string[]
|
||||
reviews?: CommentInfo[]
|
||||
status: 'open' | 'merged'
|
||||
}
|
||||
|
||||
export interface CommentInfo {
|
||||
id: number
|
||||
htmlURL: string
|
||||
submittedAt: moment.Moment | undefined
|
||||
author: string
|
||||
body: string
|
||||
}
|
||||
|
||||
type PullData = RestEndpointMethodTypes['pulls']['get']['response']['data']
|
||||
|
||||
type PullsListData = RestEndpointMethodTypes['pulls']['list']['response']['data']
|
||||
|
||||
type PullReviewData = RestEndpointMethodTypes['pulls']['listReviews']['response']['data']
|
||||
|
||||
type PullReviewsData = RestEndpointMethodTypes['pulls']['listReviews']['response']['data']
|
||||
|
||||
export class PullRequests {
|
||||
constructor(private octokit: Octokit) {}
|
||||
|
||||
@@ -122,7 +133,7 @@ export class PullRequests {
|
||||
return sortPrs(openPrs)
|
||||
}
|
||||
|
||||
async getReviewers(owner: string, repo: string, pr: PullRequestInfo): Promise<PullReviewData[]> {
|
||||
async getReviewers(owner: string, repo: string, pr: PullRequestInfo): Promise<void> {
|
||||
const options = this.octokit.pulls.listReviews.endpoint.merge({
|
||||
owner,
|
||||
repo,
|
||||
@@ -136,8 +147,25 @@ export class PullRequests {
|
||||
.map(r => r.user?.login)
|
||||
.filter(r => !!r) as string[]
|
||||
}
|
||||
}
|
||||
|
||||
return []
|
||||
async getReviews(owner: string, repo: string, pr: PullRequestInfo): Promise<void> {
|
||||
const options = this.octokit.pulls.listReviews.endpoint.merge({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: pr.number,
|
||||
sort: 'created',
|
||||
direction: 'desc'
|
||||
})
|
||||
const prReviews: CommentInfo[] = []
|
||||
for await (const response of this.octokit.paginate.iterator(options)) {
|
||||
const comments: PullReviewsData = response.data as PullReviewsData
|
||||
|
||||
for (const comment of comments) {
|
||||
prReviews.push(mapComment(comment))
|
||||
}
|
||||
}
|
||||
pr.reviews = prReviews
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +232,7 @@ const mapPullRequest = (
|
||||
baseBranch: pr.base.ref,
|
||||
branch: pr.head.ref,
|
||||
createdAt: moment(pr.created_at),
|
||||
mergedAt: pr.merged_at ? moment(pr.merged_at) : null,
|
||||
mergedAt: pr.merged_at ? moment(pr.merged_at) : undefined,
|
||||
mergeCommitSha: pr.merge_commit_sha || '',
|
||||
author: pr.user?.login || '',
|
||||
repoName: pr.base.repo.full_name,
|
||||
@@ -214,5 +242,14 @@ const mapPullRequest = (
|
||||
assignees: pr.assignees?.map(asignee => asignee?.login || '') || [],
|
||||
requestedReviewers: pr.requested_reviewers?.map(reviewer => reviewer?.login || '') || [],
|
||||
approvedReviewers: [],
|
||||
reviews: undefined,
|
||||
status
|
||||
})
|
||||
|
||||
const mapComment = (comment: Unpacked<PullReviewsData>): CommentInfo => ({
|
||||
id: comment.id,
|
||||
htmlURL: comment.html_url,
|
||||
submittedAt: comment.submitted_at ? moment(comment.submitted_at) : undefined,
|
||||
author: comment.user?.login || '',
|
||||
body: comment.body
|
||||
})
|
||||
|
||||
@@ -95,6 +95,7 @@ export class ReleaseNotes {
|
||||
|
||||
private async getMergedPullRequests(octokit: Octokit): Promise<[DiffInfo, PullRequestInfo[]]> {
|
||||
const {owner, repo, includeOpen, fetchReviewers, configuration} = this.options
|
||||
const fetchReviews = true // TEMPORARY!!
|
||||
|
||||
const diffInfo = await this.getCommitHistory(octokit)
|
||||
const commits = diffInfo.commitInfo
|
||||
@@ -198,6 +199,19 @@ export class ReleaseNotes {
|
||||
core.debug(`ℹ️ Fetching reviewers was disabled`)
|
||||
}
|
||||
|
||||
if (fetchReviews) {
|
||||
core.info(`ℹ️ Fetching reviews was enabled`)
|
||||
// update PR information with reviewers who approved
|
||||
for (const pr of finalPrs) {
|
||||
await pullRequestsApi.getReviews(owner, repo, pr)
|
||||
if ((pr.reviews?.length || 0) > 0) {
|
||||
core.info(`ℹ️ Retrieved ${pr.reviews?.length || 0} review(s) for PR ${owner}/${repo}/#${pr.number}`)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
core.debug(`ℹ️ Fetching reviews was disabled`)
|
||||
}
|
||||
|
||||
return [diffInfo, finalPrs]
|
||||
}
|
||||
|
||||
|
||||
+42
-2
@@ -1,6 +1,6 @@
|
||||
import * as core from '@actions/core'
|
||||
import {Category, DefaultConfiguration, Extractor, Placeholder, Transformer} from './configuration'
|
||||
import {PullRequestInfo, sortPullRequests} from './pullRequests'
|
||||
import {CommentInfo, PullRequestInfo, sortPullRequests} from './pullRequests'
|
||||
import {ReleaseNotesOptions} from './releaseNotes'
|
||||
import {DiffInfo} from './commits'
|
||||
import {createOrSet, haveCommonElements, haveEveryElements} from './utils'
|
||||
@@ -299,6 +299,11 @@ function fillPrTemplate(
|
||||
placeholders: Map<string, Placeholder[]> /* placeholders to apply */,
|
||||
placeholderPrMap: Map<string, string[]> /* map to keep replaced placeholder values with their key */
|
||||
): string {
|
||||
let transformed = replaceArrayPlaceholders(template, 'ASSIGNEES', pr.assignees || [])
|
||||
transformed = replaceArrayPlaceholders(transformed, 'REVIEWERS', pr.requestedReviewers || [])
|
||||
transformed = replaceArrayPlaceholders(transformed, 'APPROVERS', pr.approvedReviewers || [])
|
||||
transformed = replaceReviewPlaceholders(transformed, 'REVIEWS', pr.reviews || [])
|
||||
|
||||
const placeholderMap = new Map<string, string>()
|
||||
placeholderMap.set('NUMBER', pr.number.toString())
|
||||
placeholderMap.set('TITLE', pr.title)
|
||||
@@ -316,7 +321,7 @@ function fillPrTemplate(
|
||||
placeholderMap.set('APPROVERS', pr.approvedReviewers?.join(', ') || '')
|
||||
placeholderMap.set('BRANCH', pr.branch || '')
|
||||
placeholderMap.set('BASE_BRANCH', pr.baseBranch)
|
||||
return replacePlaceholders(template, placeholderMap, placeholders, placeholderPrMap)
|
||||
return replacePlaceholders(transformed, placeholderMap, placeholders, placeholderPrMap)
|
||||
}
|
||||
|
||||
function replacePlaceholders(
|
||||
@@ -375,6 +380,41 @@ function replacePrPlaceholders(
|
||||
return transformed
|
||||
}
|
||||
|
||||
function replaceArrayPlaceholders(template: string, key: string, values: string[]): string {
|
||||
let transformed = template
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
transformed = transformed.replaceAll(`\${{${key}[${i}]}}`, values[i])
|
||||
}
|
||||
transformed = transformed.replaceAll(`\${{${key}[*]}}`, values.join(', '))
|
||||
return transformed
|
||||
}
|
||||
|
||||
function replaceReviewPlaceholders(template: string, parentKey: string, values: CommentInfo[]): string {
|
||||
let transformed = template
|
||||
|
||||
// retrieve the keys from the CommentInfo object
|
||||
const comment: CommentInfo = {
|
||||
id: 0,
|
||||
htmlURL: '',
|
||||
submittedAt: undefined,
|
||||
author: '',
|
||||
body: ''
|
||||
}
|
||||
for (const childKey of Object.keys(comment)) {
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
transformed = transformed.replaceAll(
|
||||
`\${{${parentKey}[${i}].${childKey}}}`,
|
||||
values[i][childKey as keyof CommentInfo]?.toLocaleString('en') || ''
|
||||
)
|
||||
}
|
||||
transformed = transformed.replaceAll(
|
||||
`\${{${parentKey}[*].${childKey}}}`,
|
||||
values.map(value => value[childKey as keyof CommentInfo]?.toLocaleString('en') || '').join(', ')
|
||||
)
|
||||
}
|
||||
return transformed
|
||||
}
|
||||
|
||||
function cleanupPrPlaceHolders(
|
||||
template: string,
|
||||
placeholders: Map<string, Placeholder[]> /* placeholders to apply */
|
||||
|
||||
Reference in New Issue
Block a user