Hide obsolete Job summaries (#902)

- Injects a `<!-- gradle-job-summary: ${jobCorrelator} -->` marker on
each job summary
- Lists 100 last comments: unfortunately there is no API to specifically
filter for comments, and checking the last 100 comments (the limit) is
usually enough and does not require iterating over pages
- Mutate comments having this expected marker

I tried to add some tests, but I'm not familiar enough to setup a
complete test suite with proper mocking of GitHub/Octokit with jest.

I could potentially extract the `prComment` creation to check for the
marker presence, let me know.

Note: it seems like there is currently an issue on mutating comments as
`OUTDATED` through graphql. Although it does not work as expected
(flagging as OUTDATED) the comments are still minimized, which is what
we want.
- https://github.com/orgs/community/discussions/19865

Implements #176

---------

Co-authored-by: Daz DeBoer <daz@gradle.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Simon Marquis
2026-06-10 10:14:48 -06:00
committed by GitHub
co-authored by Daz DeBoer Claude Opus 4.8
parent a740661292
commit 318eed7038
4 changed files with 124 additions and 4 deletions
+4
View File
@@ -206,6 +206,10 @@ export class SummaryConfig {
return this.shouldAddJobSummary(this.getJobSummaryOption(), hasFailure)
}
canAddPRComment(): boolean {
return this.getPRCommentOption() !== JobSummaryOption.Never
}
shouldAddPRComment(hasFailure: boolean): boolean {
return this.shouldAddJobSummary(this.getPRCommentOption(), hasFailure)
}
+82 -2
View File
@@ -2,7 +2,7 @@ import * as core from '@actions/core'
import * as github from '@actions/github'
import {BuildResult} from './build-results'
import {SummaryConfig, getActionId, getGithubToken} from './configuration'
import {DependencyGraphConfig, getActionId, getGithubToken, getJobMatrix, SummaryConfig} from './configuration'
import {Deprecation, getDeprecations, getErrors} from './deprecation-collector'
export async function generateJobSummary(
@@ -33,6 +33,10 @@ export async function generateJobSummary(
core.info('============================')
}
if (config.canAddPRComment()) {
await minimizeObsoletePRComments()
}
if (config.shouldAddPRComment(hasFailure)) {
await addPRComment(summaryTable)
}
@@ -48,7 +52,8 @@ async function addPRComment(jobSummary: string): Promise<void> {
const pull_request_number = context.payload.pull_request.number
core.info(`Adding Job Summary as comment to PR #${pull_request_number}.`)
const prComment = `<h3>Job Summary for Gradle</h3>
const prComment = `${jobMarker(context)}
<h3>Job Summary for Gradle</h3>
<a href="${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}" target="_blank">
<h5>${context.workflow} :: <em>${context.job}</em></h5>
</a>
@@ -57,6 +62,7 @@ ${jobSummary}`
const github_token = getGithubToken()
const octokit = github.getOctokit(github_token)
try {
await octokit.rest.issues.createComment({
...context.repo,
@@ -201,3 +207,77 @@ function truncateString(str: string, maxLength: number): string {
return str
}
}
async function minimizeObsoletePRComments(): Promise<void> {
const context = github.context
if (context.payload.pull_request == null) {
core.info('No pull_request trigger detected: not minimizing obsolete PR comments')
return
}
const prNumber = context.payload.pull_request.number
core.info(`Minimizing obsolete Job Summary comments on PR #${prNumber}.`)
const marker = jobMarker(context)
const octokit = github.getOctokit(getGithubToken())
const {owner, repo} = context.repo
const query = `
query($owner: String!, $repo: String!, $prNumber: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $prNumber) {
comments(last: 100) {
nodes { id body isMinimized url }
}
}
}
}
`
let comments: PullRequestComment[]
try {
const {repository} = await octokit.graphql<CommentsQueryResult>(query, {owner, repo, prNumber})
comments = repository.pullRequest?.comments?.nodes?.filter((c): c is PullRequestComment => c !== null) ?? []
} catch (error) {
return core.warning(`Failed to fetch comments: ${error}`)
}
const mutation = `
mutation($id: ID!) {
minimizeComment(input: {subjectId: $id, classifier: OUTDATED}) {
clientMutationId
}
}
`
const commentsToMinimize = comments
.filter(c => !c.isMinimized && c.body.includes(marker))
.map(async c =>
octokit
.graphql(mutation, {id: c.id})
.then(() => core.info(`Successfully minimized (id:${c.id}, url:${c.url})`))
.catch(e => core.warning(`Failed to minimize (id:${c.id}, url:${c.url}, error:${e?.message || e})`))
)
await Promise.allSettled(commentsToMinimize)
}
export function jobMarker(context: typeof github.context): string {
const jobCorrelator = DependencyGraphConfig.constructJobCorrelator(context.workflow, context.job, getJobMatrix())
return `<!-- gradle-job-summary: ${jobCorrelator} -->`
}
interface PullRequestComment {
id: string
body: string
isMinimized: boolean
url: string
}
interface CommentsQueryResult {
repository: {
pullRequest?: {
comments?: {
nodes?: (PullRequestComment | null)[] | null
} | null
} | null
}
}
+33 -2
View File
@@ -1,8 +1,15 @@
import dedent from 'dedent'
import {describe, expect, it} from '@jest/globals'
import * as github from '@actions/github'
import {afterEach, describe, expect, it} from '@jest/globals'
import {BuildResult} from '../../src/build-results'
import {renderSummaryTable} from '../../src/job-summary'
import {jobMarker, renderSummaryTable} from '../../src/job-summary'
const MATRIX_INPUT_ENV = 'INPUT_WORKFLOW-JOB-CONTEXT'
function fakeContext(workflow: string, job: string): typeof github.context {
return {workflow, job} as unknown as typeof github.context
}
const successfulHelpBuild: BuildResult = {
rootProjectName: 'root',
@@ -177,3 +184,27 @@ describe('renderSummaryTable', () => {
})
})
})
describe('jobMarker', () => {
const original = process.env[MATRIX_INPUT_ENV]
afterEach(() => {
if (original === undefined) {
delete process.env[MATRIX_INPUT_ENV]
} else {
process.env[MATRIX_INPUT_ENV] = original
}
})
it('builds a hidden marker from the workflow and job', () => {
process.env[MATRIX_INPUT_ENV] = 'null'
const marker = jobMarker(fakeContext('CI', 'build'))
expect(marker).toBe('<!-- gradle-job-summary: ci-build -->')
})
it('includes the job matrix in the marker', () => {
process.env[MATRIX_INPUT_ENV] = JSON.stringify({os: 'ubuntu', java: '17'})
const marker = jobMarker(fakeContext('CI', 'build'))
expect(marker).toBe('<!-- gradle-job-summary: ci-build-ubuntu-17 -->')
})
})