Skip to content

Commit

Permalink
Smooth rough edges in story point syncing
Browse files Browse the repository at this point in the history
  • Loading branch information
webbnh committed Nov 16, 2024
1 parent 29071b9 commit 98a4e57
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 66 deletions.
73 changes: 48 additions & 25 deletions sync2jira/upstream_issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,37 +320,47 @@ 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)
item_nodes = project_node['fieldValues']['nodes'] if project_node else []
for item in item_nodes:
gh_field_name = item.get('fieldName', {}).get('name')
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 ValueError as err:
log.debug(
"Error while processing storypoints for issue %s/%s#%s: %s",
orgname, reponame, issuenumber, err)
except KeyError as err:
log.debug("Error fetching %s!r from GitHub %s/%s#%s: %s",
log.debug("Error fetching %s!r from GitHub issue %s/%s#%s: %s",
gh_field_name, orgname, reponame, issuenumber, err)
continue

Expand All @@ -368,25 +378,38 @@ 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:
# If there is exactly one project item, return it. Even if it doesn't
# match the configured project, having one is better than having none.
if len(project_items) == 1:
if project_number and str(project_items[0]['project']['number']) != project_number:
log.debug(
"Issue %s#%s is not associated with the configured project (%s),"
" using %s instead.",
upstream, issue_number, project_number,
project_items[0]['project']['number'])
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.
# 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])
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 @@ -615,47 +615,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 node_count == 1 else
nodes[1] if project == 2 else
None)
self.assertEqual(result, expected_result)

0 comments on commit 98a4e57

Please sign in to comment.