From 6b557fe60110ec579f6fb6a94872aa073d200780 Mon Sep 17 00:00:00 2001 From: Mfon Eti-mfon Date: Fri, 15 Oct 2021 15:25:19 +0100 Subject: [PATCH 1/3] (api) Make duration fields available as filters in the /cases/search/ page Refs #1923 --- tcms/rpc/api/testcase.py | 11 +++++ tcms/rpc/tests/test_testcase.py | 29 ++++++++++++ tcms/testcases/forms.py | 12 +++++ tcms/testcases/static/testcases/js/search.js | 47 ++++++++++++++++++- .../testcases/templates/testcases/search.html | 26 ++++++++++ 5 files changed, 124 insertions(+), 1 deletion(-) diff --git a/tcms/rpc/api/testcase.py b/tcms/rpc/api/testcase.py index 1334a3285f..487ef5b255 100644 --- a/tcms/rpc/api/testcase.py +++ b/tcms/rpc/api/testcase.py @@ -5,6 +5,7 @@ from django.db.models.functions import Coalesce from django.forms import EmailField, ValidationError from django.forms.models import model_to_dict +from django.utils.dateparse import parse_duration from modernrpc.core import REQUEST_KEY, rpc_method from tcms.core import helpers @@ -279,9 +280,19 @@ def filter(query=None): # pylint: disable=redefined-builtin :return: Serialized list of :class:`tcms.testcases.models.TestCase` objects. :rtype: list(dict) """ + if query is None: query = {} + if "setup_duration" in query: + query["setup_duration"] = parse_duration(query["setup_duration"]) + + if "testing_duration" in query: + query["testing_duration"] = parse_duration(query["testing_duration"]) + + if "expected_duration" in query: + query["expected_duration"] = parse_duration(query["expected_duration"]) + qs = ( TestCase.objects.annotate( expected_duration=Coalesce("setup_duration", timedelta(0)) diff --git a/tcms/rpc/tests/test_testcase.py b/tcms/rpc/tests/test_testcase.py index ca2e00a37d..3a18f17430 100644 --- a/tcms/rpc/tests/test_testcase.py +++ b/tcms/rpc/tests/test_testcase.py @@ -223,6 +223,35 @@ def test_duration_properties_in_result( self.assertEqual(result[0]["testing_duration"], testing_duration) self.assertEqual(result[0]["expected_duration"], expected_duration) + def test_filter_by_setup_duration(self): + case = TestCaseFactory(setup_duration=timedelta(seconds=45)) + + result = self.rpc_client.TestCase.filter({"setup_duration": "0:00:45"}) + + self.assertIsNotNone(result) + self.assertEqual(len(result), 1) + self.assertEqual(result[0]["id"], case.pk) + + def test_filter_by_testing_duration(self): + case = TestCaseFactory(testing_duration=timedelta(minutes=2)) + + result = self.rpc_client.TestCase.filter({"testing_duration": "0:02:00"}) + + self.assertIsNotNone(result) + self.assertEqual(len(result), 1) + self.assertEqual(result[0]["id"], case.pk) + + def test_filter_by_expected_duration(self): + case = TestCaseFactory( + setup_duration=timedelta(seconds=45), testing_duration=timedelta(minutes=2) + ) + + result = self.rpc_client.TestCase.filter({"expected_duration": "0:02:45"}) + + self.assertIsNotNone(result) + self.assertEqual(len(result), 1) + self.assertEqual(result[0]["id"], case.pk) + class TestUpdate(APITestCase): non_existing_username = "FakeUsername" diff --git a/tcms/testcases/forms.py b/tcms/testcases/forms.py index d3857f963e..5af2b68ae9 100644 --- a/tcms/testcases/forms.py +++ b/tcms/testcases/forms.py @@ -124,6 +124,18 @@ class Meta: widget=forms.CheckboxSelectMultiple(), required=False, ) + setup_duration = forms.DurationField( + widget=DurationWidget(), + required=False, + ) + testing_duration = forms.DurationField( + widget=DurationWidget(), + required=False, + ) + expected_duration = forms.DurationField( + widget=DurationWidget(), + required=False, + ) def populate(self, product_id=None): if product_id: diff --git a/tcms/testcases/static/testcases/js/search.js b/tcms/testcases/static/testcases/js/search.js index 758f0da3b5..3fa2e4b03e 100644 --- a/tcms/testcases/static/testcases/js/search.js +++ b/tcms/testcases/static/testcases/js/search.js @@ -52,6 +52,21 @@ function pre_process_data (data, callback) { }) } +function formatDuration(seconds) { + let numSecondsInDay = 24 * 60 * 60; + let days = Math.floor(seconds / numSecondsInDay); + let rest = seconds % numSecondsInDay; + + let date = new Date(0); + date.setSeconds(rest); + let timeString = date.toISOString().substr(11, 8); + + if (days) { + return `${days} ${timeString}`; + } + return `${timeString}`; +} + $(document).ready(function () { const table = $('#resultsTable').DataTable({ pageLength: $('#navbar').data('defaultpagesize'), @@ -106,6 +121,18 @@ $(document).ready(function () { params.is_automated = false }; + if (!['', '0'].includes($('#id_setup_duration').val())) { + params.setup_duration = $('#id_setup_duration').val() + }; + + if (!['', '0'].includes($('#id_testing_duration').val())) { + params.testing_duration = $('#id_testing_duration').val() + }; + + if (!['', '0'].includes($('#id_expected_duration').val())) { + params.expected_duration = $('#id_expected_duration').val() + }; + const text = $('#id_text').val() if (text) { params.text__icontains = text @@ -145,7 +172,25 @@ $(document).ready(function () { { data: 'case_status__name' }, { data: 'is_automated' }, { data: 'author__username' }, - { data: 'tag_names' } + { data: 'tag_names' }, + { + data: 'setup_duration', + render: function(data, type, full, meta) { + return formatDuration(data); + } + }, + { + data: 'testing_duration', + render: function(data, type, full, meta) { + return formatDuration(data); + } + }, + { + data: 'expected_duration', + render: function(data, type, full, meta) { + return formatDuration(data); + } + } ], dom: 't', language: { diff --git a/tcms/testcases/templates/testcases/search.html b/tcms/testcases/templates/testcases/search.html index 05d7311fc4..444b82cb97 100644 --- a/tcms/testcases/templates/testcases/search.html +++ b/tcms/testcases/templates/testcases/search.html @@ -2,7 +2,10 @@ {% load i18n %} {% load static %} +{% block head %} +{{ form.media }} {% block title %}{% trans "Search test cases" %}{% endblock %} +{% endblock %} {% block contents %}
@@ -128,6 +131,29 @@
+
+ +
+
+ {{ form.setup_duration }} +
+
+ + +
+
+ {{ form.testing_duration }} +
+
+ + +
+
+ {{ form.expected_duration }} +
+
+
+
From 397928c182ccd1a185dd6fa2bc01f8b80187d7ed Mon Sep 17 00:00:00 2001 From: Mfon Eti-mfon Date: Fri, 15 Oct 2021 20:03:18 +0100 Subject: [PATCH 2/3] Corrections --- tcms/testcases/static/testcases/js/search.js | 14 ++++++++------ tcms/testcases/templates/testcases/search.html | 8 ++++++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/tcms/testcases/static/testcases/js/search.js b/tcms/testcases/static/testcases/js/search.js index 3fa2e4b03e..fe3aaabb4d 100644 --- a/tcms/testcases/static/testcases/js/search.js +++ b/tcms/testcases/static/testcases/js/search.js @@ -61,10 +61,12 @@ function formatDuration(seconds) { date.setSeconds(rest); let timeString = date.toISOString().substr(11, 8); - if (days) { - return `${days} ${timeString}`; + if (days === 0) { + return timeString; } - return `${timeString}`; + + let dayOrDays = days === 1 ? 'day' : 'days'; + return `${days} ${dayOrDays}, ${timeString}`; } $(document).ready(function () { @@ -121,15 +123,15 @@ $(document).ready(function () { params.is_automated = false }; - if (!['', '0'].includes($('#id_setup_duration').val())) { + if ($('#id_setup_duration').val() !== '0') { params.setup_duration = $('#id_setup_duration').val() }; - if (!['', '0'].includes($('#id_testing_duration').val())) { + if (($('#id_testing_duration').val() !== '0')) { params.testing_duration = $('#id_testing_duration').val() }; - if (!['', '0'].includes($('#id_expected_duration').val())) { + if (($('#id_expected_duration').val() !== '0')) { params.expected_duration = $('#id_expected_duration').val() }; diff --git a/tcms/testcases/templates/testcases/search.html b/tcms/testcases/templates/testcases/search.html index 444b82cb97..0301a3274e 100644 --- a/tcms/testcases/templates/testcases/search.html +++ b/tcms/testcases/templates/testcases/search.html @@ -3,10 +3,11 @@ {% load static %} {% block head %} -{{ form.media }} -{% block title %}{% trans "Search test cases" %}{% endblock %} + {{ form.media }} {% endblock %} +{% block title %}{% trans "Search test cases" %}{% endblock %} + {% block contents %}
@@ -177,6 +178,9 @@ {% trans "Automated" %} {% trans "Author" %} {% trans "Tags" %} + {% trans "Setup duration" %} + {% trans "Testing duration" %} + {% trans "Expected duration" %} From 02c9ceee835327e9617b96e0d0f3cfbf12a6a464 Mon Sep 17 00:00:00 2001 From: Mfon Eti-mfon Date: Fri, 15 Oct 2021 21:11:08 +0100 Subject: [PATCH 3/3] More corrections --- tcms/testcases/static/testcases/js/search.js | 32 ++++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tcms/testcases/static/testcases/js/search.js b/tcms/testcases/static/testcases/js/search.js index fe3aaabb4d..a1d195d845 100644 --- a/tcms/testcases/static/testcases/js/search.js +++ b/tcms/testcases/static/testcases/js/search.js @@ -52,21 +52,21 @@ function pre_process_data (data, callback) { }) } -function formatDuration(seconds) { - let numSecondsInDay = 24 * 60 * 60; - let days = Math.floor(seconds / numSecondsInDay); - let rest = seconds % numSecondsInDay; +function formatDuration (seconds) { + const numSecondsInDay = 24 * 60 * 60 + const days = Math.floor(seconds / numSecondsInDay) + const rest = seconds % numSecondsInDay - let date = new Date(0); - date.setSeconds(rest); - let timeString = date.toISOString().substr(11, 8); + const date = new Date(0) + date.setSeconds(rest) + const timeString = date.toISOString().substr(11, 8) if (days === 0) { - return timeString; + return timeString } - let dayOrDays = days === 1 ? 'day' : 'days'; - return `${days} ${dayOrDays}, ${timeString}`; + const dayOrDays = days === 1 ? 'day' : 'days' + return `${days} ${dayOrDays}, ${timeString}` } $(document).ready(function () { @@ -177,20 +177,20 @@ $(document).ready(function () { { data: 'tag_names' }, { data: 'setup_duration', - render: function(data, type, full, meta) { - return formatDuration(data); + render: function (data, type, full, meta) { + return formatDuration(data) } }, { data: 'testing_duration', - render: function(data, type, full, meta) { - return formatDuration(data); + render: function (data, type, full, meta) { + return formatDuration(data) } }, { data: 'expected_duration', - render: function(data, type, full, meta) { - return formatDuration(data); + render: function (data, type, full, meta) { + return formatDuration(data) } } ],