Files
houston-be/internal/processor/action/start_incident_modal_submission_action.go
Shashank Shekhar 36d590221c INFRA-2829 | Implemented transaction in create incident flow (#371)
* INFRA-2829 | Implemented transaction in create incident flow

* INFRA-2829 | created util func for rollback

* INFRA-2829 | removed redundant cod to create slack channel
2024-02-16 14:47:22 +05:30

321 lines
11 KiB
Go

package action
import (
"encoding/json"
"fmt"
"gorm.io/gorm"
"houston/appcontext"
"houston/common/metrics"
"houston/common/util"
"houston/internal/processor/action/view"
"houston/logger"
"houston/model/incident"
"houston/model/severity"
"houston/model/team"
conference2 "houston/pkg/conference"
"houston/pkg/slackbot"
"houston/service/conference"
incidentService "houston/service/incident"
incidentServiceImpl "houston/service/incident/impl"
request "houston/service/request/incident"
"strings"
"time"
"github.com/slack-go/slack"
"github.com/slack-go/slack/socketmode"
"github.com/spf13/viper"
"go.uber.org/zap"
incidentHelper "houston/common/util"
)
type CreateIncidentAction struct {
client *socketmode.Client
incidentRepository *incident.Repository
slackbotClient *slackbot.Client
teamRepository *team.Repository
severityRepository *severity.Repository
db *gorm.DB
}
func NewCreateIncidentProcessor(client *socketmode.Client, incidentService *incident.Repository, teamService *team.Repository, severityService *severity.Repository, slackbotClient *slackbot.Client, db *gorm.DB) *CreateIncidentAction {
return &CreateIncidentAction{
client: client,
incidentRepository: incidentService,
teamRepository: teamService,
severityRepository: severityService,
slackbotClient: slackbotClient,
db: db,
}
}
func (isp *CreateIncidentAction) CreateIncidentModalCommandProcessing(callback slack.InteractionCallback, request *socketmode.Request) {
// Build create incident request
createIncidentRequest := buildCreateIncidentDTO(callback)
logger.Info("[CIP] incident request created", zap.Any("request", createIncidentRequest))
// Save the incident to the database
tx := isp.db.Begin()
defer util.RollbackTransaction(tx)
incidentEntity, err := isp.incidentRepository.CreateIncidentEntity(createIncidentRequest, tx)
if err != nil {
logger.Error("[CIP] Error while creating incident", zap.Error(err))
tx.Rollback()
return
}
channel, err := appcontext.GetSlackService().CreateSlackChannel(incidentEntity.ID)
if err != nil {
logger.Error("[CIP] Error while creating incident channel", zap.Error(err))
tx.Rollback()
return
}
tx.Commit()
incidentEntity.SlackChannel = channel.ID
incidentEntity.IncidentName = channel.Name
err = isp.incidentRepository.UpdateIncident(incidentEntity)
if err != nil {
logger.Error(fmt.Sprintf("[CIP] failed to update the slack channel name for incident-id: %v", incidentEntity.ID))
return
}
teamEntity, severityEntity, incidentStatusEntity, err := isp.getTeamAndSeverityAndStatus(incidentEntity.TeamId,
incidentEntity.SeverityId, incidentEntity.Status)
if err != nil {
logger.Error("[CIP] failed while getting team, severity and status", zap.Error(err))
return
}
topic := fmt.Sprintf("%s-%s(%s) Incident-%d | %s", teamEntity.Name, severityEntity.Name, severityEntity.Description, incidentEntity.ID, incidentEntity.Title)
isp.slackbotClient.SetChannelTopic(channel.ID, topic)
go func() {
// Post incident summary to Blaze Group channel and incident channel
_, err := isp.postIncidentSummary(callback.View.PrivateMetadata, channel.ID, incidentEntity, teamEntity,
severityEntity, incidentStatusEntity)
if err != nil {
logger.Error("[CIP] error while posting incident summary", zap.Error(err))
return
}
//Add user who created the incident
isp.slackbotClient.InviteUsersToConversation(incidentEntity.SlackChannel, incidentEntity.CreatedBy)
// add default users to the incident
err = isp.addDefaultUsersToIncident(channel.ID, teamEntity, severityEntity)
if err != nil {
logger.Error("[CIP] error while adding default users to incident", zap.Error(err))
}
if len(strings.TrimSpace(teamEntity.OncallHandle)) > 0 {
incidentHelper.TagPseOrDevOncallToIncident(channel.ID, severityEntity, teamEntity, isp.slackbotClient, isp.client)
}
err = incidentHelper.AssignResponderToIncident(
isp.incidentRepository,
incidentEntity,
teamEntity, severityEntity,
isp.client,
incidentEntity.CreatedBy,
)
if err != nil {
logger.Error("[CIP] error while assigning responder to the incident ", zap.Error(err))
}
err = incidentService.PostIncidentSLAMessageToChannel(incidentEntity, isp.severityRepository, isp.client)
if err != nil {
return
}
if viper.GetBool("ENABLE_CONFERENCE") {
calendarActions := conference2.GetCalendarActions()
calendarService := conference.NewCalendarService(calendarActions)
calendarEvent, err := calendarService.CreateEvent(incidentEntity.IncidentName)
if err != nil {
logger.Error(fmt.Sprintf("Unable to create conference event due to error : %s", err.Error()))
} else {
util.UpdateIncidentWithConferenceDetails(incidentEntity, calendarEvent, isp.incidentRepository)
msgUpdate := NewIncidentChannelMessageUpdateAction(isp.client, isp.incidentRepository, isp.teamRepository, isp.severityRepository)
blazeMessageUpdate := msgUpdate
msgUpdate.ProcessAction(incidentEntity.SlackChannel)
blazeMessageUpdate.ProcessAction(callback.Channel.ID)
bookmarkParam := slack.AddBookmarkParameters{Link: calendarEvent.ConferenceLink, Title: calendarService.GetConferenceTitle()}
_, err := isp.client.AddBookmark(channel.ID, bookmarkParam)
if err != nil {
logger.Error(fmt.Sprintf("Unable to add conference link as bookmark for channel %s due to error: %s", incidentEntity.SlackChannel, err.Error()))
}
msgOption := slack.MsgOptionText(fmt.Sprintf(util.ConferenceMessage, calendarEvent.ConferenceLink), false)
_, _, err = isp.client.PostMessage(channel.ID, msgOption)
if err != nil {
logger.Error(fmt.Sprintf("Unable to post message to channel %s due to error: %s", incidentEntity.SlackChannel, err.Error()))
}
}
}
}()
// Acknowledge the interaction callback
var payload interface{}
isp.client.Ack(*request, payload)
go func() {
if len(teamEntity.WebhookSlackChannel) == 0 {
return
}
msg := fmt.Sprintf("*<@%s>* started an Incident\n `%s(%s) %s` :slack: <#%s>: %s",
incidentEntity.CreatedBy, severityEntity.Name, severityEntity.Description, teamEntity.Name, incidentEntity.SlackChannel, incidentEntity.Title)
msgOption := slack.MsgOptionText(msg, false)
_, _, errMessage := isp.client.PostMessage(teamEntity.WebhookSlackChannel, msgOption)
if errMessage != nil {
logger.Error("post response failed for ResolveIncident", zap.Error(errMessage))
return
}
}()
}
func (isp *CreateIncidentAction) CreateIncidentModalCommandProcessingV2(
callback slack.InteractionCallback,
request *socketmode.Request,
) {
// Build create incident request
createIncidentRequest, err := buildCreateIncidentRequestV2(callback)
if err != nil {
logger.Error("[CIP] Error in building CreateIncidentRequestV2", zap.Error(err))
return
}
logger.Info("[CIP] incident request created", zap.Any("request", createIncidentRequest))
service := incidentServiceImpl.NewIncidentServiceV2(isp.db)
_, err = service.CreateIncident(*createIncidentRequest, "SLACK", callback.View.PrivateMetadata)
if err != nil {
logger.Error("[CIP] Error while creating incident", zap.Error(err))
metrics.PublishIncidentCreationFailureMetric()
return
}
// Acknowledge the interaction callback
var payload interface{}
isp.client.Ack(*request, payload)
}
func (isp *CreateIncidentAction) addDefaultUsersToIncident(channelId string, teamEntity *team.TeamEntity,
severityEntity *severity.SeverityEntity) error {
var userIdList []string
userIdList = append(append(userIdList, teamEntity.SlackUserIds...), severityEntity.SlackUserIds...)
for _, o := range userIdList {
isp.slackbotClient.InviteUsersToConversation(channelId, o)
}
return nil
}
func (isp *CreateIncidentAction) postIncidentSummary(blazeGroupChannelID, incidentChannelID string,
incidentEntity *incident.IncidentEntity, teamEntity *team.TeamEntity, severityEntity *severity.SeverityEntity,
incidentStatusEntity *incident.IncidentStatusEntity) (*string, error) {
// Post incident summary to Blaze Group channel and incident channel
blocks := view.IncidentSummarySection(incidentEntity, teamEntity, severityEntity, incidentStatusEntity)
color := util.GetColorBySeverity(incidentEntity.SeverityId)
att := slack.Attachment{Blocks: blocks, Color: color}
_, timestamp, err := isp.client.PostMessage(blazeGroupChannelID, slack.MsgOptionAttachments(att))
if err == nil {
err := isp.incidentRepository.CreateIncidentChannelEntry(&incident.CreateIncidentChannelEntry{
SlackChannel: blazeGroupChannelID,
MessageTimeStamp: timestamp,
IncidentId: incidentEntity.ID,
})
if err != nil {
return nil, err
}
} else {
return nil, fmt.Errorf("[CIP] error in saving message %v", err)
}
_, timestamp, err = isp.client.PostMessage(incidentChannelID, slack.MsgOptionAttachments(att))
if err == nil {
isp.incidentRepository.CreateIncidentChannelEntry(&incident.CreateIncidentChannelEntry{
SlackChannel: incidentChannelID,
MessageTimeStamp: timestamp,
IncidentId: incidentEntity.ID,
})
} else {
return nil, fmt.Errorf("[CIP] error in saving message %v", err)
}
return &timestamp, nil
}
func (isp *CreateIncidentAction) getTeamAndSeverityAndStatus(teamId, severityId, status uint) (*team.TeamEntity,
*severity.SeverityEntity, *incident.IncidentStatusEntity, error) {
teamEntity, err := isp.teamRepository.FindTeamById(teamId)
if err != nil || teamEntity == nil {
return nil, nil, nil, err
}
severityEntity, err := isp.severityRepository.FindSeverityById(severityId)
if err != nil || severityEntity == nil {
return nil, nil, nil, err
}
incidentStatusEntity, err := isp.incidentRepository.FindIncidentStatusById(status)
if err != nil || incidentStatusEntity == nil {
return nil, nil, nil, err
}
return teamEntity, severityEntity, incidentStatusEntity, nil
}
func buildCreateIncidentDTO(callback slack.InteractionCallback) *incident.CreateIncidentDTO {
blockActions := callback.View.State.Values
var createIncidentDTO incident.CreateIncidentDTO
var requestMap = make(map[string]string, 0)
for _, actions := range blockActions {
for actionID, a := range actions {
if string(a.Type) == string(slack.METPlainTextInput) {
requestMap[actionID] = a.Value
}
if string(a.Type) == slack.OptTypeStatic {
requestMap[actionID] = a.SelectedOption.Value
}
}
}
desRequestMap, _ := json.Marshal(requestMap)
json.Unmarshal(desRequestMap, &createIncidentDTO)
createIncidentDTO.Status = incident.Investigating
createIncidentDTO.StartTime = time.Now()
createIncidentDTO.EnableReminder = false
createIncidentDTO.CreatedBy = callback.User.ID
createIncidentDTO.UpdatedBy = callback.User.ID
return &createIncidentDTO
}
func buildCreateIncidentRequestV2(callback slack.InteractionCallback) (*request.CreateIncidentRequestV2, error) {
blockActions := callback.View.State.Values
var createIncidentRequest request.CreateIncidentRequestV2
var requestMap = make(map[string]string)
for _, actions := range blockActions {
for actionID, a := range actions {
if string(a.Type) == string(slack.METPlainTextInput) {
requestMap[actionID] = a.Value
}
if string(a.Type) == slack.OptTypeStatic {
requestMap[actionID] = a.SelectedOption.Value
}
}
}
desRequestMap, _ := json.Marshal(requestMap)
err := json.Unmarshal(desRequestMap, &createIncidentRequest)
if err != nil {
return nil, err
}
createIncidentRequest.CreatedBy = callback.User.ID
return &createIncidentRequest, nil
}