Skip to content

Commit a087d10

Browse files
authored
Locker action (#10)
1 parent 0ab6cc0 commit a087d10

File tree

9 files changed

+221
-0
lines changed

9 files changed

+221
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ The entire `condition` section is optional - you can run all rules all the time
163163
- [`labeler`](bot/actions/labeler/labeler.md) - Add/Remove label to/from an issue
164164
- [`sizing`](bot/actions/sizing/sizing.md) - Size a pull request
165165
- [`trigger`](bot/actions/trigger/trigger.md) - Send HTTP triggers
166+
- [`locker`](bot/actions/locker/locker.md) - Lock an issue
166167

167168
# Example Configuration
168169

actions.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
_ "github.com/bivas/rivi/bot/actions/automerge"
66
_ "github.com/bivas/rivi/bot/actions/commenter"
77
_ "github.com/bivas/rivi/bot/actions/labeler"
8+
_ "github.com/bivas/rivi/bot/actions/locker"
89
_ "github.com/bivas/rivi/bot/actions/sizing"
910
_ "github.com/bivas/rivi/bot/actions/trigger"
1011
)

bot/actions/locker/locker.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package locker
2+
3+
import (
4+
"fmt"
5+
"github.com/bivas/rivi/bot"
6+
"github.com/bivas/rivi/util"
7+
"github.com/mitchellh/mapstructure"
8+
)
9+
10+
type LockableEventData interface {
11+
Lock()
12+
Unlock()
13+
LockState() bool
14+
}
15+
16+
type action struct {
17+
rule *rule
18+
err error
19+
}
20+
21+
func (a *action) Apply(config bot.Configuration, meta bot.EventData) {
22+
lockable, ok := meta.(LockableEventData)
23+
if !ok {
24+
util.Logger.Warning("Event data does not support locking. Check your configurations")
25+
a.err = fmt.Errorf("Event data does not support locking")
26+
return
27+
}
28+
if lockable.LockState() {
29+
util.Logger.Debug("Issue is locked")
30+
if a.rule.State == "unlock" || a.rule.State == "change" {
31+
util.Logger.Debug("unlocking issue %d", meta.GetNumber())
32+
lockable.Unlock()
33+
} else if a.rule.State == "lock" {
34+
util.Logger.Debug("Issue %d is already locked - nothing changed", meta.GetNumber())
35+
}
36+
} else {
37+
util.Logger.Debug("Issue is unlocked")
38+
if a.rule.State == "lock" || a.rule.State == "change" {
39+
util.Logger.Debug("Locking issue %d", meta.GetNumber())
40+
lockable.Lock()
41+
} else if a.rule.State == "lock" {
42+
util.Logger.Debug("Issue %d is already unlocked - nothing changed", meta.GetNumber())
43+
}
44+
}
45+
}
46+
47+
type factory struct {
48+
}
49+
50+
func (*factory) BuildAction(config map[string]interface{}) bot.Action {
51+
item := rule{}
52+
if e := mapstructure.Decode(config, &item); e != nil {
53+
panic(e)
54+
}
55+
return &action{rule: &item}
56+
}
57+
58+
func init() {
59+
bot.RegisterAction("locker", &factory{})
60+
}

bot/actions/locker/locker.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Locker
2+
3+
## Goal
4+
5+
Ability to lock/unlock an issue
6+
7+
## Requirements
8+
9+
None
10+
11+
## Options
12+
13+
- `state` (required) - sets the issue state. Can be `lock`, `unlock` or `change`
14+
15+
## Example
16+
```yaml
17+
rules:
18+
example:
19+
locker:
20+
state: lock
21+
```

