From 928a768b8242889ed757bd88497943867014fb23 Mon Sep 17 00:00:00 2001 From: Vijay Joshi Date: Thu, 16 Nov 2023 12:03:34 +0530 Subject: [PATCH] TP-48197 : Implementation of RCA Service, repo, handler, validator and webhook for posting RCA to incident channel along with unit tests (#282) * TP-48197 : Implementation of RCA Service, repo, handler, validator and webhook for posting RCA to incident channel * Added unit tests * Added migration script" * PR Review Changes * Update migratrion --- Makefile | 3 + cmd/app/handler/rca_handler.go | 52 ++++ cmd/app/server.go | 14 ++ common/util/constant.go | 5 + db/migration/000006_add_rca_schema.up.sql | 9 + model/rca/entity.go | 19 ++ repository/rca/impl/rca_repository.go | 41 ++++ repository/rca/rca_repository_interface.go | 8 + service/incident/incident_service_v2.go | 11 + .../incident/incident_service_v2_interface.go | 16 ++ service/rca/rca_service.go | 145 +++++++++++ service/rca/rca_service_test.go | 229 ++++++++++++++++++ service/request/post_rca.go | 17 ++ service/slack/slack_service_interface.go | 33 +++ service/utils/validations.go | 34 +++ 15 files changed, 636 insertions(+) create mode 100644 cmd/app/handler/rca_handler.go create mode 100644 db/migration/000006_add_rca_schema.up.sql create mode 100644 model/rca/entity.go create mode 100644 repository/rca/impl/rca_repository.go create mode 100644 repository/rca/rca_repository_interface.go create mode 100644 service/incident/incident_service_v2_interface.go create mode 100644 service/rca/rca_service.go create mode 100644 service/rca/rca_service_test.go create mode 100644 service/request/post_rca.go create mode 100644 service/slack/slack_service_interface.go diff --git a/Makefile b/Makefile index 5f641c4..782b605 100644 --- a/Makefile +++ b/Makefile @@ -34,3 +34,6 @@ generatemocks: cd $(CURDIR)/pkg/google/googleDrive && minimock -i GoogleDriveActions -s _mock.go -o $(CURDIR)/mocks cd $(CURDIR)/pkg/conference && minimock -i ICalendarActions -s _mock.go -o $(CURDIR)/mocks cd $(CURDIR)/pkg/rest/ && minimock -i ClientActions -s _mock.go -o $(CURDIR)/mocks + cd $(CURDIR)/service/slack && minimock -i ISlackService -s _mock.go -o $(CURDIR)/mocks + cd $(CURDIR)/service/incident && minimock -i IIncidentService -s _mock.go -o $(CURDIR)/mocks + cd $(CURDIR)/repository/rca && minimock -i IRcaRepository -s _mock.go -o $(CURDIR)/mocks diff --git a/cmd/app/handler/rca_handler.go b/cmd/app/handler/rca_handler.go new file mode 100644 index 0000000..de43c9e --- /dev/null +++ b/cmd/app/handler/rca_handler.go @@ -0,0 +1,52 @@ +package handler + +import ( + "fmt" + "github.com/gin-gonic/gin" + "go.uber.org/zap" + "houston/logger" + "houston/service/rca" + request "houston/service/request" + common "houston/service/response/common" + utils "houston/service/utils" + "net/http" +) + +const ( + rcaLogTag = "[rca_handler]" +) + +type RcaHandler struct { + gin *gin.Engine + service *rca.RcaService +} + +func NewRcaHandler(gin *gin.Engine, rcaService *rca.RcaService) *RcaHandler { + return &RcaHandler{ + gin: gin, + service: rcaService, + } +} + +func (handler *RcaHandler) HandlePostRca(c *gin.Context) { + var postRcaRequest request.PostRcaRequest + if err := c.ShouldBindJSON(&postRcaRequest); err != nil { + c.JSON(http.StatusInternalServerError, err) + return + } + + if err := utils.ValidatePostRcaRequest(postRcaRequest); err != nil { + logger.Error(fmt.Sprintf("%s Received invalid request to post Rca", rcaLogTag), zap.Error(err)) + c.JSON(http.StatusBadRequest, common.ErrorResponse(err, http.StatusBadRequest, nil)) + return + } + + err := handler.service.PostRcaToIncidentChannel(postRcaRequest) + + if err != nil { + c.JSON(http.StatusBadRequest, common.ErrorResponse(err, http.StatusBadRequest, nil)) + return + } + + c.JSON(http.StatusOK, common.SuccessResponse("Message posted successfully!", http.StatusOK)) +} diff --git a/cmd/app/server.go b/cmd/app/server.go index cd301ca..5c84054 100644 --- a/cmd/app/server.go +++ b/cmd/app/server.go @@ -12,8 +12,11 @@ import ( "houston/logger" "houston/model/ingester" "houston/pkg/slackbot" + rcaRepository "houston/repository/rca/impl" "houston/service" "houston/service/incident" + "houston/service/rca" + "houston/service/slack" "net/http" "strings" "time" @@ -39,6 +42,7 @@ func (s *Server) Handler(houstonGroup *gin.RouterGroup) { s.incidentClientHandler(houstonGroup) s.incidentClientHandlerV2(houstonGroup) s.filtersHandlerV2(houstonGroup) + s.rcaHandler(houstonGroup) s.gin.Use(s.createMiddleware()) s.teamHandler(houstonGroup) s.severityHandler(houstonGroup) @@ -150,6 +154,16 @@ func (s *Server) logHandler(houstonGroup *gin.RouterGroup) { houstonGroup.GET("/logs/:log_type/:id", logHandler.GetLogs) } +func (s *Server) rcaHandler(houstonGroup *gin.RouterGroup) { + slackService := slack.NewSlackService() + incidentServiceV2 := incident.NewIncidentServiceV2(s.db) + rcaRepository := rcaRepository.NewRcaRepository(s.db) + rcaService := rca.NewRcaService(incidentServiceV2, slackService, rcaRepository) + rcaHandler := handler.NewRcaHandler(s.gin, rcaService) + + houstonGroup.POST("/rca", rcaHandler.HandlePostRca) +} + func (s *Server) usersHandler(houstonGroup *gin.RouterGroup) { houstonClient := NewHoustonClient() slackClient := slackbot.NewSlackClient(houstonClient.socketModeClient) diff --git a/common/util/constant.go b/common/util/constant.go index 9a4bc53..adc29dd 100644 --- a/common/util/constant.go +++ b/common/util/constant.go @@ -71,3 +71,8 @@ const ( DocumentServiceDOCFileType = "DOC" DocumentServiceDOCXFileType = "DOCX" ) + +const ( + RcaStatusSuccess = "SUCCESS" + RcaStatusFailure = "FAILURE" +) diff --git a/db/migration/000006_add_rca_schema.up.sql b/db/migration/000006_add_rca_schema.up.sql new file mode 100644 index 0000000..53a6595 --- /dev/null +++ b/db/migration/000006_add_rca_schema.up.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS rca ( + id SERIAL PRIMARY KEY, + incident_id integer NOT NULL REFERENCES incident(id), + rca_link text, + rca_input_links text[], + status text NOT NULL, + created_at timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP +); \ No newline at end of file diff --git a/model/rca/entity.go b/model/rca/entity.go new file mode 100644 index 0000000..9d76a9b --- /dev/null +++ b/model/rca/entity.go @@ -0,0 +1,19 @@ +package rca + +import ( + "time" +) + +type RcaEntity struct { + ID uint `gorm:"primaryKey"` + IncidentID uint `gorm:"index"` + RcaLink string `gorm:"type:text"` + RcaInputLinks []string `gorm:"type:text[]"` + Status string `gorm:"type:text"` + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` +} + +func (RcaEntity) TableName() string { + return "rca" +} diff --git a/repository/rca/impl/rca_repository.go b/repository/rca/impl/rca_repository.go new file mode 100644 index 0000000..a22426c --- /dev/null +++ b/repository/rca/impl/rca_repository.go @@ -0,0 +1,41 @@ +package impl + +import ( + "gorm.io/gorm" + "houston/model/rca" +) + +type Repository struct { + gormClient *gorm.DB +} + +func NewRcaRepository(gormClient *gorm.DB) *Repository { + return &Repository{ + gormClient: gormClient, + } +} + +func (r *Repository) FetchRcaByIncidentId(incidentID uint) (*rca.RcaEntity, error) { + var rcaEntity rca.RcaEntity + + query := r.gormClient.Model(&rca.RcaEntity{}) + + query = query.Where("incident_id = ?", incidentID) + + result := query.First(&rcaEntity) + + if result.Error != nil { + return nil, result.Error + } + + return &rcaEntity, nil +} + +func (r *Repository) UpdateRca(rcaEntity *rca.RcaEntity) error { + result := r.gormClient.Updates(rcaEntity) + if result.Error != nil { + return result.Error + } + + return nil +} diff --git a/repository/rca/rca_repository_interface.go b/repository/rca/rca_repository_interface.go new file mode 100644 index 0000000..bae4f10 --- /dev/null +++ b/repository/rca/rca_repository_interface.go @@ -0,0 +1,8 @@ +package rca + +import "houston/model/rca" + +type IRcaRepository interface { + FetchRcaByIncidentId(incidentID uint) (*rca.RcaEntity, error) + UpdateRca(rcaEntity *rca.RcaEntity) error +} diff --git a/service/incident/incident_service_v2.go b/service/incident/incident_service_v2.go index 485c112..c69b5f1 100644 --- a/service/incident/incident_service_v2.go +++ b/service/incident/incident_service_v2.go @@ -150,6 +150,17 @@ func (i *IncidentServiceV2) CreateIncident( return service.ConvertToIncidentResponse(*incidentEntity), nil } +func (i *IncidentServiceV2) GetIncidentById(incidentId uint) (*incident.IncidentEntity, error) { + logger.Info(fmt.Sprintf("received request to get incident by id for id: %d", incidentId)) + incidentEntity, err := i.incidentRepository.FindIncidentById(incidentId) + if err != nil { + errorMessage := fmt.Sprintf("failed to fetch indient by id: %d", incidentId) + logger.Error(errorMessage, zap.Error(err)) + return nil, err + } + return incidentEntity, nil +} + func (i *IncidentServiceV2) LinkJiraToIncident(incidentId uint, linkedBy string, jiraLinks ...string) error { logger.Info(fmt.Sprintf("%s received request to link JIRA to %d", logTag, incidentId)) logTag := fmt.Sprintf("%s [LinkJiraToIncident]", logTag) diff --git a/service/incident/incident_service_v2_interface.go b/service/incident/incident_service_v2_interface.go new file mode 100644 index 0000000..a7b8854 --- /dev/null +++ b/service/incident/incident_service_v2_interface.go @@ -0,0 +1,16 @@ +package incident + +import ( + "houston/model/incident" + request "houston/service/request" + service "houston/service/response" +) + +type IIncidentService interface { + CreateIncident(request request.CreateIncidentRequestV2, source string, blazeGroupChannelID string) (service.IncidentResponse, error) + GetIncidentById(incidentId uint) (*incident.IncidentEntity, error) + LinkJiraToIncident(incidentId uint, linkedBy string, jiraLinks ...string) error + UnLinkJiraFromIncident(incidentId uint, unLinkedBy, jiraLink string) error + GetAllOpenIncidents() ([]incident.IncidentEntity, int, error) + GetIncidentRoleByIncidentIdAndRole(incidentId uint, role string) (*incident.IncidentRoleEntity, error) +} diff --git a/service/rca/rca_service.go b/service/rca/rca_service.go new file mode 100644 index 0000000..6c71744 --- /dev/null +++ b/service/rca/rca_service.go @@ -0,0 +1,145 @@ +package rca + +import ( + "encoding/json" + "errors" + "fmt" + "go.uber.org/zap" + "houston/common/util" + "houston/logger" + "houston/repository/rca" + "houston/service/incident" + service "houston/service/request" + slackService "houston/service/slack" + "time" +) + +type RcaService struct { + incidentService incident.IIncidentService + slackService slackService.ISlackService + rcaRepository rca.IRcaRepository +} + +const logTag = "[post-rca]" + +func NewRcaService(incidentService incident.IIncidentService, slackService slackService.ISlackService, rcaRepository rca.IRcaRepository) *RcaService { + return &RcaService{ + incidentService: incidentService, + slackService: slackService, + rcaRepository: rcaRepository, + } +} + +func (r *RcaService) PostRcaToIncidentChannel(postRcaRequest service.PostRcaRequest) error { + incidentEntity, err := r.incidentService.GetIncidentById(postRcaRequest.IncidentID) + if err != nil { + logger.Error( + fmt.Sprintf("%s Error while fetching incident by id: %d", logTag, postRcaRequest.IncidentID), + zap.Error(err), + ) + return errors.New("Could not find incident with given id") + } + + rcaEntity, err := r.rcaRepository.FetchRcaByIncidentId(postRcaRequest.IncidentID) + if err != nil { + logger.Error( + fmt.Sprintf("%s Error while fetching rca by incident id: %d", logTag, postRcaRequest.IncidentID), + zap.Error(err), + ) + return errors.New("Could not find rca entry for the given incident id") + } + + if rcaEntity == nil { + errorMessage := fmt.Sprintf("%s Could not find rca with incident id %d", logTag, postRcaRequest.IncidentID) + logger.Error(errorMessage) + return errors.New(errorMessage) + } + + switch postRcaRequest.Status { + case util.RcaStatusSuccess: + var successData service.SuccessData + err := json.Unmarshal(postRcaRequest.Data, &successData) + if err != nil { + logger.Error( + fmt.Sprintf("%s Error while unmarshaling json : %s to sucess data", logTag, postRcaRequest.Data), + zap.Error(err), + ) + return err + } + + rcaEntity.RcaLink = successData.RcaLink + rcaEntity.Status = "SUCCESS" + rcaEntity.UpdatedAt = time.Now() + + err = r.rcaRepository.UpdateRca(rcaEntity) + if err != nil { + logger.Error( + fmt.Sprintf("%s Error while updating rca", logTag), + zap.Error(err), + ) + return err + } + + _, err = r.slackService.PostMessageByChannelID( + fmt.Sprintf("This incident’s generated RCA can be viewed *<%s|here>*", successData.RcaLink), + false, + incidentEntity.SlackChannel, + ) + if err != nil { + logger.Error( + fmt.Sprintf( + "%s Error in posting rca link to slack channel with channel id: %s", + logTag, + incidentEntity.SlackChannel, + ), + zap.Error(err), + ) + return errors.New("Could not post RCA to the given incident channel") + } + + case util.RcaStatusFailure: + var failureData service.FailureData + err := json.Unmarshal(postRcaRequest.Data, &failureData) + if err != nil { + logger.Error( + fmt.Sprintf("%s Error while unmarshaling json : %s to failure data", logTag, postRcaRequest.Data), + zap.Error(err), + ) + return err + } + + logger.Info(fmt.Sprintf("RCA generation had failed due to the reason: %s", failureData.Reason)) + + rcaEntity.Status = "FAILURE" + rcaEntity.UpdatedAt = time.Now() + + err = r.rcaRepository.UpdateRca(rcaEntity) + if err != nil { + logger.Error( + fmt.Sprintf("%s Error while updating rca table entry", logTag), + zap.Error(err), + ) + return err + } + + _, err = r.slackService.PostMessageByChannelID( + "Some issue occurred while generating RCA", + false, + incidentEntity.SlackChannel, + ) + + if err != nil { + logger.Error( + fmt.Sprintf( + "%s Error in posting rca failure to slack channel with channel id: %s", + logTag, + incidentEntity.SlackChannel, + ), + zap.Error(err), + ) + return errors.New("Could not post failure message to the given incident channel") + } + } + + return nil +} diff --git a/service/rca/rca_service_test.go b/service/rca/rca_service_test.go new file mode 100644 index 0000000..40c229a --- /dev/null +++ b/service/rca/rca_service_test.go @@ -0,0 +1,229 @@ +package rca + +import ( + "encoding/json" + "errors" + "fmt" + "github.com/stretchr/testify/assert" + "gorm.io/gorm" + "houston/logger" + "houston/mocks" + incident "houston/model/incident" + "houston/model/rca" + service "houston/service/request" + "testing" + "time" +) + +func setupTest(t *testing.T) (*mocks.IIncidentServiceMock, *mocks.ISlackServiceMock, *mocks.IRcaRepositoryMock, *RcaService) { + logger.InitLogger() + incidentService := mocks.NewIIncidentServiceMock(t) + slackService := mocks.NewISlackServiceMock(t) + rcaRepository := mocks.NewIRcaRepositoryMock(t) + rcaService := NewRcaService(incidentService, slackService, rcaRepository) + + return incidentService, slackService, rcaRepository, rcaService +} + +func TestRcaService_PostRcaToIncidentChannel_Success(t *testing.T) { + incidentService, slackService, rcaRepository, rcaService := setupTest(t) + + incidentService.GetIncidentByIdMock.When(1).Then(GetMockIncident(), nil) + + testRca := getMockRCA() + + rcaRepository.FetchRcaByIncidentIdMock.When(1).Then(testRca, nil) + + rcaRepository.UpdateRcaMock.When(testRca).Then(nil) + + slackService.PostMessageByChannelIDMock.When( + fmt.Sprintf("This incident’s generated RCA can be viewed *<%s|here>*", "test_link"), + false, + "1234", + ).Then("", nil) + + testData, _ := json.Marshal(service.SuccessData{RcaLink: "test_link"}) + + err := rcaService.PostRcaToIncidentChannel(service.PostRcaRequest{ + IncidentID: 1, + Status: "SUCCESS", + Data: testData, + }) + assert.NoError(t, err, "Expected no error") +} + +func TestRcaService_PostRcaToIncidentChannel_Failure(t *testing.T) { + incidentService, slackService, rcaRepository, rcaService := setupTest(t) + + incidentService.GetIncidentByIdMock.When(1).Then(GetMockIncident(), nil) + + testRca := getMockRCA() + + rcaRepository.FetchRcaByIncidentIdMock.When(1).Then(testRca, nil) + + rcaRepository.UpdateRcaMock.When(testRca).Then(nil) + + slackService.PostMessageByChannelIDMock.When( + "Some issue occurred while generating RCA", + false, + "1234", + ).Then("", nil) + + testData, _ := json.Marshal(service.FailureData{Reason: "transcript file too long"}) + + err := rcaService.PostRcaToIncidentChannel(service.PostRcaRequest{ + IncidentID: 1, + Status: "FAILURE", + Data: testData, + }) + + assert.NoError(t, err, "Expected no error") +} + +func TestRcaService_PostRcaToIncidentChannel_InvalidIncident(t *testing.T) { + incidentService, _, _, rcaService := setupTest(t) + + incidentService.GetIncidentByIdMock.When(1).Then(nil, errors.New("record not found")) + + testData, _ := json.Marshal(service.SuccessData{RcaLink: "test_link"}) + err := rcaService.PostRcaToIncidentChannel(service.PostRcaRequest{ + IncidentID: 1, + Status: "SUCCESS", + Data: testData, + }) + + assert.EqualError(t, err, "Could not find incident with given id") +} + +func TestRcaService_PostRcaToIncidentChannel_NonExistingRca(t *testing.T) { + incidentService, _, rcaRepository, rcaService := setupTest(t) + + incidentService.GetIncidentByIdMock.When(1).Then(GetMockIncident(), nil) + + rcaRepository.FetchRcaByIncidentIdMock.When(1).Then(nil, errors.New("record not found")) + + testData, _ := json.Marshal(service.SuccessData{RcaLink: "test_link"}) + err := rcaService.PostRcaToIncidentChannel(service.PostRcaRequest{ + IncidentID: 1, + Status: "SUCCESS", + Data: testData, + }) + + assert.EqualError(t, err, "Could not find rca entry for the given incident id") +} + +func TestRcaService_PostRcaToIncidentChannel_UpdateFailure(t *testing.T) { + incidentService, _, rcaRepository, rcaService := setupTest(t) + + incidentService.GetIncidentByIdMock.When(1).Then(GetMockIncident(), nil) + + testRca := getMockRCA() + + testData, _ := json.Marshal(service.SuccessData{RcaLink: "test_link"}) + + rcaRepository.FetchRcaByIncidentIdMock.When(1).Then(testRca, nil) + + rcaRepository.UpdateRcaMock.When(testRca).Then(errors.New("DB error")) + + err := rcaService.PostRcaToIncidentChannel(service.PostRcaRequest{ + IncidentID: 1, + Status: "SUCCESS", + Data: testData, + }) + + assert.EqualError(t, err, "DB error") +} + +func TestRcaService_PostRcaToIncidentChannel_SlackFailure_RcaSuccessCase(t *testing.T) { + incidentService, slackService, rcaRepository, rcaService := setupTest(t) + + incidentService.GetIncidentByIdMock.When(1).Then(GetMockIncident(), nil) + + testRca := getMockRCA() + + testData, _ := json.Marshal(service.SuccessData{RcaLink: "test_link"}) + + rcaRepository.FetchRcaByIncidentIdMock.When(1).Then(testRca, nil) + + rcaRepository.UpdateRcaMock.When(testRca).Then(nil) + + slackService.PostMessageByChannelIDMock.When( + fmt.Sprintf("This incident’s generated RCA can be viewed *<%s|here>*", "test_link"), + false, + "1234", + ).Then("", errors.New("failed to send message")) + + err := rcaService.PostRcaToIncidentChannel(service.PostRcaRequest{ + IncidentID: 1, + Status: "SUCCESS", + Data: testData, + }) + + assert.EqualError(t, err, "Could not post RCA to the given incident channel") +} + +func TestRcaService_PostRcaToIncidentChannel_SlackFailure_RcaFailureCase(t *testing.T) { + incidentService, slackService, rcaRepository, rcaService := setupTest(t) + + incidentService.GetIncidentByIdMock.When(1).Then(GetMockIncident(), nil) + + testRca := getMockRCA() + + rcaRepository.FetchRcaByIncidentIdMock.When(1).Then(testRca, nil) + + rcaRepository.UpdateRcaMock.When(testRca).Then(nil) + + slackService.PostMessageByChannelIDMock.When( + "Some issue occurred while generating RCA", + false, + "1234", + ).Then("", errors.New("failed to send message")) + + testData, _ := json.Marshal(service.FailureData{Reason: "transcript file too long"}) + + err := rcaService.PostRcaToIncidentChannel(service.PostRcaRequest{ + IncidentID: 1, + Status: "FAILURE", + Data: testData, + }) + + assert.EqualError(t, err, "Could not post failure message to the given incident channel") +} + +func GetMockIncident() *incident.IncidentEntity { + return &incident.IncidentEntity{ + Model: gorm.Model{}, + Title: "Mock Title", + Description: "Mock Description", + Status: 1, + SeverityId: 4, + IncidentName: "Mock Name", + SlackChannel: "1234", + DetectionTime: nil, + StartTime: time.Time{}, + EndTime: nil, + TeamId: 1, + JiraLinks: nil, + ConfluenceId: nil, + SeverityTat: time.Time{}, + RemindMeAt: nil, + EnableReminder: false, + CreatedBy: "123456", + UpdatedBy: "123456", + MetaData: nil, + RCA: "", + ConferenceId: "", + ConferenceLink: "", + } +} + +func getMockRCA() *rca.RcaEntity { + return &rca.RcaEntity{ + ID: 1, + IncidentID: 1, + RcaLink: "", + RcaInputLinks: nil, + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + } +} diff --git a/service/request/post_rca.go b/service/request/post_rca.go new file mode 100644 index 0000000..160b64d --- /dev/null +++ b/service/request/post_rca.go @@ -0,0 +1,17 @@ +package service + +import "gorm.io/datatypes" + +type PostRcaRequest struct { + IncidentID uint `json:"incident_id"` + Status string `json:"status"` + Data datatypes.JSON `json:"data"` +} + +type SuccessData struct { + RcaLink string `json:"rca_link"` +} + +type FailureData struct { + Reason string `json:"reason"` +} diff --git a/service/slack/slack_service_interface.go b/service/slack/slack_service_interface.go new file mode 100644 index 0000000..57d39af --- /dev/null +++ b/service/slack/slack_service_interface.go @@ -0,0 +1,33 @@ +package slack + +import ( + "github.com/slack-go/slack" + "houston/model/incident" + "houston/model/severity" + "houston/model/team" + service "houston/service/response" +) + +type ISlackService interface { + SendDM(recipientID string, messageText string) error + PostMessage(message string, escape bool, channel *slack.Channel) (string, error) + AddBookmark(bookmark string, channel *slack.Channel, title string) + PostMessageOption(channelID string, messageOption slack.MsgOption) (string, error) + PostMessageByChannelID(message string, escape bool, channelID string) (string, error) + PostEphemeralByChannelID(message string, userID string, escape bool, channelID string) error + PostMessageBlocks(channelID string, blocks slack.Blocks, color string) (string, error) + GetConversationReplies(channelID string, timeStamp string, limit int) (msgs []slack.Message, hasMore bool, nextCursor string, err error) + SetChannelTopic(channel *slack.Channel, teamEntity *team.TeamEntity, severityEntity *severity.SeverityEntity, incidentEntity *incident.IncidentEntity) error + GetUserByEmailOrID(userEmailOrSlackID string) (*slack.User, error) + GetUserByEmail(userEmail string) (*slack.User, error) + GetUserBySlackID(slackUserID string) (*slack.User, error) + GetSlackUserIdOrEmail(email string) string + IsASlackUser(slackIdOrEmail string) (bool, *slack.User) + CreateSlackChannel(incidentId uint) (*slack.Channel, error) + InviteUsersToConversation(channelId string, userIds ...string) error + GetSlackConversationHistoryWithReplies(channelId string) ([]service.ConversationResponse, error) + GetChannelConversationHistory(channelId string) ([]slack.Message, error) + GetAllUsersInConversation(channelId string) ([]string, error) + GetNonBotUsersInConversation(channelID string) ([]string, error) + GetUsersInfo(users ...string) *[]slack.User +} diff --git a/service/utils/validations.go b/service/utils/validations.go index 9c9d27d..39c5dd1 100644 --- a/service/utils/validations.go +++ b/service/utils/validations.go @@ -1,6 +1,7 @@ package service import ( + "encoding/json" "errors" "fmt" "github.com/google/uuid" @@ -83,6 +84,34 @@ func ValidateCreateIncidentRequestV2(request service.CreateIncidentRequestV2) er return nil } +func ValidatePostRcaRequest(request service.PostRcaRequest) error { + if request.Status == StatusSuccess { + var successData service.SuccessData + err := json.Unmarshal(request.Data, &successData) + if err != nil { + return errors.New("Invalid success data") + } + + if successData.RcaLink == "" { + return errors.New("Please provide RCA link on successful RCA generation") + } + } else if request.Status == StatusFailure { + var failureData service.FailureData + err := json.Unmarshal(request.Data, &failureData) + if err != nil { + return errors.New("Invalid failure data") + } + + if failureData.Reason == "" { + return errors.New("Please provide failure reason for RCA generation failure") + } + } else { + return errors.New("rca generation status must be specified as 'SUCCESS' or 'FAILURE'") + } + + return nil +} + func ValidateLinkJiraRequest(request service.LinkJiraRequest) error { var errorMessage []string if request.IncidentID == 0 { @@ -135,3 +164,8 @@ func ValidateIdParameter(id string) (uint, error) { } return uint(ID), nil } + +const ( + StatusSuccess = "SUCCESS" + StatusFailure = "FAILURE" +)