Skip to content

Commit

Permalink
feat: Add only-users functionality (singingwolfboy#6)
Browse files Browse the repository at this point in the history
* feat: added only-users list

Adding only-users list that allows us to set the list of users to create an automated Clubhouse story for. For example for dependabot PRs for which we want to track in our sprint cycles.
  • Loading branch information
jdcargile authored Sep 26, 2020
1 parent 4b86221 commit 33db874
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 28 deletions.
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,9 @@ and examine the API response to find the `id` for each user.
Note that Clubhouse makes a distinction between a `User` and a `Member`:
you need to look up the UUID for the `Member` object.

## Ignore Users
## Ignored Users

You can also add a list of GitHub users to ignore for this integration.
You can also add a list of GitHub users to ignore for this integration by using the `ignored-users` input.
Multiple users should be separated by commas.

```yaml
Expand All @@ -133,3 +133,17 @@ Multiple users should be separated by commas.
project-name: Engineering
ignored-users: hubot, dependabot
```

## Only Users

You can also add a list of GitHub `only-users` for this integration. This works opposite of the ignored users list above. For example, if you wanted only PRs from a specific GitHub user such as dependabot PRs.
Multiple users should be separated by commas.

```yaml
- uses: singingwolfboy/[email protected]
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
clubhouse-token: ${{ secrets.CLUBHOUSE_TOKEN }}
project-name: Engineering
only-users: dependabot
```
50 changes: 50 additions & 0 deletions __tests__/util.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ afterEach(() => {
delete process.env["INPUT_CLUBHOUSE-TOKEN"];
delete process.env["INPUT_GITHUB-TOKEN"];
delete process.env["INPUT_PROJECT-NAME"];
delete process.env["INPUT_ONLY-USERS"];
delete process.env["INPUT_IGNORED-USERS"];
nock.restore();
});

Expand Down Expand Up @@ -185,3 +187,51 @@ test("getClubhouseURLFromPullRequest comment", async () => {

scope.done();
});

test("shouldProcessPullRequestForUser user lists not defined", async () => {
expect(util.shouldProcessPullRequestForUser("fake-user-1")).toBeTruthy();
});

test("shouldProcessPullRequestForUser user in both lists", async () => {
const author = "fake-user-1";
process.env["INPUT_ONLY-USERS"] = `${author}, fake-user-2`;
process.env["INPUT_IGNORED-USERS"] = `${author}, fake-user-3`;
expect(() => util.shouldProcessPullRequestForUser(author)).toThrowError(
`PR author ${author} is defined in both ignored-users and only-users lists. Cancelling Clubhouse workflow...`
);
});

test("shouldProcessPullRequestForUser both lists defined", async () => {
process.env["INPUT_ONLY-USERS"] = "fake-user-1, fake-user-2";
process.env["INPUT_IGNORED-USERS"] = "fake-user-3, fake-user-4";
expect(() => {
util.shouldProcessPullRequestForUser("fake-user-2");
}).toBeTruthy();
});

const usersTestCases = [
// [author, userList, expectedResult, userListType]
["fake-user-1", "fake-user-2", "false", "INPUT_ONLY-USERS"],
["fake-user-2", "fake-user-2", "true", "INPUT_ONLY-USERS"],
["fake-user-1", "fake-user-2, fake-user-3", "false", "INPUT_ONLY-USERS"],
["fake-user-2", "fake-user-2, fake-user-3", "true", "INPUT_ONLY-USERS"],
["fake-user-1", "fake-user-2", "true", "INPUT_IGNORED-USERS"],
["fake-user-2", "fake-user-2", "false", "INPUT_IGNORED-USERS"],
["fake-user-1", "fake-user-2, fake-user-3", "true", "INPUT_IGNORED-USERS"],
["fake-user-2", "fake-user-2, fake-user-3", "false", "INPUT_IGNORED-USERS"],
];

describe("shouldProcessPullRequestForUser", () => {
test.each(usersTestCases)(
"for author %p and list %p, returns %p for input type %p",
(user, userList, expectedResult, inputListType) => {
process.env[inputListType] = userList;
const result = util.shouldProcessPullRequestForUser(user);
if (expectedResult === "true") {
expect(result).toBeTruthy();
} else if (expectedResult === "false") {
expect(result).toBeFalsy();
}
}
);
});
2 changes: 2 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ inputs:
Clubhouse story is successfully created. The template is populated
with a `story` variable.
default: "Clubhouse story: {{{ story.app_url }}}"
only-users:
description: Comma-separated list of specific GitHub users to create Clubhouse stories for.
ignored-users:
description: Comma-separated list of GitHub users to ignore. Often used for bots.
user-map:
Expand Down
61 changes: 48 additions & 13 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,9 @@ function run() {
return;
}
const payload = github_1.context.payload;
const ignoredUsers = util_1.getIgnoredUsers();
const author = payload.pull_request.user.login;
if (ignoredUsers.has(author)) {
core.debug(`ignored pull_request event from user ${author}`);
if (!util_1.shouldProcessPullRequestForUser(author))
return;
}
switch (payload.action) {
case "opened":
return opened_1.default();
Expand Down Expand Up @@ -275,7 +272,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.updateClubhouseStoryById = exports.addCommentToPullRequest = exports.getClubhouseURLFromPullRequest = exports.getClubhouseStoryIdFromBranchName = exports.createClubhouseStory = exports.getClubhouseWorkflowState = exports.getClubhouseProjectByName = exports.getClubhouseProject = exports.getClubhouseStoryById = exports.getClubhouseUserId = exports.getIgnoredUsers = exports.CLUBHOUSE_BRANCH_NAME_REGEXP = exports.CLUBHOUSE_STORY_URL_REGEXP = void 0;
exports.updateClubhouseStoryById = exports.addCommentToPullRequest = exports.getClubhouseURLFromPullRequest = exports.getClubhouseStoryIdFromBranchName = exports.createClubhouseStory = exports.getClubhouseWorkflowState = exports.getClubhouseProjectByName = exports.getClubhouseProject = exports.getClubhouseStoryById = exports.getClubhouseUserId = exports.getUserListAsSet = exports.shouldProcessPullRequestForUser = exports.CLUBHOUSE_BRANCH_NAME_REGEXP = exports.CLUBHOUSE_STORY_URL_REGEXP = void 0;
const core = __importStar(__webpack_require__(186));
const github = __importStar(__webpack_require__(438));
exports.CLUBHOUSE_STORY_URL_REGEXP = /https:\/\/app.clubhouse.io\/\w+\/story\/(\d+)(\/[A-Za-z0-9-]*)?/;
Expand All @@ -289,18 +286,56 @@ exports.CLUBHOUSE_BRANCH_NAME_REGEXP = /^(?:.+[-\/])?ch(\d+)(?:[-\/].+)?$/;
function stringFromMap(map) {
return JSON.stringify(Object.fromEntries(Array.from(map.entries()).sort()));
}
function getIgnoredUsers() {
const s = new Set();
const IGNORED_USERS = core.getInput("ignored-users");
if (!IGNORED_USERS) {
return s;
function shouldProcessPullRequestForUser(user) {
const ignoredUsers = getUserListAsSet(core.getInput("ignored-users"));
const onlyUsers = getUserListAsSet(core.getInput("only-users"));
if (ignoredUsers.size === 0 && onlyUsers.size === 0) {
core.debug("No users defined in only-users or ignored-users. Proceeding with Clubhouse workflow...");
return true;
}
if (onlyUsers.size > 0 && ignoredUsers.size > 0) {
if (onlyUsers.has(user) && ignoredUsers.has(user)) {
const errorMessage = `PR author ${user} is defined in both ignored-users and only-users lists. Cancelling Clubhouse workflow...`;
core.setFailed(errorMessage);
throw new Error(errorMessage);
}
else {
core.debug(`Users are defined in both lists. This may create unexpected results.`);
}
}
if (onlyUsers.size > 0) {
if (onlyUsers.has(user)) {
core.debug(`PR author ${user} is defined in only-users list. Proceeding with Clubhouse workflow...`);
return true;
}
else {
core.debug(`You have defined a only-users list, but PR author ${user} isn't in this list. Ignoring user...`);
return false;
}
}
for (const username of IGNORED_USERS.split(",")) {
s.add(username.trim());
if (ignoredUsers.size > 0) {
if (ignoredUsers.has(user)) {
core.debug(`PR author ${user} is defined in ignored-users list. Ignoring user...`);
return false;
}
else {
core.debug(`PR author ${user} is NOT defined in ignored-users list. Proceeding with Clubhouse workflow...`);
return true;
}
}
return true;
}
exports.shouldProcessPullRequestForUser = shouldProcessPullRequestForUser;
function getUserListAsSet(userList) {
const s = new Set();
if (userList) {
for (const username of userList.split(",")) {
s.add(username.trim());
}
}
return s;
}
exports.getIgnoredUsers = getIgnoredUsers;
exports.getUserListAsSet = getUserListAsSet;
function getClubhouseUserId(githubUsername, http) {
return __awaiter(this, void 0, void 0, function* () {
const USER_MAP_STRING = core.getInput("user-map");
Expand Down
9 changes: 3 additions & 6 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { context } from "@actions/github";
import { EventPayloads } from "@octokit/webhooks";
import opened from "./opened";
import closed from "./closed";
import { getIgnoredUsers } from "./util";
import { shouldProcessPullRequestForUser } from "./util";

async function run(): Promise<void> {
if (context.eventName !== "pull_request") {
Expand All @@ -12,12 +12,9 @@ async function run(): Promise<void> {
}

const payload = context.payload as EventPayloads.WebhookPayloadPullRequest;
const ignoredUsers = getIgnoredUsers();
const author = payload.pull_request.user.login;
if (ignoredUsers.has(author)) {
core.debug(`ignored pull_request event from user ${author}`);
return;
}

if (!shouldProcessPullRequestForUser(author)) return;

switch (payload.action) {
case "opened":
Expand Down
66 changes: 59 additions & 7 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,66 @@ function stringFromMap(map: Map<Stringable, Stringable>): string {
return JSON.stringify(Object.fromEntries(Array.from(map.entries()).sort()));
}

export function getIgnoredUsers(): Set<string> {
const s = new Set<string>();
const IGNORED_USERS = core.getInput("ignored-users");
if (!IGNORED_USERS) {
return s;
export function shouldProcessPullRequestForUser(user: string): boolean {
const ignoredUsers = getUserListAsSet(core.getInput("ignored-users"));
const onlyUsers = getUserListAsSet(core.getInput("only-users"));

if (ignoredUsers.size === 0 && onlyUsers.size === 0) {
core.debug(
"No users defined in only-users or ignored-users. Proceeding with Clubhouse workflow..."
);
return true;
}

if (onlyUsers.size > 0 && ignoredUsers.size > 0) {
if (onlyUsers.has(user) && ignoredUsers.has(user)) {
const errorMessage = `PR author ${user} is defined in both ignored-users and only-users lists. Cancelling Clubhouse workflow...`;
core.setFailed(errorMessage);
throw new Error(errorMessage);
} else {
core.debug(
`Users are defined in both lists. This may create unexpected results.`
);
}
}

if (onlyUsers.size > 0) {
if (onlyUsers.has(user)) {
core.debug(
`PR author ${user} is defined in only-users list. Proceeding with Clubhouse workflow...`
);
return true;
} else {
core.debug(
`You have defined a only-users list, but PR author ${user} isn't in this list. Ignoring user...`
);
return false;
}
}
for (const username of IGNORED_USERS.split(",")) {
s.add(username.trim());

if (ignoredUsers.size > 0) {
if (ignoredUsers.has(user)) {
core.debug(
`PR author ${user} is defined in ignored-users list. Ignoring user...`
);
return false;
} else {
core.debug(
`PR author ${user} is NOT defined in ignored-users list. Proceeding with Clubhouse workflow...`
);
return true;
}
}

return true;
}

export function getUserListAsSet(userList: string): Set<string> {
const s = new Set<string>();
if (userList) {
for (const username of userList.split(",")) {
s.add(username.trim());
}
}
return s;
}
Expand Down

0 comments on commit 33db874

Please sign in to comment.