From cadf7fc3cce04702de183e0604204c1537cb52fb Mon Sep 17 00:00:00 2001 From: Varun-Kolanu Date: Fri, 14 Jun 2024 19:30:51 +0530 Subject: [PATCH] refactor: add classes, object_oriented code --- .env.example | 15 +-- README.md | 68 +++++------ CODE_OF_CONDUCT.md => docs/CODE_OF_CONDUCT.md | 0 CONTRIBUTING.md => docs/CONTRIBUTING.md | 28 +++-- package-lock.json | 16 --- package.json | 1 - src/app.js | 33 +++-- src/classes/event_handler.js | 59 +++++++++ src/handlers/issue_comment.js | 114 ++++++++++++++++++ src/handlers/issue_comment_created.js | 111 ----------------- src/handlers/issue_opened.js | 53 +++----- src/{utils => helpers}/config.js | 4 +- src/helpers/issue_opener.js | 13 +- src/helpers/should_assign.js | 13 +- src/helpers/should_unassign.js | 7 +- src/helpers/skip_commenters.js | 6 +- src/routes/routes.js | 14 +++ src/server.js | 1 + ..._created.test.js => issue_comment.test.js} | 0 test/utils/setupNocks.js | 14 +-- 20 files changed, 317 insertions(+), 253 deletions(-) rename CODE_OF_CONDUCT.md => docs/CODE_OF_CONDUCT.md (100%) rename CONTRIBUTING.md => docs/CONTRIBUTING.md (64%) create mode 100644 src/classes/event_handler.js create mode 100644 src/handlers/issue_comment.js delete mode 100644 src/handlers/issue_comment_created.js rename src/{utils => helpers}/config.js (74%) create mode 100644 src/routes/routes.js rename test/integration/{issue_comment_created.test.js => issue_comment.test.js} (100%) diff --git a/.env.example b/.env.example index 6e106aa..5228f3a 100644 --- a/.env.example +++ b/.env.example @@ -1,9 +1,6 @@ -# The ID of your GitHub App -APP_ID= -WEBHOOK_SECRET=development - -# Use `trace` to get verbose logging or `info` to show less -LOG_LEVEL=debug - -# Go to https://smee.io/new set this to the URL that you are redirected to. -WEBHOOK_PROXY_URL= +WEBHOOK_PROXY_URL=https://smee.io/... +APP_ID=... +PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n......-----END RSA PRIVATE KEY-----\n" +WEBHOOK_SECRET=.... +GITHUB_CLIENT_ID=Iv23ct.... +GITHUB_CLIENT_SECRET=... \ No newline at end of file diff --git a/README.md b/README.md index 34605bc..5d88183 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,25 @@ # GitHub Issue Assigner Bot +[contributing]: /docs/CONTRIBUTING.md + This GitHub bot helps manage issue assignments in a repository by automatically assigning or unassigning issues based on predefined rules and user commands in comments. The bot is implemented using Probot, a framework for building GitHub Apps. If you find this project helpful, please consider giving it a star ⭐ on GitHub. It helps others discover the project and shows your appreciation for the work! +See probot published app [here](https://probot.github.io/apps/issue-assigner/) + # Features - Automatic Assignment: Users can request assignment to an issue by commenting with a specific command. - Limit Assignees: Configure maximum number of assignees allowed per issue. - Limit Assigned Issues: Set a limit on the number of issues a user can be assigned to at a time in the repository. - Automatic unassignment: Users can request unassignment to an issue by commenting with a specific command. -- Issue opened comments: Bot greets the user when issue is opened +- Issue opened comments: Bot greets the user when issue is opened. - Customizable Responses: Customize the bot's responses and prompts using a YAML configuration file. # Usage -1. [Install](https://github.com/apps/issue-assigner) the bot in your account. +1. [Install](https://github.com/apps/issue-assigner/installations/new) the bot in your account. 2. After installing the bot, create a file `.github/issue-assigner.yml` in the repo and paste the following content: ```yml @@ -68,39 +72,33 @@ If you find this project helpful, please consider giving it a star ⭐ on GitHub 4. You can edit the values in the yml to customize the comments from the bot. 5. For example, commenting '@issue-asigner claim' will assign the issue and '@issue-assigner abandon' will remove the assignment. -# Contributing - -If you have any suggestions or want to report a bug, open an issue or make a pull request. - -## Prerequisites - -1. Git installed -2. Node installed - -## Setup - -1. Clone the repository - - ```bash - git clone https://github.com/Varun-Kolanu/issue-assigner.git - ``` - -2. Open the repo - - ```bash - cd issue-assigner - ``` - -3. Install dependencies - - ```bash - npm i - ``` - -4. Run the app - ```bash - npm start - ``` +See these issues for seeing all features practically: [#3](https://github.com/Varun-Kolanu/issue-assigner/issues/3) and [#4](https://github.com/Varun-Kolanu/issue-assigner/issues/4) + +See [Contributing guide][contributing] for contributing to the project. + +# Folder structure + +```bash +. +├── .github/ ## configuration files +│ └── issue-assigner.yml +├── assets/ ## assets like images, icons, etc +├── src/ ## main source code +│ ├── classes/ # classes for event handlers defined +│ ├── handlers/ # functions to handle webhook events from github +│ │ ├── issue_comment.js +│ │ └── issue_opened.js +│ ├── helpers/ # utility functions to help handlers +│ ├── app.js # main file which exports probot app +│ └── server.js # server for the app +├── test/ ## tests for app +│ ├── fixtures/ # mock data for testing +│ ├── integration/ # integration tests +│ ├── unit/ # unit tests +│ └── utils/ # utility functions for tests +├── CONTRIBUTING.md ## Contributing Guide +└── README.md ## Readme +``` # License diff --git a/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md similarity index 100% rename from CODE_OF_CONDUCT.md rename to docs/CODE_OF_CONDUCT.md diff --git a/CONTRIBUTING.md b/docs/CONTRIBUTING.md similarity index 64% rename from CONTRIBUTING.md rename to docs/CONTRIBUTING.md index f6ebb81..3a2ed6a 100644 --- a/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -1,28 +1,38 @@ ## Contributing -[fork]: /fork -[pr]: /compare [code-of-conduct]: CODE_OF_CONDUCT.md Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project you agree to abide by its terms. +## Prerequisites + +1. Git installed +2. Node installed + ## Issues and PRs If you have suggestions for how this project could be improved, or want to report a bug, open an issue! We'd love all and any contributions. If you have questions, too, we'd love to hear them. We'd also love PRs. If you're thinking of a large PR, we advise opening up an issue first to talk about it, though! Look at the links below if you're not sure how to open a PR. +To get an issue assigned, use the issue-assigner bot: + +1. Comment `@issue-assigner claim` to get an issue assigned. +2. Comment `@issue-assigner abandon` to get an issue unassigned. + ## Submitting a pull request -1. [Fork][fork] and clone the repository. -1. Configure and install the dependencies: `npm install`. -1. Make sure the tests pass on your machine: `npm test`, note: these tests also apply the linter, so there's no need to lint separately. -1. Create a new branch: `git checkout -b my-branch-name`. -1. Make your change, add tests, and make sure the tests still pass. -1. Push to your fork and [submit a pull request][pr]. -1. Pat your self on the back and wait for your pull request to be reviewed and merged. +1. Fork and clone the repository. +2. Configure and install the dependencies: `npm install`. +3. Make sure the tests pass on your machine: `npm test`. +4. Create a new branch: `git checkout -b my-branch-name`. +5. Make your change, add tests, and make sure the tests still pass. +6. Push to your fork and submit a pull request. +7. The pull request should contain a phrase like `fixes #1`, `closes #1` etc., to get the PR linked to the issue you re trying to solve. +8. Make the PR a Draft, if not ready for a review and make it Open when your PR is ready. +9. Pat your self on the back and wait for your pull request to be reviewed and merged. Here are a few things you can do that will increase the likelihood of your pull request being accepted: diff --git a/package-lock.json b/package-lock.json index 5e6898d..dd0e828 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,6 @@ "axios": "^1.7.2", "body-parser": "^1.20.2", "dotenv": "^16.4.5", - "express": "^4.19.2", "probot": "^13.0.1" }, "devDependencies": { @@ -1447,21 +1446,6 @@ "node": ">= 0.6" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", diff --git a/package.json b/package.json index 95ab0f8..abe2710 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,6 @@ "axios": "^1.7.2", "body-parser": "^1.20.2", "dotenv": "^16.4.5", - "express": "^4.19.2", "probot": "^13.0.1" }, "devDependencies": { diff --git a/src/app.js b/src/app.js index f6ed22f..3a05493 100644 --- a/src/app.js +++ b/src/app.js @@ -1,25 +1,24 @@ -import bodyParser from "body-parser"; -import issue_comment_created from "./handlers/issue_comment_created.js"; -import issue_opened from "./handlers/issue_opened.js"; +import { + IssueCommentHandler, + IssueOpenerHandler, +} from "./classes/event_handler.js"; +import routes from "./routes/routes.js"; export default async (app, { getRouter }) => { - const router = getRouter("/issue-assigner"); - router.use(bodyParser.json()); - router.get("/", async (req, res) => { - res.status(200).send("Welcome to Issue Assigner"); - }); + // Define routes of server + routes(getRouter); - router.post("/webhook", (req, res) => { - console.log("Webhook from marketplace: ", req.body); - res.status(200).json({ success: "Webhook received successfully" }); - }); + /* Webhooks received */ // Issue opened by a user - app.on("issues.opened", issue_opened); + app.on("issues.opened", async (context) => { + const issueOpenerHandlerObj = new IssueOpenerHandler(context); + await issueOpenerHandlerObj.handleEvent(); + }); // Comment is created in an issue by a user - app.on( - ["issue_comment.created", "issue_comment.edited"], - issue_comment_created - ); + app.on(["issue_comment.created", "issue_comment.edited"], async (context) => { + const issueCommentHandlerObj = new IssueCommentHandler(context); + await issueCommentHandlerObj.handleEvent(); + }); }; diff --git a/src/classes/event_handler.js b/src/classes/event_handler.js new file mode 100644 index 0000000..830b7f4 --- /dev/null +++ b/src/classes/event_handler.js @@ -0,0 +1,59 @@ +import issueCommentHandler from "../handlers/issue_comment.js"; +import issueOpenerHandler from "../handlers/issue_opened.js"; +import { getConfig } from "../helpers/config.js"; + +// Event handler for webhook events +class EventHandler { + // context contains the information of repo + constructor(context) { + this.context = context; + } + + async init() { + // catching issue from the payload of webhook + const { issue } = this.context.payload; + + // Fetching maintainers of the repository to be used later + const collaboratorsJson = + await this.context.octokit.repos.listCollaborators(this.context.repo({})); + const maintainers = collaboratorsJson.data + .filter((coll) => { + return ( + coll.permissions.admin || + coll.permissions.maintain || + coll.permissions.triage + ); + }) + .map((coll) => coll.login); + + // Fetching config, like yml from `.github` folder of repo. + const config = await getConfig(this.context); + + // Saving + this.issue = issue; + this.maintainers = maintainers; + this.config = config; + } + + handleEvent() { + throw new Error("handleEvent method must be implemented"); + } +} + +// Inheriting EventHandler +export class IssueCommentHandler extends EventHandler { + // Override + async handleEvent() { + await this.init(); + await issueCommentHandler.call(this); + } +} + +// Inheriting EventHandler +export class IssueOpenerHandler extends EventHandler { + // Override + async handleEvent() { + await this.init(); + await issueOpenerHandler.call(this); + } +} diff --git a/src/handlers/issue_comment.js b/src/handlers/issue_comment.js new file mode 100644 index 0000000..b7f151e --- /dev/null +++ b/src/handlers/issue_comment.js @@ -0,0 +1,114 @@ +import { skipCommenters } from "../helpers/skip_commenters.js"; +import getAssignedIssues from "../helpers/get_assigned_issues.js"; +import { issueOrIssues, thisOrThese } from "../helpers/pluralize.js"; +import { getConfig } from "../helpers/config.js"; +import { Request, checkRequest } from "../helpers/check_request.js"; +import { Assignment, shouldAssign } from "../helpers/should_assign.js"; +import { UnAssignment, shouldUnAssign } from "../helpers/should_unassign.js"; + +export default async function issueCommentHandler() { + // Fetching comment + const { comment } = this.context.payload; + const commenter = comment.user.login; + + // If commenter is bot or maintainer. + if (skipCommenters(commenter, this.maintainers)) return; + + // Get assignees of the issue + const assignees = this.issue.assignees.map( + (assigneeJson) => assigneeJson.login + ); + + // Check what the commenter is requesting + const request = checkRequest(comment.body, this.config); + + if (request === Request.SKIP) return; + + if (request === Request.ASSIGN) { + // Get already assigned issues in the repo of the user requesting + const assignedIssues = await getAssignedIssues(this.context, commenter); + const numAssignedIssues = assignedIssues.length; + + // should_assign contains the key to be used in the config file. + const should_assign = shouldAssign( + commenter, + assignees, + numAssignedIssues, + this.config + ); + + if (should_assign === Assignment.SKIP) return; + + if (should_assign === Assignment.MAX_ISSUES) { + // If maximum number of issues of issues for a user reached. + return await this.context.octokit.issues.createComment( + this.context.issue({ + body: `@${commenter} You already have ${thisOrThese( + numAssignedIssues + )} ${issueOrIssues(numAssignedIssues)} assigned: ${assignedIssues.map( + (issue) => `[ issue#${issue.number} ](${issue.html_url})` + )}. Abandon your existing ${issueOrIssues( + numAssignedIssues + )} or contact a maintainer if you want to get this issue assigned.`, + }) + ); + } + + if ( + should_assign !== Assignment.ASSIGN && + should_assign !== Assignment.ASSIGN_COMMENT + ) { + return await this.context.octokit.issues.createComment( + this.context.issue({ + body: `@${commenter} ` + this.config[should_assign], + }) + ); + } + + // If all OK, Assign issue to user + await this.context.octokit.issues.addAssignees( + this.context.issue({ + assignees: [commenter], + }) + ); + + // Tell the user that they got issue assigned successfully + if (should_assign === Assignment.ASSIGN_COMMENT) { + return await this.context.octokit.issues.createComment( + this.context.issue({ + body: `@${commenter} ` + this.config[should_assign], + }) + ); + } + } + + // Request for unassignment of issue + else if (request === Request.UNASSIGN) { + const should_unassign = shouldUnAssign(commenter, assignees, this.config); + if (should_unassign === UnAssignment.SKIP) return; + + if (should_unassign === UnAssignment.ALREADY) { + // Issue was already not assigned. + return await this.context.octokit.issues.createComment( + this.context.issue({ + body: `@${commenter} ` + this.config[should_unassign], + }) + ); + } + + // If OK, remove the assignee requesting. + await this.context.octokit.issues.removeAssignees( + this.context.issue({ + assignees: [commenter], + }) + ); + + if (should_unassign === UnAssignment.UNASSIGN_COMMENT) { + await this.context.octokit.issues.createComment( + this.context.issue({ + body: `@${commenter} ` + this.config[should_unassign], + }) + ); + } + } +} diff --git a/src/handlers/issue_comment_created.js b/src/handlers/issue_comment_created.js deleted file mode 100644 index 1aaa0b5..0000000 --- a/src/handlers/issue_comment_created.js +++ /dev/null @@ -1,111 +0,0 @@ -import { skipCommenters } from "../helpers/skip_commenters.js"; -import getAssignedIssues from "../helpers/get_assigned_issues.js"; -import { issueOrIssues, thisOrThese } from "../helpers/pluralize.js"; -import { getConfig } from "../utils/config.js"; -import { Request, checkRequest } from "../helpers/check_request.js"; -import { Assignment, shouldAssign } from "../helpers/should_assign.js"; -import { UnAssignment, shouldUnAssign } from "../helpers/should_unassign.js"; - -export default async (context) => { - const { issue, comment } = context.payload; - const commenter = comment.user.login; - const collaboratorsJson = await context.octokit.repos.listCollaborators( - context.repo({}) - ); - const collaborators = collaboratorsJson.data - .filter((coll) => { - return ( - coll.permissions.admin || - coll.permissions.maintain || - coll.permissions.triage - ); - }) - .map((coll) => coll.login); - - if (skipCommenters(commenter, collaborators)) return; - - const config = await getConfig(context); - - const assignees = issue.assignees.map((assigneeJson) => assigneeJson.login); - - const request = checkRequest(comment.body, config); - if (request === Request.SKIP) return; - - if (request === Request.ASSIGN) { - const assignedIssues = await getAssignedIssues(context, commenter); - const numAssignedIssues = assignedIssues.length; - const should_assign = shouldAssign( - commenter, - assignees, - numAssignedIssues, - config - ); - - if (should_assign === Assignment.SKIP) return; - - if (should_assign === Assignment.MAX_ISSUES) { - return await context.octokit.issues.createComment( - context.issue({ - body: `@${commenter} You already have ${thisOrThese( - numAssignedIssues - )} ${issueOrIssues(numAssignedIssues)} assigned: ${assignedIssues.map( - (issue) => `[ issue#${issue.number} ](${issue.html_url})` - )}. Abandon your existing ${issueOrIssues( - numAssignedIssues - )} or contact a maintainer if you want to get this issue assigned.`, - }) - ); - } - - if ( - should_assign !== Assignment.ASSIGN && - should_assign !== Assignment.ASSIGN_COMMENT - ) { - return await context.octokit.issues.createComment( - context.issue({ - body: `@${commenter} ` + config[should_assign], - }) - ); - } - - // If all OK, Assign issue to user - await context.octokit.issues.addAssignees( - context.issue({ - assignees: [commenter], - }) - ); - - if (should_assign === Assignment.ASSIGN_COMMENT) { - return await context.octokit.issues.createComment( - context.issue({ - body: `@${commenter} ` + config[should_assign], - }) - ); - } - } else if (request === Request.UNASSIGN) { - const should_unassign = shouldUnAssign(commenter, assignees, config); - if (should_unassign === UnAssignment.SKIP) return; - - if (should_unassign === UnAssignment.ALREADY) { - return await context.octokit.issues.createComment( - context.issue({ - body: `@${commenter} ` + config[should_unassign], - }) - ); - } - - await context.octokit.issues.removeAssignees( - context.issue({ - assignees: [commenter], - }) - ); - - if (should_unassign === UnAssignment.UNASSIGN_COMMENT) { - await context.octokit.issues.createComment( - context.issue({ - body: `@${commenter} ` + config[should_unassign], - }) - ); - } - } -}; diff --git a/src/handlers/issue_opened.js b/src/handlers/issue_opened.js index 1b70c28..991df13 100644 --- a/src/handlers/issue_opened.js +++ b/src/handlers/issue_opened.js @@ -1,41 +1,26 @@ import { OpenerIsMaintainer, issueOpener } from "../helpers/issue_opener.js"; -import { getConfig } from "../utils/config.js"; -export default async (context) => { - const { issue } = context.payload; - const collaboratorsJson = await context.octokit.repos.listCollaborators( - context.repo({}) - ); - const collaborators = collaboratorsJson.data - .filter((coll) => { - return ( - coll.permissions.admin || - coll.permissions.maintain || - coll.permissions.triage - ); - }) - .map((coll) => coll.login); - const issue_opener_username = issue.user.login; - - const config = await getConfig(context); +export default async function issueOpenerHandler() { + const issue_opener_username = this.issue.user.login; + // To know who is the issue opener const issue_opener = issueOpener( - config, - collaborators, + this.config, + this.maintainers, issue_opener_username ); + + // Skip if (issue_opener === OpenerIsMaintainer.SKIP) return; - if (issue_opener === OpenerIsMaintainer.YES) { - return await context.octokit.issues.createComment( - context.issue({ - body: config[issue_opener], - }) - ); - } else { - return await context.octokit.issues.createComment( - context.issue({ - body: `@${issue_opener_username} ` + config[issue_opener], - }) - ); - } -}; + + // If issue opener is maintainer, bot comments to instruct other contributors + // Otherwise, greets the user who opened the issue + await this.context.octokit.issues.createComment( + this.context.issue({ + body: + (issue_opener === OpenerIsMaintainer.NO + ? `@${issue_opener_username} ` + : "") + this.config[issue_opener], + }) + ); +} diff --git a/src/utils/config.js b/src/helpers/config.js similarity index 74% rename from src/utils/config.js rename to src/helpers/config.js index c2e8838..9975c4a 100644 --- a/src/utils/config.js +++ b/src/helpers/config.js @@ -1,6 +1,6 @@ import axios from "axios"; -import { checkConfig } from "../helpers/check_config.js"; -import replacePlaceholders from "../helpers/replace_name.js"; +import { checkConfig } from "./check_config.js"; +import replacePlaceholders from "./replace_name.js"; export const getConfig = async function (context) { // Load the configuration diff --git a/src/helpers/issue_opener.js b/src/helpers/issue_opener.js index c184a24..d1d3f1f 100644 --- a/src/helpers/issue_opener.js +++ b/src/helpers/issue_opener.js @@ -1,15 +1,22 @@ +// Keys are enums, values denote the keys that are defined in config file export const OpenerIsMaintainer = Object.freeze({ YES: "issue-opener-is-maintainer", NO: "issue-opener-not-maintainer", SKIP: "Skip", }); -export const issueOpener = (config, collaborators, issue_opener_username) => { - if (collaborators.includes(issue_opener_username)) { +export const issueOpener = (config, maintainers, issue_opener_username) => { + // Issue opener is maintainer + if (maintainers.includes(issue_opener_username)) { + // If feature exists for commenting on issues opened by maintainer in config return config[OpenerIsMaintainer.YES] ? OpenerIsMaintainer.YES : OpenerIsMaintainer.SKIP; - } else { + } + + // Issue opener is not a maintainer + else { + // If feature exists for commenting on issues opened by non-maintainer in config return config[OpenerIsMaintainer.NO] ? OpenerIsMaintainer.NO : OpenerIsMaintainer.SKIP; diff --git a/src/helpers/should_assign.js b/src/helpers/should_assign.js index eae1578..93204fb 100644 --- a/src/helpers/should_assign.js +++ b/src/helpers/should_assign.js @@ -1,3 +1,4 @@ +// Keys are enums, values denote the keys that are defined in config file export const Assignment = Object.freeze({ ALREADY: "issue-already-assigned", MAX_ASSIGNEES: "max-assignees", @@ -9,11 +10,13 @@ export const Assignment = Object.freeze({ }); export function shouldAssign(commenter, assignees, numAssignedIssues, config) { + // If the is already assigned to the commenter return. if (assignees.includes(commenter)) { if (config[Assignment.ALREADY]) return Assignment.ALREADY; else return Assignment.SKIP; } + // If there is feature of limiting maximum number of assignees to an issue and the current issue exceeded that limit, return. if ( Assignment.MAX_ASSIGNEES in config && assignees.length >= Number(config[Assignment.MAX_ASSIGNEES]) @@ -23,13 +26,19 @@ export function shouldAssign(commenter, assignees, numAssignedIssues, config) { else return Assignment.SKIP; } + // If there is feature of limiting maximum number of assignements to a user and the current user exceeded the limit, return. if ( Assignment.MAX_ISSUES in config && numAssignedIssues >= Number(config[Assignment.MAX_ISSUES]) - ) - return Assignment.MAX_ISSUES; + ) { + return Assignment.MAX_ISSUES; // Comment is fixed and not customizable, as it contains some complex string manipulation + } + + // If everything is fine + // If there is feature to tell the user that they got an issue assigned if (config[Assignment.ASSIGN_COMMENT]) return Assignment.ASSIGN_COMMENT; + // Which means only to assign without commenting return Assignment.ASSIGN; } diff --git a/src/helpers/should_unassign.js b/src/helpers/should_unassign.js index 0650656..6707fd1 100644 --- a/src/helpers/should_unassign.js +++ b/src/helpers/should_unassign.js @@ -1,3 +1,4 @@ +// Keys are enums, values denote the keys that are defined in config file export const UnAssignment = Object.freeze({ ALREADY: "issue-was-not-assigned", UNASSIGN_COMMENT: "unassigned-comment", @@ -6,10 +7,14 @@ export const UnAssignment = Object.freeze({ }); export function shouldUnAssign(commenter, assignees, config) { + // If issue was not assigned to the commenter already, return. if (!assignees.includes(commenter)) { if (config[UnAssignment.ALREADY]) return UnAssignment.ALREADY; else return UnAssignment.SKIP; - } else { + } + + // If issue was assigned to the commenter, unassign. + else { if (config[UnAssignment.UNASSIGN_COMMENT]) return UnAssignment.UNASSIGN_COMMENT; return UnAssignment.UNASSIGN; diff --git a/src/helpers/skip_commenters.js b/src/helpers/skip_commenters.js index 6b5ae13..21da2f6 100644 --- a/src/helpers/skip_commenters.js +++ b/src/helpers/skip_commenters.js @@ -1,9 +1,9 @@ -export function skipCommenters(commenter, collaborators) { +export function skipCommenters(commenter, maintainers) { // Skip if bot commented if (commenter.includes("[bot]")) return true; - // Skip if a collaborator comments - if (collaborators.includes(commenter)) return true; + // Skip if a maintainer comments so that maintainer shall not accidentally assign themselves while explaining to the users + if (maintainers.includes(commenter)) return true; return false; } diff --git a/src/routes/routes.js b/src/routes/routes.js new file mode 100644 index 0000000..67c7de3 --- /dev/null +++ b/src/routes/routes.js @@ -0,0 +1,14 @@ +import bodyParser from "body-parser"; + +export default function routes(getRouter) { + const router = getRouter("/issue-assigner"); + router.use(bodyParser.json()); + router.get("/", (_, res) => { + res.status(200).send("Welcome to Issue Assigner"); + }); + + router.post("/webhook", (req, res) => { + console.log("Webhook from marketplace: ", req.body); + res.status(200).json({ success: "Webhook received successfully" }); + }); +} diff --git a/src/server.js b/src/server.js index 35ba688..484f0a5 100644 --- a/src/server.js +++ b/src/server.js @@ -1,4 +1,5 @@ import { run } from "probot"; import app from "./app.js"; +// loads the app and starts the server run(app); diff --git a/test/integration/issue_comment_created.test.js b/test/integration/issue_comment.test.js similarity index 100% rename from test/integration/issue_comment_created.test.js rename to test/integration/issue_comment.test.js diff --git a/test/utils/setupNocks.js b/test/utils/setupNocks.js index 35122bb..4a2c265 100644 --- a/test/utils/setupNocks.js +++ b/test/utils/setupNocks.js @@ -9,13 +9,7 @@ const configYmlPath = path.join(__dirname, "../fixtures/sample-config.yml"); const configYml = fs.readFileSync(configYmlPath, "utf-8"); export const setupNock = (commentBody) => { - return nock("https://api.github.com") - .post("/app/installations/2/access_tokens") - .reply(200, { token: "test" }) - .get("/repos/test-owner/test-repo/collaborators") - .reply(200, [{ login: "test-maintainer", permissions: { maintain: true } }]) - .get("/repos/test-owner/test-repo/contents/.github%2Fissue-assigner.yml") - .reply(200, configYml) + return setupNockSkip() .post("/repos/test-owner/test-repo/issues/1/comments", (body) => { assert.deepStrictEqual(body, { body: commentBody }); return true; @@ -28,9 +22,9 @@ export const setupNockSkip = () => { .post("/app/installations/2/access_tokens") .reply(200, { token: "test" }) .get("/repos/test-owner/test-repo/collaborators") - .reply(200, [ - { login: "test-maintainer", permissions: { maintain: true } }, - ]); + .reply(200, [{ login: "test-maintainer", permissions: { maintain: true } }]) + .get("/repos/test-owner/test-repo/contents/.github%2Fissue-assigner.yml") + .reply(200, configYml); }; // export const setupNockAssigned = (commentBody) => {