#12 Get the web links working as a new tracking mechanism.
Merged 7 years ago by ralph. Opened 7 years ago by ralph.

file modified
+2 -8
@@ -22,14 +22,8 @@ 

          'initialize': True,

          'testing': False,

  

-         # Disable old-school mode.

-         'legacy_matching': False,

- 

-         # With this, you can specify the custom field key for an "external url"

-         # See https://pagure.io/sync-to-jira/pull-request/3

-         'jira_opts': {

-             'external_url_field': 'customfield_10400',

-         },

+         # Temporarily enable old-school mode.

+         'legacy_matching': True,

  

          #'jira': { # See /etc/fedmsg.d/sync2jira-credentials.py },

          'map': {

file modified
-6
@@ -22,12 +22,6 @@ 

          'initialize': True,

          'testing': True,

  

-         # With this, you must specify the custom field key for an "external url"

-         # See https://pagure.io/sync-to-jira/pull-request/3

-         'jira_opts': {

-             'external_url_field': 'customfield_10400',

-         },

- 

          'jira': {

              'options': {

                  'server': 'https://some_jira_server_somewhere.com',

file modified
+63 -43
@@ -18,7 +18,6 @@ 

  # Authors:  Ralph Bean <rbean@redhat.com>

  

  import distutils.version

- import json

  import operator

  

  import logging
@@ -30,32 +29,33 @@ 

  

  log = logging.getLogger(__name__)

  

+ remote_link_title = "Upstream issue"

  

  jira_cache = {}

- def get_existing_jira_issues_legacy(downstream, config):

-     """ This is our old way of matching issues: get all, search by title.

  

-     This will be phased out and removed in a future release.

-     """

  

-     key = json.dumps(downstream)

-     if not key in jira_cache:

-         kwargs = sorted(downstream.items(), key=operator.itemgetter(0))

-         client = jira.client.JIRA(**config['sync2jira']['jira'])

-         query = " AND ".join([

-             "=".join([k, v]) for k, v in kwargs

-             if v is not None

-         ]) + " AND (resolution is null OR resolution = Duplicate)"

-         results = client.search_issues(query)

-         # TODO -- handle pagination here...

-         jira_cache[key] = results

-     return jira_cache[key]

+ def get_existing_jira_issue(issue, config):

+     """ Get a jira issue by the linked remote issue.

  

+     This is the new supported way of doing this.

+     """

+     client = jira.client.JIRA(**config['sync2jira']['jira'])

+     query = 'issueFunction in linkedIssuesOfRemote("%s") and ' \

+         'issueFunction in linkedIssuesOfRemote("%s")' % (

+             remote_link_title, issue.url)

+     client = jira.client.JIRA(**config['sync2jira']['jira'])

+     results = client.search_issues(query)

+     log.info("Found %i results for query %r" % (len(results), query))

+     if results:

+         return results[0]

+     else:

+         return None

  

- def get_existing_jira_issue(issue, config):

-     """ This is the supported way of matching issues.

  

-     Use the upstream url to uniquely grab individual downstream issues.

+ def get_existing_jira_issue_legacy(issue, config):

+     """ This is our old way of matching issues: use the special url field.

+ 

+     This will be phased out and removed in a future release.

      """

  

      kwargs = dict(issue.downstream.items())
@@ -73,6 +73,34 @@ 

      else:

          return None

  

+ def _attach_link(config, downstream, remote_link):

+     log.info("    Attaching tracking link %r to %r" % (

+         remote_link, downstream.key))

+     modified_desc = downstream.fields.description + " "

+     client = jira.client.JIRA(**config['sync2jira']['jira'])

+ 

+     # This is crazy.  Querying for application links requires admin perms which

+     # we don't have, so duckpunch the client to think it has already made the

+     # query.

+     client._applicationlinks = []  # Crazy.

+ 

+     # Add the link.

+     client.add_remote_link(downstream.id, remote_link)

+ 

+     # Finally, after we've added the link we have to edit the issue so that it

+     # gets re-indexed, otherwise our searches won't work. Also, Handle some

+     # weird API changes here...

+     log.debug("    Modifying desc of %r to trigger re-index." % downstream.key)

+     if jira_version < distutils.version.LooseVersion('0.39'):

+         # This is the old busted way

+         # https://github.com/pycontribs/jira/issues/65

+         downstream.update(fields=dict(fields={'description': modified_desc}))

+     else:

+         # This is the new, normal way.

+         downstream.update({'description': modified_desc})

+ 

+     return downstream

+ 

  

  def upgrade_jira_issue(downstream, issue, config):

      """ Given an old legacy-style downstream issue...
@@ -80,21 +108,15 @@ 

  

      Simply mark it with an external-url field value.

      """

-     log.info("    Upgrading %r issue for %r" % (issue.downstream, issue))

+     log.info("    Upgrading %r %r issue for %r" % (

+         downstream.key, issue.downstream, issue))

      if config['sync2jira']['testing']:

          log.info("      Testing flag is true.  Skipping actual upgrade.")

          return

  

      # Do it!

-     external_url_field = config['sync2jira']['jira_opts']['external_url_field']

-     # Handle some weird API changes here...

-     if jira_version < distutils.version.LooseVersion('0.39'):

-         # This is the old busted way

-         # https://github.com/pycontribs/jira/issues/65

-         downstream.update(fields=dict(fields={external_url_field: issue.url}))

-     else:

-         # This is the new, normal way.

-         downstream.update({external_url_field: issue.url})

+     remote_link = dict(url=issue.url, title=remote_link_title)

+     _attach_link(config, downstream, remote_link)

  

  

  def create_jira_issue(issue, config):
@@ -114,10 +136,12 @@ 

      if issue.downstream['component']:

          kwargs['components'] = [dict(name=issue.downstream['component'])] # TODO - make this a list in the config

  

-     external_url_field = config['sync2jira']['jira_opts']['external_url_field']

-     kwargs[external_url_field] = issue.url

+     log.info("Creating issue.")

+     downstream = client.create_issue(**kwargs)

  

-     return client.create_issue(**kwargs)

+     remote_link = dict(url=issue.url, title=remote_link_title)

+     _attach_link(config, downstream, remote_link)

+     return downstream

  

  

  def sync_with_jira(issue, config):
@@ -125,8 +149,9 @@ 

  

      # First, check to see if we have a matching issue using the new method.

      # If we do, then just bail out.  No sync needed.

-     if get_existing_jira_issue(issue, config):

-         log.info("   Found existing, matching issue downstream.")

+     existing = get_existing_jira_issue(issue, config)

+     if existing:

+         log.info("   Found existing, matching downstream %r." % existing.key)

          return

  

      # If we're *not* configured to do legacy matching (upgrade mode) then there
@@ -141,13 +166,8 @@ 

      # - If we can't find it, create it.

      # - If we can find it, upgrade it to the new method.

      log.info("  Looking for matching downstream issue via legacy method.")

-     existing_issues = get_existing_jira_issues_legacy(issue.downstream, config)

-     existing_summaries = [i.fields.summary for i in existing_issues]

-     if issue.title not in existing_summaries:

+     match = get_existing_jira_issue_legacy(issue, config)

+     if not match:

          create_jira_issue(issue, config)

      else:

-         downstream = [

-             i for i in existing_issues

-             if i.fields.summary == issue.title

-         ][0]

-         upgrade_jira_issue(downstream, issue, config)

+         upgrade_jira_issue(match, issue, config)

file modified
+1
@@ -95,6 +95,7 @@ 

  

  def initialize(config):

      log.info("Running initialization to sync all issues from upstream to jira")

+     log.info("   Testing flag is %r" % config['sync2jira']['testing'])

      mapping = config['sync2jira']['map']

      for upstream in mapping.get('pagure', {}).keys():

          for issue in u.pagure_issues(upstream, config):

file modified
+25 -24
@@ -12,30 +12,25 @@ 

              'jira': {

                  # Nothing, really..

              },

-             'jira_opts': {

-                 'external_url_field': 'url_field',

-             },

          },

      }

  

      @mock.patch('jira.client.JIRA')

      def test_get_existing_legacy(self, client):

+         class MockIssue(object):

+             downstream = {'key': 'value'}

+             url = 'wat'

+         issue = MockIssue()

          config = self.config.copy()

          # Ensure that we get results back from the jira client.

          target1 = "target1"

-         client.return_value.search_issues = mock.MagicMock(return_value=target1)

-         result = d.get_existing_jira_issues_legacy({'key': 'value'}, config)

+         client.return_value.search_issues = mock.MagicMock(return_value=[target1])

+         result = d.get_existing_jira_issue_legacy(issue, config)

          eq_(result, target1)

  

-         # Ensure that caching works.

-         target2 = "target2"

-         client.return_value.search_issues.return_value = target2

-         result = d.get_existing_jira_issues_legacy({'key': 'value'}, config)

-         eq_(result, target1)  # Really, target1, because caching.

- 

-         # Make sure we called search issues really only once

          client.return_value.search_issues.assert_called_once_with(

-             'key=value AND (resolution is null OR resolution = Duplicate)',

+             "'External issue URL'='wat' AND key=value AND "

+             "(resolution is null OR resolution = Duplicate)",

          )

  

      @mock.patch('jira.client.JIRA')
@@ -54,8 +49,8 @@ 

          eq_(result, target1)

  

          client.return_value.search_issues.assert_called_once_with(

-             '\'External issue URL\'=\'http://threebean.org\' AND key=value AND '

-             '(resolution is null OR resolution = Duplicate)',

+             'issueFunction in linkedIssuesOfRemote("Upstream issue") and '

+             'issueFunction in linkedIssuesOfRemote("http://threebean.org")'

          )

  

      @mock.patch('jira.client.JIRA')
@@ -68,17 +63,23 @@ 

  

          downstream = mock.MagicMock()

          issue = MockIssue()

+         client_obj = mock.MagicMock()

+         client.return_value = client_obj

          d.upgrade_jira_issue(downstream, issue, config)

- 

-         downstream.update.assert_called_once_with(

-             dict(customfield_10400='http://threebean.org'),

-         )

+         remote = {

+             'url': 'http://threebean.org',

+             'title': 'Upstream issue',

+         }

+         client_obj.add_remote_link.assert_called_once_with(downstream.id, remote)

  

  

      @mock.patch('jira.client.JIRA')

      def test_create_jira_issue(self, client):

          config = self.config.copy()

-         target1 = "target1"

+         target1 = mock.Mock()

+         target1.id = "target1 id"

+         target1.key = "target key"

+         target1.fields.description = "description"

          client.return_value.create_issue = mock.MagicMock(return_value=target1)

  

          class MockIssue(object):
@@ -98,13 +99,15 @@ 

              project={'key': 'awesome'},

              description='http://threebean.org',

              summary='A title, a title...',

-             url_field='http://threebean.org',

          )

  

      @mock.patch('jira.client.JIRA')

      def test_create_jira_issue_with_custom_url(self, client):

          config = self.config.copy()

-         target1 = "target1"

+         target1 = mock.Mock()

+         target1.id = "target1 id"

+         target1.key = "target key"

+         target1.fields.description = "description"

          client.return_value.create_issue = mock.MagicMock(return_value=target1)

  

          class MockIssue(object):
@@ -116,7 +119,6 @@ 

              url = 'http://threebean.org'

  

          config['sync2jira']['testing'] = False

-         config['sync2jira']['jira_opts'] = dict(external_url_field='customfield_10400')

          result = d.create_jira_issue(MockIssue(), config)

          eq_(result, target1)

          client.return_value.create_issue.assert_called_with(
@@ -125,5 +127,4 @@ 

              project={'key': 'awesome'},

              description='http://threebean.org',

              summary='A title, a title...',

-             customfield_10400='http://threebean.org',

          )