From 36ba113e3f02ec98dfa8abd0a90b3d6fbc487c95 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Tue, 11 Oct 2022 12:00:39 +0200
Subject: [PATCH 2/2] workflow: add a trace on created form/carddata by wf
 action (#70000)

---
 tests/backoffice_pages/test_form_inspect.py | 47 ++++++++++++++++++---
 tests/workflow/test_carddata.py             | 11 ++++-
 tests/workflow/test_formdata.py             | 15 +++++--
 wcs/wf/create_formdata.py                   | 27 +++++++++++-
 4 files changed, 90 insertions(+), 10 deletions(-)

diff --git a/tests/backoffice_pages/test_form_inspect.py b/tests/backoffice_pages/test_form_inspect.py
index 9a7bc8142..45c60f3b1 100644
--- a/tests/backoffice_pages/test_form_inspect.py
+++ b/tests/backoffice_pages/test_form_inspect.py
@@ -617,6 +617,14 @@ def test_inspect_page_with_related_objects(pub):
 def test_inspect_page_actions_traces(pub):
     create_user(pub, is_admin=True)
 
+    FormDef.wipe()
+    target_formdef = FormDef()
+    target_formdef.name = 'target form'
+    target_formdef.fields = [
+        fields.StringField(id='0', label='string', varname='foo_string'),
+    ]
+    target_formdef.store()
+
     workflow = Workflow.get_default_workflow()
     workflow.id = '2'
     workflow.criticality_levels = [
@@ -629,9 +637,14 @@ def test_inspect_page_actions_traces(pub):
     trigger = action.append_trigger('timeout')
     trigger.anchor = 'creation'
     trigger.timeout = '2'
+    create = workflow.possible_status[1].add_action('create_formdata', id='_create', prepend=True)
+    create.varname = 'create_formdata'
+    create.formdef_slug = target_formdef.url_name
+    create.mappings = [
+        Mapping(field_id='0', expression='foo bar'),
+    ]
     workflow.store()
 
-    FormDef.wipe()
     formdef = FormDef()
     formdef.name = 'form title'
     formdef.workflow_id = workflow.id
@@ -651,7 +664,8 @@ def test_inspect_page_actions_traces(pub):
     formdata.refresh_from_storage()
     assert formdata.get_criticality_level_object().name == 'yellow'
 
-    resp = login(get_app(pub)).get(formdata.get_url(backoffice=True), status=200)
+    app = login(get_app(pub))
+    resp = app.get(formdata.get_url(backoffice=True), status=200)
     resp = resp.click('Data Inspector')
     assert '>Actions Tracing</' in resp
     assert [x.text for x in resp.pyquery('#inspect-timeline strong')] == ['Just Submitted', 'New']
@@ -659,6 +673,7 @@ def test_inspect_page_actions_traces(pub):
         'Email',
         'Email',
         'Automatic Jump',
+        'New Form Creation',
         'Criticality Levels',
     ]
     action_links = [x.attrib['href'] for x in resp.pyquery('#inspect-timeline a.tracing-link')]
@@ -667,22 +682,44 @@ def test_inspect_page_actions_traces(pub):
         action_links[1]
         == 'http://example.net/backoffice/workflows/2/status/just_submitted/items/_notify_new_receiver_email/'
     )
+    assert action_links[-2] == 'http://example.net/backoffice/workflows/2/status/new/items/_create/'
     assert action_links[-1] == 'http://example.net/backoffice/workflows/2/global-actions/1/items/1/'
 
     # check details are available
     assert 'Email (to Recipient)' in [
         x.text_content().split(' ', 2)[-1] for x in resp.pyquery('#inspect-timeline li')
     ]
+
+    # check links on target formdata
+    target_formdata = target_formdef.data_class().select()[0]
+    resp = app.get(target_formdata.get_url(backoffice=True) + 'inspect')
+    assert '>Actions Tracing</' in resp
+    assert [x.text for x in resp.pyquery('#inspect-timeline strong')] == ['Just Submitted', 'New']
+    assert [x.text for x in resp.pyquery('#inspect-timeline a.tracing-link') if x.text] == [
+        'New Form Creation',
+        'Email',
+        'Email',
+        'Automatic Jump',
+    ]
+    action_links = [x.attrib['href'] for x in resp.pyquery('#inspect-timeline a.tracing-link')]
+    assert action_links[0] == 'http://example.net/backoffice/workflows/_default/status/just_submitted/'
+    assert (
+        action_links[1] == 'http://example.net/backoffice/workflows/2/status/new/items/_create/'
+    )  # link on external workflow
+    assert 'New Form Creation (external workflow)' in [
+        x.text_content().split(' ', 2)[-1] for x in resp.pyquery('#inspect-timeline li')
+    ]
+
     # and there's no crash when part of the workflow changes
     workflow.global_actions = []
     workflow.store()
-    get_app(pub).get(formdata.get_url(backoffice=True) + 'inspect')
+    app.get(formdata.get_url(backoffice=True) + 'inspect')
     workflow.possible_status[0].items = []
     workflow.store()
-    get_app(pub).get(formdata.get_url(backoffice=True) + 'inspect')
+    app.get(formdata.get_url(backoffice=True) + 'inspect')
     workflow.possible_status = []
     workflow.store()
