Skip to content

Commit 8e7056e

Browse files
committed
Add status filtering and transaction consolidation
Add comprehensive status filtering support and consolidate transaction fetching: - docs/maniphest-cli.md: Added a new 'Status Filtering' section with pattern syntax, examples, combining with other filters, negation, and metadata notes. Also updated examples to include --status usage and metadata. - phabfive/cli.py: Added --status option help text and examples in CLI usage, imported parse_status_patterns, parse status patterns from args, and wired status_patterns into task_search calls. Also updated --show-history description to include status history and show-metadata to indicate status matching. - phabfive/status_transitions.py: New module implementing StatusPattern parsing and matching, status ordering map, helper functions get_status_order, _parse_single_condition, parse_status_patterns, and StatusPattern class with matching logic for from/to/in/been/never/raised/lowered and negation support. - phabfive/maniphest.py: Reorganized imports, added transaction consolidation method _fetch_all_transactions to fetch columns/priority/status in one call, updated matching functions to accept pre-fetched transactions (_task_matches_priority_patterns, added _task_matches_status_patterns), updated _task_matches_any_pattern and task_search to use consolidated transactions, added status_transitions_map and matching_status_map handling, updated history and metadata builders to include status history and matched status booleans, and refactored history fetching to use consolidated method. - tests/test_status_transitions.py: Added comprehensive unit tests for status parsing and matching including orders, parsing errors, pattern combinations, and matching behavior. - tox.ini: Added py314 to envlist. These changes implement status transition filtering end-to-end: CLI, parsing, matching, history display, tests, and docs. Tests and new module are isolated; transaction fetching consolidation touches maniphest to reduce API calls and integrate status handling. 🔧 feat(status_transitions): Improve status order handling with API response Enhance status transition logic to dynamically build status order from Phabricator's maniphest.querystatuses API. Add fallback to hardcoded order when API response is unavailable. Modify functions to accept optional API response for more flexible status progression tracking. 🔧 Refactor: Update status order and test cases Modify status order to reflect new workflow and update corresponding test cases. Remove 'In Progress' and 'Stalled' statuses, adjust order of 'Blocked', 'Wontfix', 'Invalid', 'Duplicate', and 'Resolved' statuses. Update test assertions to match new status order and workflow.
1 parent 75e0df4 commit 8e7056e

File tree

6 files changed

+1279
-43
lines changed

6 files changed

+1279
-43
lines changed

docs/maniphest-cli.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,132 @@ phabfive maniphest search "My Project" --priority="in:Normal+not:lowered"
413413

414414
**Note**: `not:been:PRIORITY` is functionally equivalent to `never:PRIORITY`.
415415

