-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add the ability to pin Issues #24406
Changes from 42 commits
e03f4cf
961a95f
4d1d1d8
7aea9d6
22c0687
31804ab
65dd702
dfacb3f
8492384
8b84877
8c94178
023820f
a31a96a
c0ffcdb
f74d573
3ee07af
034c8a6
2b4eccb
0d1ccaa
abbe6e1
1704b6f
4735026
8f25b3a
7880472
9afc05e
688ace1
61a2dc1
59a5691
8010a7a
706c6cc
3dc4746
9d38fad
03761ea
ea9df52
20356cf
cac50e0
630a9f1
d5edde7
cd194a1
cadaa48
08c59de
ab35b5e
3f32853
1d098d5
d3e365a
bce5ff1
4aa9488
8643224
76ea8d9
caeae1b
e3c67cf
a4114f0
e2b81e0
36a5478
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,7 @@ import ( | |
repo_model "code.gitea.io/gitea/models/repo" | ||
user_model "code.gitea.io/gitea/models/user" | ||
"code.gitea.io/gitea/modules/log" | ||
"code.gitea.io/gitea/modules/setting" | ||
api "code.gitea.io/gitea/modules/structs" | ||
"code.gitea.io/gitea/modules/timeutil" | ||
"code.gitea.io/gitea/modules/util" | ||
|
@@ -116,6 +117,7 @@ type Issue struct { | |
PullRequest *PullRequest `xorm:"-"` | ||
NumComments int | ||
Ref string | ||
PinOrder int `xorm:"DEFAULT 0"` | ||
|
||
DeadlineUnix timeutil.TimeStamp `xorm:"INDEX"` | ||
|
||
|
@@ -684,3 +686,182 @@ func (issue *Issue) GetExternalID() int64 { return issue.OriginalAuthorID } | |
func (issue *Issue) HasOriginalAuthor() bool { | ||
return issue.OriginalAuthor != "" && issue.OriginalAuthorID != 0 | ||
} | ||
|
||
// IsPinned returns if a Issue is pinned | ||
func (issue *Issue) IsPinned() bool { | ||
return issue.PinOrder != 0 | ||
} | ||
|
||
// Pin pins a Issue | ||
func (issue *Issue) Pin(ctx context.Context, user *user_model.User) error { | ||
// If the Issue is already pinned, we don't need to pin it twice | ||
if issue.IsPinned() { | ||
return nil | ||
} | ||
|
||
var maxPin int | ||
_, err := db.GetEngine(ctx).SQL("SELECT MAX(pin_order) FROM issue WHERE repo_id = ? AND is_pull = ?", issue.RepoID, issue.IsPull).Get(&maxPin) | ||
JakobDev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
// Check if the maximum allowed Pins reached | ||
if maxPin >= setting.Repository.Issue.MaxPinned { | ||
return fmt.Errorf("You have reached the max number of pinned Issues") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
|
||
_, err = db.GetEngine(ctx).Table("issue"). | ||
Where("id = ?", issue.ID). | ||
Update(map[string]interface{}{ | ||
"pin_order": maxPin + 1, | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Add the pin event to the history | ||
opts := &CreateCommentOptions{ | ||
Type: CommentTypePin, | ||
Doer: user, | ||
Repo: issue.Repo, | ||
Issue: issue, | ||
} | ||
if _, err = CreateComment(ctx, opts); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// UnpinIssue unpins a Issue | ||
func (issue *Issue) Unpin(ctx context.Context, user *user_model.User) error { | ||
// If the Issue is not pinned, we don't need to unpin it | ||
if !issue.IsPinned() { | ||
return nil | ||
} | ||
|
||
// This sets the Pin for all Issues that come after the unpined Issue to the correct value | ||
_, err := db.GetEngine(ctx).Exec("UPDATE issue SET pin_order = pin_order - 1 WHERE repo_id = ? AND is_pull = ? AND pin_order > ?", issue.RepoID, issue.IsPull, issue.PinOrder) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
_, err = db.GetEngine(ctx).Table("issue"). | ||
Where("id = ?", issue.ID). | ||
Update(map[string]interface{}{ | ||
"pin_order": 0, | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Add the unpin event to the history | ||
opts := &CreateCommentOptions{ | ||
Type: CommentTypeUnpin, | ||
Doer: user, | ||
Repo: issue.Repo, | ||
Issue: issue, | ||
} | ||
if _, err = CreateComment(ctx, opts); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// PinOrUnpin pins or unpins a Issue | ||
func (issue *Issue) PinOrUnpin(ctx context.Context, user *user_model.User) error { | ||
if !issue.IsPinned() { | ||
return issue.Pin(ctx, user) | ||
} | ||
|
||
return issue.Unpin(ctx, user) | ||
} | ||
|
||
// MovePin moves a Pinned Issue to a new Position | ||
func (issue *Issue) MovePin(ctx context.Context, newPosition int) error { | ||
// If the Issue is not pinned, we can't move them | ||
if !issue.IsPinned() { | ||
return nil | ||
} | ||
|
||
if newPosition < 1 { | ||
return fmt.Errorf("The Position can't be lower than 1") | ||
} | ||
|
||
dbctx, committer, err := db.TxContext(ctx) | ||
if err != nil { | ||
return err | ||
} | ||
defer committer.Close() | ||
|
||
var maxPin int | ||
_, err = db.GetEngine(dbctx).SQL("SELECT MAX(pin_order) FROM issue WHERE repo_id = ? AND is_pull = ?", issue.RepoID, issue.IsPull).Get(&maxPin) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// If the new Position bigger than the current Maximum, set it to the Maximum | ||
if newPosition > maxPin+1 { | ||
newPosition = maxPin + 1 | ||
} | ||
|
||
// TODO: Run the following commands in a Transaction and Rollback, if one fails | ||
|
||
// Lower the Position of all Pinned Issue that came after the current Position | ||
_, err = db.GetEngine(dbctx).Exec("UPDATE issue SET pin_order = pin_order - 1 WHERE repo_id = ? AND is_pull = ? AND pin_order > ?", issue.RepoID, issue.IsPull, issue.PinOrder) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Higher the Position of all Pinned Issues that comes after the new Position | ||
_, err = db.GetEngine(dbctx).Exec("UPDATE issue SET pin_order = pin_order + 1 WHERE repo_id = ? AND is_pull = ? AND pin_order >= ?", issue.RepoID, issue.IsPull, newPosition) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
_, err = db.GetEngine(dbctx).Table("issue"). | ||
Where("id = ?", issue.ID). | ||
Update(map[string]interface{}{ | ||
"pin_order": newPosition, | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return committer.Commit() | ||
} | ||
|
||
// GetPinnedIssues returns the pinned Issues for the given Repo and type | ||
func GetPinnedIssues(ctx context.Context, repoID int64, isPull bool) ([]*Issue, error) { | ||
issues := make([]*Issue, 0) | ||
|
||
err := db.GetEngine(ctx). | ||
Table("issue"). | ||
Where("repo_id = ?", repoID). | ||
And("is_pull = ?", isPull). | ||
And("pin_order > 0"). | ||
OrderBy("pin_order"). | ||
Find(&issues) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
err = IssueList(issues).LoadAttributes() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return issues, nil | ||
} | ||
|
||
// IsNewPinnedAllowed returns if a new Issue or Pull request can be pinned | ||
func IsNewPinAllowed(ctx context.Context, repoID int64, isPull bool) (bool, error) { | ||
var maxPin int | ||
_, err := db.GetEngine(ctx).SQL("SELECT MAX(pin_order) FROM issue WHERE repo_id = ? AND is_pull = ?", repoID, isPull).Get(&maxPin) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use |
||
if err != nil { | ||
return false, err | ||
} | ||
|
||
return maxPin < setting.Repository.Issue.MaxPinned, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// Copyright 2023 The Gitea Authors. All rights reserved. | ||
// SPDX-License-Identifier: MIT | ||
|
||
package v1_20 //nolint | ||
|
||
import ( | ||
"xorm.io/xorm" | ||
) | ||
|
||
func AddPinOrderToIssue(x *xorm.Engine) error { | ||
type Issue struct { | ||
PinOrder int `xorm:"DEFAULT 0"` | ||
} | ||
|
||
return x.Sync(new(Issue)) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,6 +48,8 @@ type PullRequest struct { | |
Updated *time.Time `json:"updated_at"` | ||
// swagger:strfmt date-time | ||
Closed *time.Time `json:"closed_at"` | ||
|
||
PinOrder int `json:"pin_order"` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wait, pinned PRs? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, you can pin Pull Requests |
||
} | ||
|
||
// PRBranchInfo information about a branch | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
per repo!