Skip to content

Commit

Permalink
reconfiguration reopen-without-PR action (#122)
Browse files Browse the repository at this point in the history
  • Loading branch information
Rosyrain authored Sep 9, 2024
1 parent e099297 commit 8f02ae5
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 113 deletions.
15 changes: 8 additions & 7 deletions .github/workflows/robot.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -310,14 +310,15 @@ jobs:
steps:
- name: Reopen Issue Without PR
id: step1
env:
BASE_URL: https://api.github.com
with:
OWNER: ${{ github.repository_owner }} # 获取执行该作业的仓库作者 形如: matrixorigin
REPOSITORY: ${{ github.repository }} # 获取执行该作业的仓库名 形如: matrixorigin/CI
ISSUE_NUMBER: ${{ github.event.issue.number }} # 获取当前操作的issue number
GITHUB_TOKEN: ${{ secrets.TOKEN_ACTION }} # 获取github action token操作权限
ASSIGNEES: sukki37 #issue指定分配对象
whitelist: 'sukki37,fengttt,aressu1985' # 白名单成员 可以无条件删除带有特殊标签的issue
issue_owner: ${{ github.event.issue.user.login }} # issue的打开者 当满足存在关联pr时允许关闭
close_user: ${{ github.event.sender.login }} # 当前关闭issue的操作者
github_token: ${{ secrets.TOKEN_ACTION }} # 获取github action token操作权限
base_url: ${{ github.api_url }}
repository: ${{ github.repository }} # 获取执行该作业的仓库名 形如: matrixorigin/CI
issue_number: ${{ github.event.issue.number }} # 获取当前操作的issue number
assignees: 'sukki37' #issue指定分配对象
LABELS: 'no-pr-linked' #相关自定义label配置 形如 label1,label2
LABELS_NEED: 'tech-request,feature,Feature,kind/feature,attention/feature-incomplete,bug/ut,Bug fix,kind/bug,kind/subtask,kind/tech-request' # 指定需要排查的标签 label1,label2,...
uses: matrixorigin/CI/actions/reopen-without-PR@main
Expand Down
16 changes: 12 additions & 4 deletions actions/reopen-without-PR/action.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
name: Reopen issue Without PR
name: reopen-without-PR
author: rosyrain
description: When an issue is closed, if there is no associated pull_request, the labels are automatically added and assigned to the specified member.
inputs:
owner:
description: The owner of the repository
required: true
repository:
description: The repository`s name
required: true
Expand All @@ -23,7 +20,18 @@ inputs:
default: 'no-pr-linked'
labels_need:
description: Special labels that need to be handled
required: false
default: 'tech-request,feature,Feature,kind/feature,attention/feature-incomplete,bug/ut,Bug fix,kind/bug,kind/subtask,kind/tech-request'
whitelist:
description: whitelist who can close issue
required: false
default: "sukki77,fengttt,aressu1985"
issue_owner:
require: true
description: issue owner
close_user:
description: close issue user
require: true
outputs:
send:
description: Whether to send a notification
Expand Down
262 changes: 166 additions & 96 deletions actions/reopen-without-PR/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,72 +13,109 @@ import (
"strings"
)

func main() {
// 实现思路
// 1. 加载配置
// 2. 是否为需要处理的issue
// 3. 对关闭该issue的用户进行身份校验
// 3.1 若用户是白名单用户,则直接结束,不做处理
// 3.2 若用户是issue的owner,判断是否存在对关联pr;存在则不做处理,反之
// 3.3 若用户非issue的owner,白名单用户,则直接进行后续处理

var (
// 设置GitHub API的基本URL和认证信息
//Examples of parameters are as follows
//baseURL := "https://api.github.com"
//owner := "Rosyrain"
//repo := "rosyrain/github_action_test"
//issueNumber := 21
//token := "xxxxxxx"
//assignees := "Rosyrain"
//labelData := `no-pr-linked-test2,test3`
//labelsNeed := `tech-request,feature,Feature,kind/feature,attention/feature-incomplete,bug/ut,Bug fix,kind/bug,kind/subtask,kind/tech-request`

baseURL := config.GetBaseURL()
owner := config.GetOwner()
repo := config.GetRepository()
issueNumber := config.GetIssueNumber() // 你要检查的issue编号
token := config.GetGithubToken() // 从环境变量中获取GitHub访问令牌
assignees := config.GetAssignees()
labelData := config.GetLabels()
labelsNeed := config.GetLabelsNeed()

fmt.Println("owner:", owner)
hasRelatedPR := false // 是否存在关联pr

// 1.获取issue时间线判断是否存在pr
// 构建请求URL
issueURL := fmt.Sprintf("%s/repos/%s/issues/%d/timeline", baseURL, repo, issueNumber)
//baseURL = "https://api.github.com"
//owner = "Rosyrain"
//repo = "rosyrain/github_action_test"
//issueNumber = 20
//token = "xxx"
//assignees = "Rosyrain"
//labelData = `no-pr-linked-test2,test3`
//labelsNeed = `tech-request,feature,Feature,kind/feature,attention/feature-incomplete,bug/ut,Bug fix,kind/bug,kind/subtask,kind/tech-request`

baseURL = config.GetBaseURL()
issueOwner = config.GetIssueOwner()
repo = config.GetRepository()
issueNumber = config.GetIssueNumber() // 你要检查的issue编号
token = config.GetGithubToken() // 从环境变量中获取GitHub访问令牌
assignees = config.GetAssignees()
addLabelData = config.GetAddLabels()
labelsNeed = config.GetLabelsNeed()
whitelist = config.GetWhiteList()
closeUser = config.GetCloseUser()
)

// 创建请求头,包含认证信息
issueResp, err := ihttp.Request("GET", issueURL, token, nil, "")
// task 任务处理函数-- 添加标签-->转交sukki37-->reopen issue
func task() {
// 如果没有关联的pull request,给issue添加标签并转交给sukki37
// 添加标签
labelURL := fmt.Sprintf("%s/repos/%s/issues/%d/labels", baseURL, repo, issueNumber)
labelSlice := strings.Split(addLabelData, ",")
jsonData, err := json.Marshal(labelSlice)
if err != nil {
panic(fmt.Sprintf("issueUrl http.Request failed,err:%v", err))
panic("json.Marshal labelSlice failed")
}
if issueResp.StatusCode != http.StatusOK {
panic(fmt.Sprintf("connect issueUrl failed,resp.statusCode:%d", issueResp.StatusCode))
labelResp, err := ihttp.Request("POST", labelURL, token, bytes.NewReader(jsonData), "application/json")
if err != nil {
panic(fmt.Sprintf("Error adding label,err:%v", err))
}
defer issueResp.Body.Close()
fmt.Printf("get issue %d info successfully.\n", issueNumber)
if labelResp.StatusCode != http.StatusOK {
panic(fmt.Sprintf("labelURL http.Request failed,labelStatusCode:%d", labelResp.StatusCode))
}
defer labelResp.Body.Close()
fmt.Printf("Add labels %s successfully.\n", addLabelData)

// 解析响应内容(这里省略了具体的解析过程,你可以根据需要自行处理)
// 检查issue是否有关联的pull request
body, err := io.ReadAll(issueResp.Body)
// 转交给sukki37
assigneeURL := fmt.Sprintf("%s/repos/%s/issues/%d/assignees", baseURL, repo, issueNumber)
assigneeData := map[string]string{"assignees": assignees}
jsonData, err = json.Marshal(assigneeData)
if err != nil {
panic(fmt.Sprintf("Error reading issue response body:%v", err))
panic("json.Marshal assigneeData failed")
}
var issueData []interface{}
err = json.Unmarshal(body, &issueData)
assigneeResp, err := ihttp.Request("POST", assigneeURL, token, bytes.NewReader(jsonData), "application/json")
if err != nil {
panic(fmt.Sprintf("Error unmarshalling JSON:%v", err))
panic(fmt.Sprintf("Error creating assignee request:%v", err))
}
if assigneeResp.StatusCode != 201 {
panic(fmt.Sprintf("Failed to assign a problem to a specified person,assigneeStatusCode:%d", assigneeResp.StatusCode))
}
defer assigneeResp.Body.Close()
fmt.Printf("Issue labeled and assigned to %s successfully.\n", assignees)

for _, data := range issueData {
dataMap := data.(map[string]interface{})
typeEvent := dataMap["event"]
if typeEvent == "cross-referenced" {
sourceMap := dataMap["source"].(map[string]interface{})
issueMap := sourceMap["issue"].(map[string]interface{})
_, ok := issueMap["pull_request"]
if ok {
hasRelatedPR = true
break
}
}
//重新打开issue
reopenURL := fmt.Sprintf("%s/repos/%s/issues/%d", baseURL, repo, issueNumber)
reopenData := map[string]string{"state": "open"}
jsonData, err = json.Marshal(reopenData)
if err != nil {
panic("json.Marshal reopenData failed")
}
reopenResp, err := ihttp.Request("PATCH", reopenURL, token, bytes.NewReader(jsonData), "application/json")
if err != nil {
panic(fmt.Sprintf("Error creating reopen request:%v", err))
}
if reopenResp.StatusCode != http.StatusOK {
panic(fmt.Sprintf("Failed to reopen topic,reopenStatusCode:%d", reopenResp.StatusCode))
}
defer reopenResp.Body.Close()
fmt.Printf("Issue %d reopen successfully.\n", issueNumber)
if err := github.SetOutput("send", "yes"); err != nil {
fmt.Println("set outputs failed")
}
}

//2.判断是否为指定labels
func main() {
// 打印相关信息
fmt.Printf("whitelist:%s\n", whitelist)
fmt.Printf("issueOwner:%s\n", issueOwner)
fmt.Printf("closeUser:%s\n", closeUser)
fmt.Printf("baseURL:%s\n", baseURL)
fmt.Printf("repo:%s\n", repo)
fmt.Printf("issueNumber:%d\n", issueNumber)
fmt.Printf("assignees:%s\n", assignees)
fmt.Printf("addLabels:%s\n", addLabelData)
fmt.Printf("labelsNeed:%s\n", labelsNeed)

//1.判断是否为指定labels
labelNeedURL := fmt.Sprintf("%s/repos/%s/issues/%d/labels", baseURL, repo, issueNumber)
labelNeedResp, err := ihttp.Request("GET", labelNeedURL, token, nil, "")
if err != nil {
Expand Down Expand Up @@ -118,64 +155,97 @@ func main() {
}
}

//3.进行加标签转交指定人员
if !hasRelatedPR && hasSame {
// 如果没有关联的pull request,给issue添加标签并转交给sukki37
// 添加标签
labelURL := fmt.Sprintf("%s/repos/%s/issues/%d/labels", baseURL, repo, issueNumber)
labelSlice := strings.Split(labelData, ",")
jsonData, err := json.Marshal(labelSlice)
if err != nil {
panic("json.Marshal labelSlice failed")
}
labelResp, err := ihttp.Request("POST", labelURL, token, bytes.NewReader(jsonData), "application/json")
if err != nil {
panic(fmt.Sprintf("Error adding label,err:%v", err))
}
if labelResp.StatusCode != http.StatusOK {
panic(fmt.Sprintf("labelURL http.Request failed,labelStatusCode:%d", labelResp.StatusCode))
if !hasSame {
fmt.Printf("this issue don`t have special labels")
if err := github.SetOutput("send", "no"); err != nil {
fmt.Println("set outputs failed")
}
defer labelResp.Body.Close()
fmt.Printf("Add labels %s successfully.\n", labelData)
return
}

