← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~cjwatson/launchpad/better-vcs-import-emails into lp:launchpad

 

Colin Watson has proposed merging lp:~cjwatson/launchpad/better-vcs-import-emails into lp:launchpad.

Commit message:
Make updated code import emails more informative.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/better-vcs-import-emails/+merge/346733

They now have an X-Launchpad-Notification-Type header, and they show the imported-from details even if they aren't being changed since those often make it clear at a glance why an import might be failing.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/better-vcs-import-emails into lp:launchpad.
=== modified file 'lib/lp/code/doc/codeimport.txt'
--- lib/lp/code/doc/codeimport.txt	2018-05-13 09:59:59 +0000
+++ lib/lp/code/doc/codeimport.txt	2018-05-23 11:53:21 +0000
@@ -546,6 +546,9 @@
     The branch whiteboard was changed to:
     <BLANKLINE>
     stuff
+    <BLANKLINE>
+    This code import is from:
+        hello from :pserver:anoncvs@xxxxxxxxxxxxxxx:/var/cvsroot
     >>> print(cvs_import.branch.whiteboard)
     stuff
 
@@ -556,5 +559,8 @@
     >>> print(make_email_body_for_code_import_update(
     ...     cvs_import, modify_event, ''))
     The branch whiteboard was deleted.
+    <BLANKLINE>
+    This code import is from:
+        hello from :pserver:anoncvs@xxxxxxxxxxxxxxx:/var/cvsroot
     >>> print(cvs_import.branch.whiteboard)
     None

=== modified file 'lib/lp/code/mail/codeimport.py'
--- lib/lp/code/mail/codeimport.py	2016-10-03 17:00:56 +0000
+++ lib/lp/code/mail/codeimport.py	2018-05-23 11:53:21 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2016 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Email notifications related to code imports."""
@@ -102,42 +102,46 @@
         else:
             raise AssertionError('Unexpected review status for code import.')
 
-    details_change_prefix = '\n'.join(textwrap.wrap(
-        "%s is now being imported from:" % code_import.target.unique_name))
     if code_import.rcs_type == RevisionControlSystems.CVS:
+        old_details = new_details = "%s from %s" % (
+            code_import.cvs_module, code_import.cvs_root)
         if (CodeImportEventDataType.OLD_CVS_ROOT in event_data or
-            CodeImportEventDataType.OLD_CVS_MODULE in event_data):
-            new_details = '    %s from %s' % (
-                code_import.cvs_module, code_import.cvs_root)
+                CodeImportEventDataType.OLD_CVS_MODULE in event_data):
             old_root = event_data.get(
                 CodeImportEventDataType.OLD_CVS_ROOT,
                 code_import.cvs_root)
             old_module = event_data.get(
                 CodeImportEventDataType.OLD_CVS_MODULE,
                 code_import.cvs_module)
-            old_details = '    %s from %s' % (old_module, old_root)
-            body.append(
-                details_change_prefix + '\n' + new_details +
-                "\ninstead of:\n" + old_details)
+            old_details = "%s from %s" % (old_module, old_root)
     elif code_import.rcs_type in (RevisionControlSystems.BZR_SVN,
                                   RevisionControlSystems.GIT,
                                   RevisionControlSystems.BZR):
+        old_details = new_details = code_import.url
         if CodeImportEventDataType.OLD_URL in event_data:
-            old_url = event_data[CodeImportEventDataType.OLD_URL]
-            body.append(
-                details_change_prefix + '\n    ' + code_import.url +
-                "\ninstead of:\n    " + old_url)
+            old_details = event_data[CodeImportEventDataType.OLD_URL]
     else:
         raise AssertionError(
             'Unexpected rcs_type %r for code import.' % code_import.rcs_type)
 
+    if new_details != old_details:
+        body.append(
+            textwrap.fill(
+                "%s is now being imported from:" %
+                code_import.target.unique_name) +
+            "\n    " + new_details +
+            "\ninstead of:\n    " + old_details)
+
     if new_whiteboard is not None:
         if new_whiteboard != '':
             body.append("The branch whiteboard was changed to:")
-            body.append("\n".join(textwrap.wrap(new_whiteboard)))
+            body.append(textwrap.fill(new_whiteboard))
         else:
             body.append("The branch whiteboard was deleted.")
 
+    if new_details == old_details:
+        body.append("This code import is from:\n    " + new_details)
+
     return '\n\n'.join(body)
 
 
@@ -151,7 +155,10 @@
     herder_rationale = 'Operator @%s' % vcs_imports.name
     recipients.add(vcs_imports, None, herder_rationale)
 
-    headers = {'X-Launchpad-Branch': target.unique_name}
+    headers = {
+        'X-Launchpad-Notification-Type': 'code-import-updated',
+        'X-Launchpad-Branch': target.unique_name,
+        }
 
     subject = 'Code import %s status: %s' % (
         code_import.target.unique_name, code_import.review_status.title)

=== modified file 'lib/lp/code/mail/tests/test_codeimport.py'
--- lib/lp/code/mail/tests/test_codeimport.py	2016-10-11 14:26:31 +0000
+++ lib/lp/code/mail/tests/test_codeimport.py	2018-05-23 11:53:21 +0000
@@ -1,19 +1,22 @@
-# Copyright 2010-2016 Canonical Ltd.  This software is licensed under the
+# Copyright 2010-2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Tests for code import related mailings"""
 
 from email import message_from_string
