Skip to content

Commit

Permalink
New command "notify user @whateveruser active"
Browse files Browse the repository at this point in the history
  • Loading branch information
brainexe committed Aug 31, 2023
1 parent 72a826a commit b859f7e
Show file tree
Hide file tree
Showing 10 changed files with 222 additions and 23 deletions.
8 changes: 8 additions & 0 deletions client/slack.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ type SlackClient interface {

// GetThreadMessages loads message from a given thread
GetThreadMessages(ref msg.Ref) ([]slack.Message, error)

// GetUserPresence returns the current presence of a user, using the "users.getPresence" API
GetUserPresence(user string) (*slack.UserPresence, error)
}

// Slack is wrapper to the slack.Client which also holds the the socketmode.Client and all needed config
Expand Down Expand Up @@ -289,6 +292,11 @@ func (s *Slack) GetThreadMessages(ref msg.Ref) ([]slack.Message, error) {
return allMessages, nil
}

// GetUserPresence returns the current presence of a user, using the "users.getPresence" API
func (s *Slack) GetUserPresence(user string) (*slack.UserPresence, error) {
return s.Client.GetUserPresence(user)
}

// GetUserIDAndName returns the user-id and user-name based on a identifier. If can get a user-id or name
func GetUserIDAndName(identifier string) (id string, name string) {
identifier = strings.TrimPrefix(identifier, "@")
Expand Down
1 change: 1 addition & 0 deletions command/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func GetCommands(slackClient client.SlackClient, cfg config.Config) *bot.Command
NewDelayCommand(base),
NewRandomCommand(base),
NewHelpCommand(base, commands),
newUserStatusCommand(base),

weather.NewWeatherCommand(base, cfg.OpenWeather),

Expand Down
2 changes: 1 addition & 1 deletion command/jenkins/inform_idle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func TestInformIdle(t *testing.T) {
assert.True(t, actual)

// wait until watcher is ready
time.Sleep(time.Millisecond * 100)
queue.WaitTillHavingNoQueuedMessage()

assert.Equal(t, 0, queue.CountCurrentJobs())
})
Expand Down
16 changes: 8 additions & 8 deletions command/openai/openai_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import (
"net/http"
"net/http/httptest"
"testing"
"time"

"github.com/innogames/slack-bot/v2/bot"
"github.com/innogames/slack-bot/v2/bot/config"
"github.com/innogames/slack-bot/v2/bot/msg"
"github.com/innogames/slack-bot/v2/bot/storage"
"github.com/innogames/slack-bot/v2/bot/util"
"github.com/innogames/slack-bot/v2/command/queue"
"github.com/innogames/slack-bot/v2/mocks"
"github.com/slack-go/slack"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -125,7 +125,7 @@ data: [DONE]`,
mocks.AssertSlackMessage(slackClient, ref, "The answer is 2", mock.Anything, mock.Anything)

actual := commands.Run(message)
time.Sleep(time.Millisecond * 100)
queue.WaitTillHavingNoQueuedMessage()
assert.True(t, actual)

// test reply in different context -> nothing
Expand All @@ -135,7 +135,7 @@ data: [DONE]`,
message.Thread = "4321"

actual = commands.Run(message)
time.Sleep(time.Millisecond * 100)
queue.WaitTillHavingNoQueuedMessage()
assert.False(t, actual)

// test reply in same context -> ask openai with history
Expand All @@ -150,7 +150,7 @@ data: [DONE]`,
mocks.AssertSlackMessage(slackClient, message, "The answer is 3", mock.Anything)

actual = commands.Run(message)
time.Sleep(time.Millisecond * 100)
queue.WaitTillHavingNoQueuedMessage()
assert.True(t, actual)
})

Expand Down Expand Up @@ -192,7 +192,7 @@ data: [DONE]`,
mocks.AssertSlackMessage(slackClient, ref, "Incorrect API key provided: sk-1234**************************************567.", mock.Anything, mock.Anything)

actual := commands.Run(message)
time.Sleep(100 * time.Millisecond)
queue.WaitTillHavingNoQueuedMessage()
assert.True(t, actual)
})

Expand Down Expand Up @@ -234,7 +234,7 @@ data: [DONE]`,
mocks.AssertSlackMessage(slackClient, message, "Incorrect API key provided: sk-1234**************************************567.", mock.Anything, mock.Anything)

actual := commands.Run(message)
time.Sleep(100 * time.Millisecond)
queue.WaitTillHavingNoQueuedMessage()
assert.True(t, actual)
})

Expand Down Expand Up @@ -319,7 +319,7 @@ data: [DONE]`,
mocks.AssertError(slackClient, ref, "can't load thread messages: openai not reachable")
slackClient.On("GetThreadMessages", ref).Once().Return([]slack.Message{}, errors.New("openai not reachable"))
actual := commands.Run(message)
time.Sleep(time.Millisecond * 50)
queue.WaitTillHavingNoQueuedMessage()
assert.True(t, actual)

