Skip to content

Commit

Permalink
Add notice annotation and support more annotation fields (#855)
Browse files Browse the repository at this point in the history
* Add support for notice annotation and additional properties

* Add additional tests

* Update readme

* Change casing for endLine and endColumn

* Update utils.ts

* Update README.md

* Rename files to have internal- nomenclature

* Revert "Rename files to have internal- nomenclature"

This reverts commit 7911689.

* Update utils.ts
  • Loading branch information
luketomlinson committed Jul 28, 2021
1 parent 4564768 commit f0b00fd
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 8 deletions.
Binary file added docs/assets/annotations.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docs/problem-matchers.md
Expand Up @@ -6,7 +6,7 @@ Problem Matchers are a way to scan the output of actions for a specified regex p

Currently, GitHub Actions limit the annotation count in a workflow run.

- 10 warning annotations and 10 error annotations per step
- 10 warning annotations, 10 error annotations, and 10 notice annotations per step
- 50 annotations per job (sum of annotations from all the steps)
- 50 annotations per run (separate from the job annotations, these annotations aren’t created by users)

Expand Down
50 changes: 50 additions & 0 deletions packages/core/README.md
Expand Up @@ -92,6 +92,8 @@ try {

// Do stuff
core.info('Output to the actions build log')

core.notice('This is a message that will also emit an annotation')
}
catch (err) {
core.error(`Error ${err}, action may still succeed though`);
Expand All @@ -115,6 +117,54 @@ const result = await core.group('Do something async', async () => {
})
```

#### Annotations

This library has 3 methods that will produce [annotations](https://docs.github.com/en/rest/reference/checks#create-a-check-run).
```js
core.error('This is a bad error. This will also fail the build.')

core.warning('Something went wrong, but it\'s not bad enough to fail the build.')

core.notice('Something happened that you might want to know about.')
```

These will surface to the UI in the Actions page and on Pull Requests. They look something like this:

![Annotations Image](../../docs/assets/annotations.png)

These annotations can also be attached to particular lines and columns of your source files to show exactly where a problem is occuring.

These options are:
```typescript
export interface AnnotationProperties {
/**
* A title for the annotation.
*/
title?: string

/**
* The start line for the annotation.
*/
startLine?: number

/**
* The end line for the annotation. Defaults to `startLine` when `startLine` is provided.
*/
endLine?: number

/**
* The start column for the annotation. Cannot be sent when `startLine` and `endLine` are different values.
*/
startColumn?: number

/**
* The start column for the annotation. Cannot be sent when `startLine` and `endLine` are different values.
* Defaults to `startColumn` when `startColumn` is provided.
*/
endColumn?: number
}
```

#### Styling output

Colored output is supported in the Action logs via standard [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code). 3/4 bit, 8 bit and 24 bit colors are all supported.
Expand Down
47 changes: 47 additions & 0 deletions packages/core/__tests__/core.test.ts
Expand Up @@ -2,6 +2,7 @@ import * as fs from 'fs'
import * as os from 'os'
import * as path from 'path'
import * as core from '../src/core'
import {toCommandProperties} from '../src/utils'

/* eslint-disable @typescript-eslint/unbound-method */

Expand Down Expand Up @@ -269,6 +270,20 @@ describe('@actions/core', () => {
assertWriteCalls([`::error::Error: ${message}${os.EOL}`])
})

it('error handles parameters correctly', () => {
const message = 'this is my error message'
core.error(new Error(message), {
title: 'A title',
startColumn: 1,
endColumn: 2,
startLine: 5,
endLine: 5
})
assertWriteCalls([
`::error title=A title,line=5,endLine=5,col=1,endColumn=2::Error: ${message}${os.EOL}`
])
})

it('warning sets the correct message', () => {
core.warning('Warning')
assertWriteCalls([`::warning::Warning${os.EOL}`])
Expand All @@ -285,6 +300,38 @@ describe('@actions/core', () => {
assertWriteCalls([`::warning::Error: ${message}${os.EOL}`])
})

it('warning handles parameters correctly', () => {
const message = 'this is my error message'
core.warning(new Error(message), {
title: 'A title',
startColumn: 1,
endColumn: 2,
startLine: 5,
endLine: 5
})
assertWriteCalls([
`::warning title=A title,line=5,endLine=5,col=1,endColumn=2::Error: ${message}${os.EOL}`
])
})

it('annotations map field names correctly', () => {
const commandProperties = toCommandProperties({
title: 'A title',
startColumn: 1,
endColumn: 2,
startLine: 5,
endLine: 5
})
expect(commandProperties.title).toBe('A title')
expect(commandProperties.col).toBe(1)
expect(commandProperties.endColumn).toBe(2)
expect(commandProperties.line).toBe(5)
expect(commandProperties.endLine).toBe(5)

expect(commandProperties.startColumn).toBeUndefined()
expect(commandProperties.startLine).toBeUndefined()
})

it('startGroup starts a new group', () => {
core.startGroup('my-group')
assertWriteCalls([`::group::my-group${os.EOL}`])
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/command.ts
Expand Up @@ -6,7 +6,7 @@ import {toCommandValue} from './utils'
// We use any as a valid input type
/* eslint-disable @typescript-eslint/no-explicit-any */

interface CommandProperties {
export interface CommandProperties {
[key: string]: any
}

Expand Down
76 changes: 70 additions & 6 deletions packages/core/src/core.ts
@@ -1,6 +1,6 @@
import {issue, issueCommand} from './command'
import {issueCommand as issueFileCommand} from './file-command'
import {toCommandValue} from './utils'
import {toCommandProperties, toCommandValue} from './utils'

import * as os from 'os'
import * as path from 'path'
Expand Down Expand Up @@ -31,6 +31,38 @@ export enum ExitCode {
Failure = 1
}

/**
* Optional properties that can be sent with annotatation commands (notice, error, and warning)
* See: https://docs.github.com/en/rest/reference/checks#create-a-check-run for more information about annotations.
*/
export interface AnnotationProperties {
/**
* A title for the annotation.
*/
title?: string

/**
* The start line for the annotation.
*/
startLine?: number

/**
* The end line for the annotation. Defaults to `startLine` when `startLine` is provided.
*/
endLine?: number

/**
* The start column for the annotation. Cannot be sent when `startLine` and `endLine` are different values.
*/
startColumn?: number

/**
* The start column for the annotation. Cannot be sent when `startLine` and `endLine` are different values.
* Defaults to `startColumn` when `startColumn` is provided.
*/
endColumn?: number
}

//-----------------------------------------------------------------------
// Variables
//-----------------------------------------------------------------------
Expand Down Expand Up @@ -199,17 +231,49 @@ export function debug(message: string): void {
/**
* Adds an error issue
* @param message error issue message. Errors will be converted to string via toString()
* @param properties optional properties to add to the annotation.
*/
export function error(message: string | Error): void {
issue('error', message instanceof Error ? message.toString() : message)
export function error(
message: string | Error,
properties: AnnotationProperties = {}
): void {
issueCommand(
'error',
toCommandProperties(properties),
message instanceof Error ? message.toString() : message
)
}

/**
* Adds an warning issue
* Adds a warning issue
* @param message warning issue message. Errors will be converted to string via toString()
* @param properties optional properties to add to the annotation.
*/
export function warning(message: string | Error): void {
issue('warning', message instanceof Error ? message.toString() : message)
export function warning(
message: string | Error,
properties: AnnotationProperties = {}
): void {
issueCommand(
'warning',
toCommandProperties(properties),
message instanceof Error ? message.toString() : message
)
}

/**
* Adds a notice issue
* @param message notice issue message. Errors will be converted to string via toString()
* @param properties optional properties to add to the annotation.
*/
export function notice(
message: string | Error,
properties: AnnotationProperties = {}
): void {
issueCommand(
'notice',
toCommandProperties(properties),
message instanceof Error ? message.toString() : message
)
}

/**
Expand Down
25 changes: 25 additions & 0 deletions packages/core/src/utils.ts
@@ -1,6 +1,9 @@
// We use any as a valid input type
/* eslint-disable @typescript-eslint/no-explicit-any */

import {AnnotationProperties} from './core'
import {CommandProperties} from './command'

/**
* Sanitizes an input into a string so it can be passed into issueCommand safely
* @param input input to sanitize into a string
Expand All @@ -13,3 +16,25 @@ export function toCommandValue(input: any): string {
}
return JSON.stringify(input)
}

/**
*
* @param annotationProperties
* @returns The command properties to send with the actual annotation command
* See IssueCommandProperties: https://github.com/actions/runner/blob/main/src/Runner.Worker/ActionCommandManager.cs#L646
*/
export function toCommandProperties(
annotationProperties: AnnotationProperties
): CommandProperties {
if (!Object.keys(annotationProperties).length) {
return {}
}

return {
title: annotationProperties.title,
line: annotationProperties.startLine,
endLine: annotationProperties.endLine,
col: annotationProperties.startColumn,
endColumn: annotationProperties.endColumn
}
}

0 comments on commit f0b00fd

Please sign in to comment.