Skip to content

Commit

Permalink
Merge pull request #60 from ctrf-io/feat/update-existing-comment
Browse files Browse the repository at this point in the history
feat: update or overwrite existing or comment completes #51
  • Loading branch information
Ma11hewThomas authored Dec 17, 2024
2 parents 22ff6fe + d346b89 commit baf4dd3
Show file tree
Hide file tree
Showing 8 changed files with 207 additions and 22 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,27 @@ these permissions, please see GitHub's
or
[GitHub Enterprise Server documentation](https://docs.github.com/en/enterprise-server@3.14/actions/security-for-github-actions/security-guides/automatic-token-authentication#about-the-github_token-secret)

### Comment Management Inputs

`--update-comment` (optional, boolean) If true and an existing tagged comment is
found, the new report is appended to it. Otherwise, a new comment is created.

`--overwrite-comment` (optional, boolean) If true and an existing tagged comment
is found, that comment’s entire content is replaced with the new report.
Otherwise, a new comment is created.

`--comment-tag` (optional, string) A unique identifier for comments posted. Used
to find and update/overwrite existing comments.

These options provide flexibility in how you manage comments. For example, you
can continually update or overwrite a single comment or create separate comments
per workflow or job.

For example, the following command creates or updates a comment tagged with the
current workflow and job names:

`npx github-actions-ctrf pull-request path-to-your-ctrf-report.json --update-comment --comment-tag "${{ github.workflow }}-${{ github.job }}"`

## Previous Test Results

### Filtering
Expand Down
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "github-actions-ctrf",
"version": "0.0.52",
"version": "0.0.53",
"description": "View test results directly within your GitHub workflow summary and Pull Requests",
"main": "index.js",
"scripts": {
Expand Down
64 changes: 61 additions & 3 deletions src/client/github/issues.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { components } from '@octokit/openapi-types'
import { createGitHubClient } from '.'

type IssueComment = components['schemas']['issue-comment']

/**
* Adds a comment to a specified pull request on GitHub.
*
Expand All @@ -8,7 +11,7 @@ import { createGitHubClient } from '.'
* @param issue_number - The pull request number to which the comment will be added.
* @param body - The content of the comment to be added.
*
* @returns {Promise<any>} A promise that resolves to the response data from GitHub's API.
* @returns {Promise<IssueComment>} A promise that resolves to the response data from GitHub's API.
*
* @throws {Error} If the GitHub client fails to authenticate or the API request fails.
*/
Expand All @@ -17,8 +20,7 @@ export async function addCommentToPullRequest(
repo: string,
issue_number: number,
body: string
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): Promise<any> {
): Promise<IssueComment> {
const octokit = await createGitHubClient()
const response = await octokit.issues.createComment({
owner,
Expand All @@ -28,3 +30,59 @@ export async function addCommentToPullRequest(
})
return response.data
}

/**
* Updates a comment to a specified pull request on GitHub.
*
* @param comment_id - The ID number of the comment
* @param owner - The owner of the repository (organization or user).
* @param repo - The name of the repository.
* @param issue_number - The pull request number to which the comment will be added.
* @param body - The content of the comment to be added.
*
* @returns {Promise<IssueComment>} A promise that resolves to the response data from GitHub's API.
*
* @throws {Error} If the GitHub client fails to authenticate or the API request fails.
*/
export async function updateComment(
comment_id: number,
owner: string,
repo: string,
issue_number: number,
body: string
): Promise<IssueComment> {
const octokit = await createGitHubClient()
const response = await octokit.issues.updateComment({
comment_id,
owner,
repo,
issue_number,
body
})
return response.data
}

/**
* Lists comments on a specified pull request on GitHub.
*
* @param owner - The owner of the repository (organization or user).
* @param repo - The name of the repository.
* @param issue_number - The pull request number to which the comments are listed.
*
* @returns {Promise<IssueComment[]>} A promise that resolves to the response data from GitHub's API.
*
* @throws {Error} If the GitHub client fails to authenticate or the API request fails.
*/
export async function listComments(
owner: string,
repo: string,
issue_number: number
): Promise<IssueComment[]> {
const octokit = await createGitHubClient()
const response = await octokit.issues.listComments({
owner,
repo,
issue_number
})
return response.data
}
18 changes: 17 additions & 1 deletion src/core/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export interface Arguments {
domain?: string
useSuite?: boolean
useSuiteName?: boolean
updateComment?: boolean
overwriteComment?: boolean
commentTag?: string
results?: number
exitOnFail?: boolean
}
Expand Down Expand Up @@ -282,6 +285,20 @@ async function main(): Promise<void> {
description: 'Fail action when if tests fail',
default: false
})
.options('update-comment', {
type: 'boolean',
description: 'Updates existing Pull Request comment',
default: false
})
.options('overwrite-comment', {
type: 'boolean',
description: 'Overwrites existing Pull Request comment',
default: false
})
.options('comment-tag', {
type: 'string',
description: 'Tag to use to match Pull Request comments with'
})
.help()
.alias('help', 'h')
.parseSync()
Expand Down Expand Up @@ -311,7 +328,6 @@ main().catch(err => {
process.exit(1)
})

// TODO add tscod for legacy functionality
async function processPrComment(
args: Arguments,
report: CtrfReport,
Expand Down
9 changes: 8 additions & 1 deletion src/core/inputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ export function getCliInputs(args: Arguments): Inputs {
previousResultsMax: args.rows || 10,
metricsReportsMax: args.results || 100,
fetchPreviousResults: args._.includes('fetch-previous-results'),
updateComment: args.updateComment || false,
overwriteComment: args.overwriteComment || false,
commentTag: args.commentTag || '',
groupBy: groupBy,
alwaysGroupBy: false,
debug: args._.includes('debug')
Expand All @@ -47,8 +50,8 @@ export function getInputs(): Inputs {
const groupBy: 'suite' | 'filePath' =
groupByInput === 'suite' ? 'suite' : 'filePath'
return {
templatePath: core.getInput('template-path'),
ctrfPath: core.getInput('ctrf-report-path', { required: true }),
templatePath: core.getInput('template-path'),
summary: core.getInput('summary').toLowerCase() === 'true',
pullRequest: core.getInput('pull-request').toLowerCase() === 'true',
summaryReport: core.getInput('summary-report').toLowerCase() === 'true',
Expand Down Expand Up @@ -88,6 +91,10 @@ export function getInputs(): Inputs {
),
fetchPreviousResults:
core.getInput('fetch-previous-results').toLowerCase() === 'true',
updateComment: core.getInput('update-comment').toLowerCase() === 'true',
overwriteComment:
core.getInput('overwrite-comment').toLowerCase() === 'true',
commentTag: core.getInput('ctrf-report-path') || '',
groupBy: groupBy,
alwaysGroupBy: core.getInput('always-group-by').toLowerCase() === 'true',
debug: core.getInput('debug').toLowerCase() === 'true'
Expand Down
96 changes: 88 additions & 8 deletions src/github/handler.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import * as core from '@actions/core'
import { context } from '@actions/github'
import { addCommentToPullRequest } from '../client/github'
import {
addCommentToPullRequest,
updateComment,
listComments
} from '../client/github'
import { CtrfReport, Inputs } from '../types'
import { generateViews, annotateFailed } from './core'
import { components } from '@octokit/openapi-types'

type IssueComment = components['schemas']['issue-comment']

/**
* Handles the generation of views and comments for a CTRF report.
*
* - Generates various views of the CTRF report and adds them to the GitHub Actions summary.
* - Adds a comment to the pull request if the conditions are met.
* - Adds or updates a comment on the pull request if conditions are met.
* - Writes the summary to the GitHub Actions output if requested.
*
* @param inputs - The user-provided inputs for configuring views and comments.
Expand All @@ -22,12 +29,11 @@ export async function handleViewsAndComments(
generateViews(inputs, report)

if (shouldAddCommentToPullRequest(inputs, report)) {
await addCommentToPullRequest(
context.repo.owner,
context.repo.repo,
context.issue.number,
core.summary.stringify()
)
const INVISIBLE_MARKER = inputs.commentTag
? `<!-- CTRF PR COMMENT TAG: ${inputs.commentTag} -->`
: `<!-- CTRF PR COMMENT TAG: DEFAULT -->`

await postOrUpdatePRComment(inputs, INVISIBLE_MARKER)
}

if (inputs.summary && !inputs.pullRequestReport) {
Expand Down Expand Up @@ -70,3 +76,77 @@ export function handleAnnotations(inputs: Inputs, report: CtrfReport): void {
annotateFailed(report)
}
}

/**
* Posts or updates the PR comment containing the CTRF report.
*
* If a comment with the specified marker exists and `updateComment` is set (but not `overwriteComment`),
* it will append new data to the existing comment. If `overwriteComment` is set, it will overwrite
* the entire comment. Otherwise, it will create a new comment.
*
* @param inputs - The user-provided inputs for configuring the comment behavior.
* @param marker - The unique marker used to find and identify the existing comment.
*/
async function postOrUpdatePRComment(
inputs: Inputs,
marker: string
): Promise<void> {
let newSummary = core.summary.stringify()

if (!newSummary.includes(marker)) {
core.summary.addRaw(marker)
newSummary = core.summary.stringify()
}

const owner = context.repo.owner
const repo = context.repo.repo
const issue_number = context.issue.number

const existingComment = await findExistingMarkedComment(
owner,
repo,
issue_number,
marker
)

let finalBody = newSummary

if (existingComment) {
if (inputs.updateComment && !inputs.overwriteComment) {
finalBody = `${existingComment.body}\n\n---\n\n${newSummary}`
} else if (inputs.overwriteComment) {
finalBody = newSummary
}
}

if (existingComment && (inputs.updateComment || inputs.overwriteComment)) {
await updateComment(
existingComment.id,
owner,
repo,
issue_number,
finalBody
)
} else {
await addCommentToPullRequest(owner, repo, issue_number, finalBody)
}
}

/**
* Searches for an existing PR comment containing a given marker.
*
* @param owner - The owner of the repository.
* @param repo - The repository name.
* @param issue_number - The pull request number.
* @param marker - The unique marker used to identify the comment.
* @returns The comment object if found, otherwise undefined.
*/
async function findExistingMarkedComment(
owner: string,
repo: string,
issue_number: number,
marker: string
): Promise<IssueComment | undefined> {
const comments = await listComments(owner, repo, issue_number)
return comments.find(comment => comment.body && comment.body.includes(marker))
}
3 changes: 3 additions & 0 deletions src/types/reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export interface Inputs {
previousResultsMax: number
metricsReportsMax: number
fetchPreviousResults: boolean
updateComment: boolean
overwriteComment: boolean
commentTag: string
groupBy: 'suite' | 'filePath'
alwaysGroupBy: boolean
debug: boolean
Expand Down

0 comments on commit baf4dd3

Please sign in to comment.