workflow: add workflow trace on form/carddata creation/edition (#77300) #310
|
@ -644,6 +644,14 @@ def test_inspect_page_actions_traces(pub):
|
|||
]
|
||||
target_formdef.store()
|
||||
|
||||
CardDef.wipe()
|
||||
target_carddef = CardDef()
|
||||
target_carddef.name = 'target card'
|
||||
target_carddef.fields = [
|
||||
fields.StringField(id='0', label='string', varname='foo_string'),
|
||||
]
|
||||
target_carddef.store()
|
||||
|
||||
workflow = Workflow.get_default_workflow()
|
||||
workflow.id = '2'
|
||||
workflow.criticality_levels = [
|
||||
|
@ -656,12 +664,35 @@ 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'),
|
||||
]
|
||||
|
||||
st1 = workflow.possible_status[1]
|
||||
|
||||
create_formdata = st1.add_action('create_formdata', id='_create', prepend=True)
|
||||
create_formdata.varname = 'create_formdata'
|
||||
create_formdata.formdef_slug = target_formdef.url_name
|
||||
mappings = [Mapping(field_id='0', expression='foo bar')]
|
||||
create_formdata.mappings = mappings
|
||||
|
||||
create_carddata = st1.add_action('create_carddata', id='_create_card')
|
||||
create_carddata.label = 'create linked card'
|
||||
create_carddata.formdef_slug = target_carddef.url_name
|
||||
create_carddata.varname = 'created_card'
|
||||
create_carddata.mappings = mappings
|
||||
|
||||
edit_carddata = st1.add_action('edit_carddata', id='_edit_card')
|
||||
edit_carddata.label = 'edit linked card'
|
||||
edit_carddata.formdef_slug = target_carddef.url_name
|
||||
edit_carddata.varname = 'edited_card'
|
||||
edit_carddata.mappings = [Mapping(field_id='0', expression='foo bar blah')]
|
||||
|
||||
edit_carddata2 = st1.add_action('edit_carddata', id='_edit_card2')
|
||||
edit_carddata2.target_mode = 'manual'
|
||||
edit_carddata2.target_id = '{{ unknown }}'
|
||||
edit_carddata2.label = 'edit linked card with wrong target_id'
|
||||
edit_carddata2.formdef_slug = target_carddef.url_name
|
||||
edit_carddata2.varname = 'edited_card_wrong_target_id'
|
||||
edit_carddata2.mappings = [Mapping(field_id='0', expression='foo bar blah')]
|
||||
|
||||
workflow.store()
|
||||
|
||||
formdef = FormDef()
|
||||
|
@ -692,6 +723,9 @@ def test_inspect_page_actions_traces(pub):
|
|||
assert [x.text for x in resp.pyquery('#inspect-timeline .event')] == [
|
||||
'Created (frontoffice submission)',
|
||||
'Continuation',
|
||||
None, # Created form
|
||||
None, # Created card
|
||||
None, # Edited card
|
||||
'Global action timeout',
|
||||
]
|
||||
assert [x.text for x in resp.pyquery('#inspect-timeline strong')] == ['Just Submitted', 'New']
|
||||
|
@ -700,17 +734,33 @@ def test_inspect_page_actions_traces(pub):
|
|||
'Email',
|
||||
'Automatic Jump',
|
||||
'New Form Creation',
|
||||
'Create Card Data',
|
||||
'Edit Card Data',
|
||||
'Edit Card Data',
|
||||
'Criticality Levels',
|
||||
]
|
||||
event_links = [x.attrib['href'] for x in resp.pyquery('#inspect-timeline .event a')]
|
||||
assert event_links == []
|
||||
assert event_links == [
|
||||
'http://example.net/backoffice/management/target-form/1/', # Created form
|
||||
'http://example.net/backoffice/data/target-card/1/', # Created card
|
||||
'http://example.net/backoffice/data/target-card/1/', # Edited card
|
||||
]
|
||||
assert [x.text for x in resp.pyquery('#inspect-timeline .event a') if x.text] == [
|
||||
'Created form - target form #1-1',
|
||||
'Created card - target card #1-1',
|
||||
'Edited card - target card #1-1',
|
||||
]
|
||||
assert [x.text for x in resp.pyquery('#inspect-timeline .event-error')] == ['Nothing edited']
|
||||
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[-5] == 'http://example.net/backoffice/workflows/2/status/new/items/_create/'
|
||||
assert action_links[-4] == 'http://example.net/backoffice/workflows/2/status/new/items/_create_card/'
|
||||
assert action_links[-3] == 'http://example.net/backoffice/workflows/2/status/new/items/_edit_card/'
|
||||
assert action_links[-2] == 'http://example.net/backoffice/workflows/2/status/new/items/_edit_card2/'
|
||||
assert action_links[-1] == 'http://example.net/backoffice/workflows/2/global-actions/1/items/1/'
|
||||
|
||||
# check details are available
|
||||
|
|
|
@ -95,9 +95,14 @@ def test_create_carddata(pub):
|
|||
# check tracing
|
||||
carddata = carddef.data_class().select()[0]
|
||||
trace = WorkflowTrace.select_for_formdata(carddata)[0]
|
||||
assert trace.event == 'workflow-created'
|
||||
assert trace.event_args['external_workflow_id'] == wf.id
|
||||
assert trace.event_args['external_status_id'] == 'new'
|
||||
assert trace.event_args['external_item_id'] == '_create'
|
||||
trace = WorkflowTrace.select_for_formdata(formdata)[-1]
|
||||
assert trace.event == 'workflow-created-carddata'
|
||||
assert trace.event_args['external_formdef_id'] == carddef.id
|
||||
assert trace.event_args['external_formdata_id'] == carddata.id
|
||||
|
||||
errors = pub.loggederror_class.select()
|
||||
assert len(errors) == 2
|
||||
|
@ -668,9 +673,14 @@ def test_edit_carddata_with_data_sourced_object(pub):
|
|||
assert data.data['2_structured'] == {'id': 'c', 'text': 'cook', 'extra': 'plop2'}
|
||||
# check evolutions & tracing
|
||||
trace = WorkflowTrace.select_for_formdata(carddata)[0]
|
||||
assert trace.event == 'workflow-edited'
|
||||
assert trace.event_args['external_workflow_id'] == wf.id
|
||||
assert trace.event_args['external_status_id'] == 'st1'
|
||||
assert trace.event_args['external_item_id'] == 'edit'
|
||||
trace = WorkflowTrace.select_for_formdata(formdata)[-1]
|
||||
assert trace.event == 'workflow-edited-carddata'
|
||||
assert trace.event_args['external_formdef_id'] == carddef.id
|
||||
assert trace.event_args['external_formdata_id'] == carddata.id
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {'0': '1', '1': 'b'}
|
||||
|
@ -707,6 +717,28 @@ def test_edit_carddata_with_data_sourced_object(pub):
|
|||
carddata.data = data.data
|
||||
carddata.store()
|
||||
|
||||
# not target found
|
||||
edit.mappings = [
|
||||
Mapping(field_id='2', expression='{{ form_var_new_profession }}'),
|
||||
]
|
||||
edit.target_mode = 'manual'
|
||||
edit.target_id = '{{ unknown }}'
|
||||
wf.store()
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {'0': '1', '1': 'c'}
|
||||
formdata.store()
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
formdata.perform_workflow()
|
||||
# no change
|
||||
data = carddef.data_class().select()[0]
|
||||
assert data.data['2'] == 'b'
|
||||
assert data.data['2_display'] == 'baker'
|
||||
assert data.data['2_structured'] == {'id': 'b', 'text': 'baker', 'extra': 'plop'}
|
||||
trace = WorkflowTrace.select_for_formdata(formdata)[-1]
|
||||
assert trace.event == 'workflow-edited-carddata'
|
||||
assert trace.event_args == {}
|
||||
|
||||
|
||||
def test_edit_carddata_with_linked_object(pub):
|
||||
FormDef.wipe()
|
||||
|
|
|
@ -85,9 +85,14 @@ def test_create_formdata(pub):
|
|||
# check evolutions & tracing
|
||||
target_formdata = target_formdef.data_class().select()[0]
|
||||
trace = WorkflowTrace.select_for_formdata(target_formdata)[0]
|
||||
assert trace.event == 'workflow-created'
|
||||
assert trace.event_args['external_workflow_id'] == wf.id
|
||||
assert trace.event_args['external_status_id'] == 'new'
|
||||
assert trace.event_args['external_item_id'] == '_create'
|
||||
trace = WorkflowTrace.select_for_formdata(formdata)[-1]
|
||||
assert trace.event == 'workflow-created-formdata'
|
||||
assert trace.event_args['external_formdef_id'] == target_formdef.id
|
||||
assert trace.event_args['external_formdata_id'] == target_formdata.id
|
||||
|
||||
errors = pub.loggederror_class.select()
|
||||
assert len(errors) == 2
|
||||
|
|
|
@ -3803,68 +3803,21 @@ class FormBackOfficeStatusPage(FormStatusPage):
|
|||
action_classes = {x.key: x.description for x in item_classes}
|
||||
last_status_id = None
|
||||
global_event = None
|
||||
status_admin_base_url = '#'
|
||||
for trace in self.workflow_traces:
|
||||
if trace.event:
|
||||
global_event = trace if trace.is_global_event() else None
|
||||
if (
|
||||
trace.event_args
|
||||
and trace.event_args.get('external_workflow_id')
|
||||
and trace.event_args.get('external_status_id')
|
||||
and trace.event_args.get('external_item_id')
|
||||
):
|
||||
r += htmltext('<li><span class="event"><a href="%s">%s</a></span></li>') % (
|
||||
trace.get_external_url(global_event),
|
||||
trace.get_event_label(),
|
||||
)
|
||||
else:
|
||||
r += htmltext('<li><span class="event">%s</span></li>') % trace.get_event_label()
|
||||
r += htmltext(trace.print_event(global_event=global_event))
|
||||
|
||||
if trace.action_item_key:
|
||||
action_label = action_classes.get(trace.action_item_key, trace.action_item_key)
|
||||
try:
|
||||
url = '%sitems/%s/' % (
|
||||
trace.get_base_url(self.filled.formdef.workflow, trace.status_id, global_event),
|
||||
trace.action_item_id,
|
||||
)
|
||||
except KeyError:
|
||||
url = '#missing-%s' % trace.action_item_id
|
||||
r += htmltext(
|
||||
'<li><span class="datetime">%s</span> <a class="tracing-link" href="%s">%s</a>'
|
||||
) % (
|
||||
trace.timestamp.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3],
|
||||
url,
|
||||
action_label,
|
||||
trace.print_action(
|
||||
action_classes=action_classes, filled=self.filled, global_event=global_event
|
||||
)
|
||||
)
|
||||
real_action = trace.get_real_action(
|
||||
self.filled.formdef.workflow,
|
||||
trace.status_id,
|
||||
trace.action_item_id,
|
||||
global_event,
|
||||
)
|
||||
if real_action:
|
||||
details = real_action.get_inspect_details()
|
||||
if details:
|
||||
r += htmltext(' <span class="tracing-details">(%s)</span>') % details
|
||||
r += htmltext('</li>')
|
||||
|
||||
if trace.status_id != last_status_id:
|
||||
last_status_id = trace.status_id
|
||||
try:
|
||||
status = self.filled.formdef.workflow.get_status(trace.status_id)
|
||||
status_label = status.name
|
||||
status_admin_base_url = status.get_admin_url()
|
||||
except KeyError:
|
||||
status_label = _('Unavailable status (%s)') % trace.status_id
|
||||
status_admin_base_url = '#missing'
|
||||
r += htmltext(
|
||||
'<li><span class="datetime">%s</span> '
|
||||
'<a class="tracing-link" href="%s"><strong>%s</strong></a></li>'
|
||||
) % (
|
||||
trace.timestamp.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
status_admin_base_url,
|
||||
status_label,
|
||||
)
|
||||
r += htmltext(trace.print_status(filled=self.filled))
|
||||
|
||||
return r.getvalue()
|
||||
|
||||
|
|
|
@ -1796,13 +1796,18 @@ ul.form-inspector li {
|
|||
}
|
||||
}
|
||||
|
||||
span.event-datetime {
|
||||
padding: 0 1ex 0 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
span.datetime,
|
||||
span.status,
|
||||
span.event {
|
||||
span.event,
|
||||
span.event-error {
|
||||
padding: 0 1ex;
|
||||
}
|
||||
|
||||
span.event {
|
||||
span.event, span.event-error {
|
||||
font-style: italic;
|
||||
a {
|
||||
border-bottom: none;
|
||||
|
|
|
@ -32,6 +32,7 @@ class CreateCarddataWorkflowStatusItem(CreateFormdataWorkflowStatusItem):
|
|||
|
||||
formdef_class = CardDef
|
||||
evolution_part_class = LinkedCardDataEvolutionPart
|
||||
workflow_trace_event = 'workflow-created-carddata'
|
||||
|
||||
formdef_label = _('Card')
|
||||
mappings_label = _('Mappings to new card fields')
|
||||
|
|
|
@ -342,6 +342,7 @@ class CreateFormdataWorkflowStatusItem(WorkflowStatusItem):
|
|||
|
||||
formdef_class = FormDef
|
||||
evolution_part_class = LinkedFormdataEvolutionPart
|
||||
workflow_trace_event = 'workflow-created-formdata'
|
||||
|
||||
formdef_slug = None
|
||||
formdef_label = _('Form')
|
||||
|
@ -696,6 +697,12 @@ class CreateFormdataWorkflowStatusItem(WorkflowStatusItem):
|
|||
external_item_id=self.id,
|
||||
)
|
||||
new_formdata.perform_workflow()
|
||||
# add a link to created formdata
|
||||
formdata.record_workflow_event(
|
||||
self.workflow_trace_event,
|
||||
external_formdef_id=formdef.id,
|
||||
external_formdata_id=new_formdata.id,
|
||||
)
|
||||
|
||||
# update local object as it may have been modified by new_formdata
|
||||
# workflow execution.
|
||||
|
|
|
@ -52,6 +52,7 @@ class EditCarddataWorkflowStatusItem(CreateCarddataWorkflowStatusItem, ExternalW
|
|||
|
||||
formdata.store()
|
||||
|
||||
target_data = None
|
||||
for target_data in self.iter_target_datas(formdata, carddef):
|
||||
old_data = copy.deepcopy(target_data.data)
|
||||
self.apply_mappings(dest=target_data, src=formdata)
|
||||
|
@ -75,6 +76,17 @@ class EditCarddataWorkflowStatusItem(CreateCarddataWorkflowStatusItem, ExternalW
|
|||
external_item_id=self.id,
|
||||
)
|
||||
target_data.store()
|
||||
# add a link to created carddata
|
||||
formdata.record_workflow_event(
|
||||
'workflow-edited-carddata',
|
||||
external_formdef_id=carddef.id,
|
||||
external_formdata_id=target_data.id,
|
||||
)
|
||||
if target_data is None:
|
||||
# nothing updated, no target_data found
|
||||
formdata.record_workflow_event(
|
||||
'workflow-edited-carddata',
|
||||
)
|
||||
|
||||
# update local object as it may have modified itself
|
||||
formdata.refresh_from_storage()
|
||||
|
|
|
@ -65,6 +65,9 @@ class WorkflowTrace(sql.WorkflowTrace):
|
|||
'timeout-jump': _('Timeout jump'),
|
||||
'workflow-created': _('Created (by workflow action)'),
|
||||
'workflow-edited': _('Edited (by workflow action)'),
|
||||
'workflow-created-formdata': _('Created form'),
|
||||
'workflow-created-carddata': _('Created card'),
|
||||
'workflow-edited-carddata': _('Edited card'),
|
||||
'workflow-form-submit': _('Action in workflow form'),
|
||||
}.get(self.event, self.event)
|
||||
|
||||
|
@ -81,6 +84,27 @@ class WorkflowTrace(sql.WorkflowTrace):
|
|||
self._external_workflow = Workflow.get(self.event_args.get('external_workflow_id'))
|
||||
return self._external_workflow
|
||||
|
||||
@property
|
||||
def formdef(self):
|
||||
from wcs.carddef import CardDef
|
||||
from wcs.formdef import FormDef
|
||||
|
||||
if not hasattr(self, '_formdef'):
|
||||
formdef_class = FormDef
|
||||
if 'carddata' in self.event:
|
||||
formdef_class = CardDef
|
||||
|
||||
self._formdef = formdef_class.get(self.event_args.get('external_formdef_id'), ignore_errors=True)
|
||||
return self._formdef
|
||||
|
||||
@property
|
||||
def formdata(self):
|
||||
if not hasattr(self, '_formdata'):
|
||||
self._formdata = self.formdef.data_class().get(
|
||||
self.event_args.get('external_formdata_id'), ignore_errors=True
|
||||
)
|
||||
return self._formdata
|
||||
|
||||
def get_external_url(self, global_event):
|
||||
try:
|
||||
return '%sitems/%s/' % (
|
||||
|
@ -94,6 +118,15 @@ class WorkflowTrace(sql.WorkflowTrace):
|
|||
except KeyError:
|
||||
return '#missing-%s' % self.event_args.get('external_item_id')
|
||||
|
||||
def get_external_formdata_url(self, global_event):
|
||||
if self.formdef is None:
|
||||
return '#missing-formdef-%s' % self.event_args.get('external_formdef_id')
|
||||
|
||||
if self.formdata is None:
|
||||
return '#missing-formdata-%s' % self.event_args.get('external_formdata_id')
|
||||
|
||||
return self.formdata.get_backoffice_url()
|
||||
|
||||
def get_base_url(self, workflow, status_id, global_event):
|
||||
if global_event:
|
||||
if not global_event.event_args:
|
||||
|
@ -126,3 +159,79 @@ class WorkflowTrace(sql.WorkflowTrace):
|
|||
except IndexError:
|
||||
real_action = None
|
||||
return real_action
|
||||
|
||||
def print_event(self, global_event):
|
||||
if (
|
||||
self.event_args
|
||||
and self.event_args.get('external_workflow_id')
|
||||
and self.event_args.get('external_status_id')
|
||||
and self.event_args.get('external_item_id')
|
||||
):
|
||||
return '<li><span class="event"><a href="%s">%s</a></span></li>' % (
|
||||
self.get_external_url(global_event),
|
||||
self.get_event_label(),
|
||||
)
|
||||
if (
|
||||
self.event_args
|
||||
and self.event_args.get('external_formdef_id')
|
||||
and self.event_args.get('external_formdata_id')
|
||||
):
|
||||
return (
|
||||
'<li><span class="event-datetime">%s</span><span class="event"><a href="%s">%s - %s</a></span></li>'
|
||||
% (
|
||||
self.timestamp.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3],
|
||||
self.get_external_formdata_url(global_event),
|
||||
self.get_event_label(),
|
||||
self.formdata.get_display_name() if self.formdata else _('deleted'),
|
||||
)
|
||||
)
|
||||
if self.event == 'workflow-edited-carddata':
|
||||
return '<li><span class="event-datetime">%s</span><span class="event-error">%s</span></li>' % (
|
||||
self.timestamp.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3],
|
||||
_('Nothing edited'),
|
||||
)
|
||||
return '<li><span class="event">%s</span></li>' % self.get_event_label()
|
||||
|
||||
def print_action(self, action_classes, filled, global_event):
|
||||
action_label = action_classes.get(self.action_item_key, self.action_item_key)
|
||||
try:
|
||||
url = '%sitems/%s/' % (
|
||||
self.get_base_url(filled.formdef.workflow, self.status_id, global_event),
|
||||
self.action_item_id,
|
||||
)
|
||||
except KeyError:
|
||||
url = '#missing-%s' % self.action_item_id
|
||||
r = '<li><span class="datetime">%s</span> <a class="tracing-link" href="%s">%s</a>' % (
|
||||
self.timestamp.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3],
|
||||
url,
|
||||
action_label,
|
||||
)
|
||||
real_action = self.get_real_action(
|
||||
filled.formdef.workflow,
|
||||
self.status_id,
|
||||
self.action_item_id,
|
||||
global_event,
|
||||
)
|
||||
if real_action:
|
||||
details = real_action.get_inspect_details()
|
||||
if details:
|
||||
r += ' <span class="tracing-details">(%s)</span>' % details
|
||||
r += '</li>'
|
||||
return r
|
||||
|
||||
def print_status(self, filled):
|
||||
try:
|
||||
status = filled.formdef.workflow.get_status(self.status_id)
|
||||
status_label = status.name
|
||||
status_admin_base_url = status.get_admin_url()
|
||||
except KeyError:
|
||||
status_label = _('Unavailable status (%s)') % self.status_id
|
||||
status_admin_base_url = '#missing'
|
||||
return (
|
||||
'<li><span class="datetime">%s</span> '
|
||||
'<a class="tracing-link" href="%s"><strong>%s</strong></a></li>'
|
||||
) % (
|
||||
self.timestamp.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
status_admin_base_url,
|
||||
status_label,
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue