Jira link table (#331)
* TP-51013 | incident_jira entity, repo and service * TP-51013 | get jira status api * TP-51013 | added db migration file * TP-51013 | added migration query to migrate existing jira links into new table * TP-51013 | removing linked_jira_issues column from incident table * TP-51013 | removing empty jira fields if no response found for a jira key in jira api response * TP-51013 | handled jira api failure cases, will return empty jira fields * TP-51013 | removed linked_jira_issues field from incident entity * TP-51013 | handled jira link addition and removal in slack action * TP-51013 | resolving PR comments * TP-51013 | adding jira link max length check
This commit is contained in:
2
Makefile
2
Makefile
@@ -60,3 +60,5 @@ generatemocks:
|
|||||||
cd $(CURDIR)/service/krakatoa && minimock -i IKrakatoaService -s _mock.go -o $(CURDIR)/mocks
|
cd $(CURDIR)/service/krakatoa && minimock -i IKrakatoaService -s _mock.go -o $(CURDIR)/mocks
|
||||||
cd $(CURDIR)/pkg/socketModeClient && minimock -i ISocketModeClientWrapper -s _mock.go -o $(CURDIR)/mocks
|
cd $(CURDIR)/pkg/socketModeClient && minimock -i ISocketModeClientWrapper -s _mock.go -o $(CURDIR)/mocks
|
||||||
cd $(CURDIR)/pkg/maverick && minimock -i IMaverickClient -s _mock.go -o $(CURDIR)/mocks
|
cd $(CURDIR)/pkg/maverick && minimock -i IMaverickClient -s _mock.go -o $(CURDIR)/mocks
|
||||||
|
cd $(CURDIR)/service/incident_jira && minimock -i IncidentJiraService -s _mock.go -o $(CURDIR)/mocks
|
||||||
|
cd $(CURDIR)/model/incident_jira && minimock -i IncidentJiraRepository -s _mock.go -o $(CURDIR)/mocks
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ type IncidentHandler struct {
|
|||||||
gin *gin.Engine
|
gin *gin.Engine
|
||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
authService *service.AuthService
|
authService *service.AuthService
|
||||||
service *incident.IncidentServiceV2
|
service *incident.IncidentServiceV2
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIncidentHandler(gin *gin.Engine, db *gorm.DB, authService *service.AuthService, incidentService *incident.IncidentServiceV2) *IncidentHandler {
|
func NewIncidentHandler(gin *gin.Engine, db *gorm.DB, authService *service.AuthService, incidentService *incident.IncidentServiceV2) *IncidentHandler {
|
||||||
@@ -32,7 +32,7 @@ func NewIncidentHandler(gin *gin.Engine, db *gorm.DB, authService *service.AuthS
|
|||||||
gin: gin,
|
gin: gin,
|
||||||
db: db,
|
db: db,
|
||||||
authService: authService,
|
authService: authService,
|
||||||
service: incidentService,
|
service: incidentService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,10 +57,10 @@ func (handler *IncidentHandler) HandleCreateIncident(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, common.SuccessResponse(incidentResponse, http.StatusOK))
|
c.JSON(http.StatusOK, common.SuccessResponse(incidentResponse, http.StatusOK))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *IncidentHandler) HandleUpdateIncident(c *gin.Context) {
|
func (handler *IncidentHandler) HandleUpdateIncident(c *gin.Context) {
|
||||||
userEmail := c.GetHeader(util.UserEmailHeader)
|
userEmail := c.GetHeader(util.UserEmailHeader)
|
||||||
sessionToken := c.GetHeader(util.SessionTokenHeader)
|
sessionToken := c.GetHeader(util.SessionTokenHeader)
|
||||||
isValidUser, err := h.authService.CheckValidUser(sessionToken, userEmail)
|
isValidUser, err := handler.authService.CheckValidUser(sessionToken, userEmail)
|
||||||
if err != nil || !isValidUser {
|
if err != nil || !isValidUser {
|
||||||
c.JSON(http.StatusUnauthorized, common.ErrorResponse(errors.New("Unauthorized user"), http.StatusUnauthorized, nil))
|
c.JSON(http.StatusUnauthorized, common.ErrorResponse(errors.New("Unauthorized user"), http.StatusUnauthorized, nil))
|
||||||
return
|
return
|
||||||
@@ -77,7 +77,7 @@ func (h *IncidentHandler) HandleUpdateIncident(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
incidentServiceV2 := incident.NewIncidentServiceV2(h.db)
|
incidentServiceV2 := incident.NewIncidentServiceV2(handler.db)
|
||||||
|
|
||||||
result, err := incidentServiceV2.UpdateIncident(updateIncidentRequest, userEmail)
|
result, err := incidentServiceV2.UpdateIncident(updateIncidentRequest, userEmail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -136,3 +136,20 @@ func (handler *IncidentHandler) HandleJiraUnLinking(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, common.SuccessResponse("JIRA link removed successfully", http.StatusOK))
|
c.JSON(http.StatusOK, common.SuccessResponse("JIRA link removed successfully", http.StatusOK))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (handler *IncidentHandler) HandleGetJiraStatuses(c *gin.Context) {
|
||||||
|
IncidentName := c.Query("incident_name")
|
||||||
|
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
|
||||||
|
}
|
||||||
|
statuses, err := handler.service.GetJiraStatuses(IncidentName, pageNumber, pageSize)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, err)
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, statuses)
|
||||||
|
}
|
||||||
|
|||||||
@@ -160,6 +160,7 @@ func (s *Server) incidentClientHandlerV2(houstonGroup *gin.RouterGroup) {
|
|||||||
}
|
}
|
||||||
houstonGroup.POST("/link-jira-to-incident", incidentHandler.HandleJiraLinking)
|
houstonGroup.POST("/link-jira-to-incident", incidentHandler.HandleJiraLinking)
|
||||||
houstonGroup.POST("/unlink-jira-from-incident", incidentHandler.HandleJiraUnLinking)
|
houstonGroup.POST("/unlink-jira-from-incident", incidentHandler.HandleJiraUnLinking)
|
||||||
|
houstonGroup.GET("/get-jira-statuses", incidentHandler.HandleGetJiraStatuses)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) incidentHandler(houstonGroup *gin.RouterGroup) {
|
func (s *Server) incidentHandler(houstonGroup *gin.RouterGroup) {
|
||||||
|
|||||||
@@ -88,6 +88,28 @@ func Difference(s1, s2 []string) []string {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetDifference[T comparable](s1, s2 []T) []T {
|
||||||
|
combinedSlice := append(s1, s2...)
|
||||||
|
m := make(map[T]int)
|
||||||
|
for _, v := range combinedSlice {
|
||||||
|
if _, ok := m[v]; ok {
|
||||||
|
// remove element later as it exists in both slices.
|
||||||
|
m[v] += 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// new entry, add to the map!
|
||||||
|
m[v] = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
var result []T
|
||||||
|
for k, v := range m {
|
||||||
|
if v == 1 {
|
||||||
|
result = append(result, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func Intersection(s1, s2 []string) (inter []string) {
|
func Intersection(s1, s2 []string) (inter []string) {
|
||||||
hash := make(map[string]bool)
|
hash := make(map[string]bool)
|
||||||
for _, e := range s1 {
|
for _, e := range s1 {
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ create-incident-v2-enabled=CREATE_INCIDENT_V2_ENABLED
|
|||||||
get-teams.v2.enabled=GET_TEAMS_V2_ENABLED
|
get-teams.v2.enabled=GET_TEAMS_V2_ENABLED
|
||||||
#slack details
|
#slack details
|
||||||
slack.workspace.id=SLACK_WORKSPACE_ID
|
slack.workspace.id=SLACK_WORKSPACE_ID
|
||||||
navi.jira.base.url=https://navihq.atlassian.net/
|
navi.jira.base.url=https://navihq.atlassian.net/browse/
|
||||||
|
|
||||||
houston.channel.help.message=/houston: General command to open the other options| /houston severity: Opens the view to update severity of the incident| /houston set severity to <severities_label>: Sets the incident severity| /houston team: Opens the view to update team| /houston set team to <incident team>: Sets the incident team| /houston status: Opens the view to set status| /houston set status to <Investigating/Identified/Monitoring/Resolved>: Sets the incident status| /houston description: Opens the view to set incident description| /houston set description to <incident description>: Sets the incident description| /houston resolve: Opens the view to fill RCA and resolve| /houston rca: Opens the view to fill RCA
|
houston.channel.help.message=/houston: General command to open the other options| /houston severity: Opens the view to update severity of the incident| /houston set severity to <severities_label>: Sets the incident severity| /houston team: Opens the view to update team| /houston set team to <incident team>: Sets the incident team| /houston status: Opens the view to set status| /houston set status to <Investigating/Identified/Monitoring/Resolved>: Sets the incident status| /houston description: Opens the view to set incident description| /houston set description to <incident description>: Sets the incident description| /houston resolve: Opens the view to fill RCA and resolve| /houston rca: Opens the view to fill RCA
|
||||||
non.houston.channel.help.message=/houston: General command to open the other options| /houston start: Opens the view to start a new incident| /houston start <severity> <team> title <incident title> description <incident description>: Starts an incident of a specific severity and team
|
non.houston.channel.help.message=/houston: General command to open the other options| /houston start: Opens the view to start a new incident| /houston start <severity> <team> title <incident title> description <incident description>: Starts an incident of a specific severity and team
|
||||||
@@ -94,3 +94,4 @@ non.houston.channel.help.message=/houston: General command to open the other opt
|
|||||||
jira.base.url=JIRA_BASE_URL
|
jira.base.url=JIRA_BASE_URL
|
||||||
jira.username=JIRA_USERNAME
|
jira.username=JIRA_USERNAME
|
||||||
jira.api.token=JIRA_API_TOKEN
|
jira.api.token=JIRA_API_TOKEN
|
||||||
|
jira.link.max.length=JIRA_LINK_MAX_LENGTH
|
||||||
18
db/migration/000010_incident_jira_table.up.sql
Normal file
18
db/migration/000010_incident_jira_table.up.sql
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
CREATE TABLE if not exists incident_jira (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
created_at timestamp with time zone,
|
||||||
|
incident_id bigint REFERENCES incident(id),
|
||||||
|
jira_link TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Migrate existing jira links -------------------------------------------------------
|
||||||
|
|
||||||
|
INSERT INTO incident_jira (incident_id, jira_link, created_at)
|
||||||
|
SELECT incident_id, jira_link, now()
|
||||||
|
FROM (SELECT id AS incident_id, unnest(jira_links) AS jira_link
|
||||||
|
FROM incident
|
||||||
|
WHERE jira_links IS NOT NULL
|
||||||
|
AND array_length(jira_links, 1) > 0) AS subquery
|
||||||
|
WHERE jira_link <> '';
|
||||||
|
|
||||||
|
|
||||||
4
go.mod
4
go.mod
@@ -15,7 +15,7 @@ require (
|
|||||||
github.com/google/uuid v1.4.0
|
github.com/google/uuid v1.4.0
|
||||||
github.com/jackc/pgx/v5 v5.3.1
|
github.com/jackc/pgx/v5 v5.3.1
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/lib/pq v1.10.7
|
github.com/lib/pq v1.10.9
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/slack-go/slack v0.12.1
|
github.com/slack-go/slack v0.12.1
|
||||||
github.com/spf13/cobra v1.6.1
|
github.com/spf13/cobra v1.6.1
|
||||||
@@ -54,7 +54,7 @@ require (
|
|||||||
github.com/chromedp/sysutil v1.0.0 // indirect
|
github.com/chromedp/sysutil v1.0.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
github.com/go-sql-driver/mysql v1.7.1 // indirect
|
||||||
github.com/gobwas/httphead v0.1.0 // indirect
|
github.com/gobwas/httphead v0.1.0 // indirect
|
||||||
github.com/gobwas/pool v0.2.1 // indirect
|
github.com/gobwas/pool v0.2.1 // indirect
|
||||||
github.com/gobwas/ws v1.1.0 // indirect
|
github.com/gobwas/ws v1.1.0 // indirect
|
||||||
|
|||||||
@@ -5,18 +5,22 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/slack-go/slack"
|
"github.com/slack-go/slack"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
"houston/appcontext"
|
||||||
"houston/common/util"
|
"houston/common/util"
|
||||||
"houston/internal/processor/action/view"
|
"houston/internal/processor/action/view"
|
||||||
"houston/logger"
|
"houston/logger"
|
||||||
"houston/model/incident"
|
"houston/model/incident"
|
||||||
|
incidentJiraModel "houston/model/incident_jira"
|
||||||
incidentService "houston/service/incident"
|
incidentService "houston/service/incident"
|
||||||
|
"houston/service/incident_jira"
|
||||||
slack2 "houston/service/slack"
|
slack2 "houston/service/slack"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IncidentJiraLinksAction struct {
|
type IncidentJiraLinksAction struct {
|
||||||
incidentService incidentService.IIncidentService
|
incidentService incidentService.IIncidentService
|
||||||
slackService slack2.ISlackService
|
incidentJiraService incident_jira.IncidentJiraService
|
||||||
|
slackService slack2.ISlackService
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -28,8 +32,9 @@ func NewIncidentJiraLinksAction(
|
|||||||
slackService slack2.ISlackService,
|
slackService slack2.ISlackService,
|
||||||
) *IncidentJiraLinksAction {
|
) *IncidentJiraLinksAction {
|
||||||
return &IncidentJiraLinksAction{
|
return &IncidentJiraLinksAction{
|
||||||
incidentService: incidentService,
|
incidentService: incidentService,
|
||||||
slackService: slackService,
|
incidentJiraService: incident_jira.NewIncidentJiraService(incidentJiraModel.NewIncidentJiraRepo(appcontext.GetDB())),
|
||||||
|
slackService: slackService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,6 +44,7 @@ func (action *IncidentJiraLinksAction) getJiraLinksBlock(initialValue string) *s
|
|||||||
return view.CreatePlainTextInputBlock(jiraLinksBlockData)
|
return view.CreatePlainTextInputBlock(jiraLinksBlockData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: updateJiraLinks is deprecated. Use updateIncidentJiraLinks instead
|
||||||
func (action *IncidentJiraLinksAction) updateJiraLinks(jiraLinks string, callback slack.InteractionCallback, incidentEntity *incident.IncidentEntity) error {
|
func (action *IncidentJiraLinksAction) updateJiraLinks(jiraLinks string, callback slack.InteractionCallback, incidentEntity *incident.IncidentEntity) error {
|
||||||
channelID := callback.View.PrivateMetadata
|
channelID := callback.View.PrivateMetadata
|
||||||
formattedJiraLinks := strings.Split(
|
formattedJiraLinks := strings.Split(
|
||||||
@@ -72,3 +78,46 @@ func (action *IncidentJiraLinksAction) updateJiraLinks(jiraLinks string, callbac
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (action *IncidentJiraLinksAction) updateIncidentJiraLinks(jiraLinks string, incidentEntity *incident.IncidentEntity) error {
|
||||||
|
err := action.incidentJiraService.RemoveAllJiraLinksByIncidentID(incidentEntity.ID)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(fmt.Sprintf("%s unable to remove jira links for incident %s", logTag, incidentEntity.IncidentName))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
validatedJiraLinksPtr, err := action.getValidatedJiraLinks(jiraLinks)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if validatedJiraLinksPtr != nil && len(*validatedJiraLinksPtr) > 0 {
|
||||||
|
_, err = action.incidentJiraService.AddJiraLinksByIncidentID(incidentEntity.ID, *validatedJiraLinksPtr)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(fmt.Sprintf("%s unable to add jira link(s) for incident %s", logTag, incidentEntity.IncidentName))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (action *IncidentJiraLinksAction) getValidatedJiraLinks(jiraLinks string) (*[]string, error) {
|
||||||
|
var formattedJiraLinks []string
|
||||||
|
if jiraLinks != "" {
|
||||||
|
formattedJiraLinks = strings.Split(
|
||||||
|
strings.ReplaceAll(strings.ReplaceAll(jiraLinks, "\n", ""), " ", ""),
|
||||||
|
",",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(formattedJiraLinks) > 0 && formattedJiraLinks != nil {
|
||||||
|
for _, link := range formattedJiraLinks {
|
||||||
|
//Validate jira link
|
||||||
|
if !strings.HasPrefix(link, viper.GetString("navi.jira.base.url")) {
|
||||||
|
return nil, errors.New(fmt.Sprintf("%s is not a valid Jira link", link))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &formattedJiraLinks, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -75,7 +75,11 @@ func (action *IncidentRCASectionAction) ProcessIncidentRCAActionRequest(callback
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (action *IncidentRCASectionAction) PerformSetIncidentRCADetailsAction(callback slack.InteractionCallback, request *socketmode.Request, requesterType util.ViewSubmissionType) {
|
func (action *IncidentRCASectionAction) PerformSetIncidentRCADetailsAction(
|
||||||
|
callback slack.InteractionCallback,
|
||||||
|
request *socketmode.Request,
|
||||||
|
requesterType util.ViewSubmissionType,
|
||||||
|
) {
|
||||||
incidentEntity, err := action.incidentRepository.FindIncidentByChannelId(callback.View.PrivateMetadata)
|
incidentEntity, err := action.incidentRepository.FindIncidentByChannelId(callback.View.PrivateMetadata)
|
||||||
if err != nil || incidentEntity == nil {
|
if err != nil || incidentEntity == nil {
|
||||||
logger.Error(fmt.Sprintf("failed to get the incicent for channel id: %v", callback.View.PrivateMetadata))
|
logger.Error(fmt.Sprintf("failed to get the incicent for channel id: %v", callback.View.PrivateMetadata))
|
||||||
@@ -101,9 +105,20 @@ func (action *IncidentRCASectionAction) PerformSetIncidentRCADetailsAction(callb
|
|||||||
logger.Error(fmt.Sprintf("failed to update rca summary for incident id: %v", incidentEntity.ID))
|
logger.Error(fmt.Sprintf("failed to update rca summary for incident id: %v", incidentEntity.ID))
|
||||||
}
|
}
|
||||||
jiraLinksValue := actions[util.SetJiraLinks].Value
|
jiraLinksValue := actions[util.SetJiraLinks].Value
|
||||||
err = action.jiraAction.updateJiraLinks(jiraLinksValue, callback, incidentEntity)
|
err = action.jiraAction.updateIncidentJiraLinks(jiraLinksValue, incidentEntity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(fmt.Sprintf("failed to update jira link(s) for incident id: %v", incidentEntity.ID))
|
_, _ = action.client.PostEphemeral(
|
||||||
|
callback.View.PrivateMetadata,
|
||||||
|
callback.User.ID,
|
||||||
|
slack.MsgOptionText(fmt.Sprintf("Failed to update Jira link(s). %s", err.Error()), false),
|
||||||
|
)
|
||||||
|
logger.Error(fmt.Sprintf("failed to update Jira link(s) for incident id: %v. %+v", incidentEntity.ID, err))
|
||||||
|
} else {
|
||||||
|
//todo: this is to be removed after jira_links column is removed from incident table
|
||||||
|
err = action.jiraAction.updateJiraLinks(jiraLinksValue, callback, incidentEntity)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(fmt.Sprintf("failed to update Jira link(s) for incident id: %v", incidentEntity.ID))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
updatedIncidentEntity, _ := action.incidentRepository.FindIncidentById(incidentEntity.ID)
|
updatedIncidentEntity, _ := action.incidentRepository.FindIncidentById(incidentEntity.ID)
|
||||||
tagValuesMap, _ := action.tagsAction.getIncidentTagValuesAsMap(incidentEntity.ID)
|
tagValuesMap, _ := action.tagsAction.getIncidentTagValuesAsMap(incidentEntity.ID)
|
||||||
|
|||||||
16
model/incident_jira/entity.go
Normal file
16
model/incident_jira/entity.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package incident_jira
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IncidentJiraEntity struct {
|
||||||
|
ID uint `gorm:"primarykey;column:id"`
|
||||||
|
CreatedAt time.Time `gorm:"column:created_at"`
|
||||||
|
IncidentEntityID uint `gorm:"column:incident_id"`
|
||||||
|
JiraLink string `gorm:"column:jira_link"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (IncidentJiraEntity) TableName() string {
|
||||||
|
return "incident_jira"
|
||||||
|
}
|
||||||
18
model/incident_jira/incident_jira_repository.go
Normal file
18
model/incident_jira/incident_jira_repository.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package incident_jira
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IncidentJiraRepository interface {
|
||||||
|
InsertJiraLinks(incidentID uint, jiraLinks []string) ([]uint, error)
|
||||||
|
GetJiraLinksByIncidentID(incidentID uint) (*[]IncidentJiraEntity, error)
|
||||||
|
GetIncidentJiraEntity(incidentID uint, jiraLink string) (*IncidentJiraEntity, error)
|
||||||
|
DeleteJiraLink(incidentID uint, jiraLink string) error
|
||||||
|
DeleteAllJiraLinksForIncident(incidentID uint) error
|
||||||
|
GetAllJiraIdsByPage(incidentName string, pageNumber, pageSize int64) (*[]IncidentJiraLinksDTO, int64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIncidentJiraRepo(db *gorm.DB) *IncidentJiraRepositoryImpl {
|
||||||
|
return &IncidentJiraRepositoryImpl{db: db}
|
||||||
|
}
|
||||||
108
model/incident_jira/incident_jira_repository_impl.go
Normal file
108
model/incident_jira/incident_jira_repository_impl.go
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
package incident_jira
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IncidentJiraRepositoryImpl struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *IncidentJiraRepositoryImpl) InsertJiraLinks(incidentID uint, jiraLinks []string) ([]uint, error) {
|
||||||
|
var records []IncidentJiraEntity
|
||||||
|
for _, jiraLink := range jiraLinks {
|
||||||
|
records = append(records, IncidentJiraEntity{
|
||||||
|
IncidentEntityID: incidentID,
|
||||||
|
JiraLink: jiraLink,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
result := repo.db.Create(&records)
|
||||||
|
if result.Error != nil {
|
||||||
|
return nil, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
var insertedRows []uint
|
||||||
|
|
||||||
|
for _, record := range records {
|
||||||
|
insertedRows = append(insertedRows, record.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return insertedRows, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *IncidentJiraRepositoryImpl) GetJiraLinksByIncidentID(incidentID uint) (*[]IncidentJiraEntity, error) {
|
||||||
|
var entities []IncidentJiraEntity
|
||||||
|
|
||||||
|
result := repo.db.Find(&entities, "incident_id = ?", incidentID)
|
||||||
|
if result.Error != nil {
|
||||||
|
return nil, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.RowsAffected == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &entities, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *IncidentJiraRepositoryImpl) GetIncidentJiraEntity(incidentID uint, jiraLink string) (*IncidentJiraEntity, error) {
|
||||||
|
var entity IncidentJiraEntity
|
||||||
|
result := repo.db.First(&entity, "incident_id = ? AND jira_link = ?", incidentID, jiraLink)
|
||||||
|
if result.Error != nil {
|
||||||
|
return nil, result.Error
|
||||||
|
}
|
||||||
|
if result.RowsAffected == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return &entity, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *IncidentJiraRepositoryImpl) DeleteJiraLink(incidentID uint, jiraLink string) error {
|
||||||
|
result := repo.db.Delete(&IncidentJiraEntity{}, "incident_id = ? AND jira_link = ?", incidentID, jiraLink)
|
||||||
|
if result.Error != nil {
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *IncidentJiraRepositoryImpl) DeleteAllJiraLinksForIncident(incidentID uint) error {
|
||||||
|
result := repo.db.Delete(&IncidentJiraEntity{}, "incident_id = ?", incidentID)
|
||||||
|
if result.Error != nil {
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *IncidentJiraRepositoryImpl) GetAllJiraIdsByPage(incidentName string, pageNumber, pageSize int64) (*[]IncidentJiraLinksDTO, int64, error) {
|
||||||
|
var allJiraLinksFromDB []IncidentJiraLinksDTO
|
||||||
|
|
||||||
|
query := repo.db.Table("incident_jira").
|
||||||
|
Joins("JOIN incident ON incident_jira.incident_id = incident.id").
|
||||||
|
Select("incident.id as incident_id, incident.incident_name as incident_name, incident_jira.jira_link as jira_link").
|
||||||
|
Group("incident_jira.jira_link, incident.id")
|
||||||
|
|
||||||
|
if incidentName != "" {
|
||||||
|
query = query.Where("incident.incident_name LIKE ?", "%"+incidentName+"%")
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalElements int64
|
||||||
|
result := query.Count(&totalElements)
|
||||||
|
if result.Error != nil {
|
||||||
|
return nil, 0, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
result = query.Order("incident.id desc").
|
||||||
|
Offset(int(pageNumber * pageSize)).
|
||||||
|
Limit(int(pageSize)).
|
||||||
|
Find(&allJiraLinksFromDB)
|
||||||
|
|
||||||
|
if result.Error != nil {
|
||||||
|
return nil, 0, result.Error
|
||||||
|
}
|
||||||
|
if result.RowsAffected == 0 {
|
||||||
|
return nil, totalElements, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &allJiraLinksFromDB, totalElements, result.Error
|
||||||
|
}
|
||||||
7
model/incident_jira/model.go
Normal file
7
model/incident_jira/model.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package incident_jira
|
||||||
|
|
||||||
|
type IncidentJiraLinksDTO struct {
|
||||||
|
IncidentID uint `json:"incident_id"`
|
||||||
|
IncidentName string `json:"incident_name"`
|
||||||
|
JiraLink string `json:"jira_link"`
|
||||||
|
}
|
||||||
@@ -61,8 +61,6 @@ func (client *JiraClientImpl) SearchByJQL(jql string) (*response.JiraSearchJQLRe
|
|||||||
return nil, errors.NewApiError(fmt.Sprintf("Jira api returned %d", apiResponse.StatusCode), apiResponse.Status, http.StatusInternalServerError)
|
return nil, errors.NewApiError(fmt.Sprintf("Jira api returned %d", apiResponse.StatusCode), apiResponse.Status, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info(fmt.Sprintf("response body: %s", responseBody))
|
|
||||||
|
|
||||||
searchJQLResponse := response.JiraSearchJQLResponse{}
|
searchJQLResponse := response.JiraSearchJQLResponse{}
|
||||||
|
|
||||||
// Parse api response body into JiraSearchJQLResponse
|
// Parse api response body into JiraSearchJQLResponse
|
||||||
|
|||||||
@@ -14,17 +14,25 @@ import (
|
|||||||
"houston/logger"
|
"houston/logger"
|
||||||
"houston/model/incident"
|
"houston/model/incident"
|
||||||
"houston/model/incident_channel"
|
"houston/model/incident_channel"
|
||||||
|
incident_jira2 "houston/model/incident_jira"
|
||||||
"houston/model/log"
|
"houston/model/log"
|
||||||
"houston/model/severity"
|
"houston/model/severity"
|
||||||
"houston/model/team"
|
"houston/model/team"
|
||||||
"houston/model/user"
|
"houston/model/user"
|
||||||
|
"houston/pkg/atlassian"
|
||||||
|
"houston/pkg/atlassian/dto/response"
|
||||||
conference2 "houston/pkg/conference"
|
conference2 "houston/pkg/conference"
|
||||||
|
"houston/pkg/rest"
|
||||||
service2 "houston/service/conference"
|
service2 "houston/service/conference"
|
||||||
incidentChannel "houston/service/incident_channel"
|
incidentChannel "houston/service/incident_channel"
|
||||||
|
"houston/service/incident_jira"
|
||||||
"houston/service/krakatoa"
|
"houston/service/krakatoa"
|
||||||
request "houston/service/request"
|
request "houston/service/request"
|
||||||
service "houston/service/response"
|
service "houston/service/response"
|
||||||
|
common "houston/service/response/common"
|
||||||
"houston/service/slack"
|
"houston/service/slack"
|
||||||
|
"math"
|
||||||
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -41,6 +49,7 @@ type IncidentServiceV2 struct {
|
|||||||
incidentRepository incident.IIncidentRepository
|
incidentRepository incident.IIncidentRepository
|
||||||
userRepository user.IUserRepository
|
userRepository user.IUserRepository
|
||||||
krakatoaService krakatoa.IKrakatoaService
|
krakatoaService krakatoa.IKrakatoaService
|
||||||
|
incidentJiraService incident_jira.IncidentJiraService
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -66,6 +75,7 @@ func NewIncidentServiceV2(db *gorm.DB) *IncidentServiceV2 {
|
|||||||
userRepository: userRepository,
|
userRepository: userRepository,
|
||||||
incidentChannelService: incidentChannelService,
|
incidentChannelService: incidentChannelService,
|
||||||
krakatoaService: krakatoaService,
|
krakatoaService: krakatoaService,
|
||||||
|
incidentJiraService: incident_jira.NewIncidentJiraService(incident_jira2.NewIncidentJiraRepo(db)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,6 +201,11 @@ func (i *IncidentServiceV2) LinkJiraToIncident(incidentId uint, linkedBy string,
|
|||||||
logger.Info(errorMessage)
|
logger.Info(errorMessage)
|
||||||
return fmt.Errorf("failed to fetch incident by id %d", incidentId)
|
return fmt.Errorf("failed to fetch incident by id %d", incidentId)
|
||||||
}
|
}
|
||||||
|
err = i.AddJiraLinks(entity, jiraLinks...)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(fmt.Sprintf("%s failed to link JIRA to the incident: %s. %+v", logTag, entity.IncidentName, err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
if len(jiraLinks) == 1 {
|
if len(jiraLinks) == 1 {
|
||||||
if util.Contains(entity.JiraLinks, jiraLinks[0]) {
|
if util.Contains(entity.JiraLinks, jiraLinks[0]) {
|
||||||
return errors.New("This JIRA link already exists")
|
return errors.New("This JIRA link already exists")
|
||||||
@@ -220,9 +235,153 @@ func (i *IncidentServiceV2) UnLinkJiraFromIncident(incidentId uint, unLinkedBy,
|
|||||||
logger.Error(fmt.Sprintf("%s failed to unlink JIRA ID(s): %s from incident %s", logTag, jiraLink, entity.IncidentName))
|
logger.Error(fmt.Sprintf("%s failed to unlink JIRA ID(s): %s from incident %s", logTag, jiraLink, entity.IncidentName))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
err = i.RemoveJiraLink(entity, jiraLink)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(fmt.Sprintf("%s failed to link JIRA to the incident: %s", logTag, entity.IncidentName))
|
||||||
|
_, err := i.slackService.PostMessageByChannelID("failed to link JIRA", false, entity.SlackChannel)
|
||||||
|
if err != nil {
|
||||||
|
logger.Info(fmt.Sprintf("%s failed to post jira linking failure message to slack channel: %s", logTag, entity.IncidentName))
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *IncidentServiceV2) GetJiraStatuses(incidentName string, pageNumber, pageSize int64) (service.GetAllJiraStatusPaginatedResponse, error) {
|
||||||
|
emptyResponse := service.GetAllJiraStatusPaginatedResponse{}
|
||||||
|
|
||||||
|
incidentJiraLinksDTOs, totalJiraLinks, err := i.incidentJiraService.GetJiraLinks(incidentName, pageNumber, pageSize)
|
||||||
|
if err != nil {
|
||||||
|
return service.GetAllJiraStatusPaginatedResponse{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if incidentJiraLinksDTOs == nil || len(*incidentJiraLinksDTOs) == 0 {
|
||||||
|
return service.GetAllJiraStatusPaginatedResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var allJiraLinks []string
|
||||||
|
|
||||||
|
for _, dto := range *incidentJiraLinksDTOs {
|
||||||
|
allJiraLinks = append(allJiraLinks, dto.JiraLink)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(allJiraLinks) == 0 {
|
||||||
|
return service.GetAllJiraStatusPaginatedResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
jql, err := getJQLFromJiraLinks(allJiraLinks...)
|
||||||
|
if err != nil {
|
||||||
|
return emptyResponse, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var jiraKeyToJiraFieldsMap = make(map[string]response.Fields)
|
||||||
|
jiraClient := atlassian.NewJiraClient(rest.NewHttpRestClient())
|
||||||
|
jiraResponse, apiErr := jiraClient.SearchByJQL(jql)
|
||||||
|
if apiErr != nil {
|
||||||
|
logger.Error(fmt.Sprintf("%s failed to search by jql: %s. %+v", logTag, jql, err))
|
||||||
|
} else {
|
||||||
|
for _, jiraIssue := range jiraResponse.Issues {
|
||||||
|
jiraKeyToJiraFieldsMap[jiraIssue.Key] = jiraIssue.Fields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := getHoustonJiraStatuses(incidentJiraLinksDTOs, &jiraKeyToJiraFieldsMap, incidentName)
|
||||||
|
if err != nil {
|
||||||
|
return service.GetAllJiraStatusPaginatedResponse{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
page := common.Page{
|
||||||
|
PageSize: pageSize,
|
||||||
|
TotalPages: int64(math.Ceil(float64(totalJiraLinks) / float64(pageSize))),
|
||||||
|
PageNumber: pageNumber,
|
||||||
|
TotalElements: int(totalJiraLinks),
|
||||||
|
}
|
||||||
|
|
||||||
|
r := service.GetAllJiraStatusPaginatedResponse{
|
||||||
|
Data: *data,
|
||||||
|
Page: page,
|
||||||
|
Status: http.StatusOK,
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHoustonJiraStatuses(
|
||||||
|
incidentJiraLinksDTOs *[]incident_jira2.IncidentJiraLinksDTO,
|
||||||
|
jiraKeyToJiraFieldsMap *map[string]response.Fields,
|
||||||
|
incidentName string,
|
||||||
|
) (*[]service.HoustonJiraStatus, error) {
|
||||||
|
var data []service.HoustonJiraStatus
|
||||||
|
for _, dto := range *incidentJiraLinksDTOs {
|
||||||
|
jiraKeyFromJiraLink, err := extractJiraKeyFromURL(dto.JiraLink)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(
|
||||||
|
fmt.Sprintf("%s failed to extract jira key from jira link: %s for incident: %s. %+v",
|
||||||
|
logTag, dto.JiraLink, incidentName, err,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
jiraFields, ok := (*jiraKeyToJiraFieldsMap)[jiraKeyFromJiraLink]
|
||||||
|
if !ok {
|
||||||
|
logger.Error(fmt.Sprintf("%s details for jiraKey: %s could not be found in the jira api response", logTag, jiraKeyFromJiraLink))
|
||||||
|
jiraFields = response.Fields{}
|
||||||
|
}
|
||||||
|
var teamsInvolved []string
|
||||||
|
|
||||||
|
for _, teamInvolved := range jiraFields.TeamsInvolved {
|
||||||
|
teamsInvolved = append(teamsInvolved, teamInvolved.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
data = append(data, service.HoustonJiraStatus{
|
||||||
|
IncidentID: dto.IncidentID,
|
||||||
|
IncidentName: dto.IncidentName,
|
||||||
|
JiraKey: jiraKeyFromJiraLink,
|
||||||
|
JiraLink: dto.JiraLink,
|
||||||
|
JiraType: jiraFields.IssueType.Name,
|
||||||
|
JiraSummary: jiraFields.Summary,
|
||||||
|
JiraStatus: jiraFields.Status.Name,
|
||||||
|
TeamsInvolved: teamsInvolved,
|
||||||
|
JiraCreatedAt: jiraFields.CreatedAt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return &data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractJiraKeyFromURL(url string) (string, error) {
|
||||||
|
// Define a regular expression to match Jira issue URLs
|
||||||
|
naviJiraBaseUrl := viper.GetString("navi.jira.base.url")
|
||||||
|
regex := regexp.MustCompile(naviJiraBaseUrl + `([A-Z][A-Z0-9]+-\d+)`)
|
||||||
|
|
||||||
|
// Find the matches
|
||||||
|
matches := regex.FindStringSubmatch(url)
|
||||||
|
|
||||||
|
// Check if a match is found
|
||||||
|
if len(matches) < 2 {
|
||||||
|
return "", fmt.Errorf("no Jira issue key found in the URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract and return the Jira issue key
|
||||||
|
return matches[1], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getJQLFromJiraLinks(jiraLinks ...string) (string, error) {
|
||||||
|
var jiraKeys []string
|
||||||
|
for _, jiraLink := range jiraLinks {
|
||||||
|
jiraKey, err := extractJiraKeyFromURL(jiraLink)
|
||||||
|
if err == nil {
|
||||||
|
jiraKeys = append(jiraKeys, jiraKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(jiraKeys) == 0 {
|
||||||
|
return "", fmt.Errorf("no jira key could be extracted from the given links")
|
||||||
|
}
|
||||||
|
commaSeparatedJiraKeys := strings.Join(jiraKeys, ", ")
|
||||||
|
jql := fmt.Sprintf("issueKey in (%s)", commaSeparatedJiraKeys)
|
||||||
|
|
||||||
|
return jql, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetAllOpenIncidents - returns list of all the open incidents and length of the result when success otherwise error
|
// GetAllOpenIncidents - returns list of all the open incidents and length of the result when success otherwise error
|
||||||
func (i *IncidentServiceV2) GetAllOpenIncidents() ([]incident.IncidentEntity, int, error) {
|
func (i *IncidentServiceV2) GetAllOpenIncidents() ([]incident.IncidentEntity, int, error) {
|
||||||
incidents, resultLength, err := i.incidentRepository.GetAllOpenIncidents()
|
incidents, resultLength, err := i.incidentRepository.GetAllOpenIncidents()
|
||||||
@@ -340,6 +499,22 @@ func (i *IncidentServiceV2) updateJiraIDs(entity *incident.IncidentEntity, user,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *IncidentServiceV2) AddJiraLinks(entity *incident.IncidentEntity, jiraLinks ...string) error {
|
||||||
|
_, err := i.incidentJiraService.AddJiraLinksByIncidentID(entity.ID, jiraLinks)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IncidentServiceV2) RemoveJiraLink(entity *incident.IncidentEntity, jiraLink string) error {
|
||||||
|
_, err := i.incidentJiraService.RemoveJiraLinkByIncidentID(entity.ID, jiraLink)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
performs essential slack operations post incident creation including invitation of team member and incident creator to
|
performs essential slack operations post incident creation including invitation of team member and incident creator to
|
||||||
incident slack channel, tags pse/dev oncall, assigns responder, posts SLA message, creates gmeet and posts the link
|
incident slack channel, tags pse/dev oncall, assigns responder, posts SLA message, creates gmeet and posts the link
|
||||||
|
|||||||
@@ -9,10 +9,13 @@ import (
|
|||||||
type IIncidentService interface {
|
type IIncidentService interface {
|
||||||
CreateIncident(request request.CreateIncidentRequestV2, source string, blazeGroupChannelID string) (service.IncidentResponse, error)
|
CreateIncident(request request.CreateIncidentRequestV2, source string, blazeGroupChannelID string) (service.IncidentResponse, error)
|
||||||
GetIncidentById(incidentId uint) (*incident.IncidentEntity, error)
|
GetIncidentById(incidentId uint) (*incident.IncidentEntity, error)
|
||||||
|
GetIncidentByChannelID(channelID string) (*incident.IncidentEntity, error)
|
||||||
LinkJiraToIncident(incidentId uint, linkedBy string, jiraLinks ...string) error
|
LinkJiraToIncident(incidentId uint, linkedBy string, jiraLinks ...string) error
|
||||||
UnLinkJiraFromIncident(incidentId uint, unLinkedBy, jiraLink string) error
|
UnLinkJiraFromIncident(incidentId uint, unLinkedBy, jiraLink string) error
|
||||||
GetAllOpenIncidents() ([]incident.IncidentEntity, int, error)
|
GetAllOpenIncidents() ([]incident.IncidentEntity, int, error)
|
||||||
GetIncidentRoleByIncidentIdAndRole(incidentId uint, role string) (*incident.IncidentRoleEntity, error)
|
GetIncidentRoleByIncidentIdAndRole(incidentId uint, role string) (*incident.IncidentRoleEntity, error)
|
||||||
UpdateIncidentJiraLinksEntity(incidentEntity *incident.IncidentEntity, updatedBy string, jiraLinks []string) error
|
UpdateIncidentJiraLinksEntity(incidentEntity *incident.IncidentEntity, updatedBy string, jiraLinks []string) error
|
||||||
|
AddJiraLinks(entity *incident.IncidentEntity, jiraLinks ...string) error
|
||||||
|
RemoveJiraLink(entity *incident.IncidentEntity, jiraLink string) error
|
||||||
IsHoustonChannel(channelID string) (bool, error)
|
IsHoustonChannel(channelID string) (bool, error)
|
||||||
}
|
}
|
||||||
|
|||||||
15
service/incident_jira/incident_jira_service.go
Normal file
15
service/incident_jira/incident_jira_service.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package incident_jira
|
||||||
|
|
||||||
|
import "houston/model/incident_jira"
|
||||||
|
|
||||||
|
type IncidentJiraService interface {
|
||||||
|
AddJiraLinksByIncidentID(incidentID uint, jiraLinks []string) ([]uint, error)
|
||||||
|
GetJiraLinks(incidentName string, pageNumber, pageSize int64) (*[]incident_jira.IncidentJiraLinksDTO, int64, error)
|
||||||
|
GetJiraLinksByIncidentID(incidentID uint) (*[]incident_jira.IncidentJiraEntity, error)
|
||||||
|
RemoveJiraLinkByIncidentID(incidentID uint, jiraLink string) (uint, error)
|
||||||
|
RemoveAllJiraLinksByIncidentID(incidentID uint) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIncidentJiraService(repo incident_jira.IncidentJiraRepository) IncidentJiraService {
|
||||||
|
return &IncidentJiraServiceImpl{repo}
|
||||||
|
}
|
||||||
41
service/incident_jira/incident_jira_service_impl.go
Normal file
41
service/incident_jira/incident_jira_service_impl.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package incident_jira
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"houston/model/incident_jira"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IncidentJiraServiceImpl struct {
|
||||||
|
repo incident_jira.IncidentJiraRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *IncidentJiraServiceImpl) AddJiraLinksByIncidentID(incidentID uint, jiraLinks []string) ([]uint, error) {
|
||||||
|
jiraLinkMaxLength := viper.GetInt("jira.link.max.length")
|
||||||
|
for _, jiraLink := range jiraLinks {
|
||||||
|
if len(jiraLink) > jiraLinkMaxLength {
|
||||||
|
return nil, fmt.Errorf("Jira link %s is too long", jiraLink)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return service.repo.InsertJiraLinks(incidentID, jiraLinks)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *IncidentJiraServiceImpl) GetJiraLinks(incidentName string, pageNumber, pageSize int64) (*[]incident_jira.IncidentJiraLinksDTO, int64, error) {
|
||||||
|
return service.repo.GetAllJiraIdsByPage(incidentName, pageNumber, pageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *IncidentJiraServiceImpl) GetJiraLinksByIncidentID(incidentID uint) (*[]incident_jira.IncidentJiraEntity, error) {
|
||||||
|
return service.repo.GetJiraLinksByIncidentID(incidentID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *IncidentJiraServiceImpl) RemoveJiraLinkByIncidentID(incidentID uint, jiraLink string) (uint, error) {
|
||||||
|
incidentJiraEntity, err := service.repo.GetIncidentJiraEntity(incidentID, jiraLink)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return incidentJiraEntity.ID, service.repo.DeleteJiraLink(incidentID, jiraLink)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *IncidentJiraServiceImpl) RemoveAllJiraLinksByIncidentID(incidentID uint) error {
|
||||||
|
return service.repo.DeleteAllJiraLinksForIncident(incidentID)
|
||||||
|
}
|
||||||
68
service/incident_jira/incident_jira_service_test.go
Normal file
68
service/incident_jira/incident_jira_service_test.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package incident_jira
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gojuno/minimock/v3"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
"houston/logger"
|
||||||
|
"houston/mocks"
|
||||||
|
"houston/model/incident_jira"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IncidentJiraServiceSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
controller *minimock.Controller
|
||||||
|
repoMock *mocks.IncidentJiraRepositoryMock
|
||||||
|
service IncidentJiraService
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *IncidentJiraServiceSuite) SetupSuite() {
|
||||||
|
logger.InitLogger()
|
||||||
|
viper.Set("jira.link.max.length", 50)
|
||||||
|
suite.controller = minimock.NewController(suite.T())
|
||||||
|
suite.T().Cleanup(suite.controller.Finish)
|
||||||
|
suite.repoMock = mocks.NewIncidentJiraRepositoryMock(suite.controller)
|
||||||
|
suite.service = NewIncidentJiraService(suite.repoMock)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJiraClient(t *testing.T) {
|
||||||
|
suite.Run(t, new(IncidentJiraServiceSuite))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *IncidentJiraServiceSuite) TestIncidentJiraServiceImpl_AddJiraLinksByIncidentID() {
|
||||||
|
|
||||||
|
suite.repoMock.InsertJiraLinksMock.When(1, []string{"https://navihq.atlassian.net/browse/TP-48564"}).Then([]uint{1}, nil)
|
||||||
|
|
||||||
|
_, err := suite.service.AddJiraLinksByIncidentID(1, []string{"https://navihq.atlassian.net/browse/TP-48564"})
|
||||||
|
if err != nil {
|
||||||
|
suite.Fail("Add Jira Link Failed", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *IncidentJiraServiceSuite) TestIncidentJiraServiceImpl_GetJiraLinksPaginated() {
|
||||||
|
|
||||||
|
suite.repoMock.GetAllJiraIdsByPageMock.When("", 1, 10).Then(&[]incident_jira.IncidentJiraLinksDTO{}, int64(0), nil)
|
||||||
|
|
||||||
|
_, _, err := suite.service.GetJiraLinks("", 1, 10)
|
||||||
|
if err != nil {
|
||||||
|
suite.Fail("Add Jira Link Failed", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *IncidentJiraServiceSuite) TestIncidentJiraServiceImpl_RemoveJiraLinkByIncidentID() {
|
||||||
|
e := incident_jira.IncidentJiraEntity{
|
||||||
|
ID: uint(1),
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
IncidentEntityID: 1,
|
||||||
|
JiraLink: "https://navihq.atlassian.net/browse/TP-48564",
|
||||||
|
}
|
||||||
|
suite.repoMock.GetIncidentJiraEntityMock.When(1, "https://navihq.atlassian.net/browse/TP-48564").Then(&e, nil)
|
||||||
|
suite.repoMock.DeleteJiraLinkMock.When(1, "https://navihq.atlassian.net/browse/TP-48564").Then(nil)
|
||||||
|
|
||||||
|
_, err := suite.service.RemoveJiraLinkByIncidentID(1, "https://navihq.atlassian.net/browse/TP-48564")
|
||||||
|
if err != nil {
|
||||||
|
suite.Fail("Remove Jira Link By incident ID Failed", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
21
service/response/get_all_jira_status_response.go
Normal file
21
service/response/get_all_jira_status_response.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import service "houston/service/response/common"
|
||||||
|
|
||||||
|
type GetAllJiraStatusPaginatedResponse struct {
|
||||||
|
Data []HoustonJiraStatus `json:"data"`
|
||||||
|
Page service.Page `json:"page"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HoustonJiraStatus struct {
|
||||||
|
IncidentID uint `json:"incidentID"`
|
||||||
|
IncidentName string `json:"incidentName"`
|
||||||
|
JiraKey string `json:"jiraKey"`
|
||||||
|
JiraLink string `json:"jiraLink"`
|
||||||
|
JiraType string `json:"jiraType"`
|
||||||
|
JiraSummary string `json:"jiraSummary"`
|
||||||
|
JiraStatus string `json:"jiraStatus"`
|
||||||
|
TeamsInvolved []string `json:"teamsInvolved"`
|
||||||
|
JiraCreatedAt string `json:"jiraCreateAt"`
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user