diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 979e154..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f784377 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +/.idea +.idea +/out + +go.sum + +.DS_STORE \ No newline at end of file diff --git a/model/request/incident_status.go b/api/request/severity.go similarity index 74% rename from model/request/incident_status.go rename to api/request/severity.go index 24341b4..9cc7c4a 100644 --- a/model/request/incident_status.go +++ b/api/request/severity.go @@ -1,6 +1,6 @@ package request -type AddIncidentStatusRequest struct { +type AddSeverityRequest struct { Name string `json:"name,omitempty"` Description string `json:"description,omitempty"` } diff --git a/model/request/team.go b/api/request/team.go similarity index 62% rename from model/request/team.go rename to api/request/team.go index 3445f58..8c1659b 100644 --- a/model/request/team.go +++ b/api/request/team.go @@ -2,5 +2,5 @@ package request type AddTeamRequest struct { Name string `json:"name,omitempty"` - OncallHandle string `json:"oncall_handle,omitempty"` + OnCallHandle string `json:"on_call_handle,omitempty"` } diff --git a/cmd/app/handler/severity_handler.go b/cmd/app/handler/severity_handler.go index e9b76f5..df0d0ae 100644 --- a/cmd/app/handler/severity_handler.go +++ b/cmd/app/handler/severity_handler.go @@ -1,8 +1,7 @@ package handler import ( - "houston/model/request" - "houston/pkg/postgres/query" + "houston/api/request" "net/http" "github.com/gin-gonic/gin" @@ -33,11 +32,11 @@ func (sh *severityHandler) AddSeverity(c *gin.Context) { sh.logger.Info("add severity request received", zap.String("severity_name", addSeverityRequest.Name)) - err := query.AddSeverity(sh.db, sh.logger, addSeverityRequest) - if err != nil { - c.JSON(http.StatusInternalServerError, err) - return - } + //err := query.AddSeverity(sh.db, sh.logger, addSeverityRequest) + //if err != nil { + // c.JSON(http.StatusInternalServerError, err) + // return + //} c.JSON(http.StatusOK, nil) diff --git a/cmd/app/handler/slack_handler.go b/cmd/app/handler/slack_handler.go index bea47e0..7ebb571 100644 --- a/cmd/app/handler/slack_handler.go +++ b/cmd/app/handler/slack_handler.go @@ -1,32 +1,41 @@ package handler import ( - "houston/pkg/slack/houston" - "github.com/slack-go/slack" "github.com/slack-go/slack/slackevents" "github.com/slack-go/slack/socketmode" "go.uber.org/zap" "gorm.io/gorm" + "houston/internal/processor" + "houston/pkg/postgres/service/incident" + "houston/pkg/postgres/service/severity" + "houston/pkg/postgres/service/tag" + "houston/pkg/postgres/service/team" + "houston/pkg/slackbot" ) type slackHandler struct { - logger *zap.Logger - socketModeClient *socketmode.Client - slackClient *slack.Client - db *gorm.DB - houstonCommandHandler *houston.HoustonCommandHandler + logger *zap.Logger + socketModeClient *socketmode.Client + slashCommandProcessor *processor.SlashCommandProcessor + memberJoinCallbackProcessor *processor.MemberJoinedCallbackEventProcessor + blockActionProcessor *processor.BlockActionProcessor + viewSubmissionProcessor *processor.ViewSubmissionProcessor } -func NewSlackHandler(logger *zap.Logger, socketModeClient *socketmode.Client, - db *gorm.DB, - slackClient *slack.Client) *slackHandler { +func NewSlackHandler(logger *zap.Logger, gormClient *gorm.DB, socketModeClient *socketmode.Client) *slackHandler { + severityService := severity.NewSeverityService(logger, gormClient) + incidentService := incident.NewIncidentService(logger, gormClient, severityService) + tagService := tag.NewTagService(logger, gormClient) + teamService := team.NewTeamService(logger, gormClient) + slackbotClient := slackbot.NewSlackClient(logger, socketModeClient) return &slackHandler{ - logger: logger, - socketModeClient: socketModeClient, - db: db, - slackClient: slackClient, - houstonCommandHandler: houston.NewHoustonCommandHandler(socketModeClient, logger, db), + logger: logger, + socketModeClient: socketModeClient, + slashCommandProcessor: processor.NewSlashCommandProcessor(logger, socketModeClient, incidentService), + memberJoinCallbackProcessor: processor.NewMemberJoinedCallbackEventProcessor(logger, socketModeClient, incidentService, teamService, severityService), + blockActionProcessor: processor.NewBlockActionProcessor(logger, socketModeClient, incidentService, teamService, severityService, tagService, slackbotClient), + viewSubmissionProcessor: processor.NewViewSubmissionProcessor(logger, socketModeClient, incidentService, teamService, severityService, tagService, slackbotClient), } } @@ -35,58 +44,64 @@ func (sh *slackHandler) HoustonConnect() { for evt := range sh.socketModeClient.Events { switch evt.Type { case socketmode.EventTypeConnecting: - sh.logger.Info("houston connecting to slack with socket mode ...") + { + sh.logger.Info("houston connecting to slackbot with socket mode") + } case socketmode.EventTypeConnectionError: - sh.logger.Error("Blazelss connection failed. retrying later ...") + { + sh.logger.Error("houston connection failed.") + } case socketmode.EventTypeConnected: - sh.logger.Info("houston connected to slack with socket mode.") + { + sh.logger.Info("houston connected to slackbot with socket mode") + } case socketmode.EventTypeEventsAPI: - ev, _ := evt.Data.(slackevents.EventsAPIEvent) - sh.logger.Info("event api", zap.Any("ev", ev)) - switch ev.Type { - case slackevents.CallbackEvent: - iev := ev.InnerEvent - switch ev := iev.Data.(type) { - case *slackevents.AppMentionEvent: - case *slackevents.MemberJoinedChannelEvent: - sh.houstonCommandHandler.ProcessMemberJoinEvent(ev, evt.Request) + { + ev, _ := evt.Data.(slackevents.EventsAPIEvent) + sh.logger.Info("event api", zap.Any("ev", ev)) + switch ev.Type { + case slackevents.CallbackEvent: + iev := ev.InnerEvent + switch ev := iev.Data.(type) { + case *slackevents.MemberJoinedChannelEvent: + sh.memberJoinCallbackProcessor.ProcessCommand(ev, evt.Request) + } } - case slackevents.URLVerification: - case string(slackevents.MemberJoinedChannel): } case socketmode.EventTypeInteractive: - callback, _ := evt.Data.(slack.InteractionCallback) + { + callback, _ := evt.Data.(slack.InteractionCallback) - switch callback.Type { - case slack.InteractionTypeBlockActions: - sh.logger.Info("received interaction type block action", - zap.String("action_id", callback.ActionID), zap.String("block_id", callback.BlockID)) - sh.houstonCommandHandler.ProcessButtonHandler(callback, evt.Request) - case slack.InteractionTypeShortcut: - case slack.InteractionTypeViewSubmission: - sh.logger.Info("received interaction type view submission", - zap.String("action_id", callback.ActionID), zap.String("block_id", callback.BlockID)) - - sh.logger.Info("payload data", zap.Any("callback", callback), zap.Any("request", evt.Request)) - sh.houstonCommandHandler.ProcessModalCallbackEvent(callback, evt.Request) - case slack.InteractionTypeDialogSubmission: - default: + switch callback.Type { + case slack.InteractionTypeBlockActions: + { + sh.logger.Info("received interaction type block action", + zap.String("action_id", callback.ActionID), zap.String("block_id", callback.BlockID)) + sh.blockActionProcessor.ProcessCommand(callback, evt.Request) + } + case slack.InteractionTypeViewSubmission: + { + sh.logger.Info("received interaction type view submission", + zap.String("action_id", callback.ActionID), zap.String("block_id", callback.BlockID)) + sh.logger.Info("payload data", zap.Any("callback", callback), zap.Any("request", evt.Request)) + sh.viewSubmissionProcessor.ProcessCommand(callback, evt.Request) + } + } } - // command.ProcessStartIncidentCommand(client, evt.Request, callback.TriggerID) case socketmode.EventTypeSlashCommand: - cmd, _ := evt.Data.(slack.SlashCommand) + { + cmd, _ := evt.Data.(slack.SlashCommand) - sh.logger.Info("houston processing slash command", - zap.String("command", cmd.Text), zap.String("channel_name", cmd.ChannelName), zap.String("user_name", cmd.UserName)) + sh.logger.Info("houston processing slash command", + zap.String("command", cmd.Text), zap.String("channel_name", cmd.ChannelName), zap.String("user_name", cmd.UserName)) - sh.houstonCommandHandler.ProcessSlashCommand(evt) - - // command.ProcessMainCommand(client, evt.Request) - - // client.Ack(*evt.Request, payload) + sh.slashCommandProcessor.ProcessSlashCommand(evt) + } default: - sh.logger.Error("houston unexpected event type received", zap.Any("event_type", evt.Type)) + { + sh.logger.Error("houston unexpected event type received", zap.Any("event_type", evt.Type)) + } } } }() diff --git a/cmd/app/handler/team_handler.go b/cmd/app/handler/team_handler.go index 0480925..8c46f6e 100644 --- a/cmd/app/handler/team_handler.go +++ b/cmd/app/handler/team_handler.go @@ -1,8 +1,7 @@ package handler import ( - "houston/model/request" - "houston/pkg/postgres/query" + "houston/api/request" "net/http" "github.com/gin-gonic/gin" @@ -32,11 +31,11 @@ func (th *teamHandler) AddTeam(c *gin.Context) { } th.logger.Info("add team request received", zap.String("team_name", addTeamRequest.Name)) - err := query.AddTeam(th.db, addTeamRequest) - if err != nil { - c.JSON(http.StatusInternalServerError, err) - return - } + //err := query.AddTeam(th.db, addTeamRequest) + //if err != nil { + // c.JSON(http.StatusInternalServerError, err) + // return + //} c.JSON(http.StatusOK, nil) } diff --git a/cmd/app/server.go b/cmd/app/server.go index addb381..6586f3b 100644 --- a/cmd/app/server.go +++ b/cmd/app/server.go @@ -2,13 +2,11 @@ package app import ( "fmt" - "houston/cmd/app/handler" - "houston/internal/cron" - "github.com/gin-gonic/gin" "github.com/spf13/viper" "go.uber.org/zap" "gorm.io/gorm" + "houston/cmd/app/handler" ) type Server struct { @@ -29,14 +27,14 @@ func (s *Server) Handler() { s.teamHandler() s.severityHandler() - //this should always be at the end since it opens websocket to connect to slack + //this should always be at the end since it opens websocket to connect to slackbot s.houstonHandler() } func (s *Server) houstonHandler() { houstonClient := NewHoustonClient(s.logger) - houstonHandler := handler.NewSlackHandler(s.logger, houstonClient.socketmodeClient, s.db, houstonClient.slackClient) - cron.RunJob(houstonClient.slackClient, s.db, s.logger) + houstonHandler := handler.NewSlackHandler(s.logger, s.db, houstonClient.socketModeClient) + //cron.RunJob(houstonClient.slackClient, s.db, s.logger) houstonHandler.HoustonConnect() } diff --git a/cmd/app/slack.go b/cmd/app/slack.go index ed32a48..a1187f6 100644 --- a/cmd/app/slack.go +++ b/cmd/app/slack.go @@ -1,6 +1,7 @@ package app import ( + "github.com/spf13/viper" "os" "strings" @@ -10,23 +11,21 @@ import ( ) type HoustonSlack struct { - socketmodeClient *socketmode.Client - slackClient *slack.Client + socketModeClient *socketmode.Client logger *zap.Logger } func NewHoustonClient(logger *zap.Logger) *HoustonSlack { - socketmodeClient, slackClient := slackConnect(logger) + socketModeClient := slackConnect(logger) return &HoustonSlack{ - socketmodeClient: socketmodeClient, - slackClient: slackClient, + socketModeClient: socketModeClient, logger: logger, } } -func slackConnect(logger *zap.Logger) (*socketmode.Client, *slack.Client) { - appToken := os.Getenv("HOUSTON_SLACK_APP_TOKEN") +func slackConnect(logger *zap.Logger) *socketmode.Client { + appToken := viper.GetString("HOUSTON_SLACK_APP_TOKEN") if appToken == "" { logger.Error("HOUSTON_SLACK_APP_TOKEN must be set.") os.Exit(1) @@ -37,7 +36,7 @@ func slackConnect(logger *zap.Logger) (*socketmode.Client, *slack.Client) { os.Exit(1) } - botToken := os.Getenv("HOUSTON_SLACK_BOT_TOKEN") + botToken := viper.GetString("HOUSTON_SLACK_BOT_TOKEN") if botToken == "" { logger.Error("HOUSTON_SLACK_BOT_TOKEN must be set.") os.Exit(1) @@ -59,5 +58,5 @@ func slackConnect(logger *zap.Logger) (*socketmode.Client, *slack.Client) { socketmode.OptionDebug(false), ) - return client, api + return client } diff --git a/cmd/main.go b/cmd/main.go index 0b383ed..5ea016a 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,6 +1,7 @@ package main import ( + "github.com/spf13/viper" "houston/cmd/app" "houston/config" "houston/pkg/postgres" @@ -21,15 +22,15 @@ func main() { command := &cobra.Command{ Use: "houston", - Short: "houston is replacement for blameless and incident management slack bot at Navi", - Long: "houston is replacement for blameless and incident management slack bot at Navi", + Short: "houston is replacement for blameless and incident management slackbot bot at Navi", + Long: "houston is replacement for blameless and incident management slackbot bot at Navi", RunE: func(cmd *cobra.Command, args []string) error { r := gin.New() r.Use(ginzap.Ginzap(logger, time.RFC3339, true)) r.Use(ginzap.RecoveryWithZap(logger, true)) - db := postgres.PQConnection(logger) + db := postgres.NewGormClient(viper.GetString("POSTGRES_DSN"), logger) sv := app.NewServer(r, logger, db) sv.Handler() diff --git a/common/util/common_util.go b/common/util/common_util.go new file mode 100644 index 0000000..1559458 --- /dev/null +++ b/common/util/common_util.go @@ -0,0 +1,14 @@ +package util + +func GetColorBySeverity(severityId uint) string { + switch severityId { + case 1: + return "#fc3838" + case 2: + return "#fc9338" + case 3: + return "#ebfa1b" + default: + return "#7288db" + } +} diff --git a/common/util/constant.go b/common/util/constant.go new file mode 100644 index 0000000..a2957d9 --- /dev/null +++ b/common/util/constant.go @@ -0,0 +1,33 @@ +package util + +type BlockActionType string + +const ( + StartIncident BlockActionType = "start_incident" + ShowIncidents = "show_incidents" + Incident = "incident" + Tags = "tags" + AssignIncidentRole = "assign_incident_role" + ResolveIncident = "resolve_incident" + SetIncidentStatus = "set_incident_status" + SetIncidentType = "set_incident_type" + SetIncidentSeverity = "set_incident_severity" + SetIncidentTitle = "set_incident_title" + SetIncidentDescription = "set_incident_description" + AddTags = "add_tags" + ShowTags = "show_tags" + RemoveTag = "remove_tags" +) + +type ViewSubmissionType string + +const ( + StartIncidentSubmit ViewSubmissionType = "start_incident_submit" + AssignIncidentRoleSubmit = "assign_incident_role_submit" + SetIncidentStatusSubmit = "set_incident_status_submit" + SetIncidentTitleSubmit = "set_incident_title_submit" + SetIncidentDescriptionSubmit = "set_incident_description_submit" + SetIncidentSeveritySubmit = "set_incident_severity_submit" + SetIncidentTypeSubmit = "set_incident_type_submit" + UpdateTagSubmit = "updateTagSubmit" +) diff --git a/config/application.properties b/config/application.properties index 9e97d01..52d97d1 100644 --- a/config/application.properties +++ b/config/application.properties @@ -1,6 +1,7 @@ -HOUSTON_SLACK_APP_TOKEN=xapp-1-A0444DP8XU5-4136817231606-8e441610b1696fd4f5fa81e9507a3d6f625504ac07588c63543972ff3b147321 -HOUSTON_SLACK_BOT_TOKEN=j22 +HOUSTON_SLACK_APP_TOKEN=xapp-1-A04TBQ7PGSJ-4960174100544-3c648a093c830a718bd81aff36cf0f433633312e16a0a6e11408bf5063a4785d +HOUSTON_SLACK_BOT_TOKEN=token ENVIRONMENT=local SHOW_INCIDENTS_LIMIT=10 PORT=8080 -METRIC_PORT=9090 \ No newline at end of file +METRIC_PORT=9090 +POSTGRES_DSN=postgresql://postgres:admin@localhost:5432/houston diff --git a/config/config.go b/config/config.go index 2f62b63..6a593b4 100644 --- a/config/config.go +++ b/config/config.go @@ -14,7 +14,7 @@ func LoadHoustonConfig(logger *zap.Logger) { err := viper.ReadInConfig() if err != nil { - logger.Error("Error while loading blazeless configuration", zap.Error(err)) + logger.Error("Error while loading houston configuration", zap.Error(err)) os.Exit(1) } } diff --git a/entity/alerts.go b/entity/alerts.go deleted file mode 100644 index bf8a239..0000000 --- a/entity/alerts.go +++ /dev/null @@ -1,16 +0,0 @@ -package entity - -import "gorm.io/gorm" - -type AlertsEntity struct { - gorm.Model - Name string - Team string - Service string - Status bool - Version int -} - -func (AlertsEntity) TableName() string { - return "alerts" -} diff --git a/entity/audit.go b/entity/audit.go deleted file mode 100644 index 08b60d8..0000000 --- a/entity/audit.go +++ /dev/null @@ -1,16 +0,0 @@ -package entity - -import "gorm.io/gorm" - -type IncidentAuditEntity struct { - gorm.Model - IncidentId uint - Event string - UserName string - UserId string - Version int -} - -func (IncidentAuditEntity) TableName() string { - return "incident_audit" -} diff --git a/entity/contributing_factor.go b/entity/contributing_factor.go deleted file mode 100644 index 3aa7c6d..0000000 --- a/entity/contributing_factor.go +++ /dev/null @@ -1,13 +0,0 @@ -package entity - -import "gorm.io/gorm" - -type ContributingFactorEntity struct { - gorm.Model - Label string - Version int -} - -func (ContributingFactorEntity) TableName() string { - return "contributing_factor" -} diff --git a/entity/customer_tags.go b/entity/customer_tags.go deleted file mode 100644 index f82c956..0000000 --- a/entity/customer_tags.go +++ /dev/null @@ -1,13 +0,0 @@ -package entity - -import "gorm.io/gorm" - -type CustomerTagsEntity struct { - gorm.Model - Label string - Version int -} - -func (CustomerTagsEntity) TableName() string { - return "customer_tags" -} diff --git a/entity/incident.go b/entity/incident.go deleted file mode 100644 index 1a46ed5..0000000 --- a/entity/incident.go +++ /dev/null @@ -1,54 +0,0 @@ -package entity - -import ( - "time" - - "gorm.io/gorm" -) - -type IncidentStatus string - -const ( - Investigating IncidentStatus = "INVESTIGATING" - Identified = "IDENTIFIED" - Monitoring = "MONITORING" - Resolved = "RESOLVED" - Duplicated = "DUPLICATED" -) - -type IncidentEntity struct { - gorm.Model - Title string - Description string - Status IncidentStatus - SeverityId int - IncidentName string - SlackChannel string - DetectionTime *time.Time - CustomerImpactStartTime time.Time - CustomerImpactEndTime *time.Time - TeamsId int - JiraId string - ConfluenceId string - SeverityTat time.Time - RemindMeAt *time.Time - EnableReminder bool - CreatedBy string - UpdatedBy string - Version int -} - -func (IncidentEntity) TableName() string { - return "incidents" -} - -type IncidentStatusEntity struct { - gorm.Model - Name string - Description string - Version int -} - -func (IncidentStatusEntity) TableName() string { - return "incident_status" -} diff --git a/entity/incident_roles.go b/entity/incident_roles.go deleted file mode 100644 index a9d2dec..0000000 --- a/entity/incident_roles.go +++ /dev/null @@ -1,24 +0,0 @@ -package entity - -import "gorm.io/gorm" - -type IncidentRole string - -const ( - RETROSPECTIVE IncidentRole = "RETROSPECTIVE" - RESPONDER IncidentRole = "RESPONDER" - SERVICE_OWNER IncidentRole = "SERVICE_OWNER" -) - -type IncidentRoles struct { - gorm.Model - IncidentId int - Role IncidentRole - AssignedToUserSlackId string - AssignedByUserSlackId string - Version int -} - -func (IncidentRoles) TableName() string { - return "incident_roles" -} diff --git a/entity/incident_severity_team_join.go b/entity/incident_severity_team_join.go deleted file mode 100644 index a1f5753..0000000 --- a/entity/incident_severity_team_join.go +++ /dev/null @@ -1,12 +0,0 @@ -package entity - -type IncidentSeverityTeamJoinEntity struct { - IncidentId int - Title string - Status IncidentStatus - SeverityId int - SeverityName string - SlackChannel string - TeamsId int - TeamsName string -} diff --git a/entity/incident_tags_contributing_factor_mapping.go b/entity/incident_tags_contributing_factor_mapping.go deleted file mode 100644 index 3ba9b8c..0000000 --- a/entity/incident_tags_contributing_factor_mapping.go +++ /dev/null @@ -1,14 +0,0 @@ -package entity - -import "gorm.io/gorm" - -type IncidentTagsContributingFactorMapping struct { - gorm.Model - IncidentId int - ContributingFactorId int - Version int -} - -func (IncidentTagsContributingFactorMapping) TableName() string { - return "incidents_tags_contributing_factor_mapping" -} diff --git a/entity/incidents_tags_customer_mapping.go b/entity/incidents_tags_customer_mapping.go deleted file mode 100644 index 5f2d1ab..0000000 --- a/entity/incidents_tags_customer_mapping.go +++ /dev/null @@ -1,14 +0,0 @@ -package entity - -import "gorm.io/gorm" - -type IncidentTagsCustomerMapping struct { - gorm.Model - IncidentId int - CustomerTagsId int - Version int -} - -func (IncidentTagsCustomerMapping) TableName() string { - return "incidents_tags_customer_mapping" -} diff --git a/entity/incidents_tags_data_platform_mapping.go b/entity/incidents_tags_data_platform_mapping.go deleted file mode 100644 index 00fbb9e..0000000 --- a/entity/incidents_tags_data_platform_mapping.go +++ /dev/null @@ -1,14 +0,0 @@ -package entity - -import "gorm.io/gorm" - -type IncidentsTagsDataPlatformMapping struct { - gorm.Model - IncidentId int - DataPlatformTag string - Version int -} - -func (IncidentsTagsDataPlatformMapping) TableName() string { - return "incidents_tags_data_platform_mapping" -} diff --git a/entity/incidents_tags_mapping.go b/entity/incidents_tags_mapping.go deleted file mode 100644 index 20e6032..0000000 --- a/entity/incidents_tags_mapping.go +++ /dev/null @@ -1,14 +0,0 @@ -package entity - -import "gorm.io/gorm" - -type IncidentsTagsMapping struct { - gorm.Model - IncidentId int - TagId int - Version int -} - -func (IncidentsTagsMapping) TableName() string { - return "incidents_tags_mapping" -} diff --git a/entity/messages.go b/entity/messages.go deleted file mode 100644 index 78b5758..0000000 --- a/entity/messages.go +++ /dev/null @@ -1,21 +0,0 @@ -package entity - -import ( - "time" - - "gorm.io/gorm" -) - -type MessageEntity struct { - gorm.Model - SlackChannel string `gorm:"column:slack_channel"` - IncidentName string `gorm:"column:incident_name"` - MessageTimeStamp string `gorm:"column:message_timestamp"` - CreatedAt time.Time - UpdatedAt time.Time - Version int -} - -func (MessageEntity) TableName() string { - return "messages" -} diff --git a/entity/severity.go b/entity/severity.go deleted file mode 100644 index e22399c..0000000 --- a/entity/severity.go +++ /dev/null @@ -1,15 +0,0 @@ -package entity - -import "gorm.io/gorm" - -type SeverityEntity struct { - gorm.Model - Name string `gorm:"column:name"` - Description string `gorm:"column:description"` - Version int - Sla int -} - -func (SeverityEntity) TableName() string { - return "severity" -} diff --git a/entity/tags.go b/entity/tags.go deleted file mode 100644 index 1d8669e..0000000 --- a/entity/tags.go +++ /dev/null @@ -1,13 +0,0 @@ -package entity - -import "gorm.io/gorm" - -type TagsEntity struct { - gorm.Model - Label string `gorm:"column:label"` - Version int -} - -func (TagsEntity) TableName() string { - return "tags" -} diff --git a/entity/team_tags_mapping.go b/entity/team_tags_mapping.go deleted file mode 100644 index 7a27f34..0000000 --- a/entity/team_tags_mapping.go +++ /dev/null @@ -1,14 +0,0 @@ -package entity - -import "gorm.io/gorm" - -type TeamTagsMapping struct { - gorm.Model - TeamsId int - TagId int - Version int -} - -func (TeamTagsMapping) TableName() string { - return "teams_tags_mapping" -} diff --git a/entity/teams.go b/entity/teams.go deleted file mode 100644 index f3c3739..0000000 --- a/entity/teams.go +++ /dev/null @@ -1,20 +0,0 @@ -package entity - -import ( - "gorm.io/gorm" -) - -type TeamEntity struct { - gorm.Model - Name string `gorm:"column:name"` - OncallHandle string `gorm:"column:oncall_handle"` - SecondaryOncallHandle string `gorm:"column:secondary_oncall_handle"` - ManagerHandle string `gorm:"column:manager_handle"` - SecondaryManagerHandle string `gorm:"column:secondary_manager_handle"` - Active bool `gorm:"column:active"` - Version int `gorm:"column:version"` -} - -func (TeamEntity) TableName() string { - return "teams" -} diff --git a/entity/teams_severity_users_mapping.go b/entity/teams_severity_users_mapping.go deleted file mode 100644 index 530a9db..0000000 --- a/entity/teams_severity_users_mapping.go +++ /dev/null @@ -1,26 +0,0 @@ -package entity - -import ( - "gorm.io/gorm" -) - -type EntityType string - -const ( - TEAM EntityType = "TEAM" - SEVERITY EntityType = "SEVERITY" -) - -type TeamsSeverityUsersMapping struct { - gorm.Model - EntityType EntityType - EntityId int - UsersId int - DefaultAddInIncidents bool - teamRole string - Version int -} - -func (TeamsSeverityUsersMapping) TableName() string { - return "teams_severity_user_mapping" -} diff --git a/entity/users.go b/entity/users.go deleted file mode 100644 index b56a2fb..0000000 --- a/entity/users.go +++ /dev/null @@ -1,11 +0,0 @@ -package entity - -type UsersEntity struct { - Name string - SlackUserId string - Active bool -} - -func (UsersEntity) TableName() string { - return "users" -} diff --git a/go.mod b/go.mod index 337d3ed..aaf5384 100644 --- a/go.mod +++ b/go.mod @@ -1,60 +1,38 @@ module houston -go 1.20 +go 1.19 require ( - github.com/Shopify/sarama v1.38.1 github.com/gin-contrib/zap v0.1.0 github.com/gin-gonic/gin v1.9.0 - github.com/google/uuid v1.3.0 github.com/joho/godotenv v1.5.1 + github.com/lib/pq v1.10.7 github.com/slack-go/slack v0.12.1 github.com/spf13/cobra v1.6.1 github.com/spf13/viper v1.15.0 go.uber.org/zap v1.24.0 - google.golang.org/api v0.107.0 gorm.io/driver/postgres v1.4.8 gorm.io/gorm v1.24.5 ) require ( - cloud.google.com/go/compute v1.14.0 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect github.com/bytedance/sonic v1.8.2 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/eapache/go-resiliency v1.3.0 // indirect - github.com/eapache/go-xerial-snappy v0.0.0-20230111030713-bf00bc1b83b6 // indirect - github.com/eapache/queue v1.1.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.11.2 // indirect github.com/goccy/go-json v0.10.0 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/golang/snappy v0.0.4 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.1 // indirect - github.com/googleapis/gax-go/v2 v2.7.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgx/v5 v5.3.0 // indirect - github.com/jcmturner/aescts/v2 v2.0.0 // indirect - github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect - github.com/jcmturner/gofork v1.7.6 // indirect - github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect - github.com/jcmturner/rpc/v2 v2.0.3 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.16.0 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/magiconair/properties v1.8.7 // indirect @@ -63,8 +41,6 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect - github.com/pierrec/lz4/v4 v4.1.17 // indirect - github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/spf13/afero v1.9.4 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect @@ -72,7 +48,6 @@ require ( github.com/subosito/gotenv v1.4.2 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.10 // indirect - go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/otel v1.13.0 // indirect go.opentelemetry.io/otel/trace v1.13.0 // indirect go.uber.org/atomic v1.10.0 // indirect @@ -80,12 +55,8 @@ require ( golang.org/x/arch v0.2.0 // indirect golang.org/x/crypto v0.6.0 // indirect golang.org/x/net v0.7.0 // indirect - golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect golang.org/x/sys v0.5.0 // indirect golang.org/x/text v0.7.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef // indirect - google.golang.org/grpc v1.52.0 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum deleted file mode 100644 index 6ed13a5..0000000 --- a/go.sum +++ /dev/null @@ -1,709 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go v0.105.0 h1:DNtEKRBAAzeS4KyIory52wWHuClNaXJ5x1F7xa4q+5Y= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v1.14.0 h1:hfm2+FfxVmnRlh6LpB7cg1ZNU+5edAHmW679JePztk0= -cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Shopify/sarama v1.38.1 h1:lqqPUPQZ7zPqYlWpTh+LQ9bhYNu2xJL6k1SJN4WVe2A= -github.com/Shopify/sarama v1.38.1/go.mod h1:iwv9a67Ha8VNa+TifujYoWGxWnu2kNVAQdSdZ4X2o5g= -github.com/Shopify/toxiproxy/v2 v2.5.0 h1:i4LPT+qrSlKNtQf5QliVjdP08GyAH8+BUIc9gT0eahc= -github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.8.2 h1:Eq1oE3xWIBE3tj2ZtJFK1rDAx7+uA4bRytozVhXMHKY= -github.com/bytedance/sonic v1.8.2/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/eapache/go-resiliency v1.3.0 h1:RRL0nge+cWGlxXbUzJ7yMcq6w2XBEr19dCN6HECGaT0= -github.com/eapache/go-resiliency v1.3.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho= -github.com/eapache/go-xerial-snappy v0.0.0-20230111030713-bf00bc1b83b6 h1:8yY/I9ndfrgrXUbOGObLHKBR4Fl3nZXwM2c7OYTT8hM= -github.com/eapache/go-xerial-snappy v0.0.0-20230111030713-bf00bc1b83b6/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0= -github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-contrib/zap v0.1.0 h1:RMSFFJo34XZogV62OgOzvrlaMNmXrNxmJ3bFmMwl6Cc= -github.com/gin-contrib/zap v0.1.0/go.mod h1:hvnZaPs478H1PGvRP8w89ZZbyJUiyip4ddiI/53WG3o= -github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= -github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8= -github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= -github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= -github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= -github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= -github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= -github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.2.1 h1:RY7tHKZcRlk788d5WSo/e83gOyyy742E8GSs771ySpg= -github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ= -github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= -github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= -github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.3.0 h1:/NQi8KHMpKWHInxXesC8yD4DhkXPrVhmnwYkjp9AmBA= -github.com/jackc/pgx/v5 v5.3.0/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= -github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= -github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= -github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= -github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= -github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= -github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= -github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= -github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= -github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= -github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= -github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= -github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= -github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= -github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= -github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= -github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= -github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= -github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= -github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= -github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= -github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc= -github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= -github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/slack-go/slack v0.12.1 h1:X97b9g2hnITDtNsNe5GkGx6O2/Sz/uC20ejRZN6QxOw= -github.com/slack-go/slack v0.12.1/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= -github.com/spf13/afero v1.9.4 h1:Sd43wM1IWz/s1aVXdOBkjJvuP8UdyqioeE4AmM0QsBs= -github.com/spf13/afero v1.9.4/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= -github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= -github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= -github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= -github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= -github.com/ugorji/go/codec v1.2.10 h1:eimT6Lsr+2lzmSZxPhLFoOWFmQqwk0fllJJ5hEbTXtQ= -github.com/ugorji/go/codec v1.2.10/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/otel v1.10.0/go.mod h1:NbvWjCthWHKBEUMpf0/v8ZRZlni86PpGFEMA9pnQSnQ= -go.opentelemetry.io/otel v1.13.0 h1:1ZAKnNQKwBBxFtww/GwxNUyTf0AxkZzrukO8MeXqe4Y= -go.opentelemetry.io/otel v1.13.0/go.mod h1:FH3RtdZCzRkJYFTCsAKDy9l/XYjMdNv6QrkFFB8DvVg= -go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/AzrK+kxfGqySM= -go.opentelemetry.io/otel/trace v1.13.0 h1:CBgRZ6ntv+Amuj1jDsMhZtlAPT6gbyIRdaIzFhfBSdY= -go.opentelemetry.io/otel/trace v1.13.0/go.mod h1:muCvmmO9KKpvuXSf3KKAXXB2ygNYHQ+ZfI5X08d3tds= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= -go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= -go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= -go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.2.0 h1:W1sUEHXiJTfjaFJ5SLo0N6lZn+0eO5gWD1MFeTGqQEY= -golang.org/x/arch v0.2.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk= -golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.107.0 h1:I2SlFjD8ZWabaIFOfeEDg3pf0BHJDh6iYQ1ic3Yu/UU= -google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef h1:uQ2vjV/sHTsWSqdKeLqmwitzgvjMl7o4IdtHwUDXSJY= -google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.52.0 h1:kd48UiU7EHsV4rnLyOJRuP/Il/UHE7gdDAQ+SZI7nZk= -google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/postgres v1.4.8 h1:NDWizaclb7Q2aupT0jkwK8jx1HVCNzt+PQ8v/VnxviA= -gorm.io/driver/postgres v1.4.8/go.mod h1:O9MruWGNLUBUWVYfWuBClpf3HeGjOoybY0SNmCs3wsw= -gorm.io/gorm v1.24.2/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= -gorm.io/gorm v1.24.5 h1:g6OPREKqqlWq4kh/3MCQbZKImeB9e6Xgc4zD+JgNZGE= -gorm.io/gorm v1.24.5/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/golang-1.png b/golang-1.png deleted file mode 100644 index 6ea687b..0000000 Binary files a/golang-1.png and /dev/null differ diff --git a/internal/cron/cron.go b/internal/cron/cron.go index bd44fe1..9751265 100644 --- a/internal/cron/cron.go +++ b/internal/cron/cron.go @@ -1,87 +1,88 @@ package cron -import ( - "fmt" - "houston/pkg/postgres/query" - "houston/pkg/slack/houston" - "time" - - "github.com/robfig/cron" - "github.com/slack-go/slack" - "go.uber.org/zap" - "gorm.io/gorm" -) - -func RunJob(slackClient *slack.Client, db *gorm.DB, logger *zap.Logger) { - c := cron.New() - //RUN EVERY HOUR - c.AddFunc("0 0 * * * *", func() { - fmt.Println("Running job at", time.Now().Format(time.RFC3339)) - - severityUserMap := make(map[int][]string) - severityData, err := query.FindSeverity(db, logger) - for _, o := range severityData { - userIdList, err := query.FindDefaultUserIdToBeAddedBySeverity(db, int(o.ID)) - if err != nil { - logger.Error("FindDefaultUserIdToBeAddedBySeverity error in cron job") - return - } - severityUserMap[int(o.ID)] = userIdList - } - - //FETCH INCIDENTS WHICH ARE NOT RESOLVED AND CURRENT TIMESTAMP>=SEVERITY TAT FIELD - incidents, err := query.FindIncidentsBreachingSevTat(db) - if err != nil { - logger.Error("FindIncidentsBreachingSevTat error", - zap.Error(err)) - return - } - for i := 0; i < len(incidents); i++ { - var currentSeverityId int = incidents[i].SeverityId - var severityString string - //CHECK IF SEVERITY IS ALREADY 0 OR NOT. SEV-0 is saved as id = 1 in db - if currentSeverityId-1 > 0 { - incidents[i].SeverityId = currentSeverityId - 1 - severityEntity, err := query.FindSeverityById(db, incidents[i].SeverityId) - if err != nil { - logger.Error("failed to fetch FindSeverityByIdin cron job", - zap.Int("severityId", incidents[i].SeverityId), zap.Error(err)) - } - incidents[i].SeverityTat = time.Now().AddDate(0, 0, severityEntity.Sla) - severityString = fmt.Sprintln(severityEntity.Name + " (" + severityEntity.Description + ")") - } - - incidents[i].UpdatedAt = time.Now() - err = query.UpdateIncident(db, &incidents[i]) - if err != nil { - logger.Error("failed to update incident in cron job", - zap.String("channel", incidents[i].SlackChannel), zap.Error(err)) - } - - //DEFAULT USER ADDITION - for _, o := range severityUserMap[incidents[i].SeverityId] { - //throws error if the customer is already present in channel - _, err := slackClient.InviteUsersToConversation(incidents[i].SlackChannel, o) - if err != nil { - logger.Error("Slack Client InviteUsersToConversation error in cron job") - return - } - } - - //UPDATING MESSAGE - houston.UpdateMessage(db, &incidents[i], nil, slackClient) - - //SENDING MESSAGE IN THE CHANNEL - if currentSeverityId > 1 { - msgOption := slack.MsgOptionText(fmt.Sprintf("Issue has been escalated to "+severityString+" as there was TAT breach"), true) - _, _, errMessage := slackClient.PostMessage(incidents[i].SlackChannel, msgOption) - if errMessage != nil { - logger.Error("PostMessage failed for cronJob ", zap.Error(errMessage), zap.Int("incidentId", int(incidents[i].ID))) - return - } - } - } - - }) - c.Start() -} +// +//import ( +// "fmt" +// "houston/internal/processor" +// "houston/pkg/postgres/query" +// "time" +// +// "github.com/robfig/cron" +// "github.com/slack-go/slack" +// "go.uber.org/zap" +// "gorm.io/gorm" +//) +// +//func RunJob(slackClient *slack.Client, db *gorm.DB, logger *zap.Logger) { +// c := cron.New() +// //RUN EVERY HOUR +// c.AddFunc("0 0 * * * *", func() { +// fmt.Println("Running job at", time.Now().Format(time.RFC3339)) +// +// severityUserMap := make(map[int][]string) +// severityData, err := query.FindSeverity(db, logger) +// for _, o := range severityData { +// userIdList, err := query.FindDefaultUserIdToBeAddedBySeverity(db, int(o.ID)) +// if err != nil { +// logger.Error("FindDefaultUserIdToBeAddedBySeverity error in cron job") +// return +// } +// severityUserMap[int(o.ID)] = userIdList +// } +// +// //FETCH INCIDENTS WHICH ARE NOT RESOLVED AND CURRENT TIMESTAMP>=SEVERITY TAT FIELD +// incidents, err := query.FindIncidentsBreachingSevTat(db) +// if err != nil { +// logger.Error("FindIncidentsBreachingSevTat error", +// zap.Error(err)) +// return +// } +// for i := 0; i < len(incidents); i++ { +// var currentSeverityId int = incidents[i].SeverityId +// var severityString string +// //CHECK IF SEVERITY IS ALREADY 0 OR NOT. SEV-0 is saved as id = 1 in db +// if currentSeverityId-1 > 0 { +// incidents[i].SeverityId = currentSeverityId - 1 +// severityEntity, err := query.FindSeverityById(db, incidents[i].SeverityId) +// if err != nil { +// logger.Error("failed to fetch FindSeverityByIdin cron job", +// zap.Int("severityId", incidents[i].SeverityId), zap.Error(err)) +// } +// incidents[i].SeverityTat = time.Now().AddDate(0, 0, severityEntity.Sla) +// severityString = fmt.Sprintln(severityEntity.Name + " (" + severityEntity.Description + ")") +// } +// +// incidents[i].UpdatedAt = time.Now() +// err = query.UpdateIncident(db, &incidents[i]) +// if err != nil { +// logger.Error("failed to update incident in cron job", +// zap.String("channel", incidents[i].SlackChannel), zap.Error(err)) +// } +// +// //DEFAULT USER ADDITION +// for _, o := range severityUserMap[incidents[i].SeverityId] { +// //throws error if the customer is already present in channel +// _, err := slackClient.InviteUsersToConversation(incidents[i].SlackChannel, o) +// if err != nil { +// logger.Error("Slack Client InviteUsersToConversation error in cron job") +// return +// } +// } +// +// //UPDATING MESSAGE +// processor.UpdateMessage(db, &incidents[i], nil, slackClient) +// +// //SENDING MESSAGE IN THE CHANNEL +// if currentSeverityId > 1 { +// msgOption := slack.MsgOptionText(fmt.Sprintf("Issue has been escalated to "+severityString+" as there was TAT breach"), true) +// _, _, errMessage := slackClient.PostMessage(incidents[i].SlackChannel, msgOption) +// if errMessage != nil { +// logger.Error("PostMessage failed for cronJob ", zap.Error(errMessage), zap.Int("incidentId", int(incidents[i].ID))) +// return +// } +// } +// } +// +// }) +// c.Start() +//} diff --git a/pkg/slack/houston/command/incident_assign.go b/internal/processor/action/incident_assign_action.go similarity index 52% rename from pkg/slack/houston/command/incident_assign.go rename to internal/processor/action/incident_assign_action.go index 659c657..8d8b071 100644 --- a/pkg/slack/houston/command/incident_assign.go +++ b/internal/processor/action/incident_assign_action.go @@ -1,38 +1,35 @@ -package command +package action import ( "encoding/json" "fmt" - "houston/model/request" - "houston/pkg/postgres/query" - houston "houston/pkg/slack/houston/design" - "github.com/slack-go/slack" "github.com/slack-go/slack/socketmode" "go.uber.org/zap" - "gorm.io/gorm" + "houston/internal/processor/action/view" + "houston/pkg/postgres/service/incident" ) -type incidentAssignProcessor struct { - client *socketmode.Client - db *gorm.DB - logger *zap.Logger +type AssignIncidentAction struct { + client *socketmode.Client + logger *zap.Logger + incidentService *incident.Service } -func NewIncidentAssignProcessor(client *socketmode.Client, db *gorm.DB, logger *zap.Logger) *incidentAssignProcessor { - return &incidentAssignProcessor{ - client: client, - db: db, - logger: logger, +func NewAssignIncidentAction(client *socketmode.Client, logger *zap.Logger, incidentService *incident.Service) *AssignIncidentAction { + return &AssignIncidentAction{ + client: client, + logger: logger, + incidentService: incidentService, } } -func (iap *incidentAssignProcessor) IncidentAssignProcess(callback slack.InteractionCallback, request *socketmode.Request) { +func (iap *AssignIncidentAction) IncidentAssignProcess(callback slack.InteractionCallback, request *socketmode.Request) { - modalRequest := houston.GenerateModalForIncidentAssign(callback.Channel) + modalRequest := view.GenerateModalForIncidentAssign(callback.Channel) _, err := iap.client.OpenView(callback.TriggerID, modalRequest) if err != nil { - iap.logger.Error("houston slack openview command failed.", + iap.logger.Error("houston slackbot open view command failed.", zap.String("trigger_id", callback.TriggerID), zap.String("channel_id", callback.Channel.ID), zap.Error(err)) return } @@ -41,27 +38,30 @@ func (iap *incidentAssignProcessor) IncidentAssignProcess(callback slack.Interac } -func (iap *incidentAssignProcessor) IncidentAssignModalCommandProcessing(callback slack.InteractionCallback, request *socketmode.Request) { - incidentEntity, err := query.FindIncidentByChannelId(iap.db, callback.View.PrivateMetadata) +func (iap *AssignIncidentAction) IncidentAssignModalCommandProcessing(callback slack.InteractionCallback, request *socketmode.Request) { + incidentEntity, err := iap.incidentService.FindIncidentByChannelId(callback.View.PrivateMetadata) if err != nil { iap.logger.Error("FindIncidentByChannelId error", zap.String("incident_slack_channel_id", callback.View.PrivateMetadata), zap.String("user_id", callback.User.ID), zap.Error(err)) + return } else if incidentEntity == nil { iap.logger.Error("IncidentEntity not found ", zap.String("incident_slack_channel_id", callback.View.PrivateMetadata), zap.String("channel", callback.Channel.Name), zap.String("user_id", callback.User.ID), zap.Error(err)) return } - assignIncidentRoleRequest := buildAssignIncidentRoleRequest(callback.View.State.Values) - assignIncidentRoleRequest.CreatedById = callback.User.ID - assignIncidentRoleRequest.IncidentId = int(incidentEntity.ID) + + assignIncidentRoleRequest := buildAssignIncidentRoleRequest(callback, incidentEntity) + iap.logger.Info("request", zap.Any("request", assignIncidentRoleRequest)) - err = query.UpsertIncidentRole(iap.db, assignIncidentRoleRequest) + + err = iap.incidentService.UpsertIncidentRole(assignIncidentRoleRequest) if err != nil { iap.logger.Error("UpsertIncidentRole failed", zap.Error(err)) return } + msgOption := slack.MsgOptionText(fmt.Sprintf("<@%s> is assigned to %s by <@%s>", assignIncidentRoleRequest.UserId, assignIncidentRoleRequest.Role, assignIncidentRoleRequest.CreatedById), false) _, _, errMessage := iap.client.PostMessage(callback.View.PrivateMetadata, msgOption) if errMessage != nil { @@ -74,21 +74,24 @@ func (iap *incidentAssignProcessor) IncidentAssignModalCommandProcessing(callbac } -func buildAssignIncidentRoleRequest(blockActions map[string]map[string]slack.BlockAction) *request.AddIncidentRoleRequest { - var addIncidentRoleRequest request.AddIncidentRoleRequest +func buildAssignIncidentRoleRequest(callback slack.InteractionCallback, incidentEntity *incident.IncidentEntity) *incident.AddIncidentRoleRequest { + blockActions := callback.View.State.Values + var addIncidentRoleRequest incident.AddIncidentRoleRequest var requestMap = make(map[string]string, 0) for _, actions := range blockActions { - for actionID, action := range actions { - if action.Type == "users_select" { - requestMap[actionID] = action.SelectedUser + for actionID, a := range actions { + if a.Type == "users_select" { + requestMap[actionID] = a.SelectedUser } - if action.Type == "static_select" { - requestMap[actionID] = action.SelectedOption.Value + if a.Type == "static_select" { + requestMap[actionID] = a.SelectedOption.Value } } } desRequestMap, _ := json.Marshal(requestMap) json.Unmarshal(desRequestMap, &addIncidentRoleRequest) + addIncidentRoleRequest.CreatedById = callback.User.ID + addIncidentRoleRequest.IncidentId = int(incidentEntity.ID) return &addIncidentRoleRequest } diff --git a/internal/processor/action/incident_channel_message_update_action.go b/internal/processor/action/incident_channel_message_update_action.go new file mode 100644 index 0000000..9f0a43c --- /dev/null +++ b/internal/processor/action/incident_channel_message_update_action.go @@ -0,0 +1,82 @@ +package action + +import ( + "errors" + "fmt" + "github.com/slack-go/slack" + "github.com/slack-go/slack/socketmode" + "go.uber.org/zap" + "houston/common/util" + "houston/internal/processor/action/view" + "houston/pkg/postgres/service/incident" + "houston/pkg/postgres/service/severity" + "houston/pkg/postgres/service/team" +) + +type IncidentChannelMessageUpdateAction struct { + socketModeClient *socketmode.Client + logger *zap.Logger + incidentService *incident.Service + teamService *team.Service + severityService *severity.Service +} + +func NewIncidentChannelMessageUpdateAction(socketModeClient *socketmode.Client, logger *zap.Logger, + incidentService *incident.Service, teamService *team.Service, severityService *severity.Service) *IncidentChannelMessageUpdateAction { + return &IncidentChannelMessageUpdateAction{ + socketModeClient: socketModeClient, + logger: logger, + incidentService: incidentService, + teamService: teamService, + severityService: severityService, + } +} + +func (icm *IncidentChannelMessageUpdateAction) ProcessAction(channelId string) { + incidentEntity, teamEntity, severityEntity, incidentChannels, incidentStatusEntity, err := icm.getEntities(channelId) + if err != nil { + return + } + + blocks := view.IncidentSummarySection(incidentEntity, teamEntity, severityEntity, incidentStatusEntity) + color := util.GetColorBySeverity(severityEntity.ID) + att := slack.Attachment{Blocks: blocks, Color: color} + for _, message := range *incidentChannels { + _, _, _, err := icm.socketModeClient.UpdateMessage(message.SlackChannel, message.MessageTimeStamp, slack.MsgOptionAttachments(att)) + if err != nil { + icm.logger.Error(fmt.Sprintf("exception occurred while updating the message to all the incident "+ + "channels for incidentId: %v", incidentEntity.ID), zap.Error(err)) + return + } + } +} + +func (icm *IncidentChannelMessageUpdateAction) getEntities(channelId string) (*incident.IncidentEntity, *team.TeamEntity, + *severity.SeverityEntity, *[]incident.IncidentChannelEntity, *incident.IncidentStatusEntity, error) { + incidentEntity, err := icm.incidentService.FindIncidentByChannelId(channelId) + if err != nil || incidentEntity == nil { + return nil, nil, nil, nil, nil, errors.New("exception occurred while getting incident") + } + + incidentChannels, err := icm.incidentService.GetIncidentChannels(incidentEntity.ID) + if err != nil || incidentChannels == nil { + return nil, nil, nil, nil, nil, errors.New("exception occurred while getting incident channels") + } + + teamEntity, err := icm.teamService.FindTeamById(incidentEntity.TeamId) + if err != nil || teamEntity == nil { + return nil, nil, nil, nil, nil, errors.New("exception occurred while getting incident team") + } + + severityEntity, err := icm.severityService.FindSeverityById(incidentEntity.SeverityId) + if err != nil || severityEntity == nil { + return nil, nil, nil, nil, nil, errors.New("exception occurred while getting incident severity") + } + + incidentStatusEntity, err := icm.incidentService.FindIncidentStatusById(incidentEntity.Status) + if err != nil || incidentStatusEntity == nil { + return nil, nil, nil, nil, nil, errors.New("exception occurred while getting incident status") + } + + return incidentEntity, teamEntity, severityEntity, incidentChannels, incidentStatusEntity, nil +} diff --git a/pkg/slack/houston/command/incident_resolve.go b/internal/processor/action/incident_resolve_action.go similarity index 51% rename from pkg/slack/houston/command/incident_resolve.go rename to internal/processor/action/incident_resolve_action.go index 6e566c0..cb483b0 100644 --- a/pkg/slack/houston/command/incident_resolve.go +++ b/internal/processor/action/incident_resolve_action.go @@ -1,57 +1,58 @@ -package command +package action import ( "fmt" - "houston/entity" - "houston/pkg/postgres/query" + "houston/pkg/postgres/service/incident" "time" "github.com/slack-go/slack" "github.com/slack-go/slack/socketmode" "go.uber.org/zap" - "gorm.io/gorm" ) -type incidentResolveProcessor struct { - client *socketmode.Client - db *gorm.DB - logger *zap.Logger +type ResolveIncidentAction struct { + client *socketmode.Client + logger *zap.Logger + incidentService *incident.Service } -func NewIncidentResolveProcessor(client *socketmode.Client, db *gorm.DB, logger *zap.Logger) *incidentResolveProcessor { - return &incidentResolveProcessor{ - client: client, - db: db, - logger: logger, +func NewIncidentResolveProcessor(client *socketmode.Client, logger *zap.Logger, incidentService *incident.Service) *ResolveIncidentAction { + return &ResolveIncidentAction{ + client: client, + logger: logger, + incidentService: incidentService, } } -func (irp *incidentResolveProcessor) IncidentResolveProcess(callback slack.InteractionCallback, request *socketmode.Request) { +func (irp *ResolveIncidentAction) IncidentResolveProcess(callback slack.InteractionCallback, request *socketmode.Request) { channelId := callback.Channel.ID - incidentEntity, err := query.FindIncidentByChannelId(irp.db, channelId) + incidentEntity, err := irp.incidentService.FindIncidentByChannelId(channelId) if err != nil { irp.logger.Error("incident not found", zap.String("channel", channelId), zap.String("user_id", callback.User.ID), zap.Error(err)) } - incidentEntity.Status = entity.Resolved - now := time.Now() - incidentEntity.CustomerImpactEndTime = &now + incidentStatusEntity, _ := irp.incidentService.FindIncidentStatusByName(incident.Resolved) - err = query.UpdateIncident(irp.db, incidentEntity) + now := time.Now() + incidentEntity.Status = incidentStatusEntity.ID + incidentEntity.EndTime = &now + + err = irp.incidentService.UpdateIncident(incidentEntity) if err != nil { irp.logger.Error("failed to update incident to resolve state", zap.String("channel", channelId), zap.String("user_id", callback.User.ID), zap.Error(err)) + return } irp.logger.Info("successfully resolved the incident", zap.String("channel", channelId), zap.String("user_id", callback.User.ID)) - msgOption := slack.MsgOptionText(fmt.Sprintf("<@%s> > set status to %s", callback.User.ID, incidentEntity.Status), false) - + msgOption := slack.MsgOptionText(fmt.Sprintf("<@%s> > set status to %s", callback.User.ID, + incident.Resolved), false) _, _, errMessage := irp.client.PostMessage(callback.Channel.ID, msgOption) if errMessage != nil { irp.logger.Error("post response failed for ResolveIncident", zap.Error(errMessage)) diff --git a/internal/processor/action/incident_show_tags_action.go b/internal/processor/action/incident_show_tags_action.go new file mode 100644 index 0000000..a49f1d5 --- /dev/null +++ b/internal/processor/action/incident_show_tags_action.go @@ -0,0 +1,87 @@ +package action + +import ( + "fmt" + "houston/pkg/postgres/service/incident" + "houston/pkg/postgres/service/tag" + + "github.com/slack-go/slack" + "github.com/slack-go/slack/socketmode" + "go.uber.org/zap" +) + +type IncidentShowTagsAction struct { + client *socketmode.Client + logger *zap.Logger + incidentService *incident.Service + tagService *tag.Service +} + +func NewIncidentShowTagsProcessor(client *socketmode.Client, logger *zap.Logger, incidentService *incident.Service, tagService *tag.Service) *IncidentShowTagsAction { + return &IncidentShowTagsAction{ + client: client, + logger: logger, + incidentService: incidentService, + tagService: tagService, + } +} + +func (isp *IncidentShowTagsAction) IncidentShowTagsRequestProcess(callback slack.InteractionCallback, request *socketmode.Request) { + incidentEntity, err := isp.incidentService.FindIncidentByChannelId(callback.Channel.ID) + if err != nil || incidentEntity == nil { + isp.logger.Error(fmt.Sprintf("failure while getting incident for channel: %v", callback.Channel.ID)) + return + } + + incidentTagsEntities, err := isp.incidentService.GetIncidentTagsByIncidentId(incidentEntity.ID) + if err != nil || incidentTagsEntities == nil { + isp.logger.Error(fmt.Sprintf("failure while getting incident tags for incident id: %v", incidentEntity.ID)) + return + } + + var msgStrings []string + + for _, incidentTagEntity := range *incidentTagsEntities { + tagEntity, err := isp.tagService.FindById(incidentTagEntity.TagId) + if err != nil || tagEntity == nil { + isp.logger.Error(fmt.Sprintf("failure while getting tags for incident id: %v", incidentEntity.ID)) + return + } + + if incidentTagEntity.FreeTextValue != nil && *incidentTagEntity.FreeTextValue != "" { + msgStrings = append(msgStrings, fmt.Sprintf("\n\n %v : \n %v", tagEntity.Label, *incidentTagEntity.FreeTextValue)) + } else if incidentTagEntity.TagValueIds != nil { + tagValues, err := isp.tagService.FindTagValuesByIds(incidentTagEntity.TagValueIds) + if err != nil { + isp.logger.Error(fmt.Sprintf("failure while getting tag values for incident id: %v", incidentEntity.ID)) + return + } else if tagValues == nil { + continue + } + var msg string + + for _, tv := range *tagValues { + if msg == "" { + msg = tv.Value + } else { + msg = msg + " " + tv.Value + } + } + msgStrings = append(msgStrings, fmt.Sprintf("\n\n %v : \n %v", tagEntity.Label, msg)) + } + } + + var finalMsg string + for _, msg := range msgStrings { + finalMsg = finalMsg + msg + } + + msgOption := slack.MsgOptionText(fmt.Sprintf(finalMsg), true) + _, errMessage := isp.client.PostEphemeral(callback.Channel.ID, callback.User.ID, msgOption) + if errMessage != nil { + isp.logger.Error("post ephemeral message response failed for IncidentShowTagsRequestProcess", zap.Error(errMessage)) + return + } + var payload interface{} + isp.client.Ack(*request, payload) +} diff --git a/pkg/slack/houston/command/incident_update_description.go b/internal/processor/action/incident_update_description_action.go similarity index 63% rename from pkg/slack/houston/command/incident_update_description.go rename to internal/processor/action/incident_update_description_action.go index d02878c..f99d51f 100644 --- a/pkg/slack/houston/command/incident_update_description.go +++ b/internal/processor/action/incident_update_description_action.go @@ -1,32 +1,30 @@ -package command +package action import ( "fmt" - "houston/pkg/postgres/query" - houston "houston/pkg/slack/houston/design" - "github.com/slack-go/slack" "github.com/slack-go/slack/socketmode" "go.uber.org/zap" - "gorm.io/gorm" + "houston/internal/processor/action/view" + "houston/pkg/postgres/service/incident" ) -type incidentUpdateDescriptionProcessor struct { - client *socketmode.Client - db *gorm.DB - logger *zap.Logger +type IncidentUpdateDescriptionAction struct { + client *socketmode.Client + logger *zap.Logger + incidentService *incident.Service } -func NewIncidentUpdateDescriptionProcessor(client *socketmode.Client, db *gorm.DB, logger *zap.Logger) *incidentUpdateDescriptionProcessor { - return &incidentUpdateDescriptionProcessor{ - client: client, - db: db, - logger: logger, +func NewIncidentUpdateDescriptionAction(client *socketmode.Client, logger *zap.Logger, incidentService *incident.Service) *IncidentUpdateDescriptionAction { + return &IncidentUpdateDescriptionAction{ + client: client, + logger: logger, + incidentService: incidentService, } } -func (idp *incidentUpdateDescriptionProcessor) IncidentUpdateDescriptionRequestProcess(callback slack.InteractionCallback, request *socketmode.Request) { - result, err := query.FindIncidentByChannelId(idp.db, callback.Channel.ID) +func (idp *IncidentUpdateDescriptionAction) IncidentUpdateDescriptionRequestProcess(callback slack.InteractionCallback, request *socketmode.Request) { + result, err := idp.incidentService.FindIncidentByChannelId(callback.Channel.ID) if err != nil { idp.logger.Error("FindIncidentByChannelId error ", zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name), @@ -38,11 +36,11 @@ func (idp *incidentUpdateDescriptionProcessor) IncidentUpdateDescriptionRequestP zap.String("user_id", callback.User.ID), zap.Error(err)) return } - modalRequest := houston.BuildIncidentUpdateDescriptionModal(idp.db, callback.Channel, result.Description) + modalRequest := view.BuildIncidentUpdateDescriptionModal(callback.Channel, result.Description) _, err = idp.client.OpenView(callback.TriggerID, modalRequest) if err != nil { - idp.logger.Error("houston slack openview command for IncidentUpdateDescriptionRequestProcess failed.", + idp.logger.Error("houston slackbot openview command for IncidentUpdateDescriptionRequestProcess failed.", zap.String("trigger_id", callback.TriggerID), zap.String("channel_id", callback.Channel.ID), zap.Error(err)) return } @@ -50,8 +48,8 @@ func (idp *incidentUpdateDescriptionProcessor) IncidentUpdateDescriptionRequestP idp.client.Ack(*request, payload) } -func (itp *incidentUpdateDescriptionProcessor) IncidentUpdateDescription(callback slack.InteractionCallback, request *socketmode.Request, channel slack.Channel, user slack.User) { - incidentEntity, err := query.FindIncidentByChannelId(itp.db, callback.View.PrivateMetadata) +func (itp *IncidentUpdateDescriptionAction) IncidentUpdateDescription(callback slack.InteractionCallback, request *socketmode.Request, channel slack.Channel, user slack.User) { + incidentEntity, err := itp.incidentService.FindIncidentByChannelId(callback.View.PrivateMetadata) if err != nil { itp.logger.Error("FindIncidentByChannelId error", zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name), @@ -68,7 +66,7 @@ func (itp *incidentUpdateDescriptionProcessor) IncidentUpdateDescription(callbac incidentEntity.Description = incidentDescription incidentEntity.UpdatedBy = user.ID - err = query.UpdateIncident(itp.db, incidentEntity) + err = itp.incidentService.UpdateIncident(incidentEntity) if err != nil { itp.logger.Error("IncidentUpdateDescription error", zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name), @@ -89,9 +87,9 @@ func (itp *incidentUpdateDescriptionProcessor) IncidentUpdateDescription(callbac func buildUpdateIncidentDescriptionRequest(blockActions map[string]map[string]slack.BlockAction) string { var requestMap = make(map[string]string, 0) for _, actions := range blockActions { - for actionID, action := range actions { - if action.Type == "plain_text_input" { - requestMap[actionID] = action.Value + for actionID, a := range actions { + if a.Type == "plain_text_input" { + requestMap[actionID] = a.Value } } } diff --git a/pkg/slack/houston/command/incident_update_severity.go b/internal/processor/action/incident_update_severity_action.go similarity index 55% rename from pkg/slack/houston/command/incident_update_severity.go rename to internal/processor/action/incident_update_severity_action.go index 1e6262d..8af3b4d 100644 --- a/pkg/slack/houston/command/incident_update_severity.go +++ b/internal/processor/action/incident_update_severity_action.go @@ -1,46 +1,50 @@ -package command +package action import ( "fmt" - "houston/pkg/postgres/query" - "houston/pkg/slack/common" - houston "houston/pkg/slack/houston/design" + "houston/internal/processor/action/view" + "houston/pkg/postgres/service/incident" + "houston/pkg/postgres/service/severity" + "houston/pkg/slackbot" "strconv" "time" "github.com/slack-go/slack" "github.com/slack-go/slack/socketmode" "go.uber.org/zap" - "gorm.io/gorm" ) -type incidentUpdateSevertityProcessor struct { - client *socketmode.Client - db *gorm.DB - logger *zap.Logger +type IncidentUpdateSevertityAction struct { + client *socketmode.Client + logger *zap.Logger + severityService *severity.Service + incidentService *incident.Service + slackbotClient *slackbot.Client } -func NewIncidentUpdateSeverityProcessor(client *socketmode.Client, db *gorm.DB, logger *zap.Logger) *incidentUpdateSevertityProcessor { - return &incidentUpdateSevertityProcessor{ - client: client, - db: db, - logger: logger, +func NewIncidentUpdateSeverityAction(client *socketmode.Client, logger *zap.Logger, incidentService *incident.Service, severityService *severity.Service, slackbotClient *slackbot.Client) *IncidentUpdateSevertityAction { + return &IncidentUpdateSevertityAction{ + client: client, + logger: logger, + severityService: severityService, + incidentService: incidentService, + slackbotClient: slackbotClient, } } -func (isp *incidentUpdateSevertityProcessor) IncidentUpdateSeverityRequestProcess(callback slack.InteractionCallback, request *socketmode.Request) { - incidentSeverity, err := query.FindIncidentSeverityEntity(isp.db, isp.logger) - if err != nil { +func (isp *IncidentUpdateSevertityAction) IncidentUpdateSeverityRequestProcess(callback slack.InteractionCallback, request *socketmode.Request) { + incidentSeverity, err := isp.severityService.GetAllActiveSeverity() + if err != nil || incidentSeverity == nil { isp.logger.Error("FindSeverityEntity error", zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name), zap.String("user_id", callback.User.ID), zap.Error(err)) return } - modalRequest := houston.BuildIncidentUpdateSeverityModal(callback.Channel, incidentSeverity) + modalRequest := view.BuildIncidentUpdateSeverityModal(callback.Channel, *incidentSeverity) _, err = isp.client.OpenView(callback.TriggerID, modalRequest) if err != nil { - isp.logger.Error("houston slack openview command failed.", + isp.logger.Error("houston slackbot openview command failed.", zap.String("trigger_id", callback.TriggerID), zap.String("channel_id", callback.Channel.ID), zap.Error(err)) return } @@ -49,12 +53,13 @@ func (isp *incidentUpdateSevertityProcessor) IncidentUpdateSeverityRequestProces } -func (isp *incidentUpdateSevertityProcessor) IncidentUpdateSeverity(callback slack.InteractionCallback, request *socketmode.Request, channel slack.Channel, user slack.User) { - incidentEntity, err := query.FindIncidentByChannelId(isp.db, callback.View.PrivateMetadata) +func (isp *IncidentUpdateSevertityAction) IncidentUpdateSeverity(callback slack.InteractionCallback, request *socketmode.Request, channel slack.Channel, user slack.User) { + incidentEntity, err := isp.incidentService.FindIncidentByChannelId(callback.View.PrivateMetadata) if err != nil { isp.logger.Error("FindIncidentByChannelId error", zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name), zap.String("user_id", user.ID), zap.Error(err)) + return } else if incidentEntity == nil { isp.logger.Error("IncidentEntity not found ", zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name), @@ -63,38 +68,33 @@ func (isp *incidentUpdateSevertityProcessor) IncidentUpdateSeverity(callback sla } incidentSeverityId := buildUpdateIncidentSeverityRequest(isp.logger, callback.View.State.Values) - result, err := query.FindIncidentSeverityEntityById(isp.db, isp.logger, incidentSeverityId) + incidentSeverityEntity, err := isp.severityService.FindIncidentSeverityEntityById(incidentSeverityId) if err != nil { isp.logger.Error("FindIncidentSeverityEntityById error", zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name), zap.String("user_id", user.ID), zap.Error(err)) return - } else if result == nil { + } else if incidentSeverityEntity == nil { isp.logger.Error("SeverityEntity not found", zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name), zap.String("user_id", user.ID), zap.Error(err)) return } - incidentEntity.SeverityId = int(result.ID) + incidentEntity.SeverityId = incidentSeverityEntity.ID incidentEntity.UpdatedBy = user.ID - incidentEntity.SeverityTat = time.Now().AddDate(0, 0, result.Sla) - err = query.UpdateIncident(isp.db, incidentEntity) + incidentEntity.SeverityTat = time.Now().AddDate(0, 0, incidentSeverityEntity.Sla) + err = isp.incidentService.UpdateIncident(incidentEntity) if err != nil { isp.logger.Error("UpdateIncident error", zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name), zap.String("user_id", user.ID), zap.Error(err)) } - userIdList, err := query.FindDefaultUserIdToBeAddedBySeverity(isp.db, int(result.ID)) - if err != nil { - isp.logger.Error("FindDefaultUserIdToBeAddedBySeverity error", - zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name), - zap.String("user_id", user.ID), zap.Error(err)) - return + + for _, o := range incidentSeverityEntity.SlackUserIds { + isp.slackbotClient.InviteUsersToConversation(callback.View.PrivateMetadata, o) } - for _, o := range userIdList { - common.InviteUsersToConversation(isp.client, isp.logger, callback.View.PrivateMetadata, o) - } - msgOption := slack.MsgOptionText(fmt.Sprintf("<@%s> > set severity to %s", user.ID, result.Name), false) + + msgOption := slack.MsgOptionText(fmt.Sprintf("<@%s> > set severity to %s", user.ID, incidentSeverityEntity.Name), false) _, _, errMessage := isp.client.PostMessage(callback.View.PrivateMetadata, msgOption) if errMessage != nil { isp.logger.Error("post response failed for IncidentUpdateSeverity", zap.Error(errMessage)) @@ -104,14 +104,13 @@ func (isp *incidentUpdateSevertityProcessor) IncidentUpdateSeverity(callback sla isp.client.Ack(*request, payload) } -//TODO - ADD USER ACCORDING TO SEVERITY - +// TODO - ADD USER ACCORDING TO SEVERITY func buildUpdateIncidentSeverityRequest(logger *zap.Logger, blockActions map[string]map[string]slack.BlockAction) int { var requestMap = make(map[string]string, 0) for _, actions := range blockActions { - for actionID, action := range actions { - if action.Type == "static_select" { - requestMap[actionID] = action.SelectedOption.Value + for actionID, a := range actions { + if a.Type == "static_select" { + requestMap[actionID] = a.SelectedOption.Value } } } diff --git a/pkg/slack/houston/command/incident_update_status.go b/internal/processor/action/incident_update_status_action.go similarity index 57% rename from pkg/slack/houston/command/incident_update_status.go rename to internal/processor/action/incident_update_status_action.go index 6c16dbc..e37fe91 100644 --- a/pkg/slack/houston/command/incident_update_status.go +++ b/internal/processor/action/incident_update_status_action.go @@ -1,39 +1,43 @@ -package command +package action import ( "fmt" - "houston/entity" - "houston/pkg/postgres/query" - houston "houston/pkg/slack/houston/design" + "houston/internal/processor/action/view" + "houston/pkg/postgres/service/incident" "strconv" "time" "github.com/slack-go/slack" "github.com/slack-go/slack/socketmode" "go.uber.org/zap" - "gorm.io/gorm" ) -type incidentUpdateStatusProcessor struct { - client *socketmode.Client - db *gorm.DB - logger *zap.Logger +type UpdateIncidentAction struct { + client *socketmode.Client + logger *zap.Logger + incidentService *incident.Service } -func NewIncidentUpdateStatusProcessor(client *socketmode.Client, db *gorm.DB, logger *zap.Logger) *incidentUpdateStatusProcessor { - return &incidentUpdateStatusProcessor{ - client: client, - db: db, - logger: logger, +func NewIncidentUpdateAction(client *socketmode.Client, logger *zap.Logger, incidentService *incident.Service) *UpdateIncidentAction { + return &UpdateIncidentAction{ + client: client, + logger: logger, + incidentService: incidentService, } } -func (isp *incidentUpdateStatusProcessor) IncidentUpdateStatusRequestProcess(callback slack.InteractionCallback, request *socketmode.Request) { - modalRequest := houston.BuildIncidentUpdateStatusModal(isp.db, callback.Channel) +func (isp *UpdateIncidentAction) IncidentUpdateStatusRequestProcess(callback slack.InteractionCallback, request *socketmode.Request) { + incidentStatuses, err := isp.incidentService.FetchAllIncidentStatuses() + if err != nil || incidentStatuses == nil { + isp.logger.Error("failed to get the all active incident statuses") + return + } - _, err := isp.client.OpenView(callback.TriggerID, modalRequest) + modalRequest := view.BuildIncidentUpdateStatusModal(*incidentStatuses, callback.Channel) + + _, err = isp.client.OpenView(callback.TriggerID, modalRequest) if err != nil { - isp.logger.Error("houston slack openview command failed.", + isp.logger.Error("houston slackbot openview command failed.", zap.String("trigger_id", callback.TriggerID), zap.String("channel_id", callback.Channel.ID), zap.Error(err)) return } @@ -41,8 +45,8 @@ func (isp *incidentUpdateStatusProcessor) IncidentUpdateStatusRequestProcess(cal isp.client.Ack(*request, payload) } -func (isp *incidentUpdateStatusProcessor) IncidentUpdateStatus(callback slack.InteractionCallback, request *socketmode.Request, channel slack.Channel, user slack.User) { - incidentEntity, err := query.FindIncidentByChannelId(isp.db, callback.View.PrivateMetadata) +func (isp *UpdateIncidentAction) IncidentUpdateStatus(callback slack.InteractionCallback, request *socketmode.Request, channel slack.Channel, user slack.User) { + incidentEntity, err := isp.incidentService.FindIncidentByChannelId(callback.View.PrivateMetadata) if err != nil { isp.logger.Error("FindIncidentBySlackChannelId error", zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name), @@ -57,37 +61,39 @@ func (isp *incidentUpdateStatusProcessor) IncidentUpdateStatus(callback slack.In } incidentStatusId := buildUpdateIncidentStatusRequest(isp.logger, callback.View.State.Values) - result, err := query.FindIncidentStatusById(isp.db, incidentStatusId) + result, err := isp.incidentService.FindIncidentStatusById(incidentStatusId) if err != nil { isp.logger.Error("FindIncidentStatusById error", zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name), zap.String("user_id", user.ID), zap.Error(err)) + return } else if result == nil { isp.logger.Error("IncidentStatusEntity Object not found", zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name), zap.String("user_id", user.ID), zap.Error(err)) return } - incidentEntity.Status = entity.IncidentStatus(result.Name) + + incidentEntity.Status = result.ID incidentEntity.UpdatedBy = user.ID - if incidentEntity.Status == "RESOLVED" { + if result.IsTerminalStatus { now := time.Now() - incidentEntity.CustomerImpactEndTime = &now + incidentEntity.EndTime = &now } - err = query.UpdateIncident(isp.db, incidentEntity) + err = isp.incidentService.UpdateIncident(incidentEntity) if err != nil { isp.logger.Error("UpdateIncident error", zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name), zap.String("user_id", user.ID), zap.Error(err)) } - msgOption := slack.MsgOptionText(fmt.Sprintf("<@%s> > set status to %s", user.ID, incidentEntity.Status), false) + msgOption := slack.MsgOptionText(fmt.Sprintf("<@%s> > set status to %s", user.ID, result.Name), false) _, _, errMessage := isp.client.PostMessage(callback.View.PrivateMetadata, msgOption) if errMessage != nil { isp.logger.Error("post response failed for IncidentUpdateStatus", zap.Error(errMessage)) return } - if incidentEntity.Status == "RESOLVED" { + if result.IsTerminalStatus { isp.client.ArchiveConversation(callback.View.PrivateMetadata) } @@ -95,14 +101,12 @@ func (isp *incidentUpdateStatusProcessor) IncidentUpdateStatus(callback slack.In isp.client.Ack(*request, payload) } -//TODO - FOR RESOLVED SCENARIO - -func buildUpdateIncidentStatusRequest(logger *zap.Logger, blockActions map[string]map[string]slack.BlockAction) int { +func buildUpdateIncidentStatusRequest(logger *zap.Logger, blockActions map[string]map[string]slack.BlockAction) uint { var requestMap = make(map[string]string, 0) for _, actions := range blockActions { - for actionID, action := range actions { - if action.Type == "static_select" { - requestMap[actionID] = action.SelectedOption.Value + for actionID, a := range actions { + if a.Type == "static_select" { + requestMap[actionID] = a.SelectedOption.Value } } } @@ -112,5 +116,5 @@ func buildUpdateIncidentStatusRequest(logger *zap.Logger, blockActions map[strin if err != nil { logger.Error("String conversion to int faileed in buildUpdateIncidentTypeRequest for "+selectedValue, zap.Error(err)) } - return selectedValueInInt + return uint(selectedValueInInt) } diff --git a/internal/processor/action/incident_update_tags_action.go b/internal/processor/action/incident_update_tags_action.go new file mode 100644 index 0000000..66c3cdd --- /dev/null +++ b/internal/processor/action/incident_update_tags_action.go @@ -0,0 +1,191 @@ +package action + +import ( + "fmt" + "github.com/lib/pq" + "houston/internal/processor/action/view" + "houston/pkg/postgres/service/incident" + "houston/pkg/postgres/service/tag" + "houston/pkg/postgres/service/team" + "strconv" + + "github.com/slack-go/slack" + "github.com/slack-go/slack/socketmode" + "go.uber.org/zap" +) + +type IncidentUpdateTagsAction struct { + client *socketmode.Client + logger *zap.Logger + incidentService *incident.Service + teamService *team.Service + tagService *tag.Service +} + +func NewIncidentUpdateTagsAction(client *socketmode.Client, logger *zap.Logger, incidentService *incident.Service, + teamService *team.Service, tagService *tag.Service) *IncidentUpdateTagsAction { + return &IncidentUpdateTagsAction{ + client: client, + logger: logger, + incidentService: incidentService, + teamService: teamService, + tagService: tagService, + } +} + +func (itp *IncidentUpdateTagsAction) IncidentUpdateTagsRequestProcess(callback slack.InteractionCallback, request *socketmode.Request) { + incidentEntity, err := itp.incidentService.FindIncidentByChannelId(callback.Channel.ID) + if err != nil || incidentEntity == nil { + itp.logger.Error(fmt.Sprintf("failure while getting incident entity for channel: %v", callback.Channel.ID)) + return + } + + team, err := itp.teamService.FindTeamById(incidentEntity.TeamId) + if err != nil || team == nil { + itp.logger.Error(fmt.Sprintf("failure while getting team for incident id: %v", incidentEntity.ID)) + return + } + + tags, err := itp.tagService.FindTagsByTeamId(team.ID) + if err != nil || tags == nil { + itp.logger.Error(fmt.Sprintf("failure while getting tags for incident id: %v", incidentEntity.ID)) + return + } + + var blocks []slack.InputBlock + + for _, t := range *tags { + tagValues, err := itp.tagService.FindTagValuesByTagId(t.Id) + if err != nil { + itp.logger.Error(fmt.Sprintf("failed to get the tag values for tagId: %v", t.Id)) + return + } + + incidentTag, err := itp.incidentService.GetIncidentTagByTagId(incidentEntity.ID, t.Id) + if err != nil { + itp.logger.Error(fmt.Sprintf("failed to get the incident tag for incidentId: %v", incidentEntity.ID)) + return + } + + var block *slack.InputBlock + + if incidentTag == nil { + incidentTag, err = itp.incidentService.CreateIncidentTag(incidentEntity.ID, t.Id) + if err != nil || incidentTag == nil { + itp.logger.Error(fmt.Sprintf("failure while creating tag for incident id: %v", incidentEntity.ID)) + return + } + } + + if t.Type == tag.FreeText { + var initialValue string + if incidentTag.FreeTextValue == nil { + initialValue = "" + } else { + initialValue = *incidentTag.FreeTextValue + } + block = view.CreateInputBlock(t, nil, initialValue, t.Optional) + } else { + var initialTags []tag.TagValueEntity + + if tagValues == nil { + itp.logger.Error(fmt.Sprintf("no tag values are present for tag: %v", t.Id)) + return + } + + if incidentTag.TagValueIds != nil { + for _, tv := range *tagValues { + for _, it := range incidentTag.TagValueIds { + if tv.ID == uint(it) { + ltv := tv + initialTags = append(initialTags, ltv) + } + } + } + } + + block = view.CreateInputBlock(t, *tagValues, initialTags, t.Optional) + } + + blocks = append(blocks, *block) + } + + modalRequest := view.BuildIncidentUpdateTagModal(callback.Channel, blocks) + + _, err = itp.client.OpenView(callback.TriggerID, modalRequest) + if err != nil { + itp.logger.Error("houston slackbot openview command for IncidentUpdateTagsRequestProcess failed.", + zap.String("trigger_id", callback.TriggerID), zap.String("channel_id", callback.Channel.ID), zap.Error(err)) + return + } + var payload interface{} + itp.client.Ack(*request, payload) +} + +func (itp *IncidentUpdateTagsAction) IncidentUpdateTags(callback slack.InteractionCallback, request *socketmode.Request) { + incidentEntity, err := itp.incidentService.FindIncidentByChannelId(callback.View.PrivateMetadata) + if err != nil || incidentEntity == nil { + itp.logger.Error(fmt.Sprintf("failed to get the incicent for channel id: %v", callback.View.PrivateMetadata)) + return + } + + incidentTagsEntity, err := itp.incidentService.GetIncidentTagsByIncidentId(incidentEntity.ID) + if err != nil || incidentTagsEntity == nil { + itp.logger.Error(fmt.Sprintf("failed to get the incicent tags for incident id: %v", incidentEntity.ID)) + return + } + + blockActions := callback.View.State.Values + actions := make(map[string]slack.BlockAction, 0) + + for _, a := range blockActions { + for key, value := range a { + actions[key] = value + } + } + + // build request to update the tag values + for _, it := range *incidentTagsEntity { + tagEntity, err := itp.tagService.FindById(it.TagId) + if err != nil || tagEntity == nil { + itp.logger.Error(fmt.Sprintf("failed to get the tag for id: %v", it.TagId)) + return + } + + if tagEntity.Type == tag.FreeText { + localValue := actions[tagEntity.ActionId].Value + it.FreeTextValue = &localValue + } else if tagEntity.Type == tag.SingleValue { + localValue := actions[tagEntity.ActionId].SelectedOption.Value + value, err := strconv.Atoi(localValue) + if err != nil { + itp.logger.Error(fmt.Sprintf("string to int conversion failed for incident: %v, tag value: %v", + incidentEntity.ID, localValue)) + return + } + it.TagValueIds = pq.Int32Array{int32(value)} + } else if tagEntity.Type == tag.MultiValue { + var valueArray pq.Int32Array + for _, o := range actions[tagEntity.ActionId].SelectedOptions { + localValue, err := strconv.Atoi(o.Value) + if err != nil { + itp.logger.Error(fmt.Sprintf("string to int conversion failed for incident: %v, tag value: %v", + incidentEntity.ID, localValue)) + return + } + valueArray = append(valueArray, int32(localValue)) + } + it.TagValueIds = valueArray + } + + _, err = itp.incidentService.SaveIncidentTag(it) + if err != nil { + itp.logger.Error(fmt.Sprintf("Failed while saving incident tag values for incidentId: %v", + callback.View.PrivateMetadata), zap.Error(err)) + return + } + } + + var payload interface{} + itp.client.Ack(*request, payload) +} diff --git a/pkg/slack/houston/command/incident_update_title.go b/internal/processor/action/incident_update_title_action.go similarity index 64% rename from pkg/slack/houston/command/incident_update_title.go rename to internal/processor/action/incident_update_title_action.go index 28e3d61..324871d 100644 --- a/pkg/slack/houston/command/incident_update_title.go +++ b/internal/processor/action/incident_update_title_action.go @@ -1,32 +1,30 @@ -package command +package action import ( "fmt" - "houston/pkg/postgres/query" - houston "houston/pkg/slack/houston/design" - "github.com/slack-go/slack" "github.com/slack-go/slack/socketmode" "go.uber.org/zap" - "gorm.io/gorm" + "houston/internal/processor/action/view" + "houston/pkg/postgres/service/incident" ) -type incidentUpdateTitleProcessor struct { - client *socketmode.Client - db *gorm.DB - logger *zap.Logger +type IncidentUpdateTitleAction struct { + client *socketmode.Client + logger *zap.Logger + incidentService *incident.Service } -func NewIncidentUpdateTitleProcessor(client *socketmode.Client, db *gorm.DB, logger *zap.Logger) *incidentUpdateTitleProcessor { - return &incidentUpdateTitleProcessor{ - client: client, - db: db, - logger: logger, +func NewIncidentUpdateTitleAction(client *socketmode.Client, logger *zap.Logger, incidentService *incident.Service) *IncidentUpdateTitleAction { + return &IncidentUpdateTitleAction{ + client: client, + logger: logger, + incidentService: incidentService, } } -func (itp *incidentUpdateTitleProcessor) IncidentUpdateTitleRequestProcess(callback slack.InteractionCallback, request *socketmode.Request) { - result, err := query.FindIncidentByChannelId(itp.db, callback.Channel.ID) +func (itp *IncidentUpdateTitleAction) IncidentUpdateTitleRequestProcess(callback slack.InteractionCallback, request *socketmode.Request) { + result, err := itp.incidentService.FindIncidentByChannelId(callback.Channel.ID) if err != nil { itp.logger.Error("FindIncidentByChannelId error", zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name), @@ -38,11 +36,11 @@ func (itp *incidentUpdateTitleProcessor) IncidentUpdateTitleRequestProcess(callb zap.String("user_id", callback.User.ID), zap.Error(err)) return } - modalRequest := houston.BuildIncidentUpdateTitleModal(itp.db, callback.Channel, result.Title) + modalRequest := view.BuildIncidentUpdateTitleModal(callback.Channel, result.Title) _, err = itp.client.OpenView(callback.TriggerID, modalRequest) if err != nil { - itp.logger.Error("houston slack openview command for IncidentUpdateTitleRequestProcess failed.", + itp.logger.Error("houston slackbot openview command for IncidentUpdateTitleRequestProcess failed.", zap.String("trigger_id", callback.TriggerID), zap.String("channel_id", callback.Channel.ID), zap.Error(err)) return } @@ -50,8 +48,8 @@ func (itp *incidentUpdateTitleProcessor) IncidentUpdateTitleRequestProcess(callb itp.client.Ack(*request, payload) } -func (itp *incidentUpdateTitleProcessor) IncidentUpdateTitle(callback slack.InteractionCallback, request *socketmode.Request, channel slack.Channel, user slack.User) { - incidentEntity, err := query.FindIncidentByChannelId(itp.db, callback.View.PrivateMetadata) +func (itp *IncidentUpdateTitleAction) IncidentUpdateTitle(callback slack.InteractionCallback, request *socketmode.Request, channel slack.Channel, user slack.User) { + incidentEntity, err := itp.incidentService.FindIncidentByChannelId(callback.View.PrivateMetadata) if err != nil { itp.logger.Error("FindIncidentByChannelId error", zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name), @@ -68,7 +66,7 @@ func (itp *incidentUpdateTitleProcessor) IncidentUpdateTitle(callback slack.Inte incidentEntity.Title = incidentTitle incidentEntity.UpdatedBy = user.ID - err = query.UpdateIncident(itp.db, incidentEntity) + err = itp.incidentService.UpdateIncident(incidentEntity) if err != nil { itp.logger.Error("IncidentUpdateTitle error", zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name), @@ -82,12 +80,12 @@ func (itp *incidentUpdateTitleProcessor) IncidentUpdateTitle(callback slack.Inte return } - result, err := query.FindIncidentSeverityTeamJoin(itp.db, incidentEntity.SlackChannel) + result, err := itp.incidentService.FindIncidentSeverityTeamJoin(incidentEntity.SlackChannel) if err != nil { itp.logger.Error("query failed for FindIncidentSeverityTeamJoin", zap.Error(errMessage)) return } - msgOption = slack.MsgOptionText(fmt.Sprintf("set the channel topic: %s : %s %s | %s", result.TeamsName, result.SeverityName, incidentEntity.IncidentName, incidentEntity.Title), false) + msgOption = slack.MsgOptionText(fmt.Sprintf("set the channel topic: %s : %s %s | %s", result.TeamName, result.SeverityName, incidentEntity.IncidentName, incidentEntity.Title), false) _, _, errMessage = itp.client.PostMessage(callback.View.PrivateMetadata, msgOption) if errMessage != nil { itp.logger.Error("post response failed for IncidentUpdateTitle", zap.Error(errMessage)) @@ -100,9 +98,9 @@ func (itp *incidentUpdateTitleProcessor) IncidentUpdateTitle(callback slack.Inte func buildUpdateIncidentTitleRequest(blockActions map[string]map[string]slack.BlockAction) string { var requestMap = make(map[string]string, 0) for _, actions := range blockActions { - for actionID, action := range actions { - if action.Type == "plain_text_input" { - requestMap[actionID] = action.Value + for actionID, a := range actions { + if a.Type == "plain_text_input" { + requestMap[actionID] = a.Value } } } diff --git a/internal/processor/action/incident_update_type_action.go b/internal/processor/action/incident_update_type_action.go new file mode 100644 index 0000000..237e252 --- /dev/null +++ b/internal/processor/action/incident_update_type_action.go @@ -0,0 +1,147 @@ +package action + +import ( + "fmt" + "houston/internal/processor/action/view" + "houston/pkg/postgres/service/incident" + "houston/pkg/postgres/service/team" + "houston/pkg/slackbot" + "strconv" + "time" + + "github.com/slack-go/slack" + "github.com/slack-go/slack/socketmode" + "go.uber.org/zap" +) + +type IncidentUpdateTypeAction struct { + socketModeClient *socketmode.Client + logger *zap.Logger + teamService *team.Service + incidentService *incident.Service + slackbotClient *slackbot.Client +} + +func NewIncidentUpdateTypeAction(client *socketmode.Client, logger *zap.Logger, incidentService *incident.Service, teamService *team.Service, slackbotClient *slackbot.Client) *IncidentUpdateTypeAction { + return &IncidentUpdateTypeAction{ + socketModeClient: client, + logger: logger, + teamService: teamService, + incidentService: incidentService, + slackbotClient: slackbotClient, + } +} + +func (itp *IncidentUpdateTypeAction) IncidentUpdateTypeRequestProcess(callback slack.InteractionCallback, request *socketmode.Request) { + teams, err := itp.teamService.GetAllActiveTeams() + if err != nil { + itp.logger.Error("GetAllActiveTeams error", + zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name), + zap.String("user_id", callback.User.ID), zap.Error(err)) + return + } + + modalRequest := view.BuildIncidentUpdateTypeModal(callback.Channel, *teams) + + _, err = itp.socketModeClient.OpenView(callback.TriggerID, modalRequest) + if err != nil { + itp.logger.Error("houston slackbot openview command failed.", + zap.String("trigger_id", callback.TriggerID), zap.String("channel_id", callback.Channel.ID), zap.Error(err)) + return + } + var payload interface{} + itp.socketModeClient.Ack(*request, payload) +} + +func (itp *IncidentUpdateTypeAction) IncidentUpdateType(callback slack.InteractionCallback, request *socketmode.Request, channel slack.Channel, user slack.User) { + incidentEntity, err := itp.incidentService.FindIncidentByChannelId(callback.View.PrivateMetadata) + if err != nil { + itp.logger.Error("FindIncidentByChannelId error", + zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name), + zap.String("user_id", user.ID), zap.Error(err)) + return + } else if incidentEntity == nil { + itp.logger.Error("IncidentEntity not found ", + zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name), + zap.String("user_id", callback.User.ID), zap.Error(err)) + return + } + + incidentTypeId := itp.buildUpdateIncidentTypeRequest(callback.View.State.Values) + teamEntity, err := itp.teamService.FindTeamById(incidentTypeId) + if err != nil { + itp.logger.Error("FindTeamEntityById error", + zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name), + zap.String("user_id", user.ID), zap.Error(err)) + return + } else if teamEntity == nil { + itp.logger.Error("Team Not Found", + zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name), + zap.String("user_id", user.ID), zap.Error(err)) + return + } + incidentEntity.TeamId = teamEntity.ID + incidentEntity.UpdatedBy = user.ID + err = itp.incidentService.UpdateIncident(incidentEntity) + if err != nil { + itp.logger.Error("UpdateIncident error", + zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name), + zap.String("user_id", user.ID), zap.Error(err)) + } + + itp.addDefaultUsersToIncident(callback.View.PrivateMetadata, incidentEntity.TeamId) + + msgOption := slack.MsgOptionText(fmt.Sprintf("<@%s> > set Team to %s", user.ID, teamEntity.Name), false) + _, _, errMessage := itp.socketModeClient.PostMessage(callback.View.PrivateMetadata, msgOption) + if errMessage != nil { + itp.logger.Error("post response failed for IncidentUpdateType", zap.Error(errMessage)) + return + } + var payload interface{} + itp.socketModeClient.Ack(*request, payload) +} + +func (itp *IncidentUpdateTypeAction) addDefaultUsersToIncident(channelId string, teamId uint) error { + team, _ := itp.teamService.FindTeamById(teamId) + + userIdList := team.SlackUserIds + + for _, o := range userIdList { + itp.slackbotClient.InviteUsersToConversation(channelId, o) + } + return nil +} + +func (itp *IncidentUpdateTypeAction) InviteOnCallPersonToIncident(channelId, ts string) { + go func() { + time.Sleep(3 * time.Second) + msg, _, _, _ := itp.socketModeClient.GetConversationReplies(&slack.GetConversationRepliesParameters{ + ChannelID: channelId, + Timestamp: ts, + Limit: 2, + }, + ) + if len(msg) > 1 { + //User id needs to sliced from `<@XXXXXXXXXXXX>` format to `XXXXXXXXXXXX` + itp.socketModeClient.InviteUsersToConversation(channelId, msg[1].Text[2:13]) + } + }() +} + +func (itp *IncidentUpdateTypeAction) buildUpdateIncidentTypeRequest(blockActions map[string]map[string]slack.BlockAction) uint { + var requestMap = make(map[string]string, 0) + for _, actions := range blockActions { + for actionID, a := range actions { + if a.Type == "static_select" { + requestMap[actionID] = a.SelectedOption.Value + } + } + } + + selectedValue := requestMap["incident_type_modal_request"] + selectedValueInInt, err := strconv.Atoi(selectedValue) + if err != nil { + itp.logger.Error("String conversion to int faileed in buildUpdateIncidentTypeRequest for "+selectedValue, zap.Error(err)) + } + return uint(selectedValueInInt) +} diff --git a/internal/processor/action/member_join_action.go b/internal/processor/action/member_join_action.go new file mode 100644 index 0000000..2bd01e0 --- /dev/null +++ b/internal/processor/action/member_join_action.go @@ -0,0 +1,85 @@ +package action + +import ( + "github.com/slack-go/slack/slackevents" + "github.com/slack-go/slack/socketmode" + "go.uber.org/zap" + "houston/internal/processor/action/view" + "houston/pkg/postgres/service/incident" + "houston/pkg/postgres/service/severity" + "houston/pkg/postgres/service/team" +) + +type MemberJoinAction struct { + client *socketmode.Client + logger *zap.Logger + incidentService *incident.Service + teamService *team.Service + severityService *severity.Service +} + +func NewMemberJoinAction(socketModeClient *socketmode.Client, logger *zap.Logger, incidentService *incident.Service, teamService *team.Service, severityService *severity.Service) *MemberJoinAction { + return &MemberJoinAction{ + client: socketModeClient, + logger: logger, + incidentService: incidentService, + teamService: teamService, + severityService: severityService, + } +} + +func (mp *MemberJoinAction) PerformAction(memberJoinedChannelEvent *slackevents.MemberJoinedChannelEvent) { + mp.logger.Info("processing member join action", zap.String("channel", memberJoinedChannelEvent.Channel)) + + incidentEntity, err := mp.incidentService.FindIncidentByChannelId(memberJoinedChannelEvent.Channel) + if err != nil { + mp.logger.Error("error in searching incident", zap.String("channel", memberJoinedChannelEvent.Channel), + zap.String("user_id", memberJoinedChannelEvent.User), zap.Error(err)) + return + } else if err == nil && incidentEntity == nil { + mp.logger.Info("incident not found", zap.String("channel", memberJoinedChannelEvent.Channel), + zap.String("user_id", memberJoinedChannelEvent.User), zap.Error(err)) + return + } + + teamEntity, err := mp.teamService.FindTeamById(incidentEntity.TeamId) + if err != nil { + mp.logger.Error("error in fetching team", zap.String("channel", memberJoinedChannelEvent.Channel), + zap.Uint("incident_id", incidentEntity.ID), zap.Error(err)) + return + } else if teamEntity == nil { + mp.logger.Info("team not found", zap.String("channel", memberJoinedChannelEvent.Channel), + zap.Uint("incident_id", incidentEntity.ID)) + return + } + + severityEntity, err := mp.severityService.FindSeverityById(incidentEntity.SeverityId) + if err != nil { + mp.logger.Error("error in fetching severity", zap.String("channel", memberJoinedChannelEvent.Channel), + zap.Uint("incident_id", incidentEntity.ID), zap.Error(err)) + return + } else if severityEntity == nil { + mp.logger.Info("severity not found", zap.String("channel", memberJoinedChannelEvent.Channel), + zap.Uint("incident_id", incidentEntity.ID)) + return + } + + incidentStatusEntity, err := mp.incidentService.FindIncidentStatusById(incidentEntity.Status) + if err != nil { + mp.logger.Error("error in fetching incident status", zap.String("channel", memberJoinedChannelEvent.Channel), + zap.Uint("incident_id", incidentEntity.ID), zap.Error(err)) + return + } else if incidentStatusEntity == nil { + mp.logger.Info("incident status not found", zap.String("channel", memberJoinedChannelEvent.Channel), + zap.Uint("incident_id", incidentEntity.ID)) + return + } + + blocks := view.IncidentSummarySection(incidentEntity, teamEntity, severityEntity, incidentStatusEntity) + msgOption := view.IncidentEphemeralMessage(incidentEntity, blocks) + + _, err = mp.client.PostEphemeral(memberJoinedChannelEvent.Channel, memberJoinedChannelEvent.User, msgOption) + if err != nil { + mp.logger.Error("post response failed", zap.Error(err)) + } +} diff --git a/internal/processor/action/show_incidents_action.go b/internal/processor/action/show_incidents_action.go new file mode 100644 index 0000000..82205d6 --- /dev/null +++ b/internal/processor/action/show_incidents_action.go @@ -0,0 +1,46 @@ +package action + +import ( + "github.com/slack-go/slack" + "github.com/slack-go/slack/socketmode" + "github.com/spf13/viper" + "go.uber.org/zap" + "houston/internal/processor/action/view" + "houston/pkg/postgres/service/incident" +) + +type ShowIncidentsAction struct { + client *socketmode.Client + logger *zap.Logger + incidentService *incident.Service +} + +func ShowIncidentsProcessor(client *socketmode.Client, logger *zap.Logger, incidentService *incident.Service) *ShowIncidentsAction { + return &ShowIncidentsAction{ + client: client, + logger: logger, + incidentService: incidentService, + } +} + +func (sip *ShowIncidentsAction) ProcessAction(channel slack.Channel, user slack.User, triggerId string, request *socketmode.Request) { + + limit := viper.GetInt("SHOW_INCIDENTS_LIMIT") + s, err := sip.incidentService.GetOpenIncidents(limit) + if err != nil { + sip.logger.Error("GetOpenIncidents query failed.", + zap.String("trigger_id", triggerId), zap.String("channel_id", channel.ID), zap.Error(err)) + return + } + + blocks := view.GenerateModalForShowIncidentsButtonSection(*s) + msgOption := slack.MsgOptionBlocks(blocks...) + _, err = sip.client.Client.PostEphemeral(channel.ID, user.ID, msgOption) + if err != nil { + sip.logger.Error("houston slackbot PostEphemeral command failed for ProcessAction.", + zap.String("trigger_id", triggerId), zap.String("channel_id", channel.ID), zap.String("user_id", user.ID), zap.Error(err)) + return + } + var payload interface{} + sip.client.Ack(*request, payload) +} diff --git a/internal/processor/action/slash_command_action.go b/internal/processor/action/slash_command_action.go new file mode 100644 index 0000000..e1982e6 --- /dev/null +++ b/internal/processor/action/slash_command_action.go @@ -0,0 +1,49 @@ +package action + +import ( + "github.com/slack-go/slack" + "github.com/slack-go/slack/socketmode" + "go.uber.org/zap" + "houston/internal/processor/action/view" + "houston/pkg/postgres/service/incident" +) + +type SlashCommandAction struct { + incidentService *incident.Service + logger *zap.Logger + socketModeClient *socketmode.Client +} + +func NewSlashCommandAction(service *incident.Service, logger *zap.Logger, socketModeClient *socketmode.Client) *SlashCommandAction { + return &SlashCommandAction{ + incidentService: service, + logger: logger, + socketModeClient: socketModeClient, + } +} + +func (sca *SlashCommandAction) PerformAction(evt *socketmode.Event) { + cmd, ok := evt.Data.(slack.SlashCommand) + sca.logger.Info("processing houston command", zap.Any("payload", cmd)) + if !ok { + sca.logger.Error("event data to slash command conversion failed", zap.Any("data", evt)) + return + } + + result, err := sca.incidentService.FindIncidentByChannelId(cmd.ChannelID) + if err != nil { + sca.logger.Error("FindIncidentBySlackChannelId errors", + zap.String("channel_id", cmd.ChannelID), zap.String("channel", cmd.ChannelName), + zap.String("user_id", cmd.UserID), zap.Error(err)) + return + } + + if result != nil { + sca.logger.Info("Result", zap.String("result", result.IncidentName)) + payload := view.ExistingIncidentOptionsBlock() + sca.socketModeClient.Ack(*evt.Request, payload) + } + + payload := view.NewIncidentBlock() + sca.socketModeClient.Ack(*evt.Request, payload) +} diff --git a/internal/processor/action/start_incident_block_action.go b/internal/processor/action/start_incident_block_action.go new file mode 100644 index 0000000..ca8ab9e --- /dev/null +++ b/internal/processor/action/start_incident_block_action.go @@ -0,0 +1,53 @@ +package action + +import ( + "github.com/slack-go/slack" + "github.com/slack-go/slack/socketmode" + "go.uber.org/zap" + "houston/internal/processor/action/view" + "houston/pkg/postgres/service/severity" + "houston/pkg/postgres/service/team" +) + +type StartIncidentBlockAction struct { + socketModeClient *socketmode.Client + logger *zap.Logger + teamService *team.Service + severityService *severity.Service +} + +func NewStartIncidentBlockAction(client *socketmode.Client, logger *zap.Logger, teamService *team.Service, severityService *severity.Service) *StartIncidentBlockAction { + return &StartIncidentBlockAction{ + socketModeClient: client, + logger: logger, + teamService: teamService, + severityService: severityService, + } +} + +func (sip *StartIncidentBlockAction) ProcessAction(request *socketmode.Request, callback slack.InteractionCallback) { + teams, err := sip.teamService.GetAllActiveTeams() + if err != nil || teams == nil { + sip.logger.Error("[SIP] failed while getting all active teams") + return + } + + severities, err := sip.severityService.GetAllActiveSeverity() + if err != nil || severities == nil { + sip.logger.Error("[SIP] failed while getting all active severities") + return + } + + modal := view.GenerateModalRequest(*teams, *severities, callback.Channel.ID) + + _, err = sip.socketModeClient.OpenView(callback.TriggerID, modal) + if err != nil { + sip.logger.Error("[SIP] houston slackbot open view command failed.", + zap.String("trigger_id", callback.TriggerID), zap.String("channel_id", callback.Channel.ID), zap.Error(err)) + return + } + + sip.logger.Info("[SIP] houston successfully send modal to slackbot", zap.String("trigger_id", callback.TriggerID)) + var payload interface{} + sip.socketModeClient.Ack(*request, payload) +} diff --git a/internal/processor/action/start_incident_modal_submission_action.go b/internal/processor/action/start_incident_modal_submission_action.go new file mode 100644 index 0000000..c4aa5c8 --- /dev/null +++ b/internal/processor/action/start_incident_modal_submission_action.go @@ -0,0 +1,208 @@ +package action + +import ( + "encoding/json" + "fmt" + "github.com/slack-go/slack" + "github.com/slack-go/slack/socketmode" + "go.uber.org/zap" + "houston/common/util" + "houston/internal/processor/action/view" + "houston/pkg/postgres/service/incident" + "houston/pkg/postgres/service/severity" + "houston/pkg/postgres/service/team" + "houston/pkg/slackbot" + "time" +) + +type CreateIncidentAction struct { + client *socketmode.Client + logger *zap.Logger + incidentService *incident.Service + slackbotClient *slackbot.Client + teamService *team.Service + severityService *severity.Service +} + +func NewCreateIncidentProcessor(client *socketmode.Client, logger *zap.Logger, incidentService *incident.Service, + teamService *team.Service, severityService *severity.Service, slackbotClient *slackbot.Client) *CreateIncidentAction { + return &CreateIncidentAction{ + client: client, + logger: logger, + incidentService: incidentService, + teamService: teamService, + severityService: severityService, + slackbotClient: slackbotClient, + } +} + +func (cip *CreateIncidentAction) CreateIncidentModalCommandProcessing(callback slack.InteractionCallback, request *socketmode.Request) { + // Build create incident request + createIncidentRequest := buildCreateIncidentRequest(callback) + cip.logger.Info("[CIP] incident request created", zap.Any("request", createIncidentRequest)) + + // Save the incident to the database + incidentEntity, err := cip.incidentService.CreateIncident(createIncidentRequest) + if err != nil { + cip.logger.Error("[CIP] Error while creating incident", zap.Error(err)) + return + } + + channelID, err := cip.createSlackChannel(incidentEntity) + if err != nil { + cip.logger.Error("[CIP] Error while creating incident channel", zap.Error(err)) + return + } + + teamEntity, severityEntity, incidentStatusEntity, err := cip.getTeamAndSeverityAndStatus(incidentEntity.TeamId, + incidentEntity.SeverityId, incidentEntity.Status) + if err != nil { + cip.logger.Error("[CIP] failed whilte getting team, severity and status", zap.Error(err)) + return + } + + // Post incident summary to Blaze Group channel and incident channel + timestamp, err := cip.postIncidentSummary(callback.View.PrivateMetadata, *channelID, incidentEntity, teamEntity, + severityEntity, incidentStatusEntity) + if err != nil { + cip.logger.Error("[CIP] error while posting incident summary", zap.Error(err)) + return + } + + // add default users to the incident + err = cip.addDefaultUsersToIncident(*channelID, teamEntity, severityEntity) + if err != nil { + cip.logger.Error("[CIP] error while adding default users to incident", zap.Error(err)) + return + } + + cip.InviteOnCallPersonToIncident(*channelID, *timestamp) + + // Acknowledge the interaction callback + var payload interface{} + cip.client.Ack(*request, payload) +} + +func (cip *CreateIncidentAction) InviteOnCallPersonToIncident(channelId, ts string) { + go func() { + time.Sleep(3 * time.Second) + msg, _, _, _ := cip.client.GetConversationReplies(&slack.GetConversationRepliesParameters{ + ChannelID: channelId, + Timestamp: ts, + Limit: 2, + }, + ) + if len(msg) > 1 { + //User id needs to sliced from `<@XXXXXXXXXXXX>` format to `XXXXXXXXXXXX` + cip.client.InviteUsersToConversation(channelId, msg[1].Text[2:13]) + } + }() +} + +func (cip *CreateIncidentAction) createSlackChannel(incidentEntity *incident.IncidentEntity) (*string, error) { + channelName := fmt.Sprintf("houston-%d", incidentEntity.ID) + channelID, err := cip.slackbotClient.CreateChannel(channelName) + if err != nil { + return nil, err + } + + incidentEntity.SlackChannel = channelID + incidentEntity.IncidentName = channelName + err = cip.incidentService.UpdateIncident(incidentEntity) + if err != nil { + cip.logger.Error(fmt.Sprintf("[CIP] failed to update the slack channel name for incident-id: %v", incidentEntity.ID)) + return nil, err + } + return &channelID, nil +} + +func (cip *CreateIncidentAction) addDefaultUsersToIncident(channelId string, teamEntity *team.TeamEntity, + severityEntity *severity.SeverityEntity) error { + var userIdList []string + userIdList = append(append(userIdList, teamEntity.SlackUserIds...), severityEntity.SlackUserIds...) + + for _, o := range userIdList { + cip.slackbotClient.InviteUsersToConversation(channelId, o) + } + return nil +} + +func (cip *CreateIncidentAction) postIncidentSummary(blazeGroupChannelID, incidentChannelID string, + incidentEntity *incident.IncidentEntity, teamEntity *team.TeamEntity, severityEntity *severity.SeverityEntity, + incidentStatusEntity *incident.IncidentStatusEntity) (*string, error) { + // Post incident summary to Blaze Group channel and incident channel + blocks := view.IncidentSummarySection(incidentEntity, teamEntity, severityEntity, incidentStatusEntity) + color := util.GetColorBySeverity(incidentEntity.SeverityId) + att := slack.Attachment{Blocks: blocks, Color: color} + _, timestamp, err := cip.client.PostMessage(blazeGroupChannelID, slack.MsgOptionAttachments(att)) + + if err == nil { + cip.incidentService.CreateIncidentChannelEntry(&incident.CreateIncidentChannelEntry{ + SlackChannel: blazeGroupChannelID, + MessageTimeStamp: timestamp, + IncidentId: incidentEntity.ID, + }) + } else { + return nil, fmt.Errorf("[CIP] error in saving message %v", err) + } + + _, timestamp, err = cip.client.PostMessage(incidentChannelID, slack.MsgOptionAttachments(att)) + if err == nil { + cip.incidentService.CreateIncidentChannelEntry(&incident.CreateIncidentChannelEntry{ + SlackChannel: incidentChannelID, + MessageTimeStamp: timestamp, + IncidentId: incidentEntity.ID, + }) + } else { + return nil, fmt.Errorf("[CIP] error in saving message %v", err) + } + return ×tamp, nil +} + +func (cip *CreateIncidentAction) getTeamAndSeverityAndStatus(teamId, severityId, status uint) (*team.TeamEntity, + *severity.SeverityEntity, *incident.IncidentStatusEntity, error) { + teamEntity, err := cip.teamService.FindTeamById(teamId) + if err != nil || teamEntity == nil { + return nil, nil, nil, err + } + + severityEntity, err := cip.severityService.FindSeverityById(severityId) + if err != nil || severityEntity == nil { + return nil, nil, nil, err + } + + incidentStatusEntity, err := cip.incidentService.FindIncidentStatusById(status) + if err != nil || incidentStatusEntity == nil { + return nil, nil, nil, err + } + + return teamEntity, severityEntity, incidentStatusEntity, nil +} + +func buildCreateIncidentRequest(callback slack.InteractionCallback) *incident.CreateIncidentRequest { + blockActions := callback.View.State.Values + var createIncidentRequest incident.CreateIncidentRequest + var requestMap = make(map[string]string, 0) + + for _, actions := range blockActions { + for actionID, a := range actions { + if string(a.Type) == string(slack.METPlainTextInput) { + requestMap[actionID] = a.Value + } + if string(a.Type) == slack.OptTypeStatic { + requestMap[actionID] = a.SelectedOption.Value + } + } + } + + desRequestMap, _ := json.Marshal(requestMap) + json.Unmarshal(desRequestMap, &createIncidentRequest) + + createIncidentRequest.Status = incident.Investigating + createIncidentRequest.StartTime = time.Now() + createIncidentRequest.EnableReminder = false + createIncidentRequest.CreatedBy = callback.User.ID + createIncidentRequest.UpdatedBy = callback.User.ID + + return &createIncidentRequest +} diff --git a/internal/processor/action/view/create_incident_modal.go b/internal/processor/action/view/create_incident_modal.go new file mode 100644 index 0000000..cd5660e --- /dev/null +++ b/internal/processor/action/view/create_incident_modal.go @@ -0,0 +1,91 @@ +package view + +import ( + "houston/common/util" + "houston/pkg/postgres/service/severity" + "houston/pkg/postgres/service/team" + "strconv" + + "github.com/slack-go/slack" +) + +func GenerateModalRequest(teams []team.TeamEntity, severities []severity.SeverityEntity, channel string) slack.ModalViewRequest { + // Create a ModalViewRequest with a header and two inputs + titleText := slack.NewTextBlockObject(slack.PlainTextType, "Houston", false, false) + closeText := slack.NewTextBlockObject(slack.PlainTextType, "Close", false, false) + submitText := slack.NewTextBlockObject(slack.PlainTextType, "Send", false, false) + + headerText := slack.NewTextBlockObject("mrkdwn", "Start Incident", false, false) + headerSection := slack.NewSectionBlock(headerText, nil, nil) + + incidentTypeOptions := createOptionBlockObjectsForTeams(teams) + incidentTypeText := slack.NewTextBlockObject(slack.PlainTextType, "Incident Type", false, false) + incidentTypePlaceholder := slack.NewTextBlockObject(slack.PlainTextType, "The incident type for the incident to create", false, false) + incidentTypeOption := slack.NewOptionsSelectBlockElement(slack.OptTypeStatic, incidentTypePlaceholder, "type", incidentTypeOptions...) + incidentTypeBlock := slack.NewInputBlock("incident_type", incidentTypeText, nil, incidentTypeOption) + + severityOptions := createOptionBlockObjectsForSeverity(severities) + severityText := slack.NewTextBlockObject(slack.PlainTextType, "Severity", false, false) + severityTextPlaceholder := slack.NewTextBlockObject(slack.PlainTextType, "The severity for the incident to create", false, false) + severityOption := slack.NewOptionsSelectBlockElement(slack.OptTypeStatic, severityTextPlaceholder, "severity", severityOptions...) + severityBlock := slack.NewInputBlock("incident_severity", severityText, nil, severityOption) + + //pagerDutyImpactedServiceText := slack.NewTextBlockObject(slack.PlainTextType, "Pagerduty", false, false) + //pagerDutyImpactedServicePlaceholder := slack.NewTextBlockObject(slack.PlainTextType, "Select pagerduty impacted services", false, false) + //pagerDutyImpactedServiceElement := slack.NewPlainTextInputBlockElement(pagerDutyImpactedServicePlaceholder, "pagerduty_impacted") + //pagerDutyImpactedService := slack.NewInputBlock("Pagerduty impacted service", pagerDutyImpactedServiceText, nil, pagerDutyImpactedServiceElement) + //pagerDutyImpactedService.Optional = true + + incidentTitleText := slack.NewTextBlockObject(slack.PlainTextType, "Incident Title", false, false) + incidentTitlePlaceholder := slack.NewTextBlockObject(slack.PlainTextType, "Write something", false, false) + incidentTitleElement := slack.NewPlainTextInputBlockElement(incidentTitlePlaceholder, "title") + incidentTitle := slack.NewInputBlock("Incident title", incidentTitleText, nil, incidentTitleElement) + + incidentDescriptionText := slack.NewTextBlockObject(slack.PlainTextType, "Incident description", false, false) + incidentDescriptionPlaceholder := slack.NewTextBlockObject(slack.PlainTextType, "Write something", false, false) + incidentDescriptionElement := slack.NewPlainTextInputBlockElement(incidentDescriptionPlaceholder, "description") + incidentDescriptionElement.Multiline = true + incidentDescription := slack.NewInputBlock("Incident description", incidentDescriptionText, nil, incidentDescriptionElement) + incidentDescription.Optional = true + + blocks := slack.Blocks{ + BlockSet: []slack.Block{ + headerSection, + incidentTypeBlock, + severityBlock, + //pagerDutyImpactedService, + incidentTitle, + incidentDescription, + }, + } + + return slack.ModalViewRequest{ + Type: slack.VTModal, + Title: titleText, + Close: closeText, + Submit: submitText, + Blocks: blocks, + PrivateMetadata: channel, + CallbackID: string(util.StartIncidentSubmit), + } +} + +func createOptionBlockObjectsForSeverity(options []severity.SeverityEntity) []*slack.OptionBlockObject { + optionBlockObjects := make([]*slack.OptionBlockObject, 0, len(options)) + for _, o := range options { + localObj := o + optionText := slack.NewTextBlockObject(slack.PlainTextType, localObj.Name, false, false) + optionBlockObjects = append(optionBlockObjects, slack.NewOptionBlockObject(strconv.Itoa(int(localObj.ID)), optionText, nil)) + } + return optionBlockObjects +} + +func createOptionBlockObjectsForTeams(options []team.TeamEntity) []*slack.OptionBlockObject { + optionBlockObjects := make([]*slack.OptionBlockObject, 0, len(options)) + for _, o := range options { + localObj := o + optionText := slack.NewTextBlockObject(slack.PlainTextType, localObj.Name, false, false) + optionBlockObjects = append(optionBlockObjects, slack.NewOptionBlockObject(strconv.Itoa(int(localObj.ID)), optionText, nil)) + } + return optionBlockObjects +} diff --git a/pkg/slack/houston/design/incident_assign.go b/internal/processor/action/view/incident_assign.go similarity index 61% rename from pkg/slack/houston/design/incident_assign.go rename to internal/processor/action/view/incident_assign.go index 7ad3d7e..2aaa72c 100644 --- a/pkg/slack/houston/design/incident_assign.go +++ b/internal/processor/action/view/incident_assign.go @@ -1,49 +1,48 @@ -package houston +package view import ( - "houston/entity" - "github.com/slack-go/slack" + "houston/common/util" + "houston/pkg/postgres/service/incident" ) func GenerateModalForIncidentAssign(channel slack.Channel) slack.ModalViewRequest { - - titleText := slack.NewTextBlockObject("plain_text", "Houston", false, false) - closeText := slack.NewTextBlockObject("plain_text", "Close", false, false) - submitText := slack.NewTextBlockObject("plain_text", "Submit", false, false) + titleText := slack.NewTextBlockObject(slack.PlainTextType, "Houston", false, false) + closeText := slack.NewTextBlockObject(slack.PlainTextType, "Close", false, false) + submitText := slack.NewTextBlockObject(slack.PlainTextType, "Submit", false, false) headerText := slack.NewTextBlockObject("mrkdwn", ":information_source: Incident roles are customizable by your organization. While a user can be assigned multiple roles, a role cannot be assigned to multiple users.", false, false) headerSection := slack.NewSectionBlock(headerText, nil, nil) optionBlockObjects := []*slack.OptionBlockObject{ { Text: &slack.TextBlockObject{ - Type: "plain_text", - Text: string(entity.RESPONDER), + Type: slack.PlainTextType, + Text: string(incident.Responder), }, - Value: string(entity.RESPONDER), + Value: string(incident.Responder), }, { Text: &slack.TextBlockObject{ - Type: "plain_text", - Text: string(entity.SERVICE_OWNER), + Type: slack.PlainTextType, + Text: string(incident.ServiceOwner), }, - Value: string(entity.SERVICE_OWNER), + Value: string(incident.ServiceOwner), }, { Text: &slack.TextBlockObject{ - Type: "plain_text", - Text: string(entity.RETROSPECTIVE), + Type: slack.PlainTextType, + Text: string(incident.Retrospective), }, - Value: string(entity.RETROSPECTIVE), + Value: string(incident.Retrospective), }, } - rolePlaceholder := slack.NewTextBlockObject("plain_text", "Select a role", false, false) + rolePlaceholder := slack.NewTextBlockObject(slack.PlainTextType, "Select a role", false, false) roleTypeOption := slack.NewOptionsSelectBlockElement(slack.OptTypeStatic, rolePlaceholder, "role_type", optionBlockObjects...) roleTypeText := slack.NewTextBlockObject(slack.PlainTextType, "Roles", false, false) roleBlock := slack.NewInputBlock("role_type", roleTypeText, nil, roleTypeOption) - userPlaceholder := slack.NewTextBlockObject("plain_text", "Select people", false, false) + userPlaceholder := slack.NewTextBlockObject(slack.PlainTextType, "Select people", false, false) userTypeOption := slack.NewOptionsSelectBlockElement(slack.OptTypeUser, userPlaceholder, "users_select") userTypeText := slack.NewTextBlockObject(slack.PlainTextType, "Users", false, false) userBlock := slack.NewInputBlock("user_type", userTypeText, nil, userTypeOption) @@ -55,13 +54,13 @@ func GenerateModalForIncidentAssign(channel slack.Channel) slack.ModalViewReques }, } return slack.ModalViewRequest{ - Type: slack.ViewType("modal"), + Type: slack.VTModal, Title: titleText, Close: closeText, Submit: submitText, Blocks: blocks, PrivateMetadata: channel.ID, - CallbackID: "assignIncidentRole", + CallbackID: util.AssignIncidentRoleSubmit, } } diff --git a/pkg/slack/houston/design/incident_description.go b/internal/processor/action/view/incident_description.go similarity index 55% rename from pkg/slack/houston/design/incident_description.go rename to internal/processor/action/view/incident_description.go index abc1eac..8748ef6 100644 --- a/pkg/slack/houston/design/incident_description.go +++ b/internal/processor/action/view/incident_description.go @@ -1,20 +1,20 @@ -package houston +package view import ( "github.com/slack-go/slack" - "gorm.io/gorm" + "houston/common/util" ) -func BuildIncidentUpdateDescriptionModal(db *gorm.DB, channel slack.Channel, description string) slack.ModalViewRequest { - titleText := slack.NewTextBlockObject("plain_text", "Set Description", false, false) - closeText := slack.NewTextBlockObject("plain_text", "Close", false, false) - submitText := slack.NewTextBlockObject("plain_text", "Submit", false, false) +func BuildIncidentUpdateDescriptionModal(channel slack.Channel, description string) slack.ModalViewRequest { + titleText := slack.NewTextBlockObject(slack.PlainTextType, "Set Description", false, false) + closeText := slack.NewTextBlockObject(slack.PlainTextType, "Close", false, false) + submitText := slack.NewTextBlockObject(slack.PlainTextType, "Submit", false, false) headerText := slack.NewTextBlockObject("mrkdwn", "Description", false, false) headerSection := slack.NewSectionBlock(headerText, nil, nil) - incidentDescriptionText := slack.NewTextBlockObject("plain_text", "Incident description", false, false) - incidentDescriptionPlaceholder := slack.NewTextBlockObject("plain_text", "Write something", false, false) + incidentDescriptionText := slack.NewTextBlockObject(slack.PlainTextType, "Incident description", false, false) + incidentDescriptionPlaceholder := slack.NewTextBlockObject(slack.PlainTextType, "Write something", false, false) incidentDescriptionElement := slack.NewPlainTextInputBlockElement(incidentDescriptionPlaceholder, "incident_description") incidentDescriptionElement.Multiline = true incidentDescriptionElement.InitialValue = description @@ -30,13 +30,13 @@ func BuildIncidentUpdateDescriptionModal(db *gorm.DB, channel slack.Channel, des } return slack.ModalViewRequest{ - Type: slack.ViewType("modal"), + Type: slack.VTModal, Title: titleText, Close: closeText, Submit: submitText, Blocks: blocks, PrivateMetadata: channel.ID, - CallbackID: "setIncidentDescription", + CallbackID: util.SetIncidentDescriptionSubmit, } } diff --git a/pkg/slack/houston/design/blazeless_section.go b/internal/processor/action/view/incident_section.go similarity index 91% rename from pkg/slack/houston/design/blazeless_section.go rename to internal/processor/action/view/incident_section.go index b38666a..234c2e4 100644 --- a/pkg/slack/houston/design/blazeless_section.go +++ b/internal/processor/action/view/incident_section.go @@ -1,13 +1,16 @@ -package houston +package view -import "github.com/slack-go/slack" +import ( + "github.com/slack-go/slack" + "houston/common/util" +) -func ButtonDesign() map[string]interface{} { +func NewIncidentBlock() map[string]interface{} { payload := map[string]interface{}{ "blocks": []slack.Block{ slack.NewActionBlock("start_incident_button", slack.NewButtonBlockElement( - "start_incident_button", + string(util.StartIncident), "start_incident_button_value", &slack.TextBlockObject{ Type: slack.PlainTextType, @@ -15,7 +18,7 @@ func ButtonDesign() map[string]interface{} { }, ), slack.NewButtonBlockElement( - "show_incidents_button", + string(util.ShowIncidents), "show_incidents_button_value", &slack.TextBlockObject{ Type: slack.PlainTextType, @@ -42,11 +45,9 @@ func ButtonDesign() map[string]interface{} { } return payload - - // client.Ack(*request, payload) } -func OptionsBlock() map[string]interface{} { +func ExistingIncidentOptionsBlock() map[string]interface{} { return map[string]interface{}{ "blocks": []slack.Block{ incidentSectionBlock(), @@ -70,49 +71,49 @@ func incidentSectionBlock() *slack.SectionBlock { Type: "plain_text", Text: "Assign Incident Role", }, - Value: "assignIncidentRole", + Value: util.AssignIncidentRole, }, { Text: &slack.TextBlockObject{ Type: "plain_text", Text: "Resolve Incident", }, - Value: "resolveIncident", + Value: util.ResolveIncident, }, { Text: &slack.TextBlockObject{ Type: "plain_text", Text: "Set Incident Status", }, - Value: "setIncidentStatus", + Value: util.SetIncidentStatus, }, { Text: &slack.TextBlockObject{ Type: "plain_text", Text: "Set Incident Type", }, - Value: "setIncidentType", + Value: util.SetIncidentType, }, { Text: &slack.TextBlockObject{ Type: "plain_text", Text: "Set Incident Severity", }, - Value: "setIncidentSeverity", + Value: util.SetIncidentSeverity, }, { Text: &slack.TextBlockObject{ Type: "plain_text", Text: "Set Incident Title", }, - Value: "setIncidentTitle", + Value: util.SetIncidentTitle, }, { Text: &slack.TextBlockObject{ Type: "plain_text", Text: "Set Incident Description", }, - Value: "setIncidentDescription", + Value: util.SetIncidentDescription, }, } @@ -187,21 +188,21 @@ func tagsSectionBlock() *slack.SectionBlock { Type: "plain_text", Text: "Add Tag(s)", }, - Value: "addTags", + Value: util.AddTags, }, { Text: &slack.TextBlockObject{ Type: "plain_text", Text: "Show Tags", }, - Value: "showTags", + Value: util.ShowTags, }, { Text: &slack.TextBlockObject{ Type: "plain_text", Text: "Remove Tag", }, - Value: "removeTag", + Value: util.RemoveTag, }, } diff --git a/pkg/slack/houston/design/incident_severity.go b/internal/processor/action/view/incident_severity.go similarity index 70% rename from pkg/slack/houston/design/incident_severity.go rename to internal/processor/action/view/incident_severity.go index 96c08a8..a6f87a0 100644 --- a/pkg/slack/houston/design/incident_severity.go +++ b/internal/processor/action/view/incident_severity.go @@ -1,17 +1,18 @@ -package houston +package view import ( "fmt" - "houston/entity" + "houston/common/util" + "houston/pkg/postgres/service/severity" "strconv" "github.com/slack-go/slack" ) -func BuildIncidentUpdateSeverityModal(channel slack.Channel, incidentSeverity []entity.SeverityEntity) slack.ModalViewRequest { - titleText := slack.NewTextBlockObject("plain_text", "Set Severity of Incident", false, false) - closeText := slack.NewTextBlockObject("plain_text", "Close", false, false) - submitText := slack.NewTextBlockObject("plain_text", "Submit", false, false) +func BuildIncidentUpdateSeverityModal(channel slack.Channel, incidentSeverity []severity.SeverityEntity) slack.ModalViewRequest { + titleText := slack.NewTextBlockObject(slack.PlainTextType, "Set Severity of Incident", false, false) + closeText := slack.NewTextBlockObject(slack.PlainTextType, "Close", false, false) + submitText := slack.NewTextBlockObject(slack.PlainTextType, "Submit", false, false) headerText := slack.NewTextBlockObject("mrkdwn", "Severity", false, false) headerSection := slack.NewSectionBlock(headerText, nil, nil) @@ -30,18 +31,18 @@ func BuildIncidentUpdateSeverityModal(channel slack.Channel, incidentSeverity [] } return slack.ModalViewRequest{ - Type: slack.ViewType("modal"), + Type: slack.VTModal, Title: titleText, Close: closeText, Submit: submitText, Blocks: blocks, PrivateMetadata: channel.ID, - CallbackID: "setIncidentSeverity", + CallbackID: util.SetIncidentSeveritySubmit, } } -func createIncidentSeverityBlock(options []entity.SeverityEntity) []*slack.OptionBlockObject { +func createIncidentSeverityBlock(options []severity.SeverityEntity) []*slack.OptionBlockObject { optionBlockObjects := make([]*slack.OptionBlockObject, 0, len(options)) for _, o := range options { txt := fmt.Sprintf("%s (%s)", o.Name, o.Description) diff --git a/pkg/slack/houston/design/blazeless_incident_status_update_modal.go b/internal/processor/action/view/incident_status_update_modal.go similarity index 63% rename from pkg/slack/houston/design/blazeless_incident_status_update_modal.go rename to internal/processor/action/view/incident_status_update_modal.go index 7d1da03..908bcd0 100644 --- a/pkg/slack/houston/design/blazeless_incident_status_update_modal.go +++ b/internal/processor/action/view/incident_status_update_modal.go @@ -1,26 +1,23 @@ -package houston +package view import ( "fmt" - "houston/entity" - "houston/pkg/postgres/query" + "houston/common/util" + "houston/pkg/postgres/service/incident" "strconv" "github.com/slack-go/slack" - "gorm.io/gorm" ) -func BuildIncidentUpdateStatusModal(db *gorm.DB, channel slack.Channel) slack.ModalViewRequest { - titleText := slack.NewTextBlockObject("plain_text", "Set Status of Incident", false, false) - closeText := slack.NewTextBlockObject("plain_text", "Close", false, false) - submitText := slack.NewTextBlockObject("plain_text", "Submit", false, false) +func BuildIncidentUpdateStatusModal(statuses []incident.IncidentStatusEntity, channel slack.Channel) slack.ModalViewRequest { + titleText := slack.NewTextBlockObject(slack.PlainTextType, "Set Status of Incident", false, false) + closeText := slack.NewTextBlockObject(slack.PlainTextType, "Close", false, false) + submitText := slack.NewTextBlockObject(slack.PlainTextType, "Submit", false, false) headerText := slack.NewTextBlockObject("mrkdwn", "Status", false, false) headerSection := slack.NewSectionBlock(headerText, nil, nil) - incidentStatus, _ := query.FetchAllIncidentStatus(db) - - incidentStatusBlockOption := createIncidentStatusBlock(incidentStatus) + incidentStatusBlockOption := createIncidentStatusBlock(statuses) incidentStatusText := slack.NewTextBlockObject(slack.PlainTextType, "Incident Status", false, false) incidentStatusOption := slack.NewOptionsSelectBlockElement(slack.OptTypeStatic, nil, "incident_status_modal_request", incidentStatusBlockOption...) @@ -34,18 +31,18 @@ func BuildIncidentUpdateStatusModal(db *gorm.DB, channel slack.Channel) slack.Mo } return slack.ModalViewRequest{ - Type: slack.ViewType("modal"), + Type: slack.VTModal, Title: titleText, Close: closeText, Submit: submitText, Blocks: blocks, PrivateMetadata: channel.ID, - CallbackID: "setIncidentStatus", + CallbackID: util.SetIncidentStatusSubmit, } } -func createIncidentStatusBlock(options []entity.IncidentStatusEntity) []*slack.OptionBlockObject { +func createIncidentStatusBlock(options []incident.IncidentStatusEntity) []*slack.OptionBlockObject { optionBlockObjects := make([]*slack.OptionBlockObject, 0, len(options)) for _, o := range options { txt := fmt.Sprintf("%s - %s", o.Name, o.Description) diff --git a/internal/processor/action/view/incident_summary_section.go b/internal/processor/action/view/incident_summary_section.go new file mode 100644 index 0000000..da22647 --- /dev/null +++ b/internal/processor/action/view/incident_summary_section.go @@ -0,0 +1,40 @@ +package view + +import ( + "fmt" + "github.com/slack-go/slack" + "houston/common/util" + "houston/pkg/postgres/service/incident" + "houston/pkg/postgres/service/severity" + "houston/pkg/postgres/service/team" +) + +func IncidentSummarySection(incident *incident.IncidentEntity, team *team.TeamEntity, severity *severity.SeverityEntity, incidentStatus *incident.IncidentStatusEntity) slack.Blocks { + return slack.Blocks{ + BlockSet: []slack.Block{ + buildTypeAndChannelSectionBlock(incident, team.Name, severity.Name, incidentStatus.Name)}, + } +} + +func buildTypeAndChannelSectionBlock(incident *incident.IncidentEntity, teamName, severityName, incidentStatus string) *slack.SectionBlock { + fields := []*slack.TextBlockObject{ + slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*<@%s>* \n*%s* - *%s*\n", incident.CreatedBy, incident.IncidentName, incident.Title), false, false), + slack.NewTextBlockObject("mrkdwn", "\n", false, false), + slack.NewTextBlockObject("mrkdwn", incident.Description, false, false), + slack.NewTextBlockObject("mrkdwn", "\n", false, false), + slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Type*\n%s", teamName), false, false), + slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Channel*\n<#%s>", incident.SlackChannel), false, false), + slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Severity*\n%s", severityName), false, false), + slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Ticket*\n%s", "Integration Disabled"), false, false), + slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Status*\n%s", incidentStatus), false, false), + slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Meeting*\n%s", "Integration Disabled"), false, false), + } + block := slack.NewSectionBlock(nil, fields, nil) + + return block +} + +func IncidentEphemeralMessage(incident *incident.IncidentEntity, blocks slack.Blocks) slack.MsgOption { + color := util.GetColorBySeverity(incident.SeverityId) + return slack.MsgOptionAttachments(slack.Attachment{Blocks: blocks, Color: color}) +} diff --git a/pkg/slack/houston/design/incident_title.go b/internal/processor/action/view/incident_title.go similarity index 54% rename from pkg/slack/houston/design/incident_title.go rename to internal/processor/action/view/incident_title.go index bddd7c7..512faad 100644 --- a/pkg/slack/houston/design/incident_title.go +++ b/internal/processor/action/view/incident_title.go @@ -1,20 +1,20 @@ -package houston +package view import ( "github.com/slack-go/slack" - "gorm.io/gorm" + "houston/common/util" ) -func BuildIncidentUpdateTitleModal(db *gorm.DB, channel slack.Channel, title string) slack.ModalViewRequest { - titleText := slack.NewTextBlockObject("plain_text", "Set Title of Incident", false, false) - closeText := slack.NewTextBlockObject("plain_text", "Close", false, false) - submitText := slack.NewTextBlockObject("plain_text", "Submit", false, false) +func BuildIncidentUpdateTitleModal(channel slack.Channel, title string) slack.ModalViewRequest { + titleText := slack.NewTextBlockObject(slack.PlainTextType, "Set Title of Incident", false, false) + closeText := slack.NewTextBlockObject(slack.PlainTextType, "Close", false, false) + submitText := slack.NewTextBlockObject(slack.PlainTextType, "Submit", false, false) headerText := slack.NewTextBlockObject("mrkdwn", "Title", false, false) headerSection := slack.NewSectionBlock(headerText, nil, nil) - incidentTitleText := slack.NewTextBlockObject("plain_text", "Incident Title", false, false) - incidentTitlePlaceholder := slack.NewTextBlockObject("plain_text", "Write something", false, false) + incidentTitleText := slack.NewTextBlockObject(slack.PlainTextType, "Incident Title", false, false) + incidentTitlePlaceholder := slack.NewTextBlockObject(slack.PlainTextType, "Write something", false, false) incidentTitleElement := slack.NewPlainTextInputBlockElement(incidentTitlePlaceholder, "incident_title") incidentTitleElement.InitialValue = title incidentBelowText := slack.NewTextBlockObject("plain_text", "The title to set for the incident.", false, false) @@ -28,13 +28,13 @@ func BuildIncidentUpdateTitleModal(db *gorm.DB, channel slack.Channel, title str } return slack.ModalViewRequest{ - Type: slack.ViewType("modal"), + Type: slack.VTModal, Title: titleText, Close: closeText, Submit: submitText, Blocks: blocks, PrivateMetadata: channel.ID, - CallbackID: "setIncidentTitle", + CallbackID: util.SetIncidentTitleSubmit, } } diff --git a/internal/processor/action/view/incident_update_tags.go b/internal/processor/action/view/incident_update_tags.go new file mode 100644 index 0000000..aead0ec --- /dev/null +++ b/internal/processor/action/view/incident_update_tags.go @@ -0,0 +1,107 @@ +package view + +import ( + "fmt" + "github.com/slack-go/slack" + "houston/common/util" + "houston/pkg/postgres/service/tag" + "strconv" +) + +func BuildIncidentUpdateTagModal(channel slack.Channel, inputBlocks []slack.InputBlock) slack.ModalViewRequest { + titleText := slack.NewTextBlockObject(slack.PlainTextType, "Edit Tags", false, false) + closeText := slack.NewTextBlockObject(slack.PlainTextType, "Close", false, false) + submitText := slack.NewTextBlockObject(slack.PlainTextType, "Submit", false, false) + + var localBlocks []slack.Block + for _, block := range inputBlocks { + localBlocks = append(localBlocks, block) + } + + blocks := slack.Blocks{ + BlockSet: localBlocks, + } + + return slack.ModalViewRequest{ + Type: slack.VTModal, + Title: titleText, + Close: closeText, + Submit: submitText, + Blocks: blocks, + PrivateMetadata: channel.ID, + CallbackID: util.UpdateTagSubmit, + } + +} + +func CreateInputBlock(tagEntity tag.TagDTO, tagValues []tag.TagValueEntity, initialTagValues interface{}, isOptional bool) *slack.InputBlock { + text := slack.NewTextBlockObject(slack.PlainTextType, tagEntity.Label, false, false) + var element slack.BlockElement + + switch tagEntity.Type { + case tag.FreeText: + { + element = createPlainTextInputBlockElement(tagEntity, initialTagValues) + } + case tag.SingleValue: + { + element = createOptionsSelectBlockElement(tagEntity, tagValues, initialTagValues.([]tag.TagValueEntity)) + } + case tag.MultiValue: + { + element = createMultiOptionsSelectBlockElement(tagEntity, tagValues, initialTagValues.([]tag.TagValueEntity)) + } + default: + { + return nil + } + } + + block := slack.NewInputBlock(tagEntity.Name, text, nil, element) + block.Optional = isOptional + + return block +} + +func createPlainTextInputBlockElement(tagEntity tag.TagDTO, value interface{}) *slack.PlainTextInputBlockElement { + placeholder := slack.NewTextBlockObject(slack.PlainTextType, tagEntity.PlaceHolder, false, false) + element := slack.NewPlainTextInputBlockElement(placeholder, tagEntity.ActionId) + + if value != nil { + element.InitialValue = fmt.Sprintf("%v", value) + } else { + element.InitialValue = "" + } + + return element +} + +func createOptionsSelectBlockElement(tagEntity tag.TagDTO, tagValues []tag.TagValueEntity, initialTagValues []tag.TagValueEntity) *slack.SelectBlockElement { + blockOptions := createTagOptions(tagValues) + placeholder := slack.NewTextBlockObject(slack.PlainTextType, tagEntity.PlaceHolder, false, false) + element := slack.NewOptionsSelectBlockElement(slack.OptTypeStatic, placeholder, tagEntity.ActionId, blockOptions...) + if initialTagValues != nil { + element.InitialOption = createTagOptions(initialTagValues)[0] + } + return element +} + +func createMultiOptionsSelectBlockElement(tagEntity tag.TagDTO, tagValues []tag.TagValueEntity, initialTagValues []tag.TagValueEntity) *slack.MultiSelectBlockElement { + blockOptions := createTagOptions(tagValues) + placeholder := slack.NewTextBlockObject(slack.PlainTextType, tagEntity.PlaceHolder, false, false) + element := slack.NewOptionsMultiSelectBlockElement(slack.MultiOptTypeStatic, placeholder, tagEntity.ActionId, blockOptions...) + if initialTagValues != nil { + element.InitialOptions = createTagOptions(initialTagValues) + } + return element +} + +func createTagOptions(tagValues []tag.TagValueEntity) []*slack.OptionBlockObject { + optionBlockObjects := make([]*slack.OptionBlockObject, 0, len(tagValues)) + for _, o := range tagValues { + txt := fmt.Sprintf("%s", o.Value) + optionText := slack.NewTextBlockObject(slack.PlainTextType, txt, false, false) + optionBlockObjects = append(optionBlockObjects, slack.NewOptionBlockObject(strconv.FormatUint(uint64(o.ID), 10), optionText, nil)) + } + return optionBlockObjects +} diff --git a/pkg/slack/houston/design/incident_update_type.go b/internal/processor/action/view/incident_update_type.go similarity index 71% rename from pkg/slack/houston/design/incident_update_type.go rename to internal/processor/action/view/incident_update_type.go index 96a4815..e1e39b1 100644 --- a/pkg/slack/houston/design/incident_update_type.go +++ b/internal/processor/action/view/incident_update_type.go @@ -1,17 +1,18 @@ -package houston +package view import ( "fmt" - "houston/entity" + "houston/common/util" + "houston/pkg/postgres/service/team" "strconv" "github.com/slack-go/slack" ) -func BuildIncidentUpdateTypeModal(channel slack.Channel, teams []entity.TeamEntity) slack.ModalViewRequest { - titleText := slack.NewTextBlockObject("plain_text", "Set Type of Incident", false, false) - closeText := slack.NewTextBlockObject("plain_text", "Close", false, false) - submitText := slack.NewTextBlockObject("plain_text", "Submit", false, false) +func BuildIncidentUpdateTypeModal(channel slack.Channel, teams []team.TeamEntity) slack.ModalViewRequest { + titleText := slack.NewTextBlockObject(slack.PlainTextType, "Set Type of Incident", false, false) + closeText := slack.NewTextBlockObject(slack.PlainTextType, "Close", false, false) + submitText := slack.NewTextBlockObject(slack.PlainTextType, "Submit", false, false) headerText := slack.NewTextBlockObject("mrkdwn", "Type", false, false) headerSection := slack.NewSectionBlock(headerText, nil, nil) @@ -30,18 +31,18 @@ func BuildIncidentUpdateTypeModal(channel slack.Channel, teams []entity.TeamEnti } return slack.ModalViewRequest{ - Type: slack.ViewType("modal"), + Type: slack.VTModal, Title: titleText, Close: closeText, Submit: submitText, Blocks: blocks, PrivateMetadata: channel.ID, - CallbackID: "setIncidentType", + CallbackID: util.SetIncidentTypeSubmit, } } -func createIncidentTypeBlock(options []entity.TeamEntity) []*slack.OptionBlockObject { +func createIncidentTypeBlock(options []team.TeamEntity) []*slack.OptionBlockObject { optionBlockObjects := make([]*slack.OptionBlockObject, 0, len(options)) for _, o := range options { txt := fmt.Sprintf("%s", o.Name) diff --git a/internal/processor/action/view/show_incidents_button_section.go b/internal/processor/action/view/show_incidents_button_section.go new file mode 100644 index 0000000..bb216de --- /dev/null +++ b/internal/processor/action/view/show_incidents_button_section.go @@ -0,0 +1,37 @@ +package view + +import ( + "houston/pkg/postgres/service/incident" + + "github.com/slack-go/slack" +) + +func GenerateModalForShowIncidentsButtonSection(incident []incident.IncidentSeverityTeamDTO) []slack.Block { + contextBlock := slack.NewContextBlock("", slack.NewTextBlockObject("mrkdwn", ":eye: Only visible to you", false, false)) + + var sectionBlocks []*slack.SectionBlock + + if incident != nil { + for i := 0; i < len(incident); i++ { + fields := []*slack.TextBlockObject{ + slack.NewTextBlockObject("mrkdwn", "\n`"+incident[i].SeverityName+"` `"+incident[i].TeamName+" Incident` \n "+incident[i].Title+"\n <#"+incident[i].SlackChannel+">", false, false), + } + sectionBlocks = append(sectionBlocks, slack.NewSectionBlock(nil, fields, nil)) + } + } else { + fields := []*slack.TextBlockObject{ + slack.NewTextBlockObject("mrkdwn", "\n`No Open Incidents`\n", false, false), + } + sectionBlocks = append(sectionBlocks, slack.NewSectionBlock(nil, fields, nil)) + } + + blocks := []slack.Block{ + contextBlock, + } + for i := 0; i < len(sectionBlocks); i++ { + blocks = append(blocks, sectionBlocks[i]) + } + + return blocks + +} diff --git a/internal/processor/event_type_interactive_processor.go b/internal/processor/event_type_interactive_processor.go new file mode 100644 index 0000000..7242590 --- /dev/null +++ b/internal/processor/event_type_interactive_processor.go @@ -0,0 +1,236 @@ +package processor + +import ( + "fmt" + "github.com/slack-go/slack" + "github.com/slack-go/slack/socketmode" + "go.uber.org/zap" + "houston/common/util" + "houston/internal/processor/action" + "houston/pkg/postgres/service/incident" + "houston/pkg/postgres/service/severity" + "houston/pkg/postgres/service/tag" + "houston/pkg/postgres/service/team" + "houston/pkg/slackbot" +) + +type interactiveEventProcessor interface { + ProcessCommand(callback slack.InteractionCallback, request *socketmode.Request) +} + +type BlockActionProcessor struct { + logger *zap.Logger + socketModeClient *socketmode.Client + startIncidentBlockAction *action.StartIncidentBlockAction + showIncidentsAction *action.ShowIncidentsAction + assignIncidentAction *action.AssignIncidentAction + incidentResolveAction *action.ResolveIncidentAction + incidentUpdateAction *action.UpdateIncidentAction + incidentUpdateTypeAction *action.IncidentUpdateTypeAction + incidentUpdateSeverityAction *action.IncidentUpdateSevertityAction + incidentUpdateTitleAction *action.IncidentUpdateTitleAction + incidentUpdateDescriptionAction *action.IncidentUpdateDescriptionAction + incidentUpdateTagsAction *action.IncidentUpdateTagsAction + incidentShowTagsAction *action.IncidentShowTagsAction +} + +func NewBlockActionProcessor(logger *zap.Logger, socketModeClient *socketmode.Client, incidentService *incident.Service, + teamService *team.Service, severityService *severity.Service, tagService *tag.Service, + slackbotClient *slackbot.Client) *BlockActionProcessor { + return &BlockActionProcessor{ + logger: logger, + socketModeClient: socketModeClient, + startIncidentBlockAction: action.NewStartIncidentBlockAction(socketModeClient, logger, teamService, severityService), + showIncidentsAction: action.ShowIncidentsProcessor(socketModeClient, logger, incidentService), + assignIncidentAction: action.NewAssignIncidentAction(socketModeClient, logger, incidentService), + incidentResolveAction: action.NewIncidentResolveProcessor(socketModeClient, logger, incidentService), + incidentUpdateAction: action.NewIncidentUpdateAction(socketModeClient, logger, incidentService), + incidentUpdateTypeAction: action.NewIncidentUpdateTypeAction(socketModeClient, logger, incidentService, teamService, slackbotClient), + incidentUpdateSeverityAction: action.NewIncidentUpdateSeverityAction(socketModeClient, logger, incidentService, severityService, slackbotClient), + incidentUpdateTitleAction: action.NewIncidentUpdateTitleAction(socketModeClient, logger, incidentService), + incidentUpdateDescriptionAction: action.NewIncidentUpdateDescriptionAction(socketModeClient, logger, incidentService), + incidentUpdateTagsAction: action.NewIncidentUpdateTagsAction(socketModeClient, logger, incidentService, teamService, tagService), + incidentShowTagsAction: action.NewIncidentShowTagsProcessor(socketModeClient, logger, incidentService, tagService), + } +} + +func (bap *BlockActionProcessor) ProcessCommand(callback slack.InteractionCallback, request *socketmode.Request) { + defer func() { + if r := recover(); r != nil { + bap.logger.Error(fmt.Sprintf("[BAP] Exception occurred: %v", r.(error))) + } + }() + + actionId := util.BlockActionType(callback.ActionCallback.BlockActions[0].ActionID) + bap.logger.Info("process button callback event", zap.Any("action_id", actionId), + zap.String("channel", callback.Channel.Name), zap.String("user_id", callback.User.ID), + zap.String("user_name", callback.User.Name)) + + switch actionId { + case util.StartIncident: + { + bap.startIncidentBlockAction.ProcessAction(request, callback) + } + case util.ShowIncidents: + { + bap.showIncidentsAction.ProcessAction(callback.Channel, callback.User, callback.TriggerID, request) + } + case util.Incident: + { + bap.processIncidentCommands(callback, request) + } + + case util.Tags: + { + bap.processTagsCommands(callback, request) + } + default: + { + msgOption := slack.MsgOptionText(fmt.Sprintf("We are working on it"), false) + _, err := bap.socketModeClient.PostEphemeral(callback.Channel.ID, callback.User.ID, msgOption) + if err != nil { + bap.logger.Error("[BAP] houston slackbot PostEphemeral command failed for Working On It features", + zap.String("trigger_id", callback.TriggerID), zap.String("channel_id", callback.Channel.ID), + zap.String("user_id", callback.User.ID), zap.Error(err)) + return + } + var payload interface{} + bap.socketModeClient.Ack(*request, payload) + } + } +} + +func (bap *BlockActionProcessor) processIncidentCommands(callback slack.InteractionCallback, request *socketmode.Request) { + action1 := util.BlockActionType(callback.ActionCallback.BlockActions[0].SelectedOption.Value) + switch action1 { + case util.AssignIncidentRole: + { + bap.assignIncidentAction.IncidentAssignProcess(callback, request) + } + case util.ResolveIncident: + { + bap.incidentResolveAction.IncidentResolveProcess(callback, request) + } + case util.SetIncidentStatus: + { + bap.incidentUpdateAction.IncidentUpdateStatusRequestProcess(callback, request) + } + case util.SetIncidentType: + { + bap.incidentUpdateTypeAction.IncidentUpdateTypeRequestProcess(callback, request) + } + case util.SetIncidentSeverity: + { + bap.incidentUpdateSeverityAction.IncidentUpdateSeverityRequestProcess(callback, request) + } + case util.SetIncidentTitle: + { + bap.incidentUpdateTitleAction.IncidentUpdateTitleRequestProcess(callback, request) + } + case util.SetIncidentDescription: + { + bap.incidentUpdateDescriptionAction.IncidentUpdateDescriptionRequestProcess(callback, request) + } + } +} + +func (bap *BlockActionProcessor) processTagsCommands(callback slack.InteractionCallback, request *socketmode.Request) { + action1 := util.BlockActionType(callback.ActionCallback.BlockActions[0].SelectedOption.Value) + switch action1 { + case util.AddTags: + { + bap.incidentUpdateTagsAction.IncidentUpdateTagsRequestProcess(callback, request) + } + case util.ShowTags: + { + bap.incidentShowTagsAction.IncidentShowTagsRequestProcess(callback, request) + } + case util.RemoveTag: + { + bap.incidentUpdateTagsAction.IncidentUpdateTagsRequestProcess(callback, request) + } + } +} + +type ViewSubmissionProcessor struct { + logger *zap.Logger + socketModeClient *socketmode.Client + incidentChannelMessageUpdateAction *action.IncidentChannelMessageUpdateAction + createIncidentAction *action.CreateIncidentAction + assignIncidentAction *action.AssignIncidentAction + updateIncidentAction *action.UpdateIncidentAction + incidentUpdateTitleAction *action.IncidentUpdateTitleAction + incidentUpdateDescriptionAction *action.IncidentUpdateDescriptionAction + incidentUpdateSeverityAction *action.IncidentUpdateSevertityAction + incidentUpdateTypeAction *action.IncidentUpdateTypeAction + incidentUpdateTagsAction *action.IncidentUpdateTagsAction +} + +func NewViewSubmissionProcessor(logger *zap.Logger, socketModeClient *socketmode.Client, incidentService *incident.Service, + teamService *team.Service, severityService *severity.Service, tagService *tag.Service, + slackbotClient *slackbot.Client) *ViewSubmissionProcessor { + return &ViewSubmissionProcessor{ + logger: logger, + socketModeClient: socketModeClient, + incidentChannelMessageUpdateAction: action.NewIncidentChannelMessageUpdateAction(socketModeClient, logger, incidentService, teamService, severityService), + createIncidentAction: action.NewCreateIncidentProcessor(socketModeClient, logger, incidentService, teamService, severityService, slackbotClient), + assignIncidentAction: action.NewAssignIncidentAction(socketModeClient, logger, incidentService), + updateIncidentAction: action.NewIncidentUpdateAction(socketModeClient, logger, incidentService), + incidentUpdateTitleAction: action.NewIncidentUpdateTitleAction(socketModeClient, logger, incidentService), + incidentUpdateDescriptionAction: action.NewIncidentUpdateDescriptionAction(socketModeClient, logger, incidentService), + incidentUpdateSeverityAction: action.NewIncidentUpdateSeverityAction(socketModeClient, logger, incidentService, severityService, slackbotClient), + incidentUpdateTypeAction: action.NewIncidentUpdateTypeAction(socketModeClient, logger, incidentService, teamService, slackbotClient), + incidentUpdateTagsAction: action.NewIncidentUpdateTagsAction(socketModeClient, logger, incidentService, teamService, tagService), + } +} + +func (vsp *ViewSubmissionProcessor) ProcessCommand(callback slack.InteractionCallback, request *socketmode.Request) { + defer func() { + if r := recover(); r != nil { + vsp.logger.Error(fmt.Sprintf("[VSP] Exception occurred: %v", r.(error))) + } + }() + + var callbackId = util.ViewSubmissionType(callback.View.CallbackID) + switch callbackId { + case util.StartIncidentSubmit: + { + vsp.createIncidentAction.CreateIncidentModalCommandProcessing(callback, request) + } + case util.AssignIncidentRoleSubmit: + { + vsp.assignIncidentAction.IncidentAssignModalCommandProcessing(callback, request) + } + case util.SetIncidentStatusSubmit: + { + vsp.updateIncidentAction.IncidentUpdateStatus(callback, request, callback.Channel, callback.User) + } + case util.SetIncidentTitleSubmit: + { + vsp.incidentUpdateTitleAction.IncidentUpdateTitle(callback, request, callback.Channel, callback.User) + } + case util.SetIncidentDescriptionSubmit: + { + vsp.incidentUpdateDescriptionAction.IncidentUpdateDescription(callback, request, callback.Channel, callback.User) + } + case util.SetIncidentSeveritySubmit: + { + vsp.incidentUpdateSeverityAction.IncidentUpdateSeverity(callback, request, callback.Channel, callback.User) + } + case util.SetIncidentTypeSubmit: + { + vsp.incidentUpdateTypeAction.IncidentUpdateType(callback, request, callback.Channel, callback.User) + } + case util.UpdateTagSubmit: + { + vsp.incidentUpdateTagsAction.IncidentUpdateTags(callback, request) + } + default: + { + return + } + } + + // updates the incident info in all the channels where queried + vsp.incidentChannelMessageUpdateAction.ProcessAction(callback.View.PrivateMetadata) +} diff --git a/internal/processor/events_api_event_processor.go b/internal/processor/events_api_event_processor.go new file mode 100644 index 0000000..10d90cd --- /dev/null +++ b/internal/processor/events_api_event_processor.go @@ -0,0 +1,43 @@ +package processor + +import ( + "fmt" + "github.com/slack-go/slack/slackevents" + "github.com/slack-go/slack/socketmode" + "go.uber.org/zap" + "houston/internal/processor/action" + "houston/pkg/postgres/service/incident" + "houston/pkg/postgres/service/severity" + "houston/pkg/postgres/service/team" +) + +type eventsApiEventProcessor interface { + ProcessCommand(event *slackevents.EventsAPIEvent, request *socketmode.Request) +} + +type MemberJoinedCallbackEventProcessor struct { + logger *zap.Logger + socketModeClient *socketmode.Client + memberJoinAction *action.MemberJoinAction +} + +func NewMemberJoinedCallbackEventProcessor(logger *zap.Logger, socketModeClient *socketmode.Client, + incidentService *incident.Service, teamService *team.Service, severityService *severity.Service) *MemberJoinedCallbackEventProcessor { + return &MemberJoinedCallbackEventProcessor{ + logger: logger, + socketModeClient: socketModeClient, + memberJoinAction: action.NewMemberJoinAction(socketModeClient, logger, incidentService, teamService, severityService), + } +} + +func (mjc *MemberJoinedCallbackEventProcessor) ProcessCommand(event *slackevents.MemberJoinedChannelEvent, request *socketmode.Request) { + defer func() { + if r := recover(); r != nil { + mjc.logger.Error(fmt.Sprintf("[MJC] Exception occurred: %v", r.(error))) + } + }() + + mjc.memberJoinAction.PerformAction(event) + var payload interface{} + mjc.socketModeClient.Ack(*request, payload) +} diff --git a/internal/processor/slash_command_processor.go b/internal/processor/slash_command_processor.go new file mode 100644 index 0000000..4700fab --- /dev/null +++ b/internal/processor/slash_command_processor.go @@ -0,0 +1,33 @@ +package processor + +import ( + "fmt" + "github.com/slack-go/slack/socketmode" + "go.uber.org/zap" + "houston/internal/processor/action" + "houston/pkg/postgres/service/incident" +) + +type SlashCommandProcessor struct { + logger *zap.Logger + socketModeClient *socketmode.Client + slashCommandAction *action.SlashCommandAction +} + +func NewSlashCommandProcessor(logger *zap.Logger, socketModeClient *socketmode.Client, incidentService *incident.Service) *SlashCommandProcessor { + return &SlashCommandProcessor{ + logger: logger, + socketModeClient: socketModeClient, + slashCommandAction: action.NewSlashCommandAction(incidentService, logger, socketModeClient), + } +} + +func (scp *SlashCommandProcessor) ProcessSlashCommand(event socketmode.Event) { + defer func() { + if r := recover(); r != nil { + scp.logger.Error(fmt.Sprintf("[SCP] Exception occurred: %v", r.(error))) + } + }() + + scp.slashCommandAction.PerformAction(&event) +} diff --git a/model/.DS_Store b/model/.DS_Store deleted file mode 100644 index 47ce106..0000000 Binary files a/model/.DS_Store and /dev/null differ diff --git a/model/create_incident.go b/model/create_incident.go deleted file mode 100644 index 82d896a..0000000 --- a/model/create_incident.go +++ /dev/null @@ -1,30 +0,0 @@ -package model - -import ( - "houston/entity" - "time" -) - -type CreateIncident struct { - IncidentType string `json:"incident_type,omitempty"` - Pagerduty string `json:"pagerduty,omitempty"` - IncidentTitle string `json:"incident_title,omitempty"` - IncidentDescription string `json:"incident_description,omitempty"` - RequestOriginatedSlackChannel string - IncidentSeverity string `json:"incident_severity,omitempty"` - Status entity.IncidentStatus - IncidentName string - SlackChannel string - DetectionTime *time.Time - CustomerImpactStartTime time.Time - CustomerImpactEndTime *time.Time - TeamsId int - JiraId string - ConfluenceId string - SeverityTat time.Time - RemindMeAt *time.Time - EnableReminder bool - CreatedBy string - UpdatedBy string - Version int -} diff --git a/model/request/create_message.go b/model/request/create_message.go deleted file mode 100644 index aacebfe..0000000 --- a/model/request/create_message.go +++ /dev/null @@ -1,7 +0,0 @@ -package request - -type CreateMessage struct { - SlackChannel string `gorm:"column:slack_channel"` - IncidentName string `gorm:"column:incident_name"` - MessageTimeStamp string `gorm:"column:message_timestamp"` -} diff --git a/model/request/incident_role.go b/model/request/incident_role.go deleted file mode 100644 index 8203012..0000000 --- a/model/request/incident_role.go +++ /dev/null @@ -1,10 +0,0 @@ -package request - -import "houston/entity" - -type AddIncidentRoleRequest struct { - UserId string `json:"users_select,omitempty"` - Role entity.IncidentRole `json:"role_type,omitempty"` - IncidentId int - CreatedById string -} diff --git a/model/request/severity.go b/model/request/severity.go deleted file mode 100644 index 5065050..0000000 --- a/model/request/severity.go +++ /dev/null @@ -1,17 +0,0 @@ -package request - -type AddSeverityRequest struct { - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` -} - -type AddSeverityUserMappingRequest struct { - SeverityId uint16 `json:"severity_id,omitempty"` - Users []AddSeverityUserData `json:"users,omitempty"` -} - -type AddSeverityUserData struct { - SlackUserId string `json:"slack_user_id,omitempty"` - Primary bool `json:"primary,omitempty"` - Secondary bool `json:"secondary,omitempty"` -} diff --git a/pkg/postgres/config.go b/pkg/postgres/config.go index 72fcf37..66b083e 100644 --- a/pkg/postgres/config.go +++ b/pkg/postgres/config.go @@ -8,6 +8,23 @@ import ( "gorm.io/gorm" ) +type Client struct { + logger *zap.Logger + gormClient *gorm.DB +} + +func NewGormClient(dsn string, logger *zap.Logger) *gorm.DB { + // todo: set the connection configs + db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) + if err != nil { + logger.Error("database connection failed", zap.Error(err)) + os.Exit(1) + } + + logger.Info("database connection established") + return db +} + func PQConnection(logger *zap.Logger) *gorm.DB { dsn := "" db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) diff --git a/pkg/postgres/query/auto_increment_id.go b/pkg/postgres/query/auto_increment_id.go deleted file mode 100644 index 576ae05..0000000 --- a/pkg/postgres/query/auto_increment_id.go +++ /dev/null @@ -1,18 +0,0 @@ -package query - -import ( - "sync" - - "gorm.io/gorm" -) - -type AutoIncrementID struct { - mutex sync.Mutex -} - -func (ai *AutoIncrementID) Next(db *gorm.DB) (int, error) { - ai.mutex.Lock() - defer ai.mutex.Unlock() - id, err := FindLatestIncidentId(db) - return (id + 1), err -} \ No newline at end of file diff --git a/pkg/postgres/query/contributing_factor.go b/pkg/postgres/query/contributing_factor.go deleted file mode 100644 index 38c3a76..0000000 --- a/pkg/postgres/query/contributing_factor.go +++ /dev/null @@ -1,71 +0,0 @@ -package query - -import ( - "houston/entity" - "time" - - "gorm.io/gorm" -) - -func FindContributingFactorsByIncidentId(db *gorm.DB, incidentId int) (*entity.ContributingFactorEntity, error) { - - var cf entity.ContributingFactorEntity - result := db.Where("incidents_tags_contributing_factor_mapping.incident_id = ? AND contributing_factor.deleted_at is NULL AND incidents_tags_contributing_factor_mapping.deleted_at is NULL", incidentId).Joins("JOIN incidents_tags_contributing_factor_mapping on incidents_tags_contributing_factor_mapping.contributing_factor_id = contributing_factor.id").Find(&cf) - if result.Error != nil { - return nil, result.Error - } - if result.RowsAffected == 0 { - return nil, nil - } - return &cf, nil -} - -func FetchAllContributingFactors(db *gorm.DB) ([]entity.ContributingFactorEntity, error) { - var cf []entity.ContributingFactorEntity - result := db.Where("deleted_at is NULL").Find(&cf) - if result.Error != nil { - return nil, result.Error - } - return cf, nil -} - -func UpsertContributingFactorIdToIncidentId(db *gorm.DB, cfId int, incidentId int) error { - - incidentTagsContributingFactorMapping := &entity.IncidentTagsContributingFactorMapping{ - ContributingFactorId: cfId, - IncidentId: incidentId, - } - var cf entity.IncidentTagsContributingFactorMapping - result := db.Where("incident_id = ? and contributing_factor_id = ? and deleted_at is NULL", incidentId, cfId).Find(&cf) - - if result.Error != nil { - return result.Error - } - if result.RowsAffected == 1 { - cf.ContributingFactorId = cfId - cf.UpdatedAt = time.Now() - result := db.Save(cf) - if result != nil { - return result.Error - } - return nil - } else if result.RowsAffected == 0 { - addResult := db.Create(incidentTagsContributingFactorMapping) - if addResult != nil { - return addResult.Error - } - return nil - } - return gorm.ErrInvalidData - -} - -func SetContributingFactorDeletedAt(db *gorm.DB, incidentId int) error { - var cf entity.IncidentTagsContributingFactorMapping - result := db.Model(&cf).Where(" incident_id = ?", incidentId).Update("deleted_at", time.Now()) - - if result.Error != nil { - return result.Error - } - return nil -} diff --git a/pkg/postgres/query/customer_tags.go b/pkg/postgres/query/customer_tags.go deleted file mode 100644 index 0e1ff0e..0000000 --- a/pkg/postgres/query/customer_tags.go +++ /dev/null @@ -1,63 +0,0 @@ -package query - -import ( - "houston/entity" - "time" - - "go.uber.org/zap" - "gorm.io/gorm" -) - -func FindCustomerTagsByIncidentId(logger *zap.Logger, db *gorm.DB, incidentId int) ([]entity.CustomerTagsEntity, error) { - - var customerTags []entity.CustomerTagsEntity - result := db.Where("incidents_tags_customer_mapping.incident_id = ? AND customer_tags.deleted_at is NULL AND incidents_tags_customer_mapping.deleted_at is NULL", incidentId).Joins("JOIN incidents_tags_customer_mapping on incidents_tags_customer_mapping.customer_tags_id = customer_tags.id").Find(&customerTags) - if result.Error != nil { - return nil, result.Error - } - return customerTags, nil -} - -func FetchAllCustomerTags(db *gorm.DB) ([]entity.CustomerTagsEntity, error) { - var customerTags []entity.CustomerTagsEntity - result := db.Where("deleted_at is NULL").Find(&customerTags) - if result.Error != nil { - return nil, result.Error - } - return customerTags, nil -} - -func FindCustomerTagIdMappingWithIncidentByIncidentId(db *gorm.DB, incidentId int) ([]entity.IncidentTagsCustomerMapping, error) { - var incidentTagsCustomerMapping []entity.IncidentTagsCustomerMapping - result := db.Where("incident_id = ? AND deleted_at is NULL", incidentId).Find(&incidentTagsCustomerMapping) - if result.Error != nil { - return nil, result.Error - } - return incidentTagsCustomerMapping, nil -} - -func SetIncidentCustomerTagDeletedAt(db *gorm.DB, customerTagId int, incidentId int) error { - var incidentTagsCustomerMapping entity.IncidentTagsCustomerMapping - result := db.Model(&incidentTagsCustomerMapping).Where("customer_tags_id = ? AND incident_id = ?", customerTagId, incidentId).Update("deleted_at", time.Now()) - - if result.Error != nil { - return result.Error - } - return nil - -} - -func AddCustomerIdMappingToIncidentId(db *gorm.DB, customerTagId int, incidentId int) error { - var incidentTagsCustomerMapping = &entity.IncidentTagsCustomerMapping{ - IncidentId: incidentId, - CustomerTagsId: customerTagId, - } - - result := db.Create(incidentTagsCustomerMapping) - - if result.Error != nil { - return result.Error - } - return nil - -} diff --git a/pkg/postgres/query/incident.go b/pkg/postgres/query/incident.go deleted file mode 100644 index 5944199..0000000 --- a/pkg/postgres/query/incident.go +++ /dev/null @@ -1,143 +0,0 @@ -package query - -import ( - "fmt" - "houston/entity" - "houston/model" - "strconv" - "time" - - "gorm.io/gorm" -) - -func CreateIncident(db *gorm.DB, request *model.CreateIncident) (*entity.IncidentEntity, error) { - severityId, err := strconv.Atoi(request.IncidentSeverity) - if err != nil { - return nil, fmt.Errorf("fetch channel conversationInfo failed. err: %v", err) - } - severity, err := FindSeverityById(db, severityId) - if err != nil { - return nil, fmt.Errorf("fetch FindSeverityById failed. err: %v", err) - } - - incidentEntity := &entity.IncidentEntity{ - Title: request.IncidentTitle, - Description: request.IncidentDescription, - Status: request.Status, - SeverityId: severityId, - IncidentName: request.IncidentName, - SlackChannel: request.SlackChannel, - DetectionTime: request.DetectionTime, - CustomerImpactStartTime: request.CustomerImpactStartTime, - CustomerImpactEndTime: request.CustomerImpactEndTime, - TeamsId: request.TeamsId, - JiraId: request.JiraId, - ConfluenceId: request.ConfluenceId, - RemindMeAt: request.RemindMeAt, - EnableReminder: request.EnableReminder, - SeverityTat: time.Now().AddDate(0, 0, severity.Sla), - CreatedBy: request.CreatedBy, - UpdatedBy: request.UpdatedBy, - Version: request.Version, - } - - result := db.Create(incidentEntity) - if result.Error != nil { - return nil, result.Error - } - - return incidentEntity, nil -} - -func UpdateIncident(db *gorm.DB, incidentEntity *entity.IncidentEntity) error { - result := db.Updates(incidentEntity) - if result.Error != nil { - return result.Error - } - - return nil -} -func FindIncidentById(db *gorm.DB, incidentId string) (*entity.IncidentEntity, error) { - var incidentEntity entity.IncidentEntity - - result := db.Find(&incidentEntity, "id = ?", incidentId) - if result.Error != nil { - return nil, result.Error - } - - return &incidentEntity, nil -} - -func FindNotResolvedLatestIncidents(db *gorm.DB, limit int) ([]entity.IncidentSeverityTeamJoinEntity, error) { - var incidentSeverityTeamJoinEntity []entity.IncidentSeverityTeamJoinEntity - - result := db.Limit(limit). - Where("status <> ? AND incidents.deleted_at IS NULL", entity.Resolved). - Order("incidents.created_at desc"). - Joins("JOIN severity ON severity.id = incidents.severity_id"). - Joins("JOIN teams ON teams.id = incidents.teams_id"). - Select("incidents.title,incidents.status,incidents.slack_channel,severity.id as severity_id,severity.name as severity_name,teams.id as teams_id,teams.name as teams_name"). - Find(&entity.IncidentEntity{}). - Scan(&incidentSeverityTeamJoinEntity) - - if result.Error != nil { - return nil, result.Error - } - - return incidentSeverityTeamJoinEntity, nil -} - -func FindIncidentByChannelId(db *gorm.DB, channelId string) (*entity.IncidentEntity, error) { - var incidentEntity entity.IncidentEntity - - result := db.Find(&incidentEntity, "slack_channel = ?", channelId) - if result.Error != nil { - return nil, result.Error - } - - if result.RowsAffected == 0 { - return nil, nil - } - - return &incidentEntity, nil -} - -func FindIncidentSeverityTeamJoin(db *gorm.DB, slackChannelId string) (*entity.IncidentSeverityTeamJoinEntity, error) { - var incidentSeverityTeamJoinEntity entity.IncidentSeverityTeamJoinEntity - - result := db.Where("incidents.slack_channel = ? and incidents.deleted_at IS NULL", slackChannelId).Joins("JOIN severity ON severity.id = incidents.severity_id").Joins("JOIN teams ON teams.id = incidents.teams_id").Select("incidents.id as incident_id,incidents.title,incidents.status,incidents.slack_channel,severity.id as severity_id,severity.name as severity_name,teams.id as teams_id,teams.name as teams_name").Find(&entity.IncidentEntity{}).Scan(&incidentSeverityTeamJoinEntity) - - if result.Error != nil { - return nil, result.Error - } - if result.RowsAffected == 0 { - return nil, nil - } - - return &incidentSeverityTeamJoinEntity, nil -} - -func FindLatestIncidentId(db *gorm.DB) (int, error) { - var incidentEntity entity.IncidentEntity - - result := db.Order("incidents.id desc").Limit(1).Find(&incidentEntity) - - if result.Error != nil { - return -1, result.Error - } - - return int(incidentEntity.ID), nil -} - -func FindIncidentsBreachingSevTat(db *gorm.DB) ([]entity.IncidentEntity, error) { - var incidentEntity []entity.IncidentEntity - - currentTime := time.Now() - result := db.Where("status <> ? AND severity_tat <= ?", entity.Resolved, currentTime).Find(&incidentEntity) - - if result.Error != nil { - return nil, result.Error - } - - return incidentEntity, nil -} diff --git a/pkg/postgres/query/incident_role.go b/pkg/postgres/query/incident_role.go deleted file mode 100644 index 9bc3f46..0000000 --- a/pkg/postgres/query/incident_role.go +++ /dev/null @@ -1,42 +0,0 @@ -package query - -import ( - "houston/entity" - "houston/model/request" - "time" - - "gorm.io/gorm" -) - -func UpsertIncidentRole(db *gorm.DB, addIncidnentRoleRequest *request.AddIncidentRoleRequest) error { - incidentRolesEntity := &entity.IncidentRoles{ - IncidentId: addIncidnentRoleRequest.IncidentId, - Role: addIncidnentRoleRequest.Role, - AssignedToUserSlackId: addIncidnentRoleRequest.UserId, - AssignedByUserSlackId: addIncidnentRoleRequest.CreatedById, - } - var incidentRoles entity.IncidentRoles - - result := db.Find(&incidentRoles, "incident_id = ? AND role = ?", addIncidnentRoleRequest.IncidentId, addIncidnentRoleRequest.Role) - if result.Error != nil { - return result.Error - } - if result.RowsAffected == 1 { - incidentRolesEntity.ID = incidentRoles.ID - incidentRolesEntity.CreatedAt = incidentRoles.CreatedAt - incidentRolesEntity.UpdatedAt = time.Now() - addResult := db.Save(incidentRolesEntity) - if addResult != nil { - return addResult.Error - } - return nil - - } else if result.RowsAffected == 0 { - addResult := db.Create(incidentRolesEntity) - if addResult != nil { - return addResult.Error - } - return nil - } - return gorm.ErrInvalidData -} diff --git a/pkg/postgres/query/incident_status.go b/pkg/postgres/query/incident_status.go deleted file mode 100644 index b9de5a8..0000000 --- a/pkg/postgres/query/incident_status.go +++ /dev/null @@ -1,45 +0,0 @@ -package query - -import ( - "houston/entity" - "houston/model/request" - - "gorm.io/gorm" -) - -func CreateIncidentStatus(db *gorm.DB, request *request.AddIncidentStatusRequest) error { - incidentStatusEntity := &entity.IncidentStatusEntity{ - Name: request.Name, - Description: request.Description, - } - - result := db.Create(incidentStatusEntity) - if result.Error != nil { - return result.Error - } - - return nil -} - -func FindIncidentStatusById(db *gorm.DB, incidentStatusId int) (*entity.IncidentStatusEntity, error) { - var incidentStatusEntity entity.IncidentStatusEntity - - result := db.Find(&incidentStatusEntity, "id = ?", incidentStatusId) - if result.Error != nil { - return nil, result.Error - } - if result.RowsAffected == 0 { - return nil, nil - } - - return &incidentStatusEntity, nil -} - -func FetchAllIncidentStatus(db *gorm.DB) ([]entity.IncidentStatusEntity, error) { - var incidentStatusEntity []entity.IncidentStatusEntity - result := db.Find(&incidentStatusEntity) - if result.Error != nil { - return nil, result.Error - } - return incidentStatusEntity, nil -} diff --git a/pkg/postgres/query/incidents_tags_data_platform_mapping.go b/pkg/postgres/query/incidents_tags_data_platform_mapping.go deleted file mode 100644 index a0e9724..0000000 --- a/pkg/postgres/query/incidents_tags_data_platform_mapping.go +++ /dev/null @@ -1,52 +0,0 @@ -package query - -import ( - "houston/entity" - "time" - - "go.uber.org/zap" - "gorm.io/gorm" -) - -func FindDataPlataformTagsByIncidentId(incidenttId int, db *gorm.DB, logger *zap.Logger) (*entity.IncidentsTagsDataPlatformMapping, error) { - - var cf entity.IncidentsTagsDataPlatformMapping - result := db.Where("incidents_tags_data_platform_mapping.incident_id = ? AND incidents_tags_data_platform_mapping.deleted_at is NULL", incidenttId).Find(&cf) - if result.Error != nil { - return nil, result.Error - } - if result.RowsAffected == 0 { - return nil, nil - } - return &cf, nil -} - -func UpsertDataPlatformTagToIncidentId(db *gorm.DB, dataPlatform string, incidentId int) error { - dpEntity := &entity.IncidentsTagsDataPlatformMapping{ - DataPlatformTag: dataPlatform, - IncidentId: incidentId, - } - var dp entity.IncidentsTagsDataPlatformMapping - result := db.Where("incident_id = ? and deleted_at is NULL", incidentId).Find(&dp) - if result.Error != nil { - return result.Error - } - if result.RowsAffected == 1 { - dp.DataPlatformTag = dataPlatform - dp.UpdatedAt = time.Now() - addResult := db.Save(dp) - if addResult != nil { - return addResult.Error - } - - return nil - } else if result.RowsAffected == 0 { - addResult := db.Create(dpEntity) - if addResult != nil { - return addResult.Error - } - return nil - } - return gorm.ErrInvalidData - -} diff --git a/pkg/postgres/query/message_color_code.go b/pkg/postgres/query/message_color_code.go deleted file mode 100644 index 98f2e4c..0000000 --- a/pkg/postgres/query/message_color_code.go +++ /dev/null @@ -1,14 +0,0 @@ -package query - -func GetColorBySeverity(severity_id int) (string) { - switch severity_id { - case 1 : - return "#fc3838" - case 2 : - return "#fc9338" - case 3 : - return "#ebfa1b" - default : - return "#7288db" - } -} \ No newline at end of file diff --git a/pkg/postgres/query/messages.go b/pkg/postgres/query/messages.go deleted file mode 100644 index 0a21f93..0000000 --- a/pkg/postgres/query/messages.go +++ /dev/null @@ -1,38 +0,0 @@ -package query - -import ( - "houston/entity" - "houston/model/request" - "time" - - "gorm.io/gorm" -) - -func CreateMessage(db *gorm.DB, request *request.CreateMessage) (error) { - messageEntity := &entity.MessageEntity { - SlackChannel: request.SlackChannel, - MessageTimeStamp: request.MessageTimeStamp, - IncidentName: request.IncidentName, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - Version: 0, - } - - result := db.Create(&messageEntity) - if result.Error != nil { - return result.Error - } - - return nil -} - -func FindMessageByIncidentName(db *gorm.DB, incidentName string) ([]entity.MessageEntity, error) { - var messages []entity.MessageEntity - - result := db.Find(&messages, "incident_name = ?", incidentName) - if result.Error != nil { - return nil, result.Error - } - - return messages, nil -} diff --git a/pkg/postgres/query/severity.go b/pkg/postgres/query/severity.go deleted file mode 100644 index e87fa11..0000000 --- a/pkg/postgres/query/severity.go +++ /dev/null @@ -1,72 +0,0 @@ -package query - -import ( - "houston/entity" - "houston/model/request" - - "go.uber.org/zap" - "gorm.io/gorm" -) - -func FindSeverity(db *gorm.DB, logger *zap.Logger) ([]entity.SeverityEntity, error) { - var severityEntity []entity.SeverityEntity - result := db.Where("deleted_at is NULL").Find(&severityEntity) - if result.Error != nil { - logger.Error("fetching severity query failed", zap.Error(result.Error)) - return nil, result.Error - } - return severityEntity, nil -} - -func FindIncidentSeverityEntity(db *gorm.DB, logger *zap.Logger) ([]entity.SeverityEntity, error) { - var severityEntity []entity.SeverityEntity - - result := db.Where("deleted_at is NULL").Find(&severityEntity) - if result.Error != nil { - logger.Error("fetching severity query failed", zap.Error(result.Error)) - return nil, result.Error - } - - return severityEntity, nil -} - -func FindIncidentSeverityEntityById(db *gorm.DB, logger *zap.Logger, id int) (*entity.SeverityEntity, error) { - var severityEntity entity.SeverityEntity - - result := db.Find(&severityEntity, "id = ?", id) - if result.Error != nil { - logger.Error("fetching severity query failed", zap.Error(result.Error)) - return nil, result.Error - } else if result.RowsAffected == 0 { - logger.Error("SeverityEntity not found", zap.Error(result.Error)) - return nil, nil - } - - return &severityEntity, nil -} - -func AddSeverity(db *gorm.DB, logger *zap.Logger, addSeverityRequest request.AddSeverityRequest) error { - severityEntity := &entity.SeverityEntity{ - Name: addSeverityRequest.Name, - Description: addSeverityRequest.Description, - } - - result := db.Create(severityEntity) - if result.Error != nil { - logger.Error("failed to add severity in the database", zap.Error(result.Error)) - return result.Error - } - - return nil -} - -func FindSeverityById(db *gorm.DB, severityId int) (*entity.SeverityEntity, error) { - var severityEntity entity.SeverityEntity - - result := db.Find(&severityEntity, "id = ?", severityId) - if result.Error != nil { - return nil, result.Error - } - - return &severityEntity, nil -} diff --git a/pkg/postgres/query/tags.go b/pkg/postgres/query/tags.go deleted file mode 100644 index 6d6c5a5..0000000 --- a/pkg/postgres/query/tags.go +++ /dev/null @@ -1,121 +0,0 @@ -package query - -import ( - "houston/entity" - "time" - - "gorm.io/gorm" -) - -func FindTagsByTeamsId(db *gorm.DB, teamsId int) ([]entity.TagsEntity, error) { - var tags []entity.TagsEntity - - result := db.Where("teams_tags_mapping.teams_id = ? AND teams_tags_mapping.deleted_at IS NULL", teamsId).Joins("JOIN teams_tags_mapping ON tags.id = teams_tags_mapping.tag_id").Find(&tags) - - if result.Error != nil { - return nil, result.Error - } - - if result.RowsAffected == 0 { - return nil, nil - } - - return tags, nil -} - -func FindTagsByIncidentId(db *gorm.DB, incidentId int) ([]entity.TagsEntity, error) { - var tags []entity.TagsEntity - - result := db.Where("incidents_tags_mapping.incident_id = ? AND incidents_tags_mapping.deleted_at IS NULL", incidentId).Joins("JOIN incidents_tags_mapping ON incidents_tags_mapping.tag_id = tags.id").Find(&tags) - - if result.Error != nil { - return nil, result.Error - } - - if result.RowsAffected == 0 { - return nil, nil - } - - return tags, nil -} - -func AddTagsIdMappingToTeamsId(db *gorm.DB, tagsId int, teamsId int) error { - teamsTagsMapping := &entity.TeamTagsMapping{ - TeamsId: teamsId, - TagId: tagsId, - } - result := db.Create(teamsTagsMapping) - - if result.Error != nil { - return result.Error - } - return nil - -} - -func FindTagRecordByTeamsIdAndTagsId(db *gorm.DB, tagsId int, teamsId int) (*entity.TagsEntity, error) { - var tagsMapping entity.TagsEntity - - result := db.Where("tagsId = ? AND teamsId = ? AND deleted_at IS NULL", tagsId, teamsId).Joins("JOIN teams_tags_mapping ON tags.id = teams_tags_mapping.tags_id").Find(tagsMapping) - - if result.Error != nil { - return nil, result.Error - } - return &tagsMapping, nil - -} - -func FindTagRecordListByIncidentId(db *gorm.DB, incidenId int) ([]entity.IncidentsTagsMapping, error) { - var incidentsTagsMapping []entity.IncidentsTagsMapping - - result := db.Where("incident_id = ? AND deleted_at IS NULL", incidenId).Find(&incidentsTagsMapping) - - if result.Error != nil { - return nil, result.Error - } - return incidentsTagsMapping, nil - -} - -func AddTagsIdMappingToIncidentId(db *gorm.DB, tagId, incidenId int) error { - var incidentsTagsMapping = &entity.IncidentsTagsMapping{ - IncidentId: incidenId, - TagId: tagId, - } - - result := db.Create(incidentsTagsMapping) - - if result.Error != nil { - return result.Error - } - return nil - -} - -func SetTagsDeletedAt(db *gorm.DB, tagId int, resultId int) error { - - var incidentTags entity.IncidentsTagsMapping - result := db.Model(&incidentTags).Where("tag_id = ? AND incident_id = ?", tagId, resultId).Update("deleted_at", time.Now()) - - if result.Error != nil { - return result.Error - } - return nil - -} - -func FindTagsById(db *gorm.DB, tagId int) (*entity.TagsEntity, error) { - var tagsEntity entity.TagsEntity - - result := db.Where("id = ? AND deleted_at IS NULL", tagId).Find(tagsEntity) - - if result.Error != nil { - return nil, result.Error - } - - if result.RowsAffected == 0 { - return nil, nil - } - return &tagsEntity, nil - -} diff --git a/pkg/postgres/query/teams.go b/pkg/postgres/query/teams.go deleted file mode 100644 index d512c8c..0000000 --- a/pkg/postgres/query/teams.go +++ /dev/null @@ -1,66 +0,0 @@ -package query - -import ( - "houston/entity" - "houston/model/request" - - "gorm.io/gorm" -) - -func FindTeam(db *gorm.DB) ([]entity.TeamEntity, error) { - var teamEntity []entity.TeamEntity - - result := db.Find(&teamEntity, "active = ?", true) - if result.Error != nil { - return nil, result.Error - } - return teamEntity, nil -} - -func FindTeamList(db *gorm.DB) ([]entity.TeamEntity, error) { - var teamEntity []entity.TeamEntity - result := db.Find(&teamEntity, "active = ?", true) - if result.Error != nil { - return nil, result.Error - } - - return teamEntity, nil -} - -func FindTeamByName(db *gorm.DB, name string) (*entity.TeamEntity, error) { - var teamEntity entity.TeamEntity - result := db.Find(&teamEntity, "name = ?", name) - if result.Error != nil { - return nil, result.Error - } - - return &teamEntity, nil -} - -func AddTeam(db *gorm.DB, addTeamRequest request.AddTeamRequest) error { - teamEntity := &entity.TeamEntity{ - Name: addTeamRequest.Name, - OncallHandle: addTeamRequest.OncallHandle, - } - - result := db.Create(teamEntity) - if result.Error != nil { - return result.Error - } - - return nil -} - -func FindTeamById(db *gorm.DB, teamId int) (*entity.TeamEntity, error) { - var teamEntity entity.TeamEntity - - result := db.Find(&teamEntity, "id = ?", teamId) - if result.Error != nil { - return nil, result.Error - } else if result.RowsAffected == 0 { - return nil, nil - - } - - return &teamEntity, nil -} diff --git a/pkg/postgres/query/users.go b/pkg/postgres/query/users.go deleted file mode 100644 index 77d074c..0000000 --- a/pkg/postgres/query/users.go +++ /dev/null @@ -1,35 +0,0 @@ -package query - -import ( - "houston/entity" - - "gorm.io/gorm" -) - -func FindDefaultUserIdToBeAddedBySeverity(db *gorm.DB, severityId int) ([]string, error) { - userIds := make([]string, 0) - var user []entity.UsersEntity - result := db.Where("users.active = true And teams_severity_user_mapping.deleted_at is NULL AND teams_severity_user_mapping.default_add_in_incidents = ? AND teams_severity_user_mapping.entity_type = ? AND teams_severity_user_mapping.entity_id = ?", true, entity.SEVERITY, severityId).Joins("JOIN teams_severity_user_mapping on teams_severity_user_mapping.users_id = users.id").Find(&user) - if result.Error != nil { - return nil, result.Error - } - for _, users := range user { - userIds = append(userIds, users.SlackUserId) - } - - return userIds, nil -} - -func FindDefaultUserIdToBeAddedByTeam(db *gorm.DB, teamId int) ([]string, error) { - userIds := make([]string, 0) - var user []entity.UsersEntity - result := db.Where("users.active = true And teams_severity_user_mapping.deleted_at is NULL AND teams_severity_user_mapping.default_add_in_incidents = ? AND teams_severity_user_mapping.entity_type = ? AND teams_severity_user_mapping.entity_id = ?", true, entity.TEAM, teamId).Joins("JOIN teams_severity_user_mapping on teams_severity_user_mapping.users_id = users.id").Find(&user) - if result.Error != nil { - return nil, result.Error - } - for _, users := range user { - userIds = append(userIds, users.SlackUserId) - } - - return userIds, nil -} diff --git a/pkg/postgres/service/incident/entity.go b/pkg/postgres/service/incident/entity.go new file mode 100644 index 0000000..eb900c4 --- /dev/null +++ b/pkg/postgres/service/incident/entity.go @@ -0,0 +1,102 @@ +package incident + +import ( + "github.com/lib/pq" + "time" + + "gorm.io/gorm" +) + +type IncidentStatus string + +const ( + Investigating IncidentStatus = "Investigating" + Identified = "Identified" + Monitoring = "Monitoring" + Resolved = "Resolved" + Duplicated = "Duplicated" +) + +const ( + Retrospective IncidentRole = "Retrospective" + Responder = "Responder" + ServiceOwner = "Service Owner" +) + +type IncidentRole string + +// 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"` +} + +func (IncidentEntity) TableName() string { + return "incident" +} + +// IncidentRoleEntity this table will contain the role of the people assigned to the incident, mapping between incident +// and incident role entity will be one to many +type IncidentRoleEntity struct { + gorm.Model + IncidentId int `gorm:"column:incident_id"` + Role IncidentRole `gorm:"column:role"` + AssignedTo string `gorm:"column:assigned_to"` + AssignedBy string `gorm:"column:assigned_by"` +} + +func (IncidentRoleEntity) TableName() string { + return "incident_role" +} + +// IncidentChannelEntity contains the channels where the incident is being tracked +type IncidentChannelEntity struct { + gorm.Model + IncidentId uint `gorm:"column:incident_id"` + SlackChannel string `gorm:"column:slack_channel"` + MessageTimeStamp string `gorm:"column:message_timestamp"` +} + +func (IncidentChannelEntity) TableName() string { + return "incident_channel" +} + +// IncidentStatusEntity contains the possible incident statuses +type IncidentStatusEntity struct { + gorm.Model + Name string `gorm:"column:name"` + Description string `gorm:"column:description"` + IsTerminalStatus bool `gorm:"column:is_terminal_status"` +} + +func (IncidentStatusEntity) TableName() string { + return "incident_status" +} + +type IncidentTagEntity struct { + gorm.Model + IncidentId uint `gorm:"column:incident_id"` + TagId uint `gorm:"column:tag_id"` + TagValueIds pq.Int32Array `gorm:"column:tag_value_ids;type:integer[]"` + FreeTextValue *string `gorm:"column:free_text_value"` +} + +func (IncidentTagEntity) TableName() string { + return "incident_tag" +} diff --git a/pkg/postgres/service/incident/incident.go b/pkg/postgres/service/incident/incident.go new file mode 100644 index 0000000..60f8082 --- /dev/null +++ b/pkg/postgres/service/incident/incident.go @@ -0,0 +1,294 @@ +package incident + +import ( + "fmt" + "go.uber.org/zap" + "gorm.io/gorm" + "houston/pkg/postgres/service/severity" + "strconv" + "time" +) + +type Service struct { + logger *zap.Logger + gormClient *gorm.DB + severityService *severity.Service +} + +func NewIncidentService(logger *zap.Logger, gormClient *gorm.DB, severityService *severity.Service) *Service { + return &Service{ + logger: logger, + gormClient: gormClient, + severityService: severityService, + } +} + +func (s *Service) CreateIncident(request *CreateIncidentRequest) (*IncidentEntity, error) { + severityId, err := strconv.Atoi(request.Severity) + if err != nil { + return nil, fmt.Errorf("fetch channel conversationInfo failed. err: %v", err) + } + severity, err := s.severityService.FindSeverityById(uint(severityId)) + if err != nil { + return nil, fmt.Errorf("fetch FindSeverityById failed. err: %v", err) + } + + teamId, _ := strconv.Atoi(request.TeamId) + incidentStatusEntity, _ := s.GetIncidentStatusByStatusName(string(request.Status)) + + incidentEntity := &IncidentEntity{ + Title: request.Title, + Description: request.Description, + Status: incidentStatusEntity.ID, + SeverityId: uint(severityId), + DetectionTime: request.DetectionTime, + StartTime: request.StartTime, + TeamId: uint(teamId), + EnableReminder: request.EnableReminder, + SeverityTat: time.Now().AddDate(0, 0, severity.Sla), + CreatedBy: request.CreatedBy, + UpdatedBy: request.UpdatedBy, + } + + result := s.gormClient.Create(incidentEntity) + if result.Error != nil { + return nil, result.Error + } + + return incidentEntity, nil +} + +func (s *Service) UpdateIncident(incidentEntity *IncidentEntity) error { + result := s.gormClient.Updates(incidentEntity) + if result.Error != nil { + return result.Error + } + + return nil +} + +func (s *Service) GetIncidentStatusByStatusName(status string) (*IncidentStatusEntity, error) { + var incidentStatus IncidentStatusEntity + + result := s.gormClient.Find(&incidentStatus, "name = ?", status) + if result.Error != nil { + return nil, result.Error + } + if result.RowsAffected == 0 { + return nil, nil + } + + return &incidentStatus, nil +} + +func (s *Service) GetOpenIncidents(limit int) (*[]IncidentSeverityTeamDTO, error) { + var incidentSeverityTeamDTO []IncidentSeverityTeamDTO + + result := s.gormClient.Raw(` + select i.title, ins.name as status, i.slack_channel, s.name as severity_name, t.name as team_name + from incident i + inner join severity s on s.id = i.severity_id + inner join team t on t.id = i.team_id + inner join incident_status ins on ins.id = i.status + where ins.name <> ? and i.deleted_at is null + limit ? + `, Resolved, limit).Scan(&incidentSeverityTeamDTO) + + if result.Error != nil { + return nil, result.Error + } + if result.RowsAffected == 0 { + return nil, nil + } + + return &incidentSeverityTeamDTO, nil +} + +func (s *Service) FindIncidentByChannelId(channelId string) (*IncidentEntity, error) { + var incidentEntity IncidentEntity + + result := s.gormClient.Find(&incidentEntity, "slack_channel = ?", channelId) + if result.Error != nil { + return nil, result.Error + } + + if result.RowsAffected == 0 { + return nil, nil + } + + return &incidentEntity, nil +} + +func (s *Service) FindIncidentSeverityTeamJoin(slackChannelId string) (*IncidentSeverityTeamDTO, error) { + var incidentSeverityTeamJoinEntity IncidentSeverityTeamDTO + + result := s.gormClient.Table("incident"). + Where("incident.slack_channel = ? and incident.deleted_at IS NULL", slackChannelId). + Joins("JOIN severity ON severity.id = incident.severity_id"). + Joins("JOIN team ON team.id = incident.team_id"). + Joins("JOIN incident_status on incident.status = incident_status.id"). + Select("incident.title, incident_status.name as status, incident.slack_channel, severity.name as severity_name ,team.name as team_name"). + Scan(&incidentSeverityTeamJoinEntity) + + if result.Error != nil { + return nil, result.Error + } + if result.RowsAffected == 0 { + return nil, nil + } + + return &incidentSeverityTeamJoinEntity, nil +} + +func (s *Service) UpsertIncidentRole(addIncidentRoleRequest *AddIncidentRoleRequest) error { + incidentRolesEntity := &IncidentRoleEntity{ + IncidentId: addIncidentRoleRequest.IncidentId, + Role: addIncidentRoleRequest.Role, + AssignedTo: addIncidentRoleRequest.UserId, + AssignedBy: addIncidentRoleRequest.CreatedById, + } + var incidentRole IncidentRoleEntity + + result := s.gormClient.Find(&incidentRole, "incident_id = ? AND role = ?", addIncidentRoleRequest.IncidentId, addIncidentRoleRequest.Role) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 1 { + incidentRolesEntity.ID = incidentRole.ID + incidentRolesEntity.CreatedAt = incidentRole.CreatedAt + incidentRolesEntity.UpdatedAt = time.Now() + addResult := s.gormClient.Save(incidentRolesEntity) + if addResult != nil { + return addResult.Error + } + return nil + + } else if result.RowsAffected == 0 { + addResult := s.gormClient.Create(incidentRolesEntity) + if addResult != nil { + return addResult.Error + } + return nil + } + return gorm.ErrInvalidData +} + +func (s *Service) FindIncidentStatusById(incidentStatusId uint) (*IncidentStatusEntity, error) { + var incidentStatusEntity IncidentStatusEntity + + result := s.gormClient.Find(&incidentStatusEntity, "id = ?", incidentStatusId) + if result.Error != nil { + return nil, result.Error + } + if result.RowsAffected == 0 { + return nil, nil + } + + return &incidentStatusEntity, nil +} + +func (s *Service) FindIncidentStatusByName(name string) (*IncidentStatusEntity, error) { + var incidentStatusEntity IncidentStatusEntity + + result := s.gormClient.Find(&incidentStatusEntity, "name = ?", name) + if result.Error != nil { + return nil, result.Error + } + if result.RowsAffected == 0 { + return nil, nil + } + + return &incidentStatusEntity, nil +} + +func (s *Service) FetchAllIncidentStatuses() (*[]IncidentStatusEntity, error) { + var incidentStatusEntity []IncidentStatusEntity + result := s.gormClient.Find(&incidentStatusEntity) + if result.Error != nil { + return nil, result.Error + } + + if result.RowsAffected == 0 { + return nil, nil + } + + return &incidentStatusEntity, nil +} + +func (s *Service) CreateIncidentChannelEntry(request *CreateIncidentChannelEntry) error { + messageEntity := &IncidentChannelEntity{ + SlackChannel: request.SlackChannel, + MessageTimeStamp: request.MessageTimeStamp, + IncidentId: request.IncidentId, + } + + result := s.gormClient.Create(&messageEntity) + if result.Error != nil { + return result.Error + } + + return nil +} + +func (s *Service) CreateIncidentTag(incidentId, tagId uint) (*IncidentTagEntity, error) { + incidentTag := IncidentTagEntity{ + IncidentId: incidentId, + TagId: tagId, + } + + result := s.gormClient.Create(&incidentTag) + if result.Error != nil { + return nil, result.Error + } + return &incidentTag, nil +} + +func (s *Service) GetIncidentChannels(incidentId uint) (*[]IncidentChannelEntity, error) { + var incidentChannels []IncidentChannelEntity + + result := s.gormClient.Find(&incidentChannels, "incident_id = ?", incidentId) + if result.Error != nil { + return nil, result.Error + } + + if result.RowsAffected == 0 { + return nil, nil + } + + return &incidentChannels, nil +} + +func (s *Service) GetIncidentTagsByIncidentId(incidentId uint) (*[]IncidentTagEntity, error) { + var incidentTags []IncidentTagEntity + + result := s.gormClient.Find(&incidentTags, "incident_id = ?", incidentId) + if result.Error != nil { + return nil, result.Error + } + if result.RowsAffected == 0 { + return nil, nil + } + + return &incidentTags, nil +} + +func (s *Service) GetIncidentTagByTagId(incidentId uint, tagId uint) (*IncidentTagEntity, error) { + var incidentTag IncidentTagEntity + + result := s.gormClient.Find(&incidentTag, "incident_id = ? and tag_id = ?", incidentId, tagId) + if result.Error != nil { + return nil, result.Error + } else if result.RowsAffected == 0 { + return nil, nil + } + + return &incidentTag, nil +} + +func (s *Service) SaveIncidentTag(entity IncidentTagEntity) (*IncidentTagEntity, error) { + tx := s.gormClient.Save(&entity) + if tx.Error != nil { + return nil, tx.Error + } + return &entity, nil +} diff --git a/pkg/postgres/service/incident/model.go b/pkg/postgres/service/incident/model.go new file mode 100644 index 0000000..4231032 --- /dev/null +++ b/pkg/postgres/service/incident/model.go @@ -0,0 +1,52 @@ +package incident + +import ( + "time" +) + +type CreateIncidentRequest struct { + Title string `json:"title,omitempty"` + Description string `json:"description,omitempty"` + Severity string `json:"severity,omitempty"` + Pagerduty string `json:"pagerduty,omitempty"` + Status IncidentStatus `json:"status,omitempty"` + IncidentName string `json:"incident_name,omitempty"` + SlackChannel string `json:"slack_channel,omitempty"` + DetectionTime *time.Time `json:"detection_time,omitempty"` + StartTime time.Time `json:"start_time,omitempty"` + EndTime *time.Time `json:"end_time,omitempty"` + TeamId string `json:"type,omitempty"` + JiraId *string `json:"jira_id,omitempty"` + ConfluenceId *string `json:"confluence_id,omitempty"` + SeverityTat *time.Time `json:"severity_tat,omitempty"` + RemindMeAt *time.Time `json:"remind_me_at,omitempty"` + EnableReminder bool `json:"enable_reminder,omitempty"` + CreatedBy string `json:"created_by,omitempty"` + UpdatedBy string `json:"updated_by,omitempty"` +} + +type IncidentSeverityTeamDTO struct { + Title string + Status IncidentStatus + SeverityName string + SlackChannel string + TeamName string +} + +type AddIncidentRoleRequest struct { + UserId string `json:"users_select,omitempty"` + Role IncidentRole `json:"role_type,omitempty"` + IncidentId int + CreatedById string +} + +type CreateIncidentChannelEntry struct { + SlackChannel string `gorm:"column:slack_channel"` + IncidentId uint `gorm:"column:incident_id"` + MessageTimeStamp string `gorm:"column:message_timestamp"` +} + +type AddIncidentStatusRequest struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` +} diff --git a/pkg/postgres/service/role/entity.go b/pkg/postgres/service/role/entity.go new file mode 100644 index 0000000..821bbde --- /dev/null +++ b/pkg/postgres/service/role/entity.go @@ -0,0 +1,12 @@ +package role + +import "gorm.io/gorm" + +type RoleEntity struct { + gorm.Model + name string `gorm:"column:name"` +} + +func (RoleEntity) TableName() string { + return "role" +} diff --git a/pkg/postgres/service/role/role.go b/pkg/postgres/service/role/role.go new file mode 100644 index 0000000..99388dd --- /dev/null +++ b/pkg/postgres/service/role/role.go @@ -0,0 +1,34 @@ +package role + +import ( + "fmt" + "go.uber.org/zap" + "gorm.io/gorm" +) + +type Service struct { + logger *zap.Logger + gormClient *gorm.DB +} + +func NewRoleRepository(logger *zap.Logger, gormClient *gorm.DB) *Service { + return &Service{ + logger: logger, + gormClient: gormClient, + } +} + +func (r *Service) FindRoleById(id int) (*RoleEntity, error) { + role := RoleEntity{} + tx := r.gormClient.Raw("select * from role where id = ?", id).Scan(&role) + + if tx.Error != nil { + r.logger.Error("Error while getting role", zap.String("exception", fmt.Sprintf("%v", tx.Error))) + return nil, tx.Error + } + if tx.RowsAffected == 0 { + return nil, nil + } + + return &role, nil +} diff --git a/pkg/postgres/service/severity/entity.go b/pkg/postgres/service/severity/entity.go new file mode 100644 index 0000000..37d14e6 --- /dev/null +++ b/pkg/postgres/service/severity/entity.go @@ -0,0 +1,18 @@ +package severity + +import ( + "github.com/lib/pq" + "gorm.io/gorm" +) + +type SeverityEntity struct { + gorm.Model + Name string `gorm:"column:name"` + Description string `gorm:"column:description"` + Sla int `gorm:"column:sla"` + SlackUserIds pq.StringArray `gorm:"column:slack_user_ids;type:string[]"` +} + +func (SeverityEntity) TableName() string { + return "severity" +} diff --git a/pkg/postgres/service/severity/severity.go b/pkg/postgres/service/severity/severity.go new file mode 100644 index 0000000..32905cf --- /dev/null +++ b/pkg/postgres/service/severity/severity.go @@ -0,0 +1,62 @@ +package severity + +import ( + "go.uber.org/zap" + "gorm.io/gorm" +) + +type Service struct { + logger *zap.Logger + gormClient *gorm.DB +} + +func NewSeverityService(logger *zap.Logger, gormClient *gorm.DB) *Service { + return &Service{ + logger: logger, + gormClient: gormClient, + } +} + +func (s *Service) GetAllActiveSeverity() (*[]SeverityEntity, error) { + var severityEntity []SeverityEntity + result := s.gormClient.Where("deleted_at is NULL").Find(&severityEntity) + if result.Error != nil { + s.logger.Error("fetching severity query failed", zap.Error(result.Error)) + return nil, result.Error + } + if result.RowsAffected == 0 { + return nil, nil + } + + return &severityEntity, nil +} + +func (s *Service) FindIncidentSeverityEntityById(id int) (*SeverityEntity, error) { + var severityEntity SeverityEntity + + result := s.gormClient.Find(&severityEntity, "id = ?", id) + if result.Error != nil { + s.logger.Error("fetching severity query failed", zap.Error(result.Error)) + return nil, result.Error + } else if result.RowsAffected == 0 { + s.logger.Error("SeverityEntity not found", zap.Error(result.Error)) + return nil, nil + } + + return &severityEntity, nil +} + +func (s *Service) FindSeverityById(severityId uint) (*SeverityEntity, error) { + var severityEntity SeverityEntity + + result := s.gormClient.Find(&severityEntity, "id = ?", severityId) + if result.Error != nil { + return nil, result.Error + } + + if result.RowsAffected == 0 { + return nil, nil + } + + return &severityEntity, nil +} diff --git a/pkg/postgres/service/tag/entity.go b/pkg/postgres/service/tag/entity.go new file mode 100644 index 0000000..294dcec --- /dev/null +++ b/pkg/postgres/service/tag/entity.go @@ -0,0 +1,34 @@ +package tag + +import "gorm.io/gorm" + +type Type string + +const ( + SingleValue Type = "single_value" + MultiValue = "multi_value" + FreeText = "free_text" +) + +type TagEntity struct { + gorm.Model + Name string `gorm:"column:name"` + Label string `gorm:"column:label"` + PlaceHolder string `gorm:"column:place_holder"` + ActionId string `gorm:"column:action_id"` + Type Type `gorm:"column:type"` +} + +func (TagEntity) TableName() string { + return "tag" +} + +type TagValueEntity struct { + gorm.Model + TagId uint `gorm:"column:tag_id"` + Value string `gorm:"column:value"` +} + +func (TagValueEntity) TableName() string { + return "tag_value" +} diff --git a/pkg/postgres/service/tag/model.go b/pkg/postgres/service/tag/model.go new file mode 100644 index 0000000..e3b2d05 --- /dev/null +++ b/pkg/postgres/service/tag/model.go @@ -0,0 +1,11 @@ +package tag + +type TagDTO struct { + Id uint + Name string + Type Type + Label string + PlaceHolder string + ActionId string + Optional bool +} diff --git a/pkg/postgres/service/tag/tag.go b/pkg/postgres/service/tag/tag.go new file mode 100644 index 0000000..41a9aa4 --- /dev/null +++ b/pkg/postgres/service/tag/tag.go @@ -0,0 +1,71 @@ +package tag + +import ( + "go.uber.org/zap" + "gorm.io/gorm" +) + +type Service struct { + logger *zap.Logger + gormClient *gorm.DB +} + +func NewTagService(logger *zap.Logger, gormClient *gorm.DB) *Service { + return &Service{ + logger: logger, + gormClient: gormClient, + } +} + +func (t *Service) FindById(id uint) (*TagEntity, error) { + tag := TagEntity{} + tx := t.gormClient.Raw("select * from tag where id = ?", id).Scan(&tag) + + if tx.Error != nil { + return nil, tx.Error + } + if tx.RowsAffected == 0 { + return nil, nil + } + + return &tag, nil +} + +func (t *Service) FindTagValuesByTagId(id uint) (*[]TagValueEntity, error) { + var tagValues []TagValueEntity + tx := t.gormClient.Raw("select tv.* from tag_value tv inner join tag t on t.id = tv.tag_id where t.id = ?", id).Scan(&tagValues) + + if tx.Error != nil { + return nil, tx.Error + } + if tx.RowsAffected == 0 { + return nil, nil + } + return &tagValues, nil +} + +func (t *Service) FindTagValuesByIds(ids []int32) (*[]TagValueEntity, error) { + var tagValues []TagValueEntity + tx := t.gormClient.Raw("select * from tag_value where id in ?", ids).Scan(&tagValues) + + if tx.Error != nil { + return nil, tx.Error + } + if tx.RowsAffected == 0 { + return nil, nil + } + return &tagValues, nil +} + +func (t *Service) FindTagsByTeamId(teamId uint) (*[]TagDTO, error) { + var tags []TagDTO + tx := t.gormClient.Raw("select t.*, tt.optional from tag t inner join team_tag tt on t.id = tt.tag_id where tt.team_id = ?", teamId).Scan(&tags) + + if tx.Error != nil { + return nil, tx.Error + } + if tx.RowsAffected == 0 { + return nil, nil + } + return &tags, nil +} diff --git a/pkg/postgres/service/team/entity.go b/pkg/postgres/service/team/entity.go new file mode 100644 index 0000000..14cef70 --- /dev/null +++ b/pkg/postgres/service/team/entity.go @@ -0,0 +1,28 @@ +package team + +import ( + "github.com/lib/pq" + "gorm.io/gorm" +) + +type TeamEntity struct { + gorm.Model + Name string `gorm:"column:name"` + SlackUserIds pq.StringArray `gorm:"column:slack_user_ids;type:string[]"` + Active bool `gorm:"column:active"` +} + +func (TeamEntity) TableName() string { + return "team" +} + +type TeamTagEntity struct { + gorm.Model + TeamId int `gorm:"column:team_id"` + TagId int `gorm:"column:tag_id"` + Optional bool `gorm:"column:optional"` +} + +func (TeamTagEntity) TableName() string { + return "team_tag" +} diff --git a/pkg/postgres/service/team/team.go b/pkg/postgres/service/team/team.go new file mode 100644 index 0000000..0149263 --- /dev/null +++ b/pkg/postgres/service/team/team.go @@ -0,0 +1,43 @@ +package team + +import ( + "go.uber.org/zap" + "gorm.io/gorm" +) + +type Service struct { + logger *zap.Logger + gormClient *gorm.DB +} + +func NewTeamService(logger *zap.Logger, gormClient *gorm.DB) *Service { + return &Service{ + logger: logger, + gormClient: gormClient, + } +} + +func (s *Service) GetAllActiveTeams() (*[]TeamEntity, error) { + var teamEntity []TeamEntity + + result := s.gormClient.Find(&teamEntity, "active = ?", true) + if result.Error != nil { + return nil, result.Error + } else if result.RowsAffected == 0 { + return nil, nil + } + return &teamEntity, nil +} + +func (s *Service) FindTeamById(teamId uint) (*TeamEntity, error) { + var teamEntity TeamEntity + + result := s.gormClient.Find(&teamEntity, "id = ?", teamId) + if result.Error != nil { + return nil, result.Error + } else if result.RowsAffected == 0 { + return nil, nil + } + + return &teamEntity, nil +} diff --git a/pkg/postgres/service/user/model.go b/pkg/postgres/service/user/model.go new file mode 100644 index 0000000..f91a62e --- /dev/null +++ b/pkg/postgres/service/user/model.go @@ -0,0 +1,14 @@ +package user + +import "gorm.io/gorm" + +type UserEntity struct { + gorm.Model + Name string + SlackUserId string + Active bool +} + +func (UserEntity) TableName() string { + return "houston_user" +} diff --git a/pkg/postgres/service/user/user.go b/pkg/postgres/service/user/user.go new file mode 100644 index 0000000..a00006b --- /dev/null +++ b/pkg/postgres/service/user/user.go @@ -0,0 +1 @@ +package user diff --git a/pkg/slack/common/channel.go b/pkg/slack/common/channel.go deleted file mode 100644 index cca95ca..0000000 --- a/pkg/slack/common/channel.go +++ /dev/null @@ -1,49 +0,0 @@ -package common - -import ( - "fmt" - - "github.com/slack-go/slack" - "github.com/slack-go/slack/socketmode" - "go.uber.org/zap" -) - -func FindParticipants(client *socketmode.Client, logger *zap.Logger, channelId string) ([]string, error) { - request := &slack.GetUsersInConversationParameters{ - ChannelID: channelId, - Limit: 1000, - } - channelInfo, _, err := client.GetUsersInConversation(request) - if err != nil { - return nil, fmt.Errorf("fetch channel conversationInfo failed. err: %v", err) - } - - return channelInfo, nil -} - -func CreateChannel(client *socketmode.Client, logger *zap.Logger, channelName string) (string, error) { - request := slack.CreateConversationParams{ - ChannelName: channelName, - IsPrivate: false, - } - - channel, err := client.CreateConversation(request) - if err != nil { - logger.Error("create slack channel failed", zap.String("channel_name", channelName), zap.Error(err)) - return "", err - } - - logger.Info("created slack channel successfully", zap.String("channel_name", channelName), zap.String("channel_id", channel.ID)) - return channel.ID, nil -} - -func InviteUsersToConversation(client *socketmode.Client, logger *zap.Logger, channelId string, userId ...string) { - _, err := client.InviteUsersToConversation(channelId, userId...) - if err != nil { - logger.Error("invite users to conversation failed", - zap.String("channel_id", channelId), zap.Any("user_ids", userId), zap.Error(err)) - return - } - - logger.Info("successfully invite users to conversation", zap.String("channel_id", channelId), zap.Any("user_ids", userId)) -} diff --git a/pkg/slack/config.go b/pkg/slack/config.go deleted file mode 100644 index 578351f..0000000 --- a/pkg/slack/config.go +++ /dev/null @@ -1 +0,0 @@ -package slack diff --git a/pkg/slack/houston/blazeless_main_command.go b/pkg/slack/houston/blazeless_main_command.go deleted file mode 100644 index a5658cb..0000000 --- a/pkg/slack/houston/blazeless_main_command.go +++ /dev/null @@ -1,38 +0,0 @@ -package houston - -import ( - "houston/pkg/postgres/query" - houston "houston/pkg/slack/houston/design" - - "github.com/slack-go/slack" - "github.com/slack-go/slack/socketmode" - "go.uber.org/zap" - "gorm.io/gorm" -) - -func HoustonMainCommand(db *gorm.DB, client *socketmode.Client, logger *zap.Logger, evt *socketmode.Event) { - cmd, ok := evt.Data.(slack.SlashCommand) - logger.Info("processing houston command", zap.Any("payload", cmd)) - if !ok { - logger.Error("event data to slash command conversion failed", zap.Any("data", evt)) - return - } - - //TODO - DOES NOT THROW ERROR IF SAME SLACK ID IS PRESENT MULTIPLE TIMES - result, err := query.FindIncidentByChannelId(db, cmd.ChannelID) - if err != nil { - logger.Error("FindIncidentBySlackChannelId errors", - zap.String("channel_id", cmd.ChannelID), zap.String("channel", cmd.ChannelName), - zap.String("user_id", cmd.UserID), zap.Error(err)) - return - } - - if result != nil { - logger.Info("Result", zap.String("result", result.IncidentName)) - payload := houston.OptionsBlock() - client.Ack(*evt.Request, payload) - } - - payload := houston.ButtonDesign() - client.Ack(*evt.Request, payload) -} diff --git a/pkg/slack/houston/command/incident_show_tags.go b/pkg/slack/houston/command/incident_show_tags.go deleted file mode 100644 index dce07e6..0000000 --- a/pkg/slack/houston/command/incident_show_tags.go +++ /dev/null @@ -1,108 +0,0 @@ -package command - -import ( - "fmt" - "houston/pkg/postgres/query" - - "github.com/slack-go/slack" - "github.com/slack-go/slack/socketmode" - "go.uber.org/zap" - "gorm.io/gorm" -) - -type incidenShowTagsProcessor struct { - client *socketmode.Client - db *gorm.DB - logger *zap.Logger -} - -func NewIncidentShowTagsProcessor(client *socketmode.Client, db *gorm.DB, logger *zap.Logger) *incidenShowTagsProcessor { - return &incidenShowTagsProcessor{ - client: client, - db: db, - logger: logger, - } -} - -func (isp *incidenShowTagsProcessor) IncidentShowTagsRequestProcess(callback slack.InteractionCallback, request *socketmode.Request) { - result, err := query.FindIncidentSeverityTeamJoin(isp.db, callback.Channel.ID) - if err != nil { - isp.logger.Error("IncidentShowTagsRequestProcess error", - zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name), - zap.String("user_id", callback.User.ID), zap.Error(err)) - return - } else if result == nil { - isp.logger.Error("IncidentSeverityTeamJoin Not found", - zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name), - zap.String("user_id", callback.User.ID), zap.Error(err)) - return - } - - savedContributingFactor, err := query.FindContributingFactorsByIncidentId(isp.db, result.IncidentId) - if err != nil { - isp.logger.Error("FindContributingFactorsByIncidentId error", - zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name), - zap.String("user_id", callback.User.ID), zap.Error(err)) - return - } - cfString := "No tags" - if savedContributingFactor != nil { - cfString = savedContributingFactor.Label - } - - savedCustomerTags, err := query.FindCustomerTagsByIncidentId(isp.logger, isp.db, result.IncidentId) - if err != nil { - isp.logger.Error("FindCustomerTagsByIncidentId error", - zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name), - zap.String("user_id", callback.User.ID), zap.Error(err)) - return - } - - var customerTagString string - if len(savedCustomerTags) == 0 { - customerTagString = "No tags" - } else { - for _, o := range savedCustomerTags { - customerTagString = customerTagString + " " + o.Label - } - } - - savedTags, err := query.FindTagsByIncidentId(isp.db, result.IncidentId) - if err != nil { - isp.logger.Error("FindTagsByIncidentId error", - zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name), - zap.String("user_id", callback.User.ID), zap.Error(err)) - return - } - - var tagString string - if len(savedTags) == 0 { - tagString = "No tags" - } else { - for _, o := range savedTags { - tagString = tagString + " " + o.Label - } - } - - savedDp, err := query.FindDataPlataformTagsByIncidentId(result.IncidentId, isp.db, isp.logger) - if err != nil { - isp.logger.Error("FindDataPlataformTagsByIncidentId error", - zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name), - zap.String("user_id", callback.User.ID), zap.Error(err)) - return - } - var dpString string = "No tags" - if savedDp != nil && (len(savedDp.DataPlatformTag)) != 0 { - dpString = savedDp.DataPlatformTag - } - - msgOption := slack.MsgOptionText(fmt.Sprintf("\n\nCONTRIBUTING-FACTORS: \n %s \n\n CUSTOMER: \n %s \n\n CUSTOMER:%s \n %s \n\n DATA-PLATFORM: \n %s", cfString, customerTagString, result.TeamsName, tagString, dpString), true) - _, errMessage := isp.client.PostEphemeral(callback.Channel.ID, callback.User.ID, msgOption) - if errMessage != nil { - isp.logger.Error("PostEphemeralmresponse failed for IncidentShowTagsRequestProcess", zap.Error(errMessage)) - return - } - var payload interface{} - isp.client.Ack(*request, payload) - -} diff --git a/pkg/slack/houston/command/incident_update_tags.go b/pkg/slack/houston/command/incident_update_tags.go deleted file mode 100644 index 99b01a1..0000000 --- a/pkg/slack/houston/command/incident_update_tags.go +++ /dev/null @@ -1,315 +0,0 @@ -package command - -import ( - "houston/entity" - "houston/pkg/postgres/query" - houston "houston/pkg/slack/houston/design" - "strconv" - - "github.com/slack-go/slack" - "github.com/slack-go/slack/socketmode" - "go.uber.org/zap" - "gorm.io/gorm" -) - -type incidentUpdateTagsProcessor struct { - client *socketmode.Client - db *gorm.DB - logger *zap.Logger -} - -func NewIncidentUpdateTagsProcessor(client *socketmode.Client, db *gorm.DB, logger *zap.Logger) *incidentUpdateTagsProcessor { - return &incidentUpdateTagsProcessor{ - client: client, - db: db, - logger: logger, - } -} - -func (idp *incidentUpdateTagsProcessor) IncidentUpdateTagsRequestProcess(callback slack.InteractionCallback, request *socketmode.Request) { - result, err := query.FindIncidentSeverityTeamJoin(idp.db, callback.Channel.ID) - if err != nil { - idp.logger.Error("FindIncidentSeverityTeamJoin error", - zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name), - zap.String("user_id", callback.User.ID), zap.Error(err)) - return - } else if result == nil { - idp.logger.Error("IncidentSeverityTeamJoin Not found", - zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name), - zap.String("user_id", callback.User.ID), zap.Error(err)) - return - } - tags, err := query.FindTagsByTeamsId(idp.db, result.TeamsId) - if err != nil { - idp.logger.Error("FindTagsByTeamsId error", - zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name), - zap.String("user_id", callback.User.ID), zap.Error(err)) - return - } - incidentTags, err := query.FindTagsByIncidentId(idp.db, result.IncidentId) - if err != nil { - idp.logger.Error("FindTagsByIncidentId error", - zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name), - zap.String("user_id", callback.User.ID), zap.Error(err)) - return - } - - cf, savedCf, err := findContributingFactors(result.IncidentId, idp.db, idp.logger) - if err != nil { - idp.logger.Error("findContrxibutingFactors error", - zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name), - zap.String("user_id", callback.User.ID), zap.Error(err)) - return - } - - customerTags, savedCustomrTags, err := findCustomerTags(result.IncidentId, idp.db, idp.logger) - if err != nil { - idp.logger.Error("findCustomerTags error", - zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name), - zap.String("user_id", callback.User.ID), zap.Error(err)) - return - } - - savedDataPlatformTags, err := query.FindDataPlataformTagsByIncidentId(result.IncidentId, idp.db, idp.logger) - if err != nil { - idp.logger.Error("findDataPlataformTagsByIncidentInt error", - zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name), - zap.String("user_id", callback.User.ID), zap.Error(err)) - return - } - - modalRequest := houston.BuildIncidentUpdateTagModal(callback.Channel, tags, result.TeamsName, incidentTags, savedCf, cf, savedCustomrTags, customerTags, savedDataPlatformTags) - - _, err = idp.client.OpenView(callback.TriggerID, modalRequest) - if err != nil { - idp.logger.Error("houston slack openview command for IncidentUpdateTagsRequestProcess failed.", - zap.String("trigger_id", callback.TriggerID), zap.String("channel_id", callback.Channel.ID), zap.Error(err)) - return - } - var payload interface{} - idp.client.Ack(*request, payload) -} - -func (itp *incidentUpdateTagsProcessor) IncidentUpdateTags(callback slack.InteractionCallback, request *socketmode.Request, channel slack.Channel, user slack.User) { - result, err := query.FindIncidentSeverityTeamJoin(itp.db, callback.View.PrivateMetadata) - if err != nil { - itp.logger.Error("FindIncidentSeverityTeamJoin error", - zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name), - zap.String("user_id", callback.User.ID), zap.Error(err)) - return - } else if result == nil { - itp.logger.Error("IncidentSeverityTeamJoin Not found", - zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name), - zap.String("user_id", callback.User.ID), zap.Error(err)) - return - } - cfId, customerTagIdRequests, incidentTagIdRequests, dataPlatformString := buildUpdateIncidentTagsRequest(itp.logger, callback.View.State.Values) - - //Update Contributing factors - if cfId == 0 { - err = query.SetContributingFactorDeletedAt(itp.db, result.IncidentId) - if err != nil { - itp.logger.Error("SetContributingFactorDeletedAt err", - zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name), - zap.String("user_id", callback.User.ID), zap.Error(err)) - return - } - - } else { - err = query.UpsertContributingFactorIdToIncidentId(itp.db, cfId, result.IncidentId) - if err != nil { - itp.logger.Error("UpsertContributingFactorIdToIncidentId err", - zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name), - zap.String("user_id", callback.User.ID), zap.Error(err)) - return - } - } - - // Update Customer Tag Id mapping - customerTagIdExist, err := query.FindCustomerTagIdMappingWithIncidentByIncidentId(itp.db, result.IncidentId) - var customerTagIdList []int - - for _, o := range customerTagIdExist { - customerTagIdList = append(customerTagIdList, o.CustomerTagsId) - } - - UpdateCustomerTagId(result, callback, itp.db, itp.logger, customerTagIdList, customerTagIdRequests) - - // Team tag Id Mapping - tagsExist, err := query.FindTagRecordListByIncidentId(itp.db, result.IncidentId) - var tagIdList []int - for _, o := range tagsExist { - tagIdList = append(tagIdList, o.TagId) - } - - UpdateTeamTagId(result, callback, itp.db, itp.logger, tagIdList, incidentTagIdRequests) - - // Update Data Platform tags - err = query.UpsertDataPlatformTagToIncidentId(itp.db, dataPlatformString, result.IncidentId) - if err != nil { - itp.logger.Error("UpsertDataPlatformTagToIncidentId err", - zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name), - zap.String("user_id", callback.User.ID), zap.Error(err)) - return - } - var payload interface{} - itp.client.Ack(*request, payload) -} - -func buildUpdateIncidentTagsRequest(logger *zap.Logger, blockActions map[string]map[string]slack.BlockAction) (int, []int, []int, string) { - var requestMap = make(map[string][]slack.OptionBlockObject) - var requestMapString = make(map[string]string, 0) - for _, actions := range blockActions { - for actionID, action := range actions { - if action.Type == "multi_static_select" { - requestMap[actionID] = action.SelectedOptions - } else if action.Type == "static_select" { - requestMapString[actionID] = action.SelectedOption.Value - } else if action.Type == "plain_text_input" { - requestMapString[actionID] = action.Value - } - } - } - var customerTags []int - var tagsList []int - - for _, o := range requestMap["incident_customer_tags_modal_request"] { - selectedValueInInt, err := strconv.Atoi(o.Value) - if err != nil { - logger.Error("String to int conversion failed in buildUpdateIncidentTagseRequest for " + o.Value) - } else { - customerTags = append(customerTags, selectedValueInInt) - } - } - - for _, o := range requestMap["incident_tags_modal_request"] { - selectedValueInInt, err := strconv.Atoi(o.Value) - if err != nil { - logger.Error("String to int conversion failed in buildUpdateIncidentTagseRequest for " + o.Value) - } else { - tagsList = append(tagsList, selectedValueInInt) - } - } - - var cfId int - var err1 error - if len(requestMapString["incident_cf_modal_request"]) != 0 { - cfId, err1 = strconv.Atoi(requestMapString["incident_cf_modal_request"]) - if err1 != nil { - logger.Error("String to int conversion in CF failed in buildUpdateIncidentTagseRequest for " + requestMapString["incident_cf_modal_request"]) - } - } - - return cfId, customerTags, tagsList, requestMapString["incident_data_platform_tag_modal_request"] -} - -func diffLists(list1 []int, list2 []int) ([]int, []int) { - set1 := make(map[int]bool) - set2 := make(map[int]bool) - result1 := []int{} - result2 := []int{} - - for _, num := range list1 { - set1[num] = true - } - - for _, num := range list2 { - set2[num] = true - } - - for num, _ := range set1 { - if _, ok := set2[num]; !ok { - result1 = append(result1, num) - } - } - - for num, _ := range set2 { - if _, ok := set1[num]; !ok { - result2 = append(result2, num) - } - } - - return result1, result2 -} - -func findContributingFactors(incidentId int, db *gorm.DB, logger *zap.Logger) ([]entity.ContributingFactorEntity, *entity.ContributingFactorEntity, error) { - cf, err := query.FetchAllContributingFactors(db) - if err != nil { - logger.Error("FetchAllContributingFactors error") - return nil, nil, err - } - savedCf, err := query.FindContributingFactorsByIncidentId(db, incidentId) - if err != nil { - logger.Error("FindContributingFactorsByIncidentId error") - return nil, nil, err - } - return cf, savedCf, nil - -} - -func findCustomerTags(incidentId int, db *gorm.DB, logger *zap.Logger) ([]entity.CustomerTagsEntity, []entity.CustomerTagsEntity, error) { - customerTags, err := query.FetchAllCustomerTags(db) - if err != nil { - logger.Error("FetchAllCustomerTags error") - return nil, nil, err - } - savedCustomerTags, err := query.FindCustomerTagsByIncidentId(logger, db, incidentId) - if err != nil { - logger.Error("FindCustomerTagsByIncidentId error") - return nil, nil, err - } - return customerTags, savedCustomerTags, nil - -} - -func UpdateCustomerTagId(result *entity.IncidentSeverityTeamJoinEntity, callback slack.InteractionCallback, db *gorm.DB, logger *zap.Logger, customerTagIdList []int, customerTagIdRequests []int) { - - listCustomerTag1, listCustomerTag2 := diffLists(customerTagIdList, customerTagIdRequests) - - for _, o := range listCustomerTag1 { - err := query.SetIncidentCustomerTagDeletedAt(db, o, result.IncidentId) - if err != nil { - logger.Error("SetIncidentCustomerTagDeletedAt error", - zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name), - zap.String("user_id", callback.User.ID), zap.Error(err)) - return - } - } - - for _, index := range listCustomerTag2 { - err := query.AddCustomerIdMappingToIncidentId(db, index, result.IncidentId) - if err != nil { - logger.Error("AddCustomerIMappingToIncidentId error", - zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name), - zap.String("user_id", callback.User.ID), zap.Error(err)) - return - } - } - -} - -func UpdateTeamTagId(result *entity.IncidentSeverityTeamJoinEntity, callback slack.InteractionCallback, db *gorm.DB, logger *zap.Logger, tagIdList []int, tagIdRequests []int) { - - list1, list2 := diffLists(tagIdList, tagIdRequests) - - for _, o := range list1 { - err := query.SetTagsDeletedAt(db, o, result.IncidentId) - if err != nil { - logger.Error("SetDeletedAt error", - zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name), - zap.String("user_id", callback.User.ID), zap.Error(err)) - return - } - } - - for _, index := range list2 { - err := query.AddTagsIdMappingToIncidentId(db, index, result.IncidentId) - if err != nil { - logger.Error("AddTagsIdMappingToIncidentId error", - zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name), - zap.String("user_id", callback.User.ID), zap.Error(err)) - return - } - } - -} diff --git a/pkg/slack/houston/command/incident_update_type.go b/pkg/slack/houston/command/incident_update_type.go deleted file mode 100644 index 567f55b..0000000 --- a/pkg/slack/houston/command/incident_update_type.go +++ /dev/null @@ -1,123 +0,0 @@ -package command - -import ( - "fmt" - "houston/pkg/postgres/query" - "houston/pkg/slack/common" - houston "houston/pkg/slack/houston/design" - "strconv" - - "github.com/slack-go/slack" - "github.com/slack-go/slack/socketmode" - "go.uber.org/zap" - "gorm.io/gorm" -) - -type incidentUpdateTypeProcessor struct { - client *socketmode.Client - db *gorm.DB - logger *zap.Logger -} - -func NewIncidentUpdateTypeProcessor(client *socketmode.Client, db *gorm.DB, logger *zap.Logger) *incidentUpdateTypeProcessor { - return &incidentUpdateTypeProcessor{ - client: client, - db: db, - logger: logger, - } -} - -func (itp *incidentUpdateTypeProcessor) IncidentUpdateTypeRequestProcess(callback slack.InteractionCallback, request *socketmode.Request) { - teams, err := query.FindTeamList(itp.db) - if err != nil { - itp.logger.Error("FindTeamList error", - zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name), - zap.String("user_id", callback.User.ID), zap.Error(err)) - return - } - - modalRequest := houston.BuildIncidentUpdateTypeModal(callback.Channel, teams) - - _, err = itp.client.OpenView(callback.TriggerID, modalRequest) - if err != nil { - itp.logger.Error("houston slack openview command failed.", - zap.String("trigger_id", callback.TriggerID), zap.String("channel_id", callback.Channel.ID), zap.Error(err)) - return - } - var payload interface{} - itp.client.Ack(*request, payload) -} - -func (isp *incidentUpdateTypeProcessor) IncidentUpdateType(callback slack.InteractionCallback, request *socketmode.Request, channel slack.Channel, user slack.User) { - incidentEntity, err := query.FindIncidentByChannelId(isp.db, callback.View.PrivateMetadata) - if err != nil { - isp.logger.Error("FindIncidentByChannelId error", - zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name), - zap.String("user_id", user.ID), zap.Error(err)) - return - } else if incidentEntity == nil { - isp.logger.Error("IncidentEntity not found ", - zap.String("incident_slack_channel_id", callback.Channel.ID), zap.String("channel", callback.Channel.Name), - zap.String("user_id", callback.User.ID), zap.Error(err)) - return - } - - incidentTypeId := buildUpdateIncidentTypeRequest(isp.logger, callback.View.State.Values) - result, err := query.FindTeamById(isp.db, incidentTypeId) - if err != nil { - isp.logger.Error("FindTeamEntityById error", - zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name), - zap.String("user_id", user.ID), zap.Error(err)) - return - } else if result == nil { - isp.logger.Error("Team Not Found", - zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name), - zap.String("user_id", user.ID), zap.Error(err)) - return - } - incidentEntity.TeamsId = int(result.ID) - incidentEntity.UpdatedBy = user.ID - err = query.UpdateIncident(isp.db, incidentEntity) - if err != nil { - isp.logger.Error("UpdateIncident error", - zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name), - zap.String("user_id", user.ID), zap.Error(err)) - } - - userIdList, err := query.FindDefaultUserIdToBeAddedByTeam(isp.db, int(result.ID)) - if err != nil { - isp.logger.Error("FindDefaultUserIdToBeAddedByTeam error", - zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name), - zap.String("user_id", user.ID), zap.Error(err)) - return - } - for _, o := range userIdList { - common.InviteUsersToConversation(isp.client, isp.logger, callback.View.PrivateMetadata, o) - } - msgOption := slack.MsgOptionText(fmt.Sprintf("<@%s> > set Team to %s", user.ID, result.Name), false) - _, _, errMessage := isp.client.PostMessage(callback.View.PrivateMetadata, msgOption) - if errMessage != nil { - isp.logger.Error("post response failed for IncidentUpdateType", zap.Error(errMessage)) - return - } - var payload interface{} - isp.client.Ack(*request, payload) -} - -func buildUpdateIncidentTypeRequest(logger *zap.Logger, blockActions map[string]map[string]slack.BlockAction) int { - var requestMap = make(map[string]string, 0) - for _, actions := range blockActions { - for actionID, action := range actions { - if action.Type == "static_select" { - requestMap[actionID] = action.SelectedOption.Value - } - } - } - - selectedValue := requestMap["incident_type_modal_request"] - selectedValueInInt, err := strconv.Atoi(selectedValue) - if err != nil { - logger.Error("String conversion to int faileed in buildUpdateIncidentTypeRequest for "+selectedValue, zap.Error(err)) - } - return selectedValueInInt -} diff --git a/pkg/slack/houston/command/member_join_event.go b/pkg/slack/houston/command/member_join_event.go deleted file mode 100644 index c7487b7..0000000 --- a/pkg/slack/houston/command/member_join_event.go +++ /dev/null @@ -1,56 +0,0 @@ -package command - -import ( - "houston/pkg/postgres/query" - houston "houston/pkg/slack/houston/design" - - "github.com/slack-go/slack" - "github.com/slack-go/slack/slackevents" - "github.com/slack-go/slack/socketmode" - "go.uber.org/zap" - "gorm.io/gorm" -) - -type memberJoinProcessor struct { - client *socketmode.Client - db *gorm.DB - logger *zap.Logger -} - -func NewMemberJoinProcessor(socketmodeClient *socketmode.Client, db *gorm.DB, logger *zap.Logger) *memberJoinProcessor { - return &memberJoinProcessor{ - client: socketmodeClient, - db: db, - logger: logger, - } -} - -func (mp *memberJoinProcessor) MemberJoinProcessCommand(memberJoinedChannelEvent *slackevents.MemberJoinedChannelEvent) { - mp.logger.Info("processing member join event", zap.String("channel", memberJoinedChannelEvent.Channel)) - - incidentEntity, err := query.FindIncidentByChannelId(mp.db, memberJoinedChannelEvent.Channel) - if err != nil { - mp.logger.Error("error in searching incident", zap.String("channel", memberJoinedChannelEvent.Channel), - zap.String("user_id", memberJoinedChannelEvent.User), zap.Error(err)) - return - } else if err == nil && incidentEntity == nil { - mp.logger.Info("incident not found", zap.String("channel", memberJoinedChannelEvent.Channel), - zap.String("user_id", memberJoinedChannelEvent.User), zap.Error(err)) - return - } - - blocks, err := houston.IncidentSummarySection(incidentEntity, mp.db) - if err != nil { - mp.logger.Error("error in creating incident summary section inside incident", - zap.String("channel", memberJoinedChannelEvent.Channel), - zap.String("user_id", memberJoinedChannelEvent.User), zap.Error(err)) - return - } - mp.logger.Info("member join block", zap.Any("blocks", blocks)) - color := query.GetColorBySeverity(incidentEntity.SeverityId) - msgOption := slack.MsgOptionAttachments(slack.Attachment{Blocks: blocks, Color: color}) - _, err = mp.client.PostEphemeral(memberJoinedChannelEvent.Channel, memberJoinedChannelEvent.User, msgOption) - if err != nil { - mp.logger.Error("post response failed", zap.Error(err)) - } -} diff --git a/pkg/slack/houston/command/show_incidents.go b/pkg/slack/houston/command/show_incidents.go deleted file mode 100644 index 291c30b..0000000 --- a/pkg/slack/houston/command/show_incidents.go +++ /dev/null @@ -1,49 +0,0 @@ -package command - -import ( - "houston/pkg/postgres/query" - houston "houston/pkg/slack/houston/design" - - "github.com/slack-go/slack" - "github.com/slack-go/slack/socketmode" - "github.com/spf13/viper" - "go.uber.org/zap" - "gorm.io/gorm" -) - -type ShowIncidentsButtonProcessor struct { - client *socketmode.Client - db *gorm.DB - logger *zap.Logger -} - -func ShowIncidentsProcessor(client *socketmode.Client, db *gorm.DB, logger *zap.Logger) *ShowIncidentsButtonProcessor { - return &ShowIncidentsButtonProcessor{ - client: client, - db: db, - logger: logger, - } -} - -func (sip *ShowIncidentsButtonProcessor) ProcessShowIncidentsButtonCommand(channel slack.Channel, user slack.User, triggerId string, request *socketmode.Request) { - - limit := viper.GetInt("SHOW_INCIDENTS_LIMT") - s, err := query.FindNotResolvedLatestIncidents(sip.db, limit) - - if err != nil { - sip.logger.Error("FindNotResolvedLatestIncidents query failed.", - zap.String("trigger_id", triggerId), zap.String("channel_id", channel.ID), zap.Error(err)) - return - } - blocks := houston.GenerateModalForShowIncidentsButtonSection(s) - msgOption := slack.MsgOptionBlocks(blocks...) - _, err = sip.client.Client.PostEphemeral(channel.ID, user.ID, msgOption) - if err != nil { - sip.logger.Error("houston slack PostEphemeral command failed for ProcessShowIncidentsButtonCommand.", - zap.String("trigger_id", triggerId), zap.String("channel_id", channel.ID), zap.String("user_id", user.ID), zap.Error(err)) - return - } - var payload interface{} - sip.client.Ack(*request, payload) - -} diff --git a/pkg/slack/houston/command/start_incident.go b/pkg/slack/houston/command/start_incident.go deleted file mode 100644 index 63019c2..0000000 --- a/pkg/slack/houston/command/start_incident.go +++ /dev/null @@ -1,38 +0,0 @@ -package command - -import ( - houston "houston/pkg/slack/houston/design" - - "github.com/slack-go/slack/socketmode" - "go.uber.org/zap" - "gorm.io/gorm" -) - -type startIncidentButtonProcessor struct { - client *socketmode.Client - db *gorm.DB - logger *zap.Logger -} - -func NewStartIncidentProcessor(client *socketmode.Client, db *gorm.DB, logger *zap.Logger) *startIncidentButtonProcessor { - return &startIncidentButtonProcessor{ - client: client, - db: db, - logger: logger, - } -} - -func (sip *startIncidentButtonProcessor) ProcessStartIncidentButtonCommand( - client *socketmode.Client, request *socketmode.Request, channelId, triggerId string) { - modal := houston.GenerateModalRequest(sip.db, sip.logger, channelId) - _, err := sip.client.OpenView(triggerId, modal) - if err != nil { - sip.logger.Error("houston slack openview command failed.", - zap.String("trigger_id", triggerId), zap.String("channel_id", channelId), zap.Error(err)) - return - } - - sip.logger.Info("houston successfully send modal to slack", zap.String("trigger_id", triggerId)) - var payload interface{} - client.Ack(*request, payload) -} diff --git a/pkg/slack/houston/command/start_incident_modal_submission.go b/pkg/slack/houston/command/start_incident_modal_submission.go deleted file mode 100644 index 6038d22..0000000 --- a/pkg/slack/houston/command/start_incident_modal_submission.go +++ /dev/null @@ -1,244 +0,0 @@ -package command - -import ( - "context" - "encoding/json" - "fmt" - "houston/entity" - "houston/model" - "houston/model/request" - "houston/pkg/postgres/query" - "houston/pkg/slack/common" - houston "houston/pkg/slack/houston/design" - "strconv" - "time" - - "github.com/google/uuid" - "github.com/slack-go/slack" - "github.com/slack-go/slack/socketmode" - "go.uber.org/zap" - "google.golang.org/api/calendar/v3" - "google.golang.org/api/option" - "gorm.io/gorm" -) - -type createIncidentProcessor struct { - client *socketmode.Client - logger *zap.Logger - db *gorm.DB -} - -func NewCreateIncidentProcessor(client *socketmode.Client, logger *zap.Logger, db *gorm.DB) *createIncidentProcessor { - return &createIncidentProcessor{ - client: client, - logger: logger, - db: db, - } -} - -func (cip *createIncidentProcessor) CreateIncidentModalCommandProcessing(callback slack.InteractionCallback, request *socketmode.Request) { - // Build create incident request - createIncidentRequest := buildCreateIncidentRequest(callback.View.State.Values) - createIncidentRequest.RequestOriginatedSlackChannel = callback.View.PrivateMetadata - cip.logger.Info("incident request created", zap.Any("request", createIncidentRequest)) - - // Create a new Slack channel for the incident - ai := query.AutoIncrementID{} - id, err := ai.Next(cip.db) - if err != nil || id == 0 { - cip.logger.Error("unable to generate id", zap.Error(err)) - return - } - channelName := fmt.Sprintf("houston-%s", strconv.Itoa(id)) - channelID, err := common.CreateChannel(cip.client, cip.logger, channelName) - if err != nil { - cip.logger.Error("Unable to create channel", zap.Error(err)) - return - } - createIncidentRequest.Status = entity.Investigating - createIncidentRequest.IncidentName = channelName - createIncidentRequest.SlackChannel = channelID - - // Set default values for some fields - teamId, _ := strconv.Atoi(createIncidentRequest.IncidentType) - createIncidentRequest.DetectionTime = nil - createIncidentRequest.CustomerImpactStartTime = time.Now() - createIncidentRequest.CustomerImpactEndTime = nil - createIncidentRequest.TeamsId = teamId - createIncidentRequest.JiraId = "" - createIncidentRequest.ConfluenceId = "" - createIncidentRequest.RemindMeAt = nil - createIncidentRequest.EnableReminder = false - createIncidentRequest.CreatedBy = callback.User.ID - createIncidentRequest.UpdatedBy = callback.User.ID - createIncidentRequest.Version = 0 - - // Save the incident to the database - incidentEntity, err := query.CreateIncident(cip.db, createIncidentRequest) - if err != nil { - cip.logger.Error("Error while creating incident", zap.Error(err)) - return - } - - // Post incident summary to Blaze Group channel and incident channel - err = postIncidentSummary(cip.client, callback.View.PrivateMetadata, channelID, incidentEntity, cip.db) - if err != nil { - cip.logger.Error("error while posting incident summary", zap.Error(err)) - } - err = addDefaultUsersToIncident(cip, channelID, incidentEntity.SeverityId, incidentEntity.TeamsId) - if err != nil { - cip.logger.Error("error while adding default users to incident", zap.Error(err)) - return - } - - tagOncallToIncident(cip, teamId, channelID) - - //gmeet := createGmeetLink(cip.logger, channelName) - //if len(gmeet) != 0 { - // msgOption := slack.MsgOptionText(fmt.Sprintf("gmeet Link :", gmeet), false) - // cip.client.PostMessage(channelID, msgOption) - //} - - // Acknowledge the interaction callback - var payload interface{} - cip.client.Ack(*request, payload) -} - -func tagOncallToIncident(cip *createIncidentProcessor, teamId int, channelId string) { - teamEntity, err := query.FindTeamById(cip.db, teamId) - if (err != nil) { - cip.logger.Error("error in fetching team err: %v", zap.Error(err)) - } - msgOption := slack.MsgOptionText(fmt.Sprintf("<@%s>", teamEntity.OncallHandle), false) - _, ts, _ := cip.client.PostMessage(channelId, msgOption) - InviteOncallPersonToIncident(cip, channelId, ts) -} - -func InviteOncallPersonToIncident(cip *createIncidentProcessor, channelId, ts string) { - go func() { - time.Sleep(3 * time.Second) - msg, _, _, _ := cip.client.GetConversationReplies(&slack.GetConversationRepliesParameters{ - ChannelID: channelId, - Timestamp: ts, - Limit: 2, - }, - ) - if (len(msg) > 1) { - //User id needs to sliced from `<@XXXXXXXXXXXX>` format to `XXXXXXXXXXXX` - cip.client.InviteUsersToConversation(channelId, msg[1].Text[2:13]) - } - }() -} - -func addDefaultUsersToIncident(cip *createIncidentProcessor, channelId string, severityId, teamId int) (error) { - userIdList, err := query.FindDefaultUserIdToBeAddedBySeverity(cip.db, severityId) - if err != nil { - return err - } - for _, o := range userIdList { - common.InviteUsersToConversation(cip.client, cip.logger, channelId, o) - } - userIdList, err = query.FindDefaultUserIdToBeAddedByTeam(cip.db, teamId) - if err != nil { - return err - } - for _, o := range userIdList { - common.InviteUsersToConversation(cip.client, cip.logger, channelId, o) - } - return nil -} - -func postIncidentSummary( - client *socketmode.Client, - blazeGroupChannelID, incidentChannelID string, - incidentEntity *entity.IncidentEntity, - db *gorm.DB) error { - // Post incident summary to Blaze Group channel and incident channel - blocks, err := houston.IncidentSummarySection(incidentEntity, db) - if err != nil { - return fmt.Errorf("error in creating incident summary err: %v", err) - } - color := query.GetColorBySeverity(incidentEntity.SeverityId) - att := slack.Attachment{Blocks: blocks, Color: color} - _, timestamp, err := client.PostMessage(blazeGroupChannelID, slack.MsgOptionAttachments(att)) - if (err == nil) { - query.CreateMessage(db, &request.CreateMessage{ - SlackChannel: blazeGroupChannelID, - MessageTimeStamp: timestamp, - IncidentName: incidentEntity.IncidentName, - }) - } else { - return fmt.Errorf("error in saving message %v", err) - } - _, timestamp, err = client.PostMessage(incidentChannelID, slack.MsgOptionAttachments(att)) - if (err == nil) { - query.CreateMessage(db, &request.CreateMessage{ - SlackChannel: incidentChannelID, - MessageTimeStamp: timestamp, - IncidentName: incidentEntity.IncidentName, - }) - } else { - return fmt.Errorf("error in saving message %v", err) - } - return nil -} - -func buildCreateIncidentRequest(blockActions map[string]map[string]slack.BlockAction) *model.CreateIncident { - var createIncidentRequest model.CreateIncident - var requestMap = make(map[string]string, 0) - for _, actions := range blockActions { - for actionID, action := range actions { - if action.Type == "plain_text_input" { - requestMap[actionID] = action.Value - } - if action.Type == "static_select" { - requestMap[actionID] = action.SelectedOption.Value - } - } - } - - desRequestMap, _ := json.Marshal(requestMap) - json.Unmarshal(desRequestMap, &createIncidentRequest) - return &createIncidentRequest -} - -func createGmeetLink(logger *zap.Logger, channelName string) string { - calclient, err := calendar.NewService(context.Background(), option.WithCredentialsFile("")) - if err != nil { - logger.Error("Unable to read client secret file: ", zap.Error(err)) - return "" - } - t0 := time.Now().Format(time.RFC3339) - t1 := time.Now().Add(1 * time.Hour).Format(time.RFC3339) - event := &calendar.Event{ - Summary: channelName, - Description: "Incident", - Start: &calendar.EventDateTime{ - DateTime: t0, - }, - End: &calendar.EventDateTime{ - DateTime: t1, - }, - ConferenceData: &calendar.ConferenceData{ - CreateRequest: &calendar.CreateConferenceRequest{ - RequestId: uuid.NewString(), - ConferenceSolutionKey: &calendar.ConferenceSolutionKey{ - Type: "hangoutsMeet", - }, - Status: &calendar.ConferenceRequestStatus{ - StatusCode: "success", - }, - }, - }, - } - - calendarID := "primary" //use "primary" - event, err = calclient.Events.Insert(calendarID, event).ConferenceDataVersion(1).Do() - if err != nil { - logger.Error("Unable to create event. %v\n", zap.Error(err)) - return "" - } - - calclient.Events.Delete(calendarID, event.Id).Do() - return event.HangoutLink -} diff --git a/pkg/slack/houston/design/blazeless_modal.go b/pkg/slack/houston/design/blazeless_modal.go deleted file mode 100644 index df33258..0000000 --- a/pkg/slack/houston/design/blazeless_modal.go +++ /dev/null @@ -1,92 +0,0 @@ -package houston - -import ( - "houston/entity" - "houston/pkg/postgres/query" - "strconv" - - "github.com/slack-go/slack" - "go.uber.org/zap" - "gorm.io/gorm" -) - -func GenerateModalRequest(db *gorm.DB, logger *zap.Logger, channel string) slack.ModalViewRequest { - // Create a ModalViewRequest with a header and two inputs - titleText := slack.NewTextBlockObject("plain_text", "Houston", false, false) - closeText := slack.NewTextBlockObject("plain_text", "Close", false, false) - submitText := slack.NewTextBlockObject("plain_text", "Send", false, false) - - headerText := slack.NewTextBlockObject("mrkdwn", "Start Incident", false, false) - headerSection := slack.NewSectionBlock(headerText, nil, nil) - - teams, _ := query.FindTeam(db) - incidentTypeOptions := createOptionBlockObjectsForTeams(teams) - incidentTypeText := slack.NewTextBlockObject(slack.PlainTextType, "Incident Type", false, false) - incidentTypePlaceholder := slack.NewTextBlockObject("plain_text", "The incident type for the incident to create", false, false) - incidentTypeOption := slack.NewOptionsSelectBlockElement(slack.OptTypeStatic, incidentTypePlaceholder, "incident_type", incidentTypeOptions...) - incidentTypeBlock := slack.NewInputBlock("incident_type", incidentTypeText, nil, incidentTypeOption) - - severities, _ := query.FindSeverity(db, logger) - severityOptions := createOptionBlockObjectsForSeverity(severities) - severityText := slack.NewTextBlockObject(slack.PlainTextType, "Severity", false, false) - severityTextPlaceholder := slack.NewTextBlockObject("plain_text", "The severity for the incident to create", false, false) - severityOption := slack.NewOptionsSelectBlockElement(slack.OptTypeStatic, severityTextPlaceholder, "incident_severity", severityOptions...) - severityBlock := slack.NewInputBlock("incident_severity", severityText, nil, severityOption) - - pagerDutyImpactedServiceText := slack.NewTextBlockObject("plain_text", "Pagerduty", false, false) - pagerDutyImpactedServicePlaceholder := slack.NewTextBlockObject("plain_text", "Select pagerduty impacted services", false, false) - pagerDutyImpactedServiceElement := slack.NewPlainTextInputBlockElement(pagerDutyImpactedServicePlaceholder, "pagerduty_impacted") - pagerDutyImpactedService := slack.NewInputBlock("Pagerduty impacted service", pagerDutyImpactedServiceText, nil, pagerDutyImpactedServiceElement) - pagerDutyImpactedService.Optional = true - - incidentTitleText := slack.NewTextBlockObject("plain_text", "Incident Title", false, false) - incidentTitlePlaceholder := slack.NewTextBlockObject("plain_text", "Write something", false, false) - incidentTitleElement := slack.NewPlainTextInputBlockElement(incidentTitlePlaceholder, "incident_title") - incidentTitle := slack.NewInputBlock("Incident title", incidentTitleText, nil, incidentTitleElement) - - incidentDescriptionText := slack.NewTextBlockObject("plain_text", "Incident description", false, false) - incidentDescriptionPlaceholder := slack.NewTextBlockObject("plain_text", "Write something", false, false) - incidentDescriptionElement := slack.NewPlainTextInputBlockElement(incidentDescriptionPlaceholder, "incident_description") - incidentDescriptionElement.Multiline = true - incidentDescription := slack.NewInputBlock("Incident description", incidentDescriptionText, nil, incidentDescriptionElement) - incidentDescription.Optional = true - - blocks := slack.Blocks{ - BlockSet: []slack.Block{ - headerSection, - incidentTypeBlock, - severityBlock, - pagerDutyImpactedService, - incidentTitle, - incidentDescription, - }, - } - - return slack.ModalViewRequest{ - Type: slack.ViewType("modal"), - Title: titleText, - Close: closeText, - Submit: submitText, - Blocks: blocks, - PrivateMetadata: channel, - CallbackID: "start_incident_button", - } -} - -func createOptionBlockObjectsForSeverity(options []entity.SeverityEntity) []*slack.OptionBlockObject { - optionBlockObjects := make([]*slack.OptionBlockObject, 0, len(options)) - for _, o := range options { - optionText := slack.NewTextBlockObject(slack.PlainTextType, o.Name, false, false) - optionBlockObjects = append(optionBlockObjects, slack.NewOptionBlockObject(strconv.Itoa(int(o.ID)), optionText, nil)) - } - return optionBlockObjects -} - -func createOptionBlockObjectsForTeams(options []entity.TeamEntity) []*slack.OptionBlockObject { - optionBlockObjects := make([]*slack.OptionBlockObject, 0, len(options)) - for _, o := range options { - optionText := slack.NewTextBlockObject(slack.PlainTextType, o.Name, false, false) - optionBlockObjects = append(optionBlockObjects, slack.NewOptionBlockObject(strconv.Itoa(int(o.ID)), optionText, nil)) - } - return optionBlockObjects -} diff --git a/pkg/slack/houston/design/blazeless_show_incidents_button_section.go b/pkg/slack/houston/design/blazeless_show_incidents_button_section.go deleted file mode 100644 index 05843f0..0000000 --- a/pkg/slack/houston/design/blazeless_show_incidents_button_section.go +++ /dev/null @@ -1,29 +0,0 @@ -package houston - -import ( - "houston/entity" - - "github.com/slack-go/slack" -) - -func GenerateModalForShowIncidentsButtonSection(incident []entity.IncidentSeverityTeamJoinEntity) []slack.Block { - contextBlock := slack.NewContextBlock("", slack.NewTextBlockObject("mrkdwn", ":eye: Only visible to you", false, false)) - - sectionBlocks := []*slack.SectionBlock{} - for i := 0; i < len(incident); i++ { - fields := []*slack.TextBlockObject{ - slack.NewTextBlockObject("mrkdwn", "\n`"+incident[i].SeverityName+"` `"+incident[i].TeamsName+" Incident` \n "+incident[i].Title+"\n <#"+incident[i].SlackChannel+">", false, false), - } - sectionBlocks = append(sectionBlocks, slack.NewSectionBlock(nil, fields, nil)) - } - - blocks := []slack.Block{ - contextBlock, - } - for i := 0; i < len(sectionBlocks); i++ { - blocks = append(blocks, sectionBlocks[i]) - } - - return blocks - -} diff --git a/pkg/slack/houston/design/blazeless_summary_section.go b/pkg/slack/houston/design/blazeless_summary_section.go deleted file mode 100644 index 30739c2..0000000 --- a/pkg/slack/houston/design/blazeless_summary_section.go +++ /dev/null @@ -1,80 +0,0 @@ -package houston - -import ( - "fmt" - "houston/entity" - "houston/pkg/postgres/query" - - "github.com/slack-go/slack" - "gorm.io/gorm" -) - -func SummarySection(incidentEntity *entity.IncidentEntity) []slack.Block { - return []slack.Block{ - buildSummaryHeader(incidentEntity.Title), - buildTypeAndChannelSectionBlock(incidentEntity, "", ""), - buildSeverityAndTicketSectionBlock(incidentEntity), - buildStatusAndMeetingSectionBlock(incidentEntity), - } -} - -func IncidentSummarySection(incidentEntity *entity.IncidentEntity, db *gorm.DB) (slack.Blocks, error) { - teamEntity, err := query.FindTeamById(db, incidentEntity.TeamsId) - if err != nil { - return slack.Blocks{}, fmt.Errorf("error in searching team with id: %d, err: %v", incidentEntity.TeamsId, err) - } - - severityEntity, err := query.FindSeverityById(db, incidentEntity.SeverityId) - if err != nil { - return slack.Blocks{}, fmt.Errorf("error in searching severity with id: %d, err: %v", incidentEntity.SeverityId, err) - } - return slack.Blocks{ - BlockSet: []slack.Block{ - buildTypeAndChannelSectionBlock(incidentEntity, teamEntity.Name, severityEntity.Name)}, - }, nil -} - -func buildSummaryHeader(incidentTitle string) *slack.SectionBlock { - headerText := slack.NewTextBlockObject("plain_text", incidentTitle, true, true) - headerSection := slack.NewSectionBlock(headerText, nil, nil) - - return headerSection -} - -func buildTypeAndChannelSectionBlock(incidentEntity *entity.IncidentEntity, teamName, severityName string) *slack.SectionBlock { - fields := []*slack.TextBlockObject{ - slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*<@%s>* \n*%s* - *%s*\n", incidentEntity.CreatedBy, incidentEntity.IncidentName, incidentEntity.Title), false, false), - slack.NewTextBlockObject("mrkdwn", "\n", false, false), - slack.NewTextBlockObject("mrkdwn", incidentEntity.Description, false, false), - slack.NewTextBlockObject("mrkdwn", "\n", false, false), - slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Type*\n%s", teamName), false, false), - slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Channel*\n<#%s>", incidentEntity.SlackChannel), false, false), - slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Severity*\n%s", severityName), false, false), - slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Ticket*\n%s", "Integration Disabled"), false, false), - slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Status*\n%s", incidentEntity.Status), false, false), - slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Meeting*\n%s", "Integration Disabled"), false, false), - } - block := slack.NewSectionBlock(nil, fields, nil) - - return block -} - -func buildSeverityAndTicketSectionBlock(incidentEntity *entity.IncidentEntity) *slack.SectionBlock { - fields := []*slack.TextBlockObject{ - slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Severity*\n%d", incidentEntity.SeverityId), false, false), - slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Ticket*\n%s", "integration disabled"), false, false), - } - block := slack.NewSectionBlock(nil, fields, nil) - - return block -} - -func buildStatusAndMeetingSectionBlock(incidentEntity *entity.IncidentEntity) *slack.SectionBlock { - fields := []*slack.TextBlockObject{ - slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Status*\n%s", incidentEntity.Status), false, false), - slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Meeting*\n%s", "integration disabled"), false, false), - } - block := slack.NewSectionBlock(nil, fields, nil) - - return block -} diff --git a/pkg/slack/houston/design/incident_update_tags.go b/pkg/slack/houston/design/incident_update_tags.go deleted file mode 100644 index 3604047..0000000 --- a/pkg/slack/houston/design/incident_update_tags.go +++ /dev/null @@ -1,105 +0,0 @@ -package houston - -import ( - "fmt" - "houston/entity" - "strconv" - "strings" - - "github.com/slack-go/slack" -) - -func BuildIncidentUpdateTagModal(channel slack.Channel, tags []entity.TagsEntity, teamName string, savedTags []entity.TagsEntity, - savedContributingFactor *entity.ContributingFactorEntity, contributingFactors []entity.ContributingFactorEntity, - savedCustomerTags []entity.CustomerTagsEntity, customerTags []entity.CustomerTagsEntity, savedDataPlatformTags *entity.IncidentsTagsDataPlatformMapping) slack.ModalViewRequest { - - titleText := slack.NewTextBlockObject("plain_text", "Edit Tags", false, false) - closeText := slack.NewTextBlockObject("plain_text", "Close", false, false) - submitText := slack.NewTextBlockObject("plain_text", "Submit", false, false) - - contributingFactorBlockOption := createContributingFactorBlocks(contributingFactors) - contributingFactorText := slack.NewTextBlockObject(slack.PlainTextType, "CONTRIBUTING-FACTORS", false, false) - contributingFactorPlaceholder := slack.NewTextBlockObject(slack.PlainTextType, fmt.Sprint("SELECT CONTRIBUTING-FACTORS tags"), false, false) - contributingFactorOption := slack.NewOptionsSelectBlockElement(slack.OptTypeStatic, contributingFactorPlaceholder, "incident_cf_modal_request", contributingFactorBlockOption...) - if savedContributingFactor != nil { - contributingFactorOption.InitialOption = slack.NewOptionBlockObject(strconv.FormatUint(uint64(savedContributingFactor.ID), 10), slack.NewTextBlockObject(slack.PlainTextType, savedContributingFactor.Label, false, false), nil) - } - contributingFactorHint := slack.NewTextBlockObject(slack.PlainTextType, "CONTRIBUTING-FACTORS is configured to have one single tag, please select appropriate single tag from the dropdown", false, false) - contributingFactorBlock := slack.NewInputBlock("incident_cf_modal_request_input", contributingFactorText, contributingFactorHint, contributingFactorOption) - contributingFactorBlock.Optional = true - - customerBlockOption := createCustomerBlocks(customerTags) - customerText := slack.NewTextBlockObject(slack.PlainTextType, "CUSTOMER", false, false) - customerPlaceholder := slack.NewTextBlockObject(slack.PlainTextType, fmt.Sprint("SELECT CUSTOMER tags"), false, false) - customerOption := slack.NewOptionsMultiSelectBlockElement(slack.MultiOptTypeStatic, customerPlaceholder, "incident_customer_tags_modal_request", customerBlockOption...) - customerOption.InitialOptions = createCustomerBlocks(savedCustomerTags) - customerBlock := slack.NewInputBlock("incident_customer_tags_modal_request_input", customerText, nil, customerOption) - customerBlock.Optional = true - - incidentStatusBlockOption := createIncidentTagsBlock(tags) - incidentTagText := slack.NewTextBlockObject(slack.PlainTextType, fmt.Sprint("CUSTOMER: "+strings.ToUpper(teamName)), false, false) - incidentTagPlaceholder := slack.NewTextBlockObject(slack.PlainTextType, fmt.Sprint("SELECT CUSTOMER: "+strings.ToUpper(teamName)+" tags"), false, false) - incidentTypeOption := slack.NewOptionsMultiSelectBlockElement(slack.MultiOptTypeStatic, incidentTagPlaceholder, "incident_tags_modal_request", incidentStatusBlockOption...) - incidentTypeOption.InitialOptions = createIncidentTagsBlock(savedTags) - incidentTypeBlock := slack.NewInputBlock("incident_tags_modal_request_input", incidentTagText, nil, incidentTypeOption) - incidentTypeBlock.Optional = true - - dataPlatformText := slack.NewTextBlockObject("plain_text", "DATA PLATFORM", false, false) - dataPlatformPlaceholder := slack.NewTextBlockObject("plain_text", "Comma seperated list of tags", false, false) - dataPlatformElement := slack.NewPlainTextInputBlockElement(dataPlatformPlaceholder, "incident_data_platform_tag_modal_request") - if savedDataPlatformTags != nil { - dataPlatformElement.InitialValue = savedDataPlatformTags.DataPlatformTag - } - dataPlatformTitle := slack.NewInputBlock("DATA_PLATFORM", dataPlatformText, nil, dataPlatformElement) - dataPlatformTitle.Optional = true - - blocks := slack.Blocks{ - BlockSet: []slack.Block{ - contributingFactorBlock, - customerBlock, - incidentTypeBlock, - dataPlatformTitle, - }, - } - - return slack.ModalViewRequest{ - Type: slack.ViewType("modal"), - Title: titleText, - Close: closeText, - Submit: submitText, - Blocks: blocks, - PrivateMetadata: channel.ID, - CallbackID: "updateTag", - } - -} - -func createIncidentTagsBlock(options []entity.TagsEntity) []*slack.OptionBlockObject { - optionBlockObjects := make([]*slack.OptionBlockObject, 0, len(options)) - for _, o := range options { - txt := fmt.Sprintf(o.Label) - optionText := slack.NewTextBlockObject(slack.PlainTextType, txt, false, false) - optionBlockObjects = append(optionBlockObjects, slack.NewOptionBlockObject(strconv.FormatUint(uint64(o.ID), 10), optionText, nil)) - } - return optionBlockObjects -} - -func createContributingFactorBlocks(options []entity.ContributingFactorEntity) []*slack.OptionBlockObject { - optionBlockObjects := make([]*slack.OptionBlockObject, 0, len(options)) - for _, o := range options { - txt := fmt.Sprintf(o.Label) - optionText := slack.NewTextBlockObject(slack.PlainTextType, txt, false, false) - optionBlockObjects = append(optionBlockObjects, slack.NewOptionBlockObject(strconv.FormatUint(uint64(o.ID), 10), optionText, nil)) - } - return optionBlockObjects -} - -func createCustomerBlocks(options []entity.CustomerTagsEntity) []*slack.OptionBlockObject { - optionBlockObjects := make([]*slack.OptionBlockObject, 0, len(options)) - for _, o := range options { - txt := fmt.Sprintf("%s", o.Label) - optionText := slack.NewTextBlockObject(slack.PlainTextType, txt, false, false) - optionBlockObjects = append(optionBlockObjects, slack.NewOptionBlockObject(strconv.FormatUint(uint64(o.ID), 10), optionText, nil)) - } - return optionBlockObjects -} diff --git a/pkg/slack/houston/slash_command_processor.go b/pkg/slack/houston/slash_command_processor.go deleted file mode 100644 index 9e719b0..0000000 --- a/pkg/slack/houston/slash_command_processor.go +++ /dev/null @@ -1,201 +0,0 @@ -package houston - -import ( - "fmt" - "houston/entity" - "houston/pkg/postgres/query" - "houston/pkg/slack/houston/command" - houston "houston/pkg/slack/houston/design" - - "github.com/slack-go/slack" - "github.com/slack-go/slack/slackevents" - "github.com/slack-go/slack/socketmode" - "go.uber.org/zap" - "gorm.io/gorm" -) - -type HoustonCommandHandler struct { - logger *zap.Logger - socketmodeClient *socketmode.Client - db *gorm.DB -} - -func NewHoustonCommandHandler(socketmodeClient *socketmode.Client, logger *zap.Logger, db *gorm.DB) *HoustonCommandHandler { - return &HoustonCommandHandler{ - socketmodeClient: socketmodeClient, - logger: logger, - db: db, - } -} - -func (bch *HoustonCommandHandler) ProcessSlashCommand(evt socketmode.Event) { - HoustonMainCommand(bch.db, bch.socketmodeClient, bch.logger, &evt) - -} - -func (bch *HoustonCommandHandler) ProcessCallbackEvent(callback slack.InteractionCallback) { - bch.logger.Info("process callback event", zap.Any("callback", callback)) -} - -func (bch *HoustonCommandHandler) ProcessModalCallbackEvent(callback slack.InteractionCallback, request *socketmode.Request) { - var callbackId = callback.View.CallbackID - switch callbackId { - case "start_incident_button": - cip := command.NewCreateIncidentProcessor(bch.socketmodeClient, bch.logger, bch.db) - cip.CreateIncidentModalCommandProcessing(callback, request) - case "assignIncidentRole": - iap := command.NewIncidentAssignProcessor(bch.socketmodeClient, bch.db, bch.logger) - iap.IncidentAssignModalCommandProcessing(callback, request) - case "setIncidentStatus": - isp := command.NewIncidentUpdateStatusProcessor(bch.socketmodeClient, bch.db, bch.logger) - isp.IncidentUpdateStatus(callback, request, callback.Channel, callback.User) - case "setIncidentTitle": - itp := command.NewIncidentUpdateTitleProcessor(bch.socketmodeClient, bch.db, bch.logger) - itp.IncidentUpdateTitle(callback, request, callback.Channel, callback.User) - case "setIncidentDescription": - idp := command.NewIncidentUpdateDescriptionProcessor(bch.socketmodeClient, bch.db, bch.logger) - idp.IncidentUpdateDescription(callback, request, callback.Channel, callback.User) - case "setIncidentSeverity": - isp := command.NewIncidentUpdateSeverityProcessor(bch.socketmodeClient, bch.db, bch.logger) - isp.IncidentUpdateSeverity(callback, request, callback.Channel, callback.User) - case "setIncidentType": - itp := command.NewIncidentUpdateTypeProcessor(bch.socketmodeClient, bch.db, bch.logger) - itp.IncidentUpdateType(callback, request, callback.Channel, callback.User) - case "updateTag": - itp := command.NewIncidentUpdateTagsProcessor(bch.socketmodeClient, bch.db, bch.logger) - itp.IncidentUpdateTags(callback, request, callback.Channel, callback.User) - } - incidentEntity, _ := query.FindIncidentByChannelId(bch.db, callback.View.PrivateMetadata) - if incidentEntity != nil { - UpdateMessage(bch.db, incidentEntity, bch.socketmodeClient, nil) - } -} - -func UpdateMessage(db *gorm.DB, incidentEntity *entity.IncidentEntity, socketmodeClient *socketmode.Client, slackClient *slack.Client) { - messages, _ := query.FindMessageByIncidentName(db, incidentEntity.IncidentName) - blocks, _ := houston.IncidentSummarySection(incidentEntity, db) - color := query.GetColorBySeverity(incidentEntity.SeverityId) - att := slack.Attachment{Blocks: blocks, Color: color} - for _, message := range messages { - if socketmodeClient != nil { - socketmodeClient.UpdateMessage(message.SlackChannel, message.MessageTimeStamp, slack.MsgOptionAttachments(att)) - } else { - slackClient.UpdateMessage(message.SlackChannel, message.MessageTimeStamp, slack.MsgOptionAttachments(att)) - } - } -} - -func (bch *HoustonCommandHandler) ProcessButtonHandler(callback slack.InteractionCallback, request *socketmode.Request) { - actionId := callback.ActionCallback.BlockActions[0].ActionID - bch.logger.Info("process button callback event", zap.Any("action_id", actionId), - zap.String("channel", callback.Channel.Name), zap.String("user_id", callback.User.ID), - zap.String("user_name", callback.User.Name)) - - switch actionId { - case "start_incident_button": - bch.logger.Info("start incident button command received", - zap.String("channel", callback.Channel.Name), zap.String("user_id", callback.User.ID), - zap.String("user_name", callback.User.Name)) - sip := command.NewStartIncidentProcessor(bch.socketmodeClient, bch.db, bch.logger) - sip.ProcessStartIncidentButtonCommand(bch.socketmodeClient, request, callback.Channel.ID, callback.TriggerID) - case "show_incidents_button": - bch.logger.Info("show incidents button command received", - zap.String("channel", callback.Channel.Name), zap.String("user_id", callback.User.ID), - zap.String("user_name", callback.User.Name)) - sip := command.ShowIncidentsProcessor(bch.socketmodeClient, bch.db, bch.logger) - sip.ProcessShowIncidentsButtonCommand(callback.Channel, callback.User, callback.TriggerID, request) - case "incident": - bch.processIncidentCommands(callback, request) - case "tags": - bch.processTagsCommands(callback, request) - default: - msgOption := slack.MsgOptionText(fmt.Sprintf("We are working on it"), false) - _, err := bch.socketmodeClient.PostEphemeral(callback.Channel.ID, callback.User.ID, msgOption) - if err != nil { - bch.logger.Error("houston slack PostEphemeral command failed for Working On It features", - zap.String("trigger_id", callback.TriggerID), zap.String("channel_id", callback.Channel.ID), zap.String("user_id", callback.User.ID), zap.Error(err)) - return - } - var payload interface{} - bch.socketmodeClient.Ack(*request, payload) - } -} - -func (bch *HoustonCommandHandler) ProcessMemberJoinEvent(memberJoinedChannelEvent *slackevents.MemberJoinedChannelEvent, request *socketmode.Request) { - memberJoinProcessor := command.NewMemberJoinProcessor(bch.socketmodeClient, bch.db, bch.logger) - memberJoinProcessor.MemberJoinProcessCommand(memberJoinedChannelEvent) - var payload interface{} - bch.socketmodeClient.Ack(*request, payload) -} - -func (bch *HoustonCommandHandler) processIncidentCommands(callback slack.InteractionCallback, request *socketmode.Request) { - action := callback.ActionCallback.BlockActions[0].SelectedOption.Value - switch action { - case "assignIncidentRole": - bch.logger.Info("incident assign button command received", - zap.String("channel", callback.Channel.Name), zap.String("user_id", callback.User.ID), - zap.String("user_name", callback.User.Name)) - iap := command.NewIncidentAssignProcessor(bch.socketmodeClient, bch.db, bch.logger) - iap.IncidentAssignProcess(callback, request) - case "resolveIncident": - bch.logger.Info("incident update button command received", - zap.String("channel", callback.Channel.Name), zap.String("user_id", callback.User.ID), - zap.String("user_name", callback.User.Name)) - irp := command.NewIncidentResolveProcessor(bch.socketmodeClient, bch.db, bch.logger) - irp.IncidentResolveProcess(callback, request) - case "setIncidentStatus": - bch.logger.Info("incident update status command received", - zap.String("channel", callback.Channel.Name), zap.String("user_id", callback.User.ID), - zap.String("user_name", callback.User.Name)) - isp := command.NewIncidentUpdateStatusProcessor(bch.socketmodeClient, bch.db, bch.logger) - isp.IncidentUpdateStatusRequestProcess(callback, request) - case "setIncidentType": - bch.logger.Info("incident update type command received", - zap.String("channel", callback.Channel.Name), zap.String("user_id", callback.User.ID), - zap.String("user_name", callback.User.Name)) - itp := command.NewIncidentUpdateTypeProcessor(bch.socketmodeClient, bch.db, bch.logger) - itp.IncidentUpdateTypeRequestProcess(callback, request) - case "setIncidentSeverity": - bch.logger.Info("incident update severity command received", - zap.String("channel", callback.Channel.Name), zap.String("user_id", callback.User.ID), - zap.String("user_name", callback.User.Name)) - itp := command.NewIncidentUpdateSeverityProcessor(bch.socketmodeClient, bch.db, bch.logger) - itp.IncidentUpdateSeverityRequestProcess(callback, request) - case "setIncidentTitle": - bch.logger.Info("incident update title command received", - zap.String("channel", callback.Channel.Name), zap.String("user_id", callback.User.ID), - zap.String("user_name", callback.User.Name)) - itp := command.NewIncidentUpdateTitleProcessor(bch.socketmodeClient, bch.db, bch.logger) - itp.IncidentUpdateTitleRequestProcess(callback, request) - case "setIncidentDescription": - bch.logger.Info("incident update description command received", - zap.String("channel", callback.Channel.Name), zap.String("user_id", callback.User.ID), - zap.String("user_name", callback.User.Name)) - idp := command.NewIncidentUpdateDescriptionProcessor(bch.socketmodeClient, bch.db, bch.logger) - idp.IncidentUpdateDescriptionRequestProcess(callback, request) - } -} - -func (bch *HoustonCommandHandler) processTagsCommands(callback slack.InteractionCallback, request *socketmode.Request) { - action := callback.ActionCallback.BlockActions[0].SelectedOption.Value - switch action { - case "addTags": - bch.logger.Info("Add Tags command received", - zap.String("channel", callback.Channel.Name), zap.String("user_id", callback.User.ID), - zap.String("user_name", callback.User.Name)) - itp := command.NewIncidentUpdateTagsProcessor(bch.socketmodeClient, bch.db, bch.logger) - itp.IncidentUpdateTagsRequestProcess(callback, request) - case "showTags": - bch.logger.Info("Show Tags command received", - zap.String("channel", callback.Channel.Name), zap.String("user_id", callback.User.ID), - zap.String("user_name", callback.User.Name)) - itp := command.NewIncidentShowTagsProcessor(bch.socketmodeClient, bch.db, bch.logger) - itp.IncidentShowTagsRequestProcess(callback, request) - case "removeTag": - bch.logger.Info("Remove Tags command received", - zap.String("channel", callback.Channel.Name), zap.String("user_id", callback.User.ID), - zap.String("user_name", callback.User.Name)) - itp := command.NewIncidentUpdateTagsProcessor(bch.socketmodeClient, bch.db, bch.logger) - itp.IncidentUpdateTagsRequestProcess(callback, request) - } -} diff --git a/pkg/slack/.DS_Store b/pkg/slackbot/.DS_Store similarity index 100% rename from pkg/slack/.DS_Store rename to pkg/slackbot/.DS_Store diff --git a/pkg/slackbot/channel.go b/pkg/slackbot/channel.go new file mode 100644 index 0000000..c9e41c3 --- /dev/null +++ b/pkg/slackbot/channel.go @@ -0,0 +1,48 @@ +package slackbot + +import ( + "fmt" + "github.com/slack-go/slack" + "go.uber.org/zap" +) + +func (c *Client) FindParticipants(channelId string) ([]string, error) { + request := &slack.GetUsersInConversationParameters{ + ChannelID: channelId, + Limit: 1000, + } + channelInfo, _, err := c.socketModeClient.GetUsersInConversation(request) + if err != nil { + c.logger.Error("find participants failed", zap.String("channel_id", channelId), zap.Error(err)) + return nil, fmt.Errorf("fetch channel conversationInfo failed. err: %v", err) + } + + return channelInfo, nil +} + +func (c *Client) CreateChannel(channelName string) (string, error) { + request := slack.CreateConversationParams{ + ChannelName: channelName, + IsPrivate: false, + } + + channel, err := c.socketModeClient.CreateConversation(request) + if err != nil { + c.logger.Error("create slackbot channel failed", zap.String("channel_name", channelName), zap.Error(err)) + return "", err + } + + c.logger.Info("created slackbot channel successfully", zap.String("channel_name", channelName), zap.String("channel_id", channel.ID)) + return channel.ID, nil +} + +func (c *Client) InviteUsersToConversation(channelId string, userId ...string) { + _, err := c.socketModeClient.InviteUsersToConversation(channelId, userId...) + if err != nil { + c.logger.Error("invite users to conversation failed", + zap.String("channel_id", channelId), zap.Any("user_ids", userId), zap.Error(err)) + return + } + + c.logger.Info("successfully invite users to conversation", zap.String("channel_id", channelId), zap.Any("user_ids", userId)) +} diff --git a/pkg/slackbot/config.go b/pkg/slackbot/config.go new file mode 100644 index 0000000..5b64551 --- /dev/null +++ b/pkg/slackbot/config.go @@ -0,0 +1,18 @@ +package slackbot + +import ( + "github.com/slack-go/slack/socketmode" + "go.uber.org/zap" +) + +type Client struct { + socketModeClient *socketmode.Client + logger *zap.Logger +} + +func NewSlackClient(logger *zap.Logger, socketModeClient *socketmode.Client) *Client { + return &Client{ + socketModeClient: socketModeClient, + logger: logger, + } +} diff --git a/schema.sql b/schema.sql index ecf838a..3a72e13 100644 --- a/schema.sql +++ b/schema.sql @@ -1,53 +1,53 @@ -CREATE TABLE tenant ( - id SERIAL PRIMARY KEY, - name text, - key text, - url text, - vertical text, - version bigint default 0, - created_at timestamp without time zone, - updated_at timestamp without time zone, - deleted_at timestamp without time zone -); - -CREATE TABLE grafana ( - id SERIAL PRIMARY KEY, - name text, - url text, - tenant_id bigint, - status boolean DEFAULT false, - alert_id bigint, - version bigint default 0, - created_at timestamp without time zone, - updated_at timestamp without time zone, - deleted_at timestamp without time zone -); - -CREATE TABLE alerts ( - id SERIAL PRIMARY KEY, - name text, - team_name text, - service_name text, - status boolean DEFAULT false, - version bigint default 0, - created_at timestamp without time zone, - updated_at timestamp without time zone, - deleted_at timestamp without time zone -); +-- CREATE TABLE tenant ( +-- id SERIAL PRIMARY KEY, +-- name text, +-- key text, +-- url text, +-- vertical text, +-- version bigint default 0, +-- created_at timestamp without time zone, +-- updated_at timestamp without time zone, +-- deleted_at timestamp without time zone +-- ); +-- +-- CREATE TABLE grafana ( +-- id SERIAL PRIMARY KEY, +-- name text, +-- url text, +-- tenant_id bigint, +-- status boolean DEFAULT false, +-- alert_id bigint, +-- version bigint default 0, +-- created_at timestamp without time zone, +-- updated_at timestamp without time zone, +-- deleted_at timestamp without time zone +-- ); +-- +-- CREATE TABLE alerts ( +-- id SERIAL PRIMARY KEY, +-- name text, +-- team_name text, +-- service_name text, +-- status boolean DEFAULT false, +-- version bigint default 0, +-- created_at timestamp without time zone, +-- updated_at timestamp without time zone, +-- deleted_at timestamp without time zone +-- ); -CREATE TABLE incidents ( +CREATE TABLE incident ( id SERIAL PRIMARY KEY, title text, description text, - status varchar(50), - severity_id bigint, + status integer not null, + severity_id integer not null, incident_name text, slack_channel varchar(100), detection_time timestamp without time zone, - customer_impact_start_time timestamp without time zone, - customer_impact_end_time timestamp without time zone, - teams_id bigint, + start_time timestamp without time zone, + end_time timestamp without time zone, + team_id int not null, jira_id varchar(100), confluence_id varchar(100), created_by varchar(100), @@ -57,17 +57,13 @@ CREATE TABLE incidents ( enable_reminder boolean DEFAULT false, created_at timestamp without time zone, updated_at timestamp without time zone, - deleted_at timestamp without time zone, - version bigint default 0 + deleted_at timestamp without time zone ); -CREATE TABLE teams ( +CREATE TABLE team ( id SERIAL PRIMARY KEY, - name varchar(50), - oncall_handle varchar(100), - secondary_oncall_handle varchar(100), - manager_handle varchar(100), - secondary_manager_handle varchar(100), + name varchar(50) unique not null, + slack_user_ids varchar[] default '{}', active boolean DEFAULT false, version bigint default 0, created_at timestamp without time zone, @@ -75,112 +71,52 @@ CREATE TABLE teams ( deleted_at timestamp without time zone ); -CREATE TABLE teams_severity_user_mapping ( - id SERIAL PRIMARY KEY, - entity_type varchar(100), - entity_id bigint, - users_id bigint, - version bigint default 0, - default_add_in_incidents boolean DEFAULT false, - team_role varchar(100), +create table team_tag ( + id serial primary key, + team_id int not null, + tag_id int not null, + optional boolean default false, created_at timestamp without time zone, updated_at timestamp without time zone, deleted_at timestamp without time zone ); - -CREATE TABLE users ( +CREATE TABLE houston_user ( id SERIAL PRIMARY KEY, name varchar(50), slack_user_id varchar(100), active boolean DEFAULT true ); - CREATE TABLE severity ( id SERIAL PRIMARY KEY, name varchar(50), description text, version bigint default 0, sla int, + slack_user_ids varchar[] default '{}', created_at timestamp without time zone, updated_at timestamp without time zone, deleted_at timestamp without time zone ); -CREATE TABLE teams_tags_mapping ( +CREATE TABLE tag ( id SERIAL PRIMARY KEY, - teams_id bigint, - tag_id bigint, - version bigint default 0, + name varchar not null, + label text not null, + place_holder text, + action_id varchar(100) not null, + type varchar(100) not null, created_at timestamp without time zone, updated_at timestamp without time zone, deleted_at timestamp without time zone ); -CREATE TABLE incidents_tags_mapping ( - id SERIAL PRIMARY KEY, - incident_id bigint, - tag_id bigint, - version bigint default 0, - created_at timestamp without time zone, - updated_at timestamp without time zone, - deleted_at timestamp without time zone -); - -CREATE TABLE incidents_tags_contributing_factor_mapping ( - id SERIAL PRIMARY KEY, - incident_id bigint, - contributing_factor_id bigint, - version bigint default 0, - created_at timestamp without time zone, - updated_at timestamp without time zone, - deleted_at timestamp without time zone -); - -CREATE TABLE contributing_factor ( - id SERIAL PRIMARY KEY, - label varchar(100), - version bigint default 0, - created_at timestamp without time zone, - updated_at timestamp without time zone, - deleted_at timestamp without time zone -); - -CREATE TABLE incidents_tags_customer_mapping ( - id SERIAL PRIMARY KEY, - incident_id bigint, - customer_tags_id bigint, - version bigint default 0, - created_at timestamp without time zone, - updated_at timestamp without time zone, - deleted_at timestamp without time zone -); - -CREATE TABLE customer_tags ( - id SERIAL PRIMARY KEY, - label varchar(100), - version bigint default 0, - created_at timestamp without time zone, - updated_at timestamp without time zone, - deleted_at timestamp without time zone -); - -CREATE TABLE incidents_tags_data_platform_mapping ( - id SERIAL PRIMARY KEY, - incident_id bigint, - data_platform_tag text, - version bigint default 0, - created_at timestamp without time zone, - updated_at timestamp without time zone, - deleted_at timestamp without time zone -); - -CREATE TABLE tags ( - id SERIAL PRIMARY KEY, - label text, - version bigint default 0, - created_at timestamp without time zone, +create table tag_value ( + id serial primary key, + tag_id int not null, + value varchar not null, + create_at timestamp without time zone, updated_at timestamp without time zone, deleted_at timestamp without time zone ); @@ -189,28 +125,36 @@ CREATE TABLE incident_status ( id SERIAL PRIMARY KEY, name varchar(50), description text, + is_terminal_status boolean default false, version bigint default 0, created_at timestamp without time zone, updated_at timestamp without time zone, deleted_at timestamp without time zone ); -CREATE TABLE incident_roles ( +create table role ( + id serial primary key, + name varchar(100) not null, + created_at timestamp without time zone, + updated_at timestamp without time zone, + deleted_at timestamp without time zone +); + +CREATE TABLE incident_role ( id SERIAL PRIMARY KEY, - incident_id bigint, + incident_id integer not null, role varchar(100), - assigned_to_user_slack_id varchar(100), - assigned_by_user_slack_id varchar(100), - version bigint default 0, + assigned_to varchar(100), + assigned_by varchar(100), created_at timestamp without time zone, updated_at timestamp without time zone, deleted_at timestamp without time zone ); -CREATE TABLE messages ( +CREATE TABLE incident_channel ( id SERIAL PRIMARY KEY, slack_channel varchar(100), - incident_name text, + incident_id int not null, message_timestamp varchar(100), version bigint default 0, created_at timestamp without time zone, @@ -218,14 +162,25 @@ CREATE TABLE messages ( deleted_at timestamp without time zone ); -CREATE TABLE audit ( - id SERIAL PRIMARY KEY, - incident_id bigint, - event text, - user_name varchar(100), - user_id varchar(100), - version bigint default 0, +create table incident_tag ( + id serial primary key, + incident_id int not null, + tag_id int not null, + tag_value_ids int[] default '{}', + free_text_value text, created_at timestamp without time zone, updated_at timestamp without time zone, deleted_at timestamp without time zone -); \ No newline at end of file +); + +-- CREATE TABLE audit ( +-- id SERIAL PRIMARY KEY, +-- incident_id bigint, +-- event text, +-- user_name varchar(100), +-- user_id varchar(100), +-- version bigint default 0, +-- created_at timestamp without time zone, +-- updated_at timestamp without time zone, +-- deleted_at timestamp without time zone +-- ); \ No newline at end of file