[PATCH v4 5/5] patch-list: add inline dropdown for delegate and state one-off changes

Raxel Gutierrez raxel at google.com
Tue Aug 24 04:28:33 AEST 2021


Add dropdown for the cell values of the Delegate and State columns for
each individual patch to make one-off changes to patches. The dropdowns
are only viewable if the user has patch edit permissions.

Change the generic_list method to pass the list of states and
maintainers to the patch list view context to populate the dropdown
options. The static patch-list.js file now uses the modularity of the
fetch request and update/error messages handling of rest.js.

TODO: The loading of the patch-list page is very slow now because it
seems that for each call to check if a patch is editable by a user, the
db is accessed. Changes in the backend need to be made so this is done
with only done with only one call to the db.

Signed-off-by: Raxel Gutierrez <raxel at google.com>
---
 htdocs/README.rst                             |  6 +++
 htdocs/js/patch-list.js                       | 52 ++++++++++++++++++-
 .../patchwork/partials/patch-list.html        | 35 +++++++++++--
 patchwork/views/__init__.py                   |  6 +++
 4 files changed, 94 insertions(+), 5 deletions(-)

diff --git a/htdocs/README.rst b/htdocs/README.rst
index 6c435124..32550119 100644
--- a/htdocs/README.rst
+++ b/htdocs/README.rst
@@ -133,6 +133,12 @@ js
 
   Part of Patchwork.
 
+``patch-list.js.``
+  Event helpers and other application logic for patch-list.html. These
+  support patch list manipulation (e.g. inline dropdown changes).
+
+  Part of Patchwork.
+
 ``selectize.min.js``
   Selectize is the hybrid of a ``textbox`` and ``<select>`` box. It's jQuery
   based and it has autocomplete and native-feeling keyboard navigation; useful
diff --git a/htdocs/js/patch-list.js b/htdocs/js/patch-list.js
index 6ae13721..526f5370 100644
--- a/htdocs/js/patch-list.js
+++ b/htdocs/js/patch-list.js
@@ -1,5 +1,32 @@
+import { updateProperty } from "./rest.js";
+
 $( document ).ready(function() {
-    $("#patch-list").stickyTableHeaders();
+    let inlinePropertyDropdowns = $("td > select[class^='change-property-']");
+    $(inlinePropertyDropdowns).each(function() {
+        // Store previous dropdown selection
+        $(this).data("prevProperty", $(this).val());
+    });
+
+    // Change listener for dropdowns that change an individual patch's delegate and state properties
+    $(inlinePropertyDropdowns).change((event) => {
+        const property = event.target.getAttribute("value");
+        const { url, data } = getPatchProperties(event.target, property);
+        const updateMessage = {
+            'error': "No patches updated",
+            'success': "1 patch updated",
+        };
+        updateProperty(url, data, updateMessage).then(isSuccess => {
+            if (!isSuccess) {
+                // Revert to previous selection
+                $(event.target).val($(event.target).data("prevProperty"));
+            } else {
+                // Update to new previous selection
+                $(event.target).data("prevProperty", $(event.target).val());
+            }
+        });
+    });
+
+    $("#patchlist").stickyTableHeaders();
 
     $("#check-all").change(function(e) {
         if(this.checked) {
@@ -9,4 +36,25 @@ $( document ).ready(function() {
         }
         e.preventDefault();
     });
-});
\ No newline at end of file
+
+    /**
+     * Returns the data to make property changes to a patch through PATCH request.
+     * @param {Element} propertySelect Property select element modified.
+     * @param {string} property Patch property modified (e.g. "state", "delegate")
+     * @return {{property: string, value: string}}
+     *     property: Property field to be modified in request.
+     *     value: New value for property to be modified to in request.
+     */
+    function getPatchProperties(propertySelect, property) {
+        const selectedOption = propertySelect.options[propertySelect.selectedIndex];
+        const patchId = propertySelect.parentElement.parentElement.dataset.patchId;
+        const propertyValue = (property === "state") ? selectedOption.text
+                            : (selectedOption.value === "*") ? null : selectedOption.value
+        const data = {};
+        data[property] = propertyValue;
+        return {
+            "url": `/api/patches/${patchId}/`,
+            "data": data,
+        };
+    }
+});
diff --git a/patchwork/templates/patchwork/partials/patch-list.html b/patchwork/templates/patchwork/partials/patch-list.html
index aeb26aa8..7d2d2cc9 100644
--- a/patchwork/templates/patchwork/partials/patch-list.html
+++ b/patchwork/templates/patchwork/partials/patch-list.html
@@ -5,7 +5,7 @@
 {% load static %}
 
 {% block headers %}
