Repository: fedora-infra/bodhi

Issue:
Add a short description field for an update
As discussed in #1351, in an effort to make the titles of updates more useful, it would be great provide a new field tied to an update allowing a package maintainer to add a short description to a multi-build update. With it falling back to the logic for truncating the list outlined in #1351, if a short description is not given. 

As an example the short description could simply be "GNOME", so the label would read:

    enhancement update in Fedora 25 for GNOME

instead of:

    enhancement update in Fedora 25 for nautilus, gnome-shell and 63 others


Discussion:
Now we have [display_name](https://github.com/fedora-infra/bodhi/blob/3dca0e367dcbee25c492424228b50c6ab857212d/bodhi/server/models.py#L1615) field, but user is unable to set it on update creation/edit.

Resolving patch:
diff --git a/bodhi/client/__init__.py b/bodhi/client/__init__.py
--- a/bodhi/client/__init__.py
+++ b/bodhi/client/__init__.py
@@ -113,6 +113,8 @@ def _set_logging_debug(ctx, param, value):
                  help='Space or comma-separated list of required Taskotron tasks'),
     click.option('--suggest', help='Post-update user suggestion',
                  type=click.Choice(['logout', 'reboot'])),
+    click.option('--display-name',
+                 help='The name of the update'),
     staging_option]
 
 
diff --git a/bodhi/client/bindings.py b/bodhi/client/bindings.py
--- a/bodhi/client/bindings.py
+++ b/bodhi/client/bindings.py
@@ -218,6 +218,7 @@ def save(self, **kwargs):
         the ``edited`` keyword argument.
 
         Args:
+            display_name (basestring): The name of the update.
             builds (basestring): A list of koji builds for this update.
             type (basestring): The type of this update: ``security``, ``bugfix``,
                 ``enhancement``, and ``newpackage``.
@@ -601,6 +602,7 @@ def parse_file(self, input_file):
                 'builds': section,
                 'bugs': config.get(section, 'bugs', raw=True),
                 'close_bugs': config.getboolean(section, 'close_bugs'),
+                'display_name': config.get(section, 'display_name', raw=True),
                 'type': config.get(section, 'type', raw=True),
                 'type_': config.get(section, 'type', raw=True),
                 'request': config.get(section, 'request', raw=True),
diff --git a/bodhi/server/schemas.py b/bodhi/server/schemas.py
--- a/bodhi/server/schemas.py
+++ b/bodhi/server/schemas.py
@@ -172,6 +172,10 @@ class SaveUpdateSchema(CSRFProtectedSchema, colander.MappingSchema):
 
     bugs = Bugs(colander.Sequence(accept_scalar=True), missing=None, preparer=[util.splitter])
 
