Skip to content

Commit dd1c942

Browse files
committedJan 9, 2025
add task creation process
1 parent 70f5c78 commit dd1c942

File tree

5 files changed

+289
-36
lines changed

5 files changed

+289
-36
lines changed
 

‎package-lock.json

+83-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"test": "vitest"
1818
},
1919
"dependencies": {
20+
"axios": "^1.7.9",
2021
"js-yaml": "4.1.0",
2122
"node-fetch": "^3.3.2",
2223
"pino": "^9.6.0",
@@ -36,4 +37,4 @@
3637
"node": ">= 18"
3738
},
3839
"type": "module"
39-
}
40+
}

‎src/common.ts

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import axios, { AxiosResponse } from "axios";
2+
3+
export interface Config {
4+
repos: Repo[];
5+
project: projectComment,
6+
task: TaskComment,
7+
}
8+
9+
interface projectComment {
10+
noneProjectComment: string,
11+
noneMaintainerComment: string,
12+
}
13+
14+
interface TaskComment {
15+
scoreUndefinedComment: string,
16+
scoreInvalidComment: string,
17+
insufficientScoreComment: string,
18+
toomanyTask: string,
19+
}
20+
21+
interface Repo {
22+
name: string,
23+
maintainers: string[]
24+
}
25+
26+
27+
interface ApiResponse<T> {
28+
message: string;
29+
data: T;
30+
}
31+
32+
export const fetchData = async <T>(url: string): Promise<ApiResponse<T>> => {
33+
try {
34+
const response: AxiosResponse<ApiResponse<T>> = await axios.get(url, {
35+
headers: {
36+
'Content-Type': 'application/json',
37+
},
38+
});
39+
console.log('External API response:', response.data);
40+
return response.data;
41+
} catch (error: any) {
42+
console.error('Error fetching external API:', error);
43+
return {
44+
message: error.message || 'Unknown error occurred',
45+
data: null,
46+
} as ApiResponse<T>;
47+
}
48+
};
49+
50+
export const postData = async <T, U>(url: string, payload: U): Promise<ApiResponse<T>> => {
51+
try {
52+
const response: AxiosResponse<ApiResponse<T>> = await axios.post(url, payload, {
53+
headers: {
54+
'Content-Type': 'application/json',
55+
},
56+
});
57+
console.log('External API response:', response.data);
58+
return response.data;
59+
} catch (error: any) {
60+
console.error('Error posting external API:', error);
61+
return {
62+
message: error.message || 'Unknown error occurred',
63+
data: null,
64+
} as ApiResponse<T>;
65+
}
66+
};

‎src/index.ts

+33-32
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,68 @@
11
import { Context, Probot } from "probot";
22
import yaml from "js-yaml";
3-
4-
interface Config {
5-
traceRepos: string[];
6-
taskMaintainers: string[];
7-
noneProjectComment: string,
8-
noneAdminComment: string,
9-
}
3+
import * as Task from "./task.js";
4+
import { Config } from "./common.js";
105

