Skip to content

Commit 0eb26b4

Browse files
committed
Bamboo: Result ordering
atlassian-api#1313
1 parent 02d3e85 commit 0eb26b4

3 files changed

Lines changed: 111 additions & 0 deletions

File tree

atlassian/bamboo.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,7 @@ def results(
453453
start_index=0,
454454
max_results=25,
455455
include_all_states=False,
456+
build_state=None,
456457
):
457458
"""
458459
Get results as generic method
@@ -468,6 +469,8 @@ def results(
468469
:param start_index:
469470
:param max_results:
470471
:param include_all_states:
472+
:param build_state: Optional Bamboo result state, such as
473+
``Successful`` or ``Failed``.
471474
:return:
472475
"""
473476
resource = "result"
@@ -485,6 +488,8 @@ def results(
485488
params["issueKey"] = issue_key
486489
if include_all_states:
487490
params["includeAllStates"] = include_all_states
491+
if build_state is not None:
492+
params["buildstate"] = build_state
488493
return self.base_list_call(
489494
resource,
490495
expand=expand,
@@ -581,6 +586,7 @@ def plan_results(
581586
start_index=0,
582587
max_results=25,
583588
include_all_states=False,
589+
build_state=None,
584590
):
585591
"""
586592
Get Plan results
@@ -594,6 +600,8 @@ def plan_results(
594600
:param start_index:
595601
:param max_results:
596602
:param include_all_states:
603+
:param build_state: Optional Bamboo result state, such as
604+
``Successful`` or ``Failed``.
597605
:return:
598606
"""
599607
return self.results(
@@ -607,8 +615,71 @@ def plan_results(
607615
start_index=start_index,
608616
max_results=max_results,
609617
include_all_states=include_all_states,
618+
build_state=build_state,
610619
)
611620

621+
def ordered_plan_results(
622+
self,
623+
project_key,
624+
plan_key,
625+
order="descending",
626+
build_state=None,
627+
max_results=25,
628+
**kwargs,
629+
):
630+
"""Return retrieved plan results ordered by completion time.
631+
632+
Bamboo's result API does not expose a server-side sort parameter. This
633+
helper orders the result page client-side by ``buildCompletedTime``.
634+
Set ``max_results`` high enough to include the history being compared;
635+
this method returns a list rather than the lazy generator returned by
636+
:meth:`plan_results`.
637+
638+
:param order: ``"ascending"`` for oldest first or ``"descending"``
639+
for newest first.
640+
:param build_state: Optional ``Successful`` or ``Failed`` filter.
641+
:param max_results: Number of results Bamboo should return to sort.
642+
:return: A list of build results ordered by completion time.
643+
"""
644+
if order not in {"ascending", "descending"}:
645+
raise ValueError("order must be 'ascending' or 'descending'")
646+
647+
results = self.plan_results(
648+
project_key,
649+
plan_key,
650+
build_state=build_state,
651+
max_results=max_results,
652+
**kwargs,
653+
)
654+
return sorted(
655+
results,
656+
key=lambda result: result.get("buildCompletedTime") or "",
657+
reverse=order == "descending",
658+
)
659+
660+
def latest_successful_plan_result(self, project_key, plan_key, max_results=25, **kwargs):
661+
"""Return the newest successful plan result, or ``None`` when absent."""
662+
results = self.ordered_plan_results(
663+
project_key,
664+
plan_key,
665+
build_state="Successful",
666+
max_results=max_results,
667+
**kwargs,
668+
)
669+
return results[0] if results else None
670+
671+
def oldest_failed_plan_result(self, project_key, plan_key, max_results=25, **kwargs):
672+
"""Return the oldest failed plan result, or ``None`` when absent."""
673+
results = self.ordered_plan_results(
674+
project_key,
675+
plan_key,
676+
order="ascending",
677+
build_state="Failed",
678+
max_results=max_results,
679+
**kwargs,
680+
)
681+
return results[0] if results else None
682+
612683
def build_result(
613684
self,
614685
build_key,

docs/bamboo.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ Build results
124124
plan_results(project_key, plan_key, expand=None, favourite=False, clover_enabled=False, label=None,
125125
issue_key=None, start_index=0, max_results=25, include_all_states=False)
126126
127+
# Bamboo has no server-side result sort; order the retrieved history locally.
128+
newest_success = bamboo.latest_successful_plan_result("PROJECT", "PLAN", max_results=1000)
129+
oldest_failed = bamboo.oldest_failed_plan_result("PROJECT", "PLAN", max_results=1000)
130+
127131
# Get a single build result
128132
build_result(build_key, expand=None, include_all_states=False)
129133

tests/bamboo/test_bamboo.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,39 @@ def test_project_linked_repository_methods(mock_get, mock_post, mock_delete):
126126
assert mock_get.call_args_list[1].args[0] == "rest/api/latest/project/PROJ/repository"
127127
mock_post.assert_called_once_with("rest/api/latest/project/PROJ/repository", data={"id": 42})
128128
mock_delete.assert_called_once_with("rest/api/latest/project/PROJ/repository/42")
129+
130+
131+
def test_ordered_plan_results_and_convenience_filters(monkeypatch):
132+
bamboo = Bamboo("https://bamboo.example.test", token="token")
133+
calls = []
134+
results = [
135+
{"buildCompletedTime": "2024-01-02T00:00:00.000Z", "state": "Failed"},
136+
{"buildCompletedTime": "2024-01-03T00:00:00.000Z", "state": "Successful"},
137+
{"buildCompletedTime": "2024-01-01T00:00:00.000Z", "state": "Failed"},
138+
]
139+
140+
def plan_results(project_key, plan_key, **kwargs):
141+
calls.append((project_key, plan_key, kwargs))
142+
return (result for result in results)
143+
144+
monkeypatch.setattr(bamboo, "plan_results", plan_results)
145+
146+
assert [result["buildCompletedTime"] for result in bamboo.ordered_plan_results("PROJ", "PLAN")] == [
147+
"2024-01-03T00:00:00.000Z",
148+
"2024-01-02T00:00:00.000Z",
149+
"2024-01-01T00:00:00.000Z",
150+
]
151+
assert bamboo.latest_successful_plan_result("PROJ", "PLAN")["state"] == "Successful"
152+
assert bamboo.oldest_failed_plan_result("PROJ", "PLAN")["buildCompletedTime"] == "2024-01-01T00:00:00.000Z"
153+
assert calls[1][2]["build_state"] == "Successful"
154+
assert calls[2][2]["build_state"] == "Failed"
155+
156+
157+
@patch.object(Bamboo, "get")
158+
def test_plan_results_forwards_supported_build_state_filter(mock_get):
159+
bamboo = Bamboo("https://bamboo.example.test", token="token")
160+
mock_get.return_value = {"results": {"size": 0, "result": []}}
161+
162+
assert list(bamboo.plan_results("PROJ", "PLAN", build_state="Successful")) == []
163+
164+
assert mock_get.call_args.args[3]["buildstate"] == "Successful"

0 commit comments

Comments
 (0)