+    display_name = colander.SchemaNode(
+        colander.String(),
+        missing='',
+    )
     close_bugs = colander.SchemaNode(
         colander.Boolean(),
         missing=True,


Test patch:
diff --git a/bodhi/tests/client/test___init__.py b/bodhi/tests/client/test___init__.py
--- a/bodhi/tests/client/test___init__.py
+++ b/bodhi/tests/client/test___init__.py
@@ -51,6 +51,9 @@
 # Here is where you give an explanation of your update.
 notes=Initial Release
 
+# Update name
+display_name=fake update name
+
 # Enable request automation based on the stable/unstable karma thresholds
 autokarma=True
 stable_karma=3
@@ -434,8 +437,8 @@ def test_severity_flag(self, send_request):
                     'close_bugs': False, 'stable_karma': None, 'csrf_token': 'a_csrf_token',
                     'staging': False, 'builds': 'bodhi-2.2.4-1.el7', 'autokarma': True,
                     'suggest': None, 'notes': 'No description.', 'request': None, 'bugs': '',
-                    'requirements': None, 'unstable_karma': None, 'file': None,
-                    'notes_file': None, 'type': 'bugfix', 'severity': 'urgent'
+                    'requirements': None, 'unstable_karma': None, 'file': None, 'notes_file': None,
+                    'type': 'bugfix', 'severity': 'urgent', 'display_name': None
                 }
             ),
             mock.call(
@@ -475,7 +478,8 @@ def test_debug_flag(self, send_request):
                     'staging': False, 'builds': 'bodhi-2.2.4-1.el7', 'autokarma': True,
                     'suggest': None, 'notes': 'No description.', 'request': None,
                     'bugs': '', 'requirements': None, 'unstable_karma': None, 'file': None,
-                    'notes_file': None, 'type': 'bugfix', 'severity': 'urgent'
+                    'notes_file': None, 'type': 'bugfix', 'severity': 'urgent',
+                    'display_name': None
                 }
             ),
             mock.call(
@@ -514,7 +518,7 @@ def test_url_flag(self, send_request):
                     'staging': False, 'builds': 'bodhi-2.2.4-1.el7', 'autokarma': True,
                     'suggest': None, 'notes': 'No description.', 'request': None, 'bugs': '',
                     'requirements': None, 'unstable_karma': None, 'file': None,
-                    'notes_file': None, 'type': 'bugfix', 'severity': None
+                    'notes_file': None, 'type': 'bugfix', 'severity': None, 'display_name': None
                 }
             ),
             mock.call(
@@ -556,7 +560,7 @@ def test_file_flag(self, send_request):
                     'autokarma': 'True', 'suggest': 'unspecified', 'notes': 'Initial Release',
                     'request': 'testing', 'bugs': '123456,43212',
                     'unstable_karma': '-3', 'type_': 'bugfix', 'type': 'bugfix',
-                    'type': 'bugfix', 'severity': 'unspecified'
+                    'type': 'bugfix', 'severity': 'unspecified', 'display_name': 'fake update name'
                 }
             ),
             mock.call(
@@ -635,7 +639,49 @@ def test_close_bugs_flag(self, send_request):
                     'staging': False, 'builds': 'bodhi-2.2.4-1.el7', 'autokarma': True,
                     'suggest': None, 'notes': 'No description.', 'request': None,
                     'bugs': '1234567', 'requirements': None, 'unstable_karma': None, 'file': None,
-                    'notes_file': None, 'type': 'bugfix', 'severity': None
+                    'notes_file': None, 'type': 'bugfix', 'severity': None, 'display_name': None
+                }
+            ),
+            mock.call(
+                bindings_client,
+                'updates/FEDORA-EPEL-2016-3081a94111/get-test-results',
+                verb='GET'
+            )
+        ]
+        self.assertEqual(send_request.mock_calls, calls)
+        self.assertEqual(bindings_client.base_url, 'http://localhost:6543/')
+
+    @mock.patch('bodhi.client.bindings.BodhiClient.csrf',
+                mock.MagicMock(return_value='a_csrf_token'))
+    @mock.patch('bodhi.client.bindings.BodhiClient.send_request',
+                return_value=client_test_data.EXAMPLE_UPDATE_MUNCH, autospec=True)
+    def test_display_name_flag(self, send_request):
+        """
+        Assert correct behavior with the --close-bugs flag.
+        """
+        runner = testing.CliRunner()
+
+        result = runner.invoke(
+            client.new,
+            ['--user', 'bowlofeggs', '--password', 's3kr3t', '--autokarma', 'bodhi-2.2.4-1.el7',
+             '--bugs', '1234567', '--display-name', 'fake display name', '--url',
+             'http://localhost:6543', '--notes', 'No description.'])
+
+        self.assertEqual(result.exit_code, 0)
+        expected_output = client_test_data.EXPECTED_UPDATE_OUTPUT.replace('example.com/tests',
+                                                                          'localhost:6543')
+        self.assertTrue(compare_output(result.output, expected_output + '\n'))
+        bindings_client = send_request.mock_calls[0][1][0]
+        calls = [
+            mock.call(
+                bindings_client, 'updates/', auth=True, verb='POST',
+                data={
+                    'close_bugs': False, 'stable_karma': None, 'csrf_token': 'a_csrf_token',
+                    'staging': False, 'builds': 'bodhi-2.2.4-1.el7', 'autokarma': True,
+                    'suggest': None, 'notes': 'No description.', 'request': None,
+                    'bugs': '1234567', 'requirements': None, 'unstable_karma': None, 'file': None,
+                    'notes_file': None, 'type': 'bugfix', 'severity': None,
+                    'display_name': 'fake display name'
                 }
             ),
             mock.call(
@@ -1488,7 +1534,7 @@ def test_bugs_flag(self, send_request, query):
                     'suggest': 'unspecified', 'notes': 'New package.',
                     'notes_file': None, 'request': None, 'unstable_karma': -3,
                     'bugs': '1234,5678', 'requirements': '', 'type': 'newpackage',
-                    'severity': 'low'}),
+                    'severity': 'low', 'display_name': None}),
             mock.call(
                 bindings_client,
                 'updates/FEDORA-EPEL-2016-3081a94111/get-test-results',
@@ -1525,7 +1571,7 @@ def test_severity_flag(self, send_request, query):
                     'suggest': 'unspecified', 'notes': 'Updated package.',
                     'notes_file': None, 'request': None, 'unstable_karma': -3,
                     'bugs': '1420605', 'requirements': '', 'type': 'newpackage',
-                    'severity': 'low'
+                    'severity': 'low', 'display_name': None
                 }
             ),
             mock.call(
@@ -1568,7 +1614,7 @@ def test_url_flag(self, send_request, query):
                     'suggest': 'unspecified', 'notes': 'this is an edited note',
                     'notes_file': None, 'request': None, 'severity': 'low',
                     'bugs': '1420605', 'requirements': '', 'unstable_karma': -3,
-                    'type': 'newpackage'
+                    'type': 'newpackage', 'display_name': None
                 }
             ),
             mock.call(
@@ -1616,7 +1662,7 @@ def test_notes_file(self, send_request, query):
                         'suggest': 'unspecified', 'notes': 'This is a --notes-file note!',
                         'notes_file': 'notefile.txt', 'request': None, 'severity': 'low',
                         'bugs': '1420605', 'requirements': '', 'unstable_karma': -3,
-                        'type': 'newpackage'
+                        'type': 'newpackage', 'display_name': None
                     }
                 ),
                 mock.call(
@@ -1658,7 +1704,7 @@ def test_addbuilds_removebuilds(self, send_request, query):
                 bindings_client, 'updates/', auth=True, verb='POST',
                 data={
                     'close_bugs': False, 'stable_karma': 3, 'csrf_token': 'a_csrf_token',
-                    'staging': False,
+                    'staging': False, 'display_name': None,
                     'builds': ['tar-1.29-4.fc25', 'nedit-5.7-1.fc25'],
                     'autokarma': False, 'edited': 'FEDORA-2017-c95b33872d',
                     'suggest': u'unspecified', 'notes': u'add and remove builds',
@@ -1750,7 +1796,7 @@ def test_required_tasks(self, send_request, query):
                     'autokarma': False, 'edited': 'FEDORA-2017-c95b33872d',
                     'suggest': 'unspecified', 'notes': 'testing required tasks',
                     'notes_file': None, 'request': None, 'severity': 'low',
-                    'bugs': '1420605', 'unstable_karma': -3,
+                    'bugs': '1420605', 'unstable_karma': -3, 'display_name': None,
                     'requirements': 'dist.depcheck dist.rpmdeplint', 'type': 'newpackage'
                 }
             ),
