From 0aa298f20d0c454f828366a155d3fa8fa53665f9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= <fpeters@entrouvert.com>
Date: Mon, 1 Nov 2021 20:56:03 +0100
Subject: [PATCH] workflows: allow generated timeout to be in "human time"
 (#54930)

---
 tests/workflow/test_all.py | 64 ++++++++++++++++++++++++++++++++++++++
 wcs/wf/jump.py             | 20 ++++++++++--
 2 files changed, 81 insertions(+), 3 deletions(-)

diff --git a/tests/workflow/test_all.py b/tests/workflow/test_all.py
index c529247fe..d234a570f 100644
--- a/tests/workflow/test_all.py
+++ b/tests/workflow/test_all.py
@@ -2430,6 +2430,65 @@ def test_timeout(two_pubs):
     _apply_timeouts(two_pubs)
 
 
+def test_timeout_with_humantime_template(two_pubs):
+    workflow = Workflow(name='timeout')
+    st1 = workflow.add_status('Status1', 'st1')
+    workflow.add_status('Status2', 'st2')
+
+    jump = JumpWorkflowStatusItem()
+    jump.id = '_jump'
+    jump.by = ['_submitter', '_receiver']
+    jump.timeout = '{{ 30 }} minutes'
+    jump.status = 'st2'
+    st1.items.append(jump)
+    jump.parent = st1
+
+    workflow.store()
+
+    formdef = FormDef()
+    formdef.name = 'baz'
+    formdef.fields = []
+    formdef.workflow_id = workflow.id
+    assert formdef.get_workflow().id == workflow.id
+    formdef.store()
+
+    formdata = formdef.data_class()()
+    formdata.just_created()
+    formdata.store()
+    formdata_id = formdata.id
+
+    _apply_timeouts(two_pubs)
+    assert formdef.data_class().get(formdata_id).status == 'wf-st1'  # no change
+
+    rewind(formdata, seconds=40 * 60)
+    formdata.store()
+    _apply_timeouts(two_pubs)
+    assert formdef.data_class().get(formdata_id).status == 'wf-st2'
+
+    # invalid timeout value
+    jump.timeout = '{{ 30 }} plop'
+    workflow.store()
+    formdef.refresh_from_storage()
+
+    formdata = formdef.data_class()()
+    formdata.just_created()
+    formdata.store()
+    formdata_id = formdata.id
+
+    if two_pubs.is_using_postgresql():
+        two_pubs.loggederror_class.wipe()
+
+    rewind(formdata, seconds=40 * 60)
+    formdata.store()
+    _apply_timeouts(two_pubs)
+    assert formdef.data_class().get(formdata_id).status == 'wf-st1'  # no change
+
+    if two_pubs.is_using_postgresql():
+        assert two_pubs.loggederror_class.count() == 1
+        logged_error = two_pubs.loggederror_class.select()[0]
+        assert logged_error.summary == "Error in timeout value ('30 plop')"
+
+
 def test_legacy_timeout(pub):
     workflow = Workflow(name='timeout')
     st1 = workflow.add_status('Status1', 'st1')
@@ -2541,6 +2600,9 @@ def test_jump_missing_previous_mark(two_pubs):
         pytest.skip('this requires SQL')
         return
 
+    FormDef.wipe()
+    Workflow.wipe()
+
     workflow = Workflow(name='jump-mark')
     st1 = workflow.add_status('Status1', 'st1')
 
@@ -5482,6 +5544,8 @@ def test_workflow_action_condition(two_pubs):
     formdef.workflow_id = workflow.id
     formdef.store()
 
+    formdef.data_class().wipe()
+
     formdata1 = formdef.data_class()()
     formdata1.data = {'1': 'foo'}
     formdata1.just_created()
diff --git a/wcs/wf/jump.py b/wcs/wf/jump.py
index 6308884ef..5ade8d244 100644
--- a/wcs/wf/jump.py
+++ b/wcs/wf/jump.py
@@ -278,11 +278,25 @@ class JumpWorkflowStatusItem(WorkflowStatusJumpItem):
             must_jump = must_jump and triggered
 
         if self.timeout:
-            timeout = float(self.compute(self.timeout))
+            timeout_str = self.compute(self.timeout)
+            try:
+                timeout_seconds = float(timeout_str)
+            except ValueError:
+                timeout_seconds = humanduration2seconds(timeout_str)
+                if timeout_seconds == 0:
+                    get_publisher().record_error(
+                        _('Error in timeout value (%r)') % timeout_str,
+                        formdata=formdata,
+                        formdef=formdata.formdef,
+                        workflow=formdata.formdef.workflow,
+                        notify=False,
+                        record=True,
+                    )
+                    must_jump = False
             last = formdata.last_update_time
-            if last:
+            if last and timeout_seconds:
                 diff = time.time() - time.mktime(last)
-                must_jump = (diff > timeout) and must_jump
+                must_jump = (diff > timeout_seconds) and must_jump
 
         return must_jump
 
-- 
2.33.1