bot/actions/locker/locker_test.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package locker
2+
3+
import (
4+
"github.com/bivas/rivi/bot/mock"
5+
"github.com/stretchr/testify/assert"
6+
"testing"
7+
)
8+
9+
type mockLockableEventData struct {
10+
mock.MockEventData
11+
locked bool
12+
lockCalled, unlockCalled bool
13+
}
14+
15+
func (m *mockLockableEventData) Lock() {
16+
m.locked = true
17+
m.lockCalled = true
18+
}
19+
20+
func (m *mockLockableEventData) Unlock() {
21+
m.locked = false
22+
m.unlockCalled = true
23+
}
24+
25+
func (m *mockLockableEventData) LockState() bool {
26+
return m.locked
27+
}
28+
29+
func TestNotLockable(t *testing.T) {
30+
action := action{rule: &rule{}}
31+
meta := &mock.MockEventData{Labels: []string{}}
32+
config := &mock.MockConfiguration{}
33+
action.Apply(config, meta)
34+
assert.NotNil(t, action.err, "can't merge")
35+
}
36+
37+
func TestLock(t *testing.T) {
38+
action := action{rule: &rule{State: "lock"}}
39+
meta := &mockLockableEventData{MockEventData: mock.MockEventData{}}
40+
config := &mock.MockConfiguration{}
41+
action.Apply(config, meta)
42+
assert.Nil(t, action.err, "shouldn't error")
43+
assert.True(t, meta.locked, "should be locked")
44+
assert.True(t, meta.lockCalled, "should be locked")
45+
}
46+
47+
func TestLockWhenLocked(t *testing.T) {
48+
action := action{rule: &rule{State: "lock"}}
49+
meta := &mockLockableEventData{MockEventData: mock.MockEventData{}, locked: true}
50+
config := &mock.MockConfiguration{}
51+
action.Apply(config, meta)
52+
assert.Nil(t, action.err, "shouldn't error")
53+
assert.True(t, meta.locked, "should be locked")
54+
assert.False(t, meta.lockCalled, "no need to relock")
55+
}
56+
57+
func TestUnlockWhenLocked(t *testing.T) {
58+
action := action{rule: &rule{State: "unlock"}}
59+
meta := &mockLockableEventData{MockEventData: mock.MockEventData{}, locked: true}
60+
config := &mock.MockConfiguration{}
61+
action.Apply(config, meta)
62+
assert.Nil(t, action.err, "shouldn't error")
63+
assert.False(t, meta.locked, "should be unlocked")
64+
assert.True(t, meta.unlockCalled, "should be unlocked")
65+
}
66+
67+
func TestUnlockWhenUnlocked(t *testing.T) {
68+
action := action{rule: &rule{State: "unlock"}}
69+
meta := &mockLockableEventData{MockEventData: mock.MockEventData{}}
70+
config := &mock.MockConfiguration{}
71+
action.Apply(config, meta)
72+
assert.Nil(t, action.err, "shouldn't error")
73+
assert.False(t, meta.locked, "should be unlocked")
74+
assert.False(t, meta.unlockCalled, "no need to re-unlock")
75+
}
76+
77+
func TestStateChangeFromUnlocked(t *testing.T) {
78+
action := action{rule: &rule{State: "change"}}
79+
meta := &mockLockableEventData{MockEventData: mock.MockEventData{}}
80+
config := &mock.MockConfiguration{}
81+
action.Apply(config, meta)
82+
assert.Nil(t, action.err, "shouldn't error")
83+
assert.True(t, meta.locked, "should be locked")
84+
assert.True(t, meta.lockCalled, "lock")
85+
}
86+
87+
func TestStateChangeFromLocked(t *testing.T) {
88+
action := action{rule: &rule{State: "change"}}
89+
meta := &mockLockableEventData{MockEventData: mock.MockEventData{}, locked: true}
90+
config := &mock.MockConfiguration{}
91+
action.Apply(config, meta)
92+
assert.Nil(t, action.err, "shouldn't error")
93+
assert.False(t, meta.locked, "should be unlocked")
94+
assert.True(t, meta.unlockCalled, "unlock")
95+
}

bot/actions/locker/rule.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package locker
2+
3+
type rule struct {
4+
State string `mapstructure:"state"`
5+
}

bot/connector/github/builder.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ func (builder *eventDataBuilder) readFromClient(context *builderContext) {
8686
context.data.fileNames = fileNames
8787
stringSet := util.StringSet{Transformer: filepath.Ext}
8888
context.data.fileExt = stringSet.AddAll(fileNames).Values()
89+
context.data.locked = context.client.Locked(id)
8990
}
9091

9192
func (builder *eventDataBuilder) checkProcessState(context *builderContext) bool {

bot/connector/github/client.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,28 @@ func handleLabelsResult(labels []*github.Label, err error, logError func(error))
3838
return result
3939
}
4040

41+
func (c *ghClient) Lock(issue int) {
42+
_, err := c.client.Issues.Lock(context.Background(), c.owner, c.repo, issue)
43+
if err != nil {
44+
util.Logger.Error("Unable to set issue %d lock state. %s", issue, err)
45+
}
46+
}
47+
48+
func (c *ghClient) Unlock(issue int) {
49+
_, err := c.client.Issues.Unlock(context.Background(), c.owner, c.repo, issue)
50+
if err != nil {
51+
util.Logger.Error("Unable to set issue %d unlock state. %s", issue, err)
52+
}
53+
}
54+
55+
func (c *ghClient) Locked(issue int) bool {
56+
response, _, err := c.client.Issues.Get(context.Background(), c.owner, c.repo, issue)
57+
if err != nil {
58+
util.Logger.Error("Unable to get issue %d lock state. %s", issue, err)
59+
}
60+
return *response.Locked
61+
}
62+
4163
func (c *ghClient) GetAvailableLabels() []string {
4264
util.Logger.Debug("Getting available labels")
4365
labels, _, e := c.client.Issues.ListLabels(context.Background(), c.owner, c.repo, nil)

bot/connector/github/event_data.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ type eventData struct {
66
client *ghClient
77
number int
88
state string
9+
locked bool
910
origin string
1011
owner string
1112
repo string
@@ -22,6 +23,20 @@ type eventData struct {
2223
payload []byte
2324
}
2425

26+
func (d *eventData) Lock() {
27+
d.client.Lock(d.number)
28+
d.locked = true
29+
}
30+
31+
func (d *eventData) Unlock() {
32+
d.client.Unlock(d.number)
33+
d.locked = false
34+
}
35+
36+
func (d *eventData) LockState() bool {
37+
return d.locked
38+
}
39+
2540
func (d *eventData) GetRawPayload() []byte {
2641
return d.payload
2742
}

0 commit comments

Comments
 (0)