88
99log = logging .getLogger (__name__ )
1010
11- # Status ordering for comparison and workflow progression
11+ # Fallback status ordering for comparison and workflow progression
1212# Lower number = earlier in the workflow
1313# Used to determine if status was "raised" (progressed forward) or "lowered" (moved backward)
14- # Statuses like "Wontfix", "Invalid", "Duplicate" are considered terminal/closed states
15- STATUS_ORDER = {
14+ # This is only used if the API call to maniphest.querystatuses fails
15+ FALLBACK_STATUS_ORDER = {
1616 "open" : 0 ,
1717 "blocked" : 1 ,
1818 "wontfix" : 2 ,
2222}
2323
2424
25- def get_status_order (status_name ):
25+ def _build_status_order_from_api (api_response ):
26+ """
27+ Build status order mapping from API response.
28+
29+ The maniphest.querystatuses API returns:
30+ - openStatuses: list of open status keys
31+ - closedStatuses: dict of closed status values
32+ - statusMap: dict mapping status keys to display names
33+
34+ Parameters
35+ ----------
36+ api_response : dict
37+ Response from maniphest.querystatuses API
38+
39+ Returns
40+ -------
41+ dict
42+ Mapping of status name (lowercase) to order number
43+ """
44+ if not api_response :
45+ return FALLBACK_STATUS_ORDER
46+
47+ order_map = {}
48+
49+ # Extract status information from API response
50+ open_status_keys = api_response .get ("openStatuses" , [])
51+ closed_status_values = api_response .get ("closedStatuses" , {})
52+ status_map = api_response .get ("statusMap" , {})
53+
54+ # Build order: open statuses first (lower numbers), then closed statuses
55+ current_order = 0
56+
57+ # Add open statuses
58+ for status_key in open_status_keys :
59+ status_name = status_map .get (status_key , status_key )
60+ order_map [status_name .lower ()] = current_order
61+ current_order += 1
62+
63+ # Add closed statuses
64+ for status_key in closed_status_values .values ():
65+ status_name = status_map .get (status_key , status_key )
66+ order_map [status_name .lower ()] = current_order
67+ current_order += 1
68+
69+ return order_map
70+
71+
72+ def get_status_order (status_name , api_response = None ):
2673 """
2774 Get the numeric order of a status for comparison.
2875
2976 Parameters
3077 ----------
3178 status_name : str
3279 Status name (case-insensitive)
80+ api_response : dict, optional
81+ Full response from maniphest.querystatuses API.
82+ If not provided, uses fallback ordering.
3383
3484 Returns
3585 -------
@@ -38,7 +88,14 @@ def get_status_order(status_name):
3888 """
3989 if not status_name :
4090 return None
41- return STATUS_ORDER .get (status_name .lower ())
91+
92+ # Build order map from API response if provided
93+ if api_response :
94+ order_map = _build_status_order_from_api (api_response )
95+ else :
96+ order_map = FALLBACK_STATUS_ORDER
97+
98+ return order_map .get (status_name .lower ())
4299
43100
44101class StatusPattern :
@@ -49,15 +106,18 @@ class StatusPattern:
49106 Multiple patterns can be combined with OR logic.
50107 """
51108
52- def __init__ (self , conditions ):
109+ def __init__ (self , conditions , api_response = None ):
53110 """
54111 Parameters
55112 ----------
56113 conditions : list
57114 List of condition dicts, each with keys like:
58115 {"type": "from", "status": "Open", "direction": "raised"}
116+ api_response : dict, optional
117+ Full response from maniphest.querystatuses API for ordering
59118 """
60119 self .conditions = conditions
120+ self .api_response = api_response
61121
62122 def matches (self , status_transactions , current_status ):
63123 """
@@ -131,8 +191,8 @@ def _matches_from(self, condition, status_transactions):
131191 return True
132192
133193 # Check direction
134- old_order = get_status_order (old_value )
135- new_order = get_status_order (new_value )
194+ old_order = get_status_order (old_value , self . api_response )
195+ new_order = get_status_order (new_value , self . api_response )
136196
137197 if old_order is not None and new_order is not None :
138198 if direction == "raised" and new_order > old_order :
@@ -203,8 +263,8 @@ def _matches_raised(self, status_transactions):
203263 if old_value is None or new_value is None :
204264 continue
205265
206- old_order = get_status_order (old_value )
207- new_order = get_status_order (new_value )
266+ old_order = get_status_order (old_value , self . api_response )
267+ new_order = get_status_order (new_value , self . api_response )
208268
209269 if old_order is not None and new_order is not None :
210270 if new_order > old_order : # Higher number = further along
@@ -221,8 +281,8 @@ def _matches_lowered(self, status_transactions):
221281 if old_value is None or new_value is None :
222282 continue
223283
224- old_order = get_status_order (old_value )
225- new_order = get_status_order (new_value )
284+ old_order = get_status_order (old_value , self . api_response )
285+ new_order = get_status_order (new_value , self . api_response )
226286
227287 if old_order is not None and new_order is not None :
228288 if new_order < old_order : # Lower number = earlier stage
@@ -314,7 +374,7 @@ def _parse_single_condition(condition_str):
314374 return result
315375
316376
317- def parse_status_patterns (patterns_str ):
377+ def parse_status_patterns (patterns_str , api_response = None ):
318378 """
319379 Parse status pattern string into list of StatusPattern objects.
320380
@@ -326,6 +386,8 @@ def parse_status_patterns(patterns_str):
326386 ----------
327387 patterns_str : str
328388 Pattern string like "from:Open:raised+in:Resolved,to:Closed"
389+ api_response : dict, optional
390+ Full response from maniphest.querystatuses API for ordering
329391
330392 Returns
331393 -------
@@ -372,7 +434,7 @@ def parse_status_patterns(patterns_str):
372434 conditions .append (condition )
373435
374436 if conditions :
375- patterns .append (StatusPattern (conditions ))
437+ patterns .append (StatusPattern (conditions , api_response ))
376438
377439 if not patterns :
378440 raise PhabfiveException ("No valid status patterns found" )
0 commit comments