-  <script src="{% static "js/patch-list.js" %}"></script>
+  <script type="module" src="{% static "js/patch-list.js" %}"></script>
 {% endblock %}
 
 {% include "patchwork/partials/filters.html" %}
@@ -187,8 +187,37 @@
    <td id="patch-checks:{{patch.id}}" class="text-nowrap">{{ patch|patch_checks }}</td>
    <td id="patch-date:{{patch.id}}" class="text-nowrap">{{ patch.date|date:"Y-m-d" }}</td>
    <td id="patch-submitter:{{patch.id}}">{{ patch.submitter|personify:project }}</td>
-   <td id="patch-delegate:{{patch.id}}">{{ patch.delegate.username }}</td>
-   <td id="patch-state:{{patch.id}}">{{ patch.state }}</td>
+   <td id="patch-delegate:{{patch.id}}">
+    {% if patch.is_editable %}
+      <select class="change-property-delegate" value="delegate">
+        {% if not patch.delegate.username %}
+          <option value="*" selected>No delegate</option>
+        {% else %}
+          <option value="*" selected>{{ patch.delegate.username }}</option>
+        {% endif %}
+        {% for maintainer in maintainers %}
+          <option value="{{ maintainer.id }}">{{ maintainer.name }}</option>
+        {% endfor %}
+      </select>
+    {% else %}
+      {{ patch.delegate.username }}
+    {% endif %}
+   </td>
+   <td id="patch-state:{{patch.id}}">
+    {% if patch.is_editable %}
+      <select class="change-property-state" value="state">
+        {% for state in states %}
+          {% if state.name == patch.state.name %}
+            <option value="{{ patch.state.ordering }}" selected>{{ patch.state }}</option>
+          {% else %}
+            <option value="{{ state.ordering  }}">{{ state.name }}</option>
+          {% endif %}
+        {% endfor %}
+      </select>
+    {% else %}
+      {{ patch.state }}
+    {% endif %}
+   </td>
   </tr>
  {% empty %}
   <tr>
diff --git a/patchwork/views/__init__.py b/patchwork/views/__init__.py
index 5da8046d..d41c4609 100644
--- a/patchwork/views/__init__.py
+++ b/patchwork/views/__init__.py
@@ -16,6 +16,7 @@ from patchwork.models import Bundle
 from patchwork.models import BundlePatch
 from patchwork.models import Patch
 from patchwork.models import Project
+from patchwork.models import State
 from patchwork.models import Check
 from patchwork.paginator import Paginator
 
@@ -177,6 +178,8 @@ def generic_list(request, project, view, view_args=None, filter_settings=None,
         'project': project,
         'projects': Project.objects.all(),
         'filters': filters,
+        'maintainers': project.maintainer_project.all(),
+        'states': State.objects.all(),
     }
 
     # pagination
@@ -287,6 +290,9 @@ def generic_list(request, project, view, view_args=None, filter_settings=None,
         Prefetch('check_set', queryset=Check.objects.only(
             'context', 'user_id', 'patch_id', 'state', 'date')))
 
+    for patch in patches:
+        patch.is_editable = patch.is_editable(user)
+
     paginator = Paginator(request, patches)
 
     context.update({
-- 
2.33.0.rc2.250.ged5fa647cd-goog



More information about the Patchwork mailing list