Skip to content

Commit d2dd0cf

Browse files
Jira Integration: Allow configuring issue update via parameter
Signed-off-by: Holger Waschke <[email protected]>
1 parent f656273 commit d2dd0cf

File tree

5 files changed

+230
-72
lines changed

5 files changed

+230
-72
lines changed

config/notifiers.go

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -206,10 +206,14 @@ var (
206206
NotifierConfig: NotifierConfig{
207207
VSendResolved: true,
208208
},
209-
APIType: "auto",
210-
Summary: `{{ template "jira.default.summary" . }}`,
211-
Description: `{{ template "jira.default.description" . }}`,
212-
Priority: `{{ template "jira.default.priority" . }}`,
209+
APIType: "auto",
210+
Summary: JiraFieldConfig{
211+
Template: `{{ template "jira.default.summary" . }}`,
212+
},
213+
Description: JiraFieldConfig{
214+
Template: `{{ template "jira.default.description" . }}`,
215+
},
216+
Priority: `{{ template "jira.default.priority" . }}`,
213217
}
214218

215219
DefaultMattermostConfig = MattermostConfig{
@@ -969,19 +973,26 @@ func (c *MSTeamsV2Config) UnmarshalYAML(unmarshal func(any) error) error {
969973
return nil
970974
}
971975

976+
type JiraFieldConfig struct {
977+
// Template is the template string used to render the field.
978+
Template string `yaml:"template,omitempty" json:"template,omitempty"`
979+
// EnableUpdate indicates whether this field should be omitted when updating an existing issue.
980+
EnableUpdate *bool `yaml:"enable_update,omitempty" json:"enable_update,omitempty"`
981+
}
982+
972983
type JiraConfig struct {
973984
NotifierConfig `yaml:",inline" json:",inline"`
974985
HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"`
975986

976987
APIURL *URL `yaml:"api_url,omitempty" json:"api_url,omitempty"`
977988
APIType string `yaml:"api_type,omitempty" json:"api_type,omitempty"`
978989

979-
Project string `yaml:"project,omitempty" json:"project,omitempty"`
980-
Summary string `yaml:"summary,omitempty" json:"summary,omitempty"`
981-
Description string `yaml:"description,omitempty" json:"description,omitempty"`
982-
Labels []string `yaml:"labels,omitempty" json:"labels,omitempty"`
983-
Priority string `yaml:"priority,omitempty" json:"priority,omitempty"`
984-
IssueType string `yaml:"issue_type,omitempty" json:"issue_type,omitempty"`
990+
Project string `yaml:"project,omitempty" json:"project,omitempty"`
991+
Summary JiraFieldConfig `yaml:"summary,omitempty" json:"summary,omitempty"`
992+
Description JiraFieldConfig `yaml:"description,omitempty" json:"description,omitempty"`
993+
Labels []string `yaml:"labels,omitempty" json:"labels,omitempty"`
994+
Priority string `yaml:"priority,omitempty" json:"priority,omitempty"`
995+
IssueType string `yaml:"issue_type,omitempty" json:"issue_type,omitempty"`
985996

986997
ReopenTransition string `yaml:"reopen_transition,omitempty" json:"reopen_transition,omitempty"`
987998
ResolveTransition string `yaml:"resolve_transition,omitempty" json:"resolve_transition,omitempty"`
@@ -991,6 +1002,28 @@ type JiraConfig struct {
9911002
Fields map[string]any `yaml:"fields,omitempty" json:"custom_fields,omitempty"`
9921003
}
9931004

1005+
func (f *JiraFieldConfig) EnableUpdateValue() bool {
1006+
if f.EnableUpdate == nil {
1007+
return true
1008+
}
1009+
return *f.EnableUpdate
1010+
}
1011+
1012+
// Supports both the legacy string and the new object form.
1013+
func (f *JiraFieldConfig) UnmarshalYAML(unmarshal func(any) error) error {
1014+
// Try simple string first (backward compatibility).
1015+
var s string
1016+
if err := unmarshal(&s); err == nil {
1017+
f.Template = s
1018+
// DisableUpdate stays false by default.
1019+
return nil
1020+
}
1021+
1022+
// Fallback to full object form.
1023+
type plain JiraFieldConfig
1024+
return unmarshal((*plain)(f))
1025+
}
1026+
9941027
func (c *JiraConfig) UnmarshalYAML(unmarshal func(any) error) error {
9951028
*c = DefaultJiraConfig
9961029
type plain JiraConfig

docs/configuration.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1159,11 +1159,23 @@ The default `jira.default.description` template only works with V2.
11591159
# The project key where issues are created.
11601160
project: <string>
11611161
1162-
# Issue summary template.
1163-
[ summary: <tmpl_string> | default = '{{ template "jira.default.summary" . }}' ]
1164-
1165-
# Issue description template.
1166-
[ description: <tmpl_string> | default = '{{ template "jira.default.description" . }}' ]
1162+
# Issue summary configuration.
1163+
[ summary:
1164+
# Template for the issue summary.
1165+
[ template: <tmpl_string> | default = '{{ template "jira.default.summary" . }}' ]
1166+
1167+
# If true, the summary will not be updated when updating an existing issue.
1168+
[ enable_update: <boolean> | default = false ]
1169+
]
1170+
1171+
# Issue description configuration.
1172+
[ description:
1173+
# Template for the issue description.
1174+
[ template: <tmpl_string> | default = '{{ template "jira.default.description" . }}' ]
1175+
1176+
# If true, the description will not be updated when updating an existing issue.
1177+
[ enable_update: <boolean> | default = false ]
1178+
]
11671179
11681180
# Labels to be added to the issue.
11691181
labels:

notify/jira/jira.go

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -103,15 +103,23 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error)
103103
} else {
104104
path = "issue/" + existingIssue.Key
105105
method = http.MethodPut
106-
107-
logger.Debug("updating existing issue", "issue_key", existingIssue.Key)
106+
logger.Debug("updating existing issue", "issue_key", existingIssue.Key, "summary_update_enabled", n.conf.Summary.EnableUpdateValue(), "description_update_enabled", n.conf.Description.EnableUpdateValue())
108107
}
109108

110109
requestBody, err := n.prepareIssueRequestBody(ctx, logger, key.Hash(), tmplTextFunc)
111110
if err != nil {
112111
return false, err
113112
}
114113

114+
if method == http.MethodPut && requestBody.Fields != nil {
115+
if !n.conf.Description.EnableUpdateValue() {
116+
requestBody.Fields.Description = nil
117+
}
118+
if !n.conf.Summary.EnableUpdateValue() {
119+
requestBody.Fields.Summary = nil
120+
}
121+
}
122+
115123
_, shouldRetry, err = n.doAPIRequest(ctx, method, path, requestBody)
116124
if err != nil {
117125
return shouldRetry, fmt.Errorf("failed to %s request to %q: %w", method, path, err)
@@ -121,10 +129,11 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error)
121129
}
122130

