From a59bd14825bddc728a48081975f7fe2d62b7cefe Mon Sep 17 00:00:00 2001 From: sidpremkumar Date: Oct 23 2019 14:38:28 +0000 Subject: Add milestone support --- diff --git a/README.rst b/README.rst index 0503fff..fa58502 100644 --- a/README.rst +++ b/README.rst @@ -50,6 +50,7 @@ JIRA issues:: 'github_markdown' :: If description syncing is turned on, this flag will convert Github markdown to plaintext. 'upstream_id' :: This flag will add a comment indicating the upstream issue id when an issue is created, allowing the user to search for the issue downstream via the upstream ID. + 'milestone' :: This will sync upstream milestones to downstream labels (only appending). 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. diff --git a/docs/source/config-file.rst b/docs/source/config-file.rst index d012b53..76d21ec 100644 --- a/docs/source/config-file.rst +++ b/docs/source/config-file.rst @@ -106,6 +106,8 @@ getting a JIRA client and failure email will be sent anytime the service fails. * If description syncing is turned on, this flag will convert Github markdown to plaintext. This uses the pypandoc module. * :code:`upstream_id` * If selected this will add a comment to all newly created JIRA issue in the format 'UPSTREAM_PROJECT-#1' where the number indicates the issue ID. This allows users to search for the issue on JIRA via the issue number. + * :code:`milestone` + * If selected this will sync upstream milestones to downstream labels (only appending). .. note:: diff --git a/sync2jira/downstream.py b/sync2jira/downstream.py index 647898c..97cb7c8 100644 --- a/sync2jira/downstream.py +++ b/sync2jira/downstream.py @@ -712,9 +712,37 @@ def _update_jira_issue(existing, issue, client): log.info(" Looking for new transition(s)") _update_transition(client, existing, issue) + # Only synchornize milestones for listings that op-in + source is Pagure + if 'milestone' in updates: + log.info(" Looking for new milestone(s)") + _update_milestone(existing, issue) + log.info(' Done updating %s!' % issue.title) +def _update_milestone(existing, issue): + """ + Helper function to update the milestone of a downstream JIRA issue. + + :param jira.resource.Issue existing: Existing JIRA issue + :param sync2jira.intermediary.Issue issue: Upstream issue + :returns: Nothing + """ + if issue.fixVersion: + milestone = issue.fixVersion[0].replace(" ", "_") + else: + log.info(' No milestone found') + return + # Check if our milestone has already been syned + if milestone in existing.fields.labels: + return + # Otherwise we need to add the milestone + new_labels = _label_matching([milestone], existing.fields.labels) + data = {'labels': new_labels} + existing.update(data) + log.info(' Updated milestone') + + def _update_transition(client, existing, issue): """ Helper function to update the transition of a downstream JIRA issue. diff --git a/sync2jira/intermediary.py b/sync2jira/intermediary.py index 6b9dcab..7ca0aee 100644 --- a/sync2jira/intermediary.py +++ b/sync2jira/intermediary.py @@ -23,7 +23,8 @@ class Issue(object): def __init__(self, source, title, url, upstream, comments, config, tags, fixVersion, priority, content, - reporter, assignee, status, id, upstream_id, downstream=None): + reporter, assignee, status, id, upstream_id, + downstream=None): self.source = source self._title = title self.url = url @@ -86,7 +87,7 @@ class Issue(object): assignee=issue['assignee'], status=issue['status'], id=issue['date_created'], - upstream_id=issue['id'] + upstream_id=issue['id'], ) @classmethod @@ -125,7 +126,7 @@ class Issue(object): assignee=issue['assignees'], status=issue['state'], id=issue['id'], - upstream_id=issue['number'] + upstream_id=issue['number'], ) def __repr__(self): diff --git a/tests/test_downstream.py b/tests/test_downstream.py index 550d05e..89cf087 100644 --- a/tests/test_downstream.py +++ b/tests/test_downstream.py @@ -55,7 +55,7 @@ class TestDownstream(unittest.TestCase): {'tags': {'overwrite': False}}, {'fixVersion': {'overwrite': False}}, {'assignee': {'overwrite': True}}, 'description', 'title', - {'transition': 'CUSTOM TRANSITION'} + {'transition': 'CUSTOM TRANSITION'}, 'milestone', ], 'owner': 'mock_owner' } @@ -77,7 +77,7 @@ class TestDownstream(unittest.TestCase): {'tags': {'overwrite': False}}, {'fixVersion': {'overwrite': False}}, {'assignee': {'overwrite': True}}, 'description', 'title', - {'transition': 'CUSTOM TRANSITION'}, + {'transition': 'CUSTOM TRANSITION'}, 'milestone' ] # Mock Jira transition @@ -532,9 +532,11 @@ class TestDownstream(unittest.TestCase): @mock.patch(PATH + '_update_tags') @mock.patch(PATH + '_update_fixVersion') @mock.patch(PATH + '_update_transition') + @mock.patch(PATH + '_update_milestone') @mock.patch('jira.client.JIRA') def test_update_jira_issue(self, mock_client, + mock_update_milestone, mock_update_transition, mock_update_fixVersion, mock_update_tags, @@ -570,6 +572,10 @@ class TestDownstream(unittest.TestCase): self.mock_downstream, self.mock_issue ) + mock_update_milestone.assert_called_with( + self.mock_downstream, + self.mock_issue, + ) self.mock_downstream.update.assert_called_with({ 'summary': 'mock_title' }) @@ -1466,3 +1472,40 @@ class TestDownstream(unittest.TestCase): # Assert everything was called correctly self.assertEqual(response, True) mock_jira_client.search_issues.assert_called_with("issueFunction in linkedIssuesOfRemote('*')") + + @mock.patch(PATH + '_label_matching') + def test_update_milestone(self, + mock_label_matching): + """ + This function tests '_update_milestone' + """ + # Set up our return values + self.mock_issue.fixVersion = ["FY 20Q1"] + self.mock_downstream.fields.labels = ['test'] + mock_label_matching.return_value = ['test', 'FY_20Q1'] + + # Call the function + d._update_milestone(self.mock_downstream, self.mock_issue) + + # Assert everything was called correctly + self.mock_downstream.update.assert_called_with( + {'labels': ['test', 'FY_20Q1']}) + + @mock.patch(PATH + '_label_matching') + def test_update_milestone_no_update(self, + mock_label_matching): + """ + This function tests '_update_milestone' where we have no update + """ + # Set up our return values + self.mock_issue.fixVersion = ["FY 20Q1"] + self.mock_downstream.fields.labels = ['FY_20Q1'] + + # Call the function + d._update_milestone(self.mock_downstream, self.mock_issue) + + # Assert everything was called correctly + self.mock_downstream.update.assert_not_called() + mock_label_matching.assert_not_called() + +