feat(issue): add yt issue create #22

Merged
David merged 1 commit from feat/issue-create into main 2026-05-14 17:04:41 +02:00
Owner

Summary

Adds yt issue create as a new subcommand under issue. Creates an issue from the command line with the project's YouTrack short name (the ABC prefix in ABC-123).

yt issue create --project ABC --summary "fix crash" \
    [--description ...] [--type Bug] [--assignee jdoe] [--json]

Two-step API flow under the hood:

  1. GET /api/admin/projects?fields=id,shortName to resolve the short name to the project's internal id (required by the create body).
  2. POST /api/issues?fields=idReadable,summary,customFields(...) with {project:{id}, summary, description?, customFields?}. --type turns into a SingleEnumIssueCustomField body element, --assignee into a SingleUserIssueCustomField with {value:{login}}. The fields= selector matches issue inspect, so the response decodes straight into the existing Issue model and we can render it immediately.

Validation: empty/whitespace summary and empty project short name are rejected client-side before any HTTP call. Other validation is left to the server (unknown type values come back as a 400 with a useful body, which the existing ClientError::Status formatting surfaces).

New surface

  • yt::models::Project (id + shortName).
  • yt::api::{list_projects, resolve_project_id, create_issue, CreateIssueRequest}.
  • Client::post_json reintroduced (was dropped earlier when no mutation needed a JSON response; now needed).
  • IssueCommands::Create(IssueCreateArgs) in cli.rs plus dispatch in commands/issue/mod.rs plus commands/issue/create.rs.

Output

  • Default: Created <ID>: <summary> once the POST succeeds.
  • --json: pretty-printed Issue JSON (idReadable + summary + customFields), suitable for piping to jq.

Verification

  • cargo fmt --check clean.
  • cargo clippy --all-targets -- -D warnings clean.
  • cargo test --all-targets: 175 tests pass (15 new on this branch).
  • yt issue create --help shows the flag surface (--project, --summary, --description, --type, --assignee, --json).

Coverage: project list decode, project-id resolution (found + not-found), client-side validation (empty summary, empty project), minimal POST body, body with description, body with type + assignee custom fields, JSON output mode, missing-credentials guard, server error propagation. Plus Client::post_json gets decode + non-2xx wiremock tests of its own.

Out of scope

  • --state and --priority on create. State is normally auto-set to the project's initial state at creation time, and priority defaults too. They can be set after creation with yt issue set-state (state) or via a follow-up issue update for priority. Easy to add to create if you want.
  • An issue update subcommand for the full field-edit surface. Not needed for "create".

Test plan

  • Against a real instance: yt issue create --project <SHORT> --summary "smoke test" returns Created <SHORT>-N: smoke test and the issue is visible in the YouTrack UI.
  • yt issue create --project <SHORT> --summary x --type Bug --assignee <you> lands with the right Type + Assignee on the issue.
  • yt issue create --project NOPE --summary x exits non-zero with project 'NOPE' not found.
  • yt issue create --project <SHORT> --summary "" exits non-zero with a clear "summary required" message before any HTTP call.
  • yt issue create --project <SHORT> --summary x --json | jq .id returns <SHORT>-N.
