You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by po...@apache.org on 2020/12/27 14:02:25 UTC

[airflow-checks-action] 23/27: Rework create/update workflow, unify Checks API arguments

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 <lo...@gmail.com>
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<