+import textwrap
 
 import transaction
 
 from lp.code.enums import (
+    CodeImportReviewStatus,
     RevisionControlSystems,
     TargetRevisionControlSystems,
     )
 from lp.code.tests.helpers import GitHostingFixture
 from lp.services.mail import stub
 from lp.testing import (
+    login_celebrity,
     login_person,
     TestCaseWithFactory,
     )
@@ -146,3 +149,150 @@
             '\n'
             '-- \nYou are getting this email because you are a member of the '
             'vcs-imports team.\n', msg.get_payload(decode=True))
+
+
+class TestUpdatedCodeImports(TestCaseWithFactory):
+    """Test the emails sent out for updated code imports."""
+
+    layer = DatabaseFunctionalLayer
+
+    def assertSameDetailsEmail(self, details, unique_name):
+        msg = message_from_string(stub.test_emails[0][2])
+        self.assertEqual(
+            'code-import-updated', msg['X-Launchpad-Notification-Type'])
+        self.assertEqual(unique_name, msg['X-Launchpad-Branch'])
+        self.assertEqual(
+            'Hello,\n\n'
+            'The import has been marked as failing.\n\n'
+            'This code import is from:\n'
+            '    %(details)s\n\n'
+            '-- \nhttp://code.launchpad.dev/%(unique_name)s\n'
+            'You are getting this email because you are a member of the '
+            'vcs-imports team.\n' % {
+                'details': details,
+                'unique_name': unique_name,
+                },
+            msg.get_payload(decode=True))
+
+    def assertDifferentDetailsEmail(self, old_details, new_details,
+                                    unique_name):
+        msg = message_from_string(stub.test_emails[0][2])
+        self.assertEqual(
+            'code-import-updated', msg['X-Launchpad-Notification-Type'])
+        self.assertEqual(unique_name, msg['X-Launchpad-Branch'])
+        self.assertEqual(
+            'Hello,\n\n'
+            '%(details_change_message)s\n'
+            '    %(new_details)s\n'
+            'instead of:\n'
+            '    %(old_details)s\n'
+            '\n'
+            '-- \nhttp://code.launchpad.dev/%(unique_name)s\n'
+            'You are getting this email because you are a member of the '
+            'vcs-imports team.\n' % {
+                'details_change_message': textwrap.fill(
+                    '%s is now being imported from:' % unique_name),
+                'old_details': old_details,
+                'new_details': new_details,
+                'unique_name': unique_name,
+                },
+            msg.get_payload(decode=True))
+
+    def test_cvs_to_bzr_import_same_details(self):
+        code_import = self.factory.makeProductCodeImport(
+            cvs_root=':pserver:anonymouse@xxxxxxxxxxxxxxx:/cvsroot',
+            cvs_module='a_module')
+        unique_name = code_import.target.unique_name
+        user = login_celebrity('vcs_imports')
+        code_import.updateFromData(
+            {'review_status': CodeImportReviewStatus.FAILING}, user)
+        transaction.commit()
+        self.assertSameDetailsEmail(
+            'a_module from :pserver:anonymouse@xxxxxxxxxxxxxxx:/cvsroot',
+            unique_name)
+
+    def test_cvs_to_bzr_import_different_details(self):
+        code_import = self.factory.makeProductCodeImport(
+            cvs_root=':pserver:anonymouse@xxxxxxxxxxxxxxx:/cvsroot',
+            cvs_module='a_module')
+        unique_name = code_import.target.unique_name
+        user = login_celebrity('vcs_imports')
+        code_import.updateFromData({'cvs_module': 'another_module'}, user)
+        transaction.commit()
+        self.assertDifferentDetailsEmail(
+            'a_module from :pserver:anonymouse@xxxxxxxxxxxxxxx:/cvsroot',
+            'another_module from :pserver:anonymouse@xxxxxxxxxxxxxxx:/cvsroot',
+            unique_name)
+
+    def test_svn_to_bzr_import_same_details(self):
+        code_import = self.factory.makeProductCodeImport(
+            svn_branch_url='svn://svn.example.com/fooix/trunk')
+        unique_name = code_import.target.unique_name
+        user = login_celebrity('vcs_imports')
+        code_import.updateFromData(
+            {'review_status': CodeImportReviewStatus.FAILING}, user)
+        transaction.commit()
+        self.assertSameDetailsEmail(
+            'svn://svn.example.com/fooix/trunk', unique_name)
+
+    def test_svn_to_bzr_import_different_details(self):
+        code_import = self.factory.makeProductCodeImport(
+            svn_branch_url='svn://svn.example.com/fooix/trunk')
+        unique_name = code_import.target.unique_name
+        user = login_celebrity('vcs_imports')
+        code_import.updateFromData(
+            {'url': 'https://svn.example.com/fooix/trunk'}, user)
+        transaction.commit()
+        self.assertDifferentDetailsEmail(
+            'svn://svn.example.com/fooix/trunk',
+            'https://svn.example.com/fooix/trunk', unique_name)
+
+    def test_git_to_bzr_import_same_details(self):
+        code_import = self.factory.makeProductCodeImport(
+            git_repo_url='git://git.example.com/fooix.git')
+        unique_name = code_import.target.unique_name
+        user = login_celebrity('vcs_imports')
+        code_import.updateFromData(
+            {'review_status': CodeImportReviewStatus.FAILING}, user)
+        transaction.commit()
+        self.assertSameDetailsEmail(
+            'git://git.example.com/fooix.git', unique_name)
+
+    def test_git_to_bzr_import_different_details(self):
+        code_import = self.factory.makeProductCodeImport(
+            git_repo_url='git://git.example.com/fooix.git')
+        unique_name = code_import.target.unique_name
+        user = login_celebrity('vcs_imports')
+        code_import.updateFromData(
+            {'url': 'https://git.example.com/fooix.git'}, user)
+        transaction.commit()
+        self.assertDifferentDetailsEmail(
+            'git://git.example.com/fooix.git',
+            'https://git.example.com/fooix.git', unique_name)
+
+    def test_git_to_git_import_same_details(self):
+        self.useFixture(GitHostingFixture())
+        code_import = self.factory.makeProductCodeImport(
+            git_repo_url='git://git.example.com/fooix.git',
+            target_rcs_type=TargetRevisionControlSystems.GIT)
+        unique_name = code_import.target.unique_name
+        user = login_celebrity('vcs_imports')
+        code_import.updateFromData(
+            {'review_status': CodeImportReviewStatus.FAILING}, user)
+        transaction.commit()
+        self.assertSameDetailsEmail(
+            'git://git.example.com/fooix.git', unique_name)
+
+    def test_git_to_git_import_different_details(self):
+        self.useFixture(GitHostingFixture())
+        code_import = self.factory.makeProductCodeImport(
+            git_repo_url='git://git.example.com/fooix.git',
+            target_rcs_type=TargetRevisionControlSystems.GIT)
+        unique_name = code_import.target.unique_name
+        user = login_celebrity('vcs_imports')
+        code_import.updateFromData(
+            {'url': 'https://git.example.com/fooix.git'}, user)
+        transaction.commit()
+        self.assertDifferentDetailsEmail(
+            'git://git.example.com/fooix.git',
+            'https://git.example.com/fooix.git', unique_name)


Follow ups