Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions internal/slack/slack.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package slack
import (
"errors"
"fmt"
"sync"
"time"

"github.com/elliotchance/pie/v2"
Expand All @@ -18,6 +19,8 @@ type service struct {
client iclient
maxAttempts int
initialBackoff time.Duration
channelCache map[string]*slack.Channel
cacheMutex sync.RWMutex
}

type conversationsResult struct {
Expand All @@ -36,6 +39,7 @@ func New(token string, debug bool) (IService, error) {
client: &client{client: slackClient},
maxAttempts: 5,
initialBackoff: 2 * time.Second,
channelCache: make(map[string]*slack.Channel),
}

return &s, nil
Expand Down Expand Up @@ -68,6 +72,12 @@ func (s *service) findSlackChannel(channelName string) (channel *slack.Channel,
var channels []slack.Channel
var channelTypes = []string{"private_channel", "public_channel"}

cachedChannel := s.getCachedChannel(channelName)
if cachedChannel != nil {
log.Debug().Str("channel", channelName).Msg("Found slack channel in cache")
return cachedChannel, nil
}

for {
result, opErr := runWithRetries(func() (conversationsResult, error) {
convChannels, convCursor, convErr := s.client.GetConversations(&slack.GetConversationsParameters{
Expand All @@ -92,6 +102,7 @@ func (s *service) findSlackChannel(channelName string) (channel *slack.Channel,
if idx > -1 {
log.Info().Str("channel", channelName).Msg("Found slack channel")
channel = &channels[idx]
s.saveChannelToCache(channelName, channel)
return
} else if nextCursor == "" {
return nil, fmt.Errorf("channel %v not found", channelName)
Expand All @@ -101,6 +112,24 @@ func (s *service) findSlackChannel(channelName string) (channel *slack.Channel,
}
}

// getCachedChannel retrieves a channel from the cache if it exists
func (s *service) getCachedChannel(channelName string) (channel *slack.Channel) {
s.cacheMutex.RLock()
defer s.cacheMutex.RUnlock()
ch := s.channelCache[channelName]
return ch
}

// saveChannelToCache saves a channel to the cache
func (s *service) saveChannelToCache(channelName string, channel *slack.Channel) {
s.cacheMutex.Lock()
defer s.cacheMutex.Unlock()
if s.channelCache == nil {
s.channelCache = make(map[string]*slack.Channel)
}
s.channelCache[channelName] = channel
}

func runWithRetries[T any](operation func() (T, error), maxAttempts int, backoff time.Duration) (result T, err error) {
if maxAttempts <= 0 {
maxAttempts = 1
Expand Down
43 changes: 43 additions & 0 deletions internal/slack/slack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,46 @@ func TestPostMessageWithDynamicRateLimitRetry(t *testing.T) {
elapsed := time.Since(start)
assert.GreaterOrEqual(t, elapsed, expectedWait, "should have used Slack's dynamic RetryAfter backoff")
}

func TestChannelIsCached(t *testing.T) {
channelID := "1234"
channelName := "random channel"

mockClient := mockClient{}
mockClient.On("GetConversations", &slack.GetConversationsParameters{
ExcludeArchived: true,
Cursor: "",
Types: []string{"private_channel", "public_channel"},
Limit: 1000,
}).Return(
[]slack.Channel{
{
GroupConversation: slack.GroupConversation{
Conversation: slack.Conversation{ID: channelID},
Name: channelName,
},
},
},
"",
nil,
).Once() // Expect only one call to GetConversations

svc := service{
client: &mockClient,
maxAttempts: 3,
initialBackoff: 2 * time.Second,
}

channel, err := svc.findSlackChannel(channelName)
assert.Nil(t, err)
assert.NotNil(t, channel)
assert.Equal(t, channelID, channel.ID)

// Call again to verify it uses the cache
cachedChannel, err := svc.findSlackChannel(channelName)
assert.Nil(t, err)
assert.NotNil(t, cachedChannel)
assert.Equal(t, channelID, cachedChannel.ID)

mockClient.AssertExpectations(t)
}