feat(issues): add watchers, relations, custom fields, richer filters#103
Merged
Conversation
Closes #77. Expands the issues command to cover the rest of the Redmine REST surface that previously required `redmine api` or the web UI. New CLI commands: - `issues watchers list|add|remove` — manage watchers on an issue. - `issues relations list|add|remove` — manage issue relations. New flags on existing commands: - `issues create`: `--start-date`, `--due-date`, `--watcher`, `--custom-field`. - `issues update`: `--start-date`, `--due-date`, `--custom-field`, `--private-notes`. - `issues list`: `--author`, `--priority`, `--category`, `--parent`, `--subproject`, `--include-subprojects`, `--is-private`, `--filter` (raw Redmine filter escape hatch). Internals: - `IssueCreate` / `IssueUpdate` gain start_date, due_date, watcher_user_ids, custom_field_values, private_notes (update only). - New `RelationService` (`/issues/{id}/relations.json`, `/relations/{id}.json`). - New `IssueService.AddWatcher` / `RemoveWatcher` / `ListWatchers`. - `parseCustomFieldValues` lifted into `cmdutil.ParseCustomFieldValues` so issues and projects share one implementation. - 7 new MCP tools auto-generated: list/add/remove_issue_watcher, list/get/create/delete_issue_relation. Tests: unit tests for new ops validators, list filter wiring, watcher and relation subcommands, plus four e2e tests covering create-with-dates, private notes, the watcher lifecycle, and the relation lifecycle. Docs: English + zh-cn `issues.mdx` updated with new flags, the `watchers` and `relations` sections, the `--filter` escape-hatch tip, and the relation type reference table.
- models.Journal gains PrivateNotes so issues get --journals carries the flag through to JSON output. Without this, the field returned by Redmine was dropped during Go decoding. - e2e MCP golden refreshed to include the three new read-only issue tools: get_issue_relation, list_issue_relations, list_issue_watchers.
- update.go: drop the redundant ResolveProject roundtrip when resolving --category / --version by name; the issue payload already carries the numeric project ID, which Redmine accepts wherever the path takes an identifier. Saves one API call per affected update. - cmdutil: extract ParseKeyValuePairs and reuse it from ParseCustomFieldValues and from list.go's --filter handling, deleting the duplicate parser. - ops/relations: replace the 2-entry IssueRelationTypesSupportingDelay map with a slice + slices.Contains and delete the bespoke keys() helper. Same change for validateRelationType. - filter_resolution: collapse resolveIssueAssigneeFilter and resolveIssueAuthorFilter into a shared resolveIssueUserFilter; the named wrappers stay as thin call sites. - watcher.go / relation.go: render lists via cmdutil.RenderCollection so the JSON/CSV/table switch is gone from both new commands. - list.go: tighten the --query-id rationale comment.
- TestIssues_CustomFieldOnCreate: creates an issue with --custom-field "E2E Severity=High" and asserts the custom field round trips on the response. Closes the gap where only project create/update exercised the --custom-field flag end to end. - TestIssues_RelationPrecedesWithDelay: creates a 'precedes' relation with --delay 5 and asserts Redmine returns the delay. The earlier TestIssues_RelationLifecycle only covered 'blocks', which has no delay. - bootstrap-redmine.sh: set is_for_all=true on the seeded 'E2E Severity' custom field (and reset its tracker/filter/required flags on every bootstrap), so the field auto-applies to ephemeral projects created by the e2e fixture without requiring per-project attachment via the API.
The seeded "E2E Severity" custom field is now is_for_all=true so it attaches to every newly-created issue. Its numeric ID and value shape vary by bootstrap and aren't the schema signal the golden snapshot is meant to catch, so scrub it alongside the other per-run fields.
The two-positional commands `issues watchers add <issue-id> <user>` and
`issues watchers remove <issue-id> <user>` previously registered a
`RegisterFlagCompletionFunc("user", …)` against a --user flag that
doesn't exist, so tab-completing the user argument did nothing.
Replace the dead-code registration with a ValidArgsFunction that returns
the same CompleteUsers list when completing the second positional and
otherwise falls back to no completion. The first slot (issue-id) is
numeric and unbounded, so it stays uncompleted.
CompleteUsers used to early-return nil completions when listing users failed (403 for non-admin sessions), so the 'me' shorthand was unavailable to tab even though the resolver accepts it without an admin endpoint hit. Hoist 'me' before the API call so non-admin shells still get a useful suggestion instead of an empty list.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #77. Expands the
issuescommand group to cover the rest of the Redmine REST surface so common workflows no longer fall back toredmine apior the web UI.New CLI commands
redmine issues watchers list|add|remove— watcher lifecycle on an issue.redmine issues relations list|add|remove— issue relations (blocks/precedes/etc).New flags on existing commands
issues create:--start-date,--due-date,--watcher,--custom-field.issues update:--start-date,--due-date,--custom-field,--private-notes(requires--note).issues list:--author,--priority,--category,--parent,--subproject,--include-subprojects,--is-private, and a raw--filter key=valueescape hatch (for date ranges, custom fieldscf_X=…, subject text~login, etc).Under the hood
IssueCreate/IssueUpdategainstart_date,due_date,watcher_user_ids,custom_field_values,private_notes.RelationServicecoversPOST/DELETE /issues/{id}/relations.jsonandGET/DELETE /relations/{id}.json.IssueService.AddWatcher/RemoveWatcher/ListWatcherscovering/issues/{id}/watchers.json.parseCustomFieldValueslifted intocmdutil.ParseCustomFieldValuesso issues and projects share one implementation.mcpgenannotations:list_issue_watchers,add_issue_watcher,remove_issue_watcher,list_issue_relations,get_issue_relation,create_issue_relation,delete_issue_relation.list_issues,create_issue,update_issueMCP schemas widen with the new optional fields.Test plan
go test ./...— all packages pass (~30s).golangci-lint run— zero issues.go vet ./...andgo vet -tags=e2e ./e2e/...— clean.make e2e-up && make e2e-test && make e2e-down) against Redmine 6.1 — new tests added ine2e/issues_extended_test.go(dates, private notes, watcher lifecycle, relation lifecycle); pipeline will exercise these.Docs
docs/src/content/docs/commands/issues.mdx) and Chinese (docs/src/content/docs/zh-cn/commands/issues.mdx) updated with the new flags, thewatchersandrelationssections, the--filterescape-hatch tip, and the relation-type reference table.skills/redmine-cli/SKILL.mdupdated to mention the new subcommands.Compatibility
No breaking changes — every addition is optional. Existing scripts and MCP clients keep working unchanged.