@@ -1808,7 +1854,7 @@ def test_edit_bugless_update_without_bugs_param(self, send_request, query):
                     'close_bugs': False, 'stable_karma': 3, 'csrf_token': 'a_csrf_token',
                     'staging': False, 'builds': ['nodejs-grunt-wrap-0.3.0-2.fc25'],
                     'autokarma': False, 'edited': 'FEDORA-2017-c95b33872d',
-                    'suggest': 'unspecified', 'notes': 'New package.',
+                    'suggest': 'unspecified', 'notes': 'New package.', 'display_name': None,
                     'notes_file': None, 'request': None, 'severity': 'low',
                     'bugs': '', 'requirements': '', 'unstable_karma': -3, 'type': 'newpackage'
                 }
diff --git a/bodhi/tests/client/test_bindings.py b/bodhi/tests/client/test_bindings.py
--- a/bodhi/tests/client/test_bindings.py
+++ b/bodhi/tests/client/test_bindings.py
@@ -1647,6 +1647,9 @@ def test_parsing_valid_file(self, exists, _load_cookies, mock_open):
             "# Here is where you give an explanation of your update.\n",
             "notes=Initial Release\n",
             "\n",
+            "# Update name\n",
+            "display_name=fake update name\n",
+            "\n",
             "# Enable request automation based on the stable/unstable karma thresholds\n",
             "autokarma=True\n",
             "stable_karma=3\n",
@@ -1667,8 +1670,9 @@ def test_parsing_valid_file(self, exists, _load_cookies, mock_open):
         updates = client.parse_file("sad")
 
         self.assertEqual(len(updates), 1)
-        self.assertEqual(len(updates[0]), 12)
+        self.assertEqual(len(updates[0]), 13)
         self.assertEqual(updates[0]['close_bugs'], True)
+        self.assertEqual(updates[0]['display_name'], 'fake update name')
         self.assertEqual(updates[0]['unstable_karma'], '-3')
         self.assertEqual(updates[0]['severity'], 'unspecified')
         self.assertEqual(updates[0]['stable_karma'], '3')