-
-
Notifications
You must be signed in to change notification settings - Fork 5.8k
add close issue on move flag to board and add a cron to remove closed issues from board with that flag #28800
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
base: main
Are you sure you want to change the base?
Changes from all commits
70b3673
347ffa3
4fb4dea
b4bb836
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,6 +24,7 @@ import ( | |
"code.gitea.io/gitea/modules/web" | ||
shared_user "code.gitea.io/gitea/routers/web/shared/user" | ||
"code.gitea.io/gitea/services/forms" | ||
issue_service "code.gitea.io/gitea/services/issue" | ||
) | ||
|
||
const ( | ||
|
@@ -542,10 +543,11 @@ func AddBoardToProjectPost(ctx *context.Context) { | |
} | ||
|
||
if err := project_model.NewBoard(ctx, &project_model.Board{ | ||
ProjectID: project.ID, | ||
Title: form.Title, | ||
Color: form.Color, | ||
CreatorID: ctx.Doer.ID, | ||
ProjectID: project.ID, | ||
Title: form.Title, | ||
Color: form.Color, | ||
CreatorID: ctx.Doer.ID, | ||
CloseIssueOnMove: form.Close, | ||
}); err != nil { | ||
ctx.ServerError("NewProjectBoard", err) | ||
return | ||
|
@@ -747,5 +749,19 @@ func MoveIssues(ctx *context.Context) { | |
return | ||
} | ||
|
||
if board.CloseIssueOnMove { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. to remove the
|
||
issueID, err := strconv.ParseInt(ctx.FormString("issueID"), 10, 64) | ||
if err != nil { | ||
ctx.ServerError("moved issueID is required", err) | ||
return | ||
} | ||
|
||
err = issue_service.CloseIssue(ctx, issueID) | ||
if err != nil { | ||
ctx.ServerError("MoveIssuesOnProjectBoard unable to close issue", err) | ||
return | ||
} | ||
} | ||
|
||
ctx.JSONOK() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ import ( | |
"fmt" | ||
"net/http" | ||
"net/url" | ||
"strconv" | ||
"strings" | ||
|
||
"code.gitea.io/gitea/models/db" | ||
|
@@ -25,6 +26,7 @@ import ( | |
"code.gitea.io/gitea/modules/util" | ||
"code.gitea.io/gitea/modules/web" | ||
"code.gitea.io/gitea/services/forms" | ||
issue_service "code.gitea.io/gitea/services/issue" | ||
) | ||
|
||
const ( | ||
|
@@ -483,10 +485,11 @@ func AddBoardToProjectPost(ctx *context.Context) { | |
} | ||
|
||
if err := project_model.NewBoard(ctx, &project_model.Board{ | ||
ProjectID: project.ID, | ||
Title: form.Title, | ||
Color: form.Color, | ||
CreatorID: ctx.Doer.ID, | ||
ProjectID: project.ID, | ||
Title: form.Title, | ||
Color: form.Color, | ||
CreatorID: ctx.Doer.ID, | ||
CloseIssueOnMove: form.Close, | ||
}); err != nil { | ||
ctx.ServerError("NewProjectBoard", err) | ||
return | ||
|
@@ -696,5 +699,19 @@ func MoveIssues(ctx *context.Context) { | |
return | ||
} | ||
|
||
if board.CloseIssueOnMove { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same as above |
||
issueID, err := strconv.ParseInt(ctx.FormString("issueID"), 10, 64) | ||
if err != nil { | ||
ctx.ServerError("moved issueID is required", err) | ||
return | ||
} | ||
|
||
err = issue_service.CloseIssue(ctx, issueID) | ||
if err != nil { | ||
ctx.ServerError("MoveIssuesOnProjectBoard unable to close issue", err) | ||
return | ||
} | ||
} | ||
|
||
ctx.JSONOK() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
// Copyright 2023 The Gitea Authors. All rights reserved. | ||
// SPDX-License-Identifier: MIT | ||
|
||
package actions | ||
|
||
import ( | ||
"context" | ||
"time" | ||
|
||
"code.gitea.io/gitea/models/db" | ||
issues_model "code.gitea.io/gitea/models/issues" | ||
project_model "code.gitea.io/gitea/models/project" | ||
"code.gitea.io/gitea/modules/log" | ||
"xorm.io/builder" | ||
) | ||
|
||
// Cleanup removes closed issues from project board with close on move flag set to true | ||
func CleanupProjectIssues(taskCtx context.Context, olderThan time.Duration) error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. while this function would work with smaller instances, the resource consumption with larger ones can grow significantly with the several Could this logic be done with SQL itself, a join on projects where they have closeonmove, with issues that belong to a board that are closed? Then a separate statement to rm the issue from the project? Then in the rm statement(s) you could batch rm the rows from the DB? |
||
log.Info("CRON: remove closed issues from boards with close on move flag set to true starting...") | ||
|
||
projects, err := getAllProjects(taskCtx) | ||
if err != nil { | ||
return err | ||
} | ||
for _, project := range projects { | ||
boards, err := getAllProjectBoardWithCloseOnMove(taskCtx, project) | ||
if err != nil { | ||
log.Error("Cannot get boards of project ID %d: %v", project.ID, err) | ||
continue | ||
} | ||
log.Info("Found %d boards with close on move true", len(boards)) | ||
for _, board := range boards { | ||
issues, err := getAllIssuesOfBoard(taskCtx, board) | ||
if err != nil { | ||
log.Error("Cannot get issues of board ID %d: %v", board.ID, err) | ||
continue | ||
} | ||
issuesToBeRemoved, err := getAllIssuesToBeRemoved(taskCtx, issues) | ||
if err != nil { | ||
log.Error("Cannot get issues of to be removed of board ID %d: %v", board.ID, err) | ||
continue | ||
} | ||
for _, issueToBeRemoved := range issuesToBeRemoved { | ||
err = removeIssueFromProject(taskCtx, issueToBeRemoved, project) | ||
if err != nil { | ||
log.Error("Cannot remove issue ID %d from board ID %d: %v", issueToBeRemoved.ID, board.ID, err) | ||
continue | ||
} | ||
log.Info("Removed issue ID %d from board ID %d", issueToBeRemoved.ID, board.ID) | ||
} | ||
log.Info("completed removing closed issues from board ID %d", board.ID) | ||
} | ||
log.Info("completed removing closed issues project ID %d", project.ID) | ||
} | ||
|
||
log.Info("CRON: remove closed issues from boards with close on move flag true completed.") | ||
|
||
return nil | ||
} | ||
|
||
func getAllProjects(ctx context.Context) ([]project_model.Project, error) { | ||
var projects []project_model.Project | ||
|
||
err := db.GetEngine(ctx).Table("project").Select("*").Find(&projects) | ||
if err != nil { | ||
log.Error("unable to read project db %v", err) | ||
return projects, err | ||
} | ||
return projects, nil | ||
} | ||
|
||
func getAllProjectBoardWithCloseOnMove(ctx context.Context, project project_model.Project) ([]project_model.Board, error) { | ||
var boards []project_model.Board | ||
|
||
err := db.GetEngine(ctx).Table("project_board").Select("*").Where(builder.Eq{"project_id": project.ID}).Find(&boards) | ||
if err != nil { | ||
log.Error("unable to read project_board db %v", err) | ||
return boards, err | ||
} | ||
return boards, nil | ||
} | ||
|
||
func getAllIssuesOfBoard(ctx context.Context, board project_model.Board) ([]int64, error) { | ||
var issueIDs []int64 | ||
|
||
err := db.GetEngine(ctx).Table("project_issue").Select("issue_id").Where(builder.Eq{"project_id": board.ProjectID, "project_board_id": board.ID}).Find(&issueIDs) | ||
if err != nil { | ||
log.Error("unable to read project_issue db %v", err) | ||
return issueIDs, err | ||
} | ||
return issueIDs, nil | ||
} | ||
|
||
func getAllIssuesToBeRemoved(ctx context.Context, issueIDs []int64) ([]issues_model.Issue, error) { | ||
var issues []issues_model.Issue | ||
|
||
err := db.GetEngine(ctx).Table("issue").Select("*").Where(builder.Eq{"is_closed": 1}).Where(builder.In("id", issueIDs)).Find(&issues) | ||
if err != nil { | ||
log.Error("unable to read issue db %v", err) | ||
return issues, err | ||
} | ||
|
||
return issues, nil | ||
} | ||
|
||
func removeIssueFromProject(ctx context.Context, issue issues_model.Issue, project project_model.Project) error { | ||
projectIssue := &project_model.ProjectIssue{ | ||
IssueID: issue.ID, | ||
ProjectID: project.ID, | ||
} | ||
|
||
_, err := db.GetEngine(ctx).Table("project_issue").Delete(&projectIssue) | ||
if err != nil { | ||
log.Error("unable to delete project_issue db %v", err) | ||
return err | ||
} | ||
|
||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// Copyright 2024 The Gitea Authors. All rights reserved. | ||
// SPDX-License-Identifier: MIT | ||
|
||
package issue | ||
|
||
import ( | ||
"errors" | ||
|
||
issues_model "code.gitea.io/gitea/models/issues" | ||
"code.gitea.io/gitea/modules/context" | ||
"code.gitea.io/gitea/modules/log" | ||
) | ||
|
||
func CloseIssue(ctx *context.Context, issueID int64) error { | ||
issue, err := issues_model.GetIssueByID(ctx, issueID) | ||
if err != nil { | ||
return errors.New("failed getting issue") | ||
} | ||
|
||
if err := ChangeStatus(ctx, issue, ctx.Doer, "", true); err != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you could also remove |
||
log.Error("ChangeStatus: %v", err) | ||
|
||
if issues_model.IsErrDependenciesLeft(err) { | ||
ctx.JSONError(ctx.Tr("repo.issues.dependency.issue_close_blocked")) | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as there are new fields added to a model struct, could you add a migration to add this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
on a sidenote, regarding migration, i actually broke our prod with a migration(not this pr), it worked after i removed the migration, is there some doc about how to do migration correctly in gitea ?