Files
Atay-Makhzan/models/issues/issue.go
T

687 lines
20 KiB
Go
Raw Normal View History

2014-03-20 16:04:56 -04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
2014-03-20 16:04:56 -04:00
package issues
2014-03-20 16:04:56 -04:00
2014-03-22 13:50:50 -04:00
import (
2021-09-23 16:45:36 +01:00
"context"
"fmt"
"regexp"
"code.gitea.io/gitea/models/db"
project_model "code.gitea.io/gitea/models/project"
2021-11-19 21:39:57 +08:00
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
2019-05-11 18:21:34 +08:00
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
2017-01-24 21:43:02 -05:00
"code.gitea.io/gitea/modules/util"
2017-12-25 18:25:16 -05:00
"xorm.io/builder"
2014-03-22 13:50:50 -04:00
)
// ErrIssueNotExist represents a "IssueNotExist" kind of error.
type ErrIssueNotExist struct {
ID int64
RepoID int64
Index int64
}
// IsErrIssueNotExist checks if an error is a ErrIssueNotExist.
func IsErrIssueNotExist(err error) bool {
_, ok := err.(ErrIssueNotExist)
return ok
}
func (err ErrIssueNotExist) Error() string {
return fmt.Sprintf("issue does not exist [id: %d, repo_id: %d, index: %d]", err.ID, err.RepoID, err.Index)
}
func (err ErrIssueNotExist) Unwrap() error {
return util.ErrNotExist
}
// ErrIssueIsClosed represents a "IssueIsClosed" kind of error.
type ErrIssueIsClosed struct {
ID int64
RepoID int64
Index int64
}
// IsErrIssueIsClosed checks if an error is a ErrIssueNotExist.
func IsErrIssueIsClosed(err error) bool {
_, ok := err.(ErrIssueIsClosed)
return ok
}
func (err ErrIssueIsClosed) Error() string {
return fmt.Sprintf("issue is closed [id: %d, repo_id: %d, index: %d]", err.ID, err.RepoID, err.Index)
}
// ErrNewIssueInsert is used when the INSERT statement in newIssue fails
type ErrNewIssueInsert struct {
OriginalError error
}
// IsErrNewIssueInsert checks if an error is a ErrNewIssueInsert.
func IsErrNewIssueInsert(err error) bool {
_, ok := err.(ErrNewIssueInsert)
return ok
}
func (err ErrNewIssueInsert) Error() string {
return err.OriginalError.Error()
}
// ErrIssueWasClosed is used when close a closed issue
type ErrIssueWasClosed struct {
ID int64
Index int64
}
// IsErrIssueWasClosed checks if an error is a ErrIssueWasClosed.
func IsErrIssueWasClosed(err error) bool {
_, ok := err.(ErrIssueWasClosed)
return ok
}
func (err ErrIssueWasClosed) Error() string {
return fmt.Sprintf("Issue [%d] %d was already closed", err.ID, err.Index)
}
2014-03-22 13:50:50 -04:00
// Issue represents an issue or pull request of repository.
2014-03-20 16:04:56 -04:00
type Issue struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"INDEX UNIQUE(repo_index)"`
Repo *repo_model.Repository `xorm:"-"`
Index int64 `xorm:"UNIQUE(repo_index)"` // Index in one repository.
PosterID int64 `xorm:"INDEX"`
Poster *user_model.User `xorm:"-"`
OriginalAuthor string
OriginalAuthorID int64 `xorm:"index"`
Title string `xorm:"name"`
Content string `xorm:"LONGTEXT"`
RenderedContent string `xorm:"-"`
Labels []*Label `xorm:"-"`
MilestoneID int64 `xorm:"INDEX"`
Milestone *Milestone `xorm:"-"`
Project *project_model.Project `xorm:"-"`
Priority int
AssigneeID int64 `xorm:"-"`
Assignee *user_model.User `xorm:"-"`
IsClosed bool `xorm:"INDEX"`
IsRead bool `xorm:"-"`
IsPull bool `xorm:"INDEX"` // Indicates whether is a pull request or not.
PullRequest *PullRequest `xorm:"-"`
NumComments int
Ref string
DeadlineUnix timeutil.TimeStamp `xorm:"INDEX"`
2018-05-01 21:05:28 +02:00
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
ClosedUnix timeutil.TimeStamp `xorm:"INDEX"`
2015-08-12 17:04:23 +08:00
Attachments []*repo_model.Attachment `xorm:"-"`
Comments []*Comment `xorm:"-"`
Reactions ReactionList `xorm:"-"`
TotalTrackedTime int64 `xorm:"-"`
Assignees []*user_model.User `xorm:"-"`
// IsLocked limits commenting abilities to users on an issue
// with write access
IsLocked bool `xorm:"NOT NULL DEFAULT false"`
2020-09-11 02:09:14 +08:00
// For view issue page.
2021-11-11 07:29:30 +01:00
ShowRole RoleDescriptor `xorm:"-"`
2015-08-12 17:04:23 +08:00
}
var (
issueTasksPat *regexp.Regexp
issueTasksDonePat *regexp.Regexp
)
const (
issueTasksRegexpStr = `(^\s*[-*]\s\[[\sxX]\]\s.)|(\n\s*[-*]\s\[[\sxX]\]\s.)`
issueTasksDoneRegexpStr = `(^\s*[-*]\s\[[xX]\]\s.)|(\n\s*[-*]\s\[[xX]\]\s.)`
)
// IssueIndex represents the issue index table
type IssueIndex db.ResourceIndex
func init() {
issueTasksPat = regexp.MustCompile(issueTasksRegexpStr)
issueTasksDonePat = regexp.MustCompile(issueTasksDoneRegexpStr)
db.RegisterModel(new(Issue))
db.RegisterModel(new(IssueIndex))
}
// LoadTotalTimes load total tracked time
func (issue *Issue) LoadTotalTimes(ctx context.Context) (err error) {
opts := FindTrackedTimesOptions{IssueID: issue.ID}
issue.TotalTrackedTime, err = opts.toSession(db.GetEngine(ctx)).SumInt(&TrackedTime{}, "time")
if err != nil {
return err
}
return nil
}
2018-05-01 21:05:28 +02:00
// IsOverdue checks if the issue is overdue
func (issue *Issue) IsOverdue() bool {
if issue.IsClosed {
return issue.ClosedUnix >= issue.DeadlineUnix
}
return timeutil.TimeStampNow() >= issue.DeadlineUnix
2018-05-01 21:05:28 +02:00
}
2018-12-13 23:55:43 +08:00
// LoadRepo loads issue's repository
2022-04-08 17:11:15 +08:00
func (issue *Issue) LoadRepo(ctx context.Context) (err error) {
if issue.Repo == nil && issue.RepoID != 0 {
issue.Repo, err = repo_model.GetRepositoryByID(ctx, issue.RepoID)
2016-03-13 23:20:22 -04:00
if err != nil {
return fmt.Errorf("getRepositoryByID [%d]: %w", issue.RepoID, err)
2016-03-13 23:20:22 -04:00
}
2016-08-26 13:40:53 -07:00
}
2016-12-17 19:49:17 +08:00
return nil
}
// IsTimetrackerEnabled returns true if the repo enables timetracking
2022-12-10 10:46:31 +08:00
func (issue *Issue) IsTimetrackerEnabled(ctx context.Context) bool {
2022-04-08 17:11:15 +08:00
if err := issue.LoadRepo(ctx); err != nil {
2019-04-02 08:48:31 +01:00
log.Error(fmt.Sprintf("loadRepo: %v", err))
return false
}
2022-12-10 10:46:31 +08:00
return issue.Repo.IsTimetrackerEnabled(ctx)
}
// GetPullRequest returns the issue pull request
func (issue *Issue) GetPullRequest() (pr *PullRequest, err error) {
if !issue.IsPull {
return nil, fmt.Errorf("Issue is not a pull request")
}
pr, err = GetPullRequestByIssueID(db.DefaultContext, issue.ID)
if err != nil {
return nil, err
}
pr.Issue = issue
return pr, err
}
2018-12-13 23:55:43 +08:00
// LoadPoster loads poster
func (issue *Issue) LoadPoster(ctx context.Context) (err error) {
if issue.Poster == nil && issue.PosterID != 0 {
2023-01-31 09:45:19 +08:00
issue.Poster, err = user_model.GetPossibleUserByID(ctx, issue.PosterID)
2016-03-13 23:20:22 -04:00
if err != nil {
issue.PosterID = -1
issue.Poster = user_model.NewGhostUser()
if !user_model.IsErrUserNotExist(err) {
return fmt.Errorf("getUserByID.(poster) [%d]: %w", issue.PosterID, err)
2016-03-13 23:20:22 -04:00
}
2016-11-09 13:07:01 +08:00
err = nil
2016-03-13 23:20:22 -04:00
return
}
2016-08-26 13:40:53 -07:00
}
return err
}
2016-03-13 23:20:22 -04:00
// LoadPullRequest loads pull request info
func (issue *Issue) LoadPullRequest(ctx context.Context) (err error) {
if issue.IsPull {
if issue.PullRequest == nil && issue.ID != 0 {
issue.PullRequest, err = GetPullRequestByIssueID(ctx, issue.ID)
if err != nil {
if IsErrPullRequestNotExist(err) {
return err
}
return fmt.Errorf("getPullRequestByIssueID [%d]: %w", issue.ID, err)
2017-07-26 00:16:45 -07:00
}
}
if issue.PullRequest != nil {
issue.PullRequest.Issue = issue
}
2017-07-26 00:16:45 -07:00
}
return nil
}
func (issue *Issue) loadComments(ctx context.Context) (err error) {
2023-04-20 14:39:44 +08:00
return issue.loadCommentsByType(ctx, CommentTypeUndefined)
2019-02-19 22:39:39 +08:00
}
// LoadDiscussComments loads discuss comments
func (issue *Issue) LoadDiscussComments(ctx context.Context) error {
return issue.loadCommentsByType(ctx, CommentTypeComment)
2019-02-19 22:39:39 +08:00
}
func (issue *Issue) loadCommentsByType(ctx context.Context, tp CommentType) (err error) {
2017-09-16 13:16:21 -07:00
if issue.Comments != nil {
return nil
}
issue.Comments, err = FindComments(ctx, &FindCommentsOptions{
2017-09-16 13:16:21 -07:00
IssueID: issue.ID,
2019-02-19 22:39:39 +08:00
Type: tp,
2017-09-16 13:16:21 -07:00
})
return err
}
func (issue *Issue) loadReactions(ctx context.Context) (err error) {
if issue.Reactions != nil {
return nil
}
reactions, _, err := FindReactions(ctx, FindReactionsOptions{
IssueID: issue.ID,
})
if err != nil {
return err
}
2022-04-08 17:11:15 +08:00
if err = issue.LoadRepo(ctx); err != nil {
return err
}
// Load reaction user data
if _, err := reactions.LoadUsers(ctx, issue.Repo); err != nil {
return err
}
// Cache comments to map
comments := make(map[int64]*Comment)
for _, comment := range issue.Comments {
comments[comment.ID] = comment
}
// Add reactions either to issue or comment
for _, react := range reactions {
if react.CommentID == 0 {
issue.Reactions = append(issue.Reactions, react)
} else if comment, ok := comments[react.CommentID]; ok {
comment.Reactions = append(comment.Reactions, react)
}
}
return nil
}
// LoadMilestone load milestone of this issue.
func (issue *Issue) LoadMilestone(ctx context.Context) (err error) {
if (issue.Milestone == nil || issue.Milestone.ID != issue.MilestoneID) && issue.MilestoneID > 0 {
issue.Milestone, err = GetMilestoneByRepoID(ctx, issue.RepoID, issue.MilestoneID)
if err != nil && !IsErrMilestoneNotExist(err) {
return fmt.Errorf("getMilestoneByRepoID [repo_id: %d, milestone_id: %d]: %w", issue.RepoID, issue.MilestoneID, err)
}
}
return nil
}
// LoadAttributes loads the attribute of this issue.
func (issue *Issue) LoadAttributes(ctx context.Context) (err error) {
2022-04-08 17:11:15 +08:00
if err = issue.LoadRepo(ctx); err != nil {
return
}
if err = issue.LoadPoster(ctx); err != nil {
return
}
2022-04-28 13:48:48 +02:00
if err = issue.LoadLabels(ctx); err != nil {
return
2016-08-26 13:40:53 -07:00
}
if err = issue.LoadMilestone(ctx); err != nil {
return
2015-08-05 20:23:08 +08:00
}
2023-02-21 04:21:56 +09:00
if err = issue.LoadProject(ctx); err != nil {
2020-08-17 04:07:38 +01:00
return
}
if err = issue.LoadAssignees(ctx); err != nil {
2017-06-23 15:43:37 +02:00
return
}
if err = issue.LoadPullRequest(ctx); err != nil && !IsErrPullRequestNotExist(err) {
// It is possible pull request is not yet created.
2017-07-26 00:16:45 -07:00
return err
}
2016-08-26 13:40:53 -07:00
if issue.Attachments == nil {
issue.Attachments, err = repo_model.GetAttachmentsByIssueID(ctx, issue.ID)
2016-08-26 13:40:53 -07:00
if err != nil {
return fmt.Errorf("getAttachmentsByIssueID [%d]: %w", issue.ID, err)
2016-08-26 13:40:53 -07:00
}
}
if err = issue.loadComments(ctx); err != nil {
return err
2016-08-26 13:40:53 -07:00
}
if err = CommentList(issue.Comments).loadAttributes(ctx); err != nil {
return err
}
2022-12-10 10:46:31 +08:00
if issue.IsTimetrackerEnabled(ctx) {
if err = issue.LoadTotalTimes(ctx); err != nil {
return err
}
}
2016-08-26 13:40:53 -07:00
return issue.loadReactions(ctx)
}
// GetIsRead load the `IsRead` field of the issue
func (issue *Issue) GetIsRead(userID int64) error {
issueUser := &IssueUser{IssueID: issue.ID, UID: userID}
2021-09-23 16:45:36 +01:00
if has, err := db.GetEngine(db.DefaultContext).Get(issueUser); err != nil {
return err
} else if !has {
2017-02-08 22:47:24 -05:00
issue.IsRead = false
return nil
}
issue.IsRead = issueUser.IsRead
return nil
}
2017-03-03 22:35:42 +08:00
// APIURL returns the absolute APIURL to this issue.
func (issue *Issue) APIURL() string {
if issue.Repo == nil {
2022-04-08 17:11:15 +08:00
err := issue.LoadRepo(db.DefaultContext)
if err != nil {
log.Error("Issue[%d].APIURL(): %v", issue.ID, err)
return ""
}
}
return fmt.Sprintf("%s/issues/%d", issue.Repo.APIURL(), issue.Index)
2017-03-03 22:35:42 +08:00
}
2016-11-24 09:41:11 +01:00
// HTMLURL returns the absolute URL to this issue.
2016-08-16 10:19:09 -07:00
func (issue *Issue) HTMLURL() string {
var path string
if issue.IsPull {
path = "pulls"
} else {
path = "issues"
}
return fmt.Sprintf("%s/%s/%d", issue.Repo.HTMLURL(), path, issue.Index)
}
// Link returns the issue's relative URL.
2021-11-16 18:18:25 +00:00
func (issue *Issue) Link() string {
var path string
if issue.IsPull {
path = "pulls"
} else {
path = "issues"
}
return fmt.Sprintf("%s/%s/%d", issue.Repo.Link(), path, issue.Index)
}
2016-12-02 12:10:39 +01:00
// DiffURL returns the absolute URL to this diff
func (issue *Issue) DiffURL() string {
if issue.IsPull {
return fmt.Sprintf("%s/pulls/%d.diff", issue.Repo.HTMLURL(), issue.Index)
}
return ""
}
// PatchURL returns the absolute URL to this patch
func (issue *Issue) PatchURL() string {
if issue.IsPull {
return fmt.Sprintf("%s/pulls/%d.patch", issue.Repo.HTMLURL(), issue.Index)
}
return ""
}
2016-03-13 23:20:22 -04:00
// State returns string representation of issue status.
2016-11-22 12:24:39 +01:00
func (issue *Issue) State() api.StateType {
if issue.IsClosed {
2016-11-29 09:25:47 +01:00
return api.StateClosed
2016-03-13 23:20:22 -04:00
}
2016-11-29 09:25:47 +01:00
return api.StateOpen
}
// HashTag returns unique hash tag for issue.
2016-11-22 12:24:39 +01:00
func (issue *Issue) HashTag() string {
2020-12-25 09:59:32 +00:00
return fmt.Sprintf("issue-%d", issue.ID)
2016-03-13 23:20:22 -04:00
}
2015-08-13 16:07:11 +08:00
// IsPoster returns true if given user by ID is the poster.
2016-11-22 12:24:39 +01:00
func (issue *Issue) IsPoster(uid int64) bool {
return issue.OriginalAuthorID == 0 && issue.PosterID == uid
2015-08-13 16:07:11 +08:00
}
// GetTasks returns the amount of tasks in the issues content
func (issue *Issue) GetTasks() int {
return len(issueTasksPat.FindAllStringIndex(issue.Content, -1))
}
// GetTasksDone returns the amount of completed tasks in the issues content
func (issue *Issue) GetTasksDone() int {
return len(issueTasksDonePat.FindAllStringIndex(issue.Content, -1))
}
// GetLastEventTimestamp returns the last user visible event timestamp, either the creation of this issue or the close.
func (issue *Issue) GetLastEventTimestamp() timeutil.TimeStamp {
if issue.IsClosed {
return issue.ClosedUnix
2015-09-02 16:18:09 -04:00
}
return issue.CreatedUnix
}
2015-09-02 16:18:09 -04:00
// GetLastEventLabel returns the localization label for the current issue.
func (issue *Issue) GetLastEventLabel() string {
if issue.IsClosed {
if issue.IsPull && issue.PullRequest.HasMerged {
return "repo.pulls.merged_by"
}
return "repo.issues.closed_by"
2015-08-25 22:58:34 +08:00
}
return "repo.issues.opened_by"
2015-08-25 22:58:34 +08:00
}
// GetLastComment return last comment for the current issue.
func (issue *Issue) GetLastComment() (*Comment, error) {
var c Comment
exist, err := db.GetEngine(db.DefaultContext).Where("type = ?", CommentTypeComment).
And("issue_id = ?", issue.ID).Desc("created_unix").Get(&c)
if err != nil {
return nil, err
}
if !exist {
return nil, nil
}
return &c, nil
}
// GetLastEventLabelFake returns the localization label for the current issue without providing a link in the username.
func (issue *Issue) GetLastEventLabelFake() string {
if issue.IsClosed {
if issue.IsPull && issue.PullRequest.HasMerged {
return "repo.pulls.merged_by_fake"
}
return "repo.issues.closed_by_fake"
}
return "repo.issues.opened_by_fake"
}
// GetIssueByIndex returns raw issue without loading attributes by index in a repository.
func GetIssueByIndex(repoID, index int64) (*Issue, error) {
if index < 1 {
return nil, ErrIssueNotExist{}
}
issue := &Issue{
RepoID: repoID,
Index: index,
}
has, err := db.GetEngine(db.DefaultContext).Get(issue)
if err != nil {
return nil, err
} else if !has {
return nil, ErrIssueNotExist{0, repoID, index}
2020-01-02 08:54:22 +01:00
}
return issue, nil
}
2020-01-02 08:54:22 +01:00
// GetIssueWithAttrsByIndex returns issue by index in a repository.
func GetIssueWithAttrsByIndex(repoID, index int64) (*Issue, error) {
issue, err := GetIssueByIndex(repoID, index)
if err != nil {
return nil, err
}
return issue, issue.LoadAttributes(db.DefaultContext)
}
// GetIssueByID returns an issue by given ID.
func GetIssueByID(ctx context.Context, id int64) (*Issue, error) {
issue := new(Issue)
has, err := db.GetEngine(ctx).ID(id).Get(issue)
if err != nil {
return nil, err
} else if !has {
return nil, ErrIssueNotExist{id, 0, 0}
}
return issue, nil
}
2018-05-01 21:05:28 +02:00
// GetIssueWithAttrsByID returns an issue with attributes by given ID.
func GetIssueWithAttrsByID(id int64) (*Issue, error) {
issue, err := GetIssueByID(db.DefaultContext, id)
2021-11-19 21:39:57 +08:00
if err != nil {
return nil, err
2018-05-01 21:05:28 +02:00
}
return issue, issue.LoadAttributes(db.DefaultContext)
}
2018-05-01 21:05:28 +02:00
// GetIssuesByIDs return issues with the given IDs.
func GetIssuesByIDs(ctx context.Context, issueIDs []int64) (IssueList, error) {
issues := make([]*Issue, 0, 10)
return issues, db.GetEngine(ctx).In("id", issueIDs).Find(&issues)
}
2018-05-01 21:05:28 +02:00
// GetIssueIDsByRepoID returns all issue ids by repo id
func GetIssueIDsByRepoID(ctx context.Context, repoID int64) ([]int64, error) {
ids := make([]int64, 0, 10)
err := db.GetEngine(ctx).Table("issue").Cols("id").Where("repo_id = ?", repoID).Find(&ids)
return ids, err
}
2018-05-01 21:05:28 +02:00
// GetParticipantsIDsByIssueID returns the IDs of all users who participated in comments of an issue,
// but skips joining with `user` for performance reasons.
// User permissions must be verified elsewhere if required.
func GetParticipantsIDsByIssueID(ctx context.Context, issueID int64) ([]int64, error) {
userIDs := make([]int64, 0, 5)
return userIDs, db.GetEngine(ctx).
Table("comment").
Cols("poster_id").
Where("issue_id = ?", issueID).
And("type in (?,?,?)", CommentTypeComment, CommentTypeCode, CommentTypeReview).
Distinct("poster_id").
Find(&userIDs)
2018-05-01 21:05:28 +02:00
}
// IsUserParticipantsOfIssue return true if user is participants of an issue
func IsUserParticipantsOfIssue(user *user_model.User, issue *Issue) bool {
userIDs, err := issue.GetParticipantIDsByIssue(db.DefaultContext)
if err != nil {
log.Error(err.Error())
return false
2022-03-01 01:20:15 +01:00
}
return util.SliceContains(userIDs, user.ID)
2022-03-01 01:20:15 +01:00
}
// DependencyInfo represents high level information about an issue which is a dependency of another issue.
type DependencyInfo struct {
Issue `xorm:"extends"`
repo_model.Repository `xorm:"extends"`
}
// GetParticipantIDsByIssue returns all userIDs who are participated in comments of an issue and issue author
func (issue *Issue) GetParticipantIDsByIssue(ctx context.Context) ([]int64, error) {
2020-02-28 09:16:41 +01:00
if issue == nil {
return nil, nil
}
userIDs := make([]int64, 0, 5)
if err := db.GetEngine(ctx).Table("comment").Cols("poster_id").
2020-02-28 09:16:41 +01:00
Where("`comment`.issue_id = ?", issue.ID).
And("`comment`.type in (?,?,?)", CommentTypeComment, CommentTypeCode, CommentTypeReview).
And("`user`.is_active = ?", true).
And("`user`.prohibit_login = ?", false).
Join("INNER", "`user`", "`user`.id = `comment`.poster_id").
Distinct("poster_id").
Find(&userIDs); err != nil {
return nil, fmt.Errorf("get poster IDs: %w", err)
2020-02-28 09:16:41 +01:00
}
2023-01-11 13:31:16 +08:00
if !util.SliceContains(userIDs, issue.PosterID) {
2020-02-28 09:16:41 +01:00
return append(userIDs, issue.PosterID), nil
}
return userIDs, nil
}
// BlockedByDependencies finds all Dependencies an issue is blocked by
func (issue *Issue) BlockedByDependencies(ctx context.Context, opts db.ListOptions) (issueDeps []*DependencyInfo, err error) {
sess := db.GetEngine(ctx).
Table("issue").
Join("INNER", "repository", "repository.id = issue.repo_id").
Join("INNER", "issue_dependency", "issue_dependency.dependency_id = issue.id").
Where("issue_id = ?", issue.ID).
// sort by repo id then created date, with the issues of the same repo at the beginning of the list
OrderBy("CASE WHEN issue.repo_id = ? THEN 0 ELSE issue.repo_id END, issue.created_unix DESC", issue.RepoID)
if opts.Page != 0 {
sess = db.SetSessionPagination(sess, &opts)
}
err = sess.Find(&issueDeps)
for _, depInfo := range issueDeps {
depInfo.Issue.Repo = &depInfo.Repository
}
return issueDeps, err
}
// BlockingDependencies returns all blocking dependencies, aka all other issues a given issue blocks
func (issue *Issue) BlockingDependencies(ctx context.Context) (issueDeps []*DependencyInfo, err error) {
err = db.GetEngine(ctx).
Table("issue").
Join("INNER", "repository", "repository.id = issue.repo_id").
Join("INNER", "issue_dependency", "issue_dependency.issue_id = issue.id").
Where("dependency_id = ?", issue.ID).
// sort by repo id then created date, with the issues of the same repo at the beginning of the list
2022-06-05 03:18:50 +08:00
OrderBy("CASE WHEN issue.repo_id = ? THEN 0 ELSE issue.repo_id END, issue.created_unix DESC", issue.RepoID).
Find(&issueDeps)
for _, depInfo := range issueDeps {
depInfo.Issue.Repo = &depInfo.Repository
}
return issueDeps, err
}
func migratedIssueCond(tp api.GitServiceType) builder.Cond {
return builder.In("issue_id",
builder.Select("issue.id").
From("issue").
InnerJoin("repository", "issue.repo_id = repository.id").
Where(builder.Eq{
"repository.original_service_type": tp,
}),
)
}
// RemapExternalUser ExternalUserRemappable interface
func (issue *Issue) RemapExternalUser(externalName string, externalID, userID int64) error {
issue.OriginalAuthor = externalName
issue.OriginalAuthorID = externalID
issue.PosterID = userID
return nil
}
// GetUserID ExternalUserRemappable interface
func (issue *Issue) GetUserID() int64 { return issue.PosterID }
// GetExternalName ExternalUserRemappable interface
func (issue *Issue) GetExternalName() string { return issue.OriginalAuthor }
// GetExternalID ExternalUserRemappable interface
func (issue *Issue) GetExternalID() int64 { return issue.OriginalAuthorID }
// HasOriginalAuthor returns if an issue was migrated and has an original author.
func (issue *Issue) HasOriginalAuthor() bool {
return issue.OriginalAuthor != "" && issue.OriginalAuthorID != 0
}