You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

150 lines
5.0 KiB
Markdown

+++
title = "Wire --filter flag into list, ready, and migrate commands"
priority = 8
status = "done"
ticket_type = "feature"
dependencies = ["c2a024"]
+++
## Summary
Add `--filter KEY=VALUE` (repeatable) to the `list`, `ready`, and `migrate` CLI commands.
Parse filter arguments into a `TicketFilter` and apply it in each command handler.
Depends on: TicketFilter module (ticket c2a024).
## CLI changes (src/main.rs)
Add to `Commands::List`, `Commands::Ready`, and `Commands::Migrate` variants:
```rust
/// Filter tickets: key=value pairs (repeatable).
/// Keys: priority, type, status, title.
/// Different keys are ANDed; same key with multiple values is ORed.
/// Values support glob wildcards: status=* matches all statuses.
#[arg(long = "filter", value_name = "KEY=VALUE")]
filter: Vec<String>,
```
Update the `dispatch` function to pass filter args through to each handler.
## Handler changes
### cmd_list(filter_args, json)
```rust
let filter = filter::parse_filters(&filter_args)?;
let tickets: Vec<Ticket> = list_tickets(&root).await?
.into_iter()
.filter(|t| filter.matches(t))
.collect();
```
Note: the default done-exclusion behaviour (ticket for that is separate) will also
live here, layered on top of this filter application.
### cmd_ready(filter_args, json)
Apply the user filter AFTER the ready check. The ready check (not done + all deps done)
is always applied first; the user's filter narrows further within ready tickets.
```rust
let filter = filter::parse_filters(&filter_args)?;
// ... build done_ids as before ...
let ready: Vec<Ticket> = all
.into_iter()
.filter(|t| {
t.status \!= Status::Done
&& t.dependencies.iter().all(|dep| done_ids.contains(dep.as_str()))
&& filter.matches(t)
})
.collect();
```
### cmd_migrate(filter_args, dry_run, json)
For migrate, the filter selects which tickets are candidates for migration.
Tickets not matching the filter are skipped (counted separately, not treated as errors).
Add a `skipped` field to `MigrateReport` in `store.rs`:
```rust
pub struct MigrateReport {
pub updated: usize,
pub already_current: usize,
pub skipped: usize, // NEW: tickets excluded by filter
pub errors: Vec<(String, String)>,
}
```
Update `migrate_tickets` signature in `store.rs`:
```rust
pub async fn migrate_tickets(
root: &Path,
dry_run: bool,
filter: &TicketFilter,
) -> Result<MigrateReport>
```
Inside the per-file loop, after deserialising the ticket, check `filter.matches(&ticket)`.
If false: increment `report.skipped` and continue to next file.
Update `cmd_migrate` to parse the filter and pass it to `migrate_tickets`.
## display.rs changes
Update `format_migrate_report` and `format_migrate_report_json` to include the
`skipped` count:
Human format:
```
Migrated 3 tickets.
Current 5 tickets (already up to date).
Skipped 2 tickets (did not match filter).
Errors 1 ticket could not be migrated:
bad_ticket.json: trailing comma at line 4
```
Only print the "Skipped" line when `skipped > 0`.
JSON format: add `"skipped": N` key to the existing object.
## files touched
- `src/main.rs``filter` fields on List/Ready/Migrate variants, updated dispatch,
updated cmd_list/cmd_ready/cmd_migrate handlers
- `src/store.rs``MigrateReport::skipped`, `migrate_tickets` gains `filter` param
- `src/display.rs` — updated `format_migrate_report` and `format_migrate_report_json`
- `src/tests.rs` — unit tests for updated migrate report formatting
- `tests/integration.rs` — integration tests
## Integration tests to add (tests/integration.rs)
**list filtering:**
- Create tickets: 1 bug/todo, 1 task/in_progress, 1 bug/done.
`nbd list --filter type=bug` shows only the bug tickets (done-exclusion is separate,
but this test can use non-done bugs).
- `nbd list --filter status=in_progress` shows only in_progress tickets.
- `nbd list --filter status=todo --filter status=in_progress` shows both todo and in_progress
(OR within same key).
- `nbd list --filter type=bug --filter status=todo` shows only bug+todo tickets
(AND across keys).
- `nbd list --filter title=*login*` shows only tickets whose title contains "login".
- `nbd list --filter status=*` matches all statuses (wildcard).
- `nbd list --filter type=unknown` exits non-zero with an error (unknown key passes through
as a value, but "unknown" does not match any type → empty results, or error? Error on
unknown key is preferable).
- `nbd list --filter badformat` (no `=`) exits non-zero with an error.
**ready filtering:**
- `nbd ready --filter type=bug` returns only ready bug tickets.
- `nbd ready --filter priority=8` returns only ready tickets with priority 8.
**migrate filtering:**
- Create two tickets. Run `nbd migrate --filter status=todo --dry-run`.
Verify `skipped` count in JSON output matches tickets not matching the filter.
- `nbd migrate --filter status=todo --json` includes `skipped` key.
**error cases:**
- `--filter` with unknown key exits non-zero.
- `--filter` with no `=` exits non-zero.