Skip to content

Comments

feat: custom fields in view, field-options command, transition --field#29

Merged
Hinne1 merged 2 commits intomainfrom
feature/NX-15169-custom-fields-transitions
Feb 18, 2026
Merged

feat: custom fields in view, field-options command, transition --field#29
Hinne1 merged 2 commits intomainfrom
feature/NX-15169-custom-fields-transitions

Conversation

@Hinne1
Copy link
Contributor

@Hinne1 Hinne1 commented Feb 18, 2026

Summary

  • Custom fields in atl issue view: IssueFields.UnmarshalJSON now captures customfield_* keys into Extra map. View output shows custom fields in both JSON (custom_fields section) and plain text.
  • atl issue field-options command: Discovers allowed values for select/radio fields via the createmeta API. Supports --project, --type, --field filter.
  • atl issue transition --field: Pass required fields when transitioning (e.g. --field "Resolution=Fixed").
  • Shared field_util.go: Extracts ParseCustomField + coerceFieldValue helpers with type-aware coercion (select, radio, multiselect, textarea, labels, numbers). Refactored edit.go and create.go to use shared helpers.

Context

While resolving NX-15169 (Sentry bug), transitioning the bug to "Bereit für Überprüfung" required setting custom fields (Repo, Application, Ursprung, Fehlverhalten) — but the CLI had no way to discover valid values or pass fields during transitions.

Test plan

  • make check passes (tests + lint, 0 issues)
  • atl issue view NX-15169 --json shows custom_fields section
  • atl issue field-options --project NX --type Bug lists fields with allowed values
  • atl issue transition with --field flag sets fields during transition

- Show custom fields in `atl issue view` (JSON and plain text)
- Add `atl issue field-options` to discover allowed values for select/radio fields
- Add `--field` flag to `atl issue transition` for transitions requiring fields
- Extract shared field parsing utilities (ParseCustomField, coerceFieldValue)
- Refactor edit and create commands to use shared helpers
@gemini-code-assist
Copy link

Summary of Changes

Hello @Hinne1, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the Jira CLI's capabilities for interacting with custom fields. It empowers users to view comprehensive custom field data within issue details, efficiently discover valid options for various field types, and seamlessly provide necessary custom field values during issue transitions. These improvements streamline workflows that involve complex Jira configurations and broaden the CLI's utility for managing diverse issue types.

Highlights

  • Custom Fields in Issue View: The atl issue view command now displays custom fields in both plain text and JSON output. Custom field data is captured into an Extra map within the IssueFields struct and formatted for readability.
  • New atl issue field-options Command: A new command has been introduced to discover allowed values for select, radio, and other constrained fields. It queries the Jira createmeta API and supports filtering by project, issue type, and specific field names.
  • Transition with Custom Fields: The atl issue transition command now accepts a --field flag, allowing users to provide required custom field values when performing an issue transition.
  • Shared Field Utility: A new field_util.go module centralizes logic for parsing custom field key-value pairs, resolving field names to IDs, and coercing values to appropriate types (e.g., select, radio, multiselect, textarea, labels, numbers) for the Jira API.
