Skip to content
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

feat: update or overwrite existing or comment #60

Merged
merged 6 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading