gitea/models/project/issue.go
Giteabot b6ede69a1b
Fixes for unreachable project issues when transfer repository from organization (#31770) (#31828)
Backport #31770 by @emrebdr

When transferring repositories that have issues linked to a project
board to another organization, the issues remain associated with the
original project board. This causes the columns in the project board to
become bugged, making it difficult to move other issues in or out of the
affected columns. As a solution, I removed the issue relations since the
other organization does not have this project table.

Fix for #31538

Co-authored-by: Edip Emre Bodur <emrebdr29@gmail.com>
Co-authored-by: Jason Song <i@wolfogre.com>
2024-08-14 09:57:23 +08:00

150 lines
4.3 KiB
Go

// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package project
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
)
// ProjectIssue saves relation from issue to a project
type ProjectIssue struct { //revive:disable-line:exported
ID int64 `xorm:"pk autoincr"`
IssueID int64 `xorm:"INDEX"`
ProjectID int64 `xorm:"INDEX"`
// ProjectBoardID should not be zero since 1.22. If it's zero, the issue will not be displayed on UI and it might result in errors.
ProjectBoardID int64 `xorm:"INDEX"`
// the sorting order on the board
Sorting int64 `xorm:"NOT NULL DEFAULT 0"`
}
func init() {
db.RegisterModel(new(ProjectIssue))
}
func deleteProjectIssuesByProjectID(ctx context.Context, projectID int64) error {
_, err := db.GetEngine(ctx).Where("project_id=?", projectID).Delete(&ProjectIssue{})
return err
}
// NumIssues return counter of all issues assigned to a project
func (p *Project) NumIssues(ctx context.Context) int {
c, err := db.GetEngine(ctx).Table("project_issue").
Where("project_id=?", p.ID).
GroupBy("issue_id").
Cols("issue_id").
Count()
if err != nil {
log.Error("NumIssues: %v", err)
return 0
}
return int(c)
}
// NumClosedIssues return counter of closed issues assigned to a project
func (p *Project) NumClosedIssues(ctx context.Context) int {
c, err := db.GetEngine(ctx).Table("project_issue").
Join("INNER", "issue", "project_issue.issue_id=issue.id").
Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, true).
Cols("issue_id").
Count()
if err != nil {
log.Error("NumClosedIssues: %v", err)
return 0
}
return int(c)
}
// NumOpenIssues return counter of open issues assigned to a project
func (p *Project) NumOpenIssues(ctx context.Context) int {
c, err := db.GetEngine(ctx).Table("project_issue").
Join("INNER", "issue", "project_issue.issue_id=issue.id").
Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, false).
Cols("issue_id").
Count()
if err != nil {
log.Error("NumOpenIssues: %v", err)
return 0
}
return int(c)
}
// MoveIssuesOnProjectBoard moves or keeps issues in a column and sorts them inside that column
func MoveIssuesOnProjectBoard(ctx context.Context, board *Board, sortedIssueIDs map[int64]int64) error {
return db.WithTx(ctx, func(ctx context.Context) error {
sess := db.GetEngine(ctx)
issueIDs := util.ValuesOfMap(sortedIssueIDs)
count, err := sess.Table(new(ProjectIssue)).Where("project_id=?", board.ProjectID).In("issue_id", issueIDs).Count()
if err != nil {
return err
}
if int(count) != len(sortedIssueIDs) {
return fmt.Errorf("all issues have to be added to a project first")
}
for sorting, issueID := range sortedIssueIDs {
_, err = sess.Exec("UPDATE `project_issue` SET project_board_id=?, sorting=? WHERE issue_id=?", board.ID, sorting, issueID)
if err != nil {
return err
}
}
return nil
})
}
func (b *Board) moveIssuesToAnotherColumn(ctx context.Context, newColumn *Board) error {
if b.ProjectID != newColumn.ProjectID {
return fmt.Errorf("columns have to be in the same project")
}
if b.ID == newColumn.ID {
return nil
}
res := struct {
MaxSorting int64
IssueCount int64
}{}
if _, err := db.GetEngine(ctx).Select("max(sorting) as max_sorting, count(*) as issue_count").
Table("project_issue").
Where("project_id=?", newColumn.ProjectID).
And("project_board_id=?", newColumn.ID).
Get(&res); err != nil {
return err
}
issues, err := b.GetIssues(ctx)
if err != nil {
return err
}
if len(issues) == 0 {
return nil
}
nextSorting := util.Iif(res.IssueCount > 0, res.MaxSorting+1, 0)
return db.WithTx(ctx, func(ctx context.Context) error {
for i, issue := range issues {
issue.ProjectBoardID = newColumn.ID
issue.Sorting = nextSorting + int64(i)
if _, err := db.GetEngine(ctx).ID(issue.ID).Cols("project_board_id", "sorting").Update(issue); err != nil {
return err
}
}
return nil
})
}
// DeleteAllProjectIssueByIssueIDsAndProjectIDs delete all project's issues by issue's and project's ids
func DeleteAllProjectIssueByIssueIDsAndProjectIDs(ctx context.Context, issueIDs, projectIDs []int64) error {
_, err := db.GetEngine(ctx).In("project_id", projectIDs).In("issue_id", issueIDs).Delete(&ProjectIssue{})
return err
}