backoffice: add tracing information to inspect page (#54497)

This commit is contained in:
Frédéric Péters 2021-06-01 22:25:07 +02:00
parent 622a2cb5b3
commit edaf3946f2
4 changed files with 138 additions and 2 deletions

View File

@ -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 '<h2>Actions Tracing</h2>' 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)

View File

@ -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('</ul>')
r += htmltext('</div>')
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('<div id="inspect-timeline" class="section">')
r += htmltext('<h2>%s</h2></li>\n') % _('Actions Tracing')
r += htmltext('<ul class="form-inspector biglist">')
wf_status = None
status_admin_base_url = '#'
for evolution in self.filled.evolution:
if evolution.status and evolution.status != wf_status:
for part in evolution.parts or []:
if isinstance(part, ActionsTracingEvolutionPart):
if part.actions:
r += (
htmltext('<li><span class="event">%s</span></li>')
% part.get_event_label()
)
break
try:
status = self.filled.formdef.workflow.get_status(evolution.status)
status_label = status.name
status_admin_base_url = status.get_admin_url()
except KeyError:
status_label = _('Unavailable status (%s)') % evolution.status
status_admin_base_url = '#missing'
r += htmltext(
'<li><span class="datetime">%s</span> '
'<a class="tracing-link" href="%s"><strong>%s</strong></a></li>'
) % (
time.strftime('%Y-%m-%d %H:%M:%S', evolution.time) if evolution.time else '-',
status_admin_base_url,
status_label,
)
if evolution.status:
wf_status = evolution.status
first_part = True
for part in evolution.parts or []:
if isinstance(part, ActionsTracingEvolutionPart):
if not first_part and part.actions:
r += htmltext('<li><span class="event">%s</span></li>') % part.get_event_label()
first_part = False
for action_ts, action_key, action_id in part.actions:
action_label = action_classes.get(action_key, action_key)
try:
url = '%sitems/%s/' % (
part.get_base_url(self.filled.formdef.workflow, wf_status),
action_id,
)
except KeyError:
url = '#missing-%s' % action_id
r += htmltext(
'<li><span class="datetime">%s</span> '
'<a class="tracing-link" href="%s">%s</a></li>'
) % (
action_ts.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3],
url,
action_label,
)
r += htmltext('</ul>')
r += htmltext('</div>')
# markers stack
if '_markers_stack' in (self.filled.workflow_data or {}):
r += htmltext('<div id="inspect-markers" class="section">')

View File

@ -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;

View File

@ -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