416+
## Status Filtering
417+
418+
Filter tasks based on their status changes over time. This helps identify tasks that progressed through workflows, track status regressions, and analyze how task completion status evolved.
419+
420+
### Why Use Status Filtering?
421+
422+
Common use cases include:
423+
424+
- **Track completions**: Find tasks that changed to "Resolved"
425+
- **Identify regressions**: Tasks that moved backward from Resolved to Open
426+
- **Find blocked work**: Tasks that are currently Blocked
427+
- **Audit status history**: See complete status change history for tasks
428+
- **Monitor workflow progression**: Find tasks that reached specific milestones
429+
430+
### Status Pattern Types
431+
432+
| Pattern | Description | Example |
433+
|---------|-------------|---------|
434+
| `from:STATUS` | Task changed from STATUS | `from:Open` |
435+
| `from:STATUS:raised` | Task progressed from STATUS | `from:Open:raised` |
436+
| `from:STATUS:lowered` | Task regressed from STATUS | `from:Resolved:lowered` |
437+
| `to:STATUS` | Task changed to STATUS | `to:Resolved` |
438+
| `in:STATUS` | Task is currently at STATUS | `in:Resolved` |
439+
| `been:STATUS` | Task was at STATUS at any point | `been:Resolved` |
440+
| `never:STATUS` | Task was never at STATUS | `never:Blocked` |
441+
| `raised` | Task had any status progression | `raised` |
442+
| `lowered` | Task had any status regression | `lowered` |
443+
| `not:PATTERN` | Negates any pattern above | `not:in:Open`, `not:raised` |
444+
445+
**Negation Prefix `not:`**: Any pattern can be prefixed with `not:` to negate its meaning. This is a general negation operator that works with all pattern types. For example:
446+
- `not:in:Open` - Tasks NOT currently Open
447+
- `not:raised` - Tasks whose status hasn't progressed
448+
- `not:been:Resolved` - Tasks never been Resolved (equivalent to `never:Resolved`)
449+
450+
### Status Values
451+
452+
The tool dynamically fetches status information from your Phabricator/Phorge instance using the `maniphest.querystatuses` API. Standard Phabricator statuses include (in progression order):
453+
454+
- **Open** (0) - Initial state for new tasks
455+
- **Blocked** (1) - Task is blocked/waiting on something
456+
- **Wontfix** (2) - Terminal: Won't be fixed
457+
- **Invalid** (3) - Terminal: Invalid task
458+
- **Duplicate** (4) - Terminal: Duplicate of another task
459+
- **Resolved** (5) - Terminal: Task completed successfully
460+
461+
**Open vs Closed**: Only Open and Blocked are "open" statuses. All others (Wontfix, Invalid, Duplicate, Resolved) are terminal/closed states.
462+
463+
**Status Progression**:
464+
- Moving from a lower number to a higher number is considered **"raised"** (forward progression)
465+
- Moving from a higher number to a lower number is considered **"lowered"** (regression/reopening)
466+
- For example: Open (0) → Resolved (5) is "raised" (task progressed forward)
467+
- For example: Resolved (5) → Open (0) is "lowered" (task was reopened)
468+
469+
**Note**: If your Phabricator/Phorge instance uses custom statuses, the tool will automatically adapt to your configuration.
470+
471+
### Basic Status Examples
472+
473+
```bash
474+
# Find tasks currently Open
475+
phabfive maniphest search "My Project" --status="in:Open"
476+
477+
# Find tasks that were ever Resolved
478+
phabfive maniphest search "My Project" --status="been:Resolved"
479+
480+
# Find tasks that progressed from Open
481+
phabfive maniphest search "My Project" --status="from:Open:raised"
482+
483+
# Find tasks that had any status progression
484+
phabfive maniphest search "My Project" --status=raised
485+
```
486+
487+
### Combining Column, Priority, and Status Filters
488+
489+
You can combine all three filter types for powerful queries:
490+
491+
```bash
492+
# Tasks moved to Done AND were raised from Open AND are currently Resolved
493+
phabfive maniphest search '*' \
494+
--column='to:Done' \
495+
--priority='from:Normal:raised' \
496+
--status='in:Resolved'
497+
498+
# Tasks in progress that have been blocked
499+
phabfive maniphest search "My Project" \
500+
--column="in:In Progress" \
501+
--status="been:Blocked"
502+
503+
# Recently completed tasks that were never blocked
504+
phabfive maniphest search "My Project" \
505+
--status="to:Resolved" \
506+
--updated-after=7 \
507+
--status="never:Blocked"
508+
```
509+
510+
### Status OR/AND Logic
511+
512+
Same as column and priority patterns, status patterns support OR (comma) and AND (plus):
513+
514+
```bash
515+
# Tasks currently Open OR Blocked
516+
phabfive maniphest search "My Project" --status="in:Open,in:Blocked"
517+
518+
# Tasks raised from Open AND currently Resolved
519+
phabfive maniphest search "My Project" --status="from:Open:raised+in:Resolved"
520+
```
521+
522+
### Status Negation Patterns
523+
524+
Use the `not:` prefix to negate status patterns:
525+
526+
```bash
527+
# Tasks NOT currently Open
528+
phabfive maniphest search "My Project" --status="not:in:Open"
529+
530+
# Tasks whose status has NOT progressed
531+
phabfive maniphest search "My Project" --status="not:raised"
532+
533+
# Tasks NOT Resolved AND have been Blocked at some point
534+
phabfive maniphest search "My Project" --status="not:in:Resolved+been:Blocked"
535+
536+
# Tasks that progressed but did NOT reach Resolved
537+
phabfive maniphest search "My Project" --status="raised+not:in:Resolved"
538+
```
539+
540+
**Note**: `not:been:STATUS` is functionally equivalent to `never:STATUS`.
541+
416542
## Viewing Metadata
417543