## Summary Adds `yt issue create` as a new subcommand under `issue`. Creates an issue from the command line with the project's YouTrack short name (the `ABC` prefix in `ABC-123`). ``` yt issue create --project ABC --summary "fix crash" \ [--description ...] [--type Bug] [--assignee jdoe] [--json] ``` Two-step API flow under the hood: 1. `GET /api/admin/projects?fields=id,shortName` to resolve the short name to the project's internal id (required by the create body). 2. `POST /api/issues?fields=idReadable,summary,customFields(...)` with `{project:{id}, summary, description?, customFields?}`. `--type` turns into a `SingleEnumIssueCustomField` body element, `--assignee` into a `SingleUserIssueCustomField` with `{value:{login}}`. The fields= selector matches `issue inspect`, so the response decodes straight into the existing `Issue` model and we can render it immediately. Validation: empty/whitespace summary and empty project short name are rejected client-side before any HTTP call. Other validation is left to the server (unknown type values come back as a 400 with a useful body, which the existing `ClientError::Status` formatting surfaces). ## New surface - `yt::models::Project` (id + shortName). - `yt::api::{list_projects, resolve_project_id, create_issue, CreateIssueRequest}`. - `Client::post_json` reintroduced (was dropped earlier when no mutation needed a JSON response; now needed). - `IssueCommands::Create(IssueCreateArgs)` in `cli.rs` plus dispatch in `commands/issue/mod.rs` plus `commands/issue/create.rs`. ## Output - Default: `Created <ID>: <summary>` once the POST succeeds. - `--json`: pretty-printed `Issue` JSON (idReadable + summary + customFields), suitable for piping to jq. ## Verification - `cargo fmt --check` clean. - `cargo clippy --all-targets -- -D warnings` clean. - `cargo test --all-targets`: 175 tests pass (15 new on this branch). - `yt issue create --help` shows the flag surface (`--project`, `--summary`, `--description`, `--type`, `--assignee`, `--json`). Coverage: project list decode, project-id resolution (found + not-found), client-side validation (empty summary, empty project), minimal POST body, body with description, body with type + assignee custom fields, JSON output mode, missing-credentials guard, server error propagation. Plus `Client::post_json` gets decode + non-2xx wiremock tests of its own. ## Out of scope - `--state` and `--priority` on create. State is normally auto-set to the project's initial state at creation time, and priority defaults too. They can be set after creation with `yt issue set-state` (state) or via a follow-up `issue update` for priority. Easy to add to create if you want. - An `issue update` subcommand for the full field-edit surface. Not needed for "create". ## Test plan - [ ] Against a real instance: `yt issue create --project <SHORT> --summary "smoke test"` returns `Created <SHORT>-N: smoke test` and the issue is visible in the YouTrack UI. - [ ] `yt issue create --project <SHORT> --summary x --type Bug --assignee <you>` lands with the right Type + Assignee on the issue. - [ ] `yt issue create --project NOPE --summary x` exits non-zero with `project 'NOPE' not found`. - [ ] `yt issue create --project <SHORT> --summary "" ` exits non-zero with a clear "summary required" message before any HTTP call. - [ ] `yt issue create --project <SHORT> --summary x --json | jq .id` returns `<SHORT>-N`.
feat(issue): add yt issue create
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 19s
Create release / Create release from merged PR (pull_request) Has been skipped
5d144d6d84
New subcommand that creates an issue from the command line.

```
yt issue create --project ABC --summary "fix crash" [--description ...] [--type Bug] [--assignee jdoe] [--json]
```

The project is referenced by its YouTrack short name (the prefix that appears in issue ids like ABC-123). Under the hood the helper calls `GET /api/admin/projects?fields=id,shortName` to resolve the short name to the project's internal id, then `POST /api/issues?fields=idReadable,summary,customFields(...)` with `{project:{id}, summary, description?, customFields?}`. The fields= selector on the response matches what `issue inspect` uses, so the returned `Issue` is ready to render. `--type` becomes a `SingleEnumIssueCustomField` body element, `--assignee` becomes a `SingleUserIssueCustomField` with `{value:{login}}`. Empty/whitespace summaries and empty project short names are rejected client-side before any HTTP call; other validation is left to the server (e.g. unknown type values come back as a 400 with a useful message).

New surface:
- `yt::models::Project` (id + shortName).
- `yt::api::{list_projects, resolve_project_id, create_issue, CreateIssueRequest}`.
- `Client::post_json` (reintroduced; was dropped when no mutation needed a JSON response, now needed again).
- `IssueCommands::Create(IssueCreateArgs)` + dispatch in `commands/issue/mod.rs` + `commands/issue/create.rs`.

Output:
- Non-json: `Created <ID>: <summary>` once the POST succeeds.
- `--json`: pretty-printed `Issue` JSON (idReadable + summary + customFields), suitable for piping to jq.

Tests cover: project listing decode, project-id resolution (found + not-found), client-side validation (empty summary, empty project), minimal POST body, body with description, body with type + assignee custom fields, JSON output mode, missing-credentials guard, server error propagation. Plus the reintroduced `Client::post_json` gets its own pair of decode + non-2xx wiremock tests. 175 tests pass (15 new); clippy clean; fmt clean.
David merged commit 91ad2c377e into main 2026-05-14 17:04:41 +02:00
David deleted branch feat/issue-create 2026-05-14 17:04:41 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
pandoras-box/youtrack-cli!22
No description provided.