Skip to content

[Jira] Update Agile (Greenhopper) REST API URL handling #1530

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 11, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 77 additions & 30 deletions atlassian/jira.py
Original file line number Diff line number Diff line change
Expand Up @@ -4993,6 +4993,19 @@ def tempo_teams_get_memberships_for_member(self, username: str) -> T_resp_json:
# Resource: https://docs.atlassian.com/jira-software/REST/7.3.1/
#######################################################################
# /rest/agile/1.0/backlog/issue
def get_agile_resource_url(self, resource: str, legacy_api: bool = False) -> str:
"""
Prepare an 'Agile' API-specific URL relying on defaults set for the client.

:param resource: Name of an endpoint
:param legacy_api: If True - use 'greenhopper' as an API type, else - use a newer, 'agile', name.
:return: String with a full URL path to resource
"""
api_version = "1.0"
api_type = "greenhopper" if legacy_api else "agile"
api_root = self.api_root.replace("rest/api", f"rest/{api_type}")
return self.resource_url(resource=resource, api_root=api_root, api_version=api_version)

def move_issues_to_backlog(self, issue_keys: list) -> T_resp_json:
"""
Move issues to backlog
Expand All @@ -5012,7 +5025,8 @@ def add_issues_to_backlog(self, issues: list) -> T_resp_json:
"""
if not isinstance(issues, list):
raise ValueError("`issues` param should be List of Issue Keys")
url = "/rest/agile/1.0/backlog/issue"
resource = "backlog/issue"
url = self.get_agile_resource_url(resource)
data = dict(issues=issues)
return self.post(url, data=data)

Expand All @@ -5021,7 +5035,8 @@ def get_agile_board_by_filter_id(self, filter_id: T_id) -> T_resp_json:
Gets an agile board by the filter id
:param filter_id: int, str
"""
url = f"rest/agile/1.0/board/filter/{filter_id}"
resource = f"board/filter/{filter_id}"
url = self.get_agile_resource_url(resource)
return self.get(url)

# /rest/agile/1.0/board
Expand All @@ -5033,10 +5048,11 @@ def create_agile_board(self, name: str, type: str, filter_id: T_id, location: Op
:param filter_id: int
:param location: dict, Optional. Only specify this for Jira Cloud!
"""
resource = "board"
url = self.get_agile_resource_url(resource)
data: dict = {"name": name, "type": type, "filterId": filter_id}
if location:
data["location"] = location
url = "rest/agile/1.0/board"
return self.post(url, data=data)

