From b76d10d69e6297df3ad0dc244370c620b86bf613 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 | 44 ++++++++++++++++++---
 tests/workflow/test_carddata.py             |  8 +++-
 tests/workflow/test_formdata.py             | 12 ++++--
 wcs/wf/create_formdata.py                   | 20 +++++++++-
 4 files changed, 74 insertions(+), 10 deletions(-)

diff --git a/tests/backoffice_pages/test_form_inspect.py b/tests/backoffice_pages/test_form_inspect.py
index 9a7bc8142..17129dc4e 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,38 +664,59 @@ 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 .event')] == [
+        'None',
+        'Continuation',
+        'Global action timeout',
+    ]
     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] == [
         'Email',
         'Email',
         'Automatic Jump',
+        'New Form Creation',
         'Criticality Levels',
     ]
+    event_links = [x.attrib['href'] for x in resp.pyquery('#inspect-timeline .event a')]
+    assert event_links == []
     action_links = [x.attrib['href'] for x in resp.pyquery('#inspect-timeline a.tracing-link')]
     assert action_links[0] == 'http://example.net/backoffice/workflows/2/status/just_submitted/'
     assert (
         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 .event a')] == ['Created (by workflow action)']
+    event_links = [x.attrib['href'] for x in resp.pyquery('#inspect-timeline .event a')]
+    assert (
+        event_links[0] == 'http://example.net/backoffice/workflows/2/status/new/items/_create/'
+    )  # link on external workflow
+
     # 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 1755dfb54..caea4d6c2 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,12 @@ 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].external_workflow_id == wf.id
+    assert carddata.evolution[0].parts[0].external_status_id == 'new'
+    assert carddata.evolution[0].parts[0].external_item_id == '_create'
 
     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..daff3a0aa 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,19 @@ 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].external_workflow_id == wf.id
+    assert target_formdata.evolution[0].parts[0].external_status_id == 'new'
+    assert target_formdata.evolution[0].parts[0].external_item_id == '_create'
 
     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..8d4dc8e91 100644
--- a/wcs/wf/create_formdata.py
+++ b/wcs/wf/create_formdata.py
@@ -35,7 +35,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 +598,25 @@ 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
                 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)
+                action_part.external_workflow_id = self.parent.parent.id
+                action_part.external_status_id = self.parent.id
+                action_part.external_item_id = self.id
+                new_formdata._store_all_evolution = True
                 new_formdata.store()
 
             # update local object as it may have been modified by new_formdata
-- 
2.35.1

