From 0967ca812e7fdc8f5f71402a1b486d5bd061fe20 Mon Sep 17 00:00:00 2001 From: Daniel Shteremberg Date: Thu, 29 Jun 2023 03:45:24 -0700 Subject: [PATCH] Added output (#60) * Added output * removed formatting changes * more formatting changes * Fix merge conflicts * Small test refactoring * tests are passing * Add a test * Add a small jest mock for addLabels. Not used. * Add tests for more cases * get rid of an unused jest mock * fix review points * rebuild + minor README fix * fix review points * update tests * fix formatting * add tests, fix bug * cleanup --------- Co-authored-by: Daniel Shteremberg Co-authored-by: Daniel Shteremberg Co-authored-by: Patrick Ellis Co-authored-by: Andrey Lobanovich --- README.md | 50 +++++++++++++++++++++++++++++++++++++----- __tests__/main.test.ts | 30 +++++++++++++++++++++++++ action.yml | 5 +++++ dist/index.js | 14 +++++++----- src/labeler.ts | 16 +++++++++----- 5 files changed, 99 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index c4153c1c2..97ae7243a 100644 --- a/README.md +++ b/README.md @@ -109,12 +109,12 @@ jobs: Various inputs are defined in [`action.yml`](action.yml) to let you configure the labeler: -| Name | Description | Default | -| - | - | - | -| `repo-token` | Token to use to authorize label changes. Typically the `GITHUB_TOKEN` secret, with `contents:read` and `pull-requests:write` access | `github.token` | -| `configuration-path` | The path to the label configuration file | `.github/labeler.yml` | -| `sync-labels` | Whether or not to remove labels when matching files are reverted or no longer changed by the PR | `false` | -| `dot` | Whether or not to auto-include paths starting with dot (e.g. `.github`) | `false` | +| Name | Description | Default | +|----------------------|-------------------------------------------------------------------------------------------------|-----------------------| +| `repo-token` | Token to use to authorize label changes. Typically the GITHUB_TOKEN secret | N/A | +| `configuration-path` | The path to the label configuration file | `.github/labeler.yml` | +| `sync-labels` | Whether or not to remove labels when matching files are reverted or no longer changed by the PR | `false` | +| `dot` | Whether or not to auto-include paths starting with dot (e.g. `.github`) | `false` | When `dot` is disabled and you want to include _all_ files in a folder: @@ -131,6 +131,44 @@ label1: - path/to/folder/** ``` +#### Outputs + +Labeler provides the following outputs: + +| Name | Description | +|--------------|-----------------------------------------------------------| +| `new-labels` | A comma-separated list of all new labels | +| `all-labels` | A comma-separated list of all labels that the PR contains | + +The following example performs steps based on the output of labeler: +```yml +name: "My workflow" +on: +- pull_request_target + +jobs: + triage: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - id: label-the-PR + uses: actions/labeler@v3 + + - id: run-frontend-tests + if: contains(fromJson(steps.label-the-PR.outputs.all-labels), 'frontend') + run: | + echo "Running frontend tests..." + # Put your commands for running frontend tests here + + - id: run-backend-tests + if: contains(fromJson(steps.label-the-PR.outputs.all-labels), 'backend') + run: | + echo "Running backend tests..." + # Put your commands for running backend tests here +``` + ## Permissions In order to add labels to pull requests, the GitHub labeler action requires diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts index 0c5f7ec68..781a87ea2 100644 --- a/__tests__/main.test.ts +++ b/__tests__/main.test.ts @@ -13,6 +13,7 @@ const reposMock = jest.spyOn(gh.rest.repos, 'getContent'); const paginateMock = jest.spyOn(gh, 'paginate'); const getPullMock = jest.spyOn(gh.rest.pulls, 'get'); const coreWarningMock = jest.spyOn(core, 'warning'); +const setOutputSpy = jest.spyOn(core, 'setOutput'); const yamlFixtures = { 'only_pdfs.yml': fs.readFileSync('__tests__/fixtures/only_pdfs.yml') @@ -50,12 +51,21 @@ describe('run', () => { await run(); expect(setLabelsMock).toHaveBeenCalledTimes(1); + expect(setLabelsMock).toHaveBeenCalledWith({ owner: 'monalisa', repo: 'helloworld', issue_number: 123, labels: ['touched-a-pdf-file'] }); + expect(setOutputSpy).toHaveBeenCalledWith( + 'new-labels', + 'touched-a-pdf-file' + ); + expect(setOutputSpy).toHaveBeenCalledWith( + 'all-labels', + 'touched-a-pdf-file' + ); }); it('(with dot: true) adds labels to PRs that match our glob patterns', async () => { @@ -77,6 +87,14 @@ describe('run', () => { issue_number: 123, labels: ['touched-a-pdf-file'] }); + expect(setOutputSpy).toHaveBeenCalledWith( + 'new-labels', + 'touched-a-pdf-file' + ); + expect(setOutputSpy).toHaveBeenCalledWith( + 'all-labels', + 'touched-a-pdf-file' + ); }); it('(with dot: false) does not add labels to PRs that do not match our glob patterns', async () => { @@ -92,6 +110,8 @@ describe('run', () => { await run(); expect(setLabelsMock).toHaveBeenCalledTimes(0); + expect(setOutputSpy).toHaveBeenCalledWith('new-labels', ''); + expect(setOutputSpy).toHaveBeenCalledWith('all-labels', ''); }); it('(with dot: true) does not add labels to PRs that do not match our glob patterns', async () => { @@ -128,6 +148,8 @@ describe('run', () => { issue_number: 123, labels: ['manually-added'] }); + expect(setOutputSpy).toHaveBeenCalledWith('new-labels', ''); + expect(setOutputSpy).toHaveBeenCalledWith('all-labels', 'manually-added'); }); it('(with sync-labels: false) it issues no delete calls even when there are preexisting PR labels that no longer match the glob pattern', async () => { @@ -148,6 +170,11 @@ describe('run', () => { await run(); expect(setLabelsMock).toHaveBeenCalledTimes(0); + expect(setOutputSpy).toHaveBeenCalledWith('new-labels', ''); + expect(setOutputSpy).toHaveBeenCalledWith( + 'all-labels', + 'touched-a-pdf-file,manually-added' + ); }); it('(with sync-labels: false) it only logs the excess labels', async () => { @@ -178,6 +205,9 @@ describe('run', () => { 'Maximum of 100 labels allowed. Excess labels: touched-a-pdf-file', {title: 'Label limit for a PR exceeded'} ); + const allLabels: string = existingLabels.map(i => i.name).join(','); + expect(setOutputSpy).toHaveBeenCalledWith('new-labels', ''); + expect(setOutputSpy).toHaveBeenCalledWith('all-labels', allLabels); }); }); diff --git a/action.yml b/action.yml index 051718f0c..396cb8f1a 100644 --- a/action.yml +++ b/action.yml @@ -19,6 +19,11 @@ inputs: default: false required: false +outputs: + new-labels: + description: 'A comma-separated list of all new labels' + all-labels: + description: 'A comma-separated list of all labels that the PR contains' runs: using: 'node16' main: 'dist/index.js' diff --git a/dist/index.js b/dist/index.js index 4e56fe002..28b173dd7 100644 --- a/dist/index.js +++ b/dist/index.js @@ -68,8 +68,8 @@ function run() { core.debug(`fetching changed files for pr #${prNumber}`); const changedFiles = yield getChangedFiles(client, prNumber); const labelGlobs = yield getLabelGlobs(client, configPath); - const prLabels = pullRequest.labels.map(label => label.name); - const allLabels = new Set(prLabels); + const preexistingLabels = pullRequest.labels.map(l => l.name); + const allLabels = new Set(preexistingLabels); for (const [label, globs] of labelGlobs.entries()) { core.debug(`processing ${label}`); if (checkGlobs(changedFiles, globs, dot)) { @@ -79,12 +79,16 @@ function run() { allLabels.delete(label); } } - const labels = [...allLabels].slice(0, GITHUB_MAX_LABELS); + const labelsToAdd = [...allLabels].slice(0, GITHUB_MAX_LABELS); const excessLabels = [...allLabels].slice(GITHUB_MAX_LABELS); try { - if (!isListEqual(prLabels, labels)) { - yield setLabels(client, prNumber, labels); + let newLabels = []; + if (!isListEqual(labelsToAdd, preexistingLabels)) { + yield setLabels(client, prNumber, labelsToAdd); + newLabels = labelsToAdd.filter(l => !preexistingLabels.includes(l)); } + core.setOutput('new-labels', newLabels.join(',')); + core.setOutput('all-labels', labelsToAdd.join(',')); if (excessLabels.length) { core.warning(`Maximum of ${GITHUB_MAX_LABELS} labels allowed. Excess labels: ${excessLabels.join(', ')}`, { title: 'Label limit for a PR exceeded' }); } diff --git a/src/labeler.ts b/src/labeler.ts index d23fe4d61..4aac4dd63 100644 --- a/src/labeler.ts +++ b/src/labeler.ts @@ -43,8 +43,8 @@ export async function run() { configPath ); - const prLabels: string[] = pullRequest.labels.map(label => label.name); - const allLabels: Set = new Set(prLabels); + const preexistingLabels = pullRequest.labels.map(l => l.name); + const allLabels: Set = new Set(preexistingLabels); for (const [label, globs] of labelGlobs.entries()) { core.debug(`processing ${label}`); @@ -55,14 +55,20 @@ export async function run() { } } - const labels = [...allLabels].slice(0, GITHUB_MAX_LABELS); + const labelsToAdd = [...allLabels].slice(0, GITHUB_MAX_LABELS); const excessLabels = [...allLabels].slice(GITHUB_MAX_LABELS); try { - if (!isListEqual(prLabels, labels)) { - await setLabels(client, prNumber, labels); + let newLabels: string[] = []; + + if (!isListEqual(labelsToAdd, preexistingLabels)) { + await setLabels(client, prNumber, labelsToAdd); + newLabels = labelsToAdd.filter(l => !preexistingLabels.includes(l)); } + core.setOutput('new-labels', newLabels.join(',')); + core.setOutput('all-labels', labelsToAdd.join(',')); + if (excessLabels.length) { core.warning( `Maximum of ${GITHUB_MAX_LABELS} labels allowed. Excess labels: ${excessLabels.join(