From eb08b566e652604a1a6ea8e85b6187838e8e1bd2 Mon Sep 17 00:00:00 2001
From: sidpremkumar <sid.premkumar@gmail.com>
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 "<Issue %s >" % 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'}]})

