Skip to content

Commit

Permalink
support for getting story points for epic object (#5)
Browse files Browse the repository at this point in the history
* support for getting story points for epic object
* moving version to git tag managed
  • Loading branch information
studioj authored Apr 4, 2021
1 parent 8aad1a8 commit 5fccd00
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 164 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
run: |
python -m pip install --upgrade pip
python -m pip install flake8 pytest build twine
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
python -m pip install -r requirements.txt
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
Expand Down
14 changes: 11 additions & 3 deletions jira_agile_toolbox/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
__version_info__ = (0, 0, 5)
__version__ = ".".join(map(str, __version_info__))
import jira
from pkg_resources import get_distribution, DistributionNotFound

try:
__version__ = get_distribution("package-name").version
except DistributionNotFound:
# package is not installed
pass


class JiraAgileToolBox(object):
Expand All @@ -25,8 +31,10 @@ def get_storypoints_from_epic(self, epic):
if not self._story_points_custom_field:
self._story_points_custom_field = self.get_custom_field_from_name(self._story_points_custom_field_name)

epic_key = epic.key if isinstance(epic, jira.Issue) else epic

issues_in_epic = self._jira_client.search_issues(
"'Epic Link' = " + epic, fields=[self._story_points_custom_field, "status"], maxResults=0
"'Epic Link' = " + epic_key, fields=[self._story_points_custom_field, "status"], maxResults=0
)
sum_of_story_points = sum(
int(getattr(issue.fields, self._story_points_custom_field, 0))
Expand Down
Empty file added jira_agile_toolbox/version.py
Empty file.
3 changes: 2 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ description = Extends standard jira package with agile specific tooling for metr
long_description = file: README.md
long_description_content_type = text/markdown
url = https://github.com/studioj/jira-agile-toolbox
version = attr: jira_agile_toolbox.__version__
author = Jef Neefs
license = MIT
license_file = LICENSE
Expand Down Expand Up @@ -39,6 +38,8 @@ include_package_data = True
packages = find:
install_requires =
jira
setup_requires =
setuptools_scm
python_requires = >=3.6

[options.extras_require]
Expand Down
4 changes: 3 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from setuptools import setup

if __name__ == "__main__":
setup()
setup(
use_scm_version=True,
)
215 changes: 57 additions & 158 deletions tests/test_epic_interactions.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,35 @@
from unittest import TestCase
from unittest.mock import Mock, MagicMock

from jira_agile_toolbox import JiraAgileToolBox
import jira

from jira_agile_toolbox import JiraAgileToolBox

class MockedJiraIssue(object):
DEFAULT_FIELDS_RETURN_VALUE = [
{
"id": "customfield_11280",
"name": "Release Note",
"custom": True,
"orderable": True,
"navigable": True,
"searchable": True,
"clauseNames": ["cf[11280]", "Release Note"],
"schema": {"type": "option", "custom": "com.atlassian.jira.plugin.system.customfieldtypes:select", "customId": 11280},
},
{
"id": "customfield_10282",
"name": "Story Points",
"custom": True,
"orderable": True,
"navigable": True,
"searchable": True,
"clauseNames": ["cf[10282]", "Story Points"],
"schema": {"type": "number", "custom": "com.atlassian.jira.plugin.system.customfieldtypes:float", "customId": 10282},
},
]


class MockedJiraIssue(jira.Issue):
def __init__(self, story_points=None, status="Reported"):
self.fields = MagicMock()
self.fields.customfield_10282 = story_points
Expand All @@ -15,28 +40,7 @@ class TestEpicStoryPointRetrieval(TestCase):
def test_get_story_points_from_epic_returns_a_dict_containing_the_total_story_points(self):
# Given
jira_client = Mock()
jira_client.fields.return_value = [
{
"id": "customfield_11280",
"name": "Release Note",
"custom": True,
"orderable": True,
"navigable": True,
"searchable": True,
"clauseNames": ["cf[11280]", "Release Note"],
"schema": {"type": "option", "custom": "com.atlassian.jira.plugin.system.customfieldtypes:select", "customId": 11280},
},
{
"id": "customfield_10282",
"name": "Story Points",
"custom": True,
"orderable": True,
"navigable": True,
"searchable": True,
"clauseNames": ["cf[10282]", "Story Points"],
"schema": {"type": "number", "custom": "com.atlassian.jira.plugin.system.customfieldtypes:float", "customId": 10282},
},
]
jira_client.fields.return_value = DEFAULT_FIELDS_RETURN_VALUE
jira_client.search_issues.return_value = [
MockedJiraIssue(story_points=0),
MockedJiraIssue(story_points=1),
Expand All @@ -53,28 +57,7 @@ def test_get_story_points_from_epic_returns_a_dict_containing_the_total_story_po
def test_get_story_points_from_epic_looks_in_jira_for_all_children_of_the_epic(self):
# Given
jira_client = Mock()
jira_client.fields.return_value = [
{
"id": "customfield_11280",
"name": "Release Note",
"custom": True,
"orderable": True,
"navigable": True,
"searchable": True,
"clauseNames": ["cf[11280]", "Release Note"],
"schema": {"type": "option", "custom": "com.atlassian.jira.plugin.system.customfieldtypes:select", "customId": 11280},
},
{
"id": "customfield_10282",
"name": "Story Points",
"custom": True,
"orderable": True,
"navigable": True,
"searchable": True,
"clauseNames": ["cf[10282]", "Story Points"],
"schema": {"type": "number", "custom": "com.atlassian.jira.plugin.system.customfieldtypes:float", "customId": 10282},
},
]
jira_client.fields.return_value = DEFAULT_FIELDS_RETURN_VALUE
jira_client.search_issues.return_value = [
MockedJiraIssue(story_points=0),
MockedJiraIssue(story_points=1),
Expand All @@ -91,28 +74,7 @@ def test_get_story_points_from_epic_looks_in_jira_for_all_children_of_the_epic(s
def test_get_story_points_from_epic_calculates_the_total_story_pointS_for_3_issues(self):
# Given
jira_client = Mock()
jira_client.fields.return_value = [
{
"id": "customfield_11280",
"name": "Release Note",
"custom": True,
"orderable": True,
"navigable": True,
"searchable": True,
"clauseNames": ["cf[11280]", "Release Note"],
"schema": {"type": "option", "custom": "com.atlassian.jira.plugin.system.customfieldtypes:select", "customId": 11280},
},
{
"id": "customfield_10282",
"name": "Story Points",
"custom": True,
"orderable": True,
"navigable": True,
"searchable": True,
"clauseNames": ["cf[10282]", "Story Points"],
"schema": {"type": "number", "custom": "com.atlassian.jira.plugin.system.customfieldtypes:float", "customId": 10282},
},
]
jira_client.fields.return_value = DEFAULT_FIELDS_RETURN_VALUE
jira_client.search_issues.return_value = [
MockedJiraIssue(story_points=0),
MockedJiraIssue(story_points=1),
Expand All @@ -129,28 +91,7 @@ def test_get_story_points_from_epic_calculates_the_total_story_pointS_for_3_issu
def test_get_story_points_from_epic_calculates_the_total_story_pointS_for_300_issues(self):
# Given
jira_client = Mock()
jira_client.fields.return_value = [
{
"id": "customfield_11280",
"name": "Release Note",
"custom": True,
"orderable": True,
"navigable": True,
"searchable": True,
"clauseNames": ["cf[11280]", "Release Note"],
"schema": {"type": "option", "custom": "com.atlassian.jira.plugin.system.customfieldtypes:select", "customId": 11280},
},
{
"id": "customfield_10282",
"name": "Story Points",
"custom": True,
"orderable": True,
"navigable": True,
"searchable": True,
"clauseNames": ["cf[10282]", "Story Points"],
"schema": {"type": "number", "custom": "com.atlassian.jira.plugin.system.customfieldtypes:float", "customId": 10282},
},
]
jira_client.fields.return_value = DEFAULT_FIELDS_RETURN_VALUE
jira_client.search_issues.return_value = [
MockedJiraIssue(story_points=0),
MockedJiraIssue(story_points=1),
Expand All @@ -164,31 +105,10 @@ def test_get_story_points_from_epic_calculates_the_total_story_pointS_for_300_is
# Then
self.assertEqual(300, sps["total"])

def test_get_story_points_from_epic_calculates_the_total_of_zerostory_point_when_none_are_assigned(self):
def test_get_story_points_from_epic_calculates_the_total_of_zero_story_point_when_none_are_assigned(self):
# Given
jira_client = Mock()
jira_client.fields.return_value = [
{
"id": "customfield_11280",
"name": "Release Note",
"custom": True,
"orderable": True,
"navigable": True,
"searchable": True,
"clauseNames": ["cf[11280]", "Release Note"],
"schema": {"type": "option", "custom": "com.atlassian.jira.plugin.system.customfieldtypes:select", "customId": 11280},
},
{
"id": "customfield_10282",
"name": "Story Points",
"custom": True,
"orderable": True,
"navigable": True,
"searchable": True,
"clauseNames": ["cf[10282]", "Story Points"],
"schema": {"type": "number", "custom": "com.atlassian.jira.plugin.system.customfieldtypes:float", "customId": 10282},
},
]
jira_client.fields.return_value = DEFAULT_FIELDS_RETURN_VALUE
jira_client.search_issues.return_value = [
MockedJiraIssue(story_points=None),
MockedJiraIssue(),
Expand All @@ -205,28 +125,7 @@ def test_get_story_points_from_epic_calculates_the_total_of_zerostory_point_when
def test_get_story_points_from_epic_returns_a_dict_containing_the_found_statuses_as_keyword(self):
# Given
jira_client = Mock()
jira_client.fields.return_value = [
{
"id": "customfield_11280",
"name": "Release Note",
"custom": True,
"orderable": True,
"navigable": True,
"searchable": True,
"clauseNames": ["cf[11280]", "Release Note"],
"schema": {"type": "option", "custom": "com.atlassian.jira.plugin.system.customfieldtypes:select", "customId": 11280},
},
{
"id": "customfield_10282",
"name": "Story Points",
"custom": True,
"orderable": True,
"navigable": True,
"searchable": True,
"clauseNames": ["cf[10282]", "Story Points"],
"schema": {"type": "number", "custom": "com.atlassian.jira.plugin.system.customfieldtypes:float", "customId": 10282},
},
]
jira_client.fields.return_value = DEFAULT_FIELDS_RETURN_VALUE
jira_client.search_issues.return_value = [
MockedJiraIssue(0, "Reported"),
MockedJiraIssue(1, "Investigated"),
Expand All @@ -246,28 +145,7 @@ def test_get_story_points_from_epic_returns_a_dict_containing_the_found_statuses
def test_get_story_points_from_epic_returns_a_dict_containing_the_found_statuses_with_their_totals(self):
# Given
jira_client = Mock()
jira_client.fields.return_value = [
{
"id": "customfield_11280",
"name": "Release Note",
"custom": True,
"orderable": True,
"navigable": True,
"searchable": True,
"clauseNames": ["cf[11280]", "Release Note"],
"schema": {"type": "option", "custom": "com.atlassian.jira.plugin.system.customfieldtypes:select", "customId": 11280},
},
{
"id": "customfield_10282",
"name": "Story Points",
"custom": True,
"orderable": True,
"navigable": True,
"searchable": True,
"clauseNames": ["cf[10282]", "Story Points"],
"schema": {"type": "number", "custom": "com.atlassian.jira.plugin.system.customfieldtypes:float", "customId": 10282},
},
]
jira_client.fields.return_value = DEFAULT_FIELDS_RETURN_VALUE
jira_client.search_issues.return_value = [
MockedJiraIssue(0, "Reported"),
MockedJiraIssue(1, "Investigated"),
Expand All @@ -282,7 +160,7 @@ def test_get_story_points_from_epic_returns_a_dict_containing_the_found_statuses
# Then
self.assertEqual({"total": 3, "Reported": 0, "Investigated": 1, "Closed": 2}, result)

def test_if_storypoints_custom_field_is_unknown_we_get_itfirst_via_the_fields_getter(self):
def test_if_storypoints_custom_field_is_unknown_we_get_it_first_via_the_fields_getter(self):
# Given
jira_client = Mock()
jira_client.fields.return_value = [
Expand Down Expand Up @@ -323,3 +201,24 @@ def test_if_storypoints_custom_field_is_unknown_we_get_itfirst_via_the_fields_ge
jira_client.search_issues.assert_called_with("'Epic Link' = " + "PROJ001-001", fields=["customfield_10333", "status"], maxResults=0)

self.assertEqual({"total": 100, "Reported": 100}, result)

def test_if_storypoints_can_be_retrieved_from_a_jira_issue(self):
# Given
jira_client = Mock()
jira_client.fields.return_value = DEFAULT_FIELDS_RETURN_VALUE
mocked_jira_issue = MockedJiraIssue(story_points=100)
mocked_jira_issue2 = MockedJiraIssue()
mocked_jira_epic = MockedJiraIssue()
mocked_jira_epic.key = "PROJ001-001"
jira_client.search_issues.return_value = [mocked_jira_issue2, mocked_jira_issue]
jat = JiraAgileToolBox(jira_client)

# When
result = jat.get_storypoints_from_epic(mocked_jira_epic)

# Then
jira_client.fields.assert_called_once()

jira_client.search_issues.assert_called_with("'Epic Link' = " + "PROJ001-001", fields=["customfield_10282", "status"], maxResults=0)

self.assertEqual({"total": 100, "Reported": 100}, result)

0 comments on commit 5fccd00

Please sign in to comment.