INFRA-3151 : Add unarchival listener to add houston bot to incident slack channel (#416)

* INFRA-3151 : Add unarchival listener to add houston bot to incident slack channel

* INFRA-3151 : review comments

* INFRA-3151 : format fix
This commit is contained in:
Vijay Joshi
2024-04-12 18:53:06 +05:30
committed by GitHub
parent 602db7741c
commit 0ed941abc3
12 changed files with 158 additions and 8 deletions

View File

@@ -33,14 +33,15 @@ import (
)
type slackHandler struct {
socketModeClient *socketmode.Client
slashCommandProcessor *processor.SlashCommandProcessor
commandProcessor processor.CommandProcessor
memberJoinCallbackProcessor *processor.MemberJoinedCallbackEventProcessor
blockActionProcessor *processor.BlockActionProcessor
viewSubmissionProcessor *processor.ViewSubmissionProcessor
userChangeEventProcessor *processor.UserChangeEventProcessor
houstonCommandResolver *resolver.HoustonCommandResolver
socketModeClient *socketmode.Client
slashCommandProcessor *processor.SlashCommandProcessor
commandProcessor processor.CommandProcessor
memberJoinCallbackProcessor *processor.MemberJoinedCallbackEventProcessor
channelUnarchivalEventProcessor *processor.ChannelUnarchivalEventProcessor
blockActionProcessor *processor.BlockActionProcessor
viewSubmissionProcessor *processor.ViewSubmissionProcessor
userChangeEventProcessor *processor.UserChangeEventProcessor
houstonCommandResolver *resolver.HoustonCommandResolver
}
func NewSlackHandler(
@@ -74,6 +75,7 @@ func NewSlackHandler(
memberJoinCallbackProcessor: processor.NewMemberJoinedCallbackEventProcessor(
socketModeClient, incidentService, teamService, severityService,
),
channelUnarchivalEventProcessor: processor.NewChannelUnarchivalEventProcessor(),
blockActionProcessor: processor.NewBlockActionProcessor(
socketModeClient,
incidentService,
@@ -149,6 +151,10 @@ func (sh *slackHandler) HoustonConnect() {
switch innerEvent.Type {
case util.UserChangeEvent:
sh.userChangeEventProcessor.ProcessCommand(ev, evt.Request)
case util.ChannelUnarchiveEvent:
sh.channelUnarchivalEventProcessor.ProcessCommand(
innerEvent.Data.(*slackevents.ChannelUnarchiveEvent), *evt.Request, sh.socketModeClient,
)
}
switch innerEventData := innerEvent.Data.(type) {

View File

@@ -49,6 +49,7 @@ const (
const (
UserChangeEvent = "user_change"
ChannelUnarchiveEvent = "channel_unarchive"
DeactivatedUserName = "Deactivated User"
IsArchivedError = "is_archived"
NotInChannelError = "not_in_channel"

View File

@@ -0,0 +1,33 @@
package processor
import (
"fmt"
"github.com/slack-go/slack/slackevents"
"github.com/slack-go/slack/socketmode"
"houston/appcontext"
"houston/common/metrics"
"houston/logger"
"houston/service/incident"
)
type ChannelUnarchivalEventProcessor struct {
incidentService incident.IIncidentService
}
func NewChannelUnarchivalEventProcessor() *ChannelUnarchivalEventProcessor {
return &ChannelUnarchivalEventProcessor{
incidentService: appcontext.GetIncidentService(),
}
}
func (processor *ChannelUnarchivalEventProcessor) ProcessCommand(
event *slackevents.ChannelUnarchiveEvent, request socketmode.Request, client *socketmode.Client,
) {
err := processor.incidentService.AddBotUponChannelUnarchival(event.Channel)
if err != nil {
logger.Error(fmt.Sprint("Error in processing channel unarchival: ", err))
metrics.PublishHoustonFlowFailureMetrics("CHANNEL_UNARCHIVAL_EVENT", err.Error())
}
client.Ack(request)
}

View File

@@ -61,6 +61,24 @@ func (r *Repository) GetUnArchivedChannelsForTerminalIncidents() ([]IncidentChan
return incidentChannels, nil
}
func (r *Repository) GetIncidentChannelBySlackChannelId(channelId string) (*IncidentChannelEntity, error) {
var incidentChannel *IncidentChannelEntity
query := r.gormClient.
Table("incident_channel").
Where("incident_channel.slack_channel = ?", channelId).
Joins("JOIN incident ON incident_channel.slack_channel = incident.slack_channel")
if err := query.First(&incidentChannel).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, err
}
return incidentChannel, nil
}
func (r *Repository) UpdateIncidentChannelArchivalStatuses(incidentChannelIds []uint, archivalStatus bool) error {
err := r.gormClient.
Model(&IncidentChannelEntity{}).

View File

@@ -3,5 +3,6 @@ package incident_channel
type IncidentChannelRepositoryInterface interface {
GetIncidentChannels(incidentID uint) ([]IncidentChannelEntity, error)
GetUnArchivedChannelsForTerminalIncidents() ([]IncidentChannelEntity, error)
GetIncidentChannelBySlackChannelId(channelId string) (*IncidentChannelEntity, error)
UpdateIncidentChannelArchivalStatuses(incidentChannelIds []uint, archivalStatus bool) error
}

View File

@@ -0,0 +1,25 @@
package impl
import (
"fmt"
"houston/logger"
)
func (i *IncidentServiceV2) AddBotUponChannelUnarchival(channelID string) error {
const logTag = "[incident-service-add-bot-upon-channel-unarchival]"
logger.Info(fmt.Sprintf("%s received request to add bot upon channel unarchival for channel: %s", logTag, channelID))
isIncidentChannel := i.incidentChannelService.IsIncidentChannel(channelID)
if !isIncidentChannel {
logger.Info(fmt.Sprintf("%s Channel: %s is not an incident channel", logTag, channelID))
return nil
}
_, _, _, err := i.slackService.JoinConversation(channelID)
if err != nil {
logger.Error(fmt.Sprintf("%s failed to join channel %s %v", logTag, channelID, err))
return err
}
return nil
}

View File

@@ -0,0 +1,23 @@
package impl
import "errors"
func (suite *IncidentServiceSuite) Test_AddBotUponChannelUnarchival_NonIncidentChannelCase() {
suite.incidentChannelService.IsIncidentChannelMock.Return(false)
err := suite.incidentService.AddBotUponChannelUnarchival("123")
suite.NoError(err, "service must not return error")
}
func (suite *IncidentServiceSuite) Test_AddBotUponChannelUnarchival_JoinConversationFailureCase() {
suite.incidentChannelService.IsIncidentChannelMock.Return(true)
suite.slackService.JoinConversationMock.Return(nil, "", []string{}, errors.New("error"))
err := suite.incidentService.AddBotUponChannelUnarchival("123")
suite.Error(err, "service must return error")
}
func (suite *IncidentServiceSuite) Test_AddBotUponChannelUnarchival_SuccessCase() {
suite.incidentChannelService.IsIncidentChannelMock.Return(true)
suite.slackService.JoinConversationMock.Return(nil, "", []string{}, nil)
err := suite.incidentService.AddBotUponChannelUnarchival("123")
suite.NoError(err, "service must not return error")
}

View File

@@ -40,4 +40,5 @@ type IIncidentService interface {
requestType string,
) error
FetchIncidentsApproachingSlaBreach() ([]incident.IncidentDTO, error)
AddBotUponChannelUnarchival(channelID string) error
}

View File

@@ -42,3 +42,21 @@ func (i *IncidentChannelService) GetIncidentChannels(
}
return incidentChannelEntities, nil
}
func (i *IncidentChannelService) getIncidentChannelBySlackChannelId(channelId string) (*incident_channel.IncidentChannelEntity, error) {
incidentChannel, err := i.incidentChannelRepository.GetIncidentChannelBySlackChannelId(channelId)
if err != nil {
logger.Error(fmt.Sprintf("failed to get incident channel for channel id %s %v", channelId, err))
return nil, err
}
return incidentChannel, nil
}
func (i *IncidentChannelService) IsIncidentChannel(channelID string) bool {
incidentChannel, err := i.getIncidentChannelBySlackChannelId(channelID)
if err != nil {
return false
}
return incidentChannel != nil
}

View File

@@ -5,4 +5,5 @@ import "houston/model/incident_channel"
type IIncidentChannelService interface {
GetIncidentChannels(incidentID uint) ([]incident_channel.IncidentChannelEntity, error)
ArchiveIncidentChannels() error
IsIncidentChannel(channelID string) bool
}

View File

@@ -34,6 +34,28 @@ func (suite *IncidentChannelServiceSuite) Test_GetIncidentChannels_SuccessCase()
suite.Equal(incidentChannels, result)
}
func (suite *IncidentChannelServiceSuite) Test_IsIncidentChannel_RepositoryFailureCase() {
channelId := "123"
suite.incidentChannelRepository.GetIncidentChannelBySlackChannelIdMock.Return(nil, errors.New("error"))
result := suite.incidentChannelService.IsIncidentChannel(channelId)
suite.False(result, "should return false")
}
func (suite *IncidentChannelServiceSuite) Test_IsIncidentChannel_NilIncidentChannelCase() {
channelId := "123"
suite.incidentChannelRepository.GetIncidentChannelBySlackChannelIdMock.Return(nil, nil)
result := suite.incidentChannelService.IsIncidentChannel(channelId)
suite.False(result, "should return false")
}
func (suite *IncidentChannelServiceSuite) Test_IsIncidentChannel_ValidIncidentChannelCase() {
channelId := "123"
incidentChannel := GetMockIncidentChannel(1, 1, channelId, false)
suite.incidentChannelRepository.GetIncidentChannelBySlackChannelIdMock.Return(&incidentChannel, nil)
result := suite.incidentChannelService.IsIncidentChannel(channelId)
suite.True(result, "should return true")
}
func GetMockIncidentChannel(id, incidentId uint, slackChannel string, isArchived bool) incident_channel.IncidentChannelEntity {
incidentChannel := incident_channel.IncidentChannelEntity{
IncidentId: incidentId,

View File

@@ -50,4 +50,5 @@ type ISlackService interface {
PostDivider(channelId string) error
ArchiveConversation(channelId string) error
GetAllUsers() ([]slack.User, error)
JoinConversation(channelID string) (*slack.Channel, string, []string, error)
}