-    get_app(pub).get(formdata.get_url(backoffice=True) + 'inspect')
+    app.get(formdata.get_url(backoffice=True) + 'inspect')
 
 
 def test_inspect_page_missing_carddef_error(pub):
diff --git a/tests/workflow/test_carddata.py b/tests/workflow/test_carddata.py
index 7a97d00a7..16de05ea1 100644
--- a/tests/workflow/test_carddata.py
+++ b/tests/workflow/test_carddata.py
@@ -11,7 +11,7 @@ from wcs.formdef import FormDef
 from wcs.qommon.http_request import HTTPRequest
 from wcs.qommon.upload_storage import PicklableUpload
 from wcs.wf.create_formdata import Mapping
-from wcs.workflows import Workflow
+from wcs.workflows import ActionsTracingEvolutionPart, Workflow
 
 from ..utilities import clean_temporary_pub, create_temporary_pub
 
@@ -89,6 +89,15 @@ def test_create_carddata(pub):
     formdata.perform_workflow()
 
     assert carddef.data_class().count() == 1
+    # check evolutions & tracing
+    carddata = carddef.data_class().select()[0]
+    assert isinstance(carddata.evolution[0].parts[0], ActionsTracingEvolutionPart)
+    assert carddata.evolution[0].parts[0].actions[0][1:] == (
+        'create_carddata',  # item key
+        '_create',  # item id
+        'new',  # status
+        wf.id,  # workflow id
+    )
 
     errors = pub.loggederror_class.select()
     assert len(errors) == 2
diff --git a/tests/workflow/test_formdata.py b/tests/workflow/test_formdata.py
index 475d0c16f..d9f420438 100644
--- a/tests/workflow/test_formdata.py
+++ b/tests/workflow/test_formdata.py
@@ -7,7 +7,7 @@ from wcs.fields import EmailField, ItemField, StringField
 from wcs.formdef import FormDef
 from wcs.qommon.http_request import HTTPRequest
 from wcs.wf.create_formdata import Mapping
-from wcs.workflows import Workflow
+from wcs.workflows import ActionsTracingEvolutionPart, Workflow
 
 from ..utilities import clean_temporary_pub, create_temporary_pub
 
@@ -78,13 +78,22 @@ def test_create_formdata(pub):
     del source_formdef._workflow
     formdata.perform_workflow()
     assert target_formdef.data_class().count() == 1
+    # check evolutions & tracing
+    target_formdata = target_formdef.data_class().select()[0]
+    assert isinstance(target_formdata.evolution[0].parts[0], ActionsTracingEvolutionPart)
+    assert target_formdata.evolution[0].parts[0].actions[0][1:] == (
+        'create_formdata',  # item key
+        '_create',  # item id
+        'new',  # status
+        wf.id,  # workflow id
+    )
 
     errors = pub.loggederror_class.select()
     assert len(errors) == 2
     assert 'form_var_toto_string' in errors[0].exception_message
     assert errors[1].summary == 'Missing field: unknown (1), unknown (2)'
-    assert errors[0].formdata_id == str(target_formdef.data_class().select()[0].id)
-    assert errors[1].formdata_id == str(target_formdef.data_class().select()[0].id)
+    assert errors[0].formdata_id == str(target_formdata.id)
+    assert errors[1].formdata_id == str(target_formdata.id)
 
     # add field labels cache
     pub.loggederror_class.wipe()
diff --git a/wcs/wf/create_formdata.py b/wcs/wf/create_formdata.py
index 9964cf4d6..696160bfd 100644
--- a/wcs/wf/create_formdata.py
+++ b/wcs/wf/create_formdata.py
@@ -14,6 +14,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, see <http://www.gnu.org/licenses/>.
 
+import datetime
 import time
 import xml.etree.ElementTree as ET
 
@@ -35,7 +36,7 @@ from wcs.qommon.form import (
     WidgetListAsTable,
 )
 from wcs.qommon.substitution import CompatibilityNamesDict
-from wcs.workflows import EvolutionPart, WorkflowStatusItem, register_item_class
+from wcs.workflows import ActionsTracingEvolutionPart, EvolutionPart, WorkflowStatusItem, register_item_class
 
 
 class Mapping:
@@ -598,7 +599,31 @@ class CreateFormdataWorkflowStatusItem(WorkflowStatusItem):
                 new_formdata.store()
                 if formdef.enable_tracking_codes:
                     code.formdata = new_formdata  # this will .store() the code
+                # add a link to current workflow & action
+                date_now = datetime.datetime.now()
                 new_formdata.perform_workflow(event=('workflow-created', formdata.get_display_id()))
+                action_part = None
+                for evo in new_formdata.evolution:
+                    for part in evo.parts or []:
+                        if isinstance(part, ActionsTracingEvolutionPart):
+                            # in first ActionsTracingEvolutionPart found
+                            action_part = part
+                            break
+                    if action_part:
+                        break
+                if not action_part:
+                    # in a new ActionsTracingEvolutionPart if not found
+                    action_part = ActionsTracingEvolutionPart('workflow-created', [])
+                    evo.add_part(action_part)
+                performed_action = (
+                    date_now,
+                    self.key,
+                    self.id,
+                    self.parent.id,
+                    self.parent.parent.id,
+                )
+                action_part.actions.insert(0, performed_action)
+                new_formdata._store_all_evolution = True
                 new_formdata.store()
 
             # update local object as it may have been modified by new_formdata
-- 
2.35.1