// then a successful attempt
Expand All @@ -336,7 +336,7 @@ data: [DONE]`,
mocks.AssertSlackMessage(slackClient, ref, "Jolo!", mock.Anything, mock.Anything)

actual = commands.Run(message)
time.Sleep(time.Millisecond * 100)
queue.WaitTillHavingNoQueuedMessage()
assert.True(t, actual)
})
}
5 changes: 3 additions & 2 deletions command/pullrequest/github_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/innogames/slack-bot/v2/bot/matcher"
"github.com/innogames/slack-bot/v2/bot/msg"
"github.com/innogames/slack-bot/v2/bot/util"
"github.com/innogames/slack-bot/v2/command/queue"
"github.com/innogames/slack-bot/v2/mocks"
"github.com/slack-go/slack"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -43,7 +44,7 @@ func TestGithub(t *testing.T) {
mocks.AssertReaction(slackClient, "x", message)

actual := commands.Run(message)
time.Sleep(time.Millisecond * 300)
queue.WaitTillHavingNoQueuedMessage()
assert.Equal(t, true, actual)
})

Expand All @@ -57,7 +58,7 @@ func TestGithub(t *testing.T) {
mocks.AssertReaction(slackClient, "twisted_rightwards_arrows", message)

actual := commands.Run(message)
time.Sleep(time.Millisecond * 300)
time.Sleep(time.Millisecond * 200)
assert.Equal(t, true, actual)
})

Expand Down
22 changes: 22 additions & 0 deletions command/queue/queue.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package queue
import (
"strings"
"sync"
"time"

"github.com/innogames/slack-bot/v2/bot/msg"
"github.com/innogames/slack-bot/v2/bot/storage"
Expand Down Expand Up @@ -96,3 +97,24 @@ func executeFallbackCommand() {

_ = storage.DeleteCollection(storageKey)
}

// WaitTillHavingNoQueuedMessage will wait in test context until all background tasks are done.
// we use a deadline of 2s until we mark the test as failed
func WaitTillHavingNoQueuedMessage() {
deadline := time.Second * 2
timeout := time.NewTimer(deadline)
ticker := time.NewTicker(time.Millisecond * 5)
defer ticker.Stop()
defer timeout.Stop()

for {
select {
case <-timeout.C:
panic("Queue is still full after " + deadline.String())
case <-ticker.C:
if CountCurrentJobs() == 0 {
return
}
}
}
}
72 changes: 72 additions & 0 deletions command/user_status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package command

import (
"fmt"
"time"

"github.com/innogames/slack-bot/v2/command/queue"

"github.com/innogames/slack-bot/v2/bot"
"github.com/innogames/slack-bot/v2/bot/matcher"
"github.com/innogames/slack-bot/v2/bot/msg"
)

const notifyCheckInterval = time.Minute * 1

// Command which informs the user when the given user got active
func newUserStatusCommand(base bot.BaseCommand) *userStatus {
return &userStatus{
base,
notifyCheckInterval,
}
}

type userStatus struct {
bot.BaseCommand
checkInterval time.Duration
}

func (c *userStatus) GetMatcher() matcher.Matcher {
return matcher.NewRegexpMatcher(`notify user <@(?P<user>.*)> (?P<status>(away|active))`, c.NotifyUserActive)
}

func (c *userStatus) NotifyUserActive(match matcher.Result, message msg.Message) {
user := match.GetString("user")
expectedStatus := match.GetString("status")

// in case of bot restart: restart this command again
runningCommand := queue.AddRunningCommand(message, message.Text)

c.AddReaction("⌛", message)
go func() {
defer c.RemoveReaction("⌛", message)
defer runningCommand.Done()

for {
presence, err := c.SlackClient.GetUserPresence(user)
if err != nil {
c.ReplyError(message, err)
return
}

if presence.Presence == expectedStatus {
c.SendMessage(message, fmt.Sprintf("User <@%s> is %s now!", user, presence.Presence))
return
}

time.Sleep(c.checkInterval)
}
}()
}

func (c *userStatus) GetHelp() []bot.Help {
return []bot.Help{
{
Command: "notify user active",
Description: "Inform you if the given user change the slack status to active.",
Examples: []string{
"notify user @myboss active",
},
},
}
}
71 changes: 71 additions & 0 deletions command/user_status_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package command

import (
"fmt"
"testing"
"time"

"github.com/innogames/slack-bot/v2/bot"
"github.com/innogames/slack-bot/v2/bot/msg"
"github.com/innogames/slack-bot/v2/command/queue"
"github.com/innogames/slack-bot/v2/mocks"
"github.com/slack-go/slack"
"github.com/stretchr/testify/assert"
)

func TestUserStatus(t *testing.T) {
slackClient := &mocks.SlackClient{}

base := bot.BaseCommand{SlackClient: slackClient}
command := newUserStatusCommand(base)

commands := bot.Commands{}
commands.AddCommand(command)

t.Run("Invalid command", func(t *testing.T) {
message := msg.Message{}
message.Text = "notify for something"

actual := commands.Run(message)
assert.False(t, actual)
})

t.Run("Check with error", func(t *testing.T) {
message := msg.Message{}
message.Text = "notify user <@U123456> active"

err := fmt.Errorf("some slack error")
slackClient.On("GetUserPresence", "U123456").Once().Return(nil, err)

mocks.AssertReaction(slackClient, "⌛", message)
mocks.AssertRemoveReaction(slackClient, "⌛", message)
mocks.AssertError(slackClient, message, err)

actual := commands.Run(message)
assert.True(t, actual)
queue.WaitTillHavingNoQueuedMessage()
})

t.Run("Check user getting active", func(t *testing.T) {
message := msg.Message{}
message.Text = "notify user <@U123456> active"

command.checkInterval = time.Millisecond * 1
presenceAway := &slack.UserPresence{
Presence: "away",
}
presenceActive := &slack.UserPresence{
Presence: "active",
}
slackClient.On("GetUserPresence", "U123456").Once().Return(presenceAway, nil)
slackClient.On("GetUserPresence", "U123456").Once().Return(presenceActive, nil)

mocks.AssertReaction(slackClient, "⌛", message)
mocks.AssertRemoveReaction(slackClient, "⌛", message)
mocks.AssertSlackMessage(slackClient, message, "User <@U123456> is active now!")

actual := commands.Run(message)
assert.True(t, actual)
queue.WaitTillHavingNoQueuedMessage()
})
}
11 changes: 5 additions & 6 deletions mocks/Client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit b859f7e

Please sign in to comment.