Skip to content

Commit

Permalink
Merge pull request #237 from webbnh/storypoint-touchup
Browse files Browse the repository at this point in the history
Storypoint touchup
  • Loading branch information
webbnh authored Nov 25, 2024
2 parents 70736d8 + 2506d72 commit 1aa6e2b
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 80 deletions.
13 changes: 5 additions & 8 deletions sync2jira/downstream_issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -578,12 +578,10 @@ def _get_preferred_issue_types(config, issue):
# 'enhancement': 'Story'
# }
type_list = []
log.debug(config)

map = config['sync2jira'].get('map', {})
conf = map.get('github', {}).get(issue.upstream, {})

log.debug(conf)
# we consider the issue_types mapping if it exists. If it does, exclude all other logic.
if 'issue_types' in conf:
for tag, issue_type in conf['issue_types'].items():
Expand Down Expand Up @@ -616,11 +614,6 @@ def _create_jira_issue(client, issue, config):
:returns: Returns JIRA issue that was created
:rtype: jira.resources.Issue
"""
log.info("Creating %r issue for %r", issue.downstream, issue)
if config['sync2jira']['testing']:
log.info("Testing flag is true. Skipping actual creation.")
return

custom_fields = issue.downstream.get('custom_fields', {})
preferred_types = _get_preferred_issue_types(config, issue)
description = _build_description(issue)
Expand All @@ -646,7 +639,11 @@ def _create_jira_issue(client, issue, config):
if 'labels' in issue.downstream.keys():
kwargs['labels'] = issue.downstream['labels']

log.info("Creating issue.")
log.info("Creating issue for %r: %r", issue, kwargs)
if config['sync2jira']['testing']:
log.info("Testing flag is true. Skipping actual creation.")
return

downstream = client.create_issue(**kwargs)

# Add Epic link, QA, EXD-Service field if present
Expand Down
80 changes: 49 additions & 31 deletions sync2jira/upstream_issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,39 +325,46 @@ def github_issues(upstream, config):

issue_updates = config['sync2jira']['map']['github'][upstream].get('issue_updates', {})

if 'github_project_fields' in issue_updates:
if '/pull/' in issue.get('html_url', ''):
log.debug(
"Issue %s/%s#%s is a pull request; skipping project fields",
orgname, reponame, issue['number'])
elif 'github_project_fields' in issue_updates:
issue['storypoints'] = None
issue['priority'] = ''
issuenumber = issue['number']
github_project_fields = config['sync2jira']['map']['github'][upstream]['github_project_fields']
variables = {"orgname": orgname, "reponame": reponame, "issuenumber": issuenumber}
response = requests.post(graphqlurl, headers=headers, json={"query": ghquery, "variables": variables})
if response.status_code != 200:
log.debug("Error while fetching %s/%s/Issue#%s: %s", orgname, reponame, issuenumber, response.text)
log.debug("HTTP error while fetching issue %s/%s#%s: %s",
orgname, reponame, issuenumber, response.text)
continue
data = response.json()
gh_issue = data['data']['repository']['issue']
if not gh_issue:
log.debug("Error fetching issue from GitHub: %s. org: %s, repo: %s", response.text, orgname, reponame)
log.debug("GitHub error while fetching issue %s/%s#%s: %s",
orgname, reponame, issuenumber, response.text)
continue
gh_field_name = ''
try:
currentProjectNode = _get_current_project_node(upstream, project_number, issuenumber, gh_issue)
if not currentProjectNode:
continue
for item in currentProjectNode['fieldValues']['nodes']:
if 'fieldName' in item:
gh_field_name = item['fieldName'].get('name')
if 'priority' in github_project_fields\
and gh_field_name == github_project_fields['priority']['gh_field']:
issue['priority'] = item.get('name')
if 'storypoints' in github_project_fields\
and gh_field_name == github_project_fields['storypoints']['gh_field']:
project_node = _get_current_project_node(
upstream, project_number, issuenumber, gh_issue)
if project_node:
item_nodes = project_node.get('fieldValues', {}).get('nodes', {})
for item in item_nodes:
gh_field_name = item.get('fieldName', {}).get('name')
if not gh_field_name:
continue
prio_field = github_project_fields.get('priority', {}).get('gh_field')
if gh_field_name == prio_field:
issue['priority'] = item.get('name')
sp_field = github_project_fields.get('storypoints', {}).get('gh_field')
if gh_field_name == sp_field:
try:
issue['storypoints'] = int(item['number'])
except KeyError as err:
log.debug("Error fetching %s!r from GitHub %s/%s#%s: %s",
gh_field_name, orgname, reponame, issuenumber, err)
continue
except (ValueError, KeyError) as err:
log.debug(
"Error while processing storypoints for issue %s/%s#%s: %s",
orgname, reponame, issuenumber, err)

final_issues.append(issue)

Expand All @@ -373,25 +380,36 @@ def _get_current_project_node(upstream, project_number, issue_number, gh_issue):
project_items = gh_issue['projectItems']['nodes']
# If there are no project items, there is nothing to return.
if len(project_items) == 0:
log.debug("Issue %s/%s is not associated with any project", upstream, issue_number)
log.debug("Issue %s#%s is not associated with any project",
upstream, issue_number)
return None
# If there is exactly one project item, return it if we don't have a configured project.
if not project_number and len(project_items) == 1:
return project_items[0]
# There are multiple projects associated with this issue; if we don't have a configured
# project, then we don't know which one to return, so return none.

if not project_number:
prj = ", ".join([":".join([x['project']['url'], x['project']['title']]) for x in project_items])
# We don't have a configured project. If there is exactly one project
# item, we'll assume it's the right one and return it.
if len(project_items) == 1:
return project_items[0]

# There are multiple projects associated with this issue; since we
# don't have a configured project, we don't know which one to return,
# so return none.
prj = (f"{x['project']['url']}: {x['project']['title']}" for x in project_items)
log.debug(
"Project number is not configured, and the issue %s/%s is associated with more than one project: %s",
upstream, issue_number, prj)
"Project number is not configured, and the issue %s#%s"
" is associated with more than one project: %s",
upstream, issue_number, ", ".join(prj))
return None
# Return the associated project which matches the configured project if we can find it.

# Return the associated project which matches the configured project if we
# can find it.
for item in project_items:
if item['project']['number'] == project_number:
return item

log.debug("Issue is not associated with the configured project, but other associations exist.")
log.debug(
"Issue %s#%s is associated with multiple projects, "
"but none match the configured project.",
upstream, issue_number)
return None


Expand Down
61 changes: 20 additions & 41 deletions tests/test_upstream_issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -670,47 +670,26 @@ def test_fetch_github_data(self, mock_requests):
headers='mock_headers'
)

def test_get_current_project_node_no_projects(self):
"""
This function tests '_get_current_project_node' where there are no project nodes
"""
gh_issue = {'projectItems': {'nodes': []}}
result = u._get_current_project_node('org/repo', 1, 'mock_number', gh_issue)
self.assertIsNone(result)

def test_get_current_project_node_single_project_no_project_number(self):
"""
This function tests '_get_current_project_node' where there is a single project
node and no project number
"""
gh_issue = {'projectItems': {'nodes': [{'project': {'number': 1}}]}}
result = u._get_current_project_node('org/repo', None, 'mock_number', gh_issue)
self.assertEqual(result, {'project': {'number': 1}})
def test_get_current_project_node(self):
"""This function tests '_get_current_project_node' in a matrix of cases.
def test_get_current_project_node_multiple_projects_no_project_number(self):
"""
This function tests '_get_current_project_node' where there are multiple project nodes
and no project number
It tests issues with zero, one, and two associated projects when the
call is made with no configured project, with a project which matches
none of the associated projects, and with a project which matches one.
"""
gh_issue = {'projectItems': {'nodes': [
nodes = [
{'project': {'number': 1, 'url': 'url1', 'title': 'title1'}},
{'project': {'number': 2, 'url': 'url2', 'title': 'title2'}}]}}
result = u._get_current_project_node('org/repo', None, 'mock_number', gh_issue)
self.assertIsNone(result)

def test_get_current_project_node_project_not_associated(self):
"""
This function tests '_get_current_project_node' where the issue is not associated with
the configured project
"""
gh_issue = {'projectItems': {'nodes': [{'project': {'number': 1}}]}}
result = u._get_current_project_node('org/repo', 2, 'mock_number', gh_issue)
self.assertIsNone(result)

def test_get_current_project_node_success(self):
"""
This function tests '_get_current_project_node' where everything goes smoothly
"""
gh_issue = {'projectItems': {'nodes': [{'project': {'number': 1}}, {'project': {'number': 2}}]}}
result = u._get_current_project_node('org/repo', 2, 'mock_number', gh_issue)
self.assertEqual(result, {'project': {'number': 2}})
{'project': {'number': 2, 'url': 'url2', 'title': 'title2'}}]
projects = [None, 2, 5]

for project in projects:
for node_count in range(len(nodes)+1):
gh_issue = {'projectItems': {'nodes': nodes[:node_count]}}
result = u._get_current_project_node(
'org/repo', project, 'mock_number', gh_issue)
expected_result = (
None if node_count == 0 else
(nodes[0] if project is None else None) if node_count == 1 else
nodes[1] if project == 2 else
None)
self.assertEqual(result, expected_result)

0 comments on commit 1aa6e2b

Please sign in to comment.