code.gitea.io/gitea@v1.22.3/models/project/issue.go (about)

     1  // Copyright 2020 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package project
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  
    10  	"code.gitea.io/gitea/models/db"
    11  	"code.gitea.io/gitea/modules/log"
    12  	"code.gitea.io/gitea/modules/util"
    13  )
    14  
    15  // ProjectIssue saves relation from issue to a project
    16  type ProjectIssue struct { //revive:disable-line:exported
    17  	ID        int64 `xorm:"pk autoincr"`
    18  	IssueID   int64 `xorm:"INDEX"`
    19  	ProjectID int64 `xorm:"INDEX"`
    20  
    21  	// 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.
    22  	ProjectBoardID int64 `xorm:"INDEX"`
    23  
    24  	// the sorting order on the board
    25  	Sorting int64 `xorm:"NOT NULL DEFAULT 0"`
    26  }
    27  
    28  func init() {
    29  	db.RegisterModel(new(ProjectIssue))
    30  }
    31  
    32  func deleteProjectIssuesByProjectID(ctx context.Context, projectID int64) error {
    33  	_, err := db.GetEngine(ctx).Where("project_id=?", projectID).Delete(&ProjectIssue{})
    34  	return err
    35  }
    36  
    37  // NumIssues return counter of all issues assigned to a project
    38  func (p *Project) NumIssues(ctx context.Context) int {
    39  	c, err := db.GetEngine(ctx).Table("project_issue").
    40  		Where("project_id=?", p.ID).
    41  		GroupBy("issue_id").
    42  		Cols("issue_id").
    43  		Count()
    44  	if err != nil {
    45  		log.Error("NumIssues: %v", err)
    46  		return 0
    47  	}
    48  	return int(c)
    49  }
    50  
    51  // NumClosedIssues return counter of closed issues assigned to a project
    52  func (p *Project) NumClosedIssues(ctx context.Context) int {
    53  	c, err := db.GetEngine(ctx).Table("project_issue").
    54  		Join("INNER", "issue", "project_issue.issue_id=issue.id").
    55  		Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, true).
    56  		Cols("issue_id").
    57  		Count()
    58  	if err != nil {
    59  		log.Error("NumClosedIssues: %v", err)
    60  		return 0
    61  	}
    62  	return int(c)
    63  }
    64  
    65  // NumOpenIssues return counter of open issues assigned to a project
    66  func (p *Project) NumOpenIssues(ctx context.Context) int {
    67  	c, err := db.GetEngine(ctx).Table("project_issue").
    68  		Join("INNER", "issue", "project_issue.issue_id=issue.id").
    69  		Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, false).
    70  		Cols("issue_id").
    71  		Count()
    72  	if err != nil {
    73  		log.Error("NumOpenIssues: %v", err)
    74  		return 0
    75  	}
    76  	return int(c)
    77  }
    78  
    79  // MoveIssuesOnProjectBoard moves or keeps issues in a column and sorts them inside that column
    80  func MoveIssuesOnProjectBoard(ctx context.Context, board *Board, sortedIssueIDs map[int64]int64) error {
    81  	return db.WithTx(ctx, func(ctx context.Context) error {
    82  		sess := db.GetEngine(ctx)
    83  		issueIDs := util.ValuesOfMap(sortedIssueIDs)
    84  
    85  		count, err := sess.Table(new(ProjectIssue)).Where("project_id=?", board.ProjectID).In("issue_id", issueIDs).Count()
    86  		if err != nil {
    87  			return err
    88  		}
    89  		if int(count) != len(sortedIssueIDs) {
    90  			return fmt.Errorf("all issues have to be added to a project first")
    91  		}
    92  
    93  		for sorting, issueID := range sortedIssueIDs {
    94  			_, err = sess.Exec("UPDATE `project_issue` SET project_board_id=?, sorting=? WHERE issue_id=?", board.ID, sorting, issueID)
    95  			if err != nil {
    96  				return err
    97  			}
    98  		}
    99  		return nil
   100  	})
   101  }
   102  
   103  func (b *Board) moveIssuesToAnotherColumn(ctx context.Context, newColumn *Board) error {
   104  	if b.ProjectID != newColumn.ProjectID {
   105  		return fmt.Errorf("columns have to be in the same project")
   106  	}
   107  
   108  	if b.ID == newColumn.ID {
   109  		return nil
   110  	}
   111  
   112  	res := struct {
   113  		MaxSorting int64
   114  		IssueCount int64
   115  	}{}
   116  	if _, err := db.GetEngine(ctx).Select("max(sorting) as max_sorting, count(*) as issue_count").
   117  		Table("project_issue").
   118  		Where("project_id=?", newColumn.ProjectID).
   119  		And("project_board_id=?", newColumn.ID).
   120  		Get(&res); err != nil {
   121  		return err
   122  	}
   123  
   124  	issues, err := b.GetIssues(ctx)
   125  	if err != nil {
   126  		return err
   127  	}
   128  	if len(issues) == 0 {
   129  		return nil
   130  	}
   131  
   132  	nextSorting := util.Iif(res.IssueCount > 0, res.MaxSorting+1, 0)
   133  	return db.WithTx(ctx, func(ctx context.Context) error {
   134  		for i, issue := range issues {
   135  			issue.ProjectBoardID = newColumn.ID
   136  			issue.Sorting = nextSorting + int64(i)
   137  			if _, err := db.GetEngine(ctx).ID(issue.ID).Cols("project_board_id", "sorting").Update(issue); err != nil {
   138  				return err
   139  			}
   140  		}
   141  		return nil
   142  	})
   143  }
   144  
   145  // DeleteAllProjectIssueByIssueIDsAndProjectIDs delete all project's issues by issue's and project's ids
   146  func DeleteAllProjectIssueByIssueIDsAndProjectIDs(ctx context.Context, issueIDs, projectIDs []int64) error {
   147  	_, err := db.GetEngine(ctx).In("project_id", projectIDs).In("issue_id", issueIDs).Delete(&ProjectIssue{})
   148  	return err
   149  }