Changelog
  • AGENTS.md
    • Updated atl issue view examples to reflect custom field display.
    • Added an example for atl issue transition with the new --field flag.
    • Included new examples for the atl issue field-options command.
  • internal/api/jira.go
    • Modified IssueFields struct to include an Extra map for raw custom field JSON.
    • Implemented a custom UnmarshalJSON method for IssueFields to correctly parse standard and custom fields.
    • Added FormatCustomFieldValue to convert raw JSON custom field values into human-readable strings.
    • Introduced FieldMeta and FieldMetaResponse structs to represent field metadata from the Jira API.
    • Implemented GetFieldOptions method to fetch field metadata, including allowed values, for a given project and issue type.
    • Updated TransitionRequest to include a Fields map for passing custom fields during transitions.
    • Modified TransitionIssue to accept and send custom fields with the transition request.
  • internal/cmd/issue/create.go
    • Removed strconv import.
    • Refactored custom field parsing logic to use the new ParseCustomField utility function.
    • Removed the isSystemField helper function, as it was moved to field_util.go.
  • internal/cmd/issue/edit.go
    • Removed strconv import.
    • Refactored custom field parsing and type coercion logic to use the new ParseCustomField utility function.
  • internal/cmd/issue/field_options.go
    • Added a new command file for atl issue field-options.
    • Defined FieldOptionsOptions for command flags (--project, --type, --field, --json).
    • Implemented NewCmdFieldOptions to set up the Cobra command.
    • Defined FieldOptionOutput struct for structured output of field options.
    • Implemented runFieldOptions to fetch, filter, and display field metadata and allowed values.
    • Added extractAllowedValue helper to parse raw JSON allowed values into strings.
  • internal/cmd/issue/field_util.go
    • Added a new utility file for shared field logic.
    • Moved and defined isSystemField function.
    • Implemented ParseCustomField to resolve field names to IDs and coerce string values to appropriate Go types based on field schema.
    • Implemented coerceFieldValue to handle type-aware conversion for select, radio, multiselect, textarea (to ADF), labels, and numbers.
  • internal/cmd/issue/issue.go
    • Registered the new NewCmdFieldOptions command.
  • internal/cmd/issue/transition.go
    • Added CustomFields slice to TransitionOptions to capture --field flag values.
    • Updated command examples to include --field usage.
    • Added --field flag registration.
    • Integrated ParseCustomField to process custom fields provided during a transition.
    • Passed the parsed custom fields to the jira.TransitionIssue API call.
  • internal/cmd/issue/view.go
    • Imported encoding/json and sort packages.
    • Extended IssueOutput struct to include CustomFields (a map of CustomFieldOutput).
    • Defined CustomFieldOutput struct to represent individual custom fields in the output.
    • Modified runView to fetch all fields for resolving custom field IDs to names.
    • Updated formatIssueOutput to accept fieldNames and populate the CustomFields map by formatting raw custom field values.
    • Modified printIssueDetails to display custom fields in a dedicated section for plain text output.
  • internal/cmd/issue/view_test.go
    • Updated test calls for formatIssueOutput to accommodate a new parameter.
Activity
  • No human activity has been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request significantly enhances the CLI by introducing custom field support, including a new field-options command and refactoring field parsing logic into a shared field_util.go. These changes are well-integrated across view, create, edit, and transition commands. A critical security vulnerability was identified in the new GetFieldOptions API method: user-supplied parameters are interpolated into a URL path without proper encoding, which could lead to path traversal. This must be addressed by using url.PathEscape. Additionally, there are a couple of suggestions to improve overall performance and robustness.

Comment on lines 564 to 565
path := fmt.Sprintf("%s/issue/createmeta/%s/issuetypes/%s",
s.client.JiraBaseURL(), projectKey, issueTypeID)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The GetFieldOptions function constructs an API URL by directly interpolating the projectKey and issueTypeID parameters into the path string using fmt.Sprintf. Since these parameters are not URL-encoded, an attacker (or a user providing malicious input) could use path traversal sequences (e.g., ../) to cause the application to make requests to unintended API endpoints within the Jira host. While this is a CLI tool, it is a best practice to always encode path components to prevent unintended behavior, especially if the tool is used in automated environments where input might be untrusted.

Suggested change
path := fmt.Sprintf("%s/issue/createmeta/%s/issuetypes/%s",
s.client.JiraBaseURL(), projectKey, issueTypeID)
path := fmt.Sprintf("%s/issue/createmeta/%s/issuetypes/%s",
s.client.JiraBaseURL(), url.PathEscape(projectKey), url.PathEscape(issueTypeID))

