From eb08b566e652604a1a6ea8e85b6187838e8e1bd2 Mon Sep 17 00:00:00 2001 From: sidpremkumar Date: Nov 18 2019 15:00:20 +0000 Subject: Add mapping support for FixVersion --- diff --git a/README.rst b/README.rst index 91809ef..4e577d0 100644 --- a/README.rst +++ b/README.rst @@ -26,7 +26,8 @@ ____________ Each project is accompanied by an 'updates' array as seen below:: 'Demo_project': {'project': 'PROJECT', 'component': 'COMP', - 'updates': [...], 'owner': 'project_owner_username', + 'updates': [...], 'mapping': [...], + 'owner': 'project_owner_username', 'default_status': 'start_status_for_issue' 'labels: ['tag1'..], 'qa-contact': 'some@some.com', 'epic-link': 'FACTORY-1234'}, @@ -55,6 +56,10 @@ JIRA issues:: Note: Overwrite set to True will ensure that upstream issue fields will clear downstream issue fields, overwrite set to False will never delete downstream issue fields only append. +The following can be added to the mapping array to specify a type of mapping between upstream/downstream fields:: + + {'fixVersion': 'Test XXX'} :: This will map the upstream milestone 'milestone' to downstream fixVersion 'Text milestone' + The optional owner field can be used to specify a username that should be used if the program cannot find a matching downstream user to assignee an issue too. The owner field will also be used to alert users if duplicate downstream issues exist. diff --git a/docs/source/config-file.rst b/docs/source/config-file.rst index 14e4e3f..50ee891 100644 --- a/docs/source/config-file.rst +++ b/docs/source/config-file.rst @@ -61,7 +61,7 @@ getting a JIRA client and failure email will be sent anytime the service fails. 'map': { 'pagure': { 'Demo_project': {'project': 'FACTORY', 'component': 'gitbz', - 'updates': [...], 'owner': 'jira_username'}, + 'updates': [...], 'mapping': [...], 'owner': 'jira_username'}, # 'koji': { 'project': 'BREW', 'component': None, }, }, 'github': { @@ -114,7 +114,12 @@ getting a JIRA client and failure email will be sent anytime the service fails. :Overwrite: Setting this to :code:`True` will ensure that Upstream (GitHub or Pagure) values will overwrite downstream ones (i.e. if its empty upstream it'll be empty downstream) :CUSTOM_TRANSITION: Setting this value will get Sync2Jira to automatially transition downstream tickets once their upstream counterparts get closed. Set this to whatever 'closed' means downstream. -* It is strongly encouraged for teams to use the :code:`owner` field. If configured, owners will be alerted if Sync2Jira finds dupliate downstream issues. +* You can add the following to the mapping array. This array will map an upstream field to the downstream counterpart with XXX replaced. + + * :code:`{'fixVersion': 'Test XXX'}` + * Maps upstream milestone (suppose it's called 'milesone') to downstream fixVersion with a mapping (for our example it would be 'Test milesone') + +* It is strongly encouraged for teams to use the :code:`owner` field. If configured, owners will be alerted if Sync2Jira finds duplicate downstream issues. Further the owner will be used as a default in case the program is unable to find a valid assignee. .. code-block:: python diff --git a/sync2jira/intermediary.py b/sync2jira/intermediary.py index 6b9dcab..5da6854 100644 --- a/sync2jira/intermediary.py +++ b/sync2jira/intermediary.py @@ -54,6 +54,7 @@ class Issue(object): @classmethod def from_pagure(cls, upstream, issue, config): base = config['sync2jira'].get('pagure_url', 'https://pagure.io') + upstream_source = 'pagure' comments = [] for comment in issue['comments']: # Only add comments that are not Metadata updates @@ -71,8 +72,15 @@ class Issue(object): 'changed': None }) + # Perform any mapping + mapping = config['sync2jira']['map'][upstream_source][upstream].get('mapping', []) + + # Check for fixVersion + if any('fixVersion' in item for item in mapping): + cls.map_fixVersion(cls, mapping, issue) + return Issue( - source='pagure', + source=upstream_source, title=issue['title'], url=base + '/%s/issue/%i' % (upstream, issue['id']), upstream=upstream, @@ -91,6 +99,7 @@ class Issue(object): @classmethod def from_github(cls, upstream, issue, config): + upstream_source = 'github' comments = [] for comment in issue['comments']: comments.append({ @@ -109,9 +118,16 @@ class Issue(object): elif issue['state'] == 'closed': issue['state'] = 'Closed' + # Perform any mapping + mapping = config['sync2jira']['map'][upstream_source][upstream].get('mapping', []) + + # Check for fixVersion + if any('fixVersion' in item for item in mapping): + cls.map_fixVersion(cls, mapping, issue) + # TODO: Priority is broken return Issue( - source='github', + source=upstream_source, title=issue['title'], url=issue['html_url'], upstream=upstream, @@ -130,3 +146,21 @@ class Issue(object): def __repr__(self): return "" % self.url + + def map_fixVersion(self, mapping, issue): + """ + Helper function to perform any fixVersion mapping. + + :param Dict mapping: Mapping dict we are given + :param Dict issue: Upstream issue object + """ + # Get our fixVersion mapping + try: + # for python 3 > + fixVersion_map = list(filter(lambda d: "fixVersion" in d, mapping))[0]['fixVersion'] + except ValueError: + # for python 2.7 + fixVersion_map = filter(lambda d: "fixVersion" in d, mapping)[0]['fixVersion'] + + # Now update the fixVersion + issue['milestone'] = fixVersion_map.replace('XXX', issue['milestone']) diff --git a/tests/test_intermediary.py b/tests/test_intermediary.py index d27e98f..ad0a12a 100644 --- a/tests/test_intermediary.py +++ b/tests/test_intermediary.py @@ -16,10 +16,10 @@ class TestIntermediary(unittest.TestCase): 'pagure_url': 'dummy_pagure_url', 'map': { 'pagure': { - 'pagure': 'mock_downstream' + 'pagure': {'mock_downstream': 'mock_key'} }, 'github': { - 'github': 'mock_downstream' + 'github': {'mock_downstream': 'mock_key'} } } } @@ -78,7 +78,7 @@ class TestIntermediary(unittest.TestCase): self.assertEqual(response.assignee, 'mock_assignee') self.assertEqual(response.status, 'mock_status') self.assertEqual(response.id, 'mock_date') - self.assertEqual(response.downstream, 'mock_downstream') + self.assertEqual(response.downstream, {'mock_downstream': 'mock_key'}) def test_from_github_open(self): """ @@ -129,7 +129,7 @@ class TestIntermediary(unittest.TestCase): self.assertEqual(response.assignee, 'mock_assignee') self.assertEqual(response.status, 'Open') self.assertEqual(response.id, '1234') - self.assertEqual(response.downstream, 'mock_downstream') + self.assertEqual(response.downstream, {'mock_downstream': 'mock_key'}) def test_from_github_closed(self): """ @@ -180,4 +180,123 @@ class TestIntermediary(unittest.TestCase): self.assertEqual(response.assignee, 'mock_assignee') self.assertEqual(response.status, 'Closed') self.assertEqual(response.id, '1234') - self.assertEqual(response.downstream, 'mock_downstream') + self.assertEqual(response.downstream, {'mock_downstream': 'mock_key'}) + + def test_mapping_github(self): + """ + This tests the mapping feature from github + """ + # Set up return values + mock_issue = { + 'comments': [{ + 'author': 'mock_author', + 'name': 'mock_name', + 'body': 'mock_body', + 'id': 'mock_id', + 'date_created': 'mock_date' + }], + 'title': 'mock_title', + 'html_url': 'mock_url', + 'id': 1234, + 'labels': 'mock_tags', + 'milestone': 'mock_milestone', + 'priority': 'mock_priority', + 'body': 'mock_content', + 'user': 'mock_reporter', + 'assignees': 'mock_assignee', + 'state': 'closed', + 'date_created': 'mock_date', + 'number': '1', + } + self.mock_config['sync2jira']['map']['github']['github'] = { + 'mock_downstream': 'mock_key', + 'mapping': [{'fixVersion': 'Test XXX'}] + } + + # Call the function + response = i.Issue.from_github( + upstream='github', + issue=mock_issue, + config=self.mock_config + ) + + # Assert that we made the calls correctly + self.assertEqual(response.source, 'github') + self.assertEqual(response.title, '[github] mock_title') + self.assertEqual(response.url, 'mock_url') + self.assertEqual(response.upstream, 'github') + self.assertEqual(response.comments, [{'body': 'mock_body', 'name': 'mock_name', 'author': 'mock_author', + 'changed': None, 'date_created': 'mock_date', 'id': 'mock_id'}]) + self.assertEqual(response.tags, 'mock_tags') + self.assertEqual(response.fixVersion, ['Test mock_milestone']) + self.assertEqual(response.priority, None) + self.assertEqual(response.content, 'mock_content') + self.assertEqual(response.reporter, 'mock_reporter') + self.assertEqual(response.assignee, 'mock_assignee') + self.assertEqual(response.status, 'Closed') + self.assertEqual(response.id, '1234') + self.assertEqual(response.downstream, { + 'mock_downstream': 'mock_key', + 'mapping': [{'fixVersion': 'Test XXX'}]}) + + @mock.patch(PATH + 'datetime') + def test_mapping_pagure(self, + mock_datetime): + """ + This tests the mapping feature from pagure + """ + # Set up return values + mock_datetime.fromtimestamp.return_value = 'mock_date' + mock_issue = { + 'comments': [{ + 'date_created': '1234', + 'user': { + 'name': 'mock_name' + }, + 'comment': 'mock_body', + 'id': '1234', + }], + 'title': 'mock_title', + 'id': 1234, + 'tags': 'mock_tags', + 'milestone': 'mock_milestone', + 'priority': 'mock_priority', + 'content': 'mock_content', + 'user': 'mock_reporter', + 'assignee': 'mock_assignee', + 'status': 'mock_status', + 'date_created': 'mock_date' + '' + } + self.mock_config['sync2jira']['map']['pagure']['pagure'] = { + 'mock_downstream': 'mock_key', + 'mapping': [{'fixVersion': 'Test XXX'}] + } + + # Call the function + response = i.Issue.from_pagure( + upstream='pagure', + issue=mock_issue, + config=self.mock_config + ) + + # Assert that we made the calls correctly + self.assertEqual(response.source, 'pagure') + self.assertEqual(response.title, '[pagure] mock_title') + self.assertEqual(response.url, 'dummy_pagure_url/pagure/issue/1234') + self.assertEqual(response.upstream, 'pagure') + self.assertEqual(response.comments, [{'body': 'mock_body', 'name': 'mock_name', + 'author': 'mock_name', 'changed': None, + 'date_created': 'mock_date', + 'id': '1234'}]) + self.assertEqual(response.tags, 'mock_tags') + self.assertEqual(response.fixVersion, ['Test mock_milestone']) + self.assertEqual(response.priority, 'mock_priority') + self.assertEqual(response.content, 'mock_content') + self.assertEqual(response.reporter, 'mock_reporter') + self.assertEqual(response.assignee, 'mock_assignee') + self.assertEqual(response.status, 'mock_status') + self.assertEqual(response.id, 'mock_date') + self.assertEqual(response.downstream, { + 'mock_downstream': 'mock_key', + 'mapping': [{'fixVersion': 'Test XXX'}]})