import * as core from '@actions/core' import * as github from '@actions/github' import {BuildResult} from './build-results' import {CacheReport} from './cache-service' import {ProviderNote, renderCachingReport} from './caching-report' import {DependencyGraphConfig, getActionId, getGithubToken, getJobMatrix, SummaryConfig} from './configuration' import {Deprecation, getDeprecations, getErrors} from './deprecation-collector' export async function generateJobSummary( buildResults: BuildResult[], cacheReport: CacheReport, providerNote: ProviderNote | undefined, config: SummaryConfig ): Promise { core.startGroup('Generating Job Summary') const errors = renderErrors() if (errors) { core.summary.addRaw(errors) await core.summary.write() return } const summaryTable = renderSummaryTable(buildResults) const cachingReport = renderCachingReport(cacheReport, providerNote) const hasFailure = anyFailed(buildResults) core.info(summaryTable) core.info('============================') core.info(cachingReport) if (config.shouldGenerateJobSummary(hasFailure)) { core.summary.addRaw(summaryTable) core.summary.addRaw(cachingReport) await core.summary.write() } core.endGroup() if (config.canAddPRComment()) { await minimizeObsoletePRComments() } if (config.shouldAddPRComment(hasFailure)) { await addPRComment(summaryTable) } } async function addPRComment(jobSummary: string): Promise { const context = github.context if (context.payload.pull_request == null) { core.info('No pull_request trigger detected: not adding PR comment') return } const pull_request_number = context.payload.pull_request.number core.info(`Adding Job Summary as comment to PR #${pull_request_number}.`) const prComment = `${jobMarker(context)}

Job Summary for Gradle

${context.workflow} :: ${context.job}
${jobSummary}` const github_token = getGithubToken() const octokit = github.getOctokit(github_token) try { await octokit.rest.issues.createComment({ ...context.repo, issue_number: pull_request_number, body: prComment }) } catch (error) { if (error instanceof Error && error.name === 'HttpError') { core.warning(buildWarningMessage(error)) } else { throw error } } } function buildWarningMessage(error: Error): string { const mainWarning = `Failed to generate PR comment.\n${String(error)}` if (error.message === 'Resource not accessible by integration') { return `${mainWarning} Please ensure that the 'pull-requests: write' permission is available for the workflow job. Note that this permission is never available for a workflow triggered from a repository fork. ` } return mainWarning } export function renderSummaryTable(results: BuildResult[]): string { return `${renderDeprecations()}\n${renderBuildResults(results)}` } function renderErrors(): string | undefined { const errors = getErrors() if (errors.length === 0) { return undefined } return errors.map(error => `:x: ${error}`).join('\n') } function renderDeprecations(): string { const deprecations = getDeprecations() if (deprecations.length === 0) { return '' } return `

Deprecation warnings

This job uses deprecated functionality from the ${getActionId()} action. Follow the links for upgrade details.

Gradle Build Results

` } function getDeprecationHtml(deprecation: Deprecation): string { return `${deprecation.message}` } function renderBuildResults(results: BuildResult[]): string { if (results.length === 0) { return 'No Gradle build results detected.' } return ` ${results.map(result => renderBuildResultRow(result)).join('')}
Gradle Root Project Requested Tasks Gradle Version Build Outcome Build Scan®
` } function anyFailed(results: BuildResult[]): boolean { return results.some(result => result.buildFailed) } function renderBuildResultRow(result: BuildResult): string { return ` ${truncateString(result.rootProjectName, 30)} ${truncateString(result.requestedTasks, 60)} ${result.gradleVersion} ${renderOutcome(result)} ${renderBuildScan(result)} ` } function renderOutcome(result: BuildResult): string { return result.buildFailed ? ':x:' : ':white_check_mark:' } interface BadgeSpec { text: string alt: string color: string logo: boolean targetUrl: string } function renderBuildScan(result: BuildResult): string { if (result.buildScanFailed) { return renderBuildScanBadge({ text: 'Publish failed', alt: 'Build Scan publish failed', color: 'orange', logo: false, targetUrl: 'https://docs.gradle.com/develocity/gradle-plugin/#troubleshooting' }) } if (result.buildScanUri) { return renderBuildScanBadge({ text: 'Build Scan®', alt: 'Build Scan published', color: '06A0CE', logo: true, targetUrl: result.buildScanUri }) } return renderBuildScanBadge({ text: 'Not published', alt: 'Build Scan not published', color: 'lightgrey', logo: false, targetUrl: 'https://scans.gradle.com' }) } function renderBuildScanBadge({text, alt, color, logo, targetUrl}: BadgeSpec): string { const encodedText = encodeURIComponent(text) const badgeUrl = `https://img.shields.io/badge/${encodedText}-${color}${logo ? '?logo=Gradle' : ''}` const badgeHtml = `${alt}` return `${badgeHtml}` } function truncateString(str: string, maxLength: number): string { if (str.length > maxLength) { return `
${str.slice(0, maxLength - 1)}…
` } else { return str } } async function minimizeObsoletePRComments(): Promise { 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(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 `` } interface PullRequestComment { id: string body: string isMinimized: boolean url: string } interface CommentsQueryResult { repository: { pullRequest?: { comments?: { nodes?: (PullRequestComment | null)[] | null } | null } | null } }