123131
func (n *Notifier) prepareIssueRequestBody(_ context.Context, logger *slog.Logger, groupID string, tmplTextFunc template.TemplateFunc) (issue, error) {
124-
summary, err := tmplTextFunc(n.conf.Summary)
132+
summary, err := tmplTextFunc(n.conf.Summary.Template)
125133
if err != nil {
126134
return issue{}, fmt.Errorf("summary template: %w", err)
127135
}
136+
128137
project, err := tmplTextFunc(n.conf.Project)
129138
if err != nil {
130139
return issue{}, fmt.Errorf("project template: %w", err)
@@ -156,12 +165,12 @@ func (n *Notifier) prepareIssueRequestBody(_ context.Context, logger *slog.Logge
156165
requestBody := issue{Fields: &issueFields{
157166
Project: &issueProject{Key: project},
158167
Issuetype: &idNameValue{Name: issueType},
159-
Summary: summary,
168+
Summary: &summary,
160169
Labels: make([]string, 0, len(n.conf.Labels)+1),
161170
Fields: fieldsWithStringKeys,
162171
}}
163172

164-
issueDescriptionString, err := tmplTextFunc(n.conf.Description)
173+
issueDescriptionString, err := tmplTextFunc(n.conf.Description.Template)
165174
if err != nil {
166175
return issue{}, fmt.Errorf("description template: %w", err)
167176
}
@@ -171,14 +180,13 @@ func (n *Notifier) prepareIssueRequestBody(_ context.Context, logger *slog.Logge
171180
logger.Warn("Truncated description", "max_runes", maxDescriptionLenRunes)
172181
}
173182

174-
requestBody.Fields.Description = issueDescriptionString
175-
if strings.HasSuffix(n.conf.APIURL.Path, "/3") {
176-
var issueDescription any
177-
if err := json.Unmarshal([]byte(issueDescriptionString), &issueDescription); err != nil {
178-
return issue{}, fmt.Errorf("description unmarshaling: %w", err)
183+
descriptionCopy := issueDescriptionString
184+
if isAPIv3Path(n.conf.APIURL.Path) {
185+
if !json.Valid([]byte(descriptionCopy)) {
186+
return issue{}, fmt.Errorf("description template: invalid JSON for API v3")
179187
}
180-
requestBody.Fields.Description = issueDescription
181188
}
189+
requestBody.Fields.Description = &descriptionCopy
182190

183191
for i, label := range n.conf.Labels {
184192
label, err = tmplTextFunc(label)
@@ -395,3 +403,7 @@ func (n *Notifier) doAPIRequestFullPath(ctx context.Context, method, path string
395403

396404
return responseBody, false, nil
397405
}
406+
407+
func isAPIv3Path(path string) bool {
408+
return strings.HasSuffix(strings.TrimRight(path, "/"), "/3")
409+
}

0 commit comments

Comments
 (0)