def get_all_agile_boards(
Expand All @@ -5056,7 +5072,8 @@ def get_all_agile_boards(
:param limit:
:return:
"""
url = "rest/agile/1.0/board"
resource = "board"
url = self.get_agile_resource_url(resource)
params: dict = {}
if board_name:
params["name"] = board_name
Expand All @@ -5077,7 +5094,8 @@ def delete_agile_board(self, board_id: T_id) -> T_resp_json:
:param board_id:
:return:
"""
url = f"rest/agile/1.0/board/{str(board_id)}"
resource = f"board/{board_id}"
url = self.get_agile_resource_url(resource)
return self.delete(url)

def get_agile_board(self, board_id: T_id) -> T_resp_json:
Expand All @@ -5086,7 +5104,8 @@ def get_agile_board(self, board_id: T_id) -> T_resp_json:
:param board_id:
:return:
"""
url = f"rest/agile/1.0/board/{str(board_id)}"
resource = f"board/{board_id}"
url = self.get_agile_resource_url(resource)
return self.get(url)

def get_issues_for_backlog(self, board_id: T_id) -> T_resp_json:
Expand All @@ -5099,7 +5118,8 @@ def get_issues_for_backlog(self, board_id: T_id) -> T_resp_json:
By default, the returned issues are ordered by rank.
:param board_id: int, str
"""
url = f"rest/agile/1.0/board/{board_id}/backlog"
resource = f"board/{board_id}/backlog"
url = self.get_agile_resource_url(resource)
return self.get(url)

def get_agile_board_configuration(self, board_id: T_id) -> T_resp_json:
Expand All @@ -5126,7 +5146,8 @@ def get_agile_board_configuration(self, board_id: T_id) -> T_resp_json:
:param board_id:
:return:
"""
url = f"rest/agile/1.0/board/{str(board_id)}/configuration"
resource = f"board/{board_id}/configuration"
url = self.get_agile_resource_url(resource)
return self.get(url)

def get_issues_for_board(
Expand All @@ -5153,6 +5174,8 @@ def get_issues_for_board(
:param expand: OPTIONAL: expand the search result
:return:
"""
resource = f"board/{board_id}/issue"
url = self.get_agile_resource_url(resource)
params: dict = {}
if start is not None:
params["startAt"] = int(start)
Expand All @@ -5167,7 +5190,6 @@ def get_issues_for_board(
if expand is not None:
params["expand"] = expand

url = f"rest/agile/1.0/board/{board_id}/issue"
return self.get(url, params=params)

# /rest/agile/1.0/board/{boardId}/epic
Expand All @@ -5190,7 +5212,8 @@ def get_epics(
See the 'Pagination' section at the top of this page for more details.
:return:
"""
url = f"rest/agile/1.0/board/{board_id}/epic"
resource = f"board/{board_id}/epic"
url = self.get_agile_resource_url(resource)
params: dict = {}
if done:
params["done"] = done
Expand Down Expand Up @@ -5236,7 +5259,8 @@ def get_issues_for_epic(
If you exceed this limit, your results will be truncated.
:return:
"""
url = f"/rest/agile/1.0/board/{board_id}/epic/{epic_id}/issue"
resource = f"board/{board_id}/epic/{epic_id}/issue"
url = self.get_agile_resource_url(resource)
params: dict = {}
if jql:
params["jql"] = jql
Expand Down Expand Up @@ -5285,7 +5309,8 @@ def get_issues_without_epic(
If you exceed this limit, your results will be truncated.
:return:
"""
url = f"/rest/agile/1.0/board/{board_id}/epic/none/issue"
resource = f"board/{board_id}/epic/none/issue"
url = self.get_agile_resource_url(resource)
params: dict = {}
if jql:
params["jql"] = jql
Expand Down Expand Up @@ -5321,7 +5346,8 @@ def get_all_projects_associated_with_board(self, board_id: T_id, start: int = 0,
See the 'Pagination' section at the top of this page for more details
:return:
"""
url = f"/rest/agile/1.0/board/{board_id}/project"
resource = f"board/{board_id}/project"
url = self.get_agile_resource_url(resource)
params: dict = {}
if start:
params["startAt"] = start
Expand All @@ -5336,7 +5362,8 @@ def get_agile_board_properties(self, board_id: T_id) -> T_resp_json:
The user who retrieves the property keys is required to have permissions to view the board.
:param board_id: int, str
"""
url = f"rest/agile/1.0/board/{board_id}/properties"
resource = f"board/{board_id}/properties"
url = self.get_agile_resource_url(resource)
return self.get(url)

def set_agile_board_property(self, board_id: T_id, property_key: str) -> T_resp_json:
Expand All @@ -5349,7 +5376,8 @@ def set_agile_board_property(self, board_id: T_id, property_key: str) -> T_resp_
:param property_key:
:return:
"""
url = f"/rest/agile/1.0/board/{board_id}/properties/{property_key}"
resource = f"board/{board_id}/properties/{property_key}"
url = self.get_agile_resource_url(resource)
return self.put(url)

def get_agile_board_property(self, board_id: T_id, property_key: str) -> T_resp_json:
Expand All @@ -5360,7 +5388,8 @@ def get_agile_board_property(self, board_id: T_id, property_key: str) -> T_resp_
:param property_key:
:return:
"""
url = f"/rest/agile/1.0/board/{board_id}/properties/{property_key}"
resource = f"board/{board_id}/properties/{property_key}"
url = self.get_agile_resource_url(resource)
return self.get(url)

def delete_agile_board_property(self, board_id: T_id, property_key: str) -> T_resp_json:
Expand All @@ -5371,7 +5400,8 @@ def delete_agile_board_property(self, board_id: T_id, property_key: str) -> T_re
:param property_key:
:return:
"""
url = f"/rest/agile/1.0/board/{board_id}/properties/{property_key}"
resource = f"board/{board_id}/properties/{property_key}"
url = self.get_agile_resource_url(resource)
return self.delete(url)

# /rest/agile/1.0/board/{boardId}/settings/refined-velocity
Expand All @@ -5381,7 +5411,8 @@ def get_agile_board_refined_velocity(self, board_id: T_id) -> T_resp_json:
:param board_id:
:return:
"""
url = f"/rest/agile/1.0/board/{board_id}/settings/refined-velocity"
resource = f"board/{board_id}/settings/refined-velocity"
url = self.get_agile_resource_url(resource)
return self.get(url)

def set_agile_board_refined_velocity(self, board_id: T_id, data: dict) -> T_resp_json:
Expand All @@ -5391,7 +5422,8 @@ def set_agile_board_refined_velocity(self, board_id: T_id, data: dict) -> T_resp
:param data:
:return:
"""
url = f"/rest/agile/1.0/board/{board_id}/settings/refined-velocity"
resource = f"board/{board_id}/settings/refined-velocity"
url = self.get_agile_resource_url(resource)
return self.put(url, data=data)

# /rest/agile/1.0/board/{boardId}/sprint
Expand All @@ -5414,14 +5446,15 @@ def get_all_sprints_from_board(
See the 'Pagination' section at the top of this page for more details.
:return:
"""
resource = f"board/{board_id}/sprint"
url = self.get_agile_resource_url(resource)
params: dict = {}
if start:
params["startAt"] = start
if limit:
params["maxResults"] = limit
if state:
params["state"] = state
url = f"rest/agile/1.0/board/{board_id}/sprint"
return self.get(url, params=params)

@deprecated(version="3.42.0", reason="Use get_all_sprints_from_board instead")
Expand Down Expand Up @@ -5472,7 +5505,8 @@ def get_all_issues_for_sprint_in_board(
'jira.search.views.default.max' in your JIRA instance.
If you exceed this limit, your results will be truncated.
"""
url = f"/rest/agile/1.0/board/{board_id}/sprint/{sprint_id}/issue"
resource = f"board/{board_id}/sprint/{sprint_id}/issue"
url = self.get_agile_resource_url(resource)
params: dict = {}
if jql:
params["jql"] = jql
Expand Down Expand Up @@ -5510,14 +5544,15 @@ def get_all_versions_from_board(
See the 'Pagination' section at the top of this page for more details.
:return:
"""
resource = f"board/{board_id}/version"
url = self.get_agile_resource_url(resource)
params: dict = {}
if released:
params["released"] = released
if start:
params["startAt"] = start
if limit:
params["maxResults"] = limit
url = f"rest/agile/1.0/board/{board_id}/version"
return self.get(url, params=params)

def create_sprint(
Expand All @@ -5544,7 +5579,8 @@ def create_sprint(
https://docs.atlassian.com/jira-software/REST/8.9.0/#agile/1.0/sprint
isoformat can be created with datetime.datetime.isoformat()
"""
url = "/rest/agile/1.0/sprint"
resource = "sprint"
url = self.get_agile_resource_url(resource)
data = dict(name=name, originBoardId=board_id)
if start_date:
data["startDate"] = start_date
Expand Down Expand Up @@ -5580,7 +5616,8 @@ def get_sprint(self, sprint_id: T_id) -> T_resp_json:
:param sprint_id:
:return:
"""
url = f"rest/agile/1.0/sprint/{sprint_id}"
resource = f"sprint/{sprint_id}"
url = self.get_agile_resource_url(resource)
return self.get(url)

def rename_sprint(self, sprint_id: T_id, name: str, start_date: str, end_date: str) -> T_resp_json:
Expand All @@ -5592,8 +5629,10 @@ def rename_sprint(self, sprint_id: T_id, name: str, start_date: str, end_date: s
:param end_date:
:return:
"""
resource = f"sprint/{sprint_id}"
url = self.get_agile_resource_url(resource, legacy_api=True)
return self.put(
f"rest/greenhopper/1.0/sprint/{sprint_id}",
url,
data={"name": name, "startDate": start_date, "endDate": end_date},
)

Expand All @@ -5605,7 +5644,9 @@ def delete_sprint(self, sprint_id: T_id) -> T_resp_json:
:param sprint_id:
:return:
"""
return self.delete(f"rest/agile/1.0/sprint/{sprint_id}")
resource = f"sprint/{sprint_id}"
url = self.get_agile_resource_url(resource)
return self.delete(url)

def update_partially_sprint(self, sprint_id: T_id, data: dict) -> T_resp_json:
"""
Expand All @@ -5625,7 +5666,9 @@ def update_partially_sprint(self, sprint_id: T_id, data: dict) -> T_resp_json:
:param data: { "name": "new name"}
:return:
"""
return self.post(f"rest/agile/1.0/sprint/{sprint_id}", data=data)
resource = f"sprint/{sprint_id}"
url = self.get_agile_resource_url(resource)
return self.post(url, data=data)

def get_sprint_issues(self, sprint_id: T_id, start: T_id, limit: T_id) -> T_resp_json:
"""
Expand All @@ -5644,12 +5687,13 @@ def get_sprint_issues(self, sprint_id: T_id, start: T_id, limit: T_id) -> T_resp
If you exceed this limit, your results will be truncated.
:return:
"""
resource = f"sprint/{sprint_id}/issue"
url = self.get_agile_resource_url(resource)
params: dict = {}
if start:
params["startAt"] = start
if limit:
params["maxResults"] = limit
url = f"rest/agile/1.0/sprint/{sprint_id}/issue"
return self.get(url, params=params)

def update_rank(self, issues_to_rank: list, rank_before: str, customfield_number: T_id) -> T_resp_json:
Expand All @@ -5660,9 +5704,11 @@ def update_rank(self, issues_to_rank: list, rank_before: str, customfield_number
:param customfield_number: The number of the custom field Rank
:return:
"""
resource = "issue/rank"
url = self.get_agile_resource_url(resource)

return self.put(
"rest/agile/1.0/issue/rank",
url,
data={
"issues": issues_to_rank,
"rankBeforeIssue": rank_before,
Expand Down Expand Up @@ -5698,7 +5744,8 @@ def flag_issue(self, issue_keys: List[T_id], flag: bool = True) -> T_resp_json:
:return: POST request response.
:rtype: dict
"""
url = "rest/greenhopper/1.0/xboard/issue/flag/flag.json"
resource = f"xboard/issue/flag/flag.json"
url = self.get_agile_resource_url(resource, legacy_api=True)
data = {"issueKeys": issue_keys, "flag": flag}
return self.post(url, data)

Expand Down
Loading