Release 1 (#209)
* TP-43474 | Fixing time difference calculation issue (#193) * TP-43298 | Adding SLA messagae to incident channel below incident summary (#183) * TP-43298 | Resolving merge conflicts * TP-43298 | Updating serverity check condition * TP-43625 | Fixing user id check (#201) * TP-43625 | Fixing user id check * TP-43625 | Fixing user id check * Incident service v2 (#191) * TP-43339 | create-incident-v2 * TP-43339 | create-incident-v2 handler * TP-43339 | added logs and some optimiations * TP-43339 | added slack processor v2 * TP-43339 | create-incident-v2 integration with slack * TP-43339 | adding feature flag for create-incident-v2 * TP-43339 | removed redundant entity fetch * TP-43339 | Added SLA related changes to create-incident-v2 * TP-43339 | Posting incident summary to blazeGroup channel and improved logs * TP-43339 | Moving post summary method to the incident service file * TP-43339 | Removed socketModeClient usage from incident-service-v2, moved it to slack-service * TP-43339 | Updated env variable for create-incident-v2 --------- Co-authored-by: Sriram Bhargav <sriram.bhargav@navi.com>
This commit is contained in:
53
cmd/app/handler/incident_handler.go
Normal file
53
cmd/app/handler/incident_handler.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
"houston/service/incident"
|
||||
request "houston/service/request"
|
||||
common "houston/service/response/common"
|
||||
utils "houston/service/utils"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
logTag = "[incident_handler]"
|
||||
)
|
||||
|
||||
type IncidentHandler struct {
|
||||
gin *gin.Engine
|
||||
logger *zap.Logger
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewIncidentHandler(gin *gin.Engine, logger *zap.Logger, db *gorm.DB) *IncidentHandler {
|
||||
return &IncidentHandler{
|
||||
gin: gin,
|
||||
logger: logger,
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *IncidentHandler) HandleCreateIncident(c *gin.Context) {
|
||||
var createIncidentRequest request.CreateIncidentRequestV2
|
||||
if err := c.ShouldBindJSON(&createIncidentRequest); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := utils.ValidateCreateIncidentRequestV2(createIncidentRequest); err != nil {
|
||||
h.logger.Error(fmt.Sprintf("%s Received invalid request to create new incident", logTag), zap.Error(err))
|
||||
c.JSON(http.StatusBadRequest, common.ErrorResponse(err, http.StatusBadRequest, nil))
|
||||
return
|
||||
}
|
||||
incidentServiceV2 := incident.NewIncidentServiceV2(h.logger, h.db)
|
||||
incidentResponse, err := incidentServiceV2.CreateIncident(createIncidentRequest, "API", "")
|
||||
if err != nil {
|
||||
h.logger.Error(fmt.Sprintf("%s Failed to create incident", logTag), zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, common.ErrorResponse(err, http.StatusInternalServerError, nil))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, common.SuccessResponse(incidentResponse, http.StatusOK))
|
||||
}
|
||||
@@ -59,7 +59,7 @@ func NewSlackHandler(logger *zap.Logger, gormClient *gorm.DB, socketModeClient *
|
||||
logger, socketModeClient, incidentService, teamService, severityService, tagService, slackbotClient,
|
||||
),
|
||||
viewSubmissionProcessor: processor.NewViewSubmissionProcessor(
|
||||
logger, socketModeClient, incidentService, teamService, severityService, tagService, teamService, slackbotClient,
|
||||
logger, socketModeClient, incidentService, teamService, severityService, tagService, teamService, slackbotClient, gormClient,
|
||||
),
|
||||
slashCommandResolver: resolver.NewSlashCommandResolver(
|
||||
logger, diagnosticCommandProcessor, slashCommandProcessor,
|
||||
|
||||
@@ -36,6 +36,7 @@ func NewServer(gin *gin.Engine, logger *zap.Logger, db *gorm.DB, mjolnirClient *
|
||||
func (s *Server) Handler(houstonGroup *gin.RouterGroup) {
|
||||
s.readinessHandler(houstonGroup)
|
||||
s.incidentClientHandler(houstonGroup)
|
||||
s.incidentClientHandlerV2(houstonGroup)
|
||||
s.filtersHandlerV2(houstonGroup)
|
||||
s.gin.Use(s.createMiddleware())
|
||||
s.teamHandler(houstonGroup)
|
||||
@@ -110,6 +111,18 @@ func (s *Server) incidentClientHandler(houstonGroup *gin.RouterGroup) {
|
||||
houstonGroup.POST("/create-incident", incidentHandler.CreateIncident)
|
||||
}
|
||||
|
||||
func (s *Server) incidentClientHandlerV2(houstonGroup *gin.RouterGroup) {
|
||||
houstonGroup.Use(func(c *gin.Context) {
|
||||
// Add your desired header key-value pair
|
||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Headers", viper.GetString("allowed.custom.headers"))
|
||||
origin := c.Request.Header.Get("Origin")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
|
||||
})
|
||||
incidentHandler := handler.NewIncidentHandler(s.gin, s.logger, s.db)
|
||||
houstonGroup.POST("/create-incident-v2", incidentHandler.HandleCreateIncident)
|
||||
}
|
||||
|
||||
func (s *Server) incidentHandler(houstonGroup *gin.RouterGroup) {
|
||||
houstonClient := NewHoustonClient(s.logger)
|
||||
incidentHandler := service.NewIncidentService(s.gin, s.logger, s.db, houstonClient.socketModeClient)
|
||||
|
||||
11
cmd/main.go
11
cmd/main.go
@@ -34,9 +34,14 @@ func main() {
|
||||
r.Use(ginzap.RecoveryWithZap(logger, true))
|
||||
|
||||
houston := r.Group("/houston")
|
||||
db := postgres.NewGormClient(viper.GetString("postgres.dsn"), viper.GetString("postgres.connection.max.idle.time"),
|
||||
viper.GetString("postgres.connection.max.lifetime"), viper.GetInt("postgres.connections.max.idle"),
|
||||
viper.GetInt("postgres.connections.max.open"), logger)
|
||||
db := postgres.NewGormClient(
|
||||
viper.GetString("postgres.dsn"),
|
||||
viper.GetString("postgres.connection.max.idle.time"),
|
||||
viper.GetString("postgres.connection.max.lifetime"),
|
||||
viper.GetInt("postgres.connections.max.idle"),
|
||||
viper.GetInt("postgres.connections.max.open"),
|
||||
logger,
|
||||
)
|
||||
httpClient := clients.NewHttpClient()
|
||||
mjolnirClient := clients.NewMjolnirClient(httpClient.HttpClient, logger,
|
||||
viper.GetString("mjolnir.service.url"), viper.GetString("mjolnir.realm.id"),
|
||||
|
||||
@@ -5,7 +5,13 @@ import (
|
||||
"fmt"
|
||||
"github.com/slack-go/slack"
|
||||
"github.com/slack-go/slack/socketmode"
|
||||
"go.uber.org/zap"
|
||||
"houston/model/incident"
|
||||
"houston/model/severity"
|
||||
"houston/model/team"
|
||||
request "houston/service/request"
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GetColorBySeverity(severityId uint) string {
|
||||
@@ -79,3 +85,45 @@ func RemoveDuplicateStr(strSlice []string) []string {
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func PostIncidentSeverityEscalationHeadsUpMessage(severities *[]severity.SeverityEntity, incidentEntity *incident.IncidentEntity, teamEntity *team.TeamEntity, client *socketmode.Client) {
|
||||
fromSeverityName := getSeverityById(severities, incidentEntity.SeverityId)
|
||||
toSeverityName := getSeverityById(severities, incidentEntity.SeverityId-1)
|
||||
daysToEscalation := calculateDifferenceInDays(incidentEntity.SeverityTat, time.Now())
|
||||
teamSlackChannel := teamEntity.WebhookSlackChannel
|
||||
if daysToEscalation != 0 {
|
||||
msgOption := slack.MsgOptionText(fmt.Sprintf("This incident will be auto-escalated to `%v` in `%v day(s)`", toSeverityName, daysToEscalation), false)
|
||||
_, _, err := client.PostMessage(incidentEntity.SlackChannel, msgOption)
|
||||
if err != nil {
|
||||
logger.Info(fmt.Sprintf("Error posting message to incident channel for incident %v", incidentEntity.IncidentName), zap.Error(err))
|
||||
return
|
||||
}
|
||||
if teamSlackChannel != "" {
|
||||
msgOption := slack.MsgOptionText(fmt.Sprintf("<#%s> (`%v`) will be auto-escalated to `%v` in `%v day(s)`", incidentEntity.SlackChannel, fromSeverityName, toSeverityName, daysToEscalation), false)
|
||||
_, _, err := client.PostMessage(teamSlackChannel, msgOption)
|
||||
if err != nil {
|
||||
logger.Info(fmt.Sprintf("Error posting message to team channel for incident %v", incidentEntity.IncidentName), zap.Error(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
func getSeverityById(severities *[]severity.SeverityEntity, severityId uint) string {
|
||||
for _, severityEntity := range *severities {
|
||||
if severityEntity.ID == severityId {
|
||||
return severityEntity.Name
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func calculateDifferenceInDays(fromTime, toTime time.Time) int {
|
||||
fromDate := time.Date(fromTime.Year(), fromTime.Month(), fromTime.Day(), 0, 0, 0, 0, time.UTC)
|
||||
toDate := time.Date(toTime.Year(), toTime.Month(), toTime.Day(), 0, 0, 0, 0, time.UTC)
|
||||
return int(math.Abs(toDate.Sub(fromDate).Hours() / 24))
|
||||
}
|
||||
func PostMessageToIncidentChannel(message string, channelId string, client *socketmode.Client) error {
|
||||
msgOption := slack.MsgOptionText(message, false)
|
||||
_, _, errMessage := client.PostMessage(channelId, msgOption)
|
||||
return errMessage
|
||||
}
|
||||
|
||||
11
common/util/datetime_util.go
Normal file
11
common/util/datetime_util.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package util
|
||||
|
||||
import "time"
|
||||
|
||||
func GetCurrentTime() int64 {
|
||||
timeInIst, err := time.LoadLocation("Asia/Kolkata")
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return time.Now().In(timeInIst).UnixNano() / 1e6
|
||||
}
|
||||
18
common/util/json_util.go
Normal file
18
common/util/json_util.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
func JsonToStruct[JsonObject any, Struct any](obj JsonObject, result *Struct) error {
|
||||
messageBytes, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(messageBytes, &result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -69,4 +69,6 @@ grafana.token=GRAFANA_TOKEN
|
||||
config.sa.keys=CONFIG_SA_KEYS
|
||||
|
||||
create-incident.description.max-length=500
|
||||
create-incident.title.max-length=100
|
||||
create-incident.title.max-length=100
|
||||
|
||||
create-incident-v2-enabled=CREATE_INCIDENT_V2_ENABLED
|
||||
3
go.mod
3
go.mod
@@ -18,7 +18,6 @@ require (
|
||||
github.com/spf13/viper v1.16.0
|
||||
github.com/thoas/go-funk v0.9.3
|
||||
go.uber.org/zap v1.24.0
|
||||
gorm.io/datatypes v1.2.0
|
||||
gorm.io/driver/postgres v1.5.2
|
||||
gorm.io/gorm v1.25.2
|
||||
)
|
||||
@@ -46,7 +45,6 @@ require (
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/chromedp/sysutil v1.0.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/gobwas/ws v1.1.0 // indirect
|
||||
@@ -66,7 +64,6 @@ require (
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||
google.golang.org/grpc v1.55.0 // indirect
|
||||
gorm.io/driver/mysql v1.4.7 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@@ -22,8 +22,13 @@ type IncidentChannelMessageUpdateAction struct {
|
||||
severityService *severity.Repository
|
||||
}
|
||||
|
||||
func NewIncidentChannelMessageUpdateAction(socketModeClient *socketmode.Client, logger *zap.Logger,
|
||||
incidentService *incident.Repository, teamService *team.Repository, severityService *severity.Repository) *IncidentChannelMessageUpdateAction {
|
||||
func NewIncidentChannelMessageUpdateAction(
|
||||
socketModeClient *socketmode.Client,
|
||||
logger *zap.Logger,
|
||||
incidentService *incident.Repository,
|
||||
teamService *team.Repository,
|
||||
severityService *severity.Repository,
|
||||
) *IncidentChannelMessageUpdateAction {
|
||||
return &IncidentChannelMessageUpdateAction{
|
||||
socketModeClient: socketModeClient,
|
||||
logger: logger,
|
||||
|
||||
@@ -4,12 +4,15 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gorm.io/gorm"
|
||||
"houston/common/util"
|
||||
"houston/internal/processor/action/view"
|
||||
"houston/model/incident"
|
||||
"houston/model/severity"
|
||||
"houston/model/team"
|
||||
"houston/pkg/slackbot"
|
||||
incidentService "houston/service/incident"
|
||||
request "houston/service/request"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -30,10 +33,18 @@ type CreateIncidentAction struct {
|
||||
slackbotClient *slackbot.Client
|
||||
teamRepository *team.Repository
|
||||
severityRepository *severity.Repository
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewCreateIncidentProcessor(client *socketmode.Client, logger *zap.Logger, incidentService *incident.Repository,
|
||||
teamService *team.Repository, severityService *severity.Repository, slackbotClient *slackbot.Client) *CreateIncidentAction {
|
||||
func NewCreateIncidentProcessor(
|
||||
client *socketmode.Client,
|
||||
logger *zap.Logger,
|
||||
incidentService *incident.Repository,
|
||||
teamService *team.Repository,
|
||||
severityService *severity.Repository,
|
||||
slackbotClient *slackbot.Client,
|
||||
db *gorm.DB,
|
||||
) *CreateIncidentAction {
|
||||
return &CreateIncidentAction{
|
||||
client: client,
|
||||
logger: logger,
|
||||
@@ -41,16 +52,17 @@ func NewCreateIncidentProcessor(client *socketmode.Client, logger *zap.Logger, i
|
||||
teamRepository: teamService,
|
||||
severityRepository: severityService,
|
||||
slackbotClient: slackbotClient,
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (isp *CreateIncidentAction) CreateIncidentModalCommandProcessing(callback slack.InteractionCallback, request *socketmode.Request) {
|
||||
// Build create incident request
|
||||
createIncidentRequest := buildCreateIncidentRequest(callback)
|
||||
createIncidentRequest := buildCreateIncidentDTO(callback)
|
||||
isp.logger.Info("[CIP] incident request created", zap.Any("request", createIncidentRequest))
|
||||
|
||||
// Save the incident to the database
|
||||
incidentEntity, err := isp.incidentRepository.CreateIncident(createIncidentRequest)
|
||||
incidentEntity, err := isp.incidentRepository.CreateIncidentEntity(createIncidentRequest)
|
||||
if err != nil {
|
||||
isp.logger.Error("[CIP] Error while creating incident", zap.Error(err))
|
||||
return
|
||||
@@ -104,6 +116,15 @@ func (isp *CreateIncidentAction) CreateIncidentModalCommandProcessing(callback s
|
||||
isp.logger.Error("[CIP] error while assigning responder to the incident ", zap.Error(err))
|
||||
}
|
||||
|
||||
if incidentEntity.SeverityId >= 3 && incidentEntity.Status <= 4 {
|
||||
slaDate := time.Now().AddDate(0, 0, severityEntity.Sla).Format("02 Jan 2006")
|
||||
message := fmt.Sprintf("SLA for this incident is `%v day(s)` and will be closed by `%s`", severityEntity.Sla,
|
||||
slaDate)
|
||||
err := util.PostMessageToIncidentChannel(message, incidentEntity.SlackChannel, isp.client)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if viper.GetBool("ENABLE_GMEET") {
|
||||
gmeet, err := createGmeetLink(isp.logger, *channelID)
|
||||
if err != nil {
|
||||
@@ -136,6 +157,30 @@ func (isp *CreateIncidentAction) CreateIncidentModalCommandProcessing(callback s
|
||||
}()
|
||||
}
|
||||
|
||||
func (isp *CreateIncidentAction) CreateIncidentModalCommandProcessingV2(
|
||||
callback slack.InteractionCallback,
|
||||
request *socketmode.Request,
|
||||
) {
|
||||
// Build create incident request
|
||||
createIncidentRequest, err := buildCreateIncidentRequestV2(callback)
|
||||
if err != nil {
|
||||
isp.logger.Error("[CIP] Error in building CreateIncidentRequestV2", zap.Error(err))
|
||||
return
|
||||
}
|
||||
isp.logger.Info("[CIP] incident request created", zap.Any("request", createIncidentRequest))
|
||||
|
||||
service := incidentService.NewIncidentServiceV2(isp.logger, isp.db)
|
||||
_, err = service.CreateIncident(*createIncidentRequest, "SLACK", callback.View.PrivateMetadata)
|
||||
if err != nil {
|
||||
isp.logger.Error("[CIP] Error while creating incident", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
// Acknowledge the interaction callback
|
||||
var payload interface{}
|
||||
isp.client.Ack(*request, payload)
|
||||
}
|
||||
|
||||
func createGmeetLink(logger *zap.Logger, channelName string) (string, error) {
|
||||
calclient, err := calendar.NewService(context.Background(), option.WithCredentialsFile(viper.GetString("GMEET_CONFIG_FILE_PATH")))
|
||||
if err != nil {
|
||||
@@ -220,11 +265,14 @@ func (isp *CreateIncidentAction) postIncidentSummary(blazeGroupChannelID, incide
|
||||
_, timestamp, err := isp.client.PostMessage(blazeGroupChannelID, slack.MsgOptionAttachments(att))
|
||||
|
||||
if err == nil {
|
||||
isp.incidentRepository.CreateIncidentChannelEntry(&incident.CreateIncidentChannelEntry{
|
||||
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)
|
||||
}
|
||||
@@ -262,9 +310,9 @@ func (isp *CreateIncidentAction) getTeamAndSeverityAndStatus(teamId, severityId,
|
||||
return teamEntity, severityEntity, incidentStatusEntity, nil
|
||||
}
|
||||
|
||||
func buildCreateIncidentRequest(callback slack.InteractionCallback) *incident.CreateIncidentRequest {
|
||||
func buildCreateIncidentDTO(callback slack.InteractionCallback) *incident.CreateIncidentDTO {
|
||||
blockActions := callback.View.State.Values
|
||||
var createIncidentRequest incident.CreateIncidentRequest
|
||||
var createIncidentDTO incident.CreateIncidentDTO
|
||||
var requestMap = make(map[string]string, 0)
|
||||
|
||||
for _, actions := range blockActions {
|
||||
@@ -279,13 +327,39 @@ func buildCreateIncidentRequest(callback slack.InteractionCallback) *incident.Cr
|
||||
}
|
||||
|
||||
desRequestMap, _ := json.Marshal(requestMap)
|
||||
json.Unmarshal(desRequestMap, &createIncidentRequest)
|
||||
json.Unmarshal(desRequestMap, &createIncidentDTO)
|
||||
|
||||
createIncidentRequest.Status = incident.Investigating
|
||||
createIncidentRequest.StartTime = time.Now()
|
||||
createIncidentRequest.EnableReminder = false
|
||||
createIncidentRequest.CreatedBy = callback.User.ID
|
||||
createIncidentRequest.UpdatedBy = callback.User.ID
|
||||
createIncidentDTO.Status = incident.Investigating
|
||||
createIncidentDTO.StartTime = time.Now()
|
||||
createIncidentDTO.EnableReminder = false
|
||||
createIncidentDTO.CreatedBy = callback.User.ID
|
||||
createIncidentDTO.UpdatedBy = callback.User.ID
|
||||
|
||||
return &createIncidentRequest
|
||||
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
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package processor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/viper"
|
||||
"gorm.io/gorm"
|
||||
"houston/common/util"
|
||||
"houston/internal/processor/action"
|
||||
"houston/model/incident"
|
||||
@@ -192,16 +194,25 @@ type ViewSubmissionProcessor struct {
|
||||
incidentUpdateRca *action.IncidentUpdateRcaAction
|
||||
showIncidentSubmitAction *action.ShowIncidentsSubmitAction
|
||||
incidentDuplicateAction *action.DuplicateIncidentAction
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewViewSubmissionProcessor(logger *zap.Logger, socketModeClient *socketmode.Client, incidentRepository *incident.Repository,
|
||||
teamService *team.Repository, severityService *severity.Repository, tagService *tag.Repository, teamRepository *team.Repository,
|
||||
slackbotClient *slackbot.Client) *ViewSubmissionProcessor {
|
||||
func NewViewSubmissionProcessor(
|
||||
logger *zap.Logger,
|
||||
socketModeClient *socketmode.Client,
|
||||
incidentRepository *incident.Repository,
|
||||
teamService *team.Repository,
|
||||
severityService *severity.Repository,
|
||||
tagService *tag.Repository,
|
||||
teamRepository *team.Repository,
|
||||
slackbotClient *slackbot.Client,
|
||||
db *gorm.DB,
|
||||
) *ViewSubmissionProcessor {
|
||||
return &ViewSubmissionProcessor{
|
||||
logger: logger,
|
||||
socketModeClient: socketModeClient,
|
||||
incidentChannelMessageUpdateAction: action.NewIncidentChannelMessageUpdateAction(socketModeClient, logger, incidentRepository, teamService, severityService),
|
||||
createIncidentAction: action.NewCreateIncidentProcessor(socketModeClient, logger, incidentRepository, teamService, severityService, slackbotClient),
|
||||
createIncidentAction: action.NewCreateIncidentProcessor(socketModeClient, logger, incidentRepository, teamService, severityService, slackbotClient, db),
|
||||
assignIncidentAction: action.NewAssignIncidentAction(socketModeClient, logger, incidentRepository),
|
||||
updateIncidentAction: action.NewIncidentUpdateAction(socketModeClient, logger, incidentRepository, tagService, teamService, severityService),
|
||||
incidentUpdateTitleAction: action.NewIncidentUpdateTitleAction(socketModeClient, logger, incidentRepository, teamService, severityService, slackbotClient),
|
||||
@@ -226,7 +237,12 @@ func (vsp *ViewSubmissionProcessor) ProcessCommand(callback slack.InteractionCal
|
||||
switch callbackId {
|
||||
case util.StartIncidentSubmit:
|
||||
{
|
||||
vsp.createIncidentAction.CreateIncidentModalCommandProcessing(callback, request)
|
||||
createIncidentV2Enabled := viper.GetBool("CREATE_INCIDENT_V2_ENABLED")
|
||||
if createIncidentV2Enabled {
|
||||
vsp.createIncidentAction.CreateIncidentModalCommandProcessingV2(callback, request)
|
||||
} else {
|
||||
vsp.createIncidentAction.CreateIncidentModalCommandProcessing(callback, request)
|
||||
}
|
||||
}
|
||||
case util.AssignIncidentRoleSubmit:
|
||||
{
|
||||
|
||||
@@ -25,7 +25,7 @@ func NewIncidentRepository(logger *zap.Logger, gormClient *gorm.DB, severityServ
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Repository) CreateIncident(request *CreateIncidentRequest) (*IncidentEntity, error) {
|
||||
func (r *Repository) CreateIncidentEntity(request *CreateIncidentDTO) (*IncidentEntity, error) {
|
||||
severityId, err := strconv.Atoi(request.Severity)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetch channel conversationInfo failed. err: %v", err)
|
||||
|
||||
@@ -31,7 +31,7 @@ func (j JSON) Value() (driver.Value, error) {
|
||||
return json.RawMessage(j).MarshalJSON()
|
||||
}
|
||||
|
||||
type CreateIncidentRequest struct {
|
||||
type CreateIncidentDTO struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Severity string `json:"severity,omitempty"`
|
||||
|
||||
@@ -64,3 +64,27 @@ func (authService *AuthService) checkIfManagerOrAdmin(
|
||||
}
|
||||
return strings.Contains(userRoles, string(Admin)), nil
|
||||
}
|
||||
|
||||
func (authService *AuthService) checkIfAdminOrTeamMember(context *gin.Context, teamMembers []string) (bool, error) {
|
||||
sessionToken := context.Request.Header.Get("X-Session-Token")
|
||||
userEmail := context.Request.Header.Get("X-User-Email")
|
||||
sessionResponse, err := authService.mjolnirClient.GetSessionResponse(sessionToken)
|
||||
if err != nil || sessionResponse.StatusCode == 401 {
|
||||
authService.logger.Error(fmt.Sprintf("error occurred while getting session data from mjolnir for %v", userEmail), zap.Error(err))
|
||||
return false, nil
|
||||
}
|
||||
if sessionResponse.EmailId != userEmail {
|
||||
authService.logger.Error(fmt.Sprintf("user email: %v does not match the email linked to the session token", userEmail))
|
||||
return false, nil
|
||||
}
|
||||
if teamMembers == nil || len(teamMembers) == 0 {
|
||||
return strings.Contains(strings.Join(sessionResponse.Roles, ","), string(Admin)), nil
|
||||
}
|
||||
userInfo, err := authService.slackClient.GetUserByEmail(userEmail)
|
||||
if err != nil {
|
||||
authService.logger.Error(fmt.Sprintf("User with email %v was not found in slack", userEmail), zap.Error(err))
|
||||
return false, nil
|
||||
}
|
||||
return strings.Contains(strings.Join(sessionResponse.Roles, ","), string(Admin)) ||
|
||||
strings.Contains(strings.Join(teamMembers, ","), userInfo.ID), nil
|
||||
}
|
||||
|
||||
628
service/incident/incident_service_v2.go
Normal file
628
service/incident/incident_service_v2.go
Normal file
@@ -0,0 +1,628 @@
|
||||
package incident
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
slackClient "github.com/slack-go/slack"
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/api/calendar/v3"
|
||||
"google.golang.org/api/option"
|
||||
"gorm.io/gorm"
|
||||
"houston/common/util"
|
||||
"houston/internal/processor/action/view"
|
||||
"houston/model/incident"
|
||||
"houston/model/severity"
|
||||
"houston/model/team"
|
||||
"houston/model/user"
|
||||
request "houston/service/request"
|
||||
service "houston/service/response"
|
||||
"houston/service/slack"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type IncidentServiceV2 struct {
|
||||
logger *zap.Logger
|
||||
db *gorm.DB
|
||||
slackService *slack.SlackService
|
||||
teamRepository *team.Repository
|
||||
severityRepository *severity.Repository
|
||||
incidentRepository *incident.Repository
|
||||
userRepository *user.Repository
|
||||
}
|
||||
|
||||
func NewIncidentServiceV2(logger *zap.Logger, db *gorm.DB) *IncidentServiceV2 {
|
||||
teamRepository := team.NewTeamRepository(logger, db)
|
||||
severityRepository := severity.NewSeverityRepository(logger, db)
|
||||
incidentRepository := incident.NewIncidentRepository(logger, db, severityRepository)
|
||||
userRepository := user.NewUserRepository(logger, db)
|
||||
slackService := slack.NewSlackService(logger)
|
||||
return &IncidentServiceV2{
|
||||
logger: logger,
|
||||
db: db,
|
||||
slackService: slackService,
|
||||
teamRepository: teamRepository,
|
||||
severityRepository: severityRepository,
|
||||
incidentRepository: incidentRepository,
|
||||
userRepository: userRepository,
|
||||
}
|
||||
}
|
||||
|
||||
const logTag = "[create-incident-v2]"
|
||||
|
||||
/*
|
||||
gets validated request
|
||||
creates entry in DB, creates slack channel, posts incident summary, adds members, tags oncall, assigns responder
|
||||
returns error if failure otherwise nil
|
||||
*/
|
||||
|
||||
func (i *IncidentServiceV2) CreateIncident(
|
||||
request request.CreateIncidentRequestV2,
|
||||
source string,
|
||||
blazeGroupChannelID string,
|
||||
) (service.IncidentResponse, error) {
|
||||
emptyResponse := service.IncidentResponse{}
|
||||
// Create incident dto
|
||||
i.logger.Info(fmt.Sprintf("%s received request to create incident: %+v", logTag, request))
|
||||
teamEntity, severityEntity, err := getTeamAndSeverityEntity(i, request.TeamID, request.SeverityID)
|
||||
if err != nil {
|
||||
return emptyResponse, err
|
||||
}
|
||||
incidentDTO, err := buildCreateIncidentDTO(request, i.teamRepository, i.severityRepository, i.slackService)
|
||||
if err != nil {
|
||||
return emptyResponse, err
|
||||
}
|
||||
i.logger.Info(fmt.Sprintf("%s CreateIncidentDTO created", logTag))
|
||||
|
||||
// Save the incident to the database
|
||||
incidentEntity, err := i.incidentRepository.CreateIncidentEntity(incidentDTO)
|
||||
if err != nil {
|
||||
i.logger.Error(fmt.Sprintf("%s Error while creating incident", logTag), zap.Error(err))
|
||||
return emptyResponse, err
|
||||
}
|
||||
incidentName := incidentEntity.IncidentName
|
||||
i.logger.Info(fmt.Sprintf("%s Incident entity created. Incident is: %s", logTag, incidentName))
|
||||
|
||||
// Create slack channel
|
||||
// Call slack service to create a slack channel for incident
|
||||
channel, err := i.slackService.CreateSlackChannel(incidentEntity.ID)
|
||||
if err != nil {
|
||||
i.logger.Error(
|
||||
fmt.Sprintf("%s [%s] Error while crating slack channel", logTag, incidentName),
|
||||
zap.Error(err),
|
||||
)
|
||||
return emptyResponse, err
|
||||
}
|
||||
i.logger.Info(fmt.Sprintf(
|
||||
"%s [%s] Slack channel created. Channel name is %s", logTag, incidentName, channel.Name),
|
||||
)
|
||||
// Update channel details to incident entity
|
||||
incidentEntity.SlackChannel = channel.ID
|
||||
incidentEntity.IncidentName = channel.Name
|
||||
err = i.incidentRepository.UpdateIncident(incidentEntity)
|
||||
if err != nil {
|
||||
i.logger.Error(fmt.Sprintf("%s [%s] Failed to update the slack channel details in DB", logTag, incidentName))
|
||||
return emptyResponse, err
|
||||
}
|
||||
i.logger.Info(fmt.Sprintf("%s [%s] Slack channel details updated to incident entity", logTag, incidentName))
|
||||
|
||||
// Post incident summary
|
||||
// Call slack service, provide required message to be posted
|
||||
incidentStatusEntity, err := getIncidentStatusEntity(i, incidentEntity.ID, incidentEntity.Status, incidentName)
|
||||
if err != nil {
|
||||
return emptyResponse, err
|
||||
}
|
||||
i.logger.Info(fmt.Sprintf("%s [%s] Team, Severity and IncidentStatus entity fetched successfully", logTag, incidentName))
|
||||
|
||||
err = i.slackService.SetChannelTopic(channel, teamEntity, severityEntity, incidentEntity)
|
||||
if err != nil {
|
||||
i.logger.Error(
|
||||
fmt.Sprintf("%s [%s] Failed to set channel topic", logTag, incidentName),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
i.logger.Info(fmt.Sprintf("%s [%s] Channel topic is set", logTag, incidentName))
|
||||
|
||||
if source == "SLACK" {
|
||||
go func() {
|
||||
err := createIncidentWorkflow(i, channel, incidentEntity, teamEntity, severityEntity, incidentStatusEntity, blazeGroupChannelID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
err = createIncidentWorkflow(i, channel, incidentEntity, teamEntity, severityEntity, incidentStatusEntity, blazeGroupChannelID)
|
||||
if err != nil {
|
||||
return service.IncidentResponse{}, err
|
||||
}
|
||||
}
|
||||
|
||||
go postInWebhookSlackChannel(i, teamEntity, incidentEntity, severityEntity)
|
||||
|
||||
return service.ConvertToIncidentResponse(*incidentEntity), nil
|
||||
}
|
||||
|
||||
func createIncidentWorkflow(
|
||||
i *IncidentServiceV2,
|
||||
channel *slackClient.Channel,
|
||||
incidentEntity *incident.IncidentEntity,
|
||||
teamEntity *team.TeamEntity,
|
||||
severityEntity *severity.SeverityEntity,
|
||||
incidentStatusEntity *incident.IncidentStatusEntity,
|
||||
blazeGroupChannelID string,
|
||||
) error {
|
||||
incidentName := incidentEntity.IncidentName
|
||||
timestamp, err := postIncidentSummary(
|
||||
channel.ID,
|
||||
incidentEntity,
|
||||
teamEntity,
|
||||
severityEntity,
|
||||
incidentStatusEntity,
|
||||
i.incidentRepository,
|
||||
blazeGroupChannelID,
|
||||
i.slackService,
|
||||
)
|
||||
if err != nil {
|
||||
i.logger.Error(
|
||||
fmt.Sprintf("%s [%s] Error in posting incident summary to slack channel", logTag, incidentName),
|
||||
zap.Error(err),
|
||||
)
|
||||
return err
|
||||
}
|
||||
i.logger.Info(fmt.Sprintf("%s [%s] Incident summary posted", logTag, incidentName))
|
||||
// Update incident channel details
|
||||
err = i.incidentRepository.CreateIncidentChannelEntry(&incident.CreateIncidentChannelEntry{
|
||||
SlackChannel: channel.ID,
|
||||
MessageTimeStamp: *timestamp,
|
||||
IncidentId: incidentEntity.ID,
|
||||
})
|
||||
if err != nil {
|
||||
i.logger.Error(
|
||||
fmt.Sprintf("%s [%s] Error in creating incident channel entry: %d", logTag, incidentName),
|
||||
zap.Error(err),
|
||||
)
|
||||
return err
|
||||
}
|
||||
i.logger.Info(fmt.Sprintf("%s [%s] Incident channel details updated to DB", logTag, incidentName))
|
||||
|
||||
// Add required members to channel
|
||||
// Add incident creator to the channel
|
||||
// Check if the user is a slack user and get the slack user id
|
||||
isSlackUser, slackUserId := i.userRepository.IsAHoustonUser(incidentEntity.CreatedBy)
|
||||
|
||||
if isSlackUser {
|
||||
// Add user who created the incident
|
||||
err := i.slackService.InviteUsersToConversation(incidentEntity.SlackChannel, slackUserId)
|
||||
if err != nil {
|
||||
i.logger.Error(
|
||||
fmt.Sprintf(
|
||||
"%s [%s] Failed to add the incident creator %s to the channel: %s",
|
||||
logTag, incidentName, incidentEntity.CreatedBy, incidentEntity.SlackChannel,
|
||||
),
|
||||
)
|
||||
return err
|
||||
}
|
||||
i.logger.Info(fmt.Sprintf("%s [%s] Incident creator is added to the slack channel", logTag, incidentName))
|
||||
}
|
||||
// Call addMembersToIncident(), provide channel, team name and severity
|
||||
err = addMembersToIncident(channel, i, teamEntity, severityEntity, incidentName)
|
||||
if err != nil {
|
||||
_, err := i.slackService.PostMessage(
|
||||
fmt.Sprintf("`"+"Failed to add some team members to the incident channel"+"`"),
|
||||
false,
|
||||
channel,
|
||||
)
|
||||
if err != nil {
|
||||
i.logger.Debug(fmt.Sprintf("%s Failed to add members to the incident channel", logTag), zap.Error(err))
|
||||
}
|
||||
}
|
||||
i.logger.Info(fmt.Sprintf("%s [%s] All the members got added to the incident channel", logTag, incidentName))
|
||||
|
||||
// Tag oncall
|
||||
// Call slack service to tag the oncall, provide incident id and slack id to be tagged
|
||||
err = tagPseOrDevOncallToIncident(channel, severityEntity, teamEntity, i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i.logger.Info(fmt.Sprintf("%s [%s] oncall us tagged to the incident", logTag, incidentName))
|
||||
|
||||
// Assign responder
|
||||
// Call assignResponder(), provide incident id, team service should assign responder
|
||||
|
||||
err = assignResponderToIncident(
|
||||
i,
|
||||
incidentEntity,
|
||||
teamEntity,
|
||||
severityEntity,
|
||||
incidentName,
|
||||
)
|
||||
if err != nil {
|
||||
i.logger.Error(
|
||||
fmt.Sprintf("%s [%s] Failed to assign responder to the incident", logTag, incidentName),
|
||||
zap.Error(err),
|
||||
)
|
||||
return err
|
||||
}
|
||||
i.logger.Info(fmt.Sprintf("%s [%s] Responder is assigned to the incident", logTag, incidentName))
|
||||
|
||||
// Post message about the SLA
|
||||
if incidentEntity.SeverityId >= 3 && incidentEntity.Status <= 4 {
|
||||
slaDate := time.Now().AddDate(0, 0, severityEntity.Sla).Format("02 Jan 2006")
|
||||
message := fmt.Sprintf(
|
||||
"SLA for this incident is `%v day(s)` and will be closed by `%s`",
|
||||
severityEntity.Sla,
|
||||
slaDate,
|
||||
)
|
||||
_, err := i.slackService.PostMessage(message, false, channel)
|
||||
if err != nil {
|
||||
i.logger.Error(fmt.Sprintf("%s [%s] Failed to SLA information to the slack channel", logTag, incidentName), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if viper.GetBool("ENABLE_GMEET") {
|
||||
gmeet, err := createGmeetLink(i.logger, channel.Name)
|
||||
if err != nil {
|
||||
i.logger.Error(fmt.Sprintf("%s [%s] Error while creating gmeet", logTag, incidentName), zap.Error(err))
|
||||
} else {
|
||||
_, err := i.slackService.PostMessage(fmt.Sprint("gmeet: ", gmeet), false, channel)
|
||||
if err != nil {
|
||||
i.logger.Error(fmt.Sprintf("%s [%s] Failed to post Google Meet link: %s", logTag, incidentName, gmeet))
|
||||
}
|
||||
}
|
||||
i.logger.Info(fmt.Sprintf("%s [%s] Google Meeting link posted to the channel %s", logTag, incidentName, gmeet))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func postIncidentSummary(
|
||||
incidentChannelID string,
|
||||
incidentEntity *incident.IncidentEntity,
|
||||
teamEntity *team.TeamEntity,
|
||||
severityEntity *severity.SeverityEntity,
|
||||
incidentStatusEntity *incident.IncidentStatusEntity,
|
||||
incidentRepository *incident.Repository,
|
||||
blazeGroupChannelID string,
|
||||
slackService *slack.SlackService,
|
||||
) (*string, error) {
|
||||
// Post incident summary to Blaze Group channel and incident channel
|
||||
blocks := view.IncidentSummarySection(incidentEntity, teamEntity, severityEntity, incidentStatusEntity)
|
||||
color := util.GetColorBySeverity(incidentEntity.SeverityId)
|
||||
if blazeGroupChannelID != "" {
|
||||
timestamp, err := slackService.PostMessageBlocks(blazeGroupChannelID, blocks, color)
|
||||
if err == nil {
|
||||
err := incidentRepository.CreateIncidentChannelEntry(&incident.CreateIncidentChannelEntry{
|
||||
SlackChannel: blazeGroupChannelID,
|
||||
MessageTimeStamp: timestamp,
|
||||
IncidentId: incidentEntity.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"failed to create incident channel entry for blazeGroupChannelID: %s", blazeGroupChannelID,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("error in posting summary to the blazeGroup channel %v", err)
|
||||
}
|
||||
}
|
||||
timestamp, err := slackService.PostMessageBlocks(incidentChannelID, blocks, color)
|
||||
if incidentEntity.MetaData != nil {
|
||||
msgOption, noMetaDataError := util.BuildSlackTextMessageFromMetaData(incidentEntity.MetaData, true)
|
||||
if noMetaDataError == nil {
|
||||
_, err = slackService.PostMessageOption(incidentChannelID, msgOption)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"error in posting metagada to the incident channel: %s. error: %v", incidentChannelID, err,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
err := incidentRepository.CreateIncidentChannelEntry(&incident.CreateIncidentChannelEntry{
|
||||
SlackChannel: incidentChannelID,
|
||||
MessageTimeStamp: timestamp,
|
||||
IncidentId: incidentEntity.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"failed to create incident channel entry for channelID: %s", incidentChannelID,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf(
|
||||
"error in posting summary to the incident channel: %s. error: %v", incidentChannelID, err,
|
||||
)
|
||||
}
|
||||
return ×tamp, nil
|
||||
}
|
||||
|
||||
func addMembersToIncident(
|
||||
channel *slackClient.Channel,
|
||||
i *IncidentServiceV2,
|
||||
teamEntity *team.TeamEntity,
|
||||
severityEntity *severity.SeverityEntity,
|
||||
incidentName string,
|
||||
) error {
|
||||
// 1. get list of team members, list of members by severity
|
||||
var userIdList []string
|
||||
userIdList = append(
|
||||
append(
|
||||
append(
|
||||
append(userIdList, teamEntity.SlackUserIds...),
|
||||
severityEntity.SlackUserIds...,
|
||||
),
|
||||
teamEntity.OncallHandle,
|
||||
),
|
||||
teamEntity.PseOncallHandle,
|
||||
)
|
||||
err := i.slackService.InviteUsersToConversation(channel.ID, userIdList[:]...)
|
||||
if err != nil {
|
||||
i.logger.Error(
|
||||
fmt.Sprintf(
|
||||
"%s [%s] Error in adding members [%+v] to the channel %s",
|
||||
logTag, incidentName, userIdList, channel.Name,
|
||||
),
|
||||
zap.Error(err),
|
||||
)
|
||||
return err
|
||||
}
|
||||
i.logger.Info(fmt.Sprintf("%s [%s] All the members are added to the slack channel", logTag, incidentName))
|
||||
return nil
|
||||
}
|
||||
|
||||
func assignResponderToIncident(
|
||||
i *IncidentServiceV2,
|
||||
incidentEntity *incident.IncidentEntity,
|
||||
teamEntity *team.TeamEntity,
|
||||
severityEntity *severity.SeverityEntity,
|
||||
incidentName string,
|
||||
) error {
|
||||
// 1. find the responder (dev or pse oncall)
|
||||
var responder, nonEmpty = getOncallOrResponderHandle(teamEntity, severityEntity)
|
||||
// 2. updated responder in db
|
||||
if nonEmpty {
|
||||
var addIncidentRoleRequest = incident.AddIncidentRoleRequest{
|
||||
UserId: responder,
|
||||
Role: incident.Responder,
|
||||
IncidentId: int(incidentEntity.ID),
|
||||
CreatedById: incidentEntity.CreatedBy,
|
||||
}
|
||||
err := i.incidentRepository.UpsertIncidentRole(&addIncidentRoleRequest)
|
||||
if err != nil {
|
||||
i.logger.Error(fmt.Sprintf("%s [%s] Failed to upsert incident_role", logTag, incidentName), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
i.logger.Info(fmt.Sprintf("%s [%s] Incident role upserted", logTag, incidentName))
|
||||
_, err = i.slackService.PostMessageByChannelID(
|
||||
fmt.Sprintf("<@%s> is assigned as *%s* by <@%s>", addIncidentRoleRequest.UserId, addIncidentRoleRequest.Role, addIncidentRoleRequest.CreatedById),
|
||||
false,
|
||||
incidentEntity.SlackChannel,
|
||||
)
|
||||
if err != nil {
|
||||
i.logger.Error(fmt.Sprintf("%s [%s] Post response failed for AssignResponderToIncident", logTag, incidentName), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildCreateIncidentDTO(
|
||||
createIncRequest request.CreateIncidentRequestV2,
|
||||
teamRepository *team.Repository,
|
||||
severityRepository *severity.Repository,
|
||||
slackService *slack.SlackService,
|
||||
) (*incident.CreateIncidentDTO, error) {
|
||||
var createIncidentRequest incident.CreateIncidentDTO
|
||||
teamID, err := strconv.ParseUint(createIncRequest.TeamID, 10, 64)
|
||||
if err != nil {
|
||||
//todo handle error
|
||||
}
|
||||
teamEntity, err := teamRepository.FindTeamById(uint(teamID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
severityID, err := strconv.ParseUint(createIncRequest.SeverityID, 10, 64)
|
||||
if err != nil {
|
||||
//todo handle error
|
||||
}
|
||||
severityEntity, err := severityRepository.FindSeverityById(uint(severityID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var rawJson, _ = json.Marshal([]request.CreateIncidentMetaData{createIncRequest.MetaData})
|
||||
createdBy := slackService.GetSlackUserIdOrEmail(createIncRequest.CreatedBy)
|
||||
|
||||
createIncidentRequest.Title = createIncRequest.Title
|
||||
createIncidentRequest.Description = createIncRequest.Description
|
||||
createIncidentRequest.Severity = fmt.Sprintf("%d", severityEntity.ID)
|
||||
createIncidentRequest.TeamId = fmt.Sprintf("%d", teamEntity.ID)
|
||||
createIncidentRequest.Status = incident.Investigating
|
||||
createIncidentRequest.CreatedBy = createdBy
|
||||
createIncidentRequest.UpdatedBy = createdBy
|
||||
createIncidentRequest.StartTime = time.Now()
|
||||
createIncidentRequest.EnableReminder = false
|
||||
createIncidentRequest.MetaData = rawJson
|
||||
return &createIncidentRequest, nil
|
||||
}
|
||||
|
||||
func getIncidentStatusEntity(
|
||||
i *IncidentServiceV2,
|
||||
incidentID,
|
||||
statusID uint,
|
||||
incidentName string,
|
||||
) (*incident.IncidentStatusEntity, error) {
|
||||
incidentStatusEntity, err := i.incidentRepository.FindIncidentStatusById(statusID)
|
||||
if err != nil || incidentStatusEntity == nil {
|
||||
i.logger.Error(
|
||||
fmt.Sprintf(
|
||||
"%s [%s] Error in finding incident statusID entity for statusID: %d in incidentID: %d",
|
||||
logTag, incidentName, statusID, incidentID,
|
||||
),
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return incidentStatusEntity, nil
|
||||
}
|
||||
|
||||
func getTeamAndSeverityEntity(
|
||||
i *IncidentServiceV2,
|
||||
team,
|
||||
severity string,
|
||||
) (*team.TeamEntity, *severity.SeverityEntity, error) {
|
||||
teamID, err := strconv.ParseUint(team, 10, 64)
|
||||
teamEntity, err := i.teamRepository.FindTeamById(uint(teamID))
|
||||
if err != nil || teamEntity == nil {
|
||||
i.logger.Error(
|
||||
fmt.Sprintf("%s Error in finding team entity for team ID: %d", logTag, teamID),
|
||||
)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
severityID, err := strconv.ParseUint(severity, 10, 64)
|
||||
severityEntity, err := i.severityRepository.FindSeverityById(uint(severityID))
|
||||
if err != nil || severityEntity == nil {
|
||||
i.logger.Error(
|
||||
fmt.Sprintf("%s Error in finding severity entity for severity ID: %d", logTag, severityID),
|
||||
)
|
||||
return nil, nil, err
|
||||
}
|
||||
return teamEntity, severityEntity, nil
|
||||
}
|
||||
|
||||
func tagPseOrDevOncallToIncident(
|
||||
channel *slackClient.Channel,
|
||||
severityEntity *severity.SeverityEntity,
|
||||
teamEntity *team.TeamEntity,
|
||||
i *IncidentServiceV2,
|
||||
) error {
|
||||
incidentName := channel.Name
|
||||
onCallToBeTagged, nonEmpty := getOncallOrResponderHandle(teamEntity, severityEntity)
|
||||
if nonEmpty {
|
||||
//oncall handles should already be added to channel
|
||||
/*err := i.slackService.InviteUsersToConversation(channelId, onCallToBeTagged)
|
||||
if err != nil {
|
||||
i.logger.Error(fmt.Sprintf("%s Failed to invite oncall to the conversation"))
|
||||
return err
|
||||
}*/
|
||||
ts, err := i.slackService.PostMessage(fmt.Sprintf("<@%s>", onCallToBeTagged), false, channel)
|
||||
if err != nil {
|
||||
i.logger.Error(
|
||||
fmt.Sprintf("%s [%s] Failed to tag oncall in channel: %s", logTag, incidentName, channel.Name),
|
||||
zap.Error(err),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
time.Sleep(3 * time.Second)
|
||||
msg, _, _, _ := i.slackService.GetConversationReplies(channel.ID, ts, 2)
|
||||
if len(msg) > 1 {
|
||||
//User id needs to sliced from `<@XXXXXXXXXXXX>` format to `XXXXXXXXXXXX`
|
||||
err := i.slackService.InviteUsersToConversation(channel.ID, msg[1].Text[2:13])
|
||||
if err != nil {
|
||||
i.logger.Error(fmt.Sprintf("%s [%s] Failed to invite the user tagged by the oncall bot", logTag, incidentName))
|
||||
return
|
||||
}
|
||||
i.logger.Info(fmt.Sprintf("%s [%s] User tagged by the oncall bot is invited to the incident channel", logTag, incidentName))
|
||||
}
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getOncallOrResponderHandle(teamEntity *team.TeamEntity, severityEntity *severity.SeverityEntity) (string, bool) {
|
||||
onCallOrResponderHandle := ""
|
||||
if isPseIncident(teamEntity, severityEntity) {
|
||||
onCallOrResponderHandle = teamEntity.PseOncallHandle
|
||||
} else {
|
||||
onCallOrResponderHandle = teamEntity.OncallHandle
|
||||
}
|
||||
return onCallOrResponderHandle, len(strings.TrimSpace(onCallOrResponderHandle)) > 0
|
||||
}
|
||||
|
||||
func isPseIncident(teamEntity *team.TeamEntity, severityEntity *severity.SeverityEntity) bool {
|
||||
return len(strings.TrimSpace(teamEntity.PseOncallHandle)) > 0 && (severityEntity.Name == "Sev-2" || severityEntity.Name == "Sev-3")
|
||||
}
|
||||
|
||||
func postInWebhookSlackChannel(
|
||||
i *IncidentServiceV2,
|
||||
teamEntity *team.TeamEntity,
|
||||
incidentEntity *incident.IncidentEntity,
|
||||
severityEntity *severity.SeverityEntity,
|
||||
) {
|
||||
incidentName := incidentEntity.IncidentName
|
||||
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,
|
||||
)
|
||||
_, err := i.slackService.PostMessageByChannelID(msg, false, teamEntity.WebhookSlackChannel)
|
||||
if err != nil {
|
||||
i.logger.Error(
|
||||
fmt.Sprintf("%s [%s] Failed to post update in webhook slack channel: %s", logTag, incidentName, incidentEntity.SlackChannel),
|
||||
zap.Error(err),
|
||||
)
|
||||
i.logger.Info(fmt.Sprintf("%s [%s] Update posted to the webhook slack channel: %s", logTag, incidentName, incidentEntity.SlackChannel))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func createGmeetLink(logger *zap.Logger, channelName string) (string, error) {
|
||||
incidentName := channelName
|
||||
calclient, err := calendar.NewService(context.Background(), option.WithCredentialsFile(viper.GetString("GMEET_CONFIG_FILE_PATH")))
|
||||
if err != nil {
|
||||
logger.Error(
|
||||
fmt.Sprintf("%s [%s]Calander service creation failed, unable to read client secret file: ", logTag, incidentName),
|
||||
zap.Error(err),
|
||||
)
|
||||
return "", err
|
||||
}
|
||||
t0 := time.Now().Format(time.RFC3339)
|
||||
t1 := time.Now().Add(1 * time.Hour).Format(time.RFC3339)
|
||||
event := &calendar.Event{
|
||||
Summary: channelName,
|
||||
Description: "Incident",
|
||||
Start: &calendar.EventDateTime{
|
||||
DateTime: t0,
|
||||
},
|
||||
End: &calendar.EventDateTime{
|
||||
DateTime: t1,
|
||||
},
|
||||
ConferenceData: &calendar.ConferenceData{
|
||||
CreateRequest: &calendar.CreateConferenceRequest{
|
||||
RequestId: uuid.NewString(),
|
||||
ConferenceSolutionKey: &calendar.ConferenceSolutionKey{
|
||||
Type: "hangoutsMeet",
|
||||
},
|
||||
Status: &calendar.ConferenceRequestStatus{
|
||||
StatusCode: "success",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
calendarID := "primary" //use "primary"
|
||||
event, err = calclient.Events.Insert(calendarID, event).ConferenceDataVersion(1).Do()
|
||||
if err != nil {
|
||||
logger.Error("Unable to create event. %v\n", zap.Error(err))
|
||||
return "", err
|
||||
}
|
||||
|
||||
calclient.Events.Delete(calendarID, event.Id).Do()
|
||||
return event.HangoutLink, nil
|
||||
}
|
||||
@@ -333,13 +333,13 @@ func (i *incidentService) CreateIncident(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
incidentRequest, err := i.buildCreateIncidentRequest(createIncRequest)
|
||||
incidentDTO, err := i.buildCreateIncidentDTO(createIncRequest)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
// Save the incident to the database
|
||||
incidentEntity, err := i.incidentRepository.CreateIncident(incidentRequest)
|
||||
incidentEntity, err := i.incidentRepository.CreateIncidentEntity(incidentDTO)
|
||||
if err != nil {
|
||||
i.logger.Error("[Create incident Api] Error while creating incident", zap.Error(err))
|
||||
return
|
||||
@@ -351,7 +351,7 @@ func (i *incidentService) CreateIncident(c *gin.Context) {
|
||||
incidentEntity.Status,
|
||||
)
|
||||
if err != nil {
|
||||
i.logger.Error("[CIP] failed whilte getting team, severity and status", zap.Error(err))
|
||||
i.logger.Error("[CIP] failed while getting team, severity and status", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -396,6 +396,16 @@ func (i *incidentService) CreateIncident(c *gin.Context) {
|
||||
i.logger.Error("[Create incident] Error while assigning responder to the incident ", zap.Error(err))
|
||||
}
|
||||
|
||||
if incidentEntity.SeverityId >= 3 && incidentEntity.Status <= 4 {
|
||||
slaDate := time.Now().AddDate(0, 0, severityEntity.Sla).Format("02 Jan 2006")
|
||||
message := fmt.Sprintf("SLA for this incident is `%v day(s)` and will be closed by `%s`", severityEntity.Sla,
|
||||
slaDate)
|
||||
err := util.PostMessageToIncidentChannel(message, incidentEntity.SlackChannel, i.socketModeClient)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
incidentResponse := service.ConvertToIncidentResponse(*incidentEntity)
|
||||
c.JSON(http.StatusOK, common.SuccessResponse(incidentResponse, http.StatusOK))
|
||||
}
|
||||
@@ -499,8 +509,8 @@ func (i *incidentService) createSlackChannel(incidentEntity *incident.IncidentEn
|
||||
return &channelID, nil
|
||||
}
|
||||
|
||||
func (i *incidentService) buildCreateIncidentRequest(createIncRequest request.CreateIncidentRequest) (*incident.CreateIncidentRequest, error) {
|
||||
var createIncidentRequest incident.CreateIncidentRequest
|
||||
func (i *incidentService) buildCreateIncidentDTO(createIncRequest request.CreateIncidentRequest) (*incident.CreateIncidentDTO, error) {
|
||||
var createIncidentRequest incident.CreateIncidentDTO
|
||||
teamEntity, err := i.teamRepository.FindTeamByTeamName(createIncRequest.TeamName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -14,6 +14,15 @@ type CreateIncidentRequest struct {
|
||||
MetaData CreateIncidentMetaData `gorm:"json:metaData"`
|
||||
}
|
||||
|
||||
type CreateIncidentRequestV2 struct {
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
SeverityID string `json:"severity"`
|
||||
TeamID string `json:"type"`
|
||||
CreatedBy string `json:"createdBy"`
|
||||
MetaData CreateIncidentMetaData `json:"metaData"`
|
||||
}
|
||||
|
||||
type CreateIncidentMetaData struct {
|
||||
CustomerId uuid.UUID `json:"customerId"`
|
||||
CustomerName string `json:"customerName"`
|
||||
|
||||
205
service/slack/slack_service.go
Normal file
205
service/slack/slack_service.go
Normal file
@@ -0,0 +1,205 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/slack-go/slack"
|
||||
"github.com/slack-go/slack/socketmode"
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/zap"
|
||||
"houston/model/incident"
|
||||
"houston/model/severity"
|
||||
"houston/model/team"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type SlackService struct {
|
||||
SocketModeClient *socketmode.Client
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func NewSlackService(logger *zap.Logger) *SlackService {
|
||||
return &SlackService{createSocketModeClient(logger), logger}
|
||||
}
|
||||
|
||||
const logTag = "[slack-service]"
|
||||
|
||||
func (s *SlackService) PostMessage(message string, escape bool, channel *slack.Channel) (string, error) {
|
||||
msgOption := slack.MsgOptionText(message, escape)
|
||||
_, timeStamp, err := s.SocketModeClient.PostMessage(channel.ID, msgOption)
|
||||
if err != nil {
|
||||
e := fmt.Sprintf("%s Failed to post message into channel: %s", logTag, channel.Name)
|
||||
s.logger.Error(e, zap.Error(err))
|
||||
return "", fmt.Errorf("%s - %+v", e, err)
|
||||
}
|
||||
return timeStamp, nil
|
||||
}
|
||||
|
||||
func (s *SlackService) PostMessageOption(channelID string, messageOption slack.MsgOption) (string, error) {
|
||||
_, timeStamp, err := s.SocketModeClient.PostMessage(channelID, messageOption)
|
||||
if err != nil {
|
||||
e := fmt.Sprintf("%s Failed to post message into channelID: %s", logTag, channelID)
|
||||
s.logger.Error(e, zap.Error(err))
|
||||
return "", fmt.Errorf("%s - %+v", e, err)
|
||||
}
|
||||
return timeStamp, nil
|
||||
}
|
||||
|
||||
func (s *SlackService) PostMessageByChannelID(message string, escape bool, channelID string) (string, error) {
|
||||
msgOption := slack.MsgOptionText(message, escape)
|
||||
_, timeStamp, err := s.SocketModeClient.PostMessage(channelID, msgOption)
|
||||
if err != nil {
|
||||
e := fmt.Sprintf("%s Failed to post message into channel: %s", logTag, channelID)
|
||||
s.logger.Error(e, zap.Error(err))
|
||||
return "", fmt.Errorf("%s : %+v", e, err)
|
||||
}
|
||||
return timeStamp, nil
|
||||
}
|
||||
|
||||
func (s *SlackService) PostMessageBlocks(channelID string, blocks slack.Blocks, color string) (string, error) {
|
||||
att := slack.Attachment{Blocks: blocks, Color: color}
|
||||
messageOption := slack.MsgOptionAttachments(att)
|
||||
_, timestamp, err := s.SocketModeClient.PostMessage(channelID, messageOption)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return timestamp, nil
|
||||
}
|
||||
|
||||
func (s *SlackService) GetConversationReplies(channelID string, timeStamp string, limit int) (msgs []slack.Message, hasMore bool, nextCursor string, err error) {
|
||||
return s.SocketModeClient.GetConversationReplies(
|
||||
&slack.GetConversationRepliesParameters{ChannelID: channelID, Timestamp: timeStamp, Limit: limit},
|
||||
)
|
||||
}
|
||||
|
||||
func (s *SlackService) SetChannelTopic(
|
||||
channel *slack.Channel,
|
||||
teamEntity *team.TeamEntity,
|
||||
severityEntity *severity.SeverityEntity,
|
||||
incidentEntity *incident.IncidentEntity,
|
||||
) error {
|
||||
topic := fmt.Sprintf(
|
||||
"%s-%s(%s) Incident-%d | %s",
|
||||
teamEntity.Name,
|
||||
severityEntity.Name,
|
||||
severityEntity.Description,
|
||||
incidentEntity.ID,
|
||||
incidentEntity.Title,
|
||||
)
|
||||
_, err := s.SocketModeClient.SetTopicOfConversation(channel.ID, topic)
|
||||
if err != nil {
|
||||
e := fmt.Sprintf("%s set topic on slack channel failed", logTag)
|
||||
s.logger.Error(e, zap.String("channel_id", channel.Name), zap.Error(err))
|
||||
return fmt.Errorf("%s : %+v", e, err)
|
||||
}
|
||||
|
||||
s.logger.Info("set topic on slack channel successful", zap.String("channel_id", channel.Name))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SlackService) GetUserByEmail(userEmail string) (*slack.User, error) {
|
||||
user, err := s.SocketModeClient.GetUserByEmail(userEmail)
|
||||
if err != nil {
|
||||
e := fmt.Sprintf("%s error in getting user by email: %s", logTag, userEmail)
|
||||
s.logger.Error(e, zap.Error(err))
|
||||
return nil, fmt.Errorf("%s : %+v", e, err)
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (s *SlackService) GetSlackUserIdOrEmail(email string) string {
|
||||
userInfo, err := s.GetUserByEmail(email)
|
||||
if err != nil {
|
||||
return email
|
||||
} else {
|
||||
return userInfo.ID
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SlackService) CreateSlackChannel(incidentId uint) (*slack.Channel, error) {
|
||||
channel, err := createChannel(getIncidentChannelName(incidentId), s.SocketModeClient, s.logger)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s failed to create Slack Channel for incident %d. error: %+v", logTag, incidentId, err)
|
||||
}
|
||||
return channel, nil
|
||||
}
|
||||
|
||||
func (s *SlackService) InviteUsersToConversation(channelId string, userId ...string) error {
|
||||
_, err := s.SocketModeClient.InviteUsersToConversation(channelId, userId...)
|
||||
if err != nil {
|
||||
e := fmt.Sprintf("%s failed to invite users: %+v to the channel: %s", logTag, userId, channelId)
|
||||
s.logger.Error(e, zap.Error(err))
|
||||
return fmt.Errorf("%s : %+v", e, err)
|
||||
}
|
||||
|
||||
s.logger.Info(
|
||||
"successfully invite users to conversation",
|
||||
zap.String("channel_id", channelId), zap.Any("user_ids", userId),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getIncidentChannelName(incidentID uint) string {
|
||||
var channelPrefix string
|
||||
if viper.GetString("env") != "prod" {
|
||||
channelPrefix = "_test-issue-"
|
||||
} else {
|
||||
channelPrefix = "_houston-"
|
||||
}
|
||||
return channelPrefix + strconv.Itoa(int(incidentID))
|
||||
}
|
||||
|
||||
func createChannel(channelName string, socketModeClient *socketmode.Client, logger *zap.Logger) (*slack.Channel, error) {
|
||||
request := slack.CreateConversationParams{
|
||||
ChannelName: channelName,
|
||||
IsPrivate: false,
|
||||
}
|
||||
|
||||
channel, err := socketModeClient.CreateConversation(request)
|
||||
if err != nil {
|
||||
e := fmt.Sprintf("%s failed to create slack channel with name: %s", logTag, channelName)
|
||||
logger.Error(e, zap.Error(err))
|
||||
return nil, fmt.Errorf("%s : %+v", e, err)
|
||||
}
|
||||
|
||||
logger.Info("created slackbot channel successfully", zap.String("channel_name", channelName), zap.String("channel_id", channel.ID))
|
||||
return channel, nil
|
||||
}
|
||||
|
||||
func createSocketModeClient(logger *zap.Logger) *socketmode.Client {
|
||||
appToken := viper.GetString("houston.slack.app.token")
|
||||
if appToken == "" {
|
||||
logger.Error("HOUSTON_SLACK_APP_TOKEN must be set.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(appToken, "xapp-") {
|
||||
logger.Error("HOUSTON_SLACK_APP_TOKEN must have the prefix \"xapp-\".")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
botToken := viper.GetString("houston.slack.bot.token")
|
||||
if botToken == "" {
|
||||
logger.Error("HOUSTON_SLACK_BOT_TOKEN must be set.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(botToken, "xoxb-") {
|
||||
logger.Error("HOUSTON_SLACK_BOT_TOKEN must have the prefix \"xoxb-\".")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
api := slack.New(
|
||||
botToken,
|
||||
slack.OptionDebug(false),
|
||||
slack.OptionAppLevelToken(appToken),
|
||||
)
|
||||
|
||||
client := socketmode.New(
|
||||
api,
|
||||
socketmode.OptionDebug(false),
|
||||
)
|
||||
|
||||
return client
|
||||
}
|
||||
@@ -258,7 +258,7 @@ func (t *TeamService) UpdateTeam(c *gin.Context) {
|
||||
|
||||
authResult, _ := t.authService.checkIfManagerOrAdmin(c, teamEntity.ManagerHandle, Admin, Manager)
|
||||
if !authResult {
|
||||
c.JSON(http.StatusUnauthorized, common.ErrorResponse(errors.New(fmt.Sprintf("%v is neither an admin nor the manager of %v team", userEmail, teamEntity.Name)), http.StatusUnauthorized, nil))
|
||||
c.JSON(http.StatusUnauthorized, common.ErrorResponse(errors.New(fmt.Sprintf("%v is neither an admin nor a member of %v team", userEmail, teamEntity.Name)), http.StatusUnauthorized, nil))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -64,6 +64,24 @@ func ValidateCreateIncidentRequest(request service.CreateIncidentRequest) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateCreateIncidentRequestV2(request service.CreateIncidentRequestV2) error {
|
||||
if request.Title == "" || request.Description == "" {
|
||||
return errors.New("title and description should be present in create request")
|
||||
}
|
||||
descriptionMaxLength := viper.GetInt("create-incident.description.max-length")
|
||||
titleMaxLength := viper.GetInt("create-incident.title.max-length")
|
||||
if len(request.Description) > descriptionMaxLength {
|
||||
return errors.New(fmt.Sprintf("description should not be more than %v characters long", descriptionMaxLength))
|
||||
}
|
||||
if len(request.Title) > titleMaxLength {
|
||||
return errors.New(fmt.Sprintf("title should not be more than %v characters long", titleMaxLength))
|
||||
}
|
||||
if request.CreatedBy == "" {
|
||||
return errors.New("created By should be present")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateUpdateTeamRequest(request service.UpdateTeamRequest) error {
|
||||
if request.Id == 0 {
|
||||
return errors.New("id should be present in update request")
|
||||
|
||||
Reference in New Issue
Block a user