MozReview: Add DiffViewerDropdownActionHook to add dropdown menus to diff view (
Bug 1232703). r?glob
MozReview-Commit-ID: HPYJBX4Kzj
--- a/reviewboard/docs/manual/extending/extensions/hooks/action-hooks.rst
+++ b/reviewboard/docs/manual/extending/extensions/hooks/action-hooks.rst
@@ -50,16 +50,19 @@ the actions you'd like to insert. These
There are also two hooks to provide drop-down menus in the action bars:
+---------------------------------------------+-------------------------+
| Class | Location |
+=============================================+=========================+
| :py:class:`ReviewRequestDropdownActionHook` | The bar at the top of a |
| | review request. |
+---------------------------------------------+-------------------------+
+| :py:class:`DiffViewerDropdownActionHook` | The bar at the top of a |
+| | diff view. |
++---------------------------------------------+-------------------------+
| :py:class:`HeaderDropdownActionHook` | The page header. |
+---------------------------------------------+-------------------------+
These work like the basic ActionHooks, except instead of a **url** field, they
contain an **items** field which is another list of dictionaries. Only one
level of nesting is possible.
--- a/reviewboard/reviewboard/extensions/hooks.py
+++ b/reviewboard/reviewboard/extensions/hooks.py
@@ -535,16 +535,46 @@ class ReviewRequestDropdownActionHook(Ac
@six.add_metaclass(ExtensionHookPoint)
class DiffViewerActionHook(ActionHook):
"""A hook for adding an action to the diff viewer page."""
@six.add_metaclass(ExtensionHookPoint)
+class DiffViewerDropdownActionHook(ActionHook):
+ """A hook for adding an drop down action to the review request page.
+
+ The actions for a drop down action should contain:
+
+ * ``id``: The ID of this action (optional).
+ * ``label``: The label of the drop-down.
+ * ``items``: A list of ActionHook-style dicts (see ActionHook params).
+
+ For example::
+
+ actions = [{
+ 'id': 'id 0',
+ 'label': 'Title',
+ 'items': [
+ {
+ 'id': 'id 1',
+ 'label': 'Item 1',
+ 'url': '...',
+ },
+ {
+ 'id': 'id 2',
+ 'label': 'Item 2',
+ 'url': '...',
+ }
+ ]
+ }]
+ """
+
+@six.add_metaclass(ExtensionHookPoint)
class HeaderActionHook(ActionHook):
"""A hook for putting an action in the page header."""
@six.add_metaclass(ExtensionHookPoint)
class HeaderDropdownActionHook(ActionHook):
"""A hook for putting multiple actions into a header dropdown."""
@@ -907,16 +937,17 @@ class ReviewRequestPublishedEmailHook(Em
'AdminWidgetHook',
'AuthBackendHook',
'CommentDetailDisplayHook',
'DashboardColumnsHook',
'DashboardSidebarItemsHook',
'DataGridColumnsHook',
'DataGridSidebarItemsHook',
'DiffViewerActionHook',
+ 'DiffViewerDropdownActionHook',
'EmailHook',
'ExtensionHook',
'FileAttachmentThumbnailHook',
'HeaderActionHook',
'HeaderDropdownActionHook',
'HostingServiceHook',
'NavigationBarHook',
'ReviewRequestActionHook',
--- a/reviewboard/reviewboard/extensions/templatetags/rb_extensions.py
+++ b/reviewboard/reviewboard/extensions/templatetags/rb_extensions.py
@@ -3,16 +3,17 @@ from __future__ import unicode_literals
import logging
from django import template
from django.template.loader import render_to_string
from djblets.util.decorators import basictag
from reviewboard.extensions.hooks import (CommentDetailDisplayHook,
DiffViewerActionHook,
+ DiffViewerDropdownActionHook,
HeaderActionHook,
HeaderDropdownActionHook,
NavigationBarHook,
ReviewRequestActionHook,
ReviewRequestDropdownActionHook)
from reviewboard.site.urlresolvers import local_site_reverse
@@ -53,16 +54,26 @@ def action_hooks(context, hook_cls, acti
@basictag(takes_context=True)
def diffviewer_action_hooks(context):
"""Displays all registered action hooks for the diff viewer."""
return action_hooks(context, DiffViewerActionHook)
@register.tag
@basictag(takes_context=True)
+def diffviewer_dropdown_action_hooks(context):
+ """Displays all registered action hooks for the diff viewer dropdown."""
+ return action_hooks(context,
+ DiffViewerDropdownActionHook,
+ "actions",
+ "extensions/action_dropdown.html")
+
+
+@register.tag
+@basictag(takes_context=True)
def review_request_action_hooks(context):
"""Displays all registered action hooks for review requests."""
return action_hooks(context, ReviewRequestActionHook)
@register.tag
@basictag(takes_context=True)
def review_request_dropdown_action_hooks(context):
--- a/reviewboard/reviewboard/extensions/tests.py
+++ b/reviewboard/reviewboard/extensions/tests.py
@@ -15,16 +15,17 @@ from kgb import SpyAgency
from reviewboard.admin.widgets import (primary_widgets,
secondary_widgets,
Widget)
from reviewboard.admin.siteconfig import load_site_config
from reviewboard.extensions.base import Extension
from reviewboard.extensions.hooks import (AdminWidgetHook,
CommentDetailDisplayHook,
DiffViewerActionHook,
+ DiffViewerDropdownActionHook,
EmailHook,
HeaderActionHook,
HeaderDropdownActionHook,
HostingServiceHook,
NavigationBarHook,
ReviewPublishedEmailHook,
ReviewRequestActionHook,
ReviewRequestApprovalHook,
@@ -95,16 +96,21 @@ class HookTests(TestCase):
super(HookTests, self).tearDown()
self.extension.shutdown()
def test_diffviewer_action_hook(self):
"""Testing diff viewer action extension hooks"""
self._test_action_hook('diffviewer_action_hooks', DiffViewerActionHook)
+ def test_diffviewer_dropdown_action_hook(self):
+ """Testing diff viewer dropdown-action extension hooks"""
+ self._test_dropdown_action_hook('diffviewer_dropdown_action_hooks',
+ DiffViewerDropdownActionHook)
+
def test_review_request_action_hook(self):
"""Testing review request action extension hooks"""
self._test_action_hook('review_request_action_hooks',
ReviewRequestActionHook)
def test_review_request_dropdown_action_hook(self):
"""Testing review request drop-down action extension hooks"""
self._test_dropdown_action_hook('review_request_dropdown_action_hooks',
@@ -590,16 +596,19 @@ class NavigationBarTestHook(NavigationBa
def get_entries(self, context):
raise Exception
class DiffViewerActionTestHook(DiffViewerActionHook):
def get_actions(self, context):
raise Exception
+class DiffViewerDropdownActionTestHook(DiffViewerDropdownActionHook):
+ def get_actions(self, context):
+ raise Exception
class HeaderActionTestHook(HeaderActionHook):
def get_actions(self, context):
raise Exception
class HeaderDropdownActionTestHook(HeaderDropdownActionHook):
def get_actions(self, context):
@@ -749,16 +758,29 @@ class SandboxTests(TestCase):
context = Context({'comment': 'this is a comment'})
t = Template(
"{% load rb_extensions %}"
"{% diffviewer_action_hooks %}")
t.render(context).strip()
+ def test_action_hooks_diff_viewer_dropdown_hook(self):
+ """Testing sandboxing DiffViewerDropdownActionHook when
+ action_hooks throws an error"""
+ DiffViewerDropdownActionTestHook(extension=self.extension)
+
+ context = Context({'comment': 'this is a comment'})
+
+ t = Template(
+ "{% load rb_extensions %}"
+ "{% diffviewer_dropdown_action_hooks %}")
+
+ t.render(context).strip()
+
def test_action_hooks_header_hook(self):
"""Testing sandboxing HeaderActionHook when
action_hooks throws an error"""
HeaderActionTestHook(extension=self.extension)
context = Context({'comment': 'this is a comment'})
t = Template(
--- a/reviewboard/reviewboard/templates/diffviewer/view_diff_mozreview.html
+++ b/reviewboard/reviewboard/templates/diffviewer/view_diff_mozreview.html
@@ -34,16 +34,17 @@
<ul class="actions page-tabs">
<li><a href="{{review_request.get_absolute_url}}">{% trans "Reviews" %}</a></li>
<li class="active"><a href="{% url 'view-diff' review_request.display_id %}#index_header">{% trans "Diff" %}</a></li>
</ul>
<div class="actions-container">
{% star review_request %}
<ul class="actions actions-right-container">
+{% diffviewer_dropdown_action_hooks %}
<li class="has-menu">
<a href="#" class="mobile-actions-menu-label"><span class="fa fa-ellipsis-h fa-lg"></span></a>
<ul class="actions actions-right">
{% include "reviews/review_request_actions_secondary.html" %}
{% diffviewer_action_hooks %}
<li id="download-diff" {% if interdiffset %}style="display: none;"{% endif %}><a href="raw/">{% trans "Download Diff" %}</a></li>
{% include "reviews/review_request_actions_primary.html" %}
</ul>