From f86e5a09685e20a7edb3ec1ab270b2af913e9555 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= <fpeters@entrouvert.com>
Date: Mon, 17 May 2021 18:14:48 +0200
Subject: [PATCH] workflows: add jump action on status lines (#54024)

---
 tests/admin_pages/test_workflow.py            | 27 +++++++++++++++++++
 wcs/qommon/static/css/dc2/admin.scss          |  6 ++---
 .../wcs/backoffice/workflow-status.html       |  3 +++
 wcs/wf/wscall.py                              |  4 +++
 wcs/workflows.py                              | 13 +++++++++
 5 files changed, 50 insertions(+), 3 deletions(-)

diff --git a/tests/admin_pages/test_workflow.py b/tests/admin_pages/test_workflow.py
index db62e606f..f63048823 100644
--- a/tests/admin_pages/test_workflow.py
+++ b/tests/admin_pages/test_workflow.py
@@ -896,6 +896,33 @@ def test_workflows_edit_jump_timeout(pub):
     assert resp.text.count('ComputedExpressionWidget') == 1
 
 
+def test_workflows_jump_target_links(pub):
+    create_superuser(pub)
+    Workflow.wipe()
+    workflow = Workflow(name='foo')
+    st1 = workflow.add_status(name='baz')
+    st2 = workflow.add_status(name='bar')
+
+    jump = JumpWorkflowStatusItem()
+    jump.id = '_jump'
+    jump.timeout = 86400
+    jump.status = st2.id
+    st1.items.append(jump)
+    jump.parent = st1
+
+    workflow.store()
+
+    app = login(get_app(pub))
+    resp = app.get('/backoffice/workflows/1/status/1/')
+    assert resp.pyquery.find('.jump a').attr.href == 'http://example.net/backoffice/workflows/1/status/2/'
+
+    for no_target in ('_previous', '_broken', None):
+        jump.status = no_target
+        workflow.store()
+        resp = app.get('/backoffice/workflows/1/status/1/')
+        assert not resp.pyquery.find('.jump a')
+
+
 def test_workflows_edit_sms_action(pub):
     create_superuser(pub)
     Workflow.wipe()
diff --git a/wcs/qommon/static/css/dc2/admin.scss b/wcs/qommon/static/css/dc2/admin.scss
index 7527a3674..d0d0995be 100644
--- a/wcs/qommon/static/css/dc2/admin.scss
+++ b/wcs/qommon/static/css/dc2/admin.scss
@@ -1,7 +1,7 @@
 $primary-color: #386ede;
 $secondary-color: #00d6eb;
 $string-color: str-slice($primary-color + '', 2);
-$actions: add, duplicate, edit, remove;
+$actions: add, duplicate, edit, jump, remove;
 
 @mixin clearfix {
 	&::after {
@@ -2023,7 +2023,7 @@ div#side { // w.c.s. steps in backoffice submission
 		&.view {
 			margin-top: 2px;
 		}
-		&.remove, &.add, &.edit, &.duplicate {
+		&.remove, &.add, &.edit, &.duplicate, &.jump {
 			padding: 0;
 			border: 0;
 			background: transparent;
@@ -2042,7 +2042,7 @@ div#side { // w.c.s. steps in backoffice submission
 				background: url(/static/css/icons/action-#{$action}.small.#{$string-color}.png) center center no-repeat;
 				background-size: 20px;
 				background-image: url(/static/css/icons/action-#{$action}.small.#{$string-color}.png),
-				url(/static/css/icons/action-#{$action}.hover.png);
+						  url(/static/css/icons/action-#{$action}.hover.png);
 				&:hover {
 					background-image: url(/static/css/icons/action-#{$action}.hover.png);
 				}
diff --git a/wcs/templates/wcs/backoffice/workflow-status.html b/wcs/templates/wcs/backoffice/workflow-status.html
index 7e4e4b64f..c22749c38 100644
--- a/wcs/templates/wcs/backoffice/workflow-status.html
+++ b/wcs/templates/wcs/backoffice/workflow-status.html
@@ -32,6 +32,9 @@
   <li class="biglistitem" id="itemId_{{ item.id }}">
     <a href="items/{{ item.id }}/">{{ item.render_as_line|safe }}</a>
   <p class="commands">
+  {% with item.get_target_status_url as url %}
+  {% if url %}<span class="jump"><a href="{{ url }}" title="{% trans "Go to Target" %}">{% trans "Go to Target" %}</a></span>{% endif %}
+  {% endwith %}
   {% if not workflow.is_readonly %}
   <span class="edit"><a href="items/{{ item.id }}/" title="{% trans "Edit" %}">{% trans "Edit" %}</a></span>
   <span class="remove"><a href="items/{{ item.id }}/delete" rel="popup" title="{% trans "Delete" %}">{% trans "Delete" %}</a></span>
diff --git a/wcs/wf/wscall.py b/wcs/wf/wscall.py
index cf030dbb2..0da4bf9be 100644
--- a/wcs/wf/wscall.py
+++ b/wcs/wf/wscall.py
@@ -521,6 +521,10 @@ class WebserviceCallStatusItem(WorkflowStatusItem):
         formdata.store()
         raise AbortActionException()
 
+    def get_target_status_url(self):
+        # do not return anything as target status are accessory
+        return None
+
     def get_target_status(self):
         # always return self status as a target so it's included in the
         # workflow visualisation as a "normal" action, in addition to
diff --git a/wcs/workflows.py b/wcs/workflows.py
index 5d5b24357..7f93c054e 100644
--- a/wcs/workflows.py
+++ b/wcs/workflows.py
@@ -1680,6 +1680,9 @@ class WorkflowStatus:
                 continue
             yield item
 
+    def get_admin_url(self):
+        return self.parent.get_admin_url() + 'status/%s/' % self.id
+
     def evaluate_live_form(self, form, filled, user):
         for item in self.get_active_items(form, filled, user):
             item.evaluate_live_form(form, filled, user)
@@ -2187,6 +2190,16 @@ class WorkflowStatusItem(XmlSerialisable):
     def get_substitution_variables(self, formdata):
         return {}
 
+    def get_target_status_url(self):
+        if not getattr(self, 'status', None) or self.status == '_previous':
+            return None
+
+        targets = [x for x in self.parent.parent.possible_status if x.id == self.status]
+        if not targets:
+            return None
+
+        return targets[0].get_admin_url()
+
     def get_target_status(self, formdata=None):
         """Returns a list of status this item can lead to."""
         if not getattr(self, 'status', None):
-- 
2.32.0.rc0

