feat(jtk): INT-693 clear fields with an empty value; none/null clear numbers - #470
feat(jtk): INT-693 clear fields with an empty value; none/null clear numbers#470piekstra wants to merge 2 commits into
Conversation
…numbers An empty value now clears (JSON null) every structured field type - number, option, array, priority-like, issuelink, parent - and none/null additionally clear number fields, matching the existing user-field behavior. Previously FormatFieldValue only honored null-intent for user fields; a number field fell through to ParseFloat and sent the raw string, so Jira rejected every attempt to unset Story Points with 'Specify a number for the custom field', and there was no way to unset a custom field from the CLI at all. Option-like fields deliberately do not treat the string None as a clear: a select list may legitimately contain an option named None. Free-text fields keep the verbatim value - empty string is the idiomatic text clear. [INT-693]
monit-reviewer
left a comment
There was a problem hiding this comment.
Automated PR Review
Reviewed commit: 460576c16995
Profile: claude-monit-reviewer - Posting as: monit-reviewer
Summary
| Reviewer | Findings |
|---|---|
| go:implementation-tests | 1 |
| policies:conventions | 0 |
go:implementation-tests (1 finding)
Minor - tools/jtk/api/fields.go:191
The new
issuelinkclear-on-empty branch (if trimmed == "" { return nil }) is untested —TestFormatFieldValue_Clearingcovers option, array, number, priority, parent, and default string, but has no case withSchema.Type: "issuelink". This is genuinely new behavior added by this diff (the pre-existing issuelink tests only cover the non-empty id/key-wrapping path), so a regression here (e.g. someone reordering the trim/empty check relative to the Atoi branch) would ship silently. Add a case to the table, e.g.{name: "issuelink clears on empty", field: &Field{ID: "customfield_X", Schema: FieldSchema{Type: "issuelink"}}, value: "", want: nil}.
Reviewer Coverage
go:implementation-tests— complete (broad); skipped: none; constraints: nonepolicies:conventions— complete (broad); inspected 2 assigned files (3 inspected across reviewers):tools/jtk/api/fields.go,tools/jtk/internal/cmd/issues/update.go; skipped: none; constraints: none
Inspected files (3)
tools/jtk/api/fields.gotools/jtk/api/fields_test.gotools/jtk/internal/cmd/issues/update.go
0 PR discussion threads considered. 0 summarized; 0 resolved.
Completed in 3m 01s | ~$1.97 (est.) | claude-sonnet-5 | cr 0.10.282
| Field | Value |
|---|---|
| Model | claude-sonnet-5 |
| Reviewers | go:implementation-tests, policies:conventions |
| Engine | claude_cli · claude-sonnet-5 |
| Reviewed by | cr · monit-reviewer |
| Duration | 3m 01s wall · 4m 20s compute |
| Cost | ~$1.97 (est.) |
| Tokens | 56 in / 13.0k out |
Per-workstream usage
orchestrator-selection— claude-sonnet-5- In: 6
- Out: 2.4k
- Cache read: 97.9k
- Cache create: 75.9k
- Cost: ~$0.35 (est.)
- Duration: 34s
go:implementation-tests— claude-sonnet-5- In: 18
- Out: 5.2k
- Cache read: 564.5k
- Cache create: 85.7k
- Cost: ~$0.57 (est.)
- Duration: 1m 58s
policies:conventions— claude-sonnet-5- In: 26
- Out: 4.9k
- Cache read: 874.4k
- Cache create: 88.7k
- Cost: ~$0.67 (est.)
- Duration: 1m 30s
orchestrator-rollup— claude-sonnet-5- In: 6
- Out: 463
- Cache read: 102.7k
- Cache create: 91.4k
- Cost: ~$0.38 (est.)
- Duration: 15s
|
Superseded by the signed replacement PR — identical tree (cd407189), carrying this PR's review: cr APPROVED, issuelink-test finding addressed, all CI green. Closed because these commits are unsigned and the main ruleset requires verified signatures. |
…numbers (#471) ## [INT-693] Signed replacement of #470 — that branch's commits were unsigned and the `main` ruleset requires verified signatures. This commit was created via GitHub's API (GitHub-signed, verified) and its tree SHA (`cd407189`) is identical to #470's reviewed head, so the content is byte-for-byte what cr APPROVED there with all CI green and its one finding (issuelink clear-on-empty test) addressed. `jtk issues update -f 'Story Points='` (or `=none`) previously failed with Jira's per-type validation error because `FormatFieldValue` only honored null-intent for user fields; there was no way to unset a custom field from the CLI. ### The contract - Empty value clears every structured type (number, option, array, priority-like, issuelink, parent) → JSON null - `none`/`null` additionally clear number fields, matching user-field behavior - Option-like fields do NOT treat `None` as a clear — a select list may contain a literal "None" - Free-text unchanged: verbatim value; empty string is the idiomatic text clear ### Validation `TestFormatFieldValue_Clearing` covers every clear branch incl. issuelink; full `tools/jtk` suite green; gofmt clean.
[INT-693]
jtk issues update -f 'Story Points='(or=none) fails with Jira's per-type validation error —Specify a number for the custom field— becauseFormatFieldValueonly honors null-intent for user fields. A number value falls through toParseFloat, fails, and ships the raw string. There is no way to unset a custom field from the CLI at all, while--assignee nonehas worked forever.The contract
null. No structured field has a meaningful empty-string value.none/nulladditionally clear number fields, matching the existing user-field behavior — those strings can never be legitimate numbers.Noneas a clear — a select list may legitimately contain an option named "None"; only the empty value clears them.Wire shape confirmed:
UpdateIssueRequest.Fieldsismap[string]any, so a nil value marshals as JSON null — the shape Jira's update API requires for clearing.Motivating incident
An accidental Story Points + Change type write to the wrong ticket could not be reverted from the CLI — the operator had to fall back to the Jira UI.
Validation
TestFormatFieldValue_Clearingtable: clears per type, the literal-Noneoption case, free-text passthrough.tools/jtksuite green;gofmtclean.--helpexamples document the clear syntax.