From 70be50161199f06fbffd2c96a85b06a619779f2e Mon Sep 17 00:00:00 2001 From: "Mr. Senko" Date: Wed, 7 Oct 2020 16:36:59 +0300 Subject: [PATCH] Implement drag & reorder functionality & new API NOTES: - uses Sortable.js for drag & reorder - manual sorting button is located after A-Z button in toolbar - NEW API method TestPlan.update_case_order() - Manual reorder & API require the testcases.change_testcaseplan permission - toolbarDropdowns() is called from within toolbarEvents() b/c it really doesn't make sense to have this as stand-alone function - toolbarEvents() is moved within the TestCases.filter() callback b/c we need the list of test cases to be fully initialized so we can get the initial sorting order and use it later WARNINGS: - may want to unbind the on-click event for rows if users are having problems with the rows expanding while dragging them - the page doesn't use sortkey for initially displaying the rows of test cases because this field isn't available in the API result. Specifically sortkey is a field in the TestCasePlan model, which is a *through* model and the .filter() API is querying the TestCase model instead. --- tcms/package.json | 1 + tcms/rpc/api/testplan.py | 21 +++++++++++ tcms/testplans/static/testplans/js/get.js | 40 ++++++++++++++++++--- tcms/testplans/templates/testplans/get.html | 15 ++++++-- 4 files changed, 71 insertions(+), 6 deletions(-) diff --git a/tcms/package.json b/tcms/package.json index b66dee6879..6f6249fc57 100644 --- a/tcms/package.json +++ b/tcms/package.json @@ -5,6 +5,7 @@ "patternfly": "3.59.5", "prismjs": "1.21.0", "simplemde": "1.11.2", + "sortablejs": "1.12.0", "typeahead.js": "0.11.1" } } diff --git a/tcms/rpc/api/testplan.py b/tcms/rpc/api/testplan.py index eb31c51732..5541255ba0 100644 --- a/tcms/rpc/api/testplan.py +++ b/tcms/rpc/api/testplan.py @@ -17,6 +17,7 @@ 'add_case', 'remove_case', + 'update_case_order', 'add_tag', 'remove_tag', @@ -202,6 +203,26 @@ def remove_case(plan_id, case_id): TestCasePlan.objects.filter(case=case_id, plan=plan_id).delete() +@permissions_required('testcases.change_testcaseplan') +@rpc_method(name='TestPlan.update_case_order') +def update_case_order(plan_id, case_id, sortkey): + """ + .. function:: RPC TestPlan.update_case_order(plan_id, case_id, sortkey) + + Update sortkey which controls display order of the given test case in + the given test plan. + + :param plan_id: PK of TestPlan holding the selected TestCase + :type plan_id: int + :param case_id: PK of TestCase to be modified + :type case_id: int + :param sortkey: Ordering value, e.g. 10, 20, 30 + :type sortkey: int + :raises PermissionDenied: if missing *testcases.delete_testcaseplan* permission + """ + TestCasePlan.objects.filter(case=case_id, plan=plan_id).update(sortkey=sortkey) + + @permissions_required('attachments.view_attachment') @rpc_method(name='TestPlan.list_attachments') def list_attachments(plan_id, **kwargs): diff --git a/tcms/testplans/static/testplans/js/get.js b/tcms/testplans/static/testplans/js/get.js index b7d337dfa3..8c473a686f 100644 --- a/tcms/testplans/static/testplans/js/get.js +++ b/tcms/testplans/static/testplans/js/get.js @@ -24,13 +24,14 @@ $(document).ready(function() { drawTestCases(allTestCases, testPlanId, permissions); treeViewBind(); - // b/c treeViewBind() will modfy handlers/visibility for both + // b/c treeViewBind() will modify handlers/visibility for both // test plan family tree and the test cases tree adjustTestPlanFamilyTree(); - }); - toolbarDropdowns(); - toolbarEvents(testPlanId, permissions); + // b/c drag & reorder needs the initial order of test cases and + // they may not be fully loaded when Sortable() is initialized! + toolbarEvents(testPlanId, permissions); + }); collapseDocumentText(); }); @@ -278,6 +279,7 @@ function attachEvents(testCases, testPlanId, permissions) { } function toolbarEvents(testPlanId, permissions) { + toolbarDropdowns(); $('.js-checkbox-toolbar').click(function(ev) { const isChecked = ev.target.checked; @@ -320,6 +322,36 @@ function toolbarEvents(testPlanId, permissions) { sortTestCases(); }); + + // always initialize the sortable list however you can only + // move items using the handle icon on the left which becomes + // visible only when the manual sorting button is clicked + const manualSortList = new Sortable($("#testcases-list")[0], { + animation: 150, + handle: '.handle', + dataIdAttr: 'data-testcase-pk', + }); + + // IMPORTANT: this is not empty b/c Sortable() is initialized *after* + // all of the test cases have been rendered !!! + const initialOrder = manualSortList.toArray(); + + $('.js-toolbar-manual-sort').click(function(event) { + $(this).blur(); + $('.js-toolbar-manual-sort').find('span').toggleClass(['fa-sort', 'fa-check-square']); + $('.js-testcase-sort-handler, .js-testcase-expand-arrow, .js-testcase-checkbox').toggleClass('hidden'); + + const currentOrder = manualSortList.toArray(); + + // rows have been rearranged and the results must be committed to the DB + if (currentOrder.join() !== initialOrder.join()) { + currentOrder.forEach(function(tc_pk, index) { + jsonRPC('TestPlan.update_case_order', [testPlanId, tc_pk, index*10], function(result) {}); + }); + } + }); + + $('.js-toolbar-priority').click(function(ev) { let selectedCases = getSelectedTestCases(); diff --git a/tcms/testplans/templates/testplans/get.html b/tcms/testplans/templates/testplans/get.html index 48b80507b8..ba0f2fbce6 100644 --- a/tcms/testplans/templates/testplans/get.html +++ b/tcms/testplans/templates/testplans/get.html @@ -134,6 +134,12 @@

+ + {% if perms.testcases.change_testcaseplan %} + + {% endif %}
@@ -195,6 +201,7 @@

{% if perms.testcases.change_testcase %}
  • {% trans 'Reviewer' %}
  • {% endif %} +

    @@ -240,10 +247,13 @@

    -
    + +
    -
    +
    @@ -359,6 +369,7 @@

  • {% trans 'Reviewer' %}
  • +