Comment on lines 122 to 136
for _, item := range arr {
var sv struct {
Value string `json:"value"`
}
if err := json.Unmarshal(item, &sv); err == nil && sv.Value != "" {
values = append(values, sv.Value)
continue
}
var s string
if err := json.Unmarshal(item, &s); err == nil {
values = append(values, s)
continue
}
values = append(values, string(item))
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation for handling array items only checks for select-like objects ({ "value": ... }) and plain strings. This is incomplete as an array can contain other complex types that FormatCustomFieldValue can handle, such as user objects ({ "displayName": ... }).

To make this more robust and handle all supported types within an array, you can use a recursive call to FormatCustomFieldValue for each item.

This simplifies the code and correctly formats any nested types that the parent function already knows how to handle.

for _, item := range arr {
				// Recursively format each item to handle various nested types like user pickers, etc.
				formattedItem := FormatCustomFieldValue(item)
				if formattedItem != "" {
					values = append(values, formattedItem)
				}
			}

Comment on lines +28 to +53
func ParseCustomField(ctx context.Context, jira *api.JiraService, raw string) (string, interface{}, error) {
parts := strings.SplitN(raw, "=", 2)
if len(parts) != 2 {
return "", nil, fmt.Errorf("invalid field format: %s (expected key=value)", raw)
}
key, value := parts[0], parts[1]

var resolvedField *api.Field

if strings.HasPrefix(key, "customfield_") {
resolvedField, _ = jira.GetFieldByID(ctx, key)
} else if !isSystemField(key) {
var err error
resolvedField, err = jira.GetFieldByName(ctx, key)
if err != nil {
return "", nil, fmt.Errorf("failed to look up field '%s': %w", key, err)
}
if resolvedField == nil {
return "", nil, fmt.Errorf("field not found: %s\n\nUse 'atl issue fields --search \"%s\"' to find available fields", key, key)
}
key = resolvedField.ID
}

fieldValue := coerceFieldValue(resolvedField, value)
return key, fieldValue, nil
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This function is called in a loop within the create, edit, and transition commands whenever multiple --field flags are used. Inside this function, jira.GetFieldByID or jira.GetFieldByName are called, which in turn call jira.GetFields(). This results in multiple API calls to fetch the same list of all fields, which is inefficient.

To optimize this, you could cache the result of GetFields() within the JiraService instance. The cache would live for the duration of a single command execution, avoiding redundant API calls.

Here's a suggested implementation in internal/api/jira.go:

// In internal/api/jira.go

type JiraService struct {
	client      *Client
	fieldsCache []*Field // Add this cache field
}

func NewJiraService(client *Client) *JiraService {
	return &JiraService{client: client}
}

func (s *JiraService) GetFields(ctx context.Context) ([]*Field, error) {
	if s.fieldsCache != nil {
		return s.fieldsCache, nil
	}

	path := fmt.Sprintf("%s/field", s.client.JiraBaseURL())

	var fields []*Field
	if err := s.client.Get(ctx, path, &fields); err != nil {
		return nil, err
	}

	s.fieldsCache = fields
	return fields, nil
}

With this change, subsequent calls to GetFieldByID and GetFieldByName within the same command execution will use the cached data instead of making new API requests.

…ng, field caching

- URL-encode projectKey and issueTypeID in GetFieldOptions path
- Use recursive FormatCustomFieldValue for array items (handles user
  objects, nested types)
- Cache GetFields() result on JiraService for the command lifetime
@github-actions
Copy link

Merging this branch will decrease overall coverage

Impacted Packages Coverage Δ 🤖
github.com/enthus-appdev/atl-cli/internal/api 35.58% (-1.01%) 👎
github.com/enthus-appdev/atl-cli/internal/cmd/issue 6.82% (-0.57%) 👎

Coverage by file

Changed files (no unit tests)

Changed File Coverage Δ Total Covered Missed 🤖
github.com/enthus-appdev/atl-cli/internal/api/jira.go 11.03% (+0.94%) 408 (+61) 45 (+10) 363 (+51) 👍
github.com/enthus-appdev/atl-cli/internal/cmd/issue/create.go 0.00% (ø) 98 (-12) 0 98 (-12)
github.com/enthus-appdev/atl-cli/internal/cmd/issue/edit.go 0.00% (ø) 107 (-20) 0 107 (-20)
github.com/enthus-appdev/atl-cli/internal/cmd/issue/field_options.go 0.00% (ø) 83 (+83) 0 83 (+83)
github.com/enthus-appdev/atl-cli/internal/cmd/issue/field_util.go 0.00% (ø) 39 (+39) 0 39 (+39)
github.com/enthus-appdev/atl-cli/internal/cmd/issue/issue.go 0.00% (ø) 19 (+1) 0 19 (+1)
github.com/enthus-appdev/atl-cli/internal/cmd/issue/transition.go 0.00% (ø) 77 (+9) 0 77 (+9)
github.com/enthus-appdev/atl-cli/internal/cmd/issue/view.go 56.44% (-17.89%) 101 (+27) 57 (+2) 44 (+25) 💀

Please note that the "Total", "Covered", and "Missed" counts above refer to code statements instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code.

Changed unit test files

  • github.com/enthus-appdev/atl-cli/internal/cmd/issue/view_test.go

@Hinne1 Hinne1 merged commit 3cdd1ea into main Feb 18, 2026
11 checks passed
@Hinne1 Hinne1 deleted the feature/NX-15169-custom-fields-transitions branch February 18, 2026 03:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant