This is an automated email from the ASF dual-hosted git repository. potiuk pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/airflow-checks-action.git
commit 88ce8c595fcf9e7e3829dca9b6b59a972a98133e Author: Louis Brunner <[email protected]> AuthorDate: Mon Sep 7 18:48:24 2020 +0100 Rework create/update workflow, unify Checks API arguments --- .github/workflows/examples.yml | 10 ++++++--- README.md | 24 ++++++++++++++------- __tests__/main.test.ts | 45 ++++++++++++++++++++++++++++++---------- action.yml | 10 +++++++-- dist/index.js | 2 +- src/checks.ts | 47 ++++++++++++++++++------------------------ src/inputs.ts | 18 +++++++++++++++- src/main.ts | 34 ++++++++++++------------------ src/namespaces/Inputs.ts | 17 +++++++++++---- 9 files changed, 129 insertions(+), 78 deletions(-) diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index bd34184..e139d30 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -207,6 +207,7 @@ jobs: steps: - uses: actions/checkout@v1 - uses: ./ + id: init with: token: ${{ secrets.GITHUB_TOKEN }} name: Test With Init @@ -215,8 +216,10 @@ jobs: - uses: ./ with: token: ${{ secrets.GITHUB_TOKEN }} - name: Will not be used + check_id: ${{ steps.init.outputs.check_id }} status: completed + output: | + {"summary":"Some warnings in README.md"} conclusion: failure test_with_init_implicit: @@ -224,6 +227,7 @@ jobs: steps: - uses: actions/checkout@v1 - uses: ./ + id: init with: token: ${{ secrets.GITHUB_TOKEN }} name: Test With Init (Implicit) @@ -232,8 +236,8 @@ jobs: - uses: ./ with: token: ${{ secrets.GITHUB_TOKEN }} - name: Will not be used - conclusion: failure + check_id: ${{ steps.init.outputs.check_id }} + conclusion: success ## Based on job test_based_job_success: diff --git a/README.md b/README.md index 5b3991a..e996573 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,9 @@ jobs: with: token: ${{ secrets.GITHUB_TOKEN }} name: Test XYZ - conclusion: ${{ job }} - output: - summary: ${{ steps.test.outputs.summary }} - text_description: ${{ steps.test.outputs.description }} + conclusion: ${{ job.status }} + output: | + {"summary":${{ steps.test.outputs.summary }}} ``` See the [examples workflow](.github/workflows/examples.yml) for more details and examples (and see the [associated runs](https://github.com/LouisBrunner/checks-action/actions?query=workflow%3Aexamples) to see how it will look like). @@ -38,7 +37,11 @@ See the [examples workflow](.github/workflows/examples.yml) for more details and ### `name` -**Required** The name of your check +**Required** for creation, the name of the check to create (mutually exclusive with `check_id`) + +### `check_id` + +**Required** for update, ID of the check to update (mutually exclusive with `name`) ### `conclusion` @@ -93,9 +96,14 @@ Supports the same properties with the same types and names as the [Check Runs AP Note that this will override `details_url` as it relies on `action_url` (the two inputs set the same check attribute, `details_url`) +## Outputs + +### `check_id` + +The ID of the created check, useful to update it in another action (e.g. non-`completed` `status`) + ## Issues - - Action Required conclusion: button doesn't work - - Action elements: button doesn't work + - Action Required conclusion: button doesn't work? + - Action elements: button doesn't work? - Non-completed status: too many arguments required - - Name is required when completing a non-`completed` `status` check even though we don't use it (see examples `test_with_init*`) diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts index 9eae102..5514487 100644 --- a/__tests__/main.test.ts +++ b/__tests__/main.test.ts @@ -1,25 +1,48 @@ -import * as process from 'process'; import * as cp from 'child_process'; import * as path from 'path'; // shows how the runner will run a javascript action with env / stdout protocol -test('test runs', () => { - process.env['GITHUB_REPOSITORY'] = 'LB/ABC'; - process.env['INPUT_TOKEN'] = 'ABC'; - process.env['INPUT_NAME'] = 'ABC'; - process.env['INPUT_STATUS'] = 'completed'; - process.env['INPUT_CONCLUSION'] = 'success'; - const ip = path.join(__dirname, '..', 'lib', 'main.js'); +test('test runs (creation)', () => { + const entry = path.join(__dirname, '..', 'lib', 'main.js'); const options: cp.ExecSyncOptions = { - env: process.env, + env: { + GITHUB_REPOSITORY: 'LB/ABC', + INPUT_TOKEN: 'ABC', + INPUT_NAME: 'ABC', + INPUT_STATUS: 'completed', + INPUT_CONCLUSION: 'success', + }, }; try { - console.log(cp.execSync(`node ${ip}`, options).toString()); + console.log(cp.execSync(`node ${entry}`, options).toString()); } catch (e) { const error = e as Error & {stdout: Buffer}; const output = error.stdout.toString(); console.log(output); - expect(output).toMatch(/::debug::Error: HttpError: Bad credentials/); + expect(output).toMatch(/::debug::Creating a new Run/); + expect(output).toMatch(/::debug::HttpError: Bad credentials/); + } +}); + +test('test runs (update)', () => { + const entry = path.join(__dirname, '..', 'lib', 'main.js'); + const options: cp.ExecSyncOptions = { + env: { + GITHUB_REPOSITORY: 'LB/ABC', + INPUT_TOKEN: 'ABC', + INPUT_CHECK_ID: '123', + INPUT_STATUS: 'completed', + INPUT_CONCLUSION: 'success', + }, + }; + try { + console.log(cp.execSync(`node ${entry}`, options).toString()); + } catch (e) { + const error = e as Error & {stdout: Buffer}; + const output = error.stdout.toString(); + console.log(output); + expect(output).toMatch(/::debug::Updating a Run/); + expect(output).toMatch(/::debug::HttpError: Bad credentials/); } }); diff --git a/action.yml b/action.yml index 1457cc3..444da5e 100644 --- a/action.yml +++ b/action.yml @@ -9,8 +9,11 @@ inputs: description: 'your GITHUB_TOKEN' required: true name: - description: 'the name of your check' - required: true + description: 'the name of the check to create (incompatible with `check_id`)' + required: false + check_id: + description: 'ID of the check to update (incompatible with `name`)' + required: false conclusion: description: 'the conclusion of your check' required: false @@ -36,6 +39,9 @@ inputs: actions: description: 'the actions of your check' required: false +outputs: + check_id: + description: 'the ID of the created check, useful to update it in another action' runs: using: 'node12' main: 'dist/index.js' diff --git a/dist/index.js b/dist/index.js index 0c5f986..1a3450f 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1 +1 @@ -module.exports=(()=>{var __webpack_modules__={321:function(e,t,r){"use strict";var s=this&&this.__createBinding||(Object.create?function(e,t,r,s){if(s===undefined)s=r;Object.defineProperty(e,s,{enumerable:true,get:function(){return t[r]}})}:function(e,t,r,s){if(s===undefined)s=r;e[s]=t[r]});var o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:true,value:t})}:function(e,t){e["default"]=t});var n=this&&this.__importStar||function(e [...] \ No newline at end of file +module.exports=(()=>{var __webpack_modules__={321:function(e,t,r){"use strict";var s=this&&this.__createBinding||(Object.create?function(e,t,r,s){if(s===undefined)s=r;Object.defineProperty(e,s,{enumerable:true,get:function(){return t[r]}})}:function(e,t,r,s){if(s===undefined)s=r;e[s]=t[r]});var o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:true,value:t})}:function(e,t){e["default"]=t});var n=this&&this.__importStar||function(e [...] \ No newline at end of file diff --git a/src/checks.ts b/src/checks.ts index d90ee7c..e26f79e 100644 --- a/src/checks.ts +++ b/src/checks.ts @@ -7,25 +7,20 @@ type Ownership = { repo: string; }; -type CreateOptions = { - completed: boolean; -}; - -const unpackInputs = (inputs: Inputs.Args, options: {update: boolean} = {update: false}): Record<string, unknown> => { +const unpackInputs = (title: string, inputs: Inputs.Args): Record<string, unknown> => { let output; if (inputs.output) { output = { - title: options.update ? undefined : inputs.name, + title, summary: inputs.output.summary, text: inputs.output.text_description, actions: inputs.actions, images: inputs.images, }; } - const more: { - details_url?: string; - conclusion?: string; - } = {}; + + let details_url; + if (inputs.conclusion === Inputs.Conclusion.ActionRequired || inputs.actions) { if (inputs.detailsURL) { const reasonList = []; @@ -36,22 +31,22 @@ const unpackInputs = (inputs: Inputs.Args, options: {update: boolean} = {update: reasonList.push(`'actions' was provided`); } const reasons = reasonList.join(' and '); - core.warning( + core.info( `'details_url' was ignored in favor of 'action_url' because ${reasons} (see documentation for details)`, ); } - more.details_url = inputs.actionURL; + details_url = inputs.actionURL; } else if (inputs.detailsURL) { - more.details_url = inputs.detailsURL; - } - if (inputs.conclusion) { - more.conclusion = inputs.conclusion.toString(); + details_url = inputs.detailsURL; } + return { status: inputs.status.toString(), output, actions: inputs.actions, - ...more, + conclusion: inputs.conclusion ? inputs.conclusion.toString() : undefined, + completed_at: inputs.status === Inputs.Status.Completed ? formatDate() : undefined, + details_url, }; }; @@ -61,22 +56,17 @@ const formatDate = (): string => { export const createRun = async ( octokit: InstanceType<typeof GitHub>, + name: string, sha: string, ownership: Ownership, inputs: Inputs.Args, - options?: CreateOptions, ): Promise<number> => { - const dates: {completed_at?: string} = {}; - if (!options || options.completed) { - dates.completed_at = formatDate(); - } const {data} = await octokit.checks.create({ ...ownership, head_sha: sha, - name: inputs.name, + name: name, started_at: formatDate(), - ...dates, - ...unpackInputs(inputs), + ...unpackInputs(name, inputs), }); return data.id; }; @@ -87,10 +77,13 @@ export const updateRun = async ( ownership: Ownership, inputs: Inputs.Args, ): Promise<void> => { + const previous = await octokit.checks.get({ + ...ownership, + check_run_id: id, + }); await octokit.checks.update({ ...ownership, check_run_id: id, - completed_at: formatDate(), - ...unpackInputs(inputs, {update: true}), + ...unpackInputs(previous.data.name, inputs), }); }; diff --git a/src/inputs.ts b/src/inputs.ts index d5a1437..32931a1 100644 --- a/src/inputs.ts +++ b/src/inputs.ts @@ -18,12 +18,26 @@ const parseJSON = <T>(getInput: GetInput, property: string): T | undefined => { export const parseInputs = (getInput: GetInput): Inputs.Args => { const token = getInput('token', {required: true}); - const name = getInput('name', {required: true}); + + const name = getInput('name'); + const checkIDStr = getInput('check_id'); + const status = getInput('status', {required: true}) as Inputs.Status; let conclusion = getInput('conclusion') as Inputs.Conclusion; + const actionURL = getInput('action_url'); const detailsURL = getInput('details_url'); + if (name && checkIDStr) { + throw new Error(`can only provide 'name' or 'check_id'`); + } + + if (!name && !checkIDStr) { + throw new Error(`must provide 'name' or 'check_id'`); + } + + const checkID = checkIDStr ? parseInt(checkIDStr) : undefined; + if (!Object.values(Inputs.Status).includes(status)) { throw new Error(`invalid value for 'status': '${status}'`); } @@ -64,6 +78,8 @@ export const parseInputs = (getInput: GetInput): Inputs.Args => { status, conclusion, + checkID, + actionURL, detailsURL, diff --git a/src/main.ts b/src/main.ts index fa3db4e..d03548a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,9 +1,12 @@ import * as core from '@actions/core'; import * as github from '@actions/github'; +import * as Inputs from './namespaces/Inputs'; import {parseInputs} from './inputs'; import {createRun, updateRun} from './checks'; -const stateID = 'checkID'; +const isCreation = (inputs: Inputs.Args): inputs is Inputs.ArgsCreate => { + return !!(inputs as Inputs.ArgsCreate).name; +}; async function run(): Promise<void> { try { @@ -19,30 +22,19 @@ async function run(): Promise<void> { }; const sha = github.context.sha; - switch (inputs.status) { - case 'in_progress': - case 'queued': { - core.debug(`Creating a new Run`); - const id = await createRun(octokit, sha, ownership, inputs, {completed: false}); - core.saveState(stateID, id.toString()); - break; - } - case 'completed': { - const id = core.getState(stateID); - if (id) { - core.debug(`Updating a Run (${id})`); - await updateRun(octokit, parseInt(id), ownership, inputs); - } else { - core.debug(`Creating a new Run`); - await createRun(octokit, sha, ownership, inputs); - } - break; - } + if (isCreation(inputs)) { + core.debug(`Creating a new Run`); + const id = await createRun(octokit, inputs.name, sha, ownership, inputs); + core.setOutput('check_id', id); + } else { + const id = inputs.checkID; + core.debug(`Updating a Run (${id})`); + await updateRun(octokit, id, ownership, inputs); } core.debug(`Done`); } catch (e) { const error = e as Error; - core.debug(`Error: ${error.toString()}`); + core.debug(error.toString()); core.setFailed(error.message); } } diff --git a/src/namespaces/Inputs.ts b/src/namespaces/Inputs.ts index fb9a89d..8363cd9 100644 --- a/src/namespaces/Inputs.ts +++ b/src/namespaces/Inputs.ts @@ -1,19 +1,28 @@ import {RestEndpointMethodTypes} from '@octokit/rest'; -export type Args = { - name: string; +interface ArgsBase { token: string; conclusion?: Conclusion; status: Status; - actionURL: string; + actionURL?: string; detailsURL?: string; output?: Output; annotations?: Annotations; images?: Images; actions?: Actions; -}; +} + +export interface ArgsCreate extends ArgsBase { + name: string; +} + +export interface ArgsUpdate extends ArgsBase { + checkID: number; +} + +export type Args = ArgsCreate | ArgsUpdate; // ChecksCreateParamsOutputAnnotations[] export type Annotations = NonNullable<
