Skip to content

Commit c1b717a

Browse files
authored
feat: add output to step summary (#21)
* feat: add output to step summary
1 parent 1e9b578 commit c1b717a

File tree

10 files changed

+2073
-119
lines changed

10 files changed

+2073
-119
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,11 @@ jobs:
187187
# none
188188
list-tests: 'all'
189189
190+
# The location to write the report to. Supported options:
191+
# checks
192+
# step-summary
193+
output-to: 'checks'
194+
190195
# Limits number of created annotations with error message and stack trace captured during test execution.
191196
# Must be less or equal to 50.
192197
max-annotations: '10'

__tests__/dotnet-trx.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ describe('dotnet-trx tests', () => {
6464
listSuites: 'all',
6565
listTests: 'failed',
6666
onlySummary: false,
67+
slugPrefix: '',
6768
baseUrl: ''
6869
}
6970
const report = getReport([result], reportOptions)

action.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@ inputs:
6868
Detailed listing of test suites and test cases will be skipped.
6969
default: 'false'
7070
required: false
71+
output-to:
72+
description: |
73+
The location to write the report to. Supported options:
74+
- checks
75+
- step-summary
76+
default: 'checks'
77+
required: false
7178
token:
7279
description: GitHub Access Token
7380
required: false

dist/index.js

Lines changed: 1888 additions & 72 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/licenses.txt

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package-lock.json

Lines changed: 46 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"author": "Michal Dorner <dorner.michal@gmail.com>",
3232
"license": "MIT",
3333
"dependencies": {
34-
"@actions/core": "^1.2.6",
34+
"@actions/core": "^1.9.1",
3535
"@actions/exec": "^1.0.4",
3636
"@actions/github": "^4.0.0",
3737
"adm-zip": "^0.5.3",

src/main.ts

Lines changed: 101 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as core from '@actions/core'
22
import * as github from '@actions/github'
3+
import {createHash} from 'crypto'
34
import {GitHub} from '@actions/github/lib/utils'
45

56
import {ArtifactProvider} from './input-providers/artifact-provider'
@@ -30,6 +31,16 @@ async function main(): Promise<void> {
3031
}
3132
}
3233

34+
function createSlugPrefix(): string {
35+
const step_summary = process.env['GITHUB_STEP_SUMMARY']
36+
if (!step_summary || step_summary === '') {
37+
return ''
38+
}
39+
const hash = createHash('sha1')
40+
hash.update(step_summary)
41+
return hash.digest('hex').substring(0, 8)
42+
}
43+
3344
class TestReporter {
3445
readonly artifact = core.getInput('artifact', {required: false})
3546
readonly name = core.getInput('name', {required: true})
@@ -42,7 +53,9 @@ class TestReporter {
4253
readonly failOnError = core.getInput('fail-on-error', {required: true}) === 'true'
4354
readonly workDirInput = core.getInput('working-directory', {required: false})
4455
readonly onlySummary = core.getInput('only-summary', {required: false}) === 'true'
56+
readonly outputTo = core.getInput('output-to', {required: false})
4557
readonly token = core.getInput('token', {required: true})
58+
readonly slugPrefix: string = ''
4659
readonly octokit: InstanceType<typeof GitHub>
4760
readonly context = getCheckRunContext()
4861

@@ -63,6 +76,15 @@ class TestReporter {
6376
core.setFailed(`Input parameter 'max-annotations' has invalid value`)
6477
return
6578
}
79+
80+
if (this.outputTo !== 'checks' && this.outputTo !== 'step-summary') {
81+
core.setFailed(`Input parameter 'output-to' has invalid value`)
82+
return
83+
}
84+
85+
if (this.outputTo === 'step-summary') {
86+
this.slugPrefix = createSlugPrefix()
87+
}
6688
}
6789

6890
async run(): Promise<void> {
@@ -154,22 +176,37 @@ class TestReporter {
154176
results.push(tr)
155177
}
156178

157-
core.info(`Creating check run ${name}`)
158-
const createResp = await this.octokit.checks.create({
159-
head_sha: this.context.sha,
160-
name,
161-
status: 'in_progress',
162-
output: {
163-
title: name,
164-
summary: ''
165-
},
166-
...github.context.repo
167-
})
179+
let createResp = null,
180+
baseUrl = '',
181+
check_run_id = 0
182+
183+
switch (this.outputTo) {
184+
case 'checks': {
185+
core.info(`Creating check run ${name}`)
186+
createResp = await this.octokit.checks.create({
187+
head_sha: this.context.sha,
188+
name,
189+
status: 'in_progress',
190+
output: {
191+
title: name,
192+
summary: ''
193+
},
194+
...github.context.repo
195+
})
196+
baseUrl = createResp.data.html_url
197+
check_run_id = createResp.data.id
198+
break
199+
}
200+
case 'step-summary': {
201+
const run_attempt = process.env['GITHUB_RUN_ATTEMPT'] ?? 1
202+
baseUrl = `https://github.com/${github.context.repo.owner}/${github.context.repo.repo}/actions/runs/${github.context.runId}/attempts/${run_attempt}`
203+
break
204+
}
205+
}
168206

169207
core.info('Creating report summary')
170-
const {listSuites, listTests, onlySummary} = this
171-
const baseUrl = createResp.data.html_url
172-
const summary = getReport(results, {listSuites, listTests, baseUrl, onlySummary})
208+
const {listSuites, listTests, onlySummary, slugPrefix} = this
209+
const summary = getReport(results, {listSuites, listTests, baseUrl, slugPrefix, onlySummary})
173210

174211
core.info('Creating annotations')
175212
const annotations = getAnnotations(results, this.maxAnnotations)
@@ -183,22 +220,56 @@ class TestReporter {
183220
const shortSummary = `${passed} passed, ${failed} failed and ${skipped} skipped `
184221

185222
core.info(`Updating check run conclusion (${conclusion}) and output`)
186-
const resp = await this.octokit.checks.update({
187-
check_run_id: createResp.data.id,
188-
conclusion,
189-
status: 'completed',
190-
output: {
191-
title: shortSummary,
192-
summary,
193-
annotations
194-
},
195-
...github.context.repo
196-
})
197-
core.info(`Check run create response: ${resp.status}`)
198-
core.info(`Check run URL: ${resp.data.url}`)
199-
core.info(`Check run HTML: ${resp.data.html_url}`)
200-
201-
core.setOutput(Outputs.runHtmlUrl, `${resp.data.html_url}`)
223+
switch (this.outputTo) {
224+
case 'checks': {
225+
const resp = await this.octokit.checks.update({
226+
check_run_id,
227+
conclusion,
228+
status: 'completed',
229+
output: {
230+
title: shortSummary,
231+
summary,
232+
annotations
233+
},
234+
...github.context.repo
235+
})
236+
core.info(`Check run create response: ${resp.status}`)
237+
core.info(`Check run URL: ${resp.data.url}`)
238+
core.info(`Check run HTML: ${resp.data.html_url}`)
239+
break
240+
}
241+
case 'step-summary': {
242+
core.summary.addRaw(`# ${shortSummary}`)
243+
core.summary.addRaw(summary)
244+
await core.summary.write()
245+
for (const annotation of annotations) {
246+
let fn
247+
switch (annotation.annotation_level) {
248+
case 'failure':
249+
fn = core.error
250+
break
251+
case 'warning':
252+
fn = core.warning
253+
break
254+
case 'notice':
255+
fn = core.notice
256+
break
257+
default:
258+
continue
259+
}
260+
261+
fn(annotation.message, {
262+
title: annotation.title,
263+
file: annotation.path,
264+
startLine: annotation.start_line,
265+
endLine: annotation.end_line,
266+
startColumn: annotation.start_column,
267+
endColumn: annotation.end_column
268+
})
269+
}
270+
break
271+
}
272+
}
202273

203274
return results
204275
}

src/report/get-report.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ const MAX_REPORT_LENGTH = 65535
99
export interface ReportOptions {
1010
listSuites: 'all' | 'failed'
1111
listTests: 'all' | 'failed' | 'none'
12+
slugPrefix: string
1213
baseUrl: string
1314
onlySummary: boolean
1415
}
1516

1617
const defaultOptions: ReportOptions = {
1718
listSuites: 'all',
1819
listTests: 'all',
20+
slugPrefix: '',
1921
baseUrl: '',
2022
onlySummary: false
2123
}
@@ -138,7 +140,7 @@ function getTestRunsReport(testRuns: TestRunResult[], options: ReportOptions): s
138140
const tableData = testRuns.map((tr, runIndex) => {
139141
const time = formatTime(tr.time)
140142
const name = tr.path
141-
const addr = options.baseUrl + makeRunSlug(runIndex).link
143+
const addr = options.baseUrl + makeRunSlug(runIndex, options.slugPrefix).link
142144
const nameLink = link(name, addr)
143145
const passed = tr.passed > 0 ? `${tr.passed}${Icon.success}` : ''
144146
const failed = tr.failed > 0 ? `${tr.failed}${Icon.fail}` : ''
@@ -164,7 +166,7 @@ function getTestRunsReport(testRuns: TestRunResult[], options: ReportOptions): s
164166
function getSuitesReport(tr: TestRunResult, runIndex: number, options: ReportOptions): string[] {
165167
const sections: string[] = []
166168

167-
const trSlug = makeRunSlug(runIndex)
169+
const trSlug = makeRunSlug(runIndex, options.slugPrefix)
168170
const nameLink = `<a id="${trSlug.id}" href="${options.baseUrl + trSlug.link}">${tr.path}</a>`
169171
const icon = getResultIcon(tr.result)
170172
sections.push(`## ${icon}\xa0${nameLink}`)
@@ -185,7 +187,7 @@ function getSuitesReport(tr: TestRunResult, runIndex: number, options: ReportOpt
185187
const tsTime = formatTime(s.time)
186188
const tsName = s.name
187189
const skipLink = options.listTests === 'none' || (options.listTests === 'failed' && s.result !== 'failed')
188-
const tsAddr = options.baseUrl + makeSuiteSlug(runIndex, suiteIndex).link
190+
const tsAddr = options.baseUrl + makeSuiteSlug(runIndex, suiteIndex, options.slugPrefix).link
189191
const tsNameLink = skipLink ? tsName : link(tsName, tsAddr)
190192
const passed = s.passed > 0 ? `${s.passed}${Icon.success}` : ''
191193
const failed = s.failed > 0 ? `${s.failed}${Icon.fail}` : ''
@@ -219,7 +221,7 @@ function getTestsReport(ts: TestSuiteResult, runIndex: number, suiteIndex: numbe
219221
const sections: string[] = []
220222

221223
const tsName = ts.name
222-
const tsSlug = makeSuiteSlug(runIndex, suiteIndex)
224+
const tsSlug = makeSuiteSlug(runIndex, suiteIndex, options.slugPrefix)
223225
const tsNameLink = `<a id="${tsSlug.id}" href="${options.baseUrl + tsSlug.link}">${tsName}</a>`
224226
const icon = getResultIcon(ts.result)
225227
sections.push(`### ${icon}\xa0${tsNameLink}`)
@@ -251,14 +253,14 @@ function getTestsReport(ts: TestSuiteResult, runIndex: number, suiteIndex: numbe
251253
return sections
252254
}
253255

254-
function makeRunSlug(runIndex: number): {id: string; link: string} {
256+
function makeRunSlug(runIndex: number, slugPrefix: string): {id: string; link: string} {
255257
// use prefix to avoid slug conflicts after escaping the paths
256-
return slug(`r${runIndex}`)
258+
return slug(`r${slugPrefix}${runIndex}`)
257259
}
258260

259-
function makeSuiteSlug(runIndex: number, suiteIndex: number): {id: string; link: string} {
261+
function makeSuiteSlug(runIndex: number, suiteIndex: number, slugPrefix: string): {id: string; link: string} {
260262
// use prefix to avoid slug conflicts after escaping the paths
261-
return slug(`r${runIndex}s${suiteIndex}`)
263+
return slug(`r${slugPrefix}${runIndex}s${suiteIndex}`)
262264
}
263265

264266
function getResultIcon(result: TestExecutionResult): string {

0 commit comments

Comments
 (0)