TP-46247 | API to add jira links to an incident (#257)
* TP-46247 | API to add jira links to an incident * TP-464408 | Add Jira link modal * TP-45730 | renaming log entity name back to log from logger
This commit is contained in:
@@ -18,18 +18,20 @@ const (
|
||||
)
|
||||
|
||||
type IncidentHandler struct {
|
||||
gin *gin.Engine
|
||||
db *gorm.DB
|
||||
gin *gin.Engine
|
||||
db *gorm.DB
|
||||
service *incident.IncidentServiceV2
|
||||
}
|
||||
|
||||
func NewIncidentHandler(gin *gin.Engine, db *gorm.DB) *IncidentHandler {
|
||||
func NewIncidentHandler(gin *gin.Engine, db *gorm.DB, incidentService *incident.IncidentServiceV2) *IncidentHandler {
|
||||
return &IncidentHandler{
|
||||
gin: gin,
|
||||
db: db,
|
||||
gin: gin,
|
||||
db: db,
|
||||
service: incidentService,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *IncidentHandler) HandleCreateIncident(c *gin.Context) {
|
||||
func (handler *IncidentHandler) HandleCreateIncident(c *gin.Context) {
|
||||
var createIncidentRequest request.CreateIncidentRequestV2
|
||||
if err := c.ShouldBindJSON(&createIncidentRequest); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, err)
|
||||
@@ -41,8 +43,7 @@ func (h *IncidentHandler) HandleCreateIncident(c *gin.Context) {
|
||||
c.JSON(http.StatusBadRequest, common.ErrorResponse(err, http.StatusBadRequest, nil))
|
||||
return
|
||||
}
|
||||
incidentServiceV2 := incident.NewIncidentServiceV2(h.db)
|
||||
incidentResponse, err := incidentServiceV2.CreateIncident(createIncidentRequest, "API", "")
|
||||
incidentResponse, err := handler.service.CreateIncident(createIncidentRequest, "API", "")
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("%s Failed to create incident", logTag), zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, common.ErrorResponse(err, http.StatusInternalServerError, nil))
|
||||
@@ -50,3 +51,52 @@ func (h *IncidentHandler) HandleCreateIncident(c *gin.Context) {
|
||||
}
|
||||
c.JSON(http.StatusOK, common.SuccessResponse(incidentResponse, http.StatusOK))
|
||||
}
|
||||
|
||||
func (handler *IncidentHandler) HandleJiraLinking(c *gin.Context) {
|
||||
var linkJiraRequest request.LinkJiraRequest
|
||||
if err := c.ShouldBindJSON(&linkJiraRequest); err != nil {
|
||||
c.JSON(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
if err := utils.ValidateLinkJiraRequest(linkJiraRequest); err != nil {
|
||||
logger.Debug(fmt.Sprintf("%s invalid request to link Jira reveived. %s", logTag, err.Error()))
|
||||
c.JSON(http.StatusBadRequest, common.ErrorResponse(err, http.StatusBadGateway, nil))
|
||||
return
|
||||
}
|
||||
err := handler.service.LinkJiraToIncident(linkJiraRequest.IncidentID, linkJiraRequest.User, linkJiraRequest.JiraLink)
|
||||
if err != nil {
|
||||
logger.Error(
|
||||
fmt.Sprintf(
|
||||
"%s failed to link jira to the incident %d", logTag, linkJiraRequest.IncidentID,
|
||||
), zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, common.ErrorResponse(err, http.StatusBadRequest, nil))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, common.SuccessResponse("Jira link added successfully", http.StatusOK))
|
||||
}
|
||||
|
||||
func (handler *IncidentHandler) HandleJiraUnLinking(c *gin.Context) {
|
||||
var unlinkJiraRequest request.LinkJiraRequest
|
||||
if err := c.ShouldBindJSON(&unlinkJiraRequest); err != nil {
|
||||
c.JSON(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
if err := utils.ValidateLinkJiraRequest(unlinkJiraRequest); err != nil {
|
||||
logger.Debug(fmt.Sprintf("%s invalid request to unLink Jira reveived. %s", logTag, err.Error()))
|
||||
c.JSON(http.StatusBadRequest, common.ErrorResponse(err, http.StatusBadGateway, nil))
|
||||
return
|
||||
}
|
||||
if err := handler.service.UnLinkJiraFromIncident(
|
||||
unlinkJiraRequest.IncidentID, unlinkJiraRequest.User, unlinkJiraRequest.JiraLink,
|
||||
); err != nil {
|
||||
logger.Error(
|
||||
fmt.Sprintf(
|
||||
"%s failed to unlink jira from the incident %d", logTag, unlinkJiraRequest.IncidentID,
|
||||
), zap.Error(err),
|
||||
)
|
||||
c.JSON(http.StatusInternalServerError, common.ErrorResponse(err, http.StatusNotFound, nil))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, common.SuccessResponse("Jira link removed successfully", http.StatusOK))
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ import (
|
||||
"houston/model/team"
|
||||
"houston/model/user"
|
||||
"houston/pkg/slackbot"
|
||||
incidentServiceV2 "houston/service/incident"
|
||||
slack2 "houston/service/slack"
|
||||
|
||||
"github.com/slack-go/slack"
|
||||
"github.com/slack-go/slack/slackevents"
|
||||
@@ -48,6 +50,8 @@ func NewSlackHandler(gormClient *gorm.DB, socketModeClient *socketmode.Client) *
|
||||
slashCommandProcessor := processor.NewSlashCommandProcessor(socketModeClient, incidentService, slackbotClient)
|
||||
grafanaRepository := diagnostic.NewDiagnoseRepository(gormClient)
|
||||
diagnosticCommandProcessor := processor.NewDiagnosticCommandProcessor(socketModeClient, grafanaRepository)
|
||||
incidentServiceV2 := incidentServiceV2.NewIncidentServiceV2(gormClient)
|
||||
slackService := slack2.NewSlackService()
|
||||
|
||||
cron.RunJob(socketModeClient, gormClient, incidentService, severityService, teamService, shedlockService, userService)
|
||||
|
||||
@@ -59,7 +63,7 @@ func NewSlackHandler(gormClient *gorm.DB, socketModeClient *socketmode.Client) *
|
||||
socketModeClient, incidentService, teamService, severityService,
|
||||
),
|
||||
blockActionProcessor: processor.NewBlockActionProcessor(
|
||||
socketModeClient, incidentService, teamService, severityService, tagService, slackbotClient,
|
||||
socketModeClient, incidentService, teamService, severityService, tagService, slackbotClient, incidentServiceV2, slackService,
|
||||
),
|
||||
viewSubmissionProcessor: processor.NewViewSubmissionProcessor(
|
||||
socketModeClient, incidentService, teamService, severityService, tagService, teamService, slackbotClient, gormClient,
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"houston/model/ingester"
|
||||
"houston/pkg/slackbot"
|
||||
"houston/service"
|
||||
"houston/service/incident"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -120,8 +121,11 @@ func (s *Server) incidentClientHandlerV2(houstonGroup *gin.RouterGroup) {
|
||||
origin := c.Request.Header.Get("Origin")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
|
||||
})
|
||||
incidentHandler := handler.NewIncidentHandler(s.gin, s.db)
|
||||
incidentServiceV2 := incident.NewIncidentServiceV2(s.db)
|
||||
incidentHandler := handler.NewIncidentHandler(s.gin, s.db, incidentServiceV2)
|
||||
houstonGroup.POST("/create-incident-v2", incidentHandler.HandleCreateIncident)
|
||||
houstonGroup.POST("/link-jira-to-incident", incidentHandler.HandleJiraLinking)
|
||||
houstonGroup.POST("/unlink-jira-from-incident", incidentHandler.HandleJiraUnLinking)
|
||||
}
|
||||
|
||||
func (s *Server) incidentHandler(houstonGroup *gin.RouterGroup) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/slack-go/slack"
|
||||
"github.com/slack-go/slack/socketmode"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/exp/slices"
|
||||
"houston/logger"
|
||||
"houston/model/incident"
|
||||
"houston/model/severity"
|
||||
@@ -27,6 +28,49 @@ func RemoveDuplicate[T string | int](sliceList []T) []T {
|
||||
return list
|
||||
}
|
||||
|
||||
// Difference : finds difference of two slices and returns a new slice
|
||||
func Difference(s1, s2 []string) []string {
|
||||
combinedSlice := append(s1, s2...)
|
||||
m := make(map[string]int)
|
||||
for _, v := range combinedSlice {
|
||||
if _, ok := m[v]; ok {
|
||||
// remove element later as it exist in both slice.
|
||||
m[v] += 1
|
||||
continue
|
||||
}
|
||||
// new entry, add in map!
|
||||
m[v] = 1
|
||||
}
|
||||
var result []string
|
||||
for k, v := range m {
|
||||
if v == 1 {
|
||||
result = append(result, k)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func Intersection(s1, s2 []string) (inter []string) {
|
||||
hash := make(map[string]bool)
|
||||
for _, e := range s1 {
|
||||
hash[e] = true
|
||||
}
|
||||
for _, e := range s2 {
|
||||
// If elements present in the hashmap then append intersection list.
|
||||
if hash[e] {
|
||||
inter = append(inter, e)
|
||||
}
|
||||
}
|
||||
//Remove dups from slice.
|
||||
inter = RemoveDuplicate(inter)
|
||||
return
|
||||
}
|
||||
|
||||
// Contains checks if the given string exists on the slice of string and returns boolean
|
||||
func Contains[S ~[]E, E comparable](s S, v E) bool {
|
||||
return slices.Contains(s, v)
|
||||
}
|
||||
|
||||
func GetColorBySeverity(severityId uint) string {
|
||||
switch severityId {
|
||||
case 1:
|
||||
|
||||
@@ -15,6 +15,7 @@ const (
|
||||
SetIncidentTitle = "set_incident_title"
|
||||
SetIncidentDescription = "set_incident_description"
|
||||
SetRCA = "set_rca"
|
||||
SetJiraLinks = "set_jira_links"
|
||||
AddTags = "add_tags"
|
||||
ShowTags = "show_tags"
|
||||
RemoveTag = "remove_tags"
|
||||
@@ -33,6 +34,7 @@ const (
|
||||
SetIncidentTypeSubmit = "set_incident_type_submit"
|
||||
UpdateTagSubmit = "updateTagSubmit"
|
||||
SetIncidentRcaSubmit = "set_rca_submit"
|
||||
SetIncidentJiraLinksSubmit = "set_Jira_links_submit"
|
||||
ShowIncidentSubmit = "show_incident_submit"
|
||||
MarkIncidentDuplicateSubmit = "mark_incident_duplicate_submit"
|
||||
)
|
||||
|
||||
@@ -78,4 +78,5 @@ create-incident.title.max-length=100
|
||||
|
||||
create-incident-v2-enabled=CREATE_INCIDENT_V2_ENABLED
|
||||
#slack details
|
||||
slack.workspace.id=SLACK_WORKSPACE_ID
|
||||
slack.workspace.id=SLACK_WORKSPACE_ID
|
||||
navi.jira.base.url=https://navihq.atlassian.net/
|
||||
30
go.mod
30
go.mod
@@ -10,7 +10,7 @@ require (
|
||||
github.com/chromedp/chromedp v0.9.1
|
||||
github.com/gin-contrib/zap v0.1.0
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/google/uuid v1.4.0
|
||||
github.com/jackc/pgx/v5 v5.3.1
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/lib/pq v1.10.7
|
||||
@@ -19,13 +19,14 @@ require (
|
||||
github.com/spf13/viper v1.16.0
|
||||
github.com/thoas/go-funk v0.9.3
|
||||
go.uber.org/zap v1.24.0
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
|
||||
gorm.io/datatypes v1.2.0
|
||||
gorm.io/driver/postgres v1.5.2
|
||||
gorm.io/gorm v1.25.2
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute v1.19.0 // indirect
|
||||
cloud.google.com/go/compute v1.23.1 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.18.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 // indirect
|
||||
@@ -53,9 +54,9 @@ require (
|
||||
github.com/gobwas/ws v1.1.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/s2a-go v0.1.3 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.8.0 // indirect
|
||||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
@@ -63,10 +64,11 @@ require (
|
||||
github.com/prometheus/common v0.42.0 // indirect
|
||||
github.com/prometheus/procfs v0.9.0 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
golang.org/x/oauth2 v0.7.0 // indirect
|
||||
golang.org/x/oauth2 v0.13.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||
google.golang.org/grpc v1.55.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect
|
||||
google.golang.org/grpc v1.59.0 // indirect
|
||||
gorm.io/driver/mysql v1.4.7 // indirect
|
||||
)
|
||||
|
||||
@@ -110,12 +112,12 @@ require (
|
||||
go.uber.org/goleak v1.1.12 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/crypto v0.9.0 // indirect
|
||||
golang.org/x/net v0.10.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
google.golang.org/api v0.122.0
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
google.golang.org/api v0.149.0
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/slack-go/slack"
|
||||
"github.com/slack-go/slack/socketmode"
|
||||
"github.com/spf13/viper"
|
||||
"houston/internal/processor/action/view"
|
||||
"houston/logger"
|
||||
"houston/model/incident"
|
||||
incidentService "houston/service/incident"
|
||||
slack2 "houston/service/slack"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type IncidentUpdateJiraLinksAction struct {
|
||||
client *socketmode.Client
|
||||
incidentRepository *incident.Repository
|
||||
incidentService *incidentService.IncidentServiceV2
|
||||
slackService *slack2.SlackService
|
||||
}
|
||||
|
||||
const (
|
||||
logTag = "[IncidentUpdateJiraLinksAction]"
|
||||
)
|
||||
|
||||
func NewIncidentUpdateJiraLinksAction(
|
||||
client *socketmode.Client,
|
||||
incidentRepository *incident.Repository,
|
||||
incidentServiceV2 *incidentService.IncidentServiceV2,
|
||||
slackService *slack2.SlackService,
|
||||
) *IncidentUpdateJiraLinksAction {
|
||||
return &IncidentUpdateJiraLinksAction{
|
||||
client: client,
|
||||
incidentRepository: incidentRepository,
|
||||
incidentService: incidentServiceV2,
|
||||
slackService: slackService,
|
||||
}
|
||||
}
|
||||
|
||||
func (action *IncidentUpdateJiraLinksAction) IncidentUpdateJiraLinksRequestProcess(callback slack.InteractionCallback, request *socketmode.Request) {
|
||||
result, err := action.incidentRepository.FindIncidentByChannelId(callback.Channel.ID)
|
||||
if err != nil || result == nil {
|
||||
logger.Error(fmt.Sprintf("%s failed to find incident entity for: %s", logTag, callback.Channel.Name))
|
||||
return
|
||||
}
|
||||
|
||||
modalRequest := view.BuildJiraLinksModal(callback.Channel, result.JiraLinks...)
|
||||
|
||||
_, err = action.client.OpenView(callback.TriggerID, modalRequest)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("%s failed to open view command for: %s", logTag, callback.Channel.Name))
|
||||
return
|
||||
}
|
||||
var payload interface{}
|
||||
action.client.Ack(*request, payload)
|
||||
}
|
||||
|
||||
func (action *IncidentUpdateJiraLinksAction) IncidentUpdateJiraLinks(callback slack.InteractionCallback, request *socketmode.Request, channel slack.Channel, user slack.User) {
|
||||
channelID := callback.View.PrivateMetadata
|
||||
incidentEntity, err := action.incidentRepository.FindIncidentByChannelId(channelID)
|
||||
if err != nil || incidentEntity == nil {
|
||||
logger.Error(fmt.Sprintf("%s failed to find incident entity for: %s", logTag, channel.Name))
|
||||
return
|
||||
}
|
||||
|
||||
jiraLinks := strings.Split(
|
||||
strings.ReplaceAll(strings.ReplaceAll(buildUpdateJiraLinksRequest(callback.View.State.Values), "\n", ""), " ", ""),
|
||||
",",
|
||||
)
|
||||
for _, l := range jiraLinks {
|
||||
if strings.HasPrefix(l, viper.GetString("navi.jira.base.url")) == false {
|
||||
err := action.slackService.PostEphemeralByChannelID(fmt.Sprintf("%s is not a valid jira link", l), user.ID, false, channelID)
|
||||
if err != nil {
|
||||
logger.Debug(fmt.Sprintf("%s failed to post jira link validation failure ephemeral to slack channel: %s", logTag, incidentEntity.IncidentName))
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
err = action.incidentService.LinkJiraToIncident(incidentEntity.ID, user.ID, jiraLinks...)
|
||||
|
||||
var payload interface{}
|
||||
action.client.Ack(*request, payload)
|
||||
}
|
||||
|
||||
func buildUpdateJiraLinksRequest(blockActions map[string]map[string]slack.BlockAction) string {
|
||||
var requestMap = make(map[string]string, 0)
|
||||
for _, actions := range blockActions {
|
||||
for actionID, a := range actions {
|
||||
if a.Type == "plain_text_input" {
|
||||
requestMap[actionID] = a.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return requestMap["jira"]
|
||||
}
|
||||
39
internal/processor/action/view/incident_jira_links.go
Normal file
39
internal/processor/action/view/incident_jira_links.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"github.com/slack-go/slack"
|
||||
"houston/common/util"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func BuildJiraLinksModal(channel slack.Channel, jiraLinks ...string) slack.ModalViewRequest {
|
||||
titleText := slack.NewTextBlockObject(slack.PlainTextType, "Jira link(s)", false, false)
|
||||
closeText := slack.NewTextBlockObject(slack.PlainTextType, "Close", false, false)
|
||||
submitText := slack.NewTextBlockObject(slack.PlainTextType, "Submit", false, false)
|
||||
|
||||
jiraLinksText := slack.NewTextBlockObject(slack.PlainTextType, " ", false, false)
|
||||
jiraLinkPlaceholder := slack.NewTextBlockObject(slack.PlainTextType, "Add comma separated jira links here...", false, false)
|
||||
jiraLinkElement := slack.NewPlainTextInputBlockElement(jiraLinkPlaceholder, "jira")
|
||||
jiraLinkElement.Multiline = true
|
||||
jiraLinkElement.InitialValue = strings.Join(jiraLinks, ", ")
|
||||
jiraLinkElement.MaxLength = 3000
|
||||
jiraLinksBlock := slack.NewInputBlock("JIRA_LINKS", jiraLinksText, nil, jiraLinkElement)
|
||||
jiraLinksBlock.Optional = false
|
||||
|
||||
blocks := slack.Blocks{
|
||||
BlockSet: []slack.Block{
|
||||
jiraLinksBlock,
|
||||
},
|
||||
}
|
||||
|
||||
return slack.ModalViewRequest{
|
||||
Type: slack.VTModal,
|
||||
Title: titleText,
|
||||
Close: closeText,
|
||||
Submit: submitText,
|
||||
Blocks: blocks,
|
||||
PrivateMetadata: channel.ID,
|
||||
CallbackID: util.SetIncidentJiraLinksSubmit,
|
||||
}
|
||||
|
||||
}
|
||||
@@ -63,11 +63,9 @@ func ExistingIncidentOptionsBlock() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"blocks": []slack.Block{
|
||||
incidentSectionBlock(),
|
||||
taskSectionBlock(),
|
||||
tagsSectionBlock(),
|
||||
onCallSectionBlock(),
|
||||
rcaSectionBlock(),
|
||||
botHelpSectionBlock(),
|
||||
jiraLinksSectionBlock(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -331,3 +329,30 @@ func rcaSectionBlock() *slack.SectionBlock {
|
||||
}
|
||||
return slack.NewSectionBlock(textBlock, nil, accessoryOption, slack.SectionBlockOptionBlockID("set_rca"))
|
||||
}
|
||||
|
||||
func jiraLinksSectionBlock() *slack.SectionBlock {
|
||||
textBlock := &slack.TextBlockObject{
|
||||
Type: "mrkdwn",
|
||||
Text: "Jira links",
|
||||
}
|
||||
|
||||
optionBlockObjects := []*slack.OptionBlockObject{
|
||||
{
|
||||
Text: &slack.TextBlockObject{
|
||||
Type: "plain_text",
|
||||
Text: "Add Jira link(s)",
|
||||
},
|
||||
Value: util.SetJiraLinks,
|
||||
},
|
||||
}
|
||||
|
||||
accessoryOption := &slack.Accessory{
|
||||
SelectElement: &slack.SelectBlockElement{
|
||||
Type: "static_select",
|
||||
ActionID: util.SetJiraLinks,
|
||||
Options: optionBlockObjects,
|
||||
Placeholder: slack.NewTextBlockObject("plain_text", "Select command", false, false),
|
||||
},
|
||||
}
|
||||
return slack.NewSectionBlock(textBlock, nil, accessoryOption, slack.SectionBlockOptionBlockID(util.SetJiraLinks))
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
"houston/model/tag"
|
||||
"houston/model/team"
|
||||
"houston/pkg/slackbot"
|
||||
incidentService "houston/service/incident"
|
||||
slack2 "houston/service/slack"
|
||||
|
||||
"github.com/slack-go/slack"
|
||||
"github.com/slack-go/slack/socketmode"
|
||||
@@ -35,27 +37,49 @@ type BlockActionProcessor struct {
|
||||
incidentUpdateTagsAction *action.IncidentUpdateTagsAction
|
||||
incidentShowTagsAction *action.IncidentShowTagsAction
|
||||
incidentUpdateRcaAction *action.IncidentUpdateRcaAction
|
||||
incidentUpdateJiraLinksAction *action.IncidentUpdateJiraLinksAction
|
||||
incidentDuplicateAction *action.DuplicateIncidentAction
|
||||
incidentServiceV2 *incidentService.IncidentServiceV2
|
||||
slackService *slack2.SlackService
|
||||
}
|
||||
|
||||
func NewBlockActionProcessor(socketModeClient *socketmode.Client, incidentRepository *incident.Repository,
|
||||
teamService *team.Repository, severityService *severity.Repository, tagService *tag.Repository,
|
||||
slackbotClient *slackbot.Client) *BlockActionProcessor {
|
||||
func NewBlockActionProcessor(
|
||||
socketModeClient *socketmode.Client,
|
||||
incidentRepository *incident.Repository,
|
||||
teamService *team.Repository,
|
||||
severityService *severity.Repository,
|
||||
tagService *tag.Repository,
|
||||
slackbotClient *slackbot.Client,
|
||||
incidentServiceV2 *incidentService.IncidentServiceV2,
|
||||
slackService *slack2.SlackService,
|
||||
) *BlockActionProcessor {
|
||||
|
||||
return &BlockActionProcessor{
|
||||
socketModeClient: socketModeClient,
|
||||
startIncidentBlockAction: action.NewStartIncidentBlockAction(socketModeClient, teamService, severityService),
|
||||
showIncidentsAction: action.ShowIncidentsBlockAction(socketModeClient, teamService),
|
||||
assignIncidentAction: action.NewAssignIncidentAction(socketModeClient, incidentRepository),
|
||||
incidentResolveAction: action.NewIncidentResolveProcessor(socketModeClient, incidentRepository, tagService, teamService, severityService),
|
||||
incidentUpdateAction: action.NewIncidentUpdateAction(socketModeClient, incidentRepository, tagService, teamService, severityService),
|
||||
incidentUpdateTypeAction: action.NewIncidentUpdateTypeAction(socketModeClient, incidentRepository, teamService, severityService, slackbotClient),
|
||||
incidentUpdateSeverityAction: action.NewIncidentUpdateSeverityAction(socketModeClient, incidentRepository, severityService, teamService, slackbotClient),
|
||||
incidentUpdateTitleAction: action.NewIncidentUpdateTitleAction(socketModeClient, incidentRepository, teamService, severityService, slackbotClient),
|
||||
socketModeClient: socketModeClient,
|
||||
startIncidentBlockAction: action.NewStartIncidentBlockAction(socketModeClient, teamService,
|
||||
severityService),
|
||||
showIncidentsAction: action.ShowIncidentsBlockAction(socketModeClient, teamService),
|
||||
assignIncidentAction: action.NewAssignIncidentAction(socketModeClient, incidentRepository),
|
||||
incidentResolveAction: action.NewIncidentResolveProcessor(socketModeClient, incidentRepository,
|
||||
tagService, teamService, severityService),
|
||||
incidentUpdateAction: action.NewIncidentUpdateAction(socketModeClient, incidentRepository,
|
||||
tagService, teamService, severityService),
|
||||
incidentUpdateTypeAction: action.NewIncidentUpdateTypeAction(socketModeClient, incidentRepository,
|
||||
teamService, severityService, slackbotClient),
|
||||
incidentUpdateSeverityAction: action.NewIncidentUpdateSeverityAction(socketModeClient, incidentRepository,
|
||||
severityService, teamService, slackbotClient),
|
||||
incidentUpdateTitleAction: action.NewIncidentUpdateTitleAction(socketModeClient, incidentRepository,
|
||||
teamService, severityService, slackbotClient),
|
||||
incidentUpdateDescriptionAction: action.NewIncidentUpdateDescriptionAction(socketModeClient, incidentRepository),
|
||||
incidentUpdateTagsAction: action.NewIncidentUpdateTagsAction(socketModeClient, incidentRepository, teamService, tagService),
|
||||
incidentShowTagsAction: action.NewIncidentShowTagsProcessor(socketModeClient, incidentRepository, tagService),
|
||||
incidentUpdateRcaAction: action.NewIncidentUpdateRcaAction(socketModeClient, incidentRepository),
|
||||
incidentDuplicateAction: action.NewDuplicateIncidentProcessor(socketModeClient, incidentRepository, tagService, teamService, severityService),
|
||||
incidentUpdateTagsAction: action.NewIncidentUpdateTagsAction(socketModeClient, incidentRepository,
|
||||
teamService, tagService),
|
||||
incidentShowTagsAction: action.NewIncidentShowTagsProcessor(socketModeClient, incidentRepository,
|
||||
tagService),
|
||||
incidentUpdateRcaAction: action.NewIncidentUpdateRcaAction(socketModeClient, incidentRepository),
|
||||
incidentUpdateJiraLinksAction: action.NewIncidentUpdateJiraLinksAction(socketModeClient, incidentRepository,
|
||||
incidentServiceV2, slackService),
|
||||
incidentDuplicateAction: action.NewDuplicateIncidentProcessor(socketModeClient, incidentRepository,
|
||||
tagService, teamService, severityService),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +119,10 @@ func (bap *BlockActionProcessor) ProcessCommand(callback slack.InteractionCallba
|
||||
{
|
||||
bap.processRcaCommands(callback, request)
|
||||
}
|
||||
case util.SetJiraLinks:
|
||||
{
|
||||
bap.processJiraLinkCommands(callback, request)
|
||||
}
|
||||
default:
|
||||
{
|
||||
msgOption := slack.MsgOptionText(fmt.Sprintf("We are working on it"), false)
|
||||
@@ -159,6 +187,16 @@ func (bap *BlockActionProcessor) processRcaCommands(callback slack.InteractionCa
|
||||
}
|
||||
}
|
||||
|
||||
func (bap *BlockActionProcessor) processJiraLinkCommands(callback slack.InteractionCallback, request *socketmode.Request) {
|
||||
action1 := util.BlockActionType(callback.ActionCallback.BlockActions[0].SelectedOption.Value)
|
||||
switch action1 {
|
||||
case util.SetJiraLinks:
|
||||
{
|
||||
bap.incidentUpdateJiraLinksAction.IncidentUpdateJiraLinksRequestProcess(callback, request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (bap *BlockActionProcessor) processTagsCommands(callback slack.InteractionCallback, request *socketmode.Request) {
|
||||
action1 := util.BlockActionType(callback.ActionCallback.BlockActions[0].SelectedOption.Value)
|
||||
switch action1 {
|
||||
@@ -189,6 +227,7 @@ type ViewSubmissionProcessor struct {
|
||||
incidentUpdateTypeAction *action.IncidentUpdateTypeAction
|
||||
incidentUpdateTagsAction *action.IncidentUpdateTagsAction
|
||||
incidentUpdateRca *action.IncidentUpdateRcaAction
|
||||
incidentUpdateJiraLinks *action.IncidentUpdateJiraLinksAction
|
||||
showIncidentSubmitAction *action.ShowIncidentsSubmitAction
|
||||
incidentDuplicateAction *action.DuplicateIncidentAction
|
||||
db *gorm.DB
|
||||
@@ -204,20 +243,33 @@ func NewViewSubmissionProcessor(
|
||||
slackbotClient *slackbot.Client,
|
||||
db *gorm.DB,
|
||||
) *ViewSubmissionProcessor {
|
||||
incidentServiceV2 := incidentService.NewIncidentServiceV2(db)
|
||||
slackService := slack2.NewSlackService()
|
||||
return &ViewSubmissionProcessor{
|
||||
socketModeClient: socketModeClient,
|
||||
incidentChannelMessageUpdateAction: action.NewIncidentChannelMessageUpdateAction(socketModeClient, incidentRepository, teamService, severityService),
|
||||
createIncidentAction: action.NewCreateIncidentProcessor(socketModeClient, incidentRepository, teamService, severityService, slackbotClient, db),
|
||||
assignIncidentAction: action.NewAssignIncidentAction(socketModeClient, incidentRepository),
|
||||
updateIncidentAction: action.NewIncidentUpdateAction(socketModeClient, incidentRepository, tagService, teamService, severityService),
|
||||
incidentUpdateTitleAction: action.NewIncidentUpdateTitleAction(socketModeClient, incidentRepository, teamService, severityService, slackbotClient),
|
||||
incidentUpdateDescriptionAction: action.NewIncidentUpdateDescriptionAction(socketModeClient, incidentRepository),
|
||||
incidentUpdateSeverityAction: action.NewIncidentUpdateSeverityAction(socketModeClient, incidentRepository, severityService, teamService, slackbotClient),
|
||||
incidentUpdateTypeAction: action.NewIncidentUpdateTypeAction(socketModeClient, incidentRepository, teamService, severityService, slackbotClient),
|
||||
incidentUpdateTagsAction: action.NewIncidentUpdateTagsAction(socketModeClient, incidentRepository, teamService, tagService),
|
||||
incidentUpdateRca: action.NewIncidentUpdateRcaAction(socketModeClient, incidentRepository),
|
||||
showIncidentSubmitAction: action.NewShowIncidentsSubmitAction(socketModeClient, incidentRepository, teamRepository),
|
||||
incidentDuplicateAction: action.NewDuplicateIncidentProcessor(socketModeClient, incidentRepository, tagService, teamRepository, severityService),
|
||||
socketModeClient: socketModeClient,
|
||||
incidentChannelMessageUpdateAction: action.NewIncidentChannelMessageUpdateAction(socketModeClient,
|
||||
incidentRepository, teamService, severityService),
|
||||
createIncidentAction: action.NewCreateIncidentProcessor(socketModeClient, incidentRepository,
|
||||
teamService, severityService, slackbotClient, db),
|
||||
assignIncidentAction: action.NewAssignIncidentAction(socketModeClient, incidentRepository),
|
||||
updateIncidentAction: action.NewIncidentUpdateAction(socketModeClient, incidentRepository,
|
||||
tagService, teamService, severityService),
|
||||
incidentUpdateTitleAction: action.NewIncidentUpdateTitleAction(socketModeClient, incidentRepository,
|
||||
teamService, severityService, slackbotClient),
|
||||
incidentUpdateDescriptionAction: action.NewIncidentUpdateDescriptionAction(socketModeClient, incidentRepository),
|
||||
incidentUpdateSeverityAction: action.NewIncidentUpdateSeverityAction(socketModeClient, incidentRepository,
|
||||
severityService, teamService, slackbotClient),
|
||||
incidentUpdateTypeAction: action.NewIncidentUpdateTypeAction(socketModeClient, incidentRepository,
|
||||
teamService, severityService, slackbotClient),
|
||||
incidentUpdateTagsAction: action.NewIncidentUpdateTagsAction(socketModeClient, incidentRepository,
|
||||
teamService, tagService),
|
||||
incidentUpdateRca: action.NewIncidentUpdateRcaAction(socketModeClient, incidentRepository),
|
||||
incidentUpdateJiraLinks: action.NewIncidentUpdateJiraLinksAction(socketModeClient, incidentRepository,
|
||||
incidentServiceV2, slackService),
|
||||
showIncidentSubmitAction: action.NewShowIncidentsSubmitAction(socketModeClient, incidentRepository,
|
||||
teamRepository),
|
||||
incidentDuplicateAction: action.NewDuplicateIncidentProcessor(socketModeClient, incidentRepository,
|
||||
tagService, teamRepository, severityService),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,6 +323,10 @@ func (vsp *ViewSubmissionProcessor) ProcessCommand(callback slack.InteractionCal
|
||||
{
|
||||
vsp.incidentUpdateRca.IncidentUpdateRca(callback, request, callback.Channel, callback.User)
|
||||
}
|
||||
case util.SetIncidentJiraLinksSubmit:
|
||||
{
|
||||
vsp.incidentUpdateJiraLinks.IncidentUpdateJiraLinks(callback, request, callback.Channel, callback.User)
|
||||
}
|
||||
case util.ShowIncidentSubmit:
|
||||
{
|
||||
vsp.showIncidentSubmitAction.ShowIncidentsCommandProcessing(callback, request)
|
||||
|
||||
@@ -44,25 +44,25 @@ const (
|
||||
// IncidentEntity all the incident created will go in this table
|
||||
type IncidentEntity struct {
|
||||
gorm.Model
|
||||
Title string `gorm:"column:title"`
|
||||
Description string `gorm:"column:description"`
|
||||
Status uint `gorm:"column:status"`
|
||||
SeverityId uint `gorm:"column:severity_id"`
|
||||
IncidentName string `gorm:"column:incident_name"`
|
||||
SlackChannel string `gorm:"column:slack_channel"`
|
||||
DetectionTime *time.Time `gorm:"column:detection_time"`
|
||||
StartTime time.Time `gorm:"column:start_time"`
|
||||
EndTime *time.Time `gorm:"column:end_time"`
|
||||
TeamId uint `gorm:"column:team_id"`
|
||||
JiraId *string `gorm:"column:jira_id"`
|
||||
ConfluenceId *string `gorm:"column:confluence_id"`
|
||||
SeverityTat time.Time `gorm:"column:severity_tat"`
|
||||
RemindMeAt *time.Time `gorm:"column:remind_me_at"`
|
||||
EnableReminder bool `gorm:"column:enable_reminder"`
|
||||
CreatedBy string `gorm:"column:created_by"`
|
||||
UpdatedBy string `gorm:"column:updated_by"`
|
||||
MetaData JSON `gorm:"column:meta_data"`
|
||||
RCA string `gorm:"column:rca_text"`
|
||||
Title string `gorm:"column:title"`
|
||||
Description string `gorm:"column:description"`
|
||||
Status uint `gorm:"column:status"`
|
||||
SeverityId uint `gorm:"column:severity_id"`
|
||||
IncidentName string `gorm:"column:incident_name"`
|
||||
SlackChannel string `gorm:"column:slack_channel"`
|
||||
DetectionTime *time.Time `gorm:"column:detection_time"`
|
||||
StartTime time.Time `gorm:"column:start_time"`
|
||||
EndTime *time.Time `gorm:"column:end_time"`
|
||||
TeamId uint `gorm:"column:team_id"`
|
||||
JiraLinks pq.StringArray `gorm:"column:jira_links;type:string[]"`
|
||||
ConfluenceId *string `gorm:"column:confluence_id"`
|
||||
SeverityTat time.Time `gorm:"column:severity_tat"`
|
||||
RemindMeAt *time.Time `gorm:"column:remind_me_at"`
|
||||
EnableReminder bool `gorm:"column:enable_reminder"`
|
||||
CreatedBy string `gorm:"column:created_by"`
|
||||
UpdatedBy string `gorm:"column:updated_by"`
|
||||
MetaData JSON `gorm:"column:meta_data"`
|
||||
RCA string `gorm:"column:rca_text"`
|
||||
}
|
||||
|
||||
func (IncidentEntity) TableName() string {
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/slack-go/slack/socketmode"
|
||||
"github.com/spf13/viper"
|
||||
logger "houston/logger"
|
||||
"houston/logger"
|
||||
"houston/model/log"
|
||||
"houston/model/severity"
|
||||
"houston/model/team"
|
||||
@@ -297,7 +297,7 @@ func (r *Repository) FindIncidentById(Id uint) (*IncidentEntity, error) {
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
return nil, nil
|
||||
return nil, fmt.Errorf("could not find incident with ID: %d", Id)
|
||||
}
|
||||
|
||||
return &incidentEntity, nil
|
||||
|
||||
@@ -3,6 +3,7 @@ package incident
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
slackClient "github.com/slack-go/slack"
|
||||
@@ -22,6 +23,7 @@ import (
|
||||
request "houston/service/request"
|
||||
service "houston/service/response"
|
||||
"houston/service/slack"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -150,6 +152,125 @@ func (i *IncidentServiceV2) CreateIncident(
|
||||
return service.ConvertToIncidentResponse(*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)
|
||||
entity, err := i.incidentRepository.FindIncidentById(incidentId)
|
||||
if err != nil {
|
||||
errorMessage := fmt.Sprintf("%s failed to fetch indient by id %d", logTag, incidentId)
|
||||
logger.Info(errorMessage)
|
||||
return fmt.Errorf("failed to fetch incident by id %d", incidentId)
|
||||
}
|
||||
if len(jiraLinks) == 1 {
|
||||
if util.Contains(entity.JiraLinks, jiraLinks[0]) {
|
||||
return errors.New("The jira link already exists")
|
||||
}
|
||||
}
|
||||
err = i.updateJiraIDs(entity, linkedBy, logTag, LinkJira, jiraLinks...)
|
||||
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
|
||||
}
|
||||
|
||||
func (i *IncidentServiceV2) UnLinkJiraFromIncident(incidentId uint, unLinkedBy, jiraLink string) error {
|
||||
logger.Info(fmt.Sprintf("%s received request to unLink jira from %d", logTag, incidentId))
|
||||
logTag := fmt.Sprintf("%s [UnLinkJiraFromIncident]", logTag)
|
||||
entity, err := i.incidentRepository.FindIncidentById(incidentId)
|
||||
if err != nil {
|
||||
logger.Info(fmt.Sprintf("%s failed to fetch indient by id %d", logTag, incidentId))
|
||||
return fmt.Errorf("failed to fetch indient by id %d", incidentId)
|
||||
}
|
||||
err = i.updateJiraIDs(entity, unLinkedBy, logTag, UnLinkJira, jiraLink)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("%s failed to unlink Jira ID(s): %s from incident %s", logTag, jiraLink, entity.IncidentName))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
LinkJira string = "link"
|
||||
UnLinkJira = "unLink"
|
||||
)
|
||||
|
||||
func (i *IncidentServiceV2) updateJiraIDs(entity *incident.IncidentEntity, user, logTag, action string, jiraLinks ...string) error {
|
||||
slackUser, err := i.slackService.GetUserByEmailOrID(user)
|
||||
var updatedBy string
|
||||
re := regexp.MustCompile(`\n+`)
|
||||
if err != nil {
|
||||
logger.Info(fmt.Sprintf("%s failed to find user in slack for given input %s", logTag, user))
|
||||
updatedBy = user
|
||||
} else {
|
||||
updatedBy = slackUser.ID
|
||||
}
|
||||
var jiraToBeUpdated []string
|
||||
var slackMessage string
|
||||
if action == LinkJira {
|
||||
jiraToBeUpdated = util.RemoveDuplicateStr(append(entity.JiraLinks, jiraLinks...))
|
||||
var newJiraLinks []string
|
||||
for _, i := range jiraLinks {
|
||||
if !util.Contains(entity.JiraLinks, i) {
|
||||
newJiraLinks = append(newJiraLinks, i)
|
||||
}
|
||||
}
|
||||
if len(newJiraLinks) == 0 {
|
||||
_ = i.slackService.PostEphemeralByChannelID("`No new jira link to add`", updatedBy, false, entity.SlackChannel)
|
||||
} else if len(newJiraLinks) > 1 {
|
||||
slackMessage = fmt.Sprintf(
|
||||
"<@%s> `linked the following jiras to the incident: \"%s\"`",
|
||||
updatedBy, re.ReplaceAllString(strings.Join(newJiraLinks, ", "), " "),
|
||||
)
|
||||
} else {
|
||||
slackMessage = fmt.Sprintf(
|
||||
"<@%s> `linked the following jira to the incident: \"%s\"`",
|
||||
updatedBy, re.ReplaceAllString(strings.Join(newJiraLinks, ", "), " "),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
var validatedJiraIDsToBeRemoved []string
|
||||
for _, j := range jiraLinks {
|
||||
if util.Contains(entity.JiraLinks, j) {
|
||||
validatedJiraIDsToBeRemoved = append(validatedJiraIDsToBeRemoved, j)
|
||||
}
|
||||
}
|
||||
jiraToBeUpdated = util.Difference(entity.JiraLinks, validatedJiraIDsToBeRemoved)
|
||||
if len(validatedJiraIDsToBeRemoved) > 0 {
|
||||
slackMessage = fmt.Sprintf(
|
||||
"<@%s> `unlinked the following jira from the incident: \"%s\"`",
|
||||
updatedBy, re.ReplaceAllString(strings.Join(validatedJiraIDsToBeRemoved, ", "), " "),
|
||||
)
|
||||
}
|
||||
}
|
||||
if jiraToBeUpdated == nil {
|
||||
jiraToBeUpdated = make([]string, 0)
|
||||
}
|
||||
|
||||
entity.JiraLinks = jiraToBeUpdated
|
||||
entity.UpdatedBy = updatedBy
|
||||
entity.UpdatedAt = time.Now()
|
||||
|
||||
err = i.incidentRepository.UpdateIncident(entity)
|
||||
if err != nil {
|
||||
errorMessage := fmt.Sprintf("%s failed to update Jira IDs for incident %s", logTag, entity.IncidentName)
|
||||
logger.Error(errorMessage)
|
||||
return fmt.Errorf(errorMessage, err)
|
||||
}
|
||||
|
||||
if slackMessage != "" {
|
||||
_, err = i.slackService.PostMessageByChannelID(slackMessage, false, entity.SlackChannel)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("%s failed to post message about update jira link to incident for %s", logTag, entity.IncidentName))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createIncidentWorkflow(
|
||||
i *IncidentServiceV2,
|
||||
channel *slackClient.Channel,
|
||||
@@ -540,7 +661,7 @@ func tagPseOrDevOncallToIncident(
|
||||
//oncall handles should already be added to channel
|
||||
ts, err := i.slackService.PostMessage(fmt.Sprintf("<@%s>", onCallToBeTagged), false, channel)
|
||||
if err != nil {
|
||||
logger.Debug(
|
||||
logger.Info(
|
||||
fmt.Sprintf("%s [%s] failed to tag oncall in channel: %s", logTag, incidentName, channel.Name),
|
||||
zap.Error(err),
|
||||
)
|
||||
|
||||
7
service/request/link_jira_request.go
Normal file
7
service/request/link_jira_request.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package service
|
||||
|
||||
type LinkJiraRequest struct {
|
||||
IncidentID uint `json:"incident_id"`
|
||||
JiraLink string `json:"jira_link"`
|
||||
User string `json:"user"`
|
||||
}
|
||||
@@ -1,34 +1,35 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/lib/pq"
|
||||
"houston/model/incident"
|
||||
"time"
|
||||
)
|
||||
|
||||
type IncidentResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Status uint `json:"status"`
|
||||
StatusName string `json:"statusName"`
|
||||
SeverityId uint `json:"severityId"`
|
||||
SeverityName string `json:"severityName"`
|
||||
IncidentName string `json:"incidentName"`
|
||||
SlackChannel string `json:"slackChannel"`
|
||||
DetectionTime *time.Time `json:"detectionTime"`
|
||||
StartTime time.Time `json:"startTime"`
|
||||
EndTime *time.Time `json:"endTime"`
|
||||
TeamId uint `json:"teamId"`
|
||||
TeamName string `json:"teamName"`
|
||||
JiraId *string `json:"jiraId"`
|
||||
ConfluenceId *string `json:"confluenceId"`
|
||||
SeverityTat time.Time `json:"severityTat"`
|
||||
RemindMeAt *time.Time `json:"remindMeAt"`
|
||||
EnableReminder bool `json:"enableReminder"`
|
||||
CreatedBy string `json:"createdBy"`
|
||||
UpdatedBy string `json:"updatedBy"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
ID uint `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Status uint `json:"status"`
|
||||
StatusName string `json:"statusName"`
|
||||
SeverityId uint `json:"severityId"`
|
||||
SeverityName string `json:"severityName"`
|
||||
IncidentName string `json:"incidentName"`
|
||||
SlackChannel string `json:"slackChannel"`
|
||||
DetectionTime *time.Time `json:"detectionTime"`
|
||||
StartTime time.Time `json:"startTime"`
|
||||
EndTime *time.Time `json:"endTime"`
|
||||
TeamId uint `json:"teamId"`
|
||||
TeamName string `json:"teamName"`
|
||||
JiraLinks pq.StringArray `json:"jiraLinks"`
|
||||
ConfluenceId *string `json:"confluenceId"`
|
||||
SeverityTat time.Time `json:"severityTat"`
|
||||
RemindMeAt *time.Time `json:"remindMeAt"`
|
||||
EnableReminder bool `json:"enableReminder"`
|
||||
CreatedBy string `json:"createdBy"`
|
||||
UpdatedBy string `json:"updatedBy"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func ConvertToIncidentResponse(incidentEntity incident.IncidentEntity) IncidentResponse {
|
||||
@@ -44,7 +45,7 @@ func ConvertToIncidentResponse(incidentEntity incident.IncidentEntity) IncidentR
|
||||
DetectionTime: incidentEntity.DetectionTime,
|
||||
StartTime: incidentEntity.StartTime,
|
||||
EndTime: incidentEntity.EndTime,
|
||||
JiraId: incidentEntity.JiraId,
|
||||
JiraLinks: incidentEntity.JiraLinks,
|
||||
ConfluenceId: incidentEntity.ConfluenceId,
|
||||
SeverityTat: incidentEntity.SeverityTat,
|
||||
RemindMeAt: incidentEntity.RemindMeAt,
|
||||
|
||||
@@ -58,6 +58,17 @@ func (s *SlackService) PostMessageByChannelID(message string, escape bool, chann
|
||||
return timeStamp, nil
|
||||
}
|
||||
|
||||
func (s *SlackService) PostEphemeralByChannelID(message string, userID string, escape bool, channelID string) error {
|
||||
msgOption := slack.MsgOptionText(message, escape)
|
||||
_, err := s.SocketModeClient.PostEphemeral(channelID, userID, msgOption)
|
||||
if err != nil {
|
||||
e := fmt.Sprintf("%s Failed to post message into channel: %s", logTag, channelID)
|
||||
logger.Error(e, zap.Error(err))
|
||||
return fmt.Errorf("%s : %+v", e, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SlackService) PostMessageBlocks(channelID string, blocks slack.Blocks, color string) (string, error) {
|
||||
att := slack.Attachment{Blocks: blocks, Color: color}
|
||||
messageOption := slack.MsgOptionAttachments(att)
|
||||
@@ -105,10 +116,13 @@ func (s *SlackService) GetUserByEmailOrID(userEmailOrSlackID string) (*slack.Use
|
||||
slackUser, err = s.GetUserBySlackID(userEmailOrSlackID)
|
||||
if err != nil {
|
||||
slackUser, err = s.GetUserByEmail(userEmailOrSlackID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"failed to find slack user by email or slack for %s. Error: %v", userEmailOrSlackID, err,
|
||||
)
|
||||
}
|
||||
}
|
||||
return slackUser, fmt.Errorf(
|
||||
"failed to find slack user by email or slack for %s. Error: %v", userEmailOrSlackID, err,
|
||||
)
|
||||
return slackUser, nil
|
||||
}
|
||||
|
||||
func (s *SlackService) GetUserByEmail(userEmail string) (*slack.User, error) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/spf13/viper"
|
||||
service "houston/service/request"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ValidatePage(pageSize, pageNumber string) (int64, int64, error) {
|
||||
@@ -82,6 +83,26 @@ func ValidateCreateIncidentRequestV2(request service.CreateIncidentRequestV2) er
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateLinkJiraRequest(request service.LinkJiraRequest) error {
|
||||
var errorMessage []string
|
||||
if request.IncidentID == 0 {
|
||||
errorMessage = append(errorMessage, "Enter a valid incident I.D.")
|
||||
}
|
||||
if request.JiraLink == "" {
|
||||
errorMessage = append(errorMessage, "Jira links can not be empty")
|
||||
}
|
||||
if strings.HasPrefix(request.JiraLink, viper.GetString("navi.jira.base.url")) == false {
|
||||
errorMessage = append(errorMessage, fmt.Sprintf("%s is not a valid Jira link", request.JiraLink))
|
||||
}
|
||||
if request.User == "" {
|
||||
errorMessage = append(errorMessage, "Enter a valid navi email or slack ID for the linked_by field")
|
||||
}
|
||||
if errorMessage != nil {
|
||||
return fmt.Errorf(strings.Join(errorMessage, ". "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateUpdateTeamRequest(request service.UpdateTeamRequest) error {
|
||||
if request.Id == 0 {
|
||||
return errors.New("id should be present in update request")
|
||||
|
||||
Reference in New Issue
Block a user