* NTP-44973 | Return default Houston team when reporting team is found inactive. * NTP-44973 | Changing defaultTeamId from constant to env_var.
464 lines
16 KiB
Go
464 lines
16 KiB
Go
package service
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"houston/appcontext"
|
|
"houston/common/util"
|
|
"houston/internal/processor/action"
|
|
"houston/logger"
|
|
"houston/model/incident"
|
|
"houston/model/log"
|
|
"houston/model/product"
|
|
"houston/model/team"
|
|
"houston/model/user"
|
|
"houston/pkg/slackbot"
|
|
"houston/repository/incidentStatus"
|
|
"houston/repository/severity"
|
|
"houston/service/incident/impl"
|
|
incidentStatusService "houston/service/incidentStatus"
|
|
rcaService "houston/service/rca/impl"
|
|
request "houston/service/request"
|
|
service "houston/service/response"
|
|
common "houston/service/response/common"
|
|
severityServiceImpl "houston/service/severity/impl"
|
|
slack2 "houston/service/slack"
|
|
"houston/service/teamService"
|
|
"houston/service/teamUser"
|
|
userService "houston/service/user"
|
|
utils "houston/service/utils"
|
|
"math"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/slack-go/slack"
|
|
"github.com/slack-go/slack/socketmode"
|
|
"github.com/spf13/viper"
|
|
"github.com/thoas/go-funk"
|
|
"go.uber.org/zap"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type incidentService struct {
|
|
gin *gin.Engine
|
|
db *gorm.DB
|
|
socketModeClient *socketmode.Client
|
|
teamRepository *team.Repository
|
|
teamService teamService.ITeamServiceV2
|
|
severityRepository *severity.Repository
|
|
incidentRepository *incident.Repository
|
|
userRepository *user.Repository
|
|
messageUpdateAction *action.IncidentChannelMessageUpdateAction
|
|
slackbotClient *slackbot.Client
|
|
slackService slack2.ISlackService
|
|
incidentServiceV2 *impl.IncidentServiceV2
|
|
rcaService *rcaService.RcaService
|
|
incidentStatusService incidentStatusService.IncidentStatusService
|
|
userService userService.UserService
|
|
teamUserService teamUser.ITeamUserService
|
|
}
|
|
|
|
func NewIncidentService(
|
|
gin *gin.Engine, db *gorm.DB, socketModeClient *socketmode.Client, teamService teamService.ITeamServiceV2,
|
|
) *incidentService {
|
|
severityRepository := severity.NewSeverityRepository(db)
|
|
incidentStatusRepository := incidentStatus.NewIncidentStatusRepository(db)
|
|
logRepository := log.NewLogRepository(db)
|
|
teamRepository := team.NewTeamRepository(db, logRepository)
|
|
incidentStatusService := incidentStatusService.NewIncidentStatusService(incidentStatusRepository)
|
|
incidentRepository := incident.NewIncidentRepository(
|
|
db, severityServiceImpl.NewSeverityService(severityRepository),
|
|
incidentStatusService, logRepository,
|
|
teamRepository, socketModeClient,
|
|
)
|
|
userRepository := user.NewUserRepository(db)
|
|
messageUpdateAction := action.NewIncidentChannelMessageUpdateAction(
|
|
socketModeClient, incidentRepository, teamRepository, severityRepository)
|
|
slackBot := slackbot.NewSlackClient(socketModeClient)
|
|
incidentServiceV2 := impl.NewIncidentServiceV2(db)
|
|
rcaService := appcontext.GetRCAService()
|
|
return &incidentService{
|
|
gin: gin,
|
|
db: db,
|
|
socketModeClient: socketModeClient,
|
|
teamRepository: teamRepository,
|
|
teamService: teamService,
|
|
severityRepository: severityRepository,
|
|
incidentRepository: incidentRepository,
|
|
userRepository: userRepository,
|
|
messageUpdateAction: messageUpdateAction,
|
|
slackbotClient: slackBot,
|
|
slackService: appcontext.GetSlackService(),
|
|
incidentServiceV2: incidentServiceV2,
|
|
rcaService: rcaService,
|
|
incidentStatusService: incidentStatusService,
|
|
userService: userService.NewUserService(userRepository, appcontext.GetSlackService()),
|
|
teamUserService: appcontext.GetTeamUserService(),
|
|
}
|
|
}
|
|
|
|
func (i *incidentService) GetIncidents(c *gin.Context) {
|
|
IncidentId := c.Param("id")
|
|
productIds := c.Query("product_ids")
|
|
reporterTeamIds := c.Query("reporter_team_ids")
|
|
TeamIds := c.Query("team_ids")
|
|
SeverityIds := c.Query("severity_ids")
|
|
Status := c.Query("statuses")
|
|
IncidentName := c.Query("incident_name")
|
|
From := c.Query("from")
|
|
To := c.Query("to")
|
|
userId := i.getRequesterUserIdFromEmail(c.Request.Header.Get(util.UserEmailHeader))
|
|
|
|
if IncidentId != "" {
|
|
numIncidentId, err := strconv.Atoi(IncidentId)
|
|
if err != nil {
|
|
logger.Error("error in converting string to int", zap.String("IncidentId", IncidentId), zap.Error(err))
|
|
c.JSON(
|
|
http.StatusBadRequest,
|
|
common.ErrorResponse(
|
|
errors.New(fmt.Sprintf("%v is not a valid incident ID", IncidentId)),
|
|
http.StatusBadRequest, nil,
|
|
),
|
|
)
|
|
return
|
|
}
|
|
incidentEntity, err := i.incidentRepository.FindIncidentById(uint(numIncidentId))
|
|
if err != nil {
|
|
logger.Error("error in finding incident by id", zap.Int("numIncidentId", numIncidentId))
|
|
c.JSON(
|
|
http.StatusNotFound,
|
|
common.ErrorResponse(
|
|
errors.New(fmt.Sprintf("could not find incident with id: %v", IncidentId)),
|
|
http.StatusNotFound, nil,
|
|
),
|
|
)
|
|
return
|
|
}
|
|
incidentResponse, _ := i.rcaService.GetIncidentResponseWithRCALink(incidentEntity)
|
|
c.JSON(http.StatusOK, common.SuccessResponse(incidentResponse, http.StatusOK))
|
|
return
|
|
}
|
|
|
|
pageSize, pageNumber, err :=
|
|
utils.ValidatePage(c.Query("page_size"), c.Query("page_number"))
|
|
if err != nil {
|
|
logger.Error("error in query parameters", zap.Int64("page_size", pageSize),
|
|
zap.Int64("page_number", pageNumber), zap.Error(err))
|
|
c.JSON(http.StatusBadRequest, common.ErrorResponse(err, http.StatusBadRequest, nil))
|
|
return
|
|
}
|
|
|
|
incidents, totalElements, err := i.GetAllIncidents(request.IncidentFilters{
|
|
ProductIds: productIds,
|
|
ReporterTeamIds: reporterTeamIds,
|
|
TeamIds: TeamIds,
|
|
SeverityIds: SeverityIds,
|
|
StatusIds: Status,
|
|
PageSize: pageSize,
|
|
PageNumber: pageNumber,
|
|
IncidentName: IncidentName,
|
|
From: From,
|
|
To: To,
|
|
}, i.incidentRepository, userId)
|
|
if err != nil {
|
|
logger.Info("error in fetching incidents", zap.Error(err))
|
|
c.JSON(http.StatusBadRequest, common.ErrorResponse(err, http.StatusBadRequest, nil))
|
|
return
|
|
}
|
|
|
|
incidentResponses, err := i.GetIncidentResponseFromIncidentEntity(
|
|
incidents, i.incidentRepository, i.severityRepository, i.teamRepository)
|
|
if err != nil {
|
|
logger.Error("error in GetIncidentResponseFromIncidentEntity", zap.Error(err))
|
|
c.JSON(http.StatusBadRequest, common.ErrorResponse(err, http.StatusBadRequest, nil))
|
|
return
|
|
}
|
|
|
|
page := common.Page{
|
|
PageSize: pageSize,
|
|
PageNumber: pageNumber,
|
|
TotalElements: totalElements,
|
|
}
|
|
|
|
c.JSON(http.StatusOK, common.SuccessPaginatedResponse(incidentResponses, common.Page{
|
|
PageSize: page.PageSize,
|
|
TotalPages: int64(math.Ceil(float64(page.TotalElements) / float64(pageSize))),
|
|
PageNumber: pageNumber,
|
|
TotalElements: page.TotalElements,
|
|
}, http.StatusOK))
|
|
}
|
|
|
|
func (i *incidentService) getRequesterUserIdFromEmail(email string) *uint {
|
|
user, _ := i.userService.GetHoustonUserByEmailId(email)
|
|
if user == nil {
|
|
return nil
|
|
}
|
|
return &user.ID
|
|
}
|
|
|
|
func (i *incidentService) GetIncidentResponseFromIncidentEntity(
|
|
incidents []incident.IncidentEntity,
|
|
incidentRepository *incident.Repository,
|
|
severityRepository *severity.Repository,
|
|
teamRepository *team.Repository,
|
|
) ([]service.IncidentResponse, error) {
|
|
|
|
teams, err := teamRepository.GetAllActiveTeams()
|
|
if err != nil {
|
|
logger.Error("error in fetching teams", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
severities, err := severityRepository.GetAllActiveSeverity()
|
|
if err != nil {
|
|
logger.Error("error in fetching severities", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
incidentStatuses, err := i.incidentStatusService.GetAllIncidentStatuses()
|
|
if err != nil {
|
|
logger.Error("error in fetching incidentStatuses", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
userEmailMappings, err := i.GetUserIdAndIdentityMappingOfAllIncidents(incidents)
|
|
if err != nil {
|
|
logger.Error("error in fetching user emails mapping by ids", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
var incidentResponses []service.IncidentResponse
|
|
for incidentIndex := range incidents {
|
|
incidentResponse, _ := i.rcaService.GetIncidentResponseWithRCALink(&incidents[incidentIndex])
|
|
var reportingTeamResponse *service.IncidentHeaderOption = nil
|
|
if incidents[incidentIndex].ReportingTeamId != nil {
|
|
reportingTeamEntity, err := i.teamService.GetTeamById(*incidents[incidentIndex].ReportingTeamId)
|
|
if err != nil {
|
|
logger.Warn("error in fetching reporting team, returning default team")
|
|
reportingTeamEntity, err = i.teamService.GetTeamById(viper.GetUint("houston.default.team.id"))
|
|
if err != nil {
|
|
logger.Error("error in fetching default team", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
}
|
|
reportingTeamResponse = teamDTOToIncidentHeaderOption(reportingTeamEntity.ToDTO())
|
|
}
|
|
incidentResponse.ReportingTeam = reportingTeamResponse
|
|
var products []product.ProductDTO
|
|
for _, productEntity := range incidents[incidentIndex].Products {
|
|
products = append(products, *productEntity.ToDTO())
|
|
}
|
|
incidentResponse.Products = productDTOToIncidentHeaderOption(products)
|
|
incidentResponses = append(incidentResponses, incidentResponse)
|
|
for _, t := range *teams {
|
|
if t.ID == incidents[incidentIndex].TeamId {
|
|
incidentResponses[incidentIndex].TeamName = t.Name
|
|
}
|
|
}
|
|
|
|
for _, s := range *severities {
|
|
if s.ID == incidents[incidentIndex].SeverityId {
|
|
incidentResponses[incidentIndex].SeverityName = s.Name
|
|
}
|
|
}
|
|
|
|
for _, is := range *incidentStatuses {
|
|
if is.ID == incidents[incidentIndex].Status {
|
|
incidentResponses[incidentIndex].StatusName = is.Name
|
|
}
|
|
}
|
|
if userEmailMappings[incidents[incidentIndex].CreatedBy] != "" {
|
|
incidentResponses[incidentIndex].CreatedBy = userEmailMappings[incidents[incidentIndex].CreatedBy]
|
|
} else {
|
|
incidentResponses[incidentIndex].CreatedBy = incidents[incidentIndex].CreatedBy
|
|
}
|
|
|
|
if userEmailMappings[incidents[incidentIndex].UpdatedBy] != "" {
|
|
incidentResponses[incidentIndex].UpdatedBy = userEmailMappings[incidents[incidentIndex].UpdatedBy]
|
|
} else {
|
|
incidentResponses[incidentIndex].UpdatedBy = incidents[incidentIndex].UpdatedBy
|
|
}
|
|
}
|
|
return incidentResponses, nil
|
|
}
|
|
|
|
func (i *incidentService) GetUserIdAndIdentityMappingOfAllIncidents(incidents []incident.IncidentEntity) (
|
|
map[string]string, error) {
|
|
listOfUserIds := funk.Map(incidents, func(incd incident.IncidentEntity) string {
|
|
return incd.CreatedBy
|
|
}).([]string)
|
|
listOfUserIds = append(listOfUserIds, funk.Map(incidents, func(incd incident.IncidentEntity) string {
|
|
return incd.UpdatedBy
|
|
}).([]string)...)
|
|
userIdAndIdentityMapping, err := i.slackbotClient.GetUserEmailsOrNameByIds(listOfUserIds...)
|
|
if err != nil {
|
|
logger.Error("error in fetching user emails by ids", zap.Any("listOfUserIds", listOfUserIds), zap.Error(err))
|
|
return map[string]string{}, err
|
|
}
|
|
return userIdAndIdentityMapping, nil
|
|
}
|
|
|
|
func (i *incidentService) GetAllIncidents(
|
|
incidentFilters request.IncidentFilters, incidentRepository *incident.Repository, userId *uint,
|
|
) ([]incident.IncidentEntity, int, error) {
|
|
var productIds []uint
|
|
var reporterTeamIds []uint
|
|
var TeamIds []uint
|
|
var SeverityIds []uint
|
|
var StatusIds []uint
|
|
if incidentFilters.ProductIds != "" {
|
|
productIds = i.SplitStringAndGetUintArray(incidentFilters.ProductIds)
|
|
}
|
|
if incidentFilters.ReporterTeamIds != "" {
|
|
reporterTeamIds = i.SplitStringAndGetUintArray(incidentFilters.ReporterTeamIds)
|
|
}
|
|
if incidentFilters.TeamIds != "" {
|
|
TeamIds = i.SplitStringAndGetUintArray(incidentFilters.TeamIds)
|
|
}
|
|
if incidentFilters.SeverityIds != "" {
|
|
SeverityIds = i.SplitStringAndGetUintArray(incidentFilters.SeverityIds)
|
|
}
|
|
if incidentFilters.StatusIds != "" {
|
|
StatusIds = i.SplitStringAndGetUintArray(incidentFilters.StatusIds)
|
|
}
|
|
return incidentRepository.FetchAllIncidentsPaginated(
|
|
productIds, reporterTeamIds, TeamIds, SeverityIds, StatusIds, incidentFilters.PageNumber,
|
|
incidentFilters.PageSize, incidentFilters.IncidentName, incidentFilters.From, incidentFilters.To, userId,
|
|
)
|
|
}
|
|
|
|
func (i *incidentService) SplitStringAndGetUintArray(str string) []uint {
|
|
uintsStr := strings.Split(str, ",")
|
|
|
|
var uints []uint
|
|
for _, uintStr := range uintsStr {
|
|
uintVal, err := strconv.ParseUint(uintStr, 10, 64)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
uints = append(uints, uint(uintVal))
|
|
}
|
|
return uints
|
|
}
|
|
|
|
func (i *incidentService) GetIncidentHeader(c *gin.Context) {
|
|
var incidentHeaderResponse service.IncidentHeaderResponse
|
|
severityEntities, err := i.severityRepository.GetAllActiveSeverity()
|
|
if err != nil {
|
|
logger.Error("error in fetching severities", zap.Error(err))
|
|
}
|
|
for _, severity := range *severityEntities {
|
|
severityResponse := service.IncidentHeaderOption{
|
|
Value: severity.ID,
|
|
Label: severity.Name,
|
|
}
|
|
incidentHeaderResponse.Severities = append(incidentHeaderResponse.Severities, severityResponse)
|
|
}
|
|
|
|
incidentStatuses, err := i.incidentStatusService.GetAllIncidentStatuses()
|
|
if err != nil {
|
|
logger.Error("error in fetching incident statuses", zap.Error(err))
|
|
}
|
|
for _, incidentStatus := range *incidentStatuses {
|
|
incidentStatusResponse := service.IncidentHeaderOption{
|
|
Value: incidentStatus.ID,
|
|
Label: incidentStatus.Name,
|
|
}
|
|
incidentHeaderResponse.IncidentStatuses =
|
|
append(incidentHeaderResponse.IncidentStatuses, incidentStatusResponse)
|
|
}
|
|
|
|
teamEntities, err := i.teamRepository.GetAllActiveTeams()
|
|
if err != nil {
|
|
logger.Error("error in fetching severities", zap.Error(err))
|
|
}
|
|
for _, team := range *teamEntities {
|
|
teamResponse := service.IncidentHeaderOption{
|
|
Value: team.ID,
|
|
Label: team.Name,
|
|
}
|
|
incidentHeaderResponse.Teams = append(incidentHeaderResponse.Teams, teamResponse)
|
|
}
|
|
|
|
products, err := appcontext.GetProductsService().GetAllProducts()
|
|
if err != nil {
|
|
logger.Error(fmt.Sprintf("error in fetching severities %+v", err))
|
|
}
|
|
for _, product := range products {
|
|
productResponse := service.IncidentHeaderOption{
|
|
Value: product.ProductID,
|
|
Label: product.ProductName,
|
|
}
|
|
incidentHeaderResponse.Products = append(incidentHeaderResponse.Products, productResponse)
|
|
}
|
|
c.JSON(http.StatusMultiStatus, common.SuccessResponse(incidentHeaderResponse, http.StatusMultiStatus))
|
|
}
|
|
|
|
func (i *incidentService) GetTeamIncidents(c *gin.Context) {
|
|
var Statuses string
|
|
InputStatuses := c.Query("statuses")
|
|
if InputStatuses == "" {
|
|
incidentStatuses, err := i.incidentStatusService.GetAllIncidentStatuses()
|
|
if err != nil {
|
|
logger.Error("error in fetching incident statuses", zap.Error(err))
|
|
c.JSON(http.StatusBadRequest, common.ErrorResponse(err, http.StatusBadRequest, nil))
|
|
return
|
|
}
|
|
for _, incidentStatus := range *incidentStatuses {
|
|
if incidentStatus.IsTerminalStatus != true {
|
|
Statuses += fmt.Sprintf("%d,", incidentStatus.ID)
|
|
}
|
|
}
|
|
Statuses = Statuses[:len(Statuses)-1]
|
|
}
|
|
incidents, _, err := i.GetAllIncidents(request.IncidentFilters{
|
|
TeamIds: c.Param("teamId"),
|
|
StatusIds: Statuses,
|
|
PageNumber: 0,
|
|
PageSize: viper.GetInt64("CRM_TEAM_INCIDENT_COUNT"),
|
|
}, i.incidentRepository, nil)
|
|
if err != nil {
|
|
logger.Error("error in fetching incidents", zap.Error(err))
|
|
c.JSON(http.StatusBadRequest, common.ErrorResponse(err, http.StatusBadRequest, nil))
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, common.SuccessResponse(incidents, http.StatusOK))
|
|
}
|
|
|
|
func (i *incidentService) InvitePseOnCallPersonToIncident(channelId, ts string) {
|
|
go func() {
|
|
time.Sleep(3 * time.Second)
|
|
msg, _, _, _ := i.socketModeClient.GetConversationReplies(&slack.GetConversationRepliesParameters{
|
|
ChannelID: channelId,
|
|
Timestamp: ts,
|
|
Limit: 2,
|
|
},
|
|
)
|
|
if len(msg) > 1 {
|
|
//User id needs to sliced from `<@XXXXXXXXXXXX>` format to `XXXXXXXXXXXX`
|
|
i.socketModeClient.InviteUsersToConversation(channelId, msg[1].Text[2:13])
|
|
}
|
|
}()
|
|
}
|
|
|
|
func teamDTOToIncidentHeaderOption(t *team.TeamDTO) *service.IncidentHeaderOption {
|
|
return &service.IncidentHeaderOption{
|
|
Value: t.ID,
|
|
Label: t.Name,
|
|
}
|
|
}
|
|
|
|
func productDTOToIncidentHeaderOption(products []product.ProductDTO) []service.IncidentHeaderOption {
|
|
var productResponses []service.IncidentHeaderOption
|
|
for _, p := range products {
|
|
productResponses = append(productResponses, service.IncidentHeaderOption{
|
|
Value: p.ProductID,
|
|
Label: p.ProductName,
|
|
})
|
|
}
|
|
return productResponses
|
|
}
|