Skip to content

Commit

Permalink
provide extra jql query functionality (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
studioj authored Jun 5, 2021
1 parent 5b3c5d5 commit 9f492d2
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 19 deletions.
42 changes: 25 additions & 17 deletions jira_agile_toolbox/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,16 @@ def __init__(self, jira_client):
self._story_points_custom_field = None
self._story_points_custom_field_name = "Story Points"

def get_storypoints_from_epic(self, epic):
def get_storypoints_from_epic(self, epic, jql_query=""):
"""
searches for the epic and returns the number of storypoints as a dict
:param epic: and epic key as a string or the epic as a jira.Issue
:type epic: str jira.Issue
:return: a dictionary containing total story points
:rtype: dict
:param jql_query: a query of the form 'project in (PROJ001,PROJ002)' or 'issuetype not in ('Task') AND status != Closed' will be AND'ed after the autogenerated search
:type jql_query: str
``Example``
Expand All @@ -56,7 +58,7 @@ def get_storypoints_from_epic(self, epic):
self._story_points_custom_field = self._get_custom_field_from_name(self._story_points_custom_field_name)

fields_to_get = [self._story_points_custom_field, "status"]
issues_in_epic = self.get_all_issues_in_epic(epic, fields_to_get)
issues_in_epic = self.get_all_issues_in_epic(epic, fields_to_get, jql_query=jql_query)
sum_of_story_points = sum(
int(getattr(issue.fields, self._story_points_custom_field, 0))
for issue in issues_in_epic
Expand All @@ -73,10 +75,19 @@ def get_storypoints_from_epic(self, epic):
sum_of_story_points_per_state["total"] = sum_of_story_points
return sum_of_story_points_per_state

def get_all_issues_in_epic(self, epic, fields=None):
def get_all_issues_in_epic(self, epic, fields=None, jql_query=""):
"""
gets all 'Issues in Epic' as a list
:param epic: and epic key as a string or the epic as a jira.Issue
:type epic: str jira.Issue
:param fields: a string or list of strings to limit the fields to get this helps to lower the amount of data to be sent around
:type fields: str list
:param jql_query: a query of the form 'project in (PROJ001,PROJ002)' or 'issuetype not in ('Task') AND status != Closed' will be AND'ed after the autogenerated search
:type jql_query: str
:return: a list of jira.Issues
:rtype: list
``Example``
.. code-block:: python
Expand All @@ -87,20 +98,13 @@ def get_all_issues_in_epic(self, epic, fields=None):
>>> tb = JiraAgileToolBox(my_jira_client)
>>> tb.get_all_issues_in_epic("JAT-001")
[<JIRA Issue: key='JAT-002', id='67'>, <JIRA Issue: key='JAT-003', id='68'>, <JIRA Issue: key='JAT-004', id='69'>]
:param epic: and epic key as a string or the epic as a jira.Issue
:type epic: str jira.Issue
:param fields: a string or list of strings to limit the fields to get this helps to lower the amount of data to be sent around
:type fields: str list
:return: a list of jira.Issues
:rtype: list
"""
fields_to_get = self._input_validation_fields(fields)
epic_key = epic.key if isinstance(epic, jira.Issue) else epic
jql_query = f"'Epic Link' = {epic_key}"
jql_query_to_find_the_issues = f"'Epic Link' = {epic_key} AND {jql_query}" if jql_query else f"'Epic Link' = {epic_key}"
if fields_to_get:
return self._jira_client.search_issues(jql_query, fields=fields_to_get, maxResults=0)
return self._jira_client.search_issues(jql_query, maxResults=0)
return self._jira_client.search_issues(jql_query_to_find_the_issues, fields=fields_to_get, maxResults=0)
return self._jira_client.search_issues(jql_query_to_find_the_issues, maxResults=0)

def _input_validation_fields(self, fields):
fields_to_get = []
Expand Down Expand Up @@ -203,7 +207,7 @@ def rank_issues_at_top_of_project(self, ranked_list, project):
self.rank_issues_by_list(ranked_list, issue)
break

def add_labels_to_all_sub_items_of_epic(self, epic, labels, keep_already_present=True):
def add_labels_to_all_sub_items_of_epic(self, epic, labels, keep_already_present=True, jql_query=""):
"""
adds labels to all 'Issues in Epic'
Expand All @@ -213,6 +217,8 @@ def add_labels_to_all_sub_items_of_epic(self, epic, labels, keep_already_present
:type labels: str list
:param keep_already_present: if this is set to False already present labels will be overwritten (defaults to True)
:type keep_already_present: bool
:param jql_query: a query of the form 'project in (PROJ001,PROJ002)' or 'issuetype not in ('Task') AND status != Closed' will be AND'ed after the autogenerated search
:type jql_query: str
``Example``
Expand All @@ -227,7 +233,7 @@ def add_labels_to_all_sub_items_of_epic(self, epic, labels, keep_already_present
this will append the "label_to_set" to all existing labels of all Issues in Epic
"""
labels_to_set = self._input_validation_labels(labels)
items_to_update = self.get_all_issues_in_epic(epic, fields=["labels"])
items_to_update = self.get_all_issues_in_epic(epic, fields=["labels"], jql_query=jql_query)
for item in items_to_update:
if keep_already_present:
for label in labels_to_set:
Expand All @@ -253,14 +259,16 @@ def _input_validation_labels(self, labels):
raise ValueError(bad_input)
return labels_to_set

def copy_fix_version_from_epic_to_all_items_in_epic(self, epic, keep_already_present=True):
def copy_fix_version_from_epic_to_all_items_in_epic(self, epic, keep_already_present=True, jql_query=""):
"""
copies fixVersions from the epic to all 'Issues in Epic'
:param epic: and epic key as a string or the epic as a jira.Issue
:type epic: str jira.Issue
:param keep_already_present: if this is set to False already present fixVersions will be overwritten (defaults to True)
:type keep_already_present: bool
:param jql_query: a query of the form 'project in (PROJ001,PROJ002)' or 'issuetype not in ('Task') AND status != Closed' will be AND'ed after the autogenerated search
:type jql_query: str
``Example``
Expand All @@ -279,7 +287,7 @@ def copy_fix_version_from_epic_to_all_items_in_epic(self, epic, keep_already_pre
"""
jira_epic = epic if isinstance(epic, jira.Issue) else self._jira_client.issue(epic)
versions = [version.raw for version in jira_epic.fields.fixVersions]
for issue in self.get_all_issues_in_epic(jira_epic, fields=["fixVersions"]):
for issue in self.get_all_issues_in_epic(jira_epic, fields=["fixVersions"], jql_query=jql_query):

if keep_already_present:
for version in versions:
Expand Down
78 changes: 76 additions & 2 deletions tests/test_epic_interactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,31 @@ def test_get_story_points_from_epic_looks_in_jira_for_all_children_of_the_epic(s
jat = JiraAgileToolBox(self.jira_client)

# When
jat.get_storypoints_from_epic("PROJ001-001")
epic = "PROJ001-001"
jat.get_storypoints_from_epic(epic)

# Then
self.jira_client.search_issues.assert_called_with(f"'Epic Link' = {epic}", fields=["customfield_10282", "status"], maxResults=0)

def test_get_story_points_from_epic_looks_in_jira_for_all_children_of_the_epic_passes_on_a_jql_query(self):
# Given

self.jira_client.fields.return_value = DEFAULT_FIELDS_RETURN_VALUE
self.jira_client.search_issues.return_value = [
MockedJiraIssue(story_points=0),
MockedJiraIssue(story_points=1),
MockedJiraIssue(story_points=2),
]
jat = JiraAgileToolBox(self.jira_client)
epic = "PROJ001-001"
jql_query = "project in (PROJ001,PROJ002)"

# When
jat.get_storypoints_from_epic(epic, jql_query=jql_query)

# Then
self.jira_client.search_issues.assert_called_with(
"'Epic Link' = " + "PROJ001-001", fields=["customfield_10282", "status"], maxResults=0
f"'Epic Link' = {epic} AND {jql_query}", fields=["customfield_10282", "status"], maxResults=0
)

def test_get_story_points_from_epic_calculates_the_total_story_pointS_for_3_issues(self):
Expand Down Expand Up @@ -270,6 +290,25 @@ def test_get_issues_from_epic_takes_a_string_field_or_a_list_of_fields(self):
# Then
self.jira_client.search_issues.assert_called_with(f"'Epic Link' = PROJ001-001", fields=["a_specific_field"], maxResults=0)

def test_get_issues_from_epic_allows_to_filter_an_extra_jql_query(self):
# Given

self.jira_client.fields.return_value = DEFAULT_FIELDS_RETURN_VALUE
self.jira_client.search_issues.return_value = [
MockedJiraIssue(story_points=0),
MockedJiraIssue(story_points=1),
MockedJiraIssue(story_points=2),
]
jat = JiraAgileToolBox(self.jira_client)

# When
jat.get_all_issues_in_epic("PROJ001-001", fields="a_specific_field", jql_query="project in (PROJ001,PROJ002)")

# Then
self.jira_client.search_issues.assert_called_with(
f"'Epic Link' = PROJ001-001 AND project in (PROJ001,PROJ002)", fields=["a_specific_field"], maxResults=0
)


class TestSetVersionNumberForAllItemsInEpic(TestCase):
def setUp(self) -> None:
Expand All @@ -292,6 +331,41 @@ def test_copy_fix_version_from_epic_to_all_items_in_epic(self):
# Then
sub_issue1.add_field_value.assert_called_with("fixVersions", version1.raw)

def test_copy_fix_version_from_epic_to_all_items_in_epic_searches_for_the_epic(self):
# Given
sub_issue1 = MockedJiraIssue(story_points=0)
version1 = Mock(spec=jira.resources.Version)
version1.raw = VERSION_RAW
epic = MockedJiraIssue()
epic.fields.fixVersions = [version1]
epic.key = "PROJ001-001"
self.jira_client.search_issues.return_value = [sub_issue1]
jat = JiraAgileToolBox(self.jira_client)

# When
jat.copy_fix_version_from_epic_to_all_items_in_epic(epic)

# Then
self.jira_client.search_issues.assert_called_with(f"'Epic Link' = {epic.key}", fields=["fixVersions"], maxResults=0)

def test_copy_fix_version_from_epic_to_all_items_in_epic_searches_for_the_epic_and_passes_on_extra_jql_query(self):
# Given
sub_issue1 = MockedJiraIssue(story_points=0)
version1 = Mock(spec=jira.resources.Version)
version1.raw = VERSION_RAW
epic = MockedJiraIssue()
epic.fields.fixVersions = [version1]
epic.key = "PROJ001-001"
self.jira_client.search_issues.return_value = [sub_issue1]
jat = JiraAgileToolBox(self.jira_client)
jql_query = "project in (PROJ001,PROJ002)"

# When
jat.copy_fix_version_from_epic_to_all_items_in_epic(epic, jql_query=jql_query)

# Then
self.jira_client.search_issues.assert_called_with(f"'Epic Link' = {epic.key} AND {jql_query}", fields=["fixVersions"], maxResults=0)

def test_copy_fix_version_from_epic_to_all_items_in_epic_for_multiple_versions(self):
# Given
sub_issue1 = MockedJiraIssue(story_points=0)
Expand Down
17 changes: 17 additions & 0 deletions tests/test_label_interactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,23 @@ def test_setting_a_label_for_all_sub_items_retrieves_only_the_label_fields(self)
# Then
self.jira_client.search_issues.assert_called_with(f"'Epic Link' = PROJ001-001", fields=["labels"], maxResults=0)

def test_setting_a_label_for_all_sub_items_passes_on_the_jql_query(self):
# Given
self.jira_client.fields.return_value = DEFAULT_FIELDS_RETURN_VALUE
sub_story = MockedJiraIssue()
self.jira_client.search_issues.return_value = [
sub_story,
]
jat = JiraAgileToolBox(self.jira_client)

# When
epic = "PROJ001-001"
jql_query = "project in (PROJ001,PROJ002)"
jat.add_labels_to_all_sub_items_of_epic(epic, ["label_to_set"], jql_query=jql_query)

# Then
self.jira_client.search_issues.assert_called_with(f"'Epic Link' = {epic} AND {jql_query}", fields=["labels"], maxResults=0)

def test_setting_a_label_for_all_sub_items_will_remove_already_present_labels(self):
# Given
self.jira_client.fields.return_value = DEFAULT_FIELDS_RETURN_VALUE
Expand Down

0 comments on commit 9f492d2

Please sign in to comment.