Skip to content

Commit 6781647

Browse files
committed
add issue create and label event
1 parent 4041372 commit 6781647

File tree

5 files changed

+181
-106
lines changed

5 files changed

+181
-106
lines changed

src/common.ts

+28-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,26 @@
11
import axios, { AxiosResponse } from "axios";
22

3-
export interface Config {
3+
export interface R2CN {
44
repos: Repo[];
5+
}
6+
7+
interface Repo {
8+
name: string,
9+
maintainers: Maintainer[]
10+
}
11+
12+
export interface Maintainer {
13+
login: string,
14+
task: number,
15+
maxScore: number
16+
}
17+
18+
export interface Config {
19+
comment: BotComment,
20+
r2cn: R2CN,
21+
}
22+
23+
export interface BotComment {
524
project: ProjectComment,
625
task: TaskComment,
726
command: CommandComment,
@@ -12,18 +31,23 @@ export interface Config {
1231
requestRelease: RequestRelease,
1332
internFail: InternFail,
1433
internDone: InternDone,
34+
internClose: InternClose,
1535
}
1636

37+
1738
interface ProjectComment {
1839
noneProjectComment: string,
1940
noneMaintainerComment: string,
2041
}
2142

2243
interface TaskComment {
44+
success: string,
45+
taskNotFound: string,
2346
scoreUndefinedComment: string,
2447
scoreInvalidComment: string,
2548
insufficientScoreComment: string,
2649
toomanyTask: string,
50+
userToomanyTask: string,
2751
}
2852

2953
interface CommandComment {
@@ -62,9 +86,9 @@ interface InternFail {
6286
interface InternDone {
6387
success: string
6488
}
65-
interface Repo {
66-
name: string,
67-
maintainers: string[]
89+
90+
interface InternClose {
91+
success: string
6892
}
6993

7094
export interface CommandRequest {
@@ -73,7 +97,6 @@ export interface CommandRequest {
7397
github_id: number
7498
}
7599

76-
77100
interface ApiResponse<T> {
78101
message: string;
79102
data: T;

src/index.ts

+77-46
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,49 @@
11
import { Context, Probot } from "probot";
22
import yaml from "js-yaml";
33
import * as Task from "./task.js";
4-
import { Config } from "./common.js";
4+
import { R2CN, BotComment } from "./common.js";
55
import * as Student from "./student.js";
66
import { handle_mentor_cmd } from "./mentor.js";
77

88

99
export default (app: Probot) => {
1010
app.log.info(`api endpoint: ${process.env.API_ENDPOINT}`);
11-
12-
app.on(["issue_comment.created", "issue_comment.edited"], async (context) => {
13-
const comment = context.payload.comment;
14-
const config = await fetchConfig(context);
15-
if (comment.user.type === "Bot") {
16-
context.log.debug("This comment was posted by a bot!");
17-
return
18-
}
11+
app.on(["issues.opened", "issues.labeled"], async (context) => {
1912
const labels = context.payload.issue.labels;
20-
const hasLabel = labels.some((label) => label.name.startsWith("r2cn"));
21-
const creator = context.payload.issue.user.login;
22-
const repo_full_name = context.payload.repository.full_name;
23-
24-
if (hasLabel) {
25-
context.log.debug("R2cn label not found, skipping message")
13+
const hasLabel = labels?.some((label) => label.name.startsWith("r2cn"));
14+
if (!hasLabel) {
15+
context.log.debug("R2cn label not found, skipping message...")
2616
return
2717
}
18+
const config = await fetchConfig(context);
2819
if (config == null) {
2920
context.log.error("Config parsing error");
3021
return
3122
}
32-
const repo = config.repos.find((repo) => repo.name === repo_full_name);
23+
const repo_full_name = context.payload.repository.full_name;
24+
const repo = config.r2cn?.repos.find((repo) => repo.name === repo_full_name);
3325
if (!repo) {
3426
await context.octokit.issues.createComment(context.issue({
35-
body: config.project.noneProjectComment,
27+
body: config.comment.project.noneProjectComment,
3628
}));
3729
return
3830
}
39-
40-
if (!repo.maintainers.includes(creator)) {
31+
const creator = context.payload.issue.user.login;
32+
const maintainer = repo.maintainers.find(maintainer => maintainer.login === creator);
33+
if (!maintainer) {
4134
await context.octokit.issues.createComment(context.issue({
42-
body: config.project.noneMaintainerComment,
35+
body: config.comment.project.noneMaintainerComment,
4336
}));
4437
return
4538
}
4639
const task = await Task.getTask(context.payload.issue.id);
4740
if (task == null) {
48-
const checkRes: Task.CheckTaskResults = await Task.checkTask(context.payload.repository, context.payload.issue, config);
41+
const checkRes: Task.CheckTaskResults = await Task.checkTask(context.payload.repository, context.payload.issue, config, maintainer);
4942
if (checkRes.result) {
5043
const newTaskRes = await Task.newTask(context.payload.repository, context.payload.issue, checkRes.score);
5144
if (newTaskRes) {
5245
await context.octokit.issues.createComment(context.issue({
53-
body: "Task created successfully."
46+
body: config.comment.task.success
5447
}));
5548
}
5649
} else {
@@ -59,41 +52,79 @@ export default (app: Probot) => {
5952
}));
6053
}
6154
} else {
62-
const comment = context.payload.comment.body.trim();
55+
context.log.debug("Task Exist, skipping message...")
56+
}
57+
});
6358

64-
if (comment.startsWith("/request")) {
65-
let res = await Student.handle_stu_cmd(context, context.payload.comment.user, comment, config, task);
66-
context.octokit.issues.createComment(context.issue({
67-
body: res.message
68-
}));
69-
} else if (comment.startsWith("/intern")) {
70-
let res = await handle_mentor_cmd(context, context.payload.comment.user, comment, config, task);
71-
context.octokit.issues.createComment(context.issue({
72-
body: res.message
73-
}));
74-
} else {
75-
context.log.debug("Normal Comment, skipping...")
76-
}
59+
app.on(["issue_comment.created"], async (context) => {
60+
const config = await fetchConfig(context);
61+
if (context.payload.comment.user.type === "Bot") {
62+
// context.log.debug("This comment was posted by a bot!");
63+
return
64+
}
65+
if (config == null) {
66+
context.log.error("Config parsing error");
67+
return
68+
}
69+
const task = await Task.getTask(context.payload.issue.id);
70+
if (task == null) {
71+
await context.octokit.issues.createComment(context.issue({
72+
body: config.comment.task.taskNotFound
73+
}));
74+
}
75+
const command = context.payload.comment.body.trim();
76+
if (command.startsWith("/request")) {
77+
let res = await Student.handle_stu_cmd(context, config, { student: context.payload.comment.user, command, task });
78+
context.octokit.issues.createComment(context.issue({
79+
body: res.message
80+
}));
81+
} else if (command.startsWith("/intern")) {
82+
let res = await handle_mentor_cmd(context, config, {
83+
mentor: context.payload.comment.user, command, issue: context.payload.issue, task
84+
});
85+
context.octokit.issues.createComment(context.issue({
86+
body: res.message
87+
}));
88+
} else {
89+
context.log.debug("Normal Comment, skipping...")
7790
}
7891
});
7992
};
8093

8194

8295
async function fetchConfig(context: Context) {
83-
const response = await context.octokit.repos.getContent({
96+
const r2cn_conf = await context.octokit.repos.getContent({
97+
owner: "r2cn-dev",
98+
repo: "r2cn",
99+
path: "r2cn.yaml",
100+
});
101+
let r2cn: R2CN | null = null;
102+
103+
if ("type" in r2cn_conf.data && r2cn_conf.data.type === "file") {
104+
const content = Buffer.from(r2cn_conf.data.content || "", "base64").toString("utf8");
105+
r2cn = yaml.load(content) as R2CN;
106+
} else {
107+
context.log.error("Parsing r2cn.yaml failed.");
108+
}
109+
110+
const comment_conf = await context.octokit.repos.getContent({
84111
owner: "r2cn-dev",
85-
repo: "organization",
86-
path: "organization.yaml",
112+
repo: "r2cn-bot",
113+
path: "comment.yaml",
87114
});
115+
let comment: BotComment | null = null;
88116

89-
if ("type" in response.data && response.data.type === "file") {
90-
// 如果是文件,解码内容
91-
const content = Buffer.from(response.data.content || "", "base64").toString("utf8");
92-
context.log.debug("Config file content:", content);
93-
const config: Config = yaml.load(content) as Config;
94-
return config;
117+
if ("type" in comment_conf.data && comment_conf.data.type === "file") {
118+
const content = Buffer.from(comment_conf.data.content || "", "base64").toString("utf8");
119+
comment = yaml.load(content) as BotComment;
120+
} else {
121+
context.log.error("Parsing comment.yaml failed.");
122+
}
123+
// 检查是否成功解析
124+
if (r2cn && comment) {
125+
return { comment, r2cn };
95126
} else {
96-
context.log.error("The path is not a file.");
127+
context.log.error("Failed to load Config. Either r2cn or comment is null.");
97128
return null;
98129
}
99130
}

src/mentor.ts

+48-35
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
1-
import { User } from "@octokit/webhooks-types";
1+
import { Issue, User } from "@octokit/webhooks-types";
22
import { CommandRequest, Config, postData } from "./common.js";
33
import { Task, TaskStatus } from "./task.js";
44
import { releaseTask } from "./student.js";
55
import { Context } from "probot";
66

7+
interface Payload {
8+
mentor: User,
9+
command: string,
10+
issue: Issue,
11+
task: Task
12+
}
713

8-
export async function handle_mentor_cmd(context: Context, mentor: User, command: string, config: Config, task: Task) {
14+
export async function handle_mentor_cmd(context: Context, config: Config, payload: Payload) {
915
var command_res = {
1016
result: false,
1117
message: "",
1218
};
13-
19+
const { mentor, command, issue, task } = payload;
1420
const setResponse = (message: string, result: boolean = false) => {
1521
command_res.message = message;
1622
command_res.result = result;
@@ -22,60 +28,59 @@ export async function handle_mentor_cmd(context: Context, mentor: User, command:
2228
};
2329

2430
if (!isMentorAuthorized(task, mentor)) {
25-
return setResponse(config.command.noPermission);
31+
return setResponse(config.comment.command.noPermission);
2632
}
27-
33+
34+
const req = {
35+
github_issue_id: task.github_issue_id,
36+
login: mentor.login,
37+
github_id: mentor.id
38+
};
2839
switch (command) {
2940
case "/intern-disapprove":
3041
if (task.task_status !== TaskStatus.RequestAssign) {
31-
return setResponse(config.command.invalidTaskState);
42+
return setResponse(config.comment.command.invalidTaskState);
3243
}
33-
34-
await releaseTask({
35-
github_issue_id: task.github_issue_id,
36-
login: mentor.login,
37-
github_id: mentor.id
38-
});
39-
return setResponse(config.internDisapprove.success, true);
44+
await releaseTask(req);
45+
return setResponse(config.comment.internDisapprove.success, true);
4046

4147
case "/intern-approve":
4248
if (task.task_status !== TaskStatus.RequestAssign) {
43-
return setResponse(config.command.invalidTaskState);
49+
return setResponse(config.comment.command.invalidTaskState);
4450
}
45-
await internApprove({
46-
github_issue_id: task.github_issue_id,
47-
login: mentor.login,
48-
github_id: mentor.id
49-
});
50-
return setResponse(config.internApprove.success, true);
51+
await internApprove(req);
52+
return setResponse(config.comment.internApprove.success, true);
5153

5254
case "/intern-fail":
5355
if (task.task_status !== TaskStatus.Assigned) {
54-
return setResponse(config.command.invalidTaskState);
56+
return setResponse(config.comment.command.invalidTaskState);
5557
}
56-
await releaseTask({
57-
github_issue_id: task.github_issue_id,
58-
login: mentor.login,
59-
github_id: mentor.id
60-
});
61-
return setResponse(config.internFail.success, true);
58+
await releaseTask(req);
59+
return setResponse(config.comment.internFail.success, true);
6260
case "/intern-done":
6361
if (task.task_status !== TaskStatus.RequestFinish) {
64-
return setResponse(config.command.invalidTaskState);
62+
return setResponse(config.comment.command.invalidTaskState);
6563
}
6664
await context.octokit.issues.update({
6765
owner: task.owner,
6866
repo: task.repo,
6967
issue_number: task.github_issue_number,
7068
state: "closed",
7169
});
72-
await internDone({
73-
github_issue_id: task.github_issue_id,
74-
login: mentor.login,
75-
github_id: mentor.id
76-
});
77-
return setResponse(config.internDone.success, true);
78-
70+
await internDone(req);
71+
return setResponse(config.comment.internDone.success, true);
72+
case "/intern-close":
73+
let label = issue.labels?.find((label) => label.name.startsWith("r2cn"));
74+
if (label) {
75+
await context.octokit.issues.removeLabel({
76+
owner: task.owner,
77+
repo: task.repo,
78+
issue_number: task.github_issue_number,
79+
name: label.name
80+
});
81+
}
82+
await internClose(req);
83+
return setResponse(config.comment.internClose.success, true);
7984
default:
8085
return setResponse("Unsupported command");
8186
}
@@ -96,4 +101,12 @@ async function internDone(req: CommandRequest) {
96101
return res.data
97102
});
98103
return res
104+
}
105+
106+
async function internClose(req: CommandRequest) {
107+
const apiUrl = `${process.env.API_ENDPOINT}/task/intern-close`;
108+
const res = await postData<boolean, CommandRequest>(apiUrl, req).then((res) => {
109+
return res.data
110+
});
111+
return res
99112
}

0 commit comments

Comments
 (0)