backoffice: add tracing information to inspect page (#54497)
This commit is contained in:
parent
622a2cb5b3
commit
edaf3946f2
|
@ -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)
|
||||
|
|
|
@ -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">')
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue