diff --git a/tests/backoffice_pages/test_all.py b/tests/backoffice_pages/test_all.py
index 7d99c4851..10c081d86 100644
--- a/tests/backoffice_pages/test_all.py
+++ b/tests/backoffice_pages/test_all.py
@@ -5064,6 +5064,30 @@ def test_inspect_page_with_related_objects(pub):
)
+def test_inspect_page_actions_traces(pub):
+ create_environment(pub)
+ create_user(pub, is_admin=True)
+
+ formdef = FormDef.get_by_urlname('form-title')
+
+ formdef.data_class().wipe()
+ formdata = formdef.data_class()()
+ formdata.data = {}
+ formdata.just_created()
+ formdata.perform_workflow()
+ formdata.store()
+
+ resp = login(get_app(pub)).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']
+ assert [x.text for x in resp.pyquery('#inspect-timeline a.tracing-link') if x.text] == [
+ 'Email',
+ 'Email',
+ 'Automatic Jump',
+ ]
+
+
def test_workflow_jump_previous(pub):
user = create_user(pub)
create_environment(pub)
diff --git a/wcs/backoffice/management.py b/wcs/backoffice/management.py
index 01cf58a64..86763d1dd 100644
--- a/wcs/backoffice/management.py
+++ b/wcs/backoffice/management.py
@@ -43,7 +43,7 @@ from wcs.forms.backoffice import FormDefUI
from wcs.forms.common import FormStatusPage
from wcs.roles import logged_users_role
from wcs.variables import LazyFieldVar
-from wcs.workflows import WorkflowStatusItem, template_on_formdata
+from wcs.workflows import ActionsTracingEvolutionPart, WorkflowStatusItem, item_classes, template_on_formdata
from ..qommon import _, emails, errors, ezt, force_str, get_cfg, get_logger, misc, ngettext, ods, sms
from ..qommon.admin.emails import EmailsDirectory
@@ -3517,6 +3517,73 @@ class FormBackOfficeStatusPage(FormStatusPage):
r += htmltext('')
r += htmltext('')
+ has_tracing = False
+ for evolution in self.filled.evolution or []:
+ if evolution.parts and any(isinstance(x, ActionsTracingEvolutionPart) for x in evolution.parts):
+ has_tracing = True
+ break
+
+ if has_tracing:
+ action_classes = {x.key: x.description for x in item_classes}
+ r += htmltext('')
+ r += htmltext('
%s
\n') % _('Actions Tracing')
+ r += htmltext('
')
+ r += htmltext('
')
+
# markers stack
if '_markers_stack' in (self.filled.workflow_data or {}):
r += htmltext('')
diff --git a/wcs/qommon/static/css/dc2/admin.scss b/wcs/qommon/static/css/dc2/admin.scss
index 7382fbb91..8f7e884c5 100644
--- a/wcs/qommon/static/css/dc2/admin.scss
+++ b/wcs/qommon/static/css/dc2/admin.scss
@@ -1661,10 +1661,28 @@ ul.form-inspector li code {
font-size: 100%;
}
+ul.form-inspector li span.datetime {
+ padding: 0 1ex;
+}
+
+ul.form-inspector li a.tracing-link {
+ display: inline;
+ padding: 0;
+ &:hover {
+ background: transparent;
+ text-decoration: underline;
+ }
+}
+
ul.form-inspector li span.status {
padding: 0 1ex;
}
+ul.form-inspector span.event {
+ padding: 0 1ex;
+ font-style: italic;
+}
+
ul.form-inspector li div.value {
display: block;
padding: 0 0 0 1em;
diff --git a/wcs/workflows.py b/wcs/workflows.py
index f61500d07..ab73d6cd8 100644
--- a/wcs/workflows.py
+++ b/wcs/workflows.py
@@ -79,7 +79,7 @@ def perform_items(items, formdata, depth=20, event=None):
continue
if not item.check_condition(formdata):
continue
- performed_actions.append((datetime.datetime.now(), item.id))
+ performed_actions.append((datetime.datetime.now(), item.key, item.id))
try:
url = item.perform(formdata) or url
except AbortActionException as e:
@@ -343,6 +343,33 @@ class ActionsTracingEvolutionPart(EvolutionPart):
self.event_args = None
self.actions = actions
+ def get_event_label(self):
+ return {
+ 'api-created': _('Created (by API)'),
+ 'api-post-edit-action': _('Actions after edit action (by API)'),
+ 'api-trigger': _('API Trigger'),
+ 'backoffice-created': _('Created (backoffice submission)'),
+ 'continuation': _('Continuation'),
+ 'csv-import-created': _('Created (by CSV import)'),
+ 'edit-action': _('Actions after edit action'),
+ 'frontoffice-created': _('Created (frontoffice submission)'),
+ 'global-action-button': _('Click on a global action button'),
+ 'global-action': _('Global action'),
+ 'global-action-timeout': _('Global action timeout'),
+ 'timeout-jump': _('Timeout jump'),
+ 'workflow-created': _('Created (by workflow action)'),
+ 'workflow-form-submit': _('Action in workflow form'),
+ }.get(self.event, self.event)
+
+ def is_global_event(self):
+ return bool(self.event and self.event.startswith('global-'))
+
+ def get_base_url(self, workflow, status_id):
+ if self.is_global_event():
+ return '%sglobal-actions/%s/' % (workflow.get_admin_url(), self.event_args[0])
+ status = workflow.get_status(status_id)
+ return status.get_admin_url()
+
class DuplicateGlobalActionNameError(Exception):
pass