418544
Use `--show-metadata` to see why tasks matched your filters. This is especially useful when debugging complex filter combinations.
@@ -421,6 +547,7 @@ Use `--show-metadata` to see why tasks matched your filters. This is especially
421547
phabfive maniphest search '*' \
422548
--column='from:Up Next:forward' \
423549
--priority='been:Normal' \
550+
--status='in:Resolved' \
424551
--show-metadata
425552
```
426553

@@ -429,11 +556,13 @@ Output includes:
429556
Metadata:
430557
MatchedBoards: ['Development', 'GUNNAR-Core']
431558
MatchedPriority: true
559+
MatchedStatus: true
432560
```
433561

434562
The metadata section shows:
435563
- **MatchedBoards**: Which boards satisfied the `--column` filter (in alphabetical order)
436564
- **MatchedPriority**: Whether the task matched the `--priority` filter
565+
- **MatchedStatus**: Whether the task matched the `--status` filter
437566

438567
This helps you understand exactly why a task appeared in your search results.
439568

phabfive/cli.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,23 @@
155155
from:Normal:raised
156156
not:in:High+raised
157157
in:High,been:Unbreak Now!
158-
--show-history Display column and priority transition history for each task
159-
--show-metadata Display filter match metadata (which boards/priority matched)
158+
--status=PATTERNS Filter tasks by status transitions (comma=OR, plus=AND).
159+
Automatically displays status history.
160+
from:STATUS[:direction] - Changed from STATUS
161+
to:STATUS - Changed to STATUS
162+
in:STATUS - Currently at STATUS
163+
been:STATUS - Was at STATUS at any point
164+
never:STATUS - Never was at STATUS
165+
raised - Status progressed forward
166+
lowered - Status moved backward
167+
not:PATTERN - Negates any pattern above
168+
Examples:
169+
been:Open
170+
from:Open:raised
171+
not:in:Resolved+raised
172+
in:Open,been:Resolved
173+
--show-history Display column, priority, and status transition history
174+
--show-metadata Display filter match metadata (which boards/priority/status matched)
160175
161176
Options:
162177
--all Show all fields for a ticket
@@ -251,6 +266,7 @@ def run(cli_args, sub_args):
251266
from phabfive import passphrase, diffusion, paste, user, repl, maniphest
252267
from phabfive.maniphest_transitions import parse_transition_patterns
253268
from phabfive.priority_transitions import parse_priority_patterns
269+
from phabfive.status_transitions import parse_status_patterns
254270
from phabfive.constants import REPO_STATUS_CHOICES
255271
from phabfive.exceptions import PhabfiveException
256272

@@ -400,6 +416,17 @@ def run(cli_args, sub_args):
400416
retcode = 1
401417
return retcode
402418

419+
status_patterns = None
420+
if sub_args.get("--status"):
421+
try:
422+
status_patterns = parse_status_patterns(
423+
sub_args["--status"]
424+
)
425+
except Exception as e:
426+
print(f"ERROR: Invalid status filter pattern: {e}", file=sys.stderr)
427+
retcode = 1
428+
return retcode
429+
403430
# Only show history if explicitly requested
404431
show_history = sub_args.get("--show-history", False)
405432

@@ -411,6 +438,7 @@ def run(cli_args, sub_args):
411438
updated_after=sub_args["--updated-after"],
412439
transition_patterns=transition_patterns,
413440
priority_patterns=priority_patterns,
441+
status_patterns=status_patterns,
414442
show_history=show_history,
415443
show_metadata=show_metadata,
416444
)

0 commit comments

Comments
 (0)