Files
houston-be/service/incident_service.go
Amitesh Vijaykumar Magar 5ed80d4516 NTP-44973 | Return default Houston team when reporting team is inactive (#483)
* NTP-44973 | Return default Houston team when reporting team is found inactive.

* NTP-44973 | Changing defaultTeamId from constant to env_var.
2025-03-05 16:30:40 +05:30

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
}