From c46a12967156d263ef69989a81ad25e31ccd47cf Mon Sep 17 00:00:00 2001 From: Peter Ruan Date: Wed, 1 Oct 2025 22:32:45 -0700 Subject: [PATCH] [WIP] [DPTP-4524] add support to allow ActivityType to be specified when user file an issue using the team slackbot --- pkg/jira/fake.go | 5 ++++ pkg/jira/issues.go | 38 ++++++++++++++++++++++++---- pkg/slack/modals/bug/view.go | 11 +++++--- pkg/slack/modals/enhancement/view.go | 11 +++++--- pkg/slack/modals/handlers.go | 21 ++++++++++----- 5 files changed, 66 insertions(+), 20 deletions(-) diff --git a/pkg/jira/fake.go b/pkg/jira/fake.go index 2b8bc14d5ee..c094212c39d 100644 --- a/pkg/jira/fake.go +++ b/pkg/jira/fake.go @@ -27,6 +27,11 @@ type Fake struct { // FileIssue files the issue using injected behavior func (f *Fake) FileIssue(issueType, title, description, reporter string, logger *logrus.Entry) (*jira.Issue, error) { + return f.FileIssueWithFields(issueType, title, description, reporter, nil, logger) +} + +// FileIssueWithFields files the issue with custom fields using injected behavior +func (f *Fake) FileIssueWithFields(issueType, title, description, reporter string, customFields map[string]interface{}, logger *logrus.Entry) (*jira.Issue, error) { request := IssueRequest{ IssueType: issueType, Title: title, diff --git a/pkg/jira/issues.go b/pkg/jira/issues.go index 2c74f660538..e83cc03d3e0 100644 --- a/pkg/jira/issues.go +++ b/pkg/jira/issues.go @@ -19,11 +19,20 @@ const ( IssueTypeBug = "Bug" IssueTypeStory = "Story" IssueTypeTask = "Task" + + // Activity Type field ID in Jira - this should match Jira instance's custom field ID + // goto https://issues.redhat.com/rest/api/2/field/ to get the field ID + ActivityTypeFieldID = "customfield_12320040" + + // Activity Type values + ActivityTypeBugFix = "Bug Fix" + ActivityTypeEnhancement = "Enhancement" ) // IssueFiler knows how to file an issue in Jira type IssueFiler interface { FileIssue(issueType, title, description, reporter string, logger *logrus.Entry) (*jira.Issue, error) + FileIssueWithFields(issueType, title, description, reporter string, customFields map[string]interface{}, logger *logrus.Entry) (*jira.Issue, error) } type slackClient interface { @@ -78,20 +87,39 @@ type filer struct { // quirks like how issue types and projects are provided, as well as // transforming the Slack reporter ID to a Jira user, when possible. func (f *filer) FileIssue(issueType, title, description, reporter string, logger *logrus.Entry) (*jira.Issue, error) { + return f.FileIssueWithFields(issueType, title, description, reporter, nil, logger) +} + +// FileIssueWithFields files an issue with custom fields +func (f *filer) FileIssueWithFields(issueType, title, description, reporter string, customFields map[string]interface{}, logger *logrus.Entry) (*jira.Issue, error) { suffix, requester := f.resolveRequester(reporter, logger) description = fmt.Sprintf("%s\n\nThis issue was filed by %s", description, suffix) logger.WithFields(logrus.Fields{ - "title": title, - "reporter": requester.Name, - "type": issueType, + "title": title, + "reporter": requester.Name, + "type": issueType, + "customFields": len(customFields), }).Debug("Filing Jira issue.") - toCreate := &jira.Issue{Fields: &jira.IssueFields{ + + issueFields := &jira.IssueFields{ Project: f.project, Reporter: requester, Type: f.issueTypesByName[issueType], Summary: title, Description: description, - }} + } + + // Add custom fields if provided + if len(customFields) > 0 { + if issueFields.Unknowns == nil { + issueFields.Unknowns = make(map[string]interface{}) + } + for key, value := range customFields { + issueFields.Unknowns[key] = value + } + } + + toCreate := &jira.Issue{Fields: issueFields} issue, response, err := f.jiraClient.CreateIssue(toCreate) return issue, jirautil.HandleJiraError(response, err) } diff --git a/pkg/slack/modals/bug/view.go b/pkg/slack/modals/bug/view.go index 30d558bbf0a..6f09080ba65 100644 --- a/pkg/slack/modals/bug/view.go +++ b/pkg/slack/modals/bug/view.go @@ -7,7 +7,7 @@ import ( "github.com/sirupsen/logrus" "github.com/slack-go/slack" - "github.com/openshift/ci-tools/pkg/jira" + localjira "github.com/openshift/ci-tools/pkg/jira" "github.com/openshift/ci-tools/pkg/slack/interactions" "github.com/openshift/ci-tools/pkg/slack/modals" "github.com/openshift/ci-tools/pkg/slack/modals/helpdesk" @@ -164,7 +164,7 @@ func validateSubmissionHandler() interactions.PartialHandler { func issueParameters() modals.JiraIssueParameters { return modals.JiraIssueParameters{ Id: Identifier, - IssueType: jira.IssueTypeBug, + IssueType: localjira.IssueTypeBug, Template: template.Must(template.New(string(Identifier)).Parse(`h3. Symptomatic Behavior {{ .` + blockIdSymptom + ` }} @@ -180,16 +180,19 @@ h3. Category h3. How to Reproduce {{ .` + blockIdReproduction + ` }}`)), Fields: []string{modals.BlockIdTitle, blockIdCategory, blockIdOptional, blockIdSymptom, blockIdExpected, blockIdImpact, blockIdReproduction}, + CustomFields: map[string]interface{}{ + localjira.ActivityTypeFieldID: localjira.ActivityTypeBugFix, + }, } } // processSubmissionHandler files a Jira issue for this form -func processSubmissionHandler(filer jira.IssueFiler, updater modals.ViewUpdater) interactions.PartialHandler { +func processSubmissionHandler(filer localjira.IssueFiler, updater modals.ViewUpdater) interactions.PartialHandler { return interactions.PartialFromHandler(modals.ToJiraIssue(issueParameters(), filer, updater)) } // Register creates a registration entry for the bug form -func Register(filer jira.IssueFiler, client *slack.Client) *modals.FlowWithViewAndFollowUps { +func Register(filer localjira.IssueFiler, client *slack.Client) *modals.FlowWithViewAndFollowUps { return modals.ForView(Identifier, View()).WithFollowUps(map[slack.InteractionType]interactions.Handler{ slack.InteractionTypeBlockActions: helpdeskButtonHandler(client), slack.InteractionTypeViewSubmission: interactions.MultiHandler( diff --git a/pkg/slack/modals/enhancement/view.go b/pkg/slack/modals/enhancement/view.go index 6e88fe330c7..7716cc595a2 100644 --- a/pkg/slack/modals/enhancement/view.go +++ b/pkg/slack/modals/enhancement/view.go @@ -5,7 +5,7 @@ import ( "github.com/slack-go/slack" - "github.com/openshift/ci-tools/pkg/jira" + localjira "github.com/openshift/ci-tools/pkg/jira" "github.com/openshift/ci-tools/pkg/slack/interactions" "github.com/openshift/ci-tools/pkg/slack/modals" ) @@ -105,7 +105,7 @@ func View() slack.ModalViewRequest { func issueParameters() modals.JiraIssueParameters { return modals.JiraIssueParameters{ Id: Identifier, - IssueType: jira.IssueTypeStory, + IssueType: localjira.IssueTypeStory, Template: template.Must(template.New(string(Identifier)).Funcs(modals.BulletListFunc()).Parse(`h3. Overview As a {{ .` + blockIdAsA + ` }} I want {{ .` + blockIdIWant + ` }} @@ -126,16 +126,19 @@ h3. Implementation Details {{ .` + blockIdImplementation + ` }} {{- end }}`)), Fields: []string{modals.BlockIdTitle, blockIdAsA, blockIdIWant, blockIdSoThat, blockIdSummary, blockIdImpact, blockIdAcceptanceCriteria, blockIdImplementation}, + CustomFields: map[string]interface{}{ + localjira.ActivityTypeFieldID: localjira.ActivityTypeEnhancement, + }, } } // processSubmissionHandler files a Jira issue for this form -func processSubmissionHandler(filer jira.IssueFiler, updater modals.ViewUpdater) interactions.Handler { +func processSubmissionHandler(filer localjira.IssueFiler, updater modals.ViewUpdater) interactions.Handler { return modals.ToJiraIssue(issueParameters(), filer, updater) } // Register creates a registration entry for the enhancment request form -func Register(filer jira.IssueFiler, client *slack.Client) *modals.FlowWithViewAndFollowUps { +func Register(filer localjira.IssueFiler, client *slack.Client) *modals.FlowWithViewAndFollowUps { return modals.ForView(Identifier, View()).WithFollowUps(map[slack.InteractionType]interactions.Handler{ slack.InteractionTypeViewSubmission: processSubmissionHandler(filer, client), }) diff --git a/pkg/slack/modals/handlers.go b/pkg/slack/modals/handlers.go index adbaf885c88..09741ffa7f2 100644 --- a/pkg/slack/modals/handlers.go +++ b/pkg/slack/modals/handlers.go @@ -7,10 +7,11 @@ import ( "strings" "text/template" + "github.com/andygrunwald/go-jira" "github.com/sirupsen/logrus" "github.com/slack-go/slack" - "github.com/openshift/ci-tools/pkg/jira" + localjira "github.com/openshift/ci-tools/pkg/jira" "github.com/openshift/ci-tools/pkg/slack/interactions" ) @@ -48,10 +49,11 @@ func UpdateViewForButtonPress(identifier, buttonId string, updater ViewUpdater, // JiraIssueParameters holds the metadata used to create a Jira issue type JiraIssueParameters struct { - Id Identifier - IssueType string - Template *template.Template - Fields []string + Id Identifier + IssueType string + Template *template.Template + Fields []string + CustomFields map[string]interface{} // Custom fields to set on the Jira issue } // Process processes the interaction callback data to render the Jira issue title and body @@ -69,7 +71,7 @@ func (p *JiraIssueParameters) Process(callback *slack.InteractionCallback) (stri // has finished. We need this asynchronous response mechanism as the API // calls needed to file the issue often take longer than the 3sec TTL on // responding to the interaction payload we have. -func ToJiraIssue(parameters JiraIssueParameters, filer jira.IssueFiler, updater ViewUpdater) interactions.Handler { +func ToJiraIssue(parameters JiraIssueParameters, filer localjira.IssueFiler, updater ViewUpdater) interactions.Handler { return interactions.HandlerFunc(string(parameters.Id)+".jira", func(callback *slack.InteractionCallback, logger *logrus.Entry) (output []byte, err error) { logger.Infof("Submitting new %s to Jira.", parameters.Id) @@ -89,7 +91,12 @@ func ToJiraIssue(parameters JiraIssueParameters, filer jira.IssueFiler, updater return } - issue, err := filer.FileIssue(parameters.IssueType, title, body, callback.User.ID, logger) + var issue *jira.Issue + if len(parameters.CustomFields) > 0 { + issue, err = filer.FileIssueWithFields(parameters.IssueType, title, body, callback.User.ID, parameters.CustomFields, logger) + } else { + issue, err = filer.FileIssue(parameters.IssueType, title, body, callback.User.ID, logger) + } if err != nil { logger.WithError(err).Errorf("Failed to create %s Jira.", parameters.Id) overwriteView(ErrorView(fmt.Sprintf("create %s Jira issue", parameters.Id), err))