Skip to content

Commit 44114b3

Browse files
adelowolafriks
authored andcommitted
Implement "conversation lock" for issue comments (#5073)
1 parent 64ce159 commit 44114b3

File tree

19 files changed

+435
-4
lines changed

19 files changed

+435
-4
lines changed

custom/conf/app.ini.sample

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ MAX_FILES = 5
6969
; List of prefixes used in Pull Request title to mark them as Work In Progress
7070
WORK_IN_PROGRESS_PREFIXES=WIP:,[WIP]
7171

72+
[repository.issue]
73+
; List of reasons why a Pull Request or Issue can be locked
74+
LOCK_REASONS=Too heated,Off-topic,Resolved,Spam
75+
7276
[ui]
7377
; Number of repositories that are displayed on one explore page
7478
EXPLORE_PAGING_NUM = 20

docs/content/doc/advanced/config-cheat-sheet.en-us.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
7171
- `WORK_IN_PROGRESS_PREFIXES`: **WIP:,\[WIP\]**: List of prefixes used in Pull Request
7272
title to mark them as Work In Progress
7373

74+
### Repository - Issue (`repository.issue`)
75+
- `LOCK_REASONS`: **Too heated,Off-topic,Resolved,Spam**: A list of reasons why a Pull Request or Issue can be locked
76+
7477
## UI (`ui`)
7578

7679
- `EXPLORE_PAGING_NUM`: **20**: Number of repositories that are shown in one explore page.

docs/content/doc/features/comparison.en-us.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ _Symbols used in table:_
8181
| Related issues ||||||||
8282
| Confidential issues ||||||||
8383
| Comment reactions ||||||||
84-
| Lock Discussion | |||||||
84+
| Lock Discussion | |||||||
8585
| Batch issue handling ||||||||
8686
| Issue Boards ||||||||
8787
| Create new branches from issues ||||||||

models/issue.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ type Issue struct {
5757
Reactions ReactionList `xorm:"-"`
5858
TotalTrackedTime int64 `xorm:"-"`
5959
Assignees []*User `xorm:"-"`
60+
61+
// IsLocked limits commenting abilities to users on an issue
62+
// with write access
63+
IsLocked bool `xorm:"NOT NULL DEFAULT false"`
6064
}
6165

6266
var (

models/issue_comment.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ const (
8080
CommentTypeCode
8181
// Reviews a pull request by giving general feedback
8282
CommentTypeReview
83+
// Lock an issue, giving only collaborators access
84+
CommentTypeLock
85+
// Unlocks a previously locked issue
86+
CommentTypeUnlock
8387
)
8488

8589
// CommentTag defines comment tag type

models/issue_lock.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright 2019 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package models
6+
7+
// IssueLockOptions defines options for locking and/or unlocking an issue/PR
8+
type IssueLockOptions struct {
9+
Doer *User
10+
Issue *Issue
11+
Reason string
12+
}
13+
14+
// LockIssue locks an issue. This would limit commenting abilities to
15+
// users with write access to the repo
16+
func LockIssue(opts *IssueLockOptions) error {
17+
return updateIssueLock(opts, true)
18+
}
19+
20+
// UnlockIssue unlocks a previously locked issue.
21+
func UnlockIssue(opts *IssueLockOptions) error {
22+
return updateIssueLock(opts, false)
23+
}
24+
25+
func updateIssueLock(opts *IssueLockOptions, lock bool) error {
26+
if opts.Issue.IsLocked == lock {
27+
return nil
28+
}
29+
30+
opts.Issue.IsLocked = lock
31+
32+
var commentType CommentType
33+
if opts.Issue.IsLocked {
34+
commentType = CommentTypeLock
35+
} else {
36+
commentType = CommentTypeUnlock
37+
}
38+
39+
if err := UpdateIssueCols(opts.Issue, "is_locked"); err != nil {
40+
return err
41+
}
42+
43+
_, err := CreateComment(&CreateCommentOptions{
44+
Doer: opts.Doer,
45+
Issue: opts.Issue,
46+
Repo: opts.Issue.Repo,
47+
Type: commentType,
48+
Content: opts.Reason,
49+
})
50+
return err
51+
}

models/migrations/migrations.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,8 @@ var migrations = []Migration{
213213
NewMigration("rename repo is_bare to repo is_empty", renameRepoIsBareToIsEmpty),
214214
// v79 -> v80
215215
NewMigration("add can close issues via commit in any branch", addCanCloseIssuesViaCommitInAnyBranch),
216+
// v80 -> v81
217+
NewMigration("add is locked to issues", addIsLockedToIssues),
216218
}
217219

218220
// Migrate database to current version

models/migrations/v80.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2019 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package migrations
6+
7+
import "github.com/go-xorm/xorm"
8+
9+
func addIsLockedToIssues(x *xorm.Engine) error {
10+
// Issue see models/issue.go
11+
type Issue struct {
12+
ID int64 `xorm:"pk autoincr"`
13+
IsLocked bool `xorm:"NOT NULL DEFAULT false"`
14+
}
15+
16+
return x.Sync2(new(Issue))
17+
18+
}

modules/auth/repo_form.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"strings"
1111

1212
"code.gitea.io/gitea/models"
13+
"code.gitea.io/gitea/modules/setting"
1314
"code.gitea.io/gitea/routers/utils"
1415

1516
"github.com/Unknwon/com"
@@ -308,6 +309,32 @@ func (f *ReactionForm) Validate(ctx *macaron.Context, errs binding.Errors) bindi
308309
return validate(errs, ctx.Data, f, ctx.Locale)
309310
}
310311

312+
// IssueLockForm form for locking an issue
313+
type IssueLockForm struct {
314+
Reason string `binding:"Required"`
315+
}
316+
317+
// Validate validates the fields
318+
func (i *IssueLockForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
319+
return validate(errs, ctx.Data, i, ctx.Locale)
320+
}
321+
322+
// HasValidReason checks to make sure that the reason submitted in
323+
// the form matches any of the values in the config
324+
func (i IssueLockForm) HasValidReason() bool {
325+
if strings.TrimSpace(i.Reason) == "" {
326+
return true
327+
}
328+
329+
for _, v := range setting.Repository.Issue.LockReasons {
330+
if v == i.Reason {
331+
return true
332+
}
333+
}
334+
335+
return false
336+
}
337+
311338
// _____ .__.__ __
312339
// / \ |__| | ____ _______/ |_ ____ ____ ____
313340
// / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \

modules/auth/repo_form_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package auth
77
import (
88
"testing"
99

10+
"code.gitea.io/gitea/modules/setting"
1011
"github.com/stretchr/testify/assert"
1112
)
1213

@@ -39,3 +40,27 @@ func TestSubmitReviewForm_IsEmpty(t *testing.T) {
3940
assert.Equal(t, v.expected, v.form.HasEmptyContent())
4041
}
4142
}
43+
44+
func TestIssueLock_HasValidReason(t *testing.T) {
45+
46+
// Init settings
47+
_ = setting.Repository
48+
49+
cases := []struct {
50+
form IssueLockForm
51+
expected bool
52+
}{
53+
{IssueLockForm{""}, true}, // an empty reason is accepted
54+
{IssueLockForm{"Off-topic"}, true},
55+
{IssueLockForm{"Too heated"}, true},
56+
{IssueLockForm{"Spam"}, true},
57+
{IssueLockForm{"Resolved"}, true},
58+
59+
{IssueLockForm{"ZZZZ"}, false},
60+
{IssueLockForm{"I want to lock this issue"}, false},
61+
}
62+
63+
for _, v := range cases {
64+
assert.Equal(t, v.expected, v.form.HasValidReason())
65+
}
66+
}

0 commit comments

Comments
 (0)