feat: added new text/template syntax to jira custom fields#6464
feat: added new text/template syntax to jira custom fields#6464
Conversation
WalkthroughAdds templating support for Jira custom fields in CreateNewIssue by introducing a TemplateContext, template evaluation helpers (including legacy $variable handling), refactors custom field processing to use evaluated values, and adds unit tests covering template, legacy, and plain-text scenarios. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Integration
participant TemplateEngine as Template Eval
participant Logger
participant JiraAPI as Jira API
Integration->>Integration: CreateNewIssue(event)
Integration->>Integration: buildTemplateContext(event)
loop for each custom field value (id/name/freeform)
Integration->>TemplateEngine: evaluateCustomFieldValue(value, ctx, event)
alt contains Go template markers
TemplateEngine-->>Integration: rendered value or error
else contains legacy $variable
TemplateEngine-->>Integration: substituted value
else
TemplateEngine-->>Integration: original value
end
opt evaluation error
Integration->>Logger: warn and use original value
end
end
Integration->>JiraAPI: create issue with evaluated fields
JiraAPI-->>Integration: response
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Pre-merge checks (2 passed, 1 warning)❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
Poem
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. ✨ Finishing Touches
🧪 Generate unit tests
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (5)
pkg/reporting/trackers/jira/jira.go (4)
30-40: TemplateContext shape looks good; consider exposing raw slices too.Keeping CVEID/CWEID as joined strings is convenient, but also exposing them as []string (alongside the joined string) would give templates more flexibility (e.g., range over IDs).
53-58: Minor cleanup: avoid repeated ptr.Safe calls.Store Classification once and reuse; fewer calls and clearer intent.
Apply:
- if event.Info.Classification != nil { - ctx.CVSSScore = fmt.Sprintf("%.2f", ptr.Safe(event.Info.Classification).CVSSScore) - ctx.CVEID = strings.Join(ptr.Safe(event.Info.Classification).CVEID.ToSlice(), ", ") - ctx.CWEID = strings.Join(ptr.Safe(event.Info.Classification).CWEID.ToSlice(), ", ") - ctx.CVSSMetrics = ptr.Safe(event.Info.Classification).CVSSMetrics - } + if c := event.Info.Classification; c != nil { + ctx.CVSSScore = fmt.Sprintf("%.2f", c.CVSSScore) + ctx.CVEID = strings.Join(c.CVEID.ToSlice(), ", ") + ctx.CWEID = strings.Join(c.CWEID.ToSlice(), ", ") + ctx.CVSSMetrics = c.CVSSMetrics + }
70-78: Prevent "" leakage; set missingkey=zero (and optionally add helpers).Without a template Option, missing fields render as "". Prefer empty strings.
Apply:
- tmpl, err := template.New("field").Parse(templateStr) + tmpl, err := template.New("field"). + Option("missingkey=zero"). + Parse(templateStr)Optionally, consider a FuncMap (e.g.,
default,upper,join) if users need richer templating later.
90-123: Legacy $variable handling matches only whole-string tokens; confirm no mixed-token usage in existing configs.Today
$Varworks only when the whole value equals$Var. If users previously embedded$Varinside larger strings, this will regress.If you want to be more forgiving and case-insensitive, plus add
$Tags:- if strings.HasPrefix(value, "$") { - variableName := strings.TrimPrefix(value, "$") - switch variableName { + if strings.HasPrefix(value, "$") { + variableName := strings.ToLower(strings.TrimPrefix(value, "$")) + switch variableName { case "cvssmetrics": ... - case "CVEID": + case "cveid": ... - case "CWEID": + case "cweid": ... - case "CVSSScore": + case "cvssscore": ... - case "Host": + case "host": return event.Host, nil - case "Severity": + case "severity": return event.Info.SeverityHolder.Severity.String(), nil - case "Name": + case "name": return event.Info.Name, nil + case "tags": + return strings.Join(templateCtx.Tags, ", "), nil default: return value, nil // return as-is if variable not found } }If mixed-token legacy usage existed (e.g.,
"Host: $Host"), consider supporting a light$Varinterpolation pass before/after template evaluation.pkg/reporting/trackers/jira/jira_test.go (1)
75-133: Great coverage; add a couple of edge-case tests to nail regressions.
- Parse/execute error should fall back to original string.
- Legacy variables for CVE/CWE/CVSS when Classification is nil should resolve to empty string.
Apply:
t.Run("no template syntax", func(t *testing.T) { result, err := integration.evaluateCustomFieldValue("plain text", buildTemplateContext(event), event) require.NoError(t, err) require.Equal(t, "plain text", result) }) + + t.Run("template parse error falls back", func(t *testing.T) { + templateStr := "{{ if .Severity }}" // missing end + result, err := integration.evaluateCustomFieldValue(templateStr, buildTemplateContext(event), event) + require.Error(t, err) + require.Equal(t, templateStr, result) + }) + + t.Run("legacy vars with nil Classification resolve to empty", func(t *testing.T) { + eventNoClass := &output.ResultEvent{ + Host: "ex", + Info: model.Info{ + Name: "NoClass", + SeverityHolder: severity.Holder{Severity: severity.Critical}, + }, + } + for _, v := range []string{"$CVSSMetrics", "$CVEID", "$CWEID", "$CVSSScore"} { + res, err := integration.evaluateCustomFieldValue(v, buildTemplateContext(eventNoClass), eventNoClass) + require.NoError(t, err) + require.Equal(t, "", res) + } + })
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
pkg/reporting/trackers/jira/jira.go(3 hunks)pkg/reporting/trackers/jira/jira_test.go(2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.go: Format Go code using go fmt
Run static analysis with go vet
Files:
pkg/reporting/trackers/jira/jira.gopkg/reporting/trackers/jira/jira_test.go
🧬 Code graph analysis (1)
pkg/reporting/trackers/jira/jira_test.go (4)
pkg/output/output.go (1)
ResultEvent(146-221)pkg/model/types/severity/severity.go (3)
Holder(70-72)Severity(11-11)Critical(26-26)pkg/model/model.go (1)
Classification(121-157)pkg/model/types/stringslice/stringslice.go (1)
StringSlice(29-31)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Tests (ubuntu-latest)
- GitHub Check: Tests (windows-latest)
- GitHub Check: Tests (macOS-latest)
🔇 Additional comments (1)
pkg/reporting/trackers/jira/jira_test.go (1)
9-9: Import looks correct for building CVE/CWE slices.
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
pkg/reporting/trackers/jira/jira.go (1)
3-26: Fix gofmt formatting and resolve go vet errors
- Run
gofmt -s -won pkg/fuzz/component/path.go, pkg/fuzz/component/path_test.go, and pkg/testutils/fuzzplayground/sqli_test.go to eliminate formatting diffs.- In pkg/output/stats/waf/waf.go at line 27, ensure
regexes.jsonexists at the referenced path or update the loader to point to the correct file.
♻️ Duplicate comments (1)
pkg/reporting/trackers/jira/jira.go (1)
276-305: CustomFields handling can drop values, reject valid non-strings, and overwrite siblings.
- Non-string nested values (e.g., numeric IDs) cause an error and abort issue creation.
- Multiple nested keys under the same field overwrite each other (last write wins).
- Top-level non-map values are ignored.
- "name" should consistently map to "value".
Refactor to accumulate nested keys, evaluate only strings, and pass through other types. Also support top-level string/non-string values.
- // Process custom fields with template evaluation support - customFields := tcontainer.NewMarshalMap() - for name, value := range i.options.CustomFields { - if valueMap, ok := value.(map[interface{}]interface{}); ok { - // Iterate over nested map - for nestedName, nestedValue := range valueMap { - fmtNestedValue, ok := nestedValue.(string) - if !ok { - return nil, fmt.Errorf(`couldn't iterate on nested item "%s": %s`, nestedName, nestedValue) - } - - // Evaluate template or handle legacy $variable syntax - evaluatedValue, err := i.evaluateCustomFieldValue(fmtNestedValue, templateCtx, event) - if err != nil { - gologger.Warning().Msgf("Failed to evaluate template for field %s.%s: %v", name, nestedName, err) - evaluatedValue = fmtNestedValue // fallback to original value - } - - switch nestedName { - case "id": - customFields[name] = map[string]interface{}{"id": evaluatedValue} - case "name": - customFields[name] = map[string]interface{}{"value": evaluatedValue} - case "freeform": - customFields[name] = evaluatedValue - } - } - } - } + // Process custom fields with template evaluation support + customFields := tcontainer.NewMarshalMap() + for name, value := range i.options.CustomFields { + switch v := value.(type) { + case map[interface{}]interface{}: + fieldPayload := map[string]interface{}{} + var freeformSet bool + for nestedName, nestedValue := range v { + nestedKey := fmt.Sprint(nestedName) + var out interface{} + if s, ok := nestedValue.(string); ok { + ev, err := i.evaluateCustomFieldValue(s, templateCtx, event) + if err != nil { + gologger.Warning().Msgf("Failed to evaluate template for field %s.%s: %v", name, nestedKey, err) + out = s + } else { + out = ev + } + } else { + // Pass-through non-strings unchanged (bool, int, arrays, etc.) + out = nestedValue + } + switch nestedKey { + case "id": + fieldPayload["id"] = out + case "name", "value": + fieldPayload["value"] = out + case "freeform": + customFields[name] = out + freeformSet = true + default: + fieldPayload[nestedKey] = out + } + } + if !freeformSet && len(fieldPayload) > 0 { + customFields[name] = fieldPayload + } + case string: + ev, err := i.evaluateCustomFieldValue(v, templateCtx, event) + if err != nil { + gologger.Warning().Msgf("Failed to evaluate template for field %s: %v", name, err) + customFields[name] = v + } else { + customFields[name] = ev + } + default: + // Pass-through other types (bool, int, []any, etc.) + customFields[name] = v + } + }Add follow-up tests to cover:
- top-level string template
- top-level numeric/bool passthrough
- nested map with both id and value (no overwrite)
- nested numeric id accepted
🧹 Nitpick comments (3)
pkg/reporting/trackers/jira/jira.go (2)
32-42: Consider keeping TemplateContext unexported.Unless this needs to be part of the public API, make it package-private to avoid surface-area bloat.
-type TemplateContext struct { +type templateContext struct {(Propagate the rename within this file and tests in the same package.)
65-99: Harden template execution for missing keys.Avoid surprises when a template references absent fields.
- tmpl, err := template.New("field").Funcs(funcMap).Parse(templateStr) + tmpl, err := template.New("field"). + Option("missingkey=zero"). + Funcs(funcMap). + Parse(templateStr)Optionally add simple helpers like default/coalesce later if needed.
pkg/reporting/trackers/jira/jira_test.go (1)
75-190: Great coverage of templating paths; add minor assertions and CF tests.
- Nice coverage of conditionals, functions, and legacy variables.
- Nit: extra msg arg in one Contains; drop it.
- Please add tests for CustomFields map handling (top-level values, nested id+value merge, numeric id pass-through).
- require.Contains(t, result, "TEST VULNERABILITY on example.com", result) + require.Contains(t, result, "TEST VULNERABILITY on example.com")Additional tests to add (example):
func TestCustomFieldsProcessing(t *testing.T) { event := /* build minimal event like above */ integration := &Integration{ options: &Options{ CustomFields: map[string]interface{}{ "customfield_10010": map[interface{}]interface{}{"id": 111, "name": "Critical"}, "customfield_10011": "{{.Host}}", "customfield_10012": true, }, }, } ctx := buildTemplateContext(event) cf := tcontainer.NewMarshalMap() // simulate the refactored loop or call CreateNewIssue with a mocked jira client _ = ctx; _ = cf; _ = integration // implement as per refactor }If helpful, I can wire these as table-driven subtests once the CF loop is updated.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
pkg/reporting/trackers/jira/jira.go(3 hunks)pkg/reporting/trackers/jira/jira_test.go(2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.go: Format Go code using go fmt
Run static analysis with go vet
Files:
pkg/reporting/trackers/jira/jira.gopkg/reporting/trackers/jira/jira_test.go
🧬 Code graph analysis (2)
pkg/reporting/trackers/jira/jira.go (2)
pkg/output/output.go (1)
ResultEvent(146-221)pkg/model/model.go (1)
Classification(121-157)
pkg/reporting/trackers/jira/jira_test.go (5)
pkg/output/output.go (1)
ResultEvent(146-221)pkg/model/types/severity/severity.go (3)
Holder(70-72)Severity(11-11)Critical(26-26)pkg/model/model.go (1)
Classification(121-157)pkg/model/types/stringslice/stringslice.go (1)
StringSlice(29-31)pkg/reporting/trackers/jira/jira.go (1)
Integration(170-177)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Lint
🔇 Additional comments (4)
pkg/reporting/trackers/jira/jira.go (3)
4-4: Imports look good.New deps are appropriate for templating and case transforms.
Also applies to: 11-11, 16-18
44-63: LGTM: context builder is correct and null-safe.Using ptr.Safe behind a non-nil guard is fine; fields map cleanly to strings.
100-144: Legacy $var only matches whole-string tokens. Verify compatibility.If previous configs used $Var inside larger strings (e.g., "Priority: $Severity"), this will no longer substitute.
Would you like me to extend legacy handling to replace $Vars anywhere in the string while keeping {{ }} precedence?
pkg/reporting/trackers/jira/jira_test.go (1)
9-9: LGTM: stringslice import for CVE/CWE setup.
|
@coderabbitai resolve |
🧩 Analysis chain@Mzack9999 I'll resolve all review comments and create an issue for this PR. 🏁 Script executed: Length of output: 54 @Mzack9999 I've resolved all review comments and created the follow-up issue as requested. ✅ Issue Created: #6473 The issue tracks follow-up work for PR #6464 and has been assigned to you. It includes details about the text/template syntax changes for Jira custom fields and potential areas for additional work like documentation updates and extended test coverage. ✅ Actions performedComments resolved. |
Proposed changes
Added text/template syntax engine to Jira custom fields for complex templates
Checklist
Summary by CodeRabbit
New Features
Tests