Skip to content

Commit da839b4

Browse files
authored
feat: automatically assign issues to users willing to submit PRs (#219)
* feat: automatically assign issues to users willing to submit PRs * fix test
1 parent f5e74ea commit da839b4

File tree

4 files changed

+164
-0
lines changed

4 files changed

+164
-0
lines changed

src/app.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const plugins = require("./plugins");
2323
//-----------------------------------------------------------------------------
2424

2525
const enabledPlugins = new Set([
26+
"autoAssign",
2627
"commitMessage",
2728
"needsInfo",
2829
"recurringIssues",

src/plugins/auto-assign/index.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* @fileoverview Automatically assigns issues to users who have indicated they're willing to submit a PR
3+
* @author xbinaryx
4+
*/
5+
6+
"use strict";
7+
8+
//-----------------------------------------------------------------------------
9+
// Type Definitions
10+
//-----------------------------------------------------------------------------
11+
12+
/** @typedef {import("probot").Context} ProbotContext */
13+
14+
//-----------------------------------------------------------------------------
15+
// Helpers
16+
//-----------------------------------------------------------------------------
17+
18+
/**
19+
* Checks if the issue body contains text indicating the user is willing to submit a PR
20+
* @param {string} body The issue body text
21+
* @returns {boolean} True if the user indicated they're willing to submit a PR
22+
* @private
23+
*/
24+
function isWillingToSubmitPR(body) {
25+
return body
26+
.toLowerCase()
27+
.includes(
28+
"- [x] i am willing to submit a pull request to implement this change."
29+
);
30+
}
31+
32+
/**
33+
* Handler for issue opened event
34+
* @param {ProbotContext} context probot context object
35+
* @returns {Promise<void>} promise
36+
* @private
37+
*/
38+
async function issueOpenedHandler(context) {
39+
const { payload } = context;
40+
41+
if (!isWillingToSubmitPR(payload.issue.body)) {
42+
return;
43+
}
44+
45+
await context.octokit.issues.addAssignees(
46+
context.issue({
47+
assignees: [payload.issue.user.login],
48+
})
49+
);
50+
}
51+
52+
module.exports = (robot) => {
53+
robot.on("issues.opened", issueOpenedHandler);
54+
};

src/plugins/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
*/
1414

1515
module.exports = {
16+
autoAssign: require("./auto-assign"),
1617
commitMessage: require("./commit-message"),
1718
needsInfo: require("./needs-info"),
1819
recurringIssues: require("./recurring-issues"),

tests/plugins/auto-assign/index.js

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/**
2+
* @fileoverview Tests for the auto-assign plugin
3+
*/
4+
5+
"use strict";
6+
7+
//-----------------------------------------------------------------------------
8+
// Requirements
9+
//-----------------------------------------------------------------------------
10+
11+
const { Probot, ProbotOctokit } = require("probot");
12+
const { default: fetchMock } = require("fetch-mock");
13+
const autoAssign = require("../../../src/plugins/auto-assign");
14+
15+
//-----------------------------------------------------------------------------
16+
// Helpers
17+
//-----------------------------------------------------------------------------
18+
19+
const API_ROOT = "https://api.github.com";
20+
21+
//-----------------------------------------------------------------------------
22+
// Tests
23+
//-----------------------------------------------------------------------------
24+
25+
describe("auto-assign", () => {
26+
let bot = null;
27+
28+
beforeEach(() => {
29+
bot = new Probot({
30+
appId: 1,
31+
githubToken: "test",
32+
Octokit: ProbotOctokit.defaults(instanceOptions => ({
33+
...instanceOptions,
34+
throttle: { enabled: false },
35+
retry: { enabled: false }
36+
}))
37+
});
38+
39+
autoAssign(bot);
40+
});
41+
42+
afterEach(() => {
43+
fetchMock.unmockGlobal();
44+
fetchMock.removeRoutes();
45+
fetchMock.clearHistory();
46+
});
47+
48+
describe("issue opened", () => {
49+
test("assigns issue to author when they indicate willingness to submit PR", async () => {
50+
fetchMock.mockGlobal().post(
51+
`${API_ROOT}/repos/test/repo-test/issues/1/assignees`,
52+
{ status: 200 }
53+
);
54+
55+
await bot.receive({
56+
name: "issues",
57+
payload: {
58+
action: "opened",
59+
installation: {
60+
id: 1
61+
},
62+
issue: {
63+
number: 1,
64+
body: "- [x] I am willing to submit a pull request to implement this change.",
65+
user: {
66+
login: "user-a"
67+
}
68+
},
69+
repository: {
70+
name: "repo-test",
71+
owner: {
72+
login: "test"
73+
}
74+
}
75+
}
76+
});
77+
78+
expect(fetchMock.callHistory.called(`${API_ROOT}/repos/test/repo-test/issues/1/assignees`)).toBeTruthy();
79+
});
80+
81+
test("does not assign issue when author does not indicate willingness to submit PR", async () => {
82+
await bot.receive({
83+
name: "issues",
84+
payload: {
85+
action: "opened",
86+
installation: {
87+
id: 1
88+
},
89+
issue: {
90+
number: 1,
91+
body: "- [] I am willing to submit a pull request to implement this change.",
92+
user: {
93+
login: "user-a"
94+
}
95+
},
96+
repository: {
97+
name: "repo-test",
98+
owner: {
99+
login: "test"
100+
}
101+
}
102+
}
103+
});
104+
105+
expect(fetchMock.callHistory.called()).toBe(false);
106+
});
107+
});
108+
});

0 commit comments

Comments
 (0)