8000 Add project workflow feature so users can define how to execute steps when project related events fired by lunny · Pull Request #30205 · go-gitea/gitea · GitHub
[go: up one dir, main page]

Skip to content

Add project workflow feature so users can define how to execute steps when project related events fired #30205

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add NewIssue events
  • Loading branch information
lunny committed Apr 5, 2024
commit 95cecbb2943723990aa772c5de97d8b339e17ac8
8 changes: 8 additions & 0 deletions models/project/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ func (p *Project) NumOpenIssues(ctx context.Context) int {
return int(c)
}

func AddIssueToBoard(ctx context.Context, issueID int64, newBoard *Board) error {
return db.Insert(ctx, &ProjectIssue{
IssueID: issueID,
ProjectID: newBoard.ProjectID,
ProjectBoardID: newBoard.ID,
})
}

func MoveIssueToAnotherBoard(ctx context.Context, issueID int64, newBoard *Board) error {
_, err := db.GetEngine(ctx).Exec("UPDATE `project_issue` SET project_board_id=? WHERE issue_id=?", newBoard.ID, issueID)
return err
Expand Down
6 changes: 4 additions & 2 deletions modules/projects/workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const (
// Project workflow event names
EventItemAddedToProject = "item_added_to_project"
EventItemClosed = "item_closed"
EventItem
)

type Event struct {
Expand All @@ -21,8 +22,9 @@ type Event struct {
}

type Workflow struct {
Name string
Events []Event
Name string
Events []Event
ProjectID int64
}

func ParseWorkflow(content string) (*Workflow, error) {
Expand Down
138 changes: 91 additions & 47 deletions services/projects/workflow_notifier.go
< 8000 tr data-hunk="1d8d727d5ac887c1f34dcfc4cc5d0ebaf52a43b0265fe2ac4f0a379e28b23228" class="show-top-border">
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

issues_model "code.gitea.io/gitea/models/issues"
project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
Expand All @@ -32,71 +33,114 @@ func NewNotifier() notify_service.Notifier {
return &workflowNotifier{}
}

func (m *workflowNotifier) IssueChangeStatus(ctx context.Context, doer *user_model.User, commitID string, issue *issues_model.Issue, actionComment *issues_model.Comment, isClosed bool) {
if isClosed {
if err := issue.LoadRepo(ctx); err != nil {
log.Error("IssueChangeStatus: LoadRepo: %v", err)
return
}
gitRepo, err := gitrepo.OpenRepository(ctx, issue.Repo)
if err != nil {
log.Error("IssueChangeStatus: OpenRepository: %v", err)
return
func findRepoProjectsWorkflows(ctx context.Context, repo *repo_model.Repository) ([]*project_module.Workflow, error) {
gitRepo, err := gitrepo.OpenRepository(ctx, repo)
if err != nil {
log.Error("IssueChangeStatus: OpenRepository: %v", err)
return nil, err
}
defer gitRepo.Close()

// Get the commit object for the ref
commit, err := gitRepo.GetCommit(repo.DefaultBranch)
if err != nil {
log.Error("gitRepo.GetCommit: %w", err)
return nil, err
}

tree, err := commit.SubTree(".gitea/projects")
if _, ok := err.(git.ErrNotExist); ok {
return nil, nil
}
if err != nil {
log.Error("commit.SubTree: %w", err)
return nil, err
}

entries, err := tree.ListEntriesRecursiveFast()
if err != nil {
log.Error("tree.ListEntriesRecursiveFast: %w", err)
return nil, err
}

ret := make(git.Entries, 0, len(entries))
for _, entry := range entries {
if strings.HasSuffix(entry.Name(), ".yml") || strings.HasSuffix(entry.Name(), ".yaml") {
ret = append(ret, entry)
}
defer gitRepo.Close()
}
if len(ret) == 0 {
return nil, nil
}

// Get the commit object for the ref
commit, err := gitRepo.GetCommit(issue.Repo.DefaultBranch)
wfs := make([]*project_module.Workflow, 0, len(ret))
for _, entry := range ret {
workflowContent, err := commit.GetFileContent(".gitea/projects/"+entry.Name(), 1024*1024)
if err != nil {
log.Error("gitRepo.GetCommit: %w", err)
return
return nil, err
}

tree, err := commit.SubTree(".gitea/projects")
if _, ok := err.(git.ErrNotExist); ok {
return
wf, err := project_module.ParseWorkflow(workflowContent)
if err != nil {
log.Error("IssueChangeStatus: OpenRepository: %v", err)
return nil, err
}
projectName := strings.TrimSuffix(strings.TrimSuffix(entry.Name(), ".yml"), ".yaml")
project, err := project_model.GetProjectByName(ctx, repo.ID, projectName)
if err != nil {
log.Error("commit.SubTree: %w", err)
return
log.Error("IssueChangeStatus: GetProjectByName: %v", err)
return nil, err
}
wf.ProjectID = project.ID

entries, err := tree.ListEntriesRecursiveFast()
if err != nil {
log.Error("tree.ListEntriesRecursiveFast: %w", err)
wfs = append(wfs, wf)
}
return wfs, nil
}

func (m *workflowNotifier) NewIssue(ctx context.Context, issue *issues_model.Issue, mentions []*user_model.User) {
if err := issue.LoadRepo(ctx); err != nil {
log.Error("NewIssue: LoadRepo: %v", err)
return
}
wfs, err := findRepoProjectsWorkflows(ctx, issue.Repo)
if err != nil {
log.Error("NewIssue: findRepoProjectsWorkflows: %v", err)
return
}

for _, wf := range wfs {
if err := wf.FireAction(project_module.EventItemClosed, func(action project_module.Action) error {
board, err := project_model.GetBoardByProjectIDAndBoardName(ctx, wf.ProjectID, action.SetValue)
if err != nil {
log.Error("NewIssue: GetBoardByProjectIDAndBoardName: %v", err)
return err
}
return project_model.AddIssueToBoard(ctx, issue.ID, board)
}); err != nil {
log.Error("NewIssue: FireAction: %v", err)
return
}
}
}
}

ret := make(git.Entries, 0, len(entries))
for _, entry := range entries {
if strings.HasSuffix(entry.Name(), ".yml") || strings.HasSuffix(entry.Name(), ".yaml") {
ret = append(ret, entry)
}
func (m *workflowNotifier) IssueChangeStatus(ctx context.Context, doer *user_model.User, commitID string, issue *issues_model.Issue, actionComment *issues_model.Comment, isClosed bool) {
if isClosed {
if err := issue.LoadRepo(ctx); err != nil {
log.Error("IssueChangeStatus: LoadRepo: %v", err)
return
}
if len(ret) == 0 {
wfs, err := findRepoProjectsWorkflows(ctx, issue.Repo)
if err != nil {
log.Error("IssueChangeStatus: findRepoProjectsWorkflows: %v", err)
return
}

for _, entry := range ret {
workflowContent, err := commit.GetFileContent(".gitea/projects/"+entry.Name(), 1024*1024)
if err != nil {
log.Error("gitRepo.GetCommit: %w", err)
return
}

wf, err := project_module.ParseWorkflow(workflowContent)
if err != nil {
log.Error("IssueChangeStatus: OpenRepository: %v", err)
return
}
projectName := strings.TrimSuffix(strings.TrimSuffix(entry.Name(), ".yml"), ".yaml")
project, err := project_model.GetProjectByName(ctx, issue.RepoID, projectName)
if err != nil {
log.Error("IssueChangeStatus: GetProjectByName: %v", err)
return
}
for _, wf := range wfs {
if err := wf.FireAction(project_module.EventItemClosed, func(action project_module.Action) error {
board, err := project_model.GetBoardByProjectIDAndBoardName(ctx, project.ID, action.SetValue)
board, err := project_model.GetBoardByProjectIDAndBoardName(ctx, wf.ProjectID, action.SetValue)
if err != nil {
log.Error("IssueChangeStatus: GetBoardByProjectIDAndBoardName: %v", err)
return err
Expand Down
0