mirror of
https://github.com/go-gitea/gitea.git
synced 2026-04-22 02:29:27 +08:00
Why? You are working on a ticket, it's ready to be moved to the QA column in your project. Currently you have to go to the project, find the issue card, then move it. With this change you can move the issue's column on the issue page. When an issue or pull request belongs to a project board, a dropdown appears in the sidebar to move it between columns without opening the board view. Read-only users see the current column name instead. * Fix #13520 * Replace #30617 This was written using Claude Code and Opus. Closed: <img width="1346" height="507" alt="image" src="https://github.com/user-attachments/assets/7c1ea7ee-b71c-40af-bb14-aeb1d2beff73" /> Open: <img width="1315" height="577" alt="image" src="https://github.com/user-attachments/assets/4d64b065-44c2-42c7-8d20-84b5caea589a" /> --------- Signed-off-by: silverwind <me@silverwind.io> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: Nicolas <bircni@icloud.com> Co-authored-by: Cursor <cursor@cursor.com>
130 lines
3.7 KiB
Go
130 lines
3.7 KiB
Go
// Copyright 2021 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package issues
|
|
|
|
import (
|
|
"context"
|
|
|
|
"code.gitea.io/gitea/models/db"
|
|
project_model "code.gitea.io/gitea/models/project"
|
|
user_model "code.gitea.io/gitea/models/user"
|
|
"code.gitea.io/gitea/modules/util"
|
|
)
|
|
|
|
// LoadProject load the project the issue was assigned to
|
|
func (issue *Issue) LoadProject(ctx context.Context) (err error) {
|
|
if issue.Project == nil {
|
|
var p project_model.Project
|
|
has, err := db.GetEngine(ctx).Table("project").
|
|
Join("INNER", "project_issue", "project.id=project_issue.project_id").
|
|
Where("project_issue.issue_id = ?", issue.ID).Get(&p)
|
|
if err != nil {
|
|
return err
|
|
} else if has {
|
|
issue.Project = &p
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (issue *Issue) projectID(ctx context.Context) int64 {
|
|
var ip project_model.ProjectIssue
|
|
has, err := db.GetEngine(ctx).Where("issue_id=?", issue.ID).Get(&ip)
|
|
if err != nil || !has {
|
|
return 0
|
|
}
|
|
return ip.ProjectID
|
|
}
|
|
|
|
// ProjectColumnID return project column id if issue was assigned to one
|
|
func (issue *Issue) ProjectColumnID(ctx context.Context) (int64, error) {
|
|
var ip project_model.ProjectIssue
|
|
has, err := db.GetEngine(ctx).Where("issue_id=?", issue.ID).Get(&ip)
|
|
if err != nil {
|
|
return 0, err
|
|
} else if !has {
|
|
return 0, nil
|
|
}
|
|
return ip.ProjectColumnID, nil
|
|
}
|
|
|
|
func LoadProjectIssueColumnMap(ctx context.Context, projectID, defaultColumnID int64) (map[int64]int64, error) {
|
|
issues := make([]project_model.ProjectIssue, 0)
|
|
if err := db.GetEngine(ctx).Where("project_id=?", projectID).Find(&issues); err != nil {
|
|
return nil, err
|
|
}
|
|
result := make(map[int64]int64, len(issues))
|
|
for _, issue := range issues {
|
|
if issue.ProjectColumnID == 0 {
|
|
issue.ProjectColumnID = defaultColumnID
|
|
}
|
|
result[issue.IssueID] = issue.ProjectColumnID
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// IssueAssignOrRemoveProject changes the project associated with an issue
|
|
// If newProjectID is 0, the issue is removed from the project
|
|
func IssueAssignOrRemoveProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID, newColumnID int64) error {
|
|
return db.WithTx(ctx, func(ctx context.Context) error {
|
|
oldProjectID := issue.projectID(ctx)
|
|
|
|
if err := issue.LoadRepo(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Only check if we add a new project and not remove it.
|
|
if newProjectID > 0 {
|
|
newProject, err := project_model.GetProjectByID(ctx, newProjectID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !newProject.CanBeAccessedByOwnerRepo(issue.Repo.OwnerID, issue.Repo) {
|
|
return util.NewPermissionDeniedErrorf("issue %d can't be accessed by project %d", issue.ID, newProject.ID)
|
|
}
|
|
if newColumnID == 0 {
|
|
newDefaultColumn, err := newProject.MustDefaultColumn(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newColumnID = newDefaultColumn.ID
|
|
}
|
|
}
|
|
|
|
if _, err := db.GetEngine(ctx).Where("project_issue.issue_id=?", issue.ID).Delete(&project_model.ProjectIssue{}); err != nil {
|
|
return err
|
|
}
|
|
|
|
if oldProjectID > 0 || newProjectID > 0 {
|
|
if _, err := CreateComment(ctx, &CreateCommentOptions{
|
|
Type: CommentTypeProject,
|
|
Doer: doer,
|
|
Repo: issue.Repo,
|
|
Issue: issue,
|
|
OldProjectID: oldProjectID,
|
|
ProjectID: newProjectID,
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if newProjectID == 0 {
|
|
return nil
|
|
}
|
|
if newColumnID == 0 {
|
|
panic("newColumnID must not be zero") // shouldn't happen
|
|
}
|
|
|
|
newSorting, err := project_model.GetColumnIssueNextSorting(ctx, newProjectID, newColumnID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return db.Insert(ctx, &project_model.ProjectIssue{
|
|
IssueID: issue.ID,
|
|
ProjectID: newProjectID,
|
|
ProjectColumnID: newColumnID,
|
|
Sorting: newSorting,
|
|
})
|
|
})
|
|
}
|