Skip to content

Commit

Permalink
Locker action (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
bivas authored Jul 16, 2017
1 parent 0ab6cc0 commit a087d10
Show file tree
Hide file tree
Showing 9 changed files with 221 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ The entire `condition` section is optional - you can run all rules all the time
- [`labeler`](bot/actions/labeler/labeler.md) - Add/Remove label to/from an issue
- [`sizing`](bot/actions/sizing/sizing.md) - Size a pull request
- [`trigger`](bot/actions/trigger/trigger.md) - Send HTTP triggers
- [`locker`](bot/actions/locker/locker.md) - Lock an issue

# Example Configuration

Expand Down
1 change: 1 addition & 0 deletions actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
_ "github.com/bivas/rivi/bot/actions/automerge"
_ "github.com/bivas/rivi/bot/actions/commenter"
_ "github.com/bivas/rivi/bot/actions/labeler"
_ "github.com/bivas/rivi/bot/actions/locker"
_ "github.com/bivas/rivi/bot/actions/sizing"
_ "github.com/bivas/rivi/bot/actions/trigger"
)
60 changes: 60 additions & 0 deletions bot/actions/locker/locker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package locker

import (
"fmt"
"github.com/bivas/rivi/bot"
"github.com/bivas/rivi/util"
"github.com/mitchellh/mapstructure"
)

type LockableEventData interface {
Lock()
Unlock()
LockState() bool
}

type action struct {
rule *rule
err error
}

func (a *action) Apply(config bot.Configuration, meta bot.EventData) {
lockable, ok := meta.(LockableEventData)
if !ok {
util.Logger.Warning("Event data does not support locking. Check your configurations")
a.err = fmt.Errorf("Event data does not support locking")
return
}
if lockable.LockState() {
util.Logger.Debug("Issue is locked")
if a.rule.State == "unlock" || a.rule.State == "change" {
util.Logger.Debug("unlocking issue %d", meta.GetNumber())
lockable.Unlock()
} else if a.rule.State == "lock" {
util.Logger.Debug("Issue %d is already locked - nothing changed", meta.GetNumber())
}
} else {
util.Logger.Debug("Issue is unlocked")
if a.rule.State == "lock" || a.rule.State == "change" {
util.Logger.Debug("Locking issue %d", meta.GetNumber())
lockable.Lock()
} else if a.rule.State == "lock" {
util.Logger.Debug("Issue %d is already unlocked - nothing changed", meta.GetNumber())
}
}
}

type factory struct {
}

func (*factory) BuildAction(config map[string]interface{}) bot.Action {
item := rule{}
if e := mapstructure.Decode(config, &item); e != nil {
panic(e)
}
return &action{rule: &item}
}

func init() {
bot.RegisterAction("locker", &factory{})
}
21 changes: 21 additions & 0 deletions bot/actions/locker/locker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Locker

## Goal

Ability to lock/unlock an issue

## Requirements

None

## Options

- `state` (required) - sets the issue state. Can be `lock`, `unlock` or `change`

## Example
```yaml
rules:
example:
locker:
state: lock
```
95 changes: 95 additions & 0 deletions bot/actions/locker/locker_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package locker

import (
"github.com/bivas/rivi/bot/mock"
"github.com/stretchr/testify/assert"
"testing"
)

type mockLockableEventData struct {
mock.MockEventData
locked bool
lockCalled, unlockCalled bool
}

func (m *mockLockableEventData) Lock() {
m.locked = true
m.lockCalled = true
}

func (m *mockLockableEventData) Unlock() {
m.locked = false
m.unlockCalled = true
}

func (m *mockLockableEventData) LockState() bool {
return m.locked
}

func TestNotLockable(t *testing.T) {
action := action{rule: &rule{}}
meta := &mock.MockEventData{Labels: []string{}}
config := &mock.MockConfiguration{}
action.Apply(config, meta)
assert.NotNil(t, action.err, "can't merge")
}

func TestLock(t *testing.T) {
action := action{rule: &rule{State: "lock"}}
meta := &mockLockableEventData{MockEventData: mock.MockEventData{}}
config := &mock.MockConfiguration{}
action.Apply(config, meta)
assert.Nil(t, action.err, "shouldn't error")
assert.True(t, meta.locked, "should be locked")
assert.True(t, meta.lockCalled, "should be locked")
}

