mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-03 08:02:36 +09:00 
			
		
		
		
	Backport #30696 by @lunny # The problem The previous implementation will start multiple POST requests from the frontend when moving a column and another bug is moving the default column will never be remembered in fact. # What's changed - [x] This PR will allow the default column to move to a non-first position - [x] And it also uses one request instead of multiple requests when moving the columns - [x] Use a star instead of a pin as the icon for setting the default column action - [x] Inserted new column will be append to the end - [x] Fix #30701 the newly added issue will be append to the end of the default column - [x] Fix when deleting a column, all issues in it will be displayed from UI but database records exist. - [x] Add a limitation for columns in a project to 20. So the sorting will not be overflow because it's int8. Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
		
			
				
	
	
		
			163 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			163 lines
		
	
	
		
			4.4 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
 | 
						|
}
 | 
						|
 | 
						|
// ProjectBoardID return project board id if issue was assigned to one
 | 
						|
func (issue *Issue) ProjectBoardID(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.ProjectBoardID
 | 
						|
}
 | 
						|
 | 
						|
// LoadIssuesFromBoard load issues assigned to this board
 | 
						|
func LoadIssuesFromBoard(ctx context.Context, b *project_model.Board) (IssueList, error) {
 | 
						|
	issueList, err := Issues(ctx, &IssuesOptions{
 | 
						|
		ProjectBoardID: b.ID,
 | 
						|
		ProjectID:      b.ProjectID,
 | 
						|
		SortType:       "project-column-sorting",
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	if b.Default {
 | 
						|
		issues, err := Issues(ctx, &IssuesOptions{
 | 
						|
			ProjectBoardID: db.NoConditionID,
 | 
						|
			ProjectID:      b.ProjectID,
 | 
						|
			SortType:       "project-column-sorting",
 | 
						|
		})
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		issueList = append(issueList, issues...)
 | 
						|
	}
 | 
						|
 | 
						|
	if err := issueList.LoadComments(ctx); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return issueList, nil
 | 
						|
}
 | 
						|
 | 
						|
// LoadIssuesFromBoardList load issues assigned to the boards
 | 
						|
func LoadIssuesFromBoardList(ctx context.Context, bs project_model.BoardList) (map[int64]IssueList, error) {
 | 
						|
	issuesMap := make(map[int64]IssueList, len(bs))
 | 
						|
	for i := range bs {
 | 
						|
		il, err := LoadIssuesFromBoard(ctx, bs[i])
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		issuesMap[bs[i].ID] = il
 | 
						|
	}
 | 
						|
	return issuesMap, 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.GetDefaultBoard(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
 | 
						|
		}
 | 
						|
 | 
						|
		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=?", newProjectID).
 | 
						|
			And("project_board_id=?", newColumnID).
 | 
						|
			Get(&res); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		newSorting := util.Iif(res.IssueCount > 0, res.MaxSorting+1, 0)
 | 
						|
		return db.Insert(ctx, &project_model.ProjectIssue{
 | 
						|
			IssueID:        issue.ID,
 | 
						|
			ProjectID:      newProjectID,
 | 
						|
			ProjectBoardID: newColumnID,
 | 
						|
			Sorting:        newSorting,
 | 
						|
		})
 | 
						|
	})
 | 
						|
}
 |