116
export default (app: Probot) => {
12-
app.on("issues.opened", async (context) => {
13-
const issueComment = context.issue({
14-
body: "Thanks for opening this issue!",
15-
});
16-
17-
context.log.info("This issue is about context");
18-
app.log.info("This issue is about app");
19-
await context.octokit.issues.createComment(issueComment);
20-
});
7+
app.log.info(`api endpoint: ${process.env.API_ENDPOINT}`);
218

229
app.on(["issue_comment.created", "issue_comment.edited"], async (context) => {
2310
const comment = context.payload.comment;
24-
const config = await loadConfig(context);
11+
const config = await fetchConfig(context);
2512
if (comment.user.type === "Bot") {
2613
context.log.debug("This comment was posted by a bot!");
2714
return
2815
}
2916
const labels = context.payload.issue.labels;
30-
const hasLabel = labels.some((label) => label.name === "r2cn");
17+
const hasLabel = labels.some((label) => label.name.startsWith("r2cn"));
3118
const creator = context.payload.issue.user.login;
32-
const full_name = context.payload.repository.full_name;
19+
const repo_full_name = context.payload.repository.full_name;
3320

3421
if (hasLabel && config !== null) {
35-
if (!config.taskMaintainers.includes(creator)) {
36-
context.log.debug("none admin")
22+
const repo = config.repos.find((repo) => repo.name === repo_full_name);
23+
if (!repo) {
3724
await context.octokit.issues.createComment(context.issue({
38-
body: config.noneAdminComment,
25+
body: config.project.noneProjectComment,
3926
}));
4027
return
4128
}
42-
if (!config.traceRepos.includes(full_name)) {
43-
context.log.debug("none project")
29+
30+
if (!repo.maintainers.includes(creator)) {
4431
await context.octokit.issues.createComment(context.issue({
45-
body: config.noneProjectComment,
32+
body: config.project.noneMaintainerComment,
4633
}));
4734
return
4835
}
49-
// call api check task status and points.
50-
await context.octokit.issues.createComment(context.issue({
51-
body: "Task created successfully.",
52-
}));
36+
const task = await Task.getTask(context.payload.issue.id);
37+
if (task == null) {
38+
let checkRes: Task.CheckTaskResults = await Task.checkTask(context.payload.repository, context.payload.issue, config);
39+
if (checkRes.result) {
40+
let newTaskRes = await Task.newTask(context.payload.repository, context.payload.issue, checkRes.score);
41+
if (newTaskRes) {
42+
await context.octokit.issues.createComment(context.issue({
43+
body: "Task created successfully."
44+
}));
45+
}
46+
} else {
47+
await context.octokit.issues.createComment(context.issue({
48+
body: checkRes.message
49+
}));
50+
}
51+
} else {
52+
context.log.debug("Task exist, skipping...")
53+
}
5354
} else {
54-
context.log.info("didn't have r2cn label")
55+
context.log.error("R2cn label not found or config parsing error")
5556
}
5657
});
5758
};
5859

5960

60-
async function loadConfig(context: Context) {
61+
async function fetchConfig(context: Context) {
6162
const response = await context.octokit.repos.getContent({
6263
owner: "r2cn-dev",
6364
repo: "organization",
64-
path: ".github/config.yaml",
65+
path: "organization.yaml",
6566
});
6667

6768
if ("type" in response.data && response.data.type === "file") {

‎src/task.ts

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { Issue, Repository } from "@octokit/webhooks-types";
2+
import { Config, fetchData, postData } from "./common.js";
3+
4+
interface Task {
5+
github_repo_id: number,
6+
github_issue_id: number,
7+
points?: number,
8+
task_status: TaskStatus,
9+
student_github_id?: number,
10+
mentor_github_id: number,
11+
}
12+
13+
enum TaskStatus {
14+
Open,
15+
Invalid,
16+
RequestAssign,
17+
Assigned,
18+
RequestFinish,
19+
Finished,
20+
}
21+
22+
export async function getTask(issue_id: number) {
23+
const apiUrl = `${process.env.API_ENDPOINT}/task/issue/${issue_id}`;
24+
const res = await fetchData<Task>(apiUrl).then((res) => {
25+
return res.data
26+
});
27+
return res
28+
}
29+
30+
interface newTask {
31+
github_repo_id: number,
32+
github_issue_id: number,
33+
score: number,
34+
mentor_github_id: number,
35+
}
36+
37+
export async function newTask(repo: Repository, issue: Issue, score: number) {
38+
let req = {
39+
github_repo_id: repo.id,
40+
github_issue_id: issue.id,
41+
score: score,
42+
mentor_github_id: issue.user.id,
43+
}
44+
const apiUrl = `${process.env.API_ENDPOINT}/task/new`;
45+
const res = await postData<Task[], SearchTaskReq>(apiUrl, req).then((res) => {
46+
return res.data
47+
});
48+
if (res != undefined) {
49+
return true
50+
} else {
51+
return false
52+
}
53+
}
54+
55+
export interface CheckTaskResults {
56+
result: boolean,
57+
message: string,
58+
score: number,
59+
}
60+
61+
interface SearchTaskReq {
62+
github_repo_id: number
63+
}
64+
export async function checkTask(repo: Repository, issue: Issue, config: Config) {
65+
66+
const label = issue.labels?.find(label => label.name.startsWith("r2cn"));
67+
var scoreStr = label?.name.split('-')[1];
68+
var score = 0;
69+
var fail_res = {
70+
result: false,
71+
message: "",
72+
score: 0
73+
};
74+
75+
if (scoreStr == undefined) {
76+
fail_res.message = config.task.scoreUndefinedComment;
77+
return fail_res
78+
} else {
79+
score = parseInt(scoreStr)
80+
}
81+
82+
if (score > 50 || score < 2) {
83+
fail_res.message = config.task.scoreInvalidComment;
84+
return fail_res
85+
}
86+
87+
const apiUrl = `${process.env.API_ENDPOINT}/task/search`;
88+
let req = {
89+
github_repo_id: repo.id
90+
}
91+
const tasks: Task[] = await postData<Task[], SearchTaskReq>(apiUrl, req).then((res) => {
92+
return res.data
93+
});
94+
95+
if (tasks.length >= 3) {
96+
fail_res.message = config.task.toomanyTask;
97+
return fail_res
98+
}
99+
100+
return {
101+
result: true,
102+
message: "",
103+
score: score
104+
}
105+
}

0 commit comments

Comments
 (0)
Please sign in to comment.