From 33db8746ebddff49eb75c9db1298a3e2603194ec Mon Sep 17 00:00:00 2001 From: JD Cargile Date: Sat, 26 Sep 2020 15:35:35 -0600 Subject: [PATCH] feat: Add only-users functionality (#6) * 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. --- README.md | 18 ++++++++++-- __tests__/util.test.ts | 50 ++++++++++++++++++++++++++++++++ action.yml | 2 ++ dist/index.js | 61 +++++++++++++++++++++++++++++--------- src/main.ts | 9 ++---- src/util.ts | 66 +++++++++++++++++++++++++++++++++++++----- 6 files changed, 178 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index a7bdc23..3cfc280 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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/create-linked-clubhouse-story@v1.4 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + clubhouse-token: ${{ secrets.CLUBHOUSE_TOKEN }} + project-name: Engineering + only-users: dependabot +``` diff --git a/__tests__/util.test.ts b/__tests__/util.test.ts index d06ffda..a3bea36 100644 --- a/__tests__/util.test.ts +++ b/__tests__/util.test.ts @@ -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(); }); @@ -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(); + } + } + ); +}); diff --git a/action.yml b/action.yml index 0e54bc6..b1d7b96 100644 --- a/action.yml +++ b/action.yml @@ -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: diff --git a/dist/index.js b/dist/index.js index 142701c..dfeaa8a 100644 --- a/dist/index.js +++ b/dist/index.js @@ -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(); @@ -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-]*)?/; @@ -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"); diff --git a/src/main.ts b/src/main.ts index c447558..ea91ee6 100644 --- a/src/main.ts +++ b/src/main.ts @@ -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 { if (context.eventName !== "pull_request") { @@ -12,12 +12,9 @@ async function run(): Promise { } 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": diff --git a/src/util.ts b/src/util.ts index b01497c..735cd0c 100644 --- a/src/util.ts +++ b/src/util.ts @@ -29,14 +29,66 @@ function stringFromMap(map: Map): string { return JSON.stringify(Object.fromEntries(Array.from(map.entries()).sort())); } -export function getIgnoredUsers(): Set { - const s = new Set(); - 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 { + const s = new Set(); + if (userList) { + for (const username of userList.split(",")) { + s.add(username.trim()); + } } return s; }