diff --git a/README.md b/README.md
index 39cbd194..b595a7a4 100644
--- a/README.md
+++ b/README.md
@@ -145,6 +145,7 @@ The `createPullRequest()` method creates a GitHub Pull request with the files gi
| message | `string` | The commit message for the changes. Default is `'code suggestions'`. We recommend following [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/).|
| force | `boolean` | Whether or not to force push the reference even if the ancestor commits differs. Default is `false`. |
| fork | `boolean` | Whether or not code suggestion should be made from a fork, defaults to `true` (_Note: forking does not work when using `secrets.GITHUB_TOKEN` in an action_). |
+| labels | `string[]`| The list of labels to add to the pull request. Default is none. |
#### `logger`
*[Logger](https://www.npmjs.com/package/@types/pino)*
@@ -231,6 +232,10 @@ Whether or not to force push a reference with different commit history before th
*boolean*
Whether or not to attempt forking to a separate repository. Default value is: `true`.
+#### `--labels`
+*array*
+The list of labels to add to the pull request. Default is none.
+
### Example
```
code-suggester pr -o foo -r bar -d 'description' -t 'title' -m 'message' --git-dir=.
@@ -328,6 +333,10 @@ Whether or not maintainers can modify the pull request. Default value is: `true`
*boolean*
Whether or not to attempt forking to a separate repository. Default value is: `true`.
+#### `labels`
+*array*
+The list of labels to add to the pull request. Default is none.
+
#### Example
The following example is a `.github/workflows/main.yaml` file in repo `Octocat/HelloWorld`. This would add a LICENSE folder to the root `HelloWorld` repo on every pull request if it is not already there.
@@ -356,6 +365,9 @@ jobs:
message: 'chore(license): add license file'
branch: my-branch
git_dir: '.'
+ labels: |
+ bug
+ priority: p1
```
### Review a Pull Request
diff --git a/src/bin/code-suggester.ts b/src/bin/code-suggester.ts
index 110d1cfa..230bd915 100644
--- a/src/bin/code-suggester.ts
+++ b/src/bin/code-suggester.ts
@@ -93,6 +93,12 @@ yargs
default: true,
type: 'boolean',
},
+ labels: {
+ describe:
+ 'The list of labels to add to the pull request. Default is none.',
+ default: [],
+ type: 'array',
+ },
})
.command(REVIEW_PR_COMMAND, 'Review an open pull request', {
'upstream-repo': {
diff --git a/src/bin/workflow.ts b/src/bin/workflow.ts
index 561ce3e8..bc9357d0 100644
--- a/src/bin/workflow.ts
+++ b/src/bin/workflow.ts
@@ -40,6 +40,7 @@ export function coerceUserCreatePullRequestOptions(): CreatePullRequestUserOptio
primary: yargs.argv.primary as string,
maintainersCanModify: yargs.argv.maintainersCanModify as boolean,
fork: yargs.argv.fork as boolean,
+ labels: yargs.argv.labels as string[],
};
}
diff --git a/src/github-handler/index.ts b/src/github-handler/index.ts
index 617fd3dc..d5701367 100644
--- a/src/github-handler/index.ts
+++ b/src/github-handler/index.ts
@@ -17,3 +17,4 @@ export {branch} from './branch-handler';
export {commitAndPush} from './commit-and-push-handler';
export * from './pull-request-handler';
export * from './comment-handler';
+export * from './issue-handler';
diff --git a/src/github-handler/issue-handler.ts b/src/github-handler/issue-handler.ts
new file mode 100644
index 00000000..391e4ca6
--- /dev/null
+++ b/src/github-handler/issue-handler.ts
@@ -0,0 +1,52 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {BranchDomain, RepoDomain} from '../types';
+import {Octokit} from '@octokit/rest';
+import {logger} from '../logger';
+
+/**
+ * Create a GitHub PR on the upstream organization's repo
+ * Throws an error if the GitHub API fails
+ * @param {Octokit} octokit The authenticated octokit instance
+ * @param {RepoDomain} upstream The upstream repository
+ * @param {BranchDomain} origin The remote origin information that contains the origin branch
+ * @param {number} issue_number The issue number to add labels to. Can also be a PR number
+ * @param {string[]} labels The list of labels to apply to the issue/pull request. Default is []. the funciton will no-op.
+ * @returns {Promise} The list of resulting labels after the addition of the given labels
+ */
+async function addLabels(
+ octokit: Octokit,
+ upstream: RepoDomain,
+ origin: BranchDomain,
+ issue_number: number,
+ labels?: string[]
+): Promise {
+ if (!labels || labels.length === 0) {
+ return [];
+ }
+
+ const labelsResponseData = (
+ await octokit.issues.addLabels({
+ owner: upstream.owner,
+ repo: origin.repo,
+ issue_number: issue_number,
+ labels: labels,
+ })
+ ).data;
+ logger.info(`Successfully added labels ${labels} to issue: ${issue_number}`);
+ return labelsResponseData.map(l => l.name);
+}
+
+export {addLabels};
diff --git a/src/index.ts b/src/index.ts
index 2fe5ac4c..fe89eb92 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -176,6 +176,16 @@ async function createPullRequest(
gitHubConfigs.primary
);
logger.info(`Successfully opened pull request: ${prNumber}.`);
+
+ // addLabels will no-op if options.labels is undefined or empty.
+ await handler.addLabels(
+ octokit,
+ upstream,
+ originBranch,
+ prNumber,
+ options.labels
+ );
+
return prNumber;
}
diff --git a/src/types/index.ts b/src/types/index.ts
index 6bc5853a..f7300dd7 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -93,6 +93,8 @@ export interface CreatePullRequestUserOptions {
primary?: string;
// Whether or not maintainers can modify the PR. Default is true. (optional)
maintainersCanModify?: boolean;
+ // The list of labels to apply to the newly created PR. Default is empty. (optional)
+ labels?: string[];
}
/**
diff --git a/test/cli.ts b/test/cli.ts
index 4d9e7513..5453d2ca 100644
--- a/test/cli.ts
+++ b/test/cli.ts
@@ -103,6 +103,7 @@ describe('Mapping pr yargs to create PR options', () => {
primary: 'primary',
maintainersCanModify: true,
fork: true,
+ labels: ['automerge'],
};
sandbox.stub(yargs, 'argv').value({_: ['pr'], ...options});
diff --git a/test/fixtures/add-labels-response.json b/test/fixtures/add-labels-response.json
new file mode 100644
index 00000000..b1664348
--- /dev/null
+++ b/test/fixtures/add-labels-response.json
@@ -0,0 +1,20 @@
+[
+ {
+ "id": 208045946,
+ "node_id": "MDU6TGFiZWwyMDgwNDU5NDY=",
+ "url": "https://api.github.com/repos/octocat/Hello-World/labels/bug",
+ "name": "bug",
+ "description": "Something isn't working",
+ "color": "f29513",
+ "default": true
+ },
+ {
+ "id": 208045947,
+ "node_id": "MDU6TGFiZWwyMDgwNDU5NDc=",
+ "url": "https://api.github.com/repos/octocat/Hello-World/labels/enhancement",
+ "name": "enhancement",
+ "description": "New feature or request",
+ "color": "a2eeef",
+ "default": false
+ }
+]
diff --git a/test/issues.ts b/test/issues.ts
new file mode 100644
index 00000000..56524d67
--- /dev/null
+++ b/test/issues.ts
@@ -0,0 +1,110 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {expect} from 'chai';
+import {describe, it, before, afterEach} from 'mocha';
+import {octokit, setup} from './util';
+import * as sinon from 'sinon';
+import {addLabels} from '../src/github-handler/issue-handler';
+
+before(() => {
+ setup();
+});
+
+describe('Adding labels', async () => {
+ const sandbox = sinon.createSandbox();
+ const upstream = {owner: 'upstream-owner', repo: 'upstream-repo'};
+ const origin = {
+ owner: 'origin-owner',
+ repo: 'origin-repo',
+ branch: 'issues-test-branch',
+ };
+ const issue_number = 1;
+ const labels = ['enhancement'];
+ afterEach(() => {
+ sandbox.restore();
+ });
+
+ it('Invokes octokit issues add labels on an existing pull request', async () => {
+ // setup
+ const responseAddLabelsData = await import(
+ './fixtures/add-labels-response.json'
+ );
+ const addLabelsResponse = {
+ headers: {},
+ status: 200,
+ url: 'http://fake-url.com',
+ data: responseAddLabelsData,
+ };
+ const stub = sandbox
+ .stub(octokit.issues, 'addLabels')
+ .resolves(addLabelsResponse);
+ // tests
+ const resultingLabels = await addLabels(
+ octokit,
+ upstream,
+ origin,
+ issue_number,
+ labels
+ );
+ sandbox.assert.calledOnceWithExactly(stub, {
+ owner: upstream.owner,
+ repo: origin.repo,
+ issue_number: issue_number,
+ labels: labels,
+ });
+ expect(resultingLabels).to.deep.equal(['bug', 'enhancement']);
+ });
+
+ it('No-op undefined labels', async () => {
+ // setup
+ const stub = sandbox.stub(octokit.issues, 'addLabels').resolves();
+ // tests
+ const resultingLabels = await addLabels(
+ octokit,
+ upstream,
+ origin,
+ issue_number
+ );
+ sandbox.assert.neverCalledWith(stub, sinon.match.any);
+ expect(resultingLabels).to.deep.equal([]);
+ });
+
+ it('No-op with empty labels', async () => {
+ // setup
+ const stub = sandbox.stub(octokit.issues, 'addLabels').resolves();
+ // tests
+ const resultingLabels = await addLabels(
+ octokit,
+ upstream,
+ origin,
+ issue_number,
+ []
+ );
+ sandbox.assert.neverCalledWith(stub, sinon.match.any);
+ expect(resultingLabels).to.deep.equal([]);
+ });
+
+ it('Passes up the error message with a throw when octokit issues add labels fails', async () => {
+ // setup
+ const errorMsg = 'Error message';
+ sandbox.stub(octokit.issues, 'addLabels').rejects(Error(errorMsg));
+ try {
+ await addLabels(octokit, upstream, origin, issue_number, labels);
+ expect.fail();
+ } catch (err) {
+ expect(err.message).to.equal(errorMsg);
+ }
+ });
+});
diff --git a/test/main-make-pr.ts b/test/main-make-pr.ts
index d76ef50e..3e4055f1 100644
--- a/test/main-make-pr.ts
+++ b/test/main-make-pr.ts
@@ -39,6 +39,7 @@ describe('Make PR main function', () => {
const primary = 'custom-primary';
const originRepo = 'Hello-World';
const originOwner = 'octocat';
+ const labelsToAdd = ['automerge'];
const options: CreatePullRequestUserOptions = {
upstreamOwner,
upstreamRepo,
@@ -48,6 +49,7 @@ describe('Make PR main function', () => {
force,
message,
primary,
+ labels: labelsToAdd,
};
const oldHeadSha = '7fd1a60b01f91b314f59955a4e4d4e80d8edf11d';
const changes: Changes = new Map();
@@ -116,6 +118,20 @@ describe('Make PR main function', () => {
expect(testMaintainersCanModify).equals(maintainersCanModify);
expect(testPrimary).equals(primary);
},
+ addLabels: (
+ octokit: Octokit,
+ upstream: {owner: string; repo: string},
+ originBranch: {owner: string; repo: string; branch: string},
+ issue_number: number,
+ labels: string[]
+ ) => {
+ expect(originBranch.owner).equals(originOwner);
+ expect(originBranch.repo).equals(originRepo);
+ expect(originBranch.branch).equals(branch);
+ expect(upstream.owner).equals(upstreamOwner);
+ expect(upstream.repo).equals(upstreamRepo);
+ expect(labels).equals(labelsToAdd);
+ },
};
const stubMakePr = proxyquire.noCallThru()('../src/', {
'./github-handler': stubHelperHandlers,
@@ -171,6 +187,20 @@ describe('Make PR main function', () => {
expect(testMaintainersCanModify).equals(maintainersCanModify);
expect(testPrimary).equals(primary);
},
+ addLabels: (
+ octokit: Octokit,
+ upstream: {owner: string; repo: string},
+ originBranch: {owner: string; repo: string; branch: string},
+ issue_number: number,
+ labels: string[]
+ ) => {
+ expect(originBranch.owner).equals(upstreamOwner);
+ expect(originBranch.repo).equals(upstreamRepo);
+ expect(originBranch.branch).equals(branch);
+ expect(upstream.owner).equals(upstreamOwner);
+ expect(upstream.repo).equals(upstreamRepo);
+ expect(labels).equals(labelsToAdd);
+ },
};
const stubMakePr = proxyquire.noCallThru()('../src/', {
'./github-handler': stubHelperHandlers,