* INFRA-2829 | Implemented transaction in create incident flow * INFRA-2829 | created util func for rollback * INFRA-2829 | removed redundant cod to create slack channel
321 lines
11 KiB
Go
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 ×tamp, 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
|
|
}
|