TP-48512 | Implementation of RCA and tag migration (#296)
* TP-38709 | Merging the changes to master on the logfix * TP-48512 | Added button element for RCA section and implemented fill rca details * TP-48512 | Small fixes * TP-48512 | adding unit tests * TP-48512 | added unit tests * TP-48512 | updated color code for rca card * TP-48512 | Removed duplicate interface * TP-48512 | Added one more unit test * TP-48512 | added comments for jira link validation and update * TP-48512 | Merging the changes to master on the logfix # Conflicts: # cmd/app/handler/slack_handler.go * TP-48512 | Added button element for RCA section and implemented fill rca details # Conflicts: # common/util/common_util.go # common/util/constant.go # internal/processor/action/incident_resolve_action.go # internal/processor/action/incident_update_jira-links_action.go # internal/processor/action/incident_update_resolution_text_action.go # internal/processor/action/view/incident_resolution_text.go # internal/processor/action/view/incident_section.go # service/slack/slack_service.go * TP-48512 | Small fixes * TP-48512 | adding unit tests * TP-48512 | added unit tests # Conflicts: # Makefile # service/incident/incident_service_v2_interface.go * TP-48512 | updated color code for rca card * TP-48512 | Removed duplicate interface * TP-48512 | Added one more unit test * TP-48512 | added comments for jira link validation and update * TP-48512 | Fixed merge conflicts * TP-48512 | Fixed merge conflicts * TP-48512 | Fixed merge conflicts * TP-48512 | Added sql migration script for adding tags * TP-48512 | Updated sql migration script for adding tags * TP-48512 | Fixed merge conflicts and updated tags in sql migration script
This commit is contained in:
2
Makefile
2
Makefile
@@ -54,5 +54,7 @@ generatemocks:
|
||||
cd $(CURDIR)/repository/rcaInput && minimock -i IRcaInputRepository -s _mock.go -o $(CURDIR)/mocks
|
||||
cd $(CURDIR)/service/teamService && minimock -i ITeamServiceV2 -s _mock.go -o $(CURDIR)/mocks
|
||||
cd $(CURDIR)/model/user && minimock -i IUserRepositoryInterface -s _mock.go -o $(CURDIR)/mocks
|
||||
cd $(CURDIR)/model/tag && minimock -i ITagRepository -s _mock.go -o $(CURDIR)/mocks
|
||||
cd $(CURDIR)/model/incident && minimock -i IIncidentRepository -s _mock.go -o $(CURDIR)/mocks
|
||||
cd $(CURDIR)/pkg/monitoringService && minimock -i MonitoringServiceActions -s _mock.go -o $(CURDIR)/mocks
|
||||
cd $(CURDIR)/service/krakatoa && minimock -i IKrakatoaService -s _mock.go -o $(CURDIR)/mocks
|
||||
|
||||
@@ -90,8 +90,8 @@ func NewSlackHandler(gormClient *gorm.DB, socketModeClient *socketmode.Client) *
|
||||
socketModeClient, incidentService, teamService, severityService, tagService, slackbotClient, incidentServiceV2, slackService, rcaService,
|
||||
),
|
||||
viewSubmissionProcessor: processor.NewViewSubmissionProcessor(
|
||||
socketModeClient, incidentService, teamService, severityService, tagService, teamService, slackbotClient, gormClient, incidentServiceV2,
|
||||
),
|
||||
socketModeClient, incidentService, teamService, severityService, tagService, teamService, slackbotClient, gormClient,
|
||||
rcaService, incidentServiceV2),
|
||||
userChangeEventProcessor: processor.NewUserChangeEventProcessor(
|
||||
socketModeClient, userService,
|
||||
),
|
||||
|
||||
@@ -238,6 +238,40 @@ func ConvertSliceToMapOfString(input []string) map[string]string {
|
||||
return output
|
||||
}
|
||||
|
||||
type CompareArrayResults struct {
|
||||
UniqueElementsInArrayA []string
|
||||
UniqueElementsInArrayB []string
|
||||
CommonElements []string
|
||||
}
|
||||
|
||||
// CompareAndGetStringArrayResults Takes two arrays as inputs and returns the common values of two arrays and unique values of both arrays
|
||||
func CompareAndGetStringArrayResults(arrayA []string, arrayB []string) CompareArrayResults {
|
||||
uniqueInA := make([]string, 0)
|
||||
uniqueInB := make([]string, 0)
|
||||
common := make([]string, 0)
|
||||
map1 := make(map[string]bool)
|
||||
map2 := make(map[string]bool)
|
||||
for _, val := range arrayA {
|
||||
map1[val] = true
|
||||
}
|
||||
for _, val := range arrayB {
|
||||
map2[val] = true
|
||||
}
|
||||
for key := range map2 {
|
||||
if _, ok := map1[key]; !ok {
|
||||
uniqueInB = append(uniqueInB, key) //if element not present in map2 append elements in toBeAdded slice
|
||||
} else {
|
||||
common = append(common, key) // Add common elements
|
||||
}
|
||||
}
|
||||
for key := range map1 {
|
||||
if _, ok := map2[key]; !ok {
|
||||
uniqueInA = append(uniqueInA, key) //if element not present in map2 append elements in toBeAdded slice
|
||||
}
|
||||
}
|
||||
return CompareArrayResults{uniqueInA, uniqueInB, common}
|
||||
}
|
||||
|
||||
func IsBlank(input string) bool {
|
||||
trimmedInput := strings.TrimSpace(input)
|
||||
return trimmedInput == ""
|
||||
|
||||
@@ -7,7 +7,6 @@ const (
|
||||
ShowIncidents = "show_incidents"
|
||||
HelpCommand = "help_button"
|
||||
Incident = "incident"
|
||||
Tags = "tags"
|
||||
AssignIncidentRole = "assign_incident_role"
|
||||
ResolveIncident = "resolve_incident"
|
||||
SetIncidentStatus = "set_incident_status"
|
||||
@@ -15,11 +14,11 @@ const (
|
||||
SetIncidentSeverity = "set_incident_severity"
|
||||
SetIncidentTitle = "set_incident_title"
|
||||
SetIncidentDescription = "set_incident_description"
|
||||
SetRCA = "set_rca"
|
||||
SetRCADetails = "set_rca_details"
|
||||
RCASection = "rca_section"
|
||||
ShowRCADetails = "show_rca_details"
|
||||
SetRCASummary = "set_rca_summary"
|
||||
SetJiraLinks = "set_jira_links"
|
||||
AddTags = "add_tags"
|
||||
ShowTags = "show_tags"
|
||||
RemoveTag = "remove_tags"
|
||||
MarkIncidentDuplicate = "mark_incident_duplicate"
|
||||
)
|
||||
|
||||
@@ -33,8 +32,8 @@ const (
|
||||
SetIncidentDescriptionSubmit = "set_incident_description_submit"
|
||||
SetIncidentSeveritySubmit = "set_incident_severity_submit"
|
||||
SetIncidentTypeSubmit = "set_incident_type_submit"
|
||||
UpdateTagSubmit = "updateTagSubmit"
|
||||
SetIncidentRcaSubmit = "set_rca_submit"
|
||||
SetIncidentRCADetailsSubmit = "set_rca_details_submit"
|
||||
IncidentResolveSubmit = "resolve_incident_submit"
|
||||
SetIncidentJiraLinksSubmit = "set_Jira_links_submit"
|
||||
ShowIncidentSubmit = "show_incident_submit"
|
||||
MarkIncidentDuplicateSubmit = "mark_incident_duplicate_submit"
|
||||
@@ -61,8 +60,6 @@ const (
|
||||
ConferenceMessage = "To discuss, use this *<%s|Meet link>*"
|
||||
)
|
||||
|
||||
type DocumentServiceFileTypeKeys string
|
||||
|
||||
type ContentType string
|
||||
|
||||
const (
|
||||
@@ -88,10 +85,26 @@ const (
|
||||
RcaStatusPending = "PENDING"
|
||||
)
|
||||
|
||||
// service names for uploading documents to cloud
|
||||
type DocumentServiceFileTypeKeys string
|
||||
|
||||
// DocumentServiceProvider service names for uploading documents to cloud
|
||||
const (
|
||||
DocumentServiceProvider = "SA_DOCUMENT_SERVICE"
|
||||
)
|
||||
|
||||
const (
|
||||
JiraLinksLabel = "Jira link(s)"
|
||||
RCASummaryLabel = "RCA summary"
|
||||
RCADetailsLabel = "RCA details"
|
||||
JiraIdSeparator = "/browse/"
|
||||
)
|
||||
|
||||
// slack element types
|
||||
const (
|
||||
MarkDownElementType = "mrkdwn"
|
||||
PlainTextType = "plain_text"
|
||||
)
|
||||
|
||||
const (
|
||||
IdParam = "id"
|
||||
)
|
||||
|
||||
66
db/migration/000008_add_tag_tagvalues_columns.up.sql
Normal file
66
db/migration/000008_add_tag_tagvalues_columns.up.sql
Normal file
@@ -0,0 +1,66 @@
|
||||
alter table tag add column if not exists optional boolean not null default false;
|
||||
alter table tag add column if not exists active boolean not null default false;
|
||||
alter table tag add column if not exists display_order smallint NOT NULL DEFAULT 0;
|
||||
alter table tag_value add column if not exists active boolean not null default false;
|
||||
insert into tag ("name","label","place_holder","action_id","type","created_at","updated_at","deleted_at","active","optional","display_order")
|
||||
values
|
||||
('contributing_factors','Contributing factors','select contributing factors','contributing_factors_action_id','single_value','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE,FALSE,2),
|
||||
('business_affected','Business affected','select business affected','business_affected_action_id','multi_value','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE,FALSE,1),
|
||||
('additional_tags','Additional tags','select additional tags','additional_tags_action_id','multi_value','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE,TRUE,3);
|
||||
insert into tag_value ("tag_id","value","create_at","updated_at","deleted_at","active")
|
||||
values
|
||||
(22,'Internal','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(22,'Vendor','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(23,'UPI','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(23,'AMC','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(23,'Rewards','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(23,'Gold','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(23,'GI','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(23,'HL','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(23,'PL','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'LoginPage','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'PL_BasicDetails','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'PL_Permission','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'PL_MFIScreen','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'PL_WorkDetails','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'PL_GenerateOffer','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'PL_PennyDrop','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'PL_AutoPaySetup','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'PL_KYCPage','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'PL_Disbursement','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'PL_SignAgreement','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'PL_Loan/Offer_Details','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'PL_PostPurchase','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'HL_Property_Details','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'HL_Employment_Details','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'HL_Document_Upload','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'HL_WorkDetails','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'HL_PersonalDetails','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'HL_IncomeVerification','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'HL_KYC','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'HL_CommunicationAdd','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'HL_MandateSetup','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'HL_Disbursement','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'HL_PostPurchase','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'AMC_KYC','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'AMC_PennyDrop','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'AMC_MandateSetup','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'AMC_PortfolioPage','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'AMC_SIPPage','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'AMC_OrdersPage','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'AMC_FundLandingPage','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'DG_KYC','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'DG_LandingPage','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'DG_TransactionPage','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'DG_SetUpSIP','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'DG_SellPage','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'DG_BuyPage','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'GI_Renewal','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'GI_KYC','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'GI_AddMember','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'GI_NCB','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'GI_PaymentIssue','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'GI_DocumentGeneration','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'GI_Claim','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'GI_IOS','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE),
|
||||
(24,'GI_exceptions','2023-12-01 07:10:20.550404','2023-12-01 07:10:20.550404',NULL,TRUE);
|
||||
74
internal/processor/action/incident_jira_links_action.go
Normal file
74
internal/processor/action/incident_jira_links_action.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/slack-go/slack"
|
||||
"github.com/spf13/viper"
|
||||
"houston/common/util"
|
||||
"houston/internal/processor/action/view"
|
||||
"houston/logger"
|
||||
"houston/model/incident"
|
||||
incidentService "houston/service/incident"
|
||||
slack2 "houston/service/slack"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type IncidentJiraLinksAction struct {
|
||||
incidentService incidentService.IIncidentService
|
||||
slackService slack2.ISlackService
|
||||
}
|
||||
|
||||
const (
|
||||
logTag = "[IncidentJiraLinksAction]"
|
||||
)
|
||||
|
||||
func NewIncidentJiraLinksAction(
|
||||
incidentService incidentService.IIncidentService,
|
||||
slackService slack2.ISlackService,
|
||||
) *IncidentJiraLinksAction {
|
||||
return &IncidentJiraLinksAction{
|
||||
incidentService: incidentService,
|
||||
slackService: slackService,
|
||||
}
|
||||
}
|
||||
|
||||
func (action *IncidentJiraLinksAction) getJiraLinksBlock(initialValue string) *slack.InputBlock {
|
||||
jiraLinksBlockBasicData := view.BasicInputElementData{Header: "Jira link(s)", PlaceHolder: "Add comma separated jira links here...", ActionId: util.SetJiraLinks, Optional: true, MaxLength: 1500, MultiLine: true}
|
||||
jiraLinksBlockData := view.SimpleInputBlockElementData{BasicData: jiraLinksBlockBasicData, InitialValue: initialValue}
|
||||
return view.CreatePlainTextInputBlock(jiraLinksBlockData)
|
||||
}
|
||||
|
||||
func (action *IncidentJiraLinksAction) updateJiraLinks(jiraLinks string, callback slack.InteractionCallback, incidentEntity *incident.IncidentEntity) error {
|
||||
channelID := callback.View.PrivateMetadata
|
||||
formattedJiraLinks := strings.Split(
|
||||
strings.ReplaceAll(strings.ReplaceAll(jiraLinks, "\n", ""), " ", ""),
|
||||
",",
|
||||
)
|
||||
compareResults := util.CompareAndGetStringArrayResults(incidentEntity.JiraLinks, formattedJiraLinks)
|
||||
//Update jira links only if there is a change. Validate by checking if there are any unique elements in either array
|
||||
//If there are no unique elements in either array, then there is no change and skip the update
|
||||
if !(len(compareResults.UniqueElementsInArrayA) == 0 && len(compareResults.UniqueElementsInArrayB) == 0) {
|
||||
jiraLinksToBeUpdated := append(compareResults.CommonElements, compareResults.UniqueElementsInArrayB...)
|
||||
//Below validation make sure that blank jira links are not to be tested for valid jira link
|
||||
if len(jiraLinksToBeUpdated) > 0 && jiraLinksToBeUpdated[0] != "" {
|
||||
for _, link := range jiraLinksToBeUpdated {
|
||||
//Validate jira link
|
||||
if !strings.HasPrefix(link, viper.GetString("navi.jira.base.url")) {
|
||||
err := action.slackService.PostEphemeralByChannelID(fmt.Sprintf("%s is not a valid jira link", link), callback.User.ID, false, channelID)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("%s failed to post jira link validation failure ephemeral to slack channel: %s", logTag, incidentEntity.IncidentName))
|
||||
return err
|
||||
}
|
||||
return errors.New(fmt.Sprintf("%s is a invalid jira link", link))
|
||||
}
|
||||
}
|
||||
}
|
||||
err := action.incidentService.UpdateIncidentJiraLinksEntity(incidentEntity, callback.User.ID, jiraLinksToBeUpdated)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("%s unable to update jira link(s) for incident %s", logTag, incidentEntity.IncidentName))
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
84
internal/processor/action/incident_jira_links_action_test.go
Normal file
84
internal/processor/action/incident_jira_links_action_test.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gojuno/minimock/v3"
|
||||
"github.com/slack-go/slack"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"houston/logger"
|
||||
"houston/mocks"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func getJiraLinks() []string {
|
||||
return []string{"https://navihq.atlassian.net/browse/TP-44155", "https://navihq.atlassian.net/browse/TP-44157"}
|
||||
}
|
||||
|
||||
type IncidentJiraLinksActionSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func TestIncidentJiraLinksAction(t *testing.T) {
|
||||
suite.Run(t, new(IncidentJiraLinksActionSuite))
|
||||
}
|
||||
|
||||
func (suite *IncidentJiraLinksActionSuite) SetupSuite() {
|
||||
logger.InitLogger()
|
||||
viper.Set("navi.jira.base.url", "https://navihq.atlassian.net/browse/")
|
||||
}
|
||||
|
||||
func (suite *IncidentJiraLinksActionSuite) TestUpdateJiraLinksFailureAtUpdatingEntity() {
|
||||
controller := minimock.NewController(suite.T())
|
||||
suite.T().Cleanup(controller.Finish)
|
||||
incidentService := mocks.NewIIncidentServiceMock(controller)
|
||||
slackService := mocks.NewISlackServiceMock(controller)
|
||||
incidentService.UpdateIncidentJiraLinksEntityMock.Return(errors.New("failure while updating jira links"))
|
||||
jiraLinksActions := NewIncidentJiraLinksAction(incidentService, slackService)
|
||||
err := jiraLinksActions.updateJiraLinks(strings.Join(getJiraLinks(), ", "), slack.InteractionCallback{}, getMockIncidentEntity())
|
||||
suite.EqualError(err, "failure while updating jira links")
|
||||
}
|
||||
|
||||
func (suite *IncidentJiraLinksActionSuite) TestUpdateJiraLinksFailureForInvalidJiraLink() {
|
||||
controller := minimock.NewController(suite.T())
|
||||
suite.T().Cleanup(controller.Finish)
|
||||
incidentService := mocks.NewIIncidentServiceMock(controller)
|
||||
slackService := mocks.NewISlackServiceMock(controller)
|
||||
slackService.PostEphemeralByChannelIDMock.Return(nil)
|
||||
jiraLinksActions := NewIncidentJiraLinksAction(incidentService, slackService)
|
||||
err := jiraLinksActions.updateJiraLinks("dsa", slack.InteractionCallback{}, getMockIncidentEntity())
|
||||
suite.EqualError(err, "dsa is a invalid jira link")
|
||||
}
|
||||
|
||||
func (suite *IncidentJiraLinksActionSuite) TestUpdateJiraLinksSuccessBlankValue() {
|
||||
controller := minimock.NewController(suite.T())
|
||||
suite.T().Cleanup(controller.Finish)
|
||||
incidentService := mocks.NewIIncidentServiceMock(controller)
|
||||
slackService := mocks.NewISlackServiceMock(controller)
|
||||
incidentService.UpdateIncidentJiraLinksEntityMock.Return(nil)
|
||||
jiraLinksActions := NewIncidentJiraLinksAction(incidentService, slackService)
|
||||
err := jiraLinksActions.updateJiraLinks("", slack.InteractionCallback{}, getMockIncidentEntity())
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *IncidentJiraLinksActionSuite) TestUpdateJiraLinksSuccessDiffValue() {
|
||||
controller := minimock.NewController(suite.T())
|
||||
suite.T().Cleanup(controller.Finish)
|
||||
incidentService := mocks.NewIIncidentServiceMock(controller)
|
||||
slackService := mocks.NewISlackServiceMock(controller)
|
||||
incidentService.UpdateIncidentJiraLinksEntityMock.Return(nil)
|
||||
jiraLinksActions := NewIncidentJiraLinksAction(incidentService, slackService)
|
||||
err := jiraLinksActions.updateJiraLinks(strings.Join(getJiraLinks(), ", "), slack.InteractionCallback{}, getMockIncidentEntity())
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *IncidentJiraLinksActionSuite) TestUpdateJiraLinksSuccessSameValue() {
|
||||
controller := minimock.NewController(suite.T())
|
||||
suite.T().Cleanup(controller.Finish)
|
||||
incidentService := mocks.NewIIncidentServiceMock(controller)
|
||||
slackService := mocks.NewISlackServiceMock(controller)
|
||||
jiraLinksActions := NewIncidentJiraLinksAction(incidentService, slackService)
|
||||
err := jiraLinksActions.updateJiraLinks(strings.Join(getMockIncidentEntity().JiraLinks, ", "), slack.InteractionCallback{}, getMockIncidentEntity())
|
||||
suite.NoError(err)
|
||||
}
|
||||
144
internal/processor/action/incident_rca_details_action.go
Normal file
144
internal/processor/action/incident_rca_details_action.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"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/logger"
|
||||
"houston/model/incident"
|
||||
"houston/model/severity"
|
||||
"houston/model/tag"
|
||||
"houston/model/team"
|
||||
"houston/service/rca"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type IncidentRCASectionAction struct {
|
||||
client *socketmode.Client
|
||||
incidentRepository *incident.Repository
|
||||
teamRepository *team.Repository
|
||||
tagRepository *tag.Repository
|
||||
severityRepository *severity.Repository
|
||||
tagsAction *IncidentTagsAction
|
||||
rcaSummaryAction *IncidentRCASummaryAction
|
||||
jiraAction *IncidentJiraLinksAction
|
||||
rcaService *rca.RcaService
|
||||
}
|
||||
|
||||
func NewIncidentRCASectionAction(client *socketmode.Client, incidentRepo *incident.Repository,
|
||||
teamService *team.Repository, tagService *tag.Repository, severityRepository *severity.Repository, tagsAction *IncidentTagsAction, rcaSummaryAction *IncidentRCASummaryAction, jiraAction *IncidentJiraLinksAction, rcaService *rca.RcaService) *IncidentRCASectionAction {
|
||||
return &IncidentRCASectionAction{
|
||||
client: client,
|
||||
incidentRepository: incidentRepo,
|
||||
teamRepository: teamService,
|
||||
tagRepository: tagService,
|
||||
severityRepository: severityRepository,
|
||||
tagsAction: tagsAction,
|
||||
rcaSummaryAction: rcaSummaryAction,
|
||||
jiraAction: jiraAction,
|
||||
rcaService: rcaService,
|
||||
}
|
||||
}
|
||||
|
||||
func (action *IncidentRCASectionAction) ProcessIncidentRCAActionRequestForSlashCommand(channelId string, triggerId string, request *socketmode.Request, requesterType util.ViewSubmissionType) error {
|
||||
incidentEntity, err := action.incidentRepository.FindIncidentByChannelId(channelId)
|
||||
//Validates if incident is valid
|
||||
if err != nil || incidentEntity == nil {
|
||||
logger.Error(fmt.Sprintf("failure while getting incident entity for channel: %v", channelId))
|
||||
return err
|
||||
}
|
||||
tagsBlock := action.tagsAction.getTagsBlock(incidentEntity.ID)
|
||||
rcaBlock := action.rcaSummaryAction.getRCASummaryBlock(incidentEntity.RCA)
|
||||
jiraBlock := action.jiraAction.getJiraLinksBlock(strings.Join(incidentEntity.JiraLinks, ", "))
|
||||
blocks := append(tagsBlock, *rcaBlock, *jiraBlock)
|
||||
|
||||
modalRequest := view.BuildIncidentRCASectionModal(channelId, blocks, requesterType)
|
||||
_, err = action.client.OpenView(triggerId, modalRequest)
|
||||
if err != nil {
|
||||
logger.Error("houston slackbot open view command for ProcessIncidentRCAActionRequest failed.",
|
||||
zap.String("trigger_id", triggerId), zap.String("channel_id", channelId), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
var payload interface{}
|
||||
action.client.Ack(*request, payload)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (action *IncidentRCASectionAction) ProcessIncidentRCAActionRequest(callback slack.InteractionCallback, request *socketmode.Request, requesterType util.ViewSubmissionType) {
|
||||
err := action.ProcessIncidentRCAActionRequestForSlashCommand(callback.Channel.ID, callback.TriggerID, request, requesterType)
|
||||
if err != nil {
|
||||
logger.Error("houston slackbot open view command for ProcessIncidentRCAActionRequest failed.",
|
||||
zap.String("trigger_id", callback.TriggerID), zap.String("channel_id", callback.Channel.ID), zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func (action *IncidentRCASectionAction) PerformSetIncidentRCADetailsAction(callback slack.InteractionCallback, request *socketmode.Request, requesterType util.ViewSubmissionType) {
|
||||
incidentEntity, err := action.incidentRepository.FindIncidentByChannelId(callback.View.PrivateMetadata)
|
||||
if err != nil || incidentEntity == nil {
|
||||
logger.Error(fmt.Sprintf("failed to get the incicent for channel id: %v", callback.View.PrivateMetadata))
|
||||
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
|
||||
}
|
||||
}
|
||||
var payload interface{}
|
||||
action.client.Ack(*request, payload)
|
||||
err = action.tagsAction.updateTags(actions, callback, incidentEntity)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to update the incicent tags for incident id: %v", incidentEntity.ID))
|
||||
}
|
||||
rcaValue := actions[util.SetRCASummary].Value
|
||||
err = action.rcaSummaryAction.updateRCASummary(rcaValue, incidentEntity)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to update rca summary for incident id: %v", incidentEntity.ID))
|
||||
}
|
||||
jiraLinksValue := actions[util.SetJiraLinks].Value
|
||||
err = action.jiraAction.updateJiraLinks(jiraLinksValue, callback, incidentEntity)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to update jira link(s) for incident id: %v", incidentEntity.ID))
|
||||
}
|
||||
updatedIncidentEntity, _ := action.incidentRepository.FindIncidentById(incidentEntity.ID)
|
||||
tagValuesMap, _ := action.tagsAction.getIncidentTagValuesAsMap(incidentEntity.ID)
|
||||
action.postRCADetailsBlock(updatedIncidentEntity, tagValuesMap)
|
||||
action.performPostUpdateActions(requesterType, callback, request)
|
||||
}
|
||||
|
||||
func (action *IncidentRCASectionAction) PerformShowRCADetailsAction(callback slack.InteractionCallback, request *socketmode.Request) {
|
||||
incidentEntity, err := action.incidentRepository.FindIncidentByChannelId(callback.Channel.ID)
|
||||
if err != nil || incidentEntity == nil {
|
||||
logger.Error(fmt.Sprintf("failed to get the incicent for channel id: %v", callback.View.PrivateMetadata))
|
||||
return
|
||||
}
|
||||
var payload interface{}
|
||||
action.client.Ack(*request, payload)
|
||||
tagValuesMap, _ := action.tagsAction.getIncidentTagValuesAsMap(incidentEntity.ID)
|
||||
action.postRCADetailsBlock(incidentEntity, tagValuesMap)
|
||||
}
|
||||
|
||||
func (action *IncidentRCASectionAction) performPostUpdateActions(requesterType util.ViewSubmissionType, callback slack.InteractionCallback, request *socketmode.Request) {
|
||||
switch requesterType {
|
||||
case util.IncidentResolveSubmit:
|
||||
resolveAction := ResolveIncidentAction{action.client, action.incidentRepository, action.tagRepository, action.teamRepository, action.severityRepository, action.rcaService}
|
||||
resolveAction.IncidentResolveProcess(callback, request)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (action *IncidentRCASectionAction) postRCADetailsBlock(entity *incident.IncidentEntity, tagValueMaps map[string][]string) {
|
||||
blocks := view.ConstructShowRCADetailsBlock(entity, tagValueMaps)
|
||||
color := util.GetColorBySeverity(4)
|
||||
att := slack.Attachment{Blocks: blocks, Color: color}
|
||||
_, _, err := action.client.PostMessage(entity.SlackChannel, slack.MsgOptionAttachments(att))
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("exception occurred while posting RCA updates for incident %s", entity.IncidentName), zap.Error(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
43
internal/processor/action/incident_rca_summary_action.go
Normal file
43
internal/processor/action/incident_rca_summary_action.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/slack-go/slack"
|
||||
"houston/common/util"
|
||||
"houston/internal/processor/action/view"
|
||||
"houston/logger"
|
||||
"houston/model/incident"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type IncidentRCASummaryAction struct {
|
||||
incidentRepository incident.IIncidentRepository
|
||||
}
|
||||
|
||||
func NewIncidentRCASummaryAction(incidentService incident.IIncidentRepository) *IncidentRCASummaryAction {
|
||||
return &IncidentRCASummaryAction{
|
||||
incidentRepository: incidentService,
|
||||
}
|
||||
}
|
||||
|
||||
func (action *IncidentRCASummaryAction) getRCASummaryBlock(rcaInitialValue string) *slack.InputBlock {
|
||||
rcaBlockBasicData := view.BasicInputElementData{Header: "RCA summary", PlaceHolder: "Write RCA summary here", ActionId: util.SetRCASummary, Optional: false, MultiLine: true, MaxLength: 3000}
|
||||
rcaBlockData := view.SimpleInputBlockElementData{BasicData: rcaBlockBasicData, InitialValue: rcaInitialValue}
|
||||
return view.CreatePlainTextInputBlock(rcaBlockData)
|
||||
}
|
||||
|
||||
func (action *IncidentRCASummaryAction) updateRCASummary(rcaValue string, incidentEntity *incident.IncidentEntity) error {
|
||||
trimmedRCA := strings.TrimSpace(rcaValue)
|
||||
//Update RCA only if provided RCA is nt blank and different from existing
|
||||
if trimmedRCA != incidentEntity.RCA {
|
||||
incidentEntity.RCA = trimmedRCA
|
||||
err := action.incidentRepository.UpdateIncident(incidentEntity)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("IncidentUpdateRca error for incident %s : %s", incidentEntity.IncidentName, err.Error()))
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
logger.Info(fmt.Sprintf("RCA is not changed for incident %s", incidentEntity.IncidentName))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gojuno/minimock/v3"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"houston/logger"
|
||||
"houston/mocks"
|
||||
"houston/model/incident"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func getMockIncidentEntity() *incident.IncidentEntity {
|
||||
return &incident.IncidentEntity{IncidentName: "test", RCA: "test", JiraLinks: []string{"https://navihq.atlassian.net/browse/TP-44157", "https://navihq.atlassian.net/browse/TP-44158"}}
|
||||
}
|
||||
|
||||
type IncidentRCASummaryActionSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func TestIncidentRCASummaryActions(t *testing.T) {
|
||||
suite.Run(t, new(IncidentRCASummaryActionSuite))
|
||||
}
|
||||
|
||||
func (suite *IncidentRCASummaryActionSuite) SetupSuite() {
|
||||
logger.InitLogger()
|
||||
}
|
||||
|
||||
func (suite *IncidentRCASummaryActionSuite) TestUpdateRCASummaryFailure() {
|
||||
controller := minimock.NewController(suite.T())
|
||||
suite.T().Cleanup(controller.Finish)
|
||||
incidentRepo := mocks.NewIIncidentRepositoryMock(controller)
|
||||
incidentRepo.UpdateIncidentMock.Return(errors.New("failure while updating incident"))
|
||||
rcaSummaryActions := NewIncidentRCASummaryAction(incidentRepo)
|
||||
err := rcaSummaryActions.updateRCASummary("", getMockIncidentEntity())
|
||||
suite.EqualError(err, "failure while updating incident")
|
||||
}
|
||||
|
||||
func (suite *IncidentRCASummaryActionSuite) TestUpdateRCASummarySuccessForSameRCAValue() {
|
||||
controller := minimock.NewController(suite.T())
|
||||
suite.T().Cleanup(controller.Finish)
|
||||
incidentRepo := mocks.NewIIncidentRepositoryMock(controller)
|
||||
rcaSummaryActions := NewIncidentRCASummaryAction(incidentRepo)
|
||||
err := rcaSummaryActions.updateRCASummary("test", getMockIncidentEntity())
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *IncidentRCASummaryActionSuite) TestUpdateRCASummarySuccess() {
|
||||
controller := minimock.NewController(suite.T())
|
||||
suite.T().Cleanup(controller.Finish)
|
||||
incidentRepo := mocks.NewIIncidentRepositoryMock(controller)
|
||||
incidentRepo.UpdateIncidentMock.Return(nil)
|
||||
rcaSummaryActions := NewIncidentRCASummaryAction(incidentRepo)
|
||||
err := rcaSummaryActions.updateRCASummary("test1", getMockIncidentEntity())
|
||||
suite.NoError(err)
|
||||
}
|
||||
@@ -38,7 +38,7 @@ func NewIncidentResolveProcessor(client *socketmode.Client, incidentService *inc
|
||||
}
|
||||
|
||||
func (irp *ResolveIncidentAction) IncidentResolveProcess(callback slack.InteractionCallback, request *socketmode.Request) {
|
||||
channelId := callback.Channel.ID
|
||||
channelId := callback.View.PrivateMetadata
|
||||
incidentEntity, err := irp.incidentService.FindIncidentByChannelId(channelId)
|
||||
if err != nil {
|
||||
logger.Error("incident not found",
|
||||
@@ -47,40 +47,27 @@ func (irp *ResolveIncidentAction) IncidentResolveProcess(callback slack.Interact
|
||||
}
|
||||
|
||||
incidentStatusEntity, _ := irp.incidentService.FindIncidentStatusByName(incident.Resolved)
|
||||
|
||||
tags, err := irp.tagService.FindTagsByTeamId(incidentEntity.TeamId)
|
||||
|
||||
// check if tags are required to be set
|
||||
//check if active tags are set or else throw error
|
||||
var flag = true
|
||||
if tags != nil {
|
||||
for _, t := range *tags {
|
||||
if t.Optional == false {
|
||||
incidentTag, err := irp.incidentService.GetIncidentTagByTagId(incidentEntity.ID, t.Id)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to get the incident tag for incidentId: %v", incidentEntity.ID))
|
||||
activeTags, err := irp.tagService.GetMandatoryActiveTags()
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to get mandatory active tags due to error : %s", err.Error()))
|
||||
return
|
||||
}
|
||||
if len(*activeTags) > 0 {
|
||||
for _, activeTag := range *activeTags {
|
||||
incidentTag, _ := irp.incidentService.GetIncidentTagsByTagIds(incidentEntity.ID, []uint{activeTag.Id})
|
||||
if incidentTag.TagValueIds == nil || len(incidentTag.TagValueIds) < 1 {
|
||||
logger.Error(fmt.Sprintf(" %s for incidentId: %v is not set", activeTag.Label, incidentEntity.ID))
|
||||
flag = false
|
||||
msgOption := slack.MsgOptionText(fmt.Sprintf("`%s tags are not set`", activeTag.Label), false)
|
||||
_, errMessage := irp.client.PostEphemeral(callback.Channel.ID, callback.User.ID, msgOption)
|
||||
if errMessage != nil {
|
||||
logger.Error("post response failed for tags not set message", zap.Error(errMessage))
|
||||
return
|
||||
}
|
||||
if nil == incidentTag {
|
||||
flag = false
|
||||
break
|
||||
}
|
||||
if t.Type == "free_text" {
|
||||
if incidentTag.FreeTextValue == nil || len(*incidentTag.FreeTextValue) == 0 {
|
||||
flag = false
|
||||
break
|
||||
}
|
||||
|
||||
} else {
|
||||
if incidentTag.TagValueIds == nil || len(incidentTag.TagValueIds) == 0 {
|
||||
flag = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.Error(fmt.Sprintf("Tags not required for team id: %v and incident id: %v", incidentEntity.TeamId, incidentEntity.ID))
|
||||
}
|
||||
|
||||
// check if all tags are set
|
||||
@@ -100,9 +87,9 @@ func (irp *ResolveIncidentAction) IncidentResolveProcess(callback slack.Interact
|
||||
logger.Info("successfully resolved the incident",
|
||||
zap.String("channel", channelId),
|
||||
zap.String("user_id", callback.User.ID))
|
||||
msgOption := slack.MsgOptionText(fmt.Sprintf("<@%s> *>* `houston set status to %s`", callback.User.ID,
|
||||
msgOption := slack.MsgOptionText(fmt.Sprintf("<@%s> *>* `set status to %s`", callback.User.ID,
|
||||
incident.Resolved), false)
|
||||
_, _, errMessage := irp.client.PostMessage(callback.Channel.ID, msgOption)
|
||||
_, _, errMessage := irp.client.PostMessage(channelId, msgOption)
|
||||
if errMessage != nil {
|
||||
logger.Error("post response failed for ResolveIncident", zap.Error(errMessage))
|
||||
return
|
||||
@@ -136,7 +123,7 @@ func (irp *ResolveIncidentAction) IncidentResolveProcess(callback slack.Interact
|
||||
}()
|
||||
} else {
|
||||
msgOption := slack.MsgOptionText(fmt.Sprintf("`Please set tag value`"), false)
|
||||
_, errMessage := irp.client.PostEphemeral(callback.Channel.ID, callback.User.ID, msgOption)
|
||||
_, errMessage := irp.client.PostEphemeral(channelId, callback.User.ID, msgOption)
|
||||
if errMessage != nil {
|
||||
logger.Error("post response failed for ResolveIncident", zap.Error(errMessage))
|
||||
return
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"houston/logger"
|
||||
"houston/model/incident"
|
||||
"houston/model/tag"
|
||||
|
||||
"github.com/slack-go/slack"
|
||||
"github.com/slack-go/slack/socketmode"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type IncidentShowTagsAction struct {
|
||||
client *socketmode.Client
|
||||
|
||||
incidentService *incident.Repository
|
||||
tagService *tag.Repository
|
||||
}
|
||||
|
||||
func NewIncidentShowTagsProcessor(client *socketmode.Client, incidentService *incident.Repository, tagService *tag.Repository) *IncidentShowTagsAction {
|
||||
return &IncidentShowTagsAction{
|
||||
client: client,
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
logger.Error("post ephemeral message response failed for IncidentShowTagsRequestProcess", zap.Error(errMessage))
|
||||
return
|
||||
}
|
||||
var payload interface{}
|
||||
isp.client.Ack(*request, payload)
|
||||
}
|
||||
196
internal/processor/action/incident_tags_action.go
Normal file
196
internal/processor/action/incident_tags_action.go
Normal file
@@ -0,0 +1,196 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/lib/pq"
|
||||
"github.com/slack-go/slack"
|
||||
"go.uber.org/zap"
|
||||
"houston/internal/processor/action/view"
|
||||
"houston/logger"
|
||||
"houston/model/incident"
|
||||
"houston/model/tag"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type IncidentTagsAction struct {
|
||||
incidentRepository incident.IIncidentRepository
|
||||
tagRepository tag.ITagRepository
|
||||
}
|
||||
|
||||
func NewIncidentTagsAction(incidentService incident.IIncidentRepository, tagService tag.ITagRepository) *IncidentTagsAction {
|
||||
return &IncidentTagsAction{
|
||||
incidentRepository: incidentService,
|
||||
tagRepository: tagService,
|
||||
}
|
||||
}
|
||||
|
||||
func (action *IncidentTagsAction) getIncidentTagValuesAsMap(incidentId uint) (map[string][]string, error) {
|
||||
tagValuesMap := make(map[string][]string)
|
||||
tags, err := action.getActiveTags()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, tagDTO := range *tags {
|
||||
incidentTag, err := action.getIncidentTagByTagId(incidentId, tagDTO.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tagValuesIds := incidentTag.TagValueIds
|
||||
if len(tagValuesIds) > 0 {
|
||||
tagValuesMap[tagDTO.Label], err = action.getTagValuesForTagIds(&tagValuesIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return tagValuesMap, nil
|
||||
}
|
||||
|
||||
func (action *IncidentTagsAction) getTagValuesForTagIds(tagValueIds *pq.Int32Array) ([]string, error) {
|
||||
var tagValueIdsArr []uint
|
||||
for _, tagValueId := range *tagValueIds {
|
||||
tagValueIdsArr = append(tagValueIdsArr, uint(tagValueId))
|
||||
}
|
||||
tagValues, err := action.tagRepository.GetTagValuesByTagValueId(tagValueIdsArr)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failure while getting tag values for tag value ids %v", tagValueIds))
|
||||
return nil, err
|
||||
}
|
||||
return tagValues, nil
|
||||
}
|
||||
|
||||
func (action *IncidentTagsAction) getActiveTags() (*[]tag.TagDTO, error) {
|
||||
tags, err := action.tagRepository.GetActiveTags()
|
||||
//validates if any active tags are available
|
||||
if err != nil || tags == nil {
|
||||
logger.Error(fmt.Sprintf("failure while getting active tags"))
|
||||
return nil, err
|
||||
}
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
func (action *IncidentTagsAction) getIncidentTagByTagId(incidentId uint, tagId uint) (*incident.IncidentTagEntity, error) {
|
||||
incidentTag, err := action.incidentRepository.GetIncidentTagByTagId(incidentId, tagId)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to get the incident tag for incidentId: %v", incidentId))
|
||||
return nil, err
|
||||
}
|
||||
return incidentTag, nil
|
||||
}
|
||||
func (action *IncidentTagsAction) getTagsBlock(incidentId uint) []slack.InputBlock {
|
||||
tags, _ := action.getActiveTags()
|
||||
var blocks []slack.InputBlock
|
||||
for _, tagDTO := range *tags {
|
||||
tagValues, err := action.tagRepository.FindTagValuesByTagId(tagDTO.Id)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to get the tag values for tagId: %v", tagDTO.Id))
|
||||
return nil
|
||||
}
|
||||
|
||||
incidentTag, err := action.incidentRepository.GetIncidentTagByTagId(incidentId, tagDTO.Id)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to get the incident tag for incidentId: %v", incidentId))
|
||||
return nil
|
||||
}
|
||||
|
||||
var block *slack.InputBlock
|
||||
|
||||
if incidentTag == nil {
|
||||
incidentTag, err = action.incidentRepository.CreateIncidentTag(incidentId, tagDTO.Id)
|
||||
if err != nil || incidentTag == nil {
|
||||
logger.Error(fmt.Sprintf("failure while creating tag for incident id: %v", incidentId))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if tagDTO.Type == tag.FreeText {
|
||||
var initialValue string
|
||||
if incidentTag.FreeTextValue == nil {
|
||||
initialValue = ""
|
||||
} else {
|
||||
initialValue = *incidentTag.FreeTextValue
|
||||
}
|
||||
block = view.CreateInputBlockForTag(tagDTO, nil, initialValue, tagDTO.Optional)
|
||||
} else {
|
||||
var initialTags []tag.TagValueEntity
|
||||
|
||||
if tagValues == nil {
|
||||
logger.Error(fmt.Sprintf("no tag values are present for tag: %v", tagDTO.Id))
|
||||
return nil
|
||||
}
|
||||
|
||||
if incidentTag.TagValueIds != nil {
|
||||
for _, tagValue := range *tagValues {
|
||||
for _, it := range incidentTag.TagValueIds {
|
||||
if tagValue.ID == uint(it) {
|
||||
localTagValue := tagValue
|
||||
initialTags = append(initialTags, localTagValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
block = view.CreateInputBlockForTag(tagDTO, *tagValues, initialTags, tagDTO.Optional)
|
||||
}
|
||||
|
||||
blocks = append(blocks, *block)
|
||||
}
|
||||
return blocks
|
||||
}
|
||||
|
||||
func (action *IncidentTagsAction) updateTags(actions map[string]slack.BlockAction, callback slack.InteractionCallback, incidentEntity *incident.IncidentEntity) error {
|
||||
incidentTagsEntity, err := action.incidentRepository.GetIncidentTagsByIncidentId(incidentEntity.ID)
|
||||
if err != nil || incidentTagsEntity == nil {
|
||||
logger.Error(fmt.Sprintf("failed to get the incicent tags for incident id: %v", incidentEntity.ID))
|
||||
return err
|
||||
}
|
||||
for _, it := range *incidentTagsEntity {
|
||||
tagEntity, err := action.tagRepository.FindById(it.TagId)
|
||||
if err != nil || tagEntity == nil {
|
||||
logger.Error(fmt.Sprintf("failed to get the tag for id: %v", it.TagId))
|
||||
return err
|
||||
}
|
||||
var _, isValidTeamTag = actions[tagEntity.ActionId]
|
||||
if isValidTeamTag == true {
|
||||
if tagEntity.Type == tag.FreeText {
|
||||
localValue := actions[tagEntity.ActionId].Value
|
||||
if it.FreeTextValue != &localValue {
|
||||
it.FreeTextValue = &localValue
|
||||
}
|
||||
} else if tagEntity.Type == tag.SingleValue {
|
||||
localValue := actions[tagEntity.ActionId].SelectedOption.Value
|
||||
value, err := strconv.Atoi(localValue)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("string to int conversion failed for incident: %v, tag value: %v",
|
||||
incidentEntity.ID, localValue))
|
||||
return err
|
||||
}
|
||||
if (it.TagValueIds == nil && localValue != "") || it.TagValueIds[0] != int32(value) {
|
||||
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 {
|
||||
logger.Error(fmt.Sprintf("string to int conversion failed for incident: %v, tag value: %v",
|
||||
incidentEntity.ID, localValue))
|
||||
return err
|
||||
}
|
||||
valueArray = append(valueArray, int32(localValue))
|
||||
}
|
||||
if (it.TagValueIds == nil && len(valueArray) > 0) || !reflect.DeepEqual(it.TagValueIds, valueArray) {
|
||||
it.TagValueIds = valueArray
|
||||
}
|
||||
}
|
||||
}
|
||||
_, err = action.incidentRepository.SaveIncidentTag(it)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed while saving incident tag values for incidentId: %v",
|
||||
callback.View.PrivateMetadata), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
201
internal/processor/action/incident_tags_action_test.go
Normal file
201
internal/processor/action/incident_tags_action_test.go
Normal file
@@ -0,0 +1,201 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gojuno/minimock/v3"
|
||||
"github.com/lib/pq"
|
||||
"github.com/slack-go/slack"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"houston/logger"
|
||||
"houston/mocks"
|
||||
"houston/model/incident"
|
||||
"houston/model/tag"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type IncidentTagsActionSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
var tagsMock = []tag.TagDTO{{Id: 1, Label: "test", Optional: false}, {Id: 2, Label: "test", Optional: true}}
|
||||
var incidentTagDTOMock = incident.IncidentTagEntity{IncidentId: 1, TagId: 1, TagValueIds: []int32{1, 2}}
|
||||
|
||||
func TestTagsActions(t *testing.T) {
|
||||
suite.Run(t, new(IncidentTagsActionSuite))
|
||||
}
|
||||
|
||||
func (suite *IncidentTagsActionSuite) SetupSuite() {
|
||||
logger.InitLogger()
|
||||
}
|
||||
|
||||
func (suite *IncidentTagsActionSuite) Test_GetIncidentTagValuesAsMapFailureAtActiveTags() {
|
||||
controller := minimock.NewController(suite.T())
|
||||
suite.T().Cleanup(controller.Finish)
|
||||
tagRepo := mocks.NewITagRepositoryMock(controller)
|
||||
tagRepo.GetActiveTagsMock.Return(nil, errors.New("failure while getting tag value map"))
|
||||
incidentRepo := mocks.NewIIncidentRepositoryMock(controller)
|
||||
tagActions := NewIncidentTagsAction(incidentRepo, tagRepo)
|
||||
_, err := tagActions.getIncidentTagValuesAsMap(1)
|
||||
suite.EqualError(err, "failure while getting tag value map")
|
||||
}
|
||||
|
||||
func (suite *IncidentTagsActionSuite) Test_GetIncidentTagValuesAsMapFailureAtIncidentTag() {
|
||||
controller := minimock.NewController(suite.T())
|
||||
suite.T().Cleanup(controller.Finish)
|
||||
tagRepo := mocks.NewITagRepositoryMock(controller)
|
||||
tagRepo.GetActiveTagsMock.Return(&tagsMock, nil)
|
||||
incidentRepo := mocks.NewIIncidentRepositoryMock(controller)
|
||||
incidentRepo.GetIncidentTagByTagIdMock.Return(nil, errors.New("failure while getting incident tag"))
|
||||
tagActions := NewIncidentTagsAction(incidentRepo, tagRepo)
|
||||
_, err := tagActions.getIncidentTagValuesAsMap(1)
|
||||
suite.EqualError(err, "failure while getting incident tag")
|
||||
}
|
||||
|
||||
func (suite *IncidentTagsActionSuite) Test_GetIncidentTagValuesAsMapFailureAtTagValue() {
|
||||
controller := minimock.NewController(suite.T())
|
||||
suite.T().Cleanup(controller.Finish)
|
||||
tagRepo := mocks.NewITagRepositoryMock(controller)
|
||||
tagRepo.GetActiveTagsMock.Return(&tagsMock, nil)
|
||||
incidentRepo := mocks.NewIIncidentRepositoryMock(controller)
|
||||
incidentRepo.GetIncidentTagByTagIdMock.Return(&incidentTagDTOMock, nil)
|
||||
tagRepo.GetTagValuesByTagValueIdMock.Return(nil, errors.New("failure while getting tag values"))
|
||||
tagActions := NewIncidentTagsAction(incidentRepo, tagRepo)
|
||||
_, err := tagActions.getIncidentTagValuesAsMap(1)
|
||||
suite.EqualError(err, "failure while getting tag values")
|
||||
}
|
||||
|
||||
func (suite *IncidentTagsActionSuite) Test_GetIncidentTagValuesAsMapSuccess() {
|
||||
controller := minimock.NewController(suite.T())
|
||||
suite.T().Cleanup(controller.Finish)
|
||||
tagRepo := mocks.NewITagRepositoryMock(controller)
|
||||
tagRepo.GetActiveTagsMock.Return(&tagsMock, nil)
|
||||
incidentRepo := mocks.NewIIncidentRepositoryMock(controller)
|
||||
incidentRepo.GetIncidentTagByTagIdMock.Return(&incidentTagDTOMock, nil)
|
||||
tagRepo.GetTagValuesByTagValueIdMock.Return([]string{"test"}, nil)
|
||||
tagActions := NewIncidentTagsAction(incidentRepo, tagRepo)
|
||||
tagValues, err := tagActions.getIncidentTagValuesAsMap(1)
|
||||
suite.NoError(err)
|
||||
suite.Equal(map[string][]string{"test": {"test"}}, tagValues)
|
||||
}
|
||||
|
||||
func (suite *IncidentTagsActionSuite) Test_GetTagValuesForTagIdsFailure() {
|
||||
controller := minimock.NewController(suite.T())
|
||||
suite.T().Cleanup(controller.Finish)
|
||||
tagRepo := mocks.NewITagRepositoryMock(controller)
|
||||
tagRepo.GetTagValuesByTagValueIdMock.Return(nil, errors.New("failure while getting tag values"))
|
||||
incidentRepo := mocks.NewIIncidentRepositoryMock(controller)
|
||||
tagActions := NewIncidentTagsAction(incidentRepo, tagRepo)
|
||||
_, err := tagActions.getTagValuesForTagIds(&pq.Int32Array{1, 2})
|
||||
suite.EqualError(err, "failure while getting tag values")
|
||||
}
|
||||
|
||||
func (suite *IncidentTagsActionSuite) Test_GetTagValuesForTagIdsSuccess() {
|
||||
controller := minimock.NewController(suite.T())
|
||||
suite.T().Cleanup(controller.Finish)
|
||||
tagRepo := mocks.NewITagRepositoryMock(controller)
|
||||
tagRepo.GetTagValuesByTagValueIdMock.Return([]string{"test"}, nil)
|
||||
incidentRepo := mocks.NewIIncidentRepositoryMock(controller)
|
||||
tagActions := NewIncidentTagsAction(incidentRepo, tagRepo)
|
||||
tagValues, err := tagActions.getTagValuesForTagIds(&pq.Int32Array{1, 2})
|
||||
suite.NoError(err)
|
||||
suite.Equal([]string{"test"}, tagValues)
|
||||
}
|
||||
|
||||
func (suite *IncidentTagsActionSuite) Test_GetActiveTagsFailure() {
|
||||
controller := minimock.NewController(suite.T())
|
||||
suite.T().Cleanup(controller.Finish)
|
||||
tagRepo := mocks.NewITagRepositoryMock(controller)
|
||||
tagRepo.GetActiveTagsMock.Return(nil, errors.New("failure while getting active tags"))
|
||||
incidentRepo := mocks.NewIIncidentRepositoryMock(controller)
|
||||
tagActions := NewIncidentTagsAction(incidentRepo, tagRepo)
|
||||
_, err := tagActions.getActiveTags()
|
||||
suite.EqualError(err, "failure while getting active tags")
|
||||
}
|
||||
|
||||
func (suite *IncidentTagsActionSuite) Test_GetActiveTagsSuccess() {
|
||||
controller := minimock.NewController(suite.T())
|
||||
suite.T().Cleanup(controller.Finish)
|
||||
tagRepo := mocks.NewITagRepositoryMock(controller)
|
||||
tagRepo.GetActiveTagsMock.Return(&tagsMock, nil)
|
||||
incidentRepo := mocks.NewIIncidentRepositoryMock(controller)
|
||||
tagActions := NewIncidentTagsAction(incidentRepo, tagRepo)
|
||||
tags, err := tagActions.getActiveTags()
|
||||
suite.NoError(err)
|
||||
suite.Equal(tagsMock, *tags)
|
||||
}
|
||||
|
||||
func (suite *IncidentTagsActionSuite) Test_GetIncidentTagByTagIdFailure() {
|
||||
controller := minimock.NewController(suite.T())
|
||||
suite.T().Cleanup(controller.Finish)
|
||||
tagRepo := mocks.NewITagRepositoryMock(controller)
|
||||
incidentRepo := mocks.NewIIncidentRepositoryMock(controller)
|
||||
incidentRepo.GetIncidentTagByTagIdMock.Return(nil, errors.New("failure while getting incident tag"))
|
||||
tagActions := NewIncidentTagsAction(incidentRepo, tagRepo)
|
||||
_, err := tagActions.getIncidentTagByTagId(1, 1)
|
||||
suite.EqualError(err, "failure while getting incident tag")
|
||||
}
|
||||
|
||||
func (suite *IncidentTagsActionSuite) Test_GetIncidentTagByTagIdSuccess() {
|
||||
controller := minimock.NewController(suite.T())
|
||||
suite.T().Cleanup(controller.Finish)
|
||||
tagRepo := mocks.NewITagRepositoryMock(controller)
|
||||
incidentRepo := mocks.NewIIncidentRepositoryMock(controller)
|
||||
incidentRepo.GetIncidentTagByTagIdMock.Return(&incidentTagDTOMock, nil)
|
||||
tagActions := NewIncidentTagsAction(incidentRepo, tagRepo)
|
||||
incidentTag, err := tagActions.getIncidentTagByTagId(1, 1)
|
||||
suite.NoError(err)
|
||||
suite.Equal(incidentTagDTOMock, *incidentTag)
|
||||
}
|
||||
|
||||
func (suite *IncidentTagsActionSuite) TestUpdateTagsFailureAtIncident() {
|
||||
controller := minimock.NewController(suite.T())
|
||||
suite.T().Cleanup(controller.Finish)
|
||||
tagRepo := mocks.NewITagRepositoryMock(controller)
|
||||
incidentRepo := mocks.NewIIncidentRepositoryMock(controller)
|
||||
incidentRepo.GetIncidentTagsByIncidentIdMock.Return(nil, errors.New("failure while getting incident"))
|
||||
tagActions := NewIncidentTagsAction(incidentRepo, tagRepo)
|
||||
callBack := slack.InteractionCallback{}
|
||||
err := tagActions.updateTags(nil, callBack, getMockIncidentEntity())
|
||||
suite.EqualError(err, "failure while getting incident")
|
||||
}
|
||||
|
||||
func (suite *IncidentTagsActionSuite) TestUpdateTagsFailureAtTagId() {
|
||||
controller := minimock.NewController(suite.T())
|
||||
suite.T().Cleanup(controller.Finish)
|
||||
tagRepo := mocks.NewITagRepositoryMock(controller)
|
||||
incidentRepo := mocks.NewIIncidentRepositoryMock(controller)
|
||||
incidentRepo.GetIncidentTagsByIncidentIdMock.Return(&[]incident.IncidentTagEntity{{TagId: 1}}, nil)
|
||||
tagRepo.FindByIdMock.Return(nil, errors.New("failure while getting tag"))
|
||||
tagActions := NewIncidentTagsAction(incidentRepo, tagRepo)
|
||||
callBack := slack.InteractionCallback{}
|
||||
err := tagActions.updateTags(nil, callBack, getMockIncidentEntity())
|
||||
suite.EqualError(err, "failure while getting tag")
|
||||
}
|
||||
|
||||
func (suite *IncidentTagsActionSuite) TestUpdateTagsFailureAtSaveIncident() {
|
||||
controller := minimock.NewController(suite.T())
|
||||
suite.T().Cleanup(controller.Finish)
|
||||
tagRepo := mocks.NewITagRepositoryMock(controller)
|
||||
tagRepo.FindByIdMock.Return(&tag.TagEntity{Name: "test"}, nil)
|
||||
incidentRepo := mocks.NewIIncidentRepositoryMock(controller)
|
||||
incidentRepo.GetIncidentTagsByIncidentIdMock.Return(&[]incident.IncidentTagEntity{{TagId: 1}}, nil)
|
||||
incidentRepo.SaveIncidentTagMock.Return(nil, errors.New("failure while saving incident"))
|
||||
tagActions := NewIncidentTagsAction(incidentRepo, tagRepo)
|
||||
callBack := slack.InteractionCallback{}
|
||||
err := tagActions.updateTags(nil, callBack, getMockIncidentEntity())
|
||||
suite.EqualError(err, "failure while saving incident")
|
||||
}
|
||||
|
||||
func (suite *IncidentTagsActionSuite) TestUpdateTagsSuccess() {
|
||||
controller := minimock.NewController(suite.T())
|
||||
suite.T().Cleanup(controller.Finish)
|
||||
tagRepo := mocks.NewITagRepositoryMock(controller)
|
||||
tagRepo.FindByIdMock.Return(&tag.TagEntity{Name: "test"}, nil)
|
||||
incidentRepo := mocks.NewIIncidentRepositoryMock(controller)
|
||||
incidentRepo.GetIncidentTagsByIncidentIdMock.Return(&[]incident.IncidentTagEntity{{TagId: 1}}, nil)
|
||||
incidentRepo.SaveIncidentTagMock.Return(nil, nil)
|
||||
tagActions := NewIncidentTagsAction(incidentRepo, tagRepo)
|
||||
callBack := slack.InteractionCallback{}
|
||||
err := tagActions.updateTags(nil, callBack, getMockIncidentEntity())
|
||||
suite.NoError(err)
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/slack-go/slack"
|
||||
"github.com/slack-go/slack/socketmode"
|
||||
"github.com/spf13/viper"
|
||||
"houston/internal/processor/action/view"
|
||||
"houston/logger"
|
||||
"houston/model/incident"
|
||||
incidentService "houston/service/incident"
|
||||
slack2 "houston/service/slack"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type IncidentUpdateJiraLinksAction struct {
|
||||
client *socketmode.Client
|
||||
incidentRepository *incident.Repository
|
||||
incidentService *incidentService.IncidentServiceV2
|
||||
slackService *slack2.SlackService
|
||||
}
|
||||
|
||||
const (
|
||||
logTag = "[IncidentUpdateJiraLinksAction]"
|
||||
)
|
||||
|
||||
func NewIncidentUpdateJiraLinksAction(
|
||||
client *socketmode.Client,
|
||||
incidentRepository *incident.Repository,
|
||||
incidentServiceV2 *incidentService.IncidentServiceV2,
|
||||
slackService *slack2.SlackService,
|
||||
) *IncidentUpdateJiraLinksAction {
|
||||
return &IncidentUpdateJiraLinksAction{
|
||||
client: client,
|
||||
incidentRepository: incidentRepository,
|
||||
incidentService: incidentServiceV2,
|
||||
slackService: slackService,
|
||||
}
|
||||
}
|
||||
|
||||
func (action *IncidentUpdateJiraLinksAction) IncidentUpdateJiraLinksRequestProcess(callback slack.InteractionCallback, request *socketmode.Request) {
|
||||
result, err := action.incidentRepository.FindIncidentByChannelId(callback.Channel.ID)
|
||||
if err != nil || result == nil {
|
||||
logger.Error(fmt.Sprintf("%s failed to find incident entity for: %s", logTag, callback.Channel.Name))
|
||||
return
|
||||
}
|
||||
|
||||
modalRequest := view.BuildJiraLinksModal(callback.Channel.ID, result.JiraLinks...)
|
||||
|
||||
_, err = action.client.OpenView(callback.TriggerID, modalRequest)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("%s failed to open view command for: %s", logTag, callback.Channel.Name))
|
||||
return
|
||||
}
|
||||
var payload interface{}
|
||||
action.client.Ack(*request, payload)
|
||||
}
|
||||
|
||||
func (action *IncidentUpdateJiraLinksAction) IncidentUpdateJiraLinks(callback slack.InteractionCallback, request *socketmode.Request, channel slack.Channel, user slack.User) {
|
||||
channelID := callback.View.PrivateMetadata
|
||||
incidentEntity, err := action.incidentRepository.FindIncidentByChannelId(channelID)
|
||||
if err != nil || incidentEntity == nil {
|
||||
logger.Error(fmt.Sprintf("%s failed to find incident entity for: %s", logTag, channel.Name))
|
||||
return
|
||||
}
|
||||
|
||||
jiraLinks := strings.Split(
|
||||
strings.ReplaceAll(strings.ReplaceAll(buildUpdateJiraLinksRequest(callback.View.State.Values), "\n", ""), " ", ""),
|
||||
",",
|
||||
)
|
||||
for _, l := range jiraLinks {
|
||||
if strings.HasPrefix(l, viper.GetString("navi.jira.base.url")) == false {
|
||||
err := action.slackService.PostEphemeralByChannelID(fmt.Sprintf("%s is not a valid jira link", l), user.ID, false, channelID)
|
||||
if err != nil {
|
||||
logger.Debug(fmt.Sprintf("%s failed to post jira link validation failure ephemeral to slack channel: %s", logTag, incidentEntity.IncidentName))
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
err = action.incidentService.LinkJiraToIncident(incidentEntity.ID, user.ID, jiraLinks...)
|
||||
|
||||
var payload interface{}
|
||||
action.client.Ack(*request, payload)
|
||||
}
|
||||
|
||||
func buildUpdateJiraLinksRequest(blockActions map[string]map[string]slack.BlockAction) string {
|
||||
var requestMap = make(map[string]string, 0)
|
||||
for _, actions := range blockActions {
|
||||
for actionID, a := range actions {
|
||||
if a.Type == "plain_text_input" {
|
||||
requestMap[actionID] = a.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return requestMap["jira"]
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/slack-go/slack"
|
||||
"github.com/slack-go/slack/socketmode"
|
||||
"go.uber.org/zap"
|
||||
"houston/internal/processor/action/view"
|
||||
"houston/logger"
|
||||
"houston/model/incident"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type IncidentUpdateRcaAction struct {
|
||||
client *socketmode.Client
|
||||
logger *zap.Logger
|
||||
incidentRepository *incident.Repository
|
||||
}
|
||||
|
||||
func NewIncidentUpdateRcaAction(client *socketmode.Client, incidentRepository *incident.Repository) *IncidentUpdateRcaAction {
|
||||
return &IncidentUpdateRcaAction{
|
||||
client: client,
|
||||
incidentRepository: incidentRepository,
|
||||
}
|
||||
}
|
||||
|
||||
func (idp *IncidentUpdateRcaAction) IncidentUpdateRcaRequestProcess(callback slack.InteractionCallback, request *socketmode.Request) {
|
||||
result, err := idp.incidentRepository.FindIncidentByChannelId(callback.Channel.ID)
|
||||
if err != nil {
|
||||
logger.Error("FindIncidentByChannelId 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 {
|
||||
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
|
||||
}
|
||||
modalRequest := view.BuildRcaModal(callback.Channel.ID, result.RCA)
|
||||
|
||||
_, err = idp.client.OpenView(callback.TriggerID, modalRequest)
|
||||
if err != nil {
|
||||
logger.Error("houston slackbot openview command for IncidentUpdateRcaRequestProcess 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 *IncidentUpdateRcaAction) IncidentUpdateRca(callback slack.InteractionCallback, request *socketmode.Request, channel slack.Channel, user slack.User) {
|
||||
incidentEntity, err := itp.incidentRepository.FindIncidentByChannelId(callback.View.PrivateMetadata)
|
||||
if err != nil {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
incidentRca := buildUpdateRcaRequest(callback.View.State.Values)
|
||||
|
||||
incidentEntity.RCA = incidentRca
|
||||
incidentEntity.UpdatedBy = user.ID
|
||||
err = itp.incidentRepository.UpdateIncident(incidentEntity)
|
||||
if err != nil {
|
||||
logger.Error("IncidentUpdateRca error",
|
||||
zap.String("incident_slack_channel_id", channel.ID), zap.String("channel", channel.Name),
|
||||
zap.String("user_id", user.ID), zap.Error(err))
|
||||
return
|
||||
}
|
||||
re := regexp.MustCompile(`\n+`)
|
||||
msgOption := slack.MsgOptionText(fmt.Sprintf("<@%s> *>* `houston has updated RCA to \"%s\"`", user.ID, re.ReplaceAllString(incidentEntity.RCA, " ")), false)
|
||||
_, _, errMessage := itp.client.PostMessage(callback.View.PrivateMetadata, msgOption)
|
||||
if errMessage != nil {
|
||||
logger.Error("post response failed for IncidentUpdateRca", zap.Error(errMessage))
|
||||
return
|
||||
}
|
||||
|
||||
var payload interface{}
|
||||
itp.client.Ack(*request, payload)
|
||||
}
|
||||
|
||||
func buildUpdateRcaRequest(blockActions map[string]map[string]slack.BlockAction) string {
|
||||
var requestMap = make(map[string]string, 0)
|
||||
for _, actions := range blockActions {
|
||||
for actionID, a := range actions {
|
||||
if a.Type == "plain_text_input" {
|
||||
requestMap[actionID] = a.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return requestMap["rca"]
|
||||
}
|
||||
@@ -87,7 +87,7 @@ func (isp *IncidentUpdateSevertityAction) IncidentUpdateSeverity(callback slack.
|
||||
if viper.GetBool("UPDATE_INCIDENT_V2_ENABLED") {
|
||||
teamEntity, _, incidentStatusEntity, incidentChannels, err := isp.incidentServiceV2.FetchAllEntitiesForIncident(incidentEntity)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("error in fetching entities for incident with id: %d %w", incidentEntity.ID, err))
|
||||
logger.Error(fmt.Sprintf("error in fetching entities for incident with id: %d %v", incidentEntity.ID, err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ func (isp *IncidentUpdateSevertityAction) IncidentUpdateSeverity(callback slack.
|
||||
incidentStatusEntity,
|
||||
incidentChannels,
|
||||
); err != nil {
|
||||
logger.Error(fmt.Sprintf("error in updating severity: %w", err))
|
||||
logger.Error(fmt.Sprintf("error in updating severity: %v", err))
|
||||
}
|
||||
var payload interface{}
|
||||
isp.client.Ack(*request, payload)
|
||||
|
||||
@@ -1,216 +0,0 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"houston/common/util"
|
||||
"houston/internal/processor/action/view"
|
||||
"houston/logger"
|
||||
"houston/model/incident"
|
||||
"houston/model/tag"
|
||||
"houston/model/team"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/lib/pq"
|
||||
|
||||
"github.com/slack-go/slack"
|
||||
"github.com/slack-go/slack/socketmode"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type IncidentUpdateTagsAction struct {
|
||||
client *socketmode.Client
|
||||
incidentRepository *incident.Repository
|
||||
teamRepository *team.Repository
|
||||
tagRepository *tag.Repository
|
||||
}
|
||||
|
||||
func NewIncidentUpdateTagsAction(client *socketmode.Client, incidentService *incident.Repository,
|
||||
teamService *team.Repository, tagService *tag.Repository) *IncidentUpdateTagsAction {
|
||||
return &IncidentUpdateTagsAction{
|
||||
client: client,
|
||||
incidentRepository: incidentService,
|
||||
teamRepository: teamService,
|
||||
tagRepository: tagService,
|
||||
}
|
||||
}
|
||||
|
||||
func (itp *IncidentUpdateTagsAction) IncidentUpdateTagsRequestProcess(callback slack.InteractionCallback, request *socketmode.Request) {
|
||||
incidentEntity, err := itp.incidentRepository.FindIncidentByChannelId(callback.Channel.ID)
|
||||
if err != nil || incidentEntity == nil {
|
||||
logger.Error(fmt.Sprintf("failure while getting incident entity for channel: %v", callback.Channel.ID))
|
||||
return
|
||||
}
|
||||
|
||||
team, err := itp.teamRepository.FindTeamById(incidentEntity.TeamId)
|
||||
if err != nil || team == nil {
|
||||
logger.Error(fmt.Sprintf("failure while getting team for incident id: %v", incidentEntity.ID))
|
||||
return
|
||||
}
|
||||
|
||||
tags, err := itp.tagRepository.FindTagsByTeamId(team.ID)
|
||||
if err != nil || tags == nil {
|
||||
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.tagRepository.FindTagValuesByTagId(t.Id)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to get the tag values for tagId: %v", t.Id))
|
||||
return
|
||||
}
|
||||
|
||||
incidentTag, err := itp.incidentRepository.GetIncidentTagByTagId(incidentEntity.ID, t.Id)
|
||||
if err != nil {
|
||||
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.incidentRepository.CreateIncidentTag(incidentEntity.ID, t.Id)
|
||||
if err != nil || incidentTag == nil {
|
||||
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 {
|
||||
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 {
|
||||
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.incidentRepository.FindIncidentByChannelId(callback.View.PrivateMetadata)
|
||||
if err != nil || incidentEntity == nil {
|
||||
logger.Error(fmt.Sprintf("failed to get the incicent for channel id: %v", callback.View.PrivateMetadata))
|
||||
return
|
||||
}
|
||||
|
||||
incidentTagsEntity, err := itp.incidentRepository.GetIncidentTagsByIncidentId(incidentEntity.ID)
|
||||
if err != nil || incidentTagsEntity == nil {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
//update resolution text
|
||||
rca := actions[util.SetRCA].Value
|
||||
if strings.TrimSpace(rca) != "" {
|
||||
incidentEntity.RCA = rca
|
||||
err = itp.incidentRepository.UpdateIncident(incidentEntity)
|
||||
if err != nil {
|
||||
logger.Error("IncidentUpdateRca 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
|
||||
}
|
||||
re := regexp.MustCompile(`\n+`)
|
||||
msgOption := slack.MsgOptionText(fmt.Sprintf("<@%s> *>* `houston has updated RCA to \"%s\"`", callback.User.ID, re.ReplaceAllString(incidentEntity.RCA, " ")), false)
|
||||
_, _, errMessage := itp.client.PostMessage(callback.View.PrivateMetadata, msgOption)
|
||||
if errMessage != nil {
|
||||
logger.Error("post response failed for IncidentUpdateRca", zap.Error(errMessage))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// build request to update the tag values
|
||||
for _, it := range *incidentTagsEntity {
|
||||
tagEntity, err := itp.tagRepository.FindById(it.TagId)
|
||||
if err != nil || tagEntity == nil {
|
||||
logger.Error(fmt.Sprintf("failed to get the tag for id: %v", it.TagId))
|
||||
return
|
||||
}
|
||||
var _, isValidTeamTag = actions[tagEntity.ActionId]
|
||||
if isValidTeamTag == true {
|
||||
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 {
|
||||
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 {
|
||||
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.incidentRepository.SaveIncidentTag(it)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
@@ -6,9 +6,10 @@ import (
|
||||
"github.com/slack-go/slack/socketmode"
|
||||
"go.uber.org/zap"
|
||||
"houston/appcontext"
|
||||
"houston/internal/processor/action/view"
|
||||
"houston/common/util"
|
||||
"houston/logger"
|
||||
"houston/pkg/slackbot"
|
||||
"houston/service/rca"
|
||||
)
|
||||
|
||||
const openSetRCAViewModalActionLogTag = "[open_set_rca_view_modal_command_action]"
|
||||
@@ -16,15 +17,18 @@ const openSetRCAViewModalActionLogTag = "[open_set_rca_view_modal_command_action
|
||||
type OpenFillRCAViewModalCommandAction struct {
|
||||
socketModeClient *socketmode.Client
|
||||
slackBot *slackbot.Client
|
||||
rcaService *rca.RcaService
|
||||
}
|
||||
|
||||
func NewOpenFillRCAViewModalCommandAction(
|
||||
socketModeClient *socketmode.Client,
|
||||
slackBot *slackbot.Client,
|
||||
rcaService *rca.RcaService,
|
||||
) *OpenFillRCAViewModalCommandAction {
|
||||
return &OpenFillRCAViewModalCommandAction{
|
||||
socketModeClient: socketModeClient,
|
||||
slackBot: slackBot,
|
||||
rcaService: rcaService,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,30 +40,15 @@ func (action *OpenFillRCAViewModalCommandAction) PerformAction(evt *socketmode.E
|
||||
return
|
||||
}
|
||||
|
||||
err := action.openFillRCAViewModal(cmd)
|
||||
tagsAction := NewIncidentTagsAction(appcontext.GetIncidentRepo(), appcontext.GetTagRepo())
|
||||
rcaSummaryAction := NewIncidentRCASummaryAction(appcontext.GetIncidentRepo())
|
||||
jiraLinksAction := NewIncidentJiraLinksAction(appcontext.GetIncidentService(), appcontext.GetSlackService())
|
||||
rcaSectionAction := NewIncidentRCASectionAction(action.socketModeClient, appcontext.GetIncidentRepo(), appcontext.GetTeamRepo(), appcontext.GetTagRepo(), appcontext.GetSeverityRepo(), tagsAction, rcaSummaryAction, jiraLinksAction, action.rcaService)
|
||||
err := rcaSectionAction.ProcessIncidentRCAActionRequestForSlashCommand(cmd.ChannelID, cmd.TriggerID, evt.Request, util.SetIncidentRCADetailsSubmit)
|
||||
if err != nil {
|
||||
err := appcontext.GetSlackService().PostEphemeralByChannelID(err.Error(), cmd.UserID, false, cmd.ChannelID)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("%s failed to post ephemeral for create incident error. %+v", openSetRCAViewModalActionLogTag, err))
|
||||
}
|
||||
}
|
||||
|
||||
action.socketModeClient.Ack(*evt.Request)
|
||||
}
|
||||
|
||||
func (action *OpenFillRCAViewModalCommandAction) openFillRCAViewModal(cmd slack.SlashCommand) error {
|
||||
logger.Info(fmt.Sprintf("%s received request to resolve the incident", openSetRCAViewModalActionLogTag))
|
||||
|
||||
return executeForHoustonChannel(cmd, func() error {
|
||||
incidentEntity, err := appcontext.GetIncidentService().GetIncidentByChannelID(cmd.ChannelID)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("%s failed to fetch incident by channel ID: %s. %+v", openSetRCAViewModalActionLogTag, cmd.ChannelID, err))
|
||||
return genericBackendError
|
||||
}
|
||||
_, err = action.socketModeClient.OpenView(cmd.TriggerID, view.BuildRcaModal(cmd.ChannelID, incidentEntity.RCA))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open set RCA view modal")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,15 +4,12 @@ import (
|
||||
"fmt"
|
||||
"github.com/slack-go/slack"
|
||||
"github.com/slack-go/slack/socketmode"
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/zap"
|
||||
"houston/appcontext"
|
||||
"houston/common/util"
|
||||
"houston/logger"
|
||||
"houston/model/incident"
|
||||
"houston/pkg/slackbot"
|
||||
"houston/service/rca"
|
||||
"time"
|
||||
)
|
||||
|
||||
const resolveIncidentActionLogTag = "[slash_command_action]"
|
||||
@@ -42,135 +39,15 @@ func (action *ResolveIncidentCommandAction) PerformAction(evt *socketmode.Event)
|
||||
logger.Error("event data to slash command conversion failed", zap.Any("data", evt))
|
||||
return
|
||||
}
|
||||
|
||||
err := action.resolveIncident(cmd)
|
||||
tagsAction := NewIncidentTagsAction(appcontext.GetIncidentRepo(), appcontext.GetTagRepo())
|
||||
rcaSummaryAction := NewIncidentRCASummaryAction(appcontext.GetIncidentRepo())
|
||||
jiraLinksAction := NewIncidentJiraLinksAction(appcontext.GetIncidentService(), appcontext.GetSlackService())
|
||||
rcaSectionAction := NewIncidentRCASectionAction(action.socketModeClient, appcontext.GetIncidentRepo(), appcontext.GetTeamRepo(), appcontext.GetTagRepo(), appcontext.GetSeverityRepo(), tagsAction, rcaSummaryAction, jiraLinksAction, action.rcaService)
|
||||
err := rcaSectionAction.ProcessIncidentRCAActionRequestForSlashCommand(cmd.ChannelID, cmd.TriggerID, evt.Request, util.IncidentResolveSubmit)
|
||||
if err != nil {
|
||||
err := appcontext.GetSlackService().PostEphemeralByChannelID(err.Error(), cmd.UserID, false, cmd.ChannelID)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("%s failed to post ephemeral for create incident error. %+v", resolveIncidentActionLogTag, err))
|
||||
}
|
||||
}
|
||||
|
||||
action.socketModeClient.Ack(*evt.Request)
|
||||
}
|
||||
|
||||
func (action *ResolveIncidentCommandAction) resolveIncident(cmd slack.SlashCommand) error {
|
||||
logger.Info(fmt.Sprintf("%s received request to resolve the incident", resolveIncidentActionLogTag))
|
||||
|
||||
return executeForHoustonChannel(cmd, func() error {
|
||||
incidentEntity, err := appcontext.GetIncidentService().GetIncidentByChannelID(cmd.ChannelID)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("%s failed to fetch incident entity with channel ID: %s. %+v", resolveIncidentActionLogTag, cmd.ChannelID, err))
|
||||
return fmt.Errorf("failed to fetch incident entity with channel ID: %s", cmd.ChannelID)
|
||||
}
|
||||
if incidentEntity == nil {
|
||||
logger.Error(fmt.Sprintf("%s no entry found for incident with channel ID: %s in DB", resolveIncidentActionLogTag, cmd.ChannelID))
|
||||
return genericBackendError
|
||||
}
|
||||
|
||||
incidentStatusEntity, _ := appcontext.GetIncidentRepo().FindIncidentStatusByName(incident.Resolved)
|
||||
|
||||
tags, err := appcontext.GetTagRepo().FindTagsByTeamId(incidentEntity.TeamId)
|
||||
|
||||
//check if tags are required to be set
|
||||
var flag = true
|
||||
if tags != nil {
|
||||
for _, t := range *tags {
|
||||
if t.Optional == false {
|
||||
incidentTag, err := appcontext.GetIncidentRepo().GetIncidentTagByTagId(incidentEntity.ID, t.Id)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("%s failed to get the incident tag for incidentId: %d", resolveIncidentActionLogTag, incidentEntity.ID))
|
||||
return genericBackendError
|
||||
}
|
||||
if nil == incidentTag {
|
||||
flag = false
|
||||
break
|
||||
}
|
||||
if t.Type == "free_text" {
|
||||
if incidentTag.FreeTextValue == nil || len(*incidentTag.FreeTextValue) == 0 {
|
||||
flag = false
|
||||
break
|
||||
}
|
||||
|
||||
} else {
|
||||
if incidentTag.TagValueIds == nil || len(incidentTag.TagValueIds) == 0 {
|
||||
flag = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.Info(fmt.Sprintf("%s tags not required for team id: %v and incident id: %v", resolveIncidentActionLogTag, incidentEntity.TeamId, incidentEntity.ID))
|
||||
}
|
||||
|
||||
//check if all tags are set
|
||||
if flag == true {
|
||||
now := time.Now()
|
||||
incidentEntity.Status = incidentStatusEntity.ID
|
||||
incidentEntity.EndTime = &now
|
||||
|
||||
err = appcontext.GetIncidentRepo().UpdateIncident(incidentEntity)
|
||||
if err != nil {
|
||||
logger.Error("failed to update incident to resolve state",
|
||||
zap.String("channel", cmd.ChannelID),
|
||||
zap.String("user_id", cmd.UserID), zap.Error(err))
|
||||
return fmt.Errorf("failed to update incident status")
|
||||
}
|
||||
|
||||
logger.Info("successfully resolved the incident",
|
||||
zap.String("channel", cmd.ChannelID),
|
||||
zap.String("user_id", cmd.UserID))
|
||||
msgOption := slack.MsgOptionText(fmt.Sprintf("<@%s> *>* `houston set status to %s`", cmd.UserID,
|
||||
incident.Resolved), false)
|
||||
_, _, errMessage := action.socketModeClient.PostMessage(cmd.ChannelID, msgOption)
|
||||
if errMessage != nil {
|
||||
logger.Error("post response failed for ResolveIncident", zap.Error(errMessage))
|
||||
return fmt.Errorf("incident is resolved but failed to post response to slack channel")
|
||||
}
|
||||
msgUpdate := NewIncidentChannelMessageUpdateAction(
|
||||
action.socketModeClient,
|
||||
appcontext.GetIncidentRepo(),
|
||||
appcontext.GetTeamRepo(),
|
||||
appcontext.GetSeverityRepo(),
|
||||
)
|
||||
msgUpdate.ProcessAction(incidentEntity.SlackChannel)
|
||||
//ToDo() Delete Conference event if exists and if incident is resolved
|
||||
|
||||
go func() {
|
||||
if incidentEntity.SeverityId != incident.Sev0Id && incidentEntity.SeverityId != incident.Sev1Id {
|
||||
postErr := util.PostArchivingTimeToIncidentChannel(cmd.ChannelID, incident.Resolved, action.socketModeClient)
|
||||
if postErr != nil {
|
||||
logger.Error("failed to post archiving time to incident channel", zap.String("channel id", cmd.ChannelID), zap.Error(err))
|
||||
}
|
||||
}
|
||||
if viper.GetBool("RCA_GENERATION_ENABLED") {
|
||||
err = action.rcaService.SendConversationDataForGeneratingRCA(incidentEntity.ID, cmd.ChannelID)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to generate rca for incident id: %d of channel id: %s", incidentEntity.ID, cmd.ChannelID), zap.Error(err))
|
||||
_, _, errMessage := action.socketModeClient.PostMessage(cmd.ChannelID, slack.MsgOptionText("`Some issue occurred while generating RCA`", false))
|
||||
if errMessage != nil {
|
||||
logger.Error("post response failed for rca failure message", zap.Error(errMessage))
|
||||
}
|
||||
} else {
|
||||
_, _, errMessage := action.socketModeClient.PostMessage(cmd.ChannelID, slack.MsgOptionText("System RCA generation is in progress and might take 2 to 4 minutes.", false))
|
||||
if errMessage != nil {
|
||||
logger.Error("post response failed for rca generated message", zap.Error(errMessage))
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
msgOption := slack.MsgOptionText(fmt.Sprintf("`Please set tag value`"), false)
|
||||
_, errMessage := action.socketModeClient.PostEphemeral(cmd.ChannelID, cmd.UserID, msgOption)
|
||||
if errMessage != nil {
|
||||
logger.Error("post response failed for ResolveIncident", zap.Error(errMessage))
|
||||
return fmt.Errorf("`Please set tag value`")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
155
internal/processor/action/view/incident_rca_details_section.go
Normal file
155
internal/processor/action/view/incident_rca_details_section.go
Normal file
@@ -0,0 +1,155 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/slack-go/slack"
|
||||
"houston/common/util"
|
||||
"houston/model/incident"
|
||||
"houston/model/tag"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func BuildIncidentRCASectionModal(channelId string, inputBlocks []slack.InputBlock, requesterType util.ViewSubmissionType) slack.ModalViewRequest {
|
||||
titleText := slack.NewTextBlockObject(slack.PlainTextType, "RCA", 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: channelId,
|
||||
CallbackID: string(requesterType),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func ConstructShowRCADetailsBlock(incident *incident.IncidentEntity, tagValuesMap map[string][]string) slack.Blocks {
|
||||
var blockSet []slack.Block
|
||||
blockSet = append(blockSet, buildHeaderBlock(util.RCADetailsLabel))
|
||||
tagsBlockSet := buildTagsBlock(tagValuesMap)
|
||||
for _, tagBlock := range tagsBlockSet {
|
||||
blockSet = append(blockSet, *tagBlock)
|
||||
}
|
||||
blockSet = append(blockSet, buildRCASummaryBlock(incident), buildJiraLinksBlock(incident))
|
||||
return slack.Blocks{
|
||||
BlockSet: blockSet,
|
||||
}
|
||||
}
|
||||
|
||||
func buildHeaderBlock(header string) *slack.HeaderBlock {
|
||||
block := slack.NewHeaderBlock(&slack.TextBlockObject{
|
||||
Type: util.PlainTextType,
|
||||
Text: header,
|
||||
})
|
||||
return block
|
||||
}
|
||||
|
||||
func buildTagsBlock(tagValuesMap map[string][]string) []*slack.SectionBlock {
|
||||
var tagsBlocks []*slack.SectionBlock
|
||||
for key := range tagValuesMap {
|
||||
tagsBlocks = append(tagsBlocks, slack.NewSectionBlock(&slack.TextBlockObject{Type: util.MarkDownElementType, Text: fmt.Sprintf("*%s* : %s", key, strings.Join(tagValuesMap[key], ", "))}, nil, nil))
|
||||
}
|
||||
return tagsBlocks
|
||||
}
|
||||
|
||||
func buildRCASummaryBlock(entity *incident.IncidentEntity) *slack.SectionBlock {
|
||||
field := slack.TextBlockObject{Type: util.MarkDownElementType, Text: fmt.Sprintf("*%s* : %s", util.RCASummaryLabel, entity.RCA), Verbatim: false}
|
||||
block := slack.NewSectionBlock(&field, nil, nil)
|
||||
return block
|
||||
}
|
||||
|
||||
func buildJiraLinksBlock(entity *incident.IncidentEntity) *slack.SectionBlock {
|
||||
var hyperLinks []string
|
||||
for _, jiraLink := range entity.JiraLinks {
|
||||
if jiraLink != "" {
|
||||
hyperLinks = append(hyperLinks, fmt.Sprintf("<%s|%s>", jiraLink, strings.Split(jiraLink, util.JiraIdSeparator)[1]))
|
||||
}
|
||||
}
|
||||
field := slack.TextBlockObject{Type: util.MarkDownElementType, Text: fmt.Sprintf("*%s* : %s", util.JiraLinksLabel, strings.Join(hyperLinks, ", "))}
|
||||
block := slack.NewSectionBlock(&field, nil, nil)
|
||||
return block
|
||||
}
|
||||
|
||||
func CreateInputBlockForTag(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 = createPlainTextInputBlockElementForTag(tagEntity, initialTagValues)
|
||||
}
|
||||
case tag.SingleValue:
|
||||
{
|
||||
element = createOptionsSelectBlockElementForTag(tagEntity, tagValues, initialTagValues.([]tag.TagValueEntity))
|
||||
}
|
||||
case tag.MultiValue:
|
||||
{
|
||||
element = createMultiOptionsSelectBlockElementForTag(tagEntity, tagValues, initialTagValues.([]tag.TagValueEntity))
|
||||
}
|
||||
default:
|
||||
{
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
block := slack.NewInputBlock(tagEntity.Name, text, nil, element)
|
||||
block.Optional = isOptional
|
||||
|
||||
return block
|
||||
}
|
||||
|
||||
func createPlainTextInputBlockElementForTag(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 createOptionsSelectBlockElementForTag(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 createMultiOptionsSelectBlockElementForTag(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
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"github.com/slack-go/slack"
|
||||
"houston/common/util"
|
||||
)
|
||||
|
||||
func BuildRcaModal(channelID string, description string) slack.ModalViewRequest {
|
||||
titleText := slack.NewTextBlockObject(slack.PlainTextType, "Incident RCA", false, false)
|
||||
closeText := slack.NewTextBlockObject(slack.PlainTextType, "Close", false, false)
|
||||
submitText := slack.NewTextBlockObject(slack.PlainTextType, "Submit", false, false)
|
||||
|
||||
//headerText := slack.NewTextBlockObject("mrkdwn", "", false, false)
|
||||
//headerSection := slack.NewSectionBlock(headerText, nil, nil)
|
||||
|
||||
rcaText := slack.NewTextBlockObject(slack.PlainTextType, " ", false, false)
|
||||
rcaPlaceholder := slack.NewTextBlockObject(slack.PlainTextType, "Write RCA here...", false, false)
|
||||
rcaElement := slack.NewPlainTextInputBlockElement(rcaPlaceholder, "rca")
|
||||
rcaElement.Multiline = true
|
||||
rcaElement.InitialValue = description
|
||||
rcaElement.MaxLength = 3000
|
||||
rca := slack.NewInputBlock("RCA", rcaText, nil, rcaElement)
|
||||
rca.Optional = false
|
||||
|
||||
blocks := slack.Blocks{
|
||||
BlockSet: []slack.Block{
|
||||
//headerSection,
|
||||
rca,
|
||||
},
|
||||
}
|
||||
|
||||
return slack.ModalViewRequest{
|
||||
Type: slack.VTModal,
|
||||
Title: titleText,
|
||||
Close: closeText,
|
||||
Submit: submitText,
|
||||
Blocks: blocks,
|
||||
PrivateMetadata: channelID,
|
||||
CallbackID: util.SetIncidentRcaSubmit,
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,14 +26,6 @@ func NewIncidentBlock() map[string]interface{} {
|
||||
Text: "Show incidents",
|
||||
},
|
||||
),
|
||||
slack.NewButtonBlockElement(
|
||||
"show_on_call_button",
|
||||
"show_on_call_button_value",
|
||||
&slack.TextBlockObject{
|
||||
Type: slack.PlainTextType,
|
||||
Text: "Show on-call",
|
||||
},
|
||||
),
|
||||
slack.NewButtonBlockElement(
|
||||
util.HelpCommand,
|
||||
"help_commands_button_value",
|
||||
@@ -63,9 +55,7 @@ func ExistingIncidentOptionsBlock() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"blocks": []slack.Block{
|
||||
incidentSectionBlock(),
|
||||
tagsSectionBlock(),
|
||||
rcaSectionBlock(),
|
||||
jiraLinksSectionBlock(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -146,213 +136,15 @@ func incidentSectionBlock() *slack.SectionBlock {
|
||||
return slack.NewSectionBlock(textBlock, nil, accessoryOption, slack.SectionBlockOptionBlockID("incident"))
|
||||
}
|
||||
|
||||
func taskSectionBlock() *slack.SectionBlock {
|
||||
textBlock := &slack.TextBlockObject{
|
||||
Type: "mrkdwn",
|
||||
Text: "Task",
|
||||
func rcaSectionBlock() *slack.ActionBlock {
|
||||
buttonElement := &slack.ButtonBlockElement{
|
||||
Type: "button",
|
||||
ActionID: util.SetRCADetails,
|
||||
Text: &slack.TextBlockObject{
|
||||
Type: "plain_text",
|
||||
Text: "Fill RCA details",
|
||||
},
|
||||
Value: util.SetRCADetails,
|
||||
}
|
||||
|
||||
optionBlockObjects := []*slack.OptionBlockObject{
|
||||
{
|
||||
Text: &slack.TextBlockObject{
|
||||
Type: "plain_text",
|
||||
Text: "Add task",
|
||||
},
|
||||
Value: "addTask",
|
||||
},
|
||||
{
|
||||
Text: &slack.TextBlockObject{
|
||||
Type: "plain_text",
|
||||
Text: "Assign task",
|
||||
},
|
||||
Value: "assignTask",
|
||||
},
|
||||
{
|
||||
Text: &slack.TextBlockObject{
|
||||
Type: "plain_text",
|
||||
Text: "View your tasks",
|
||||
},
|
||||
Value: "viewYourTasks",
|
||||
},
|
||||
{
|
||||
Text: &slack.TextBlockObject{
|
||||
Type: "plain_text",
|
||||
Text: "View all tasks",
|
||||
},
|
||||
Value: "viewAllTasks",
|
||||
},
|
||||
}
|
||||
|
||||
accessoryOption := &slack.Accessory{
|
||||
SelectElement: &slack.SelectBlockElement{
|
||||
Type: "static_select",
|
||||
ActionID: "task",
|
||||
Options: optionBlockObjects,
|
||||
Placeholder: slack.NewTextBlockObject("plain_text", "Select command", false, false),
|
||||
},
|
||||
}
|
||||
return slack.NewSectionBlock(textBlock, nil, accessoryOption, slack.SectionBlockOptionBlockID("task"))
|
||||
}
|
||||
|
||||
func tagsSectionBlock() *slack.SectionBlock {
|
||||
textBlock := &slack.TextBlockObject{
|
||||
Type: "mrkdwn",
|
||||
Text: "Tags",
|
||||
}
|
||||
|
||||
optionBlockObjects := []*slack.OptionBlockObject{
|
||||
{
|
||||
Text: &slack.TextBlockObject{
|
||||
Type: "plain_text",
|
||||
Text: "Add tag(s)",
|
||||
},
|
||||
Value: util.AddTags,
|
||||
},
|
||||
{
|
||||
Text: &slack.TextBlockObject{
|
||||
Type: "plain_text",
|
||||
Text: "Show tags",
|
||||
},
|
||||
Value: util.ShowTags,
|
||||
},
|
||||
{
|
||||
Text: &slack.TextBlockObject{
|
||||
Type: "plain_text",
|
||||
Text: "Remove tag",
|
||||
},
|
||||
Value: util.RemoveTag,
|
||||
},
|
||||
}
|
||||
|
||||
accessoryOption := &slack.Accessory{
|
||||
SelectElement: &slack.SelectBlockElement{
|
||||
Type: "static_select",
|
||||
ActionID: "tags",
|
||||
Options: optionBlockObjects,
|
||||
Placeholder: slack.NewTextBlockObject("plain_text", "Select command", false, false),
|
||||
},
|
||||
}
|
||||
return slack.NewSectionBlock(textBlock, nil, accessoryOption, slack.SectionBlockOptionBlockID("tags"))
|
||||
}
|
||||
|
||||
func onCallSectionBlock() *slack.SectionBlock {
|
||||
textBlock := &slack.TextBlockObject{
|
||||
Type: "mrkdwn",
|
||||
Text: "On-call",
|
||||
}
|
||||
|
||||
optionBlockObjects := []*slack.OptionBlockObject{
|
||||
{
|
||||
Text: &slack.TextBlockObject{
|
||||
Type: "plain_text",
|
||||
Text: "Show on-call",
|
||||
},
|
||||
Value: "showOncall",
|
||||
},
|
||||
{
|
||||
Text: &slack.TextBlockObject{
|
||||
Type: "plain_text",
|
||||
Text: "Show escalation policy",
|
||||
},
|
||||
Value: "showEscalationPolicy",
|
||||
},
|
||||
{
|
||||
Text: &slack.TextBlockObject{
|
||||
Type: "plain_text",
|
||||
Text: "Trigger alert",
|
||||
},
|
||||
Value: "triggerAlert",
|
||||
},
|
||||
}
|
||||
|
||||
accessoryOption := &slack.Accessory{
|
||||
SelectElement: &slack.SelectBlockElement{
|
||||
Type: "static_select",
|
||||
ActionID: "on-call",
|
||||
Options: optionBlockObjects,
|
||||
Placeholder: slack.NewTextBlockObject("plain_text", "Select command", false, false),
|
||||
},
|
||||
}
|
||||
return slack.NewSectionBlock(textBlock, nil, accessoryOption, slack.SectionBlockOptionBlockID("on-call"))
|
||||
}
|
||||
|
||||
func botHelpSectionBlock() *slack.SectionBlock {
|
||||
textBlock := &slack.TextBlockObject{
|
||||
Type: "mrkdwn",
|
||||
Text: "Bot help",
|
||||
}
|
||||
|
||||
optionBlockObjects := []*slack.OptionBlockObject{
|
||||
{
|
||||
Text: &slack.TextBlockObject{
|
||||
Type: "plain_text",
|
||||
Text: "Help - commands",
|
||||
},
|
||||
Value: "helpCommands",
|
||||
},
|
||||
}
|
||||
|
||||
accessoryOption := &slack.Accessory{
|
||||
SelectElement: &slack.SelectBlockElement{
|
||||
Type: "static_select",
|
||||
ActionID: "bot-helps",
|
||||
Options: optionBlockObjects,
|
||||
Placeholder: slack.NewTextBlockObject("plain_text", "Select command", false, false),
|
||||
},
|
||||
}
|
||||
return slack.NewSectionBlock(textBlock, nil, accessoryOption, slack.SectionBlockOptionBlockID("bot-helps"))
|
||||
}
|
||||
|
||||
func rcaSectionBlock() *slack.SectionBlock {
|
||||
textBlock := &slack.TextBlockObject{
|
||||
Type: "mrkdwn",
|
||||
Text: "RCA",
|
||||
}
|
||||
|
||||
optionBlockObjects := []*slack.OptionBlockObject{
|
||||
{
|
||||
Text: &slack.TextBlockObject{
|
||||
Type: "plain_text",
|
||||
Text: "Write RCA",
|
||||
},
|
||||
Value: util.SetRCA,
|
||||
},
|
||||
}
|
||||
|
||||
accessoryOption := &slack.Accessory{
|
||||
SelectElement: &slack.SelectBlockElement{
|
||||
Type: "static_select",
|
||||
ActionID: util.SetRCA,
|
||||
Options: optionBlockObjects,
|
||||
Placeholder: slack.NewTextBlockObject("plain_text", "Select command", false, false),
|
||||
},
|
||||
}
|
||||
return slack.NewSectionBlock(textBlock, nil, accessoryOption, slack.SectionBlockOptionBlockID("set_rca"))
|
||||
}
|
||||
|
||||
func jiraLinksSectionBlock() *slack.SectionBlock {
|
||||
textBlock := &slack.TextBlockObject{
|
||||
Type: "mrkdwn",
|
||||
Text: "Jira links",
|
||||
}
|
||||
|
||||
optionBlockObjects := []*slack.OptionBlockObject{
|
||||
{
|
||||
Text: &slack.TextBlockObject{
|
||||
Type: "plain_text",
|
||||
Text: "Add Jira link(s)",
|
||||
},
|
||||
Value: util.SetJiraLinks,
|
||||
},
|
||||
}
|
||||
|
||||
accessoryOption := &slack.Accessory{
|
||||
SelectElement: &slack.SelectBlockElement{
|
||||
Type: "static_select",
|
||||
ActionID: util.SetJiraLinks,
|
||||
Options: optionBlockObjects,
|
||||
Placeholder: slack.NewTextBlockObject("plain_text", "Select command", false, false),
|
||||
},
|
||||
}
|
||||
return slack.NewSectionBlock(textBlock, nil, accessoryOption, slack.SectionBlockOptionBlockID(util.SetJiraLinks))
|
||||
return slack.NewActionBlock(util.SetRCADetails, buttonElement)
|
||||
}
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"houston/common/util"
|
||||
"houston/model/tag"
|
||||
"strconv"
|
||||
|
||||
"github.com/slack-go/slack"
|
||||
)
|
||||
|
||||
func BuildIncidentUpdateTagModal(channel slack.Channel, inputBlocks []slack.InputBlock) slack.ModalViewRequest {
|
||||
|
||||
rcaObject := slack.NewTextBlockObject(slack.PlainTextType, "RCA", false, false)
|
||||
rcaPlaceholder := slack.NewTextBlockObject(slack.PlainTextType, "Write RCA here...", false, false)
|
||||
rcaElement := slack.NewPlainTextInputBlockElement(rcaPlaceholder, util.SetRCA)
|
||||
rcaElement.Multiline = true
|
||||
rcaBlock := slack.NewInputBlock("RCA", rcaObject, nil, rcaElement)
|
||||
rcaBlock.Optional = true
|
||||
|
||||
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: append(localBlocks, rcaBlock),
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
36
internal/processor/action/view/slack_modal_helpers.go
Normal file
36
internal/processor/action/view/slack_modal_helpers.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"github.com/slack-go/slack"
|
||||
)
|
||||
|
||||
type PlainInputBlockElementData struct {
|
||||
BasicData BasicInputElementData
|
||||
}
|
||||
|
||||
type BasicInputElementData struct {
|
||||
Header string
|
||||
PlaceHolder string
|
||||
ActionId string
|
||||
Optional bool
|
||||
MultiLine bool
|
||||
MaxLength int
|
||||
}
|
||||
|
||||
type SimpleInputBlockElementData struct {
|
||||
BasicData BasicInputElementData
|
||||
InitialValue string
|
||||
}
|
||||
|
||||
func CreatePlainTextInputBlock(inputData SimpleInputBlockElementData) *slack.InputBlock {
|
||||
basicInputData := inputData.BasicData
|
||||
title := slack.NewTextBlockObject(slack.PlainTextType, basicInputData.Header, false, false)
|
||||
placeholder := slack.NewTextBlockObject(slack.PlainTextType, basicInputData.PlaceHolder, false, false)
|
||||
slackElement := slack.NewPlainTextInputBlockElement(placeholder, basicInputData.ActionId)
|
||||
slackElement.InitialValue = inputData.InitialValue
|
||||
slackElement.Multiline = basicInputData.MultiLine
|
||||
slackElement.MaxLength = basicInputData.MaxLength
|
||||
slackBlock := slack.NewInputBlock(basicInputData.Header, title, nil, slackElement)
|
||||
slackBlock.Optional = basicInputData.Optional
|
||||
return slackBlock
|
||||
}
|
||||
@@ -37,13 +37,10 @@ type BlockActionProcessor struct {
|
||||
incidentUpdateSeverityAction *action.IncidentUpdateSevertityAction
|
||||
incidentUpdateTitleAction *action.IncidentUpdateTitleAction
|
||||
incidentUpdateDescriptionAction *action.IncidentUpdateDescriptionAction
|
||||
incidentUpdateTagsAction *action.IncidentUpdateTagsAction
|
||||
incidentShowTagsAction *action.IncidentShowTagsAction
|
||||
incidentUpdateRcaAction *action.IncidentUpdateRcaAction
|
||||
incidentUpdateJiraLinksAction *action.IncidentUpdateJiraLinksAction
|
||||
incidentDuplicateAction *action.DuplicateIncidentAction
|
||||
incidentServiceV2 *incidentService.IncidentServiceV2
|
||||
slackService *slack2.SlackService
|
||||
incidentRCASectionAction *action.IncidentRCASectionAction
|
||||
}
|
||||
|
||||
func NewBlockActionProcessor(
|
||||
@@ -76,15 +73,9 @@ func NewBlockActionProcessor(
|
||||
incidentUpdateTitleAction: action.NewIncidentUpdateTitleAction(socketModeClient, incidentRepository,
|
||||
teamService, severityService, slackbotClient),
|
||||
incidentUpdateDescriptionAction: action.NewIncidentUpdateDescriptionAction(socketModeClient, incidentRepository),
|
||||
incidentUpdateTagsAction: action.NewIncidentUpdateTagsAction(socketModeClient, incidentRepository,
|
||||
teamService, tagService),
|
||||
incidentShowTagsAction: action.NewIncidentShowTagsProcessor(socketModeClient, incidentRepository,
|
||||
tagService),
|
||||
incidentUpdateRcaAction: action.NewIncidentUpdateRcaAction(socketModeClient, incidentRepository),
|
||||
incidentUpdateJiraLinksAction: action.NewIncidentUpdateJiraLinksAction(socketModeClient, incidentRepository,
|
||||
incidentServiceV2, slackService),
|
||||
incidentDuplicateAction: action.NewDuplicateIncidentProcessor(socketModeClient, incidentRepository,
|
||||
tagService, teamService, severityService),
|
||||
incidentRCASectionAction: action.NewIncidentRCASectionAction(socketModeClient, incidentRepository, teamService, tagService, severityService, action.NewIncidentTagsAction(incidentRepository, tagService), action.NewIncidentRCASummaryAction(incidentRepository), action.NewIncidentJiraLinksAction(incidentServiceV2, slackService), rcaService),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,17 +111,9 @@ func (bap *BlockActionProcessor) ProcessCommand(callback slack.InteractionCallba
|
||||
bap.processIncidentCommands(callback, request)
|
||||
}
|
||||
|
||||
case util.Tags:
|
||||
case util.SetRCADetails:
|
||||
{
|
||||
bap.processTagsCommands(callback, request)
|
||||
}
|
||||
case util.SetRCA:
|
||||
{
|
||||
bap.processRcaCommands(callback, request)
|
||||
}
|
||||
case util.SetJiraLinks:
|
||||
{
|
||||
bap.processJiraLinkCommands(callback, request)
|
||||
bap.processRCASectionCommands(callback, request)
|
||||
}
|
||||
default:
|
||||
{
|
||||
@@ -157,7 +140,7 @@ func (bap *BlockActionProcessor) processIncidentCommands(callback slack.Interact
|
||||
}
|
||||
case util.ResolveIncident:
|
||||
{
|
||||
bap.incidentResolveAction.IncidentResolveProcess(callback, request)
|
||||
bap.incidentRCASectionAction.ProcessIncidentRCAActionRequest(callback, request, util.IncidentResolveSubmit)
|
||||
}
|
||||
case util.SetIncidentStatus:
|
||||
{
|
||||
@@ -186,40 +169,16 @@ func (bap *BlockActionProcessor) processIncidentCommands(callback slack.Interact
|
||||
}
|
||||
}
|
||||
|
||||
func (bap *BlockActionProcessor) processRcaCommands(callback slack.InteractionCallback, request *socketmode.Request) {
|
||||
action1 := util.BlockActionType(callback.ActionCallback.BlockActions[0].SelectedOption.Value)
|
||||
func (bap *BlockActionProcessor) processRCASectionCommands(callback slack.InteractionCallback, request *socketmode.Request) {
|
||||
action1 := util.BlockActionType(callback.ActionCallback.BlockActions[0].ActionID)
|
||||
switch action1 {
|
||||
case util.SetRCA:
|
||||
case util.SetRCADetails:
|
||||
{
|
||||
bap.incidentUpdateRcaAction.IncidentUpdateRcaRequestProcess(callback, request)
|
||||
bap.incidentRCASectionAction.ProcessIncidentRCAActionRequest(callback, request, util.SetIncidentRCADetailsSubmit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (bap *BlockActionProcessor) processJiraLinkCommands(callback slack.InteractionCallback, request *socketmode.Request) {
|
||||
action1 := util.BlockActionType(callback.ActionCallback.BlockActions[0].SelectedOption.Value)
|
||||
switch action1 {
|
||||
case util.SetJiraLinks:
|
||||
case util.ShowRCADetails:
|
||||
{
|
||||
bap.incidentUpdateJiraLinksAction.IncidentUpdateJiraLinksRequestProcess(callback, request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (bap *BlockActionProcessor) processTagsCommands(callback slack.InteractionCallback, request *socketmode.Request) {
|
||||
action1 := util.BlockActionType(callback.ActionCallback.BlockActions[0].SelectedOption.Value)
|
||||
switch action1 {
|
||||
case util.AddTags:
|
||||
{
|
||||
bap.incidentUpdateTagsAction.IncidentUpdateTagsRequestProcess(callback, request)
|
||||
}
|
||||
case util.ShowTags:
|
||||
{
|
||||
bap.incidentShowTagsAction.IncidentShowTagsRequestProcess(callback, request)
|
||||
}
|
||||
case util.RemoveTag:
|
||||
{
|
||||
bap.incidentUpdateTagsAction.IncidentUpdateTagsRequestProcess(callback, request)
|
||||
bap.incidentRCASectionAction.PerformShowRCADetailsAction(callback, request)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -234,12 +193,11 @@ type ViewSubmissionProcessor struct {
|
||||
incidentUpdateDescriptionAction *action.IncidentUpdateDescriptionAction
|
||||
incidentUpdateSeverityAction *action.IncidentUpdateSevertityAction
|
||||
incidentUpdateTypeAction *action.IncidentUpdateTypeAction
|
||||
incidentUpdateTagsAction *action.IncidentUpdateTagsAction
|
||||
incidentUpdateRca *action.IncidentUpdateRcaAction
|
||||
incidentUpdateJiraLinks *action.IncidentUpdateJiraLinksAction
|
||||
incidentAdditionalAction *action.IncidentRCASectionAction
|
||||
showIncidentSubmitAction *action.ShowIncidentsSubmitAction
|
||||
incidentDuplicateAction *action.DuplicateIncidentAction
|
||||
db *gorm.DB
|
||||
incidentRCAAction *action.IncidentRCASectionAction
|
||||
}
|
||||
|
||||
func NewViewSubmissionProcessor(
|
||||
@@ -251,6 +209,7 @@ func NewViewSubmissionProcessor(
|
||||
teamRepository *team.Repository,
|
||||
slackbotClient *slackbot.Client,
|
||||
db *gorm.DB,
|
||||
rcaService *rca.RcaService,
|
||||
incidentServiceV2 *incidentService.IncidentServiceV2,
|
||||
) *ViewSubmissionProcessor {
|
||||
slackService := slack2.NewSlackService()
|
||||
@@ -270,15 +229,11 @@ func NewViewSubmissionProcessor(
|
||||
severityService, teamService, slackbotClient, incidentServiceV2),
|
||||
incidentUpdateTypeAction: action.NewIncidentUpdateTypeAction(socketModeClient, incidentRepository,
|
||||
teamService, severityService, slackbotClient, incidentServiceV2),
|
||||
incidentUpdateTagsAction: action.NewIncidentUpdateTagsAction(socketModeClient, incidentRepository,
|
||||
teamService, tagService),
|
||||
incidentUpdateRca: action.NewIncidentUpdateRcaAction(socketModeClient, incidentRepository),
|
||||
incidentUpdateJiraLinks: action.NewIncidentUpdateJiraLinksAction(socketModeClient, incidentRepository,
|
||||
incidentServiceV2, slackService),
|
||||
showIncidentSubmitAction: action.NewShowIncidentsSubmitAction(socketModeClient, incidentRepository,
|
||||
teamRepository),
|
||||
incidentDuplicateAction: action.NewDuplicateIncidentProcessor(socketModeClient, incidentRepository,
|
||||
tagService, teamRepository, severityService),
|
||||
incidentRCAAction: action.NewIncidentRCASectionAction(socketModeClient, incidentRepository, teamService, tagService, severityService, action.NewIncidentTagsAction(incidentRepository, tagService), action.NewIncidentRCASummaryAction(incidentRepository), action.NewIncidentJiraLinksAction(incidentServiceV2, slackService), rcaService),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,17 +279,13 @@ func (vsp *ViewSubmissionProcessor) ProcessCommand(callback slack.InteractionCal
|
||||
{
|
||||
vsp.incidentUpdateTypeAction.IncidentUpdateType(callback, request, callback.Channel, callback.User)
|
||||
}
|
||||
case util.UpdateTagSubmit:
|
||||
case util.IncidentResolveSubmit:
|
||||
{
|
||||
vsp.incidentUpdateTagsAction.IncidentUpdateTags(callback, request)
|
||||
vsp.incidentRCAAction.PerformSetIncidentRCADetailsAction(callback, request, util.IncidentResolveSubmit)
|
||||
}
|
||||
case util.SetIncidentRcaSubmit:
|
||||
case util.SetIncidentRCADetailsSubmit:
|
||||
{
|
||||
vsp.incidentUpdateRca.IncidentUpdateRca(callback, request, callback.Channel, callback.User)
|
||||
}
|
||||
case util.SetIncidentJiraLinksSubmit:
|
||||
{
|
||||
vsp.incidentUpdateJiraLinks.IncidentUpdateJiraLinks(callback, request, callback.Channel, callback.User)
|
||||
vsp.incidentRCAAction.PerformSetIncidentRCADetailsAction(callback, request, util.SetIncidentRCADetailsSubmit)
|
||||
}
|
||||
case util.ShowIncidentSubmit:
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"houston/internal/processor/action"
|
||||
"houston/logger"
|
||||
"houston/pkg/slackbot"
|
||||
"houston/service/rca"
|
||||
)
|
||||
|
||||
type OpenFillRCAViewModalCommandProcessor struct {
|
||||
@@ -18,10 +19,12 @@ const openSetRCAViewModalCommandProcessorLogTag = "[open_set_rca_view_modal_comm
|
||||
func NewOpenFillRCAViewModalCommandProcessor(
|
||||
socketModeClient *socketmode.Client,
|
||||
slackBot *slackbot.Client,
|
||||
rcaService *rca.RcaService,
|
||||
) *OpenFillRCAViewModalCommandProcessor {
|
||||
|
||||
return &OpenFillRCAViewModalCommandProcessor{
|
||||
socketModeClient: socketModeClient,
|
||||
openSetRCAViewModalCommandAction: action.NewOpenFillRCAViewModalCommandAction(socketModeClient, slackBot),
|
||||
openSetRCAViewModalCommandAction: action.NewOpenFillRCAViewModalCommandAction(socketModeClient, slackBot, rcaService),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -117,6 +117,7 @@ func (resolver *HoustonCommandResolver) Resolve(evt *socketmode.Event) processor
|
||||
commandProcessor = processor.NewOpenFillRCAViewModalCommandProcessor(
|
||||
resolver.socketModeClient,
|
||||
resolver.slackBotClient,
|
||||
resolver.rcaService,
|
||||
)
|
||||
|
||||
case strings.HasPrefix(params, internal.HelpParam):
|
||||
|
||||
@@ -567,6 +567,19 @@ func (r *Repository) GetIncidentTagByTagId(incidentId uint, tagId uint) (*Incide
|
||||
return &incidentTag, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetIncidentTagsByTagIds(incidentId uint, tagIds []uint) (*IncidentTagEntity, error) {
|
||||
var incidentTag IncidentTagEntity
|
||||
|
||||
result := r.gormClient.Find(&incidentTag, "incident_id = ? and tag_id in ?", incidentId, tagIds)
|
||||
if result.Error != nil {
|
||||
return nil, result.Error
|
||||
} else if result.RowsAffected == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &incidentTag, nil
|
||||
}
|
||||
|
||||
func (r *Repository) SaveIncidentTag(entity IncidentTagEntity) (*IncidentTagEntity, error) {
|
||||
tx := r.gormClient.Save(&entity)
|
||||
if tx.Error != nil {
|
||||
|
||||
@@ -12,11 +12,14 @@ const (
|
||||
|
||||
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"`
|
||||
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"`
|
||||
Optional bool `gorm:"column:optional"`
|
||||
Active bool `gorm:"column:active"`
|
||||
DisplayOrder int8 `gorm:"column:display_order"`
|
||||
}
|
||||
|
||||
func (TagEntity) TableName() string {
|
||||
@@ -25,8 +28,9 @@ func (TagEntity) TableName() string {
|
||||
|
||||
type TagValueEntity struct {
|
||||
gorm.Model
|
||||
TagId uint `gorm:"column:tag_id"`
|
||||
Value string `gorm:"column:value"`
|
||||
TagId uint `gorm:"column:tag_id"`
|
||||
Value string `gorm:"column:value"`
|
||||
Active bool `gorm:"column:active"`
|
||||
}
|
||||
|
||||
func (TagValueEntity) TableName() string {
|
||||
|
||||
@@ -4,6 +4,15 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ITagRepository interface {
|
||||
FindById(id uint) (*TagEntity, error)
|
||||
FindTagValuesByTagId(id uint) (*[]TagValueEntity, error)
|
||||
FindTagValuesByIds(ids []int32) (*[]TagValueEntity, error)
|
||||
FindTagsByTeamId(teamId uint) (*[]TagDTO, error)
|
||||
GetActiveTags() (*[]TagDTO, error)
|
||||
GetTagValuesByTagValueId(tagValueIds []uint) ([]string, error)
|
||||
}
|
||||
|
||||
type Repository struct {
|
||||
gormClient *gorm.DB
|
||||
}
|
||||
@@ -30,7 +39,7 @@ func (r *Repository) FindById(id uint) (*TagEntity, error) {
|
||||
|
||||
func (r *Repository) FindTagValuesByTagId(id uint) (*[]TagValueEntity, error) {
|
||||
var tagValues []TagValueEntity
|
||||
tx := r.gormClient.Raw("select tv.* from tag_value tv inner join tag t on t.id = tv.tag_id where t.id = ?", id).Scan(&tagValues)
|
||||
tx := r.gormClient.Raw("select tv.* from tag_value tv inner join tag t on t.id = tv.tag_id where t.id = ? and tv.active=?", id, true).Scan(&tagValues)
|
||||
|
||||
if tx.Error != nil {
|
||||
return nil, tx.Error
|
||||
@@ -66,3 +75,40 @@ func (r *Repository) FindTagsByTeamId(teamId uint) (*[]TagDTO, error) {
|
||||
}
|
||||
return &tags, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetActiveTags() (*[]TagDTO, error) {
|
||||
var tags []TagDTO
|
||||
tx := r.gormClient.Table(TagEntity{}.TableName()).Where("active = ?", true).Order("display_order").Find(&tags)
|
||||
if tx.Error != nil {
|
||||
return nil, tx.Error
|
||||
}
|
||||
if tx.RowsAffected == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return &tags, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetMandatoryActiveTags() (*[]TagDTO, error) {
|
||||
var tags []TagDTO
|
||||
tx := r.gormClient.Table(TagEntity{}.TableName()).Where("active = ? and optional = ?", true, false).Order("display_order").Find(&tags)
|
||||
if tx.Error != nil {
|
||||
return nil, tx.Error
|
||||
}
|
||||
if tx.RowsAffected == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return &tags, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetTagValuesByTagValueId(tagValueIds []uint) ([]string, error) {
|
||||
var tagValues []string
|
||||
tx := r.gormClient.Model(TagValueEntity{}).Where("id in ?", tagValueIds).Where("active = ?", true).Pluck("value", &tagValues)
|
||||
|
||||
if tx.Error != nil {
|
||||
return nil, tx.Error
|
||||
}
|
||||
if tx.RowsAffected == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return tagValues, nil
|
||||
}
|
||||
|
||||
@@ -254,6 +254,21 @@ const (
|
||||
UnLinkJira = "unLink"
|
||||
)
|
||||
|
||||
func (i *IncidentServiceV2) UpdateIncidentJiraLinksEntity(entity *incident.IncidentEntity, user string, jiraToBeUpdated []string) error {
|
||||
slackUser, err := i.slackService.GetUserByEmailOrID(user)
|
||||
entity.JiraLinks = jiraToBeUpdated
|
||||
entity.UpdatedBy = slackUser.ID
|
||||
entity.UpdatedAt = time.Now()
|
||||
|
||||
err = i.incidentRepository.UpdateIncident(entity)
|
||||
if err != nil {
|
||||
errorMessage := fmt.Sprintf("%s failed to update JIRA IDs for incident %s", logTag, entity.IncidentName)
|
||||
logger.Error(errorMessage)
|
||||
return fmt.Errorf(errorMessage, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *IncidentServiceV2) updateJiraIDs(entity *incident.IncidentEntity, user, logTag, action string, jiraLinks ...string) error {
|
||||
slackUser, err := i.slackService.GetUserByEmailOrID(user)
|
||||
var updatedBy string
|
||||
|
||||
@@ -13,5 +13,6 @@ type IIncidentService interface {
|
||||
UnLinkJiraFromIncident(incidentId uint, unLinkedBy, jiraLink string) error
|
||||
GetAllOpenIncidents() ([]incident.IncidentEntity, int, error)
|
||||
GetIncidentRoleByIncidentIdAndRole(incidentId uint, role string) (*incident.IncidentRoleEntity, error)
|
||||
UpdateIncidentJiraLinksEntity(incidentEntity *incident.IncidentEntity, updatedBy string, jiraLinks []string) error
|
||||
IsHoustonChannel(channelID string) (bool, error)
|
||||
}
|
||||
|
||||
@@ -503,6 +503,7 @@ func splitUsers(users []string, chunkSize int) [][]string {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func convertToConversationResponse(message slack.Message) service.ConversationResponse {
|
||||
return service.ConversationResponse{
|
||||
UserName: message.User,
|
||||
@@ -511,6 +512,20 @@ func convertToConversationResponse(message slack.Message) service.ConversationRe
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SlackService) AckRequest(request socketmode.Request) {
|
||||
var payload interface{}
|
||||
s.SocketModeClient.Ack(request, payload)
|
||||
}
|
||||
|
||||
func (s *SlackService) OpenView(callback slack.InteractionCallback, modalRequest slack.ModalViewRequest, action string) {
|
||||
_, err := s.SocketModeClient.OpenView(callback.TriggerID, modalRequest)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("houston slackbot open view command for %s failed.", action),
|
||||
zap.String("trigger_id", callback.TriggerID), zap.String("channel_id", callback.Channel.ID), zap.Error(err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SlackService) GetConversationInfo(channelId string) (*slack.Channel, error) {
|
||||
channel, err := s.SocketModeClient.GetConversationInfo(&slack.GetConversationInfoInput{
|
||||
ChannelID: channelId,
|
||||
|
||||
Reference in New Issue
Block a user