-
-
Notifications
You must be signed in to change notification settings - Fork 5.8k
Implement "conversation lock" for issue comments #5073
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
Changes from all commits
09d170a
59b2c1f
8b6afb3
2496762
af23f1c
815e6ce
e0698fd
15b8ed5
fbaa69e
08f7d16
4a09287
f4578b8
41cb4ef
808aebc
c596b9d
d7e6c0a
f517a61
f4c5d3d
40ec39f
6930064
53becb7
7abf90f
6b633c1
52da337
b8b2103
642d320
ca444dd
2b44d24
e9b48f7
cdda82f
d9ad8ec
24f68fe
d902bd4
59ed8be
0268e90
b23ce7e
b25d7e7
e8e13fa
d3e1556
e6779a6
f003778
3272f1e
fec9e37
2e3502f
6d181e7
865c618
67d6dc7
25efb71
29cf5f0
a2d0191
e85b63b
8315601
7cd2563
e308329
09d5eb7
3f7b816
aefe3bc
10dcb9c
5e979e5
48bff7d
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 |
---|---|---|
@@ -0,0 +1,51 @@ | ||
// Copyright 2019 The Gitea Authors. All rights reserved. | ||
// Use of this source code is governed by a MIT-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package models | ||
|
||
// IssueLockOptions defines options for locking and/or unlocking an issue/PR | ||
type IssueLockOptions struct { | ||
Doer *User | ||
Issue *Issue | ||
Reason string | ||
} | ||
|
||
// LockIssue locks an issue. This would limit commenting abilities to | ||
// users with write access to the repo | ||
func LockIssue(opts *IssueLockOptions) error { | ||
return updateIssueLock(opts, true) | ||
} | ||
|
||
// UnlockIssue unlocks a previously locked issue. | ||
func UnlockIssue(opts *IssueLockOptions) error { | ||
return updateIssueLock(opts, false) | ||
} | ||
|
||
func updateIssueLock(opts *IssueLockOptions, lock bool) error { | ||
if opts.Issue.IsLocked == lock { | ||
return nil | ||
} | ||
|
||
opts.Issue.IsLocked = lock | ||
|
||
var commentType CommentType | ||
if opts.Issue.IsLocked { | ||
commentType = CommentTypeLock | ||
} else { | ||
commentType = CommentTypeUnlock | ||
} | ||
|
||
if err := UpdateIssueCols(opts.Issue, "is_locked"); err != nil { | ||
return err | ||
} | ||
|
||
_, err := CreateComment(&CreateCommentOptions{ | ||
Doer: opts.Doer, | ||
Issue: opts.Issue, | ||
Repo: opts.Issue.Repo, | ||
Type: commentType, | ||
Content: opts.Reason, | ||
}) | ||
return err | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// Copyright 2019 The Gitea Authors. All rights reserved. | ||
// Use of this source code is governed by a MIT-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package migrations | ||
|
||
import "github.com/go-xorm/xorm" | ||
|
||
func addIsLockedToIssues(x *xorm.Engine) error { | ||
// Issue see models/issue.go | ||
type Issue struct { | ||
ID int64 `xorm:"pk autoincr"` | ||
IsLocked bool `xorm:"NOT NULL DEFAULT false"` | ||
} | ||
|
||
return x.Sync2(new(Issue)) | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ import ( | |
"strings" | ||
|
||
"code.gitea.io/gitea/models" | ||
"code.gitea.io/gitea/modules/setting" | ||
"code.gitea.io/gitea/routers/utils" | ||
|
||
"github.com/Unknwon/com" | ||
|
@@ -308,6 +309,32 @@ func (f *ReactionForm) Validate(ctx *macaron.Context, errs binding.Errors) bindi | |
return validate(errs, ctx.Data, f, ctx.Locale) | ||
} | ||
|
||
// IssueLockForm form for locking an issue | ||
type IssueLockForm struct { | ||
Reason string `binding:"Required"` | ||
} | ||
|
||
// Validate validates the fields | ||
func (i *IssueLockForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { | ||
return validate(errs, ctx.Data, i, ctx.Locale) | ||
} | ||
|
||
// HasValidReason checks to make sure that the reason submitted in | ||
// the form matches any of the values in the config | ||
func (i IssueLockForm) HasValidReason() bool { | ||
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. Maybe add this to 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. I think it is better if explicit enough ( I have a couple merged PRs that follow this route though ) .. And that regular I wouldn't want to mess with 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. Thoughts @JonasFranzDEV ? 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. Nudge 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. ping @jonasfranz |
||
if strings.TrimSpace(i.Reason) == "" { | ||
return true | ||
} | ||
|
||
for _, v := range setting.Repository.Issue.LockReasons { | ||
if v == i.Reason { | ||
return true | ||
} | ||
} | ||
|
||
return false | ||
} | ||
|
||
// _____ .__.__ __ | ||
// / \ |__| | ____ _______/ |_ ____ ____ ____ | ||
// / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -57,6 +57,23 @@ var ( | |
} | ||
) | ||
|
||
// MustAllowUserComment checks to make sure if an issue is locked. | ||
// If locked and user has permissions to write to the repository, | ||
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. What do you think about making it an option whether or not a repo writer can still comment on a locked issue? 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. Sounds nice. Will this potentially be a global config or an option set at the point of locking the issue ? 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. I'd make a global option (in the config file) and then a setting in the repo to overwrite the global default. 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. So, what about this? I think it'd be ok if we move the configuration to another pr. 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. @kolaente this PR is already long history, let's move such option to other PR 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. @lafriks sounds good! |
||
// then the comment is allowed, else it is blocked | ||
func MustAllowUserComment(ctx *context.Context) { | ||
|
||
issue := GetActionIssue(ctx) | ||
if ctx.Written() { | ||
return | ||
} | ||
|
||
if issue.IsLocked && !ctx.Repo.CanWrite(models.UnitTypeIssues) && !ctx.User.IsAdmin { | ||
ctx.Flash.Error(ctx.Tr("repo.issues.comment_on_locked")) | ||
ctx.Redirect(issue.HTMLURL()) | ||
return | ||
} | ||
} | ||
|
||
// MustEnableIssues check if repository enable internal issues | ||
func MustEnableIssues(ctx *context.Context) { | ||
if !ctx.Repo.CanRead(models.UnitTypeIssues) && | ||
|
@@ -898,6 +915,9 @@ func ViewIssue(ctx *context.Context) { | |
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login?redirect_to=" + ctx.Data["Link"].(string) | ||
ctx.Data["IsIssuePoster"] = ctx.IsSigned && issue.IsPoster(ctx.User.ID) | ||
ctx.Data["IsIssueWriter"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) | ||
ctx.Data["IsRepoAdmin"] = ctx.IsSigned && (ctx.Repo.IsAdmin() || ctx.User.IsAdmin) | ||
adelowo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
ctx.Data["IsRepoIssuesWriter"] = ctx.IsSigned && (ctx.Repo.CanWrite(models.UnitTypeIssues) || ctx.User.IsAdmin) | ||
ctx.Data["LockReasons"] = setting.Repository.Issue.LockReasons | ||
ctx.HTML(200, tplIssueView) | ||
} | ||
|
||
|
@@ -1118,6 +1138,11 @@ func NewComment(ctx *context.Context, form auth.CreateCommentForm) { | |
|
||
if !ctx.IsSigned || (ctx.User.ID != issue.PosterID && !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull)) { | ||
ctx.Error(403) | ||
} | ||
|
||
if issue.IsLocked && !ctx.Repo.CanWrite(models.UnitTypeIssues) && !ctx.User.IsAdmin { | ||
ctx.Flash.Error(ctx.Tr("repo.issues.comment_on_locked")) | ||
ctx.Redirect(issue.HTMLURL(), http.StatusSeeOther) | ||
return | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
// Copyright 2019 The Gitea Authors. All rights reserved. | ||
// Use of this source code is governed by a MIT-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package repo | ||
|
||
import ( | ||
"net/http" | ||
|
||
"code.gitea.io/gitea/models" | ||
"code.gitea.io/gitea/modules/auth" | ||
"code.gitea.io/gitea/modules/context" | ||
) | ||
|
||
// LockIssue locks an issue. This would limit commenting abilities to | ||
// users with write access to the repo. | ||
func LockIssue(ctx *context.Context, form auth.IssueLockForm) { | ||
|
||
issue := GetActionIssue(ctx) | ||
if ctx.Written() { | ||
return | ||
} | ||
|
||
if issue.IsLocked { | ||
ctx.Flash.Error(ctx.Tr("repo.issues.lock_duplicate")) | ||
ctx.Redirect(issue.HTMLURL()) | ||
return | ||
} | ||
|
||
if !form.HasValidReason() { | ||
ctx.Flash.Error(ctx.Tr("repo.issues.lock.unknown_reason")) | ||
ctx.Redirect(issue.HTMLURL()) | ||
return | ||
} | ||
|
||
if err := models.LockIssue(&models.IssueLockOptions{ | ||
Doer: ctx.User, | ||
Issue: issue, | ||
Reason: form.Reason, | ||
}); err != nil { | ||
ctx.ServerError("LockIssue", err) | ||
return | ||
} | ||
|
||
ctx.Redirect(issue.HTMLURL(), http.StatusSeeOther) | ||
} | ||
|
||
// UnlockIssue unlocks a previously locked issue. | ||
func UnlockIssue(ctx *context.Context) { | ||
|
||
issue := GetActionIssue(ctx) | ||
if ctx.Written() { | ||
return | ||
} | ||
|
||
if !issue.IsLocked { | ||
ctx.Flash.Error(ctx.Tr("repo.issues.unlock_error")) | ||
ctx.Redirect(issue.HTMLURL()) | ||
return | ||
} | ||
|
||
if err := models.UnlockIssue(&models.IssueLockOptions{ | ||
Doer: ctx.User, | ||
Issue: issue, | ||
}); err != nil { | ||
ctx.ServerError("UnlockIssue", err) | ||
return | ||
} | ||
|
||
ctx.Redirect(issue.HTMLURL(), http.StatusSeeOther) | ||
} |
Uh oh!
There was an error while loading. Please reload this page.