func TestLockWhenLocked(t *testing.T) {
action := action{rule: &rule{State: "lock"}}
meta := &mockLockableEventData{MockEventData: mock.MockEventData{}, locked: true}
config := &mock.MockConfiguration{}
action.Apply(config, meta)
assert.Nil(t, action.err, "shouldn't error")
assert.True(t, meta.locked, "should be locked")
assert.False(t, meta.lockCalled, "no need to relock")
}

func TestUnlockWhenLocked(t *testing.T) {
action := action{rule: &rule{State: "unlock"}}
meta := &mockLockableEventData{MockEventData: mock.MockEventData{}, locked: true}
config := &mock.MockConfiguration{}
action.Apply(config, meta)
assert.Nil(t, action.err, "shouldn't error")
assert.False(t, meta.locked, "should be unlocked")
assert.True(t, meta.unlockCalled, "should be unlocked")
}

func TestUnlockWhenUnlocked(t *testing.T) {
action := action{rule: &rule{State: "unlock"}}
meta := &mockLockableEventData{MockEventData: mock.MockEventData{}}
config := &mock.MockConfiguration{}
action.Apply(config, meta)
assert.Nil(t, action.err, "shouldn't error")
assert.False(t, meta.locked, "should be unlocked")
assert.False(t, meta.unlockCalled, "no need to re-unlock")
}

func TestStateChangeFromUnlocked(t *testing.T) {
action := action{rule: &rule{State: "change"}}
meta := &mockLockableEventData{MockEventData: mock.MockEventData{}}
config := &mock.MockConfiguration{}
action.Apply(config, meta)
assert.Nil(t, action.err, "shouldn't error")
assert.True(t, meta.locked, "should be locked")
assert.True(t, meta.lockCalled, "lock")
}

func TestStateChangeFromLocked(t *testing.T) {
action := action{rule: &rule{State: "change"}}
meta := &mockLockableEventData{MockEventData: mock.MockEventData{}, locked: true}
config := &mock.MockConfiguration{}
action.Apply(config, meta)
assert.Nil(t, action.err, "shouldn't error")
assert.False(t, meta.locked, "should be unlocked")
assert.True(t, meta.unlockCalled, "unlock")
}
5 changes: 5 additions & 0 deletions bot/actions/locker/rule.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package locker

type rule struct {
State string `mapstructure:"state"`
}
1 change: 1 addition & 0 deletions bot/connector/github/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func (builder *eventDataBuilder) readFromClient(context *builderContext) {
context.data.fileNames = fileNames
stringSet := util.StringSet{Transformer: filepath.Ext}
context.data.fileExt = stringSet.AddAll(fileNames).Values()
context.data.locked = context.client.Locked(id)
}

func (builder *eventDataBuilder) checkProcessState(context *builderContext) bool {
Expand Down
22 changes: 22 additions & 0 deletions bot/connector/github/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,28 @@ func handleLabelsResult(labels []*github.Label, err error, logError func(error))
return result
}

func (c *ghClient) Lock(issue int) {
_, err := c.client.Issues.Lock(context.Background(), c.owner, c.repo, issue)
if err != nil {
util.Logger.Error("Unable to set issue %d lock state. %s", issue, err)
}
}

func (c *ghClient) Unlock(issue int) {
_, err := c.client.Issues.Unlock(context.Background(), c.owner, c.repo, issue)
if err != nil {
util.Logger.Error("Unable to set issue %d unlock state. %s", issue, err)
}
}

func (c *ghClient) Locked(issue int) bool {
response, _, err := c.client.Issues.Get(context.Background(), c.owner, c.repo, issue)
if err != nil {
util.Logger.Error("Unable to get issue %d lock state. %s", issue, err)
}
return *response.Locked
}

func (c *ghClient) GetAvailableLabels() []string {
util.Logger.Debug("Getting available labels")
labels, _, e := c.client.Issues.ListLabels(context.Background(), c.owner, c.repo, nil)
Expand Down
15 changes: 15 additions & 0 deletions bot/connector/github/event_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ type eventData struct {
client *ghClient
number int
state string
locked bool
origin string
owner string
repo string
Expand All @@ -22,6 +23,20 @@ type eventData struct {
payload []byte
}

func (d *eventData) Lock() {
d.client.Lock(d.number)
d.locked = true
}

func (d *eventData) Unlock() {
d.client.Unlock(d.number)
d.locked = false
}

func (d *eventData) LockState() bool {
return d.locked
}

func (d *eventData) GetRawPayload() []byte {
return d.payload
}
Expand Down

0 comments on commit a087d10

Please sign in to comment.