// 转交给sukki37
assigneeURL := fmt.Sprintf("%s/repos/%s/issues/%d/assignees", baseURL, repo, issueNumber)
assigneeData := map[string]string{"assignees": assignees}
jsonData, err = json.Marshal(assigneeData)
if err != nil {
panic("json.Marshal assigneeData failed")
// 2.1判断是否为白名单人员
whiteSlice := strings.Split(whitelist, ",")

for _, user := range whiteSlice {
if closeUser == user {
fmt.Printf("close issue user %s is in whitelist\n", user)
if err := github.SetOutput("send", "no"); err != nil {
fmt.Println("set outputs failed")
}
return
}
assigneeResp, err := ihttp.Request("POST", assigneeURL, token, bytes.NewReader(jsonData), "application/json")
}

// 进行2.2/2.3判断 -- 已经确定非白名单用户
// 2.3非白名单非issue的owner直接进行处理
if closeUser != issueOwner {
fmt.Printf("closeUser %s is not issueOwner %s,will exec task...", closeUser, issueOwner)
task()
return
}

// 2.2 issue_owner用户处理
hasRelatedPR := false // 是否存在关联pr

// 1.获取issue时间线判断是否存在pr
// 构建请求URL
for page := 1; page <= 100; page++ {
fmt.Printf("get issue info,page=%d,per_page=100\n", page)
issueURL := fmt.Sprintf("%s/repos/%s/issues/%d/timeline?page=%d&per_page=100", baseURL, repo, issueNumber, page)

// 创建请求头,包含认证信息
issueResp, err := ihttp.Request("GET", issueURL, token, nil, "")
if err != nil {
panic(fmt.Sprintf("Error creating assignee request:%v", err))
panic(fmt.Sprintf("issueUrl http.Request failed,err:%v", err))
}
if assigneeResp.StatusCode != 201 {
panic(fmt.Sprintf("Failed to assign a problem to a specified person,assigneeStatusCode:%d", assigneeResp.StatusCode))
if issueResp.StatusCode != http.StatusOK {
panic(fmt.Sprintf("connect issueUrl failed,resp.statusCode:%d", issueResp.StatusCode))
}
defer assigneeResp.Body.Close()
fmt.Printf("Issue labeled and assigned to %s successfully.\n", assignees)
defer issueResp.Body.Close()
fmt.Printf("get issue %d info successfully.\n", issueNumber)

//重新打开issue
reopenURL := fmt.Sprintf("%s/repos/%s/issues/%d", baseURL, repo, issueNumber)
reopenData := map[string]string{"state": "open"}
jsonData, err = json.Marshal(reopenData)
// 解析响应内容(这里省略了具体的解析过程,你可以根据需要自行处理)
// 检查issue是否有关联的pull request
body, err := io.ReadAll(issueResp.Body)
if err != nil {
panic("json.Marshal reopenData failed")
panic(fmt.Sprintf("Error reading issue response body:%v", err))
}
reopenResp, err := ihttp.Request("PATCH", reopenURL, token, bytes.NewReader(jsonData), "application/json")
var issueData []interface{}
err = json.Unmarshal(body, &issueData)
if err != nil {
panic(fmt.Sprintf("Error creating reopen request:%v", err))
panic(fmt.Sprintf("Error unmarshalling JSON:%v", err))
}
if reopenResp.StatusCode != http.StatusOK {
panic(fmt.Sprintf("Failed to reopen topic,reopenStatusCode:%d", reopenResp.StatusCode))

// 如果获取到到数据为空,则说明已经全部获取了,退出循环
if len(issueData) == 0 {
break
}
defer reopenResp.Body.Close()
fmt.Printf("Issue %d reopen successfully.\n", issueNumber)
if err := github.SetOutput("send", "yes"); err != nil {
fmt.Println("set outputs failed")

for _, data := range issueData {
dataMap := data.(map[string]interface{})
typeEvent := dataMap["event"]
if typeEvent == "cross-referenced" {
sourceMap := dataMap["source"].(map[string]interface{})
issueMap := sourceMap["issue"].(map[string]interface{})
_, ok := issueMap["pull_request"]
if ok {
hasRelatedPR = true
break
}
}
}
if hasRelatedPR {
break
}
}

//3.进行加标签转交指定人员
if !hasRelatedPR {
fmt.Println("dont found related PR...")
task()
return
} else {
fmt.Println("Issue has related pull requests or don`t find need issue, no action taken.")
fmt.Println("Issue has related pull requests or don`t find need issue, no action token.")
if err := github.SetOutput("send", "no"); err != nil {
fmt.Println("set outputs failed")
}
Expand Down
5 changes: 4 additions & 1 deletion actions/reopen-without-PR/pkg/config/config.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package config

const (
EnvOwner = "owner"
EnvBaseURL = "base_url"
EnvRepository = "repository"
EnvIssueNumber = "issue_number"
EnvGithubToken = "github_token"
EnvAssignees = "assignees"
EnvLabels = "labels"
EnvLabelsNeed = "labels_need"
EnvWhitelist = "whitelist"
EnvIssueOwner = "issue_owner"
EnvCloseUser = "close_user"
)
Loading

0 comments on commit 8f02ae5

Please sign in to comment.