Skip to content
This repository has been archived by the owner on Apr 3, 2023. It is now read-only.

Commit

Permalink
feat: support reviewers api (#28)
Browse files Browse the repository at this point in the history
* feat: support permission api

* feat: add permission

* chore: update package-lock.json

* refactor: refine api

* refactor: refine app

* refactor: refine api url

* refactor: rename

* refactor: rename

* refactor: rename

* refactor: refine import order

* refactor: remove useless comment

* refactor: remove useless comment

* refactor: add comment
  • Loading branch information
hi-rustin committed Sep 28, 2020
1 parent c409c64 commit 1ae99a8
Show file tree
Hide file tree
Showing 15 changed files with 1,442 additions and 402 deletions.
2 changes: 1 addition & 1 deletion app.yml
Expand Up @@ -102,7 +102,7 @@ default_permissions:

# Organization members and teams.
# https://developer.github.com/v3/apps/permissions/#permission-on-members
# members: read
members: read

# View and manage users blocked by the organization.
# https://developer.github.com/v3/apps/permissions/#permission-on-organization-user-blocking
Expand Down
1,238 changes: 873 additions & 365 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions package.json
Expand Up @@ -26,6 +26,11 @@
"dependencies": {
"ajv": "^6.12.5",
"axios": "^0.20.0",
"body-parser": "^1.19.0",
"cors": "^2.8.5",
"deep-equal": "^2.0.3",
"http-status-codes": "^2.1.4",
"js-yaml": "^3.14.0",
"mysql": "^2.18.1",
"probot": "^10.7.1",
"probot-commands-pro": "^1.0.1",
Expand All @@ -36,6 +41,7 @@
},
"devDependencies": {
"@types/jest": "^26.0.9",
"@types/js-yaml": "^3.12.5",
"@types/nock": "^11.1.0",
"@types/node": "^14.11.2",
"@typescript-eslint/parser": "^4.2.0",
Expand Down
Empty file removed src/api/.keep
Empty file.
96 changes: 96 additions & 0 deletions src/api/pull/index.ts
@@ -0,0 +1,96 @@
import { Request, Response } from "express";
import { ProbotOctokit } from "probot/lib/octokit/probot-octokit";

import { PullFileQuery } from "../../queries/PullFileQuery";
import { PullReviewersQuery } from "../../queries/PullReviewersQuery";
import {
Config,
DEFAULT_CONFIG_FILE_PATH,
DEFAULT_SIG_INFO_FILE_NAME,
} from "../../config/Config";
import { config } from "../../services/utils/ConfigUtil";
import { ContributorSchema } from "../../config/SigInfoSchema";
import { StatusCodes } from "http-status-codes";
import { PullMessage } from "../../services/messages/PullMessage";
import PullService from "../../services/pull";

const listReviews = async (
req: Request,
res: Response,
pullService: PullService,
github: InstanceType<typeof ProbotOctokit>
) => {
// Collect params.
const owner = req.params.owner;
const repo = req.params.repo;
const pullNumber = Number(req.params.number);

const { data: filesData } = await github.pulls.listFiles({
owner,
repo,
pull_number: pullNumber,
});

const files: PullFileQuery[] = filesData.map((f) => {
return {
...f,
};
});

// Get config form repo.
const repoConfig = await config<Config>(
owner,
repo,
DEFAULT_CONFIG_FILE_PATH,
github
);

// Because we need this config to get maintainer team.
if (repoConfig === null) {
res.status(StatusCodes.BAD_REQUEST);
const response = {
data: null,
status: StatusCodes.BAD_REQUEST,
message: PullMessage.ConfigNotFound,
};
res.json(response);

return;
}

// Get maintainers and repo collaborators.
const { data: maintainerInfos } = await github.teams.listMembersInOrg({
org: owner,
team_slug: repoConfig.maintainerTeamSlug,
});

const maintainers: ContributorSchema[] = maintainerInfos.map((m) => {
return {
githubId: m.login,
};
});

const { data: collaboratorInfos } = await github.repos.listCollaborators({
owner,
repo,
});
const collaborators = collaboratorInfos.map((c) => {
return {
githubId: c.login,
};
});

const pullReviewersQuery: PullReviewersQuery = {
sigInfoFileName: repoConfig.sigInfoFileName || DEFAULT_SIG_INFO_FILE_NAME,
maintainers,
collaborators,
files,
};

const response = await pullService.listReviewers(pullReviewersQuery);

res.status(response.status);
res.json(response);
};

export default listReviews;
3 changes: 2 additions & 1 deletion src/config/Config.ts
@@ -1,5 +1,6 @@
export interface Config {
sigInfoFileName: string;
sigInfoFileName?: string;
maintainerTeamSlug: string;
}

export const DEFAULT_SIG_INFO_FILE_NAME = "member-list";
Expand Down
43 changes: 43 additions & 0 deletions src/index.ts
Expand Up @@ -6,12 +6,34 @@ import "reflect-metadata";
import handlePullRequestEvents from "./events/pull";
import PullService from "./services/pull";
import { SigService } from "./services/sig";
import listReviewers from "./api/pull";
import { StatusCodes } from "http-status-codes";
import { PullMessage } from "./services/messages/PullMessage";

const commands = require("probot-commands-pro");
const bodyParser = require("body-parser");
const cors = require("cors");

export = (app: Application) => {
useContainer(Container);

// Get an express router to expose new HTTP endpoints.
const router = app.route("/ti-community-bot");
router.use(bodyParser.json());
router.use(cors());

// Get app installation id.
const getInstallationId = async (owner: string): Promise<number | null> => {
const github = await app.auth();

const { data: installationInfos } = await github.apps.listInstallations();
const installations = installationInfos.filter((i) => {
return i.account.login === owner;
});

return installations.length > 0 ? installations[0].id : null;
};

createConnection()
.then(() => {
app.log.info("App starting...");
Expand All @@ -28,6 +50,27 @@ export = (app: Application) => {
Container.get(SigService)
);
});

router.get(
"/repos/:owner/:repo/pulls/:number/reviewers",
async (req, res) => {
const installationId = await getInstallationId(req.params.owner);
if (installationId === null) {
res.status(StatusCodes.BAD_REQUEST);
const response = {
data: null,
status: StatusCodes.BAD_REQUEST,
message: PullMessage.InstallationIdNotFound,
};
res.json(response);

return;
}

const github = await app.auth(installationId);
await listReviewers(req, res, Container.get(PullService), github);
}
);
})
.catch((err) => {
app.log.fatal("Connect to db failed", err);
Expand Down
9 changes: 9 additions & 0 deletions src/queries/PullReviewersQuery.ts
@@ -0,0 +1,9 @@
import { PullFileQuery } from "./PullFileQuery";
import { ContributorSchema } from "../config/SigInfoSchema";

export interface PullReviewersQuery {
sigInfoFileName: string;
files: PullFileQuery[];
maintainers: ContributorSchema[];
collaborators: ContributorSchema[];
}
4 changes: 4 additions & 0 deletions src/services/dtos/PullReviewersDTO.ts
@@ -0,0 +1,4 @@
export interface PullReviewersDTO {
reviewers: string[];
needsLGTM: number;
}
4 changes: 4 additions & 0 deletions src/services/messages/PullMessage.ts
Expand Up @@ -3,6 +3,10 @@ import sigInfoSchema from "../../config/sig.info.schema.json";
export enum PullMessage {
FormatSuccess = "Format Success.",
OnlyOneRole = "Contributors can only have one role.",
ConfigNotFound = "The community bot configuration file could not be found.",
InstallationIdNotFound = "The installation ID could not be found.",
CanNotHandleMultipleSigFiles = "Cannot handle the permissions of multiple sig info files.",
ListReviewersSuccess = "List Reviewers success.",
}

export function mustBeJSONFileMessage(fileName: string) {
Expand Down

0 comments on commit 1ae99a8

Please sign in to comment.