1
0
mirror of https://github.com/prometheus/alertmanager.git synced 2026-02-05 15:45:34 +01:00

Jira Integration: Allow configuring issue update via parameter (#4621)

* Jira Integration: Allow configuring issue update via parameter

---------

Signed-off-by: Holger Waschke <waschkester@gmail.com>
This commit is contained in:
Holger Waschke
2025-11-18 18:12:39 +01:00
committed by GitHub
parent f656273159
commit a90da796bd
5 changed files with 229 additions and 71 deletions

View File

@@ -206,10 +206,14 @@ var (
NotifierConfig: NotifierConfig{
VSendResolved: true,
},
APIType: "auto",
Summary: `{{ template "jira.default.summary" . }}`,
Description: `{{ template "jira.default.description" . }}`,
Priority: `{{ template "jira.default.priority" . }}`,
APIType: "auto",
Summary: JiraFieldConfig{
Template: `{{ template "jira.default.summary" . }}`,
},
Description: JiraFieldConfig{
Template: `{{ template "jira.default.description" . }}`,
},
Priority: `{{ template "jira.default.priority" . }}`,
}
DefaultMattermostConfig = MattermostConfig{
@@ -969,6 +973,13 @@ func (c *MSTeamsV2Config) UnmarshalYAML(unmarshal func(any) error) error {
return nil
}
type JiraFieldConfig struct {
// Template is the template string used to render the field.
Template string `yaml:"template,omitempty" json:"template,omitempty"`
// EnableUpdate indicates whether this field should be omitted when updating an existing issue.
EnableUpdate *bool `yaml:"enable_update,omitempty" json:"enable_update,omitempty"`
}
type JiraConfig struct {
NotifierConfig `yaml:",inline" json:",inline"`
HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"`
@@ -976,12 +987,12 @@ type JiraConfig struct {
APIURL *URL `yaml:"api_url,omitempty" json:"api_url,omitempty"`
APIType string `yaml:"api_type,omitempty" json:"api_type,omitempty"`
Project string `yaml:"project,omitempty" json:"project,omitempty"`
Summary string `yaml:"summary,omitempty" json:"summary,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Labels []string `yaml:"labels,omitempty" json:"labels,omitempty"`
Priority string `yaml:"priority,omitempty" json:"priority,omitempty"`
IssueType string `yaml:"issue_type,omitempty" json:"issue_type,omitempty"`
Project string `yaml:"project,omitempty" json:"project,omitempty"`
Summary JiraFieldConfig `yaml:"summary,omitempty" json:"summary,omitempty"`
Description JiraFieldConfig `yaml:"description,omitempty" json:"description,omitempty"`
Labels []string `yaml:"labels,omitempty" json:"labels,omitempty"`
Priority string `yaml:"priority,omitempty" json:"priority,omitempty"`
IssueType string `yaml:"issue_type,omitempty" json:"issue_type,omitempty"`
ReopenTransition string `yaml:"reopen_transition,omitempty" json:"reopen_transition,omitempty"`
ResolveTransition string `yaml:"resolve_transition,omitempty" json:"resolve_transition,omitempty"`
@@ -991,6 +1002,28 @@ type JiraConfig struct {
Fields map[string]any `yaml:"fields,omitempty" json:"custom_fields,omitempty"`
}
func (f *JiraFieldConfig) EnableUpdateValue() bool {
if f.EnableUpdate == nil {
return true
}
return *f.EnableUpdate
}
// Supports both the legacy string and the new object form.
func (f *JiraFieldConfig) UnmarshalYAML(unmarshal func(any) error) error {
// Try simple string first (backward compatibility).
var s string
if err := unmarshal(&s); err == nil {
f.Template = s
// DisableUpdate stays false by default.
return nil
}
// Fallback to full object form.
type plain JiraFieldConfig
return unmarshal((*plain)(f))
}
func (c *JiraConfig) UnmarshalYAML(unmarshal func(any) error) error {
*c = DefaultJiraConfig
type plain JiraConfig

View File

@@ -1159,11 +1159,23 @@ The default `jira.default.description` template only works with V2.
# The project key where issues are created.
project: <string>
# Issue summary template.
[ summary: <tmpl_string> | default = '{{ template "jira.default.summary" . }}' ]
# Issue summary configuration.
[ summary:
# Template for the issue summary.
[ template: <tmpl_string> | default = '{{ template "jira.default.summary" . }}' ]
# If true, the summary will not be updated when updating an existing issue.
[ enable_update: <boolean> | default = false ]
]
# Issue description template.
[ description: <tmpl_string> | default = '{{ template "jira.default.description" . }}' ]
# Issue description configuration.
[ description:
# Template for the issue description.
[ template: <tmpl_string> | default = '{{ template "jira.default.description" . }}' ]
# If true, the description will not be updated when updating an existing issue.
[ enable_update: <boolean> | default = false ]
]
# Labels to be added to the issue.
labels:

View File

@@ -103,8 +103,7 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error)
} else {
path = "issue/" + existingIssue.Key
method = http.MethodPut
logger.Debug("updating existing issue", "issue_key", existingIssue.Key)
logger.Debug("updating existing issue", "issue_key", existingIssue.Key, "summary_update_enabled", n.conf.Summary.EnableUpdateValue(), "description_update_enabled", n.conf.Description.EnableUpdateValue())
}
requestBody, err := n.prepareIssueRequestBody(ctx, logger, key.Hash(), tmplTextFunc)
@@ -112,6 +111,15 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error)
return false, err
}
if method == http.MethodPut && requestBody.Fields != nil {
if !n.conf.Description.EnableUpdateValue() {
requestBody.Fields.Description = nil
}
if !n.conf.Summary.EnableUpdateValue() {
requestBody.Fields.Summary = nil
}
}
_, shouldRetry, err = n.doAPIRequest(ctx, method, path, requestBody)
if err != nil {
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)
}
func (n *Notifier) prepareIssueRequestBody(_ context.Context, logger *slog.Logger, groupID string, tmplTextFunc template.TemplateFunc) (issue, error) {
summary, err := tmplTextFunc(n.conf.Summary)
summary, err := tmplTextFunc(n.conf.Summary.Template)
if err != nil {
return issue{}, fmt.Errorf("summary template: %w", err)
}
project, err := tmplTextFunc(n.conf.Project)
if err != nil {
return issue{}, fmt.Errorf("project template: %w", err)
@@ -156,12 +165,12 @@ func (n *Notifier) prepareIssueRequestBody(_ context.Context, logger *slog.Logge
requestBody := issue{Fields: &issueFields{
Project: &issueProject{Key: project},
Issuetype: &idNameValue{Name: issueType},
Summary: summary,
Summary: &summary,
Labels: make([]string, 0, len(n.conf.Labels)+1),
Fields: fieldsWithStringKeys,
}}
issueDescriptionString, err := tmplTextFunc(n.conf.Description)
issueDescriptionString, err := tmplTextFunc(n.conf.Description.Template)
if err != nil {
return issue{}, fmt.Errorf("description template: %w", err)
}
@@ -171,14 +180,13 @@ func (n *Notifier) prepareIssueRequestBody(_ context.Context, logger *slog.Logge
logger.Warn("Truncated description", "max_runes", maxDescriptionLenRunes)
}
requestBody.Fields.Description = issueDescriptionString
if strings.HasSuffix(n.conf.APIURL.Path, "/3") {
var issueDescription any
if err := json.Unmarshal([]byte(issueDescriptionString), &issueDescription); err != nil {
return issue{}, fmt.Errorf("description unmarshaling: %w", err)
descriptionCopy := issueDescriptionString
if isAPIv3Path(n.conf.APIURL.Path) {
if !json.Valid([]byte(descriptionCopy)) {
return issue{}, fmt.Errorf("description template: invalid JSON for API v3")
}
requestBody.Fields.Description = issueDescription
}
requestBody.Fields.Description = &descriptionCopy
for i, label := range n.conf.Labels {
label, err = tmplTextFunc(label)
@@ -395,3 +403,7 @@ func (n *Notifier) doAPIRequestFullPath(ctx context.Context, method, path string
return responseBody, false, nil
}
func isAPIv3Path(path string) bool {
return strings.HasSuffix(strings.TrimRight(path, "/"), "/3")
}

View File

@@ -36,6 +36,14 @@ import (
"github.com/prometheus/alertmanager/types"
)
func stringPtr(v string) *string {
return &v
}
func boolPtr(v bool) *bool {
return &v
}
func TestJiraRetry(t *testing.T) {
notifier, err := New(
&config.JiraConfig{
@@ -109,8 +117,8 @@ func TestSearchExistingIssue(t *testing.T) {
{
title: "search existing issue with project template for firing alert",
cfg: &config.JiraConfig{
Summary: `{{ template "jira.default.summary" . }}`,
Description: `{{ template "jira.default.description" . }}`,
Summary: config.JiraFieldConfig{Template: `{{ template "jira.default.summary" . }}`},
Description: config.JiraFieldConfig{Template: `{{ template "jira.default.description" . }}`},
Project: `{{ .CommonLabels.project }}`,
},
groupKey: "1",
@@ -120,8 +128,8 @@ func TestSearchExistingIssue(t *testing.T) {
{
title: "search existing issue with reopen duration for firing alert",
cfg: &config.JiraConfig{
Summary: `{{ template "jira.default.summary" . }}`,
Description: `{{ template "jira.default.description" . }}`,
Summary: config.JiraFieldConfig{Template: `{{ template "jira.default.summary" . }}`},
Description: config.JiraFieldConfig{Template: `{{ template "jira.default.description" . }}`},
Project: `{{ .CommonLabels.project }}`,
ReopenDuration: model.Duration(60 * time.Minute),
ReopenTransition: "REOPEN",
@@ -133,8 +141,8 @@ func TestSearchExistingIssue(t *testing.T) {
{
title: "search existing issue for resolved alert",
cfg: &config.JiraConfig{
Summary: `{{ template "jira.default.summary" . }}`,
Description: `{{ template "jira.default.description" . }}`,
Summary: config.JiraFieldConfig{Template: `{{ template "jira.default.summary" . }}`},
Description: config.JiraFieldConfig{Template: `{{ template "jira.default.description" . }}`},
Project: `{{ .CommonLabels.project }}`,
},
groupKey: "1",
@@ -347,8 +355,8 @@ func TestJiraTemplating(t *testing.T) {
{
title: "full-blown message with templated custom field",
cfg: &config.JiraConfig{
Summary: `{{ template "jira.default.summary" . }}`,
Description: `{{ template "jira.default.description" . }}`,
Summary: config.JiraFieldConfig{Template: `{{ template "jira.default.summary" . }}`},
Description: config.JiraFieldConfig{Template: `{{ template "jira.default.description" . }}`},
Fields: map[string]any{
"customfield_14400": `{{ template "jira.host" . }}`,
},
@@ -361,8 +369,8 @@ func TestJiraTemplating(t *testing.T) {
title: "template project",
cfg: &config.JiraConfig{
Project: `{{ .CommonLabels.lbl1 }}`,
Summary: `{{ template "jira.default.summary" . }}`,
Description: `{{ template "jira.default.description" . }}`,
Summary: config.JiraFieldConfig{Template: `{{ template "jira.default.summary" . }}`},
Description: config.JiraFieldConfig{Template: `{{ template "jira.default.description" . }}`},
},
retry: false,
},
@@ -370,31 +378,31 @@ func TestJiraTemplating(t *testing.T) {
title: "template issue type",
cfg: &config.JiraConfig{
IssueType: `{{ .CommonLabels.lbl1 }}`,
Summary: `{{ template "jira.default.summary" . }}`,
Description: `{{ template "jira.default.description" . }}`,
Summary: config.JiraFieldConfig{Template: `{{ template "jira.default.summary" . }}`},
Description: config.JiraFieldConfig{Template: `{{ template "jira.default.description" . }}`},
},
retry: false,
},
{
title: "summary with templating errors",
cfg: &config.JiraConfig{
Summary: "{{ ",
Summary: config.JiraFieldConfig{Template: "{{ "},
},
errMsg: "template: :1: unclosed action",
},
{
title: "description with templating errors",
cfg: &config.JiraConfig{
Summary: `{{ template "jira.default.summary" . }}`,
Description: "{{ ",
Summary: config.JiraFieldConfig{Template: `{{ template "jira.default.summary" . }}`},
Description: config.JiraFieldConfig{Template: "{{ "},
},
errMsg: "template: :1: unclosed action",
},
{
title: "priority with templating errors",
cfg: &config.JiraConfig{
Summary: `{{ template "jira.default.summary" . }}`,
Description: `{{ template "jira.default.description" . }}`,
Summary: config.JiraFieldConfig{Template: `{{ template "jira.default.summary" . }}`},
Description: config.JiraFieldConfig{Template: `{{ template "jira.default.description" . }}`},
Priority: "{{ ",
},
errMsg: "template: :1: unclosed action",
@@ -467,8 +475,8 @@ func TestJiraNotify(t *testing.T) {
{
title: "create new issue",
cfg: &config.JiraConfig{
Summary: `{{ template "jira.default.summary" . }}`,
Description: `{{ template "jira.default.description" . }}`,
Summary: config.JiraFieldConfig{Template: `{{ template "jira.default.summary" . }}`},
Description: config.JiraFieldConfig{Template: `{{ template "jira.default.description" . }}`},
IssueType: "Incident",
Project: "OPS",
Priority: `{{ template "jira.default.priority" . }}`,
@@ -495,8 +503,8 @@ func TestJiraNotify(t *testing.T) {
issue: issue{
Key: "",
Fields: &issueFields{
Summary: "[FIRING:1] test (vm1 critical)",
Description: "\n\n# Alerts Firing:\n\nLabels:\n - alertname = test\n - instance = vm1\n - severity = critical\n\nAnnotations:\n\nSource: \n\n\n\n\n",
Summary: stringPtr("[FIRING:1] test (vm1 critical)"),
Description: stringPtr("\n\n# Alerts Firing:\n\nLabels:\n - alertname = test\n - instance = vm1\n - severity = critical\n\nAnnotations:\n\nSource: \n\n\n\n\n"),
Issuetype: &idNameValue{Name: "Incident"},
Labels: []string{"ALERT{6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b}", "alertmanager", "test"},
Project: &issueProject{Key: "OPS"},
@@ -506,11 +514,82 @@ func TestJiraNotify(t *testing.T) {
customFieldAssetFn: func(t *testing.T, issue map[string]any) {},
errMsg: "",
},
{
title: "update existing issue with disabled summary and description",
cfg: &config.JiraConfig{
Summary: config.JiraFieldConfig{
Template: `{{ template "jira.default.summary" . }}`,
EnableUpdate: boolPtr(false),
},
Description: config.JiraFieldConfig{
Template: `{{ template "jira.default.description" . }}`,
EnableUpdate: boolPtr(false),
},
IssueType: "{{ .CommonLabels.issue_type }}",
Project: "{{ .CommonLabels.project }}",
Priority: `{{ template "jira.default.priority" . }}`,
Labels: []string{"alertmanager", "{{ .GroupLabels.alertname }}"},
ReopenDuration: model.Duration(1 * time.Hour),
ReopenTransition: "REOPEN",
ResolveTransition: "CLOSE",
WontFixResolution: "WONTFIX",
},
alert: &types.Alert{
Alert: model.Alert{
Labels: model.LabelSet{
"alertname": "test",
"instance": "vm1",
"severity": "critical",
"project": "MONITORING",
"issue_type": "MINOR",
},
StartsAt: time.Now(),
EndsAt: time.Now().Add(time.Hour),
},
},
searchResponse: issueSearchResult{
Issues: []issue{
{
Key: "MONITORING-1",
Fields: &issueFields{
Summary: stringPtr("Original Summary"),
Description: stringPtr("Original Description"),
Status: &issueStatus{
Name: "Open",
StatusCategory: struct {
Key string `json:"key"`
}{
Key: "open",
},
},
},
},
},
},
issue: issue{
Key: "MONITORING-1",
Fields: &issueFields{
// Summary and Description should NOT be present in the update request
Issuetype: &idNameValue{Name: "MINOR"},
Labels: []string{"ALERT{6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b}", "alertmanager", "test"},
Project: &issueProject{Key: "MONITORING"},
Priority: &idNameValue{Name: "High"},
},
},
customFieldAssetFn: func(t *testing.T, issue map[string]any) {
// Verify that summary and description are NOT in the update request
_, hasSummary := issue["summary"]
_, hasDescription := issue["description"]
require.False(t, hasSummary, "summary should not be present in update request")
require.False(t, hasDescription, "description should not be present in update request")
},
errMsg: "",
},
{
title: "create new issue with template project and issue type",
cfg: &config.JiraConfig{
Summary: `{{ template "jira.default.summary" . }}`,
Description: `{{ template "jira.default.description" . }}`,
Summary: config.JiraFieldConfig{Template: `{{ template "jira.default.summary" . }}`},
Description: config.JiraFieldConfig{Template: `{{ template "jira.default.description" . }}`},
IssueType: "{{ .CommonLabels.issue_type }}",
Project: "{{ .CommonLabels.project }}",
Priority: `{{ template "jira.default.priority" . }}`,
@@ -539,8 +618,8 @@ func TestJiraNotify(t *testing.T) {
issue: issue{
Key: "",
Fields: &issueFields{
Summary: "[FIRING:1] test (vm1 MINOR MONITORING critical)",
Description: "\n\n# Alerts Firing:\n\nLabels:\n - alertname = test\n - instance = vm1\n - issue_type = MINOR\n - project = MONITORING\n - severity = critical\n\nAnnotations:\n\nSource: \n\n\n\n\n",
Summary: stringPtr("[FIRING:1] test (vm1 MINOR MONITORING critical)"),
Description: stringPtr("\n\n# Alerts Firing:\n\nLabels:\n - alertname = test\n - instance = vm1\n - issue_type = MINOR\n - project = MONITORING\n - severity = critical\n\nAnnotations:\n\nSource: \n\n\n\n\n"),
Issuetype: &idNameValue{Name: "MINOR"},
Labels: []string{"ALERT{6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b}", "alertmanager", "test"},
Project: &issueProject{Key: "MONITORING"},
@@ -553,8 +632,8 @@ func TestJiraNotify(t *testing.T) {
{
title: "create new issue with custom field and too long summary",
cfg: &config.JiraConfig{
Summary: strings.Repeat("A", maxSummaryLenRunes+10),
Description: `{{ template "jira.default.description" . }}`,
Summary: config.JiraFieldConfig{Template: strings.Repeat("A", maxSummaryLenRunes+10)},
Description: config.JiraFieldConfig{Template: `{{ template "jira.default.description" . }}`},
IssueType: "Incident",
Project: "OPS",
Priority: `{{ template "jira.default.priority" . }}`,
@@ -591,8 +670,8 @@ func TestJiraNotify(t *testing.T) {
issue: issue{
Key: "",
Fields: &issueFields{
Summary: strings.Repeat("A", maxSummaryLenRunes-1) + "…",
Description: "\n\n# Alerts Firing:\n\nLabels:\n - alertname = test\n - instance = vm1\n\nAnnotations:\n\nSource: \n\n\n\n\n",
Summary: stringPtr(strings.Repeat("A", maxSummaryLenRunes-1) + "…"),
Description: stringPtr("\n\n# Alerts Firing:\n\nLabels:\n - alertname = test\n - instance = vm1\n\nAnnotations:\n\nSource: \n\n\n\n\n"),
Issuetype: &idNameValue{Name: "Incident"},
Labels: []string{"ALERT{6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b}", "alertmanager", "test"},
Project: &issueProject{Key: "OPS"},
@@ -613,8 +692,8 @@ func TestJiraNotify(t *testing.T) {
{
title: "reopen issue",
cfg: &config.JiraConfig{
Summary: `{{ template "jira.default.summary" . }}`,
Description: `{{ template "jira.default.description" . }}`,
Summary: config.JiraFieldConfig{Template: `{{ template "jira.default.summary" . }}`},
Description: config.JiraFieldConfig{Template: `{{ template "jira.default.description" . }}`},
IssueType: "Incident",
Project: "OPS",
Priority: `{{ template "jira.default.priority" . }}`,
@@ -654,8 +733,8 @@ func TestJiraNotify(t *testing.T) {
issue: issue{
Key: "",
Fields: &issueFields{
Summary: "[FIRING:1] test (vm1)",
Description: "\n\n# Alerts Firing:\n\nLabels:\n - alertname = test\n - instance = vm1\n\nAnnotations:\n\nSource: \n\n\n\n\n",
Summary: stringPtr("[FIRING:1] test (vm1)"),
Description: stringPtr("\n\n# Alerts Firing:\n\nLabels:\n - alertname = test\n - instance = vm1\n\nAnnotations:\n\nSource: \n\n\n\n\n"),
Issuetype: &idNameValue{Name: "Incident"},
Labels: []string{"ALERT{6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b}", "alertmanager", "test"},
Project: &issueProject{Key: "OPS"},
@@ -668,8 +747,8 @@ func TestJiraNotify(t *testing.T) {
{
title: "error resolve transition not found",
cfg: &config.JiraConfig{
Summary: `{{ template "jira.default.summary" . }}`,
Description: `{{ template "jira.default.description" . }}`,
Summary: config.JiraFieldConfig{Template: `{{ template "jira.default.summary" . }}`},
Description: config.JiraFieldConfig{Template: `{{ template "jira.default.description" . }}`},
IssueType: "Incident",
Project: "OPS",
Priority: `{{ template "jira.default.priority" . }}`,
@@ -709,8 +788,8 @@ func TestJiraNotify(t *testing.T) {
issue: issue{
Key: "",
Fields: &issueFields{
Summary: "[RESOLVED] test (vm1)",
Description: "\n\n\n# Alerts Resolved:\n\nLabels:\n - alertname = test\n - instance = vm1\n\nAnnotations:\n\nSource: \n\n\n\n",
Summary: stringPtr("[RESOLVED] test (vm1)"),
Description: stringPtr("\n\n\n# Alerts Resolved:\n\nLabels:\n - alertname = test\n - instance = vm1\n\nAnnotations:\n\nSource: \n\n\n\n"),
Issuetype: &idNameValue{Name: "Incident"},
Labels: []string{"ALERT{6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b}", "alertmanager", "test"},
Project: &issueProject{Key: "OPS"},
@@ -722,8 +801,8 @@ func TestJiraNotify(t *testing.T) {
{
title: "error reopen transition not found",
cfg: &config.JiraConfig{
Summary: `{{ template "jira.default.summary" . }}`,
Description: `{{ template "jira.default.description" . }}`,
Summary: config.JiraFieldConfig{Template: `{{ template "jira.default.summary" . }}`},
Description: config.JiraFieldConfig{Template: `{{ template "jira.default.description" . }}`},
IssueType: "Incident",
Project: "OPS",
Priority: `{{ template "jira.default.priority" . }}`,
@@ -763,8 +842,8 @@ func TestJiraNotify(t *testing.T) {
issue: issue{
Key: "",
Fields: &issueFields{
Summary: "[FIRING:1] test (vm1)",
Description: "\n\n# Alerts Firing:\n\nLabels:\n - alertname = test\n - instance = vm1\n\nAnnotations:\n\nSource: \n\n\n\n\n",
Summary: stringPtr("[FIRING:1] test (vm1)"),
Description: stringPtr("\n\n# Alerts Firing:\n\nLabels:\n - alertname = test\n - instance = vm1\n\nAnnotations:\n\nSource: \n\n\n\n\n"),
Issuetype: &idNameValue{Name: "Incident"},
Labels: []string{"ALERT{6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b}", "alertmanager", "test"},
Project: &issueProject{Key: "OPS"},
@@ -861,10 +940,28 @@ func TestJiraNotify(t *testing.T) {
t.Fatalf("unexpected method %s", r.Method)
}
return
case "/issue/MONITORING-1":
body, err := io.ReadAll(r.Body)
if err != nil {
panic(err)
}
var raw map[string]any
if err := json.Unmarshal(body, &raw); err != nil {
panic(err)
}
if fields, ok := raw["fields"].(map[string]any); ok {
tc.customFieldAssetFn(t, fields)
}
w.WriteHeader(http.StatusNoContent)
return
case "/issue/OPS-1":
case "/issue/OPS-2":
case "/issue/OPS-3":
case "/issue/OPS-4":
fallthrough
case "/issue":
body, err := io.ReadAll(r.Body)

View File

@@ -25,13 +25,13 @@ type issue struct {
}
type issueFields struct {
Description any `json:"description"`
Description *string `json:"description,omitempty"`
Issuetype *idNameValue `json:"issuetype,omitempty"`
Labels []string `json:"labels,omitempty"`
Priority *idNameValue `json:"priority,omitempty"`
Project *issueProject `json:"project,omitempty"`
Resolution *idNameValue `json:"resolution,omitempty"`
Summary string `json:"summary"`
Summary *string `json:"summary,omitempty"`
Status *issueStatus `json:"status,omitempty"`
Fields map[string]any `json:"-"`
@@ -69,11 +69,15 @@ type issueTransitions struct {
// MarshalJSON merges the struct issueFields and issueFields.CustomField together.
func (i issueFields) MarshalJSON() ([]byte, error) {
jsonFields := map[string]any{
"description": i.Description,
"summary": i.Summary,
jsonFields := map[string]any{}
if i.Summary != nil {
jsonFields["summary"] = *i.Summary
}
if i.Description != nil {
jsonFields["description"] = *i.Description
}
if i.Issuetype != nil {
jsonFields["issuetype"] = i.Issuetype
}