0001-workflows-only-log-condition-errors-as-functional-er.patch
tests/test_backoffice_pages.py | ||
---|---|---|
3907 | 3907 | |
3908 | 3908 |
app = login(get_app(pub)) |
3909 | 3909 |
resp = app.get('/backoffice/forms/%s/' % formdef.id) |
3910 |
assert 'ZeroDivisionError' in resp.body |
|
3910 |
assert 'Failed to evaluate condition' in resp.body |
|
3911 |
assert 'error ZeroDivisionError' in resp.body |
|
3911 | 3912 |
resp = resp.click('1 error') |
3912 | 3913 | |
3913 | 3914 |
resp = app.get('/backoffice/workflows/%s/' % workflow.id) |
3914 | 3915 |
resp2 = resp.click('1 error') |
3915 |
assert 'ZeroDivisionError' in resp2.body |
|
3916 |
resp = resp2.click('ZeroDivisionError') |
|
3917 |
assert 'integer division or modulo by zero' in resp.body |
|
3916 |
assert 'Failed to evaluate condition' in resp2.body |
|
3917 |
assert 'error ZeroDivisionError' in resp2.body |
|
3918 |
resp = resp2.click('Failed to evaluate condition') |
|
3919 |
assert 'ZeroDivisionError: integer division or modulo by zero' in resp.body |
|
3920 |
assert 'Python Expression: <pre>1/0</pre>' in resp.body |
|
3918 | 3921 |
assert not 'Acked' in resp.body |
3919 | 3922 |
resp = resp.click('Ack').follow() |
3920 | 3923 |
assert 'Acked' in resp.body |
... | ... | |
3933 | 3936 | |
3934 | 3937 |
app = login(get_app(pub)) |
3935 | 3938 |
resp = app.get('/backoffice/workflows/%s/' % workflow.id) |
3936 |
assert 'ZeroDivisionError' in resp.body |
|
3939 |
assert 'Failed to evaluate condition' in resp2.body |
|
3940 |
assert 'error ZeroDivisionError' in resp2.body |
|
3937 | 3941 |
resp2 = resp.click('1 error') |
3938 |
resp = resp2.click('ZeroDivisionError')
|
|
3942 |
resp = resp2.click('Failed to evaluate condition')
|
|
3939 | 3943 |
assert 'href="http://example.net/backoffice/management/test/' in resp.body |
3940 | 3944 | |
3941 | 3945 |
# very long error string (check it creates a viable tech_id) |
... | ... | |
3950 | 3954 | |
3951 | 3955 |
# remove formdef |
3952 | 3956 |
FormDef.wipe() |
3953 |
resp = resp2.click('ZeroDivisionError')
|
|
3957 |
resp = resp2.click('Failed to evaluate condition')
|
|
3954 | 3958 |
assert not 'href="http://example.net/backoffice/management/test/' in resp.body |
3955 | 3959 | |
3956 | 3960 |
def test_backoffice_private_status_and_history(pub): |
tests/test_form_pages.py | ||
---|---|---|
4492 | 4492 |
assert LoggedError.count() == 1 |
4493 | 4493 | |
4494 | 4494 |
error = LoggedError.get_on_index( |
4495 |
'34-12-None-None-zerodivisionerror-integer-division-or-modulo-by-zero', 'tech_id') |
|
4495 |
'34-12-just_submitted-_jump-failed-to-evaluate-condition-ZeroDivisionError-integer-division-or-modulo-by-zero', |
|
4496 |
'tech_id') |
|
4496 | 4497 |
assert error.occurences_count == 2 |
4497 | 4498 | |
4498 | 4499 |
assert len(LoggedError.get_ids_with_indexed_value('formdef_id', '34')) == 1 |
tests/test_workflows.py | ||
---|---|---|
19 | 19 |
from wcs import sessions |
20 | 20 |
from wcs.fields import (StringField, DateField, MapField, FileField, ItemField, |
21 | 21 |
ItemsField, CommentField) |
22 |
from wcs.logged_errors import LoggedError |
|
22 | 23 |
from wcs.roles import Role |
23 | 24 |
from wcs.workflows import (Workflow, WorkflowStatusItem, |
24 | 25 |
SendmailWorkflowStatusItem, SendSMSWorkflowStatusItem, |
... | ... | |
258 | 259 |
'value': 'utils.time_delta(utils.time.localtime(), "2015-01-04").days > 0'} |
259 | 260 |
assert item.must_jump(formdata) is True |
260 | 261 | |
261 | ||
262 | 262 |
def test_jump_count_condition(pub): |
263 | 263 |
FormDef.wipe() |
264 | 264 |
formdef = FormDef() |
... | ... | |
278 | 278 |
item.condition = {'type': 'python', 'value': 'form_objects.count < 2'} |
279 | 279 |
assert item.must_jump(formdata) is False |
280 | 280 | |
281 |
def test_jump_bad_python_condition(pub): |
|
282 |
FormDef.wipe() |
|
283 |
formdef = FormDef() |
|
284 |
formdef.name = 'foobar' |
|
285 |
formdef.store() |
|
286 |
pub.substitutions.feed(formdef) |
|
287 |
formdef.data_class().wipe() |
|
288 |
formdata = formdef.data_class()() |
|
289 |
item = JumpWorkflowStatusItem() |
|
290 | ||
291 |
LoggedError.wipe() |
|
292 |
item.condition = {'type': 'python', 'value': 'form_var_foobar == 0'} |
|
293 |
assert item.must_jump(formdata) is False |
|
294 |
assert LoggedError.count() == 1 |
|
295 |
logged_error = LoggedError.select()[0] |
|
296 |
assert logged_error.summary == 'Failed to evaluate condition' |
|
297 |
assert logged_error.exception_class == 'NameError' |
|
298 |
assert logged_error.exception_message == "name 'form_var_foobar' is not defined" |
|
299 |
assert logged_error.expression == 'form_var_foobar == 0' |
|
300 |
assert logged_error.expression_type == 'python' |
|
301 | ||
302 |
LoggedError.wipe() |
|
303 |
item.condition = {'type': 'python', 'value': '~ invalid ~'} |
|
304 |
assert item.must_jump(formdata) is False |
|
305 |
assert LoggedError.count() == 1 |
|
306 |
logged_error = LoggedError.select()[0] |
|
307 |
assert logged_error.summary == 'Failed to evaluate condition' |
|
308 |
assert logged_error.exception_class == 'SyntaxError' |
|
309 |
assert logged_error.exception_message == 'unexpected EOF while parsing (<string>, line 1)' |
|
310 |
assert logged_error.expression == '~ invalid ~' |
|
311 |
assert logged_error.expression_type == 'python' |
|
312 | ||
281 | 313 |
def test_jump_django_conditions(pub): |
282 | 314 |
FormDef.wipe() |
283 | 315 |
formdef = FormDef() |
... | ... | |
289 | 321 |
pub.substitutions.feed(formdata) |
290 | 322 |
item = JumpWorkflowStatusItem() |
291 | 323 | |
324 |
LoggedError.wipe() |
|
292 | 325 |
item.condition = {'type': 'django', 'value': '1 < 2'} |
293 | 326 |
assert item.must_jump(formdata) is True |
294 | 327 | |
... | ... | |
301 | 334 |
item.condition = {'type': 'django', 'value': 'form_var_foo|first|upper == "X"'} |
302 | 335 |
assert item.must_jump(formdata) is False |
303 | 336 | |
337 |
assert LoggedError.count() == 0 |
|
338 | ||
304 | 339 |
item.condition = {'type': 'django', 'value': '~ invalid ~'} |
305 | 340 |
assert item.must_jump(formdata) is False |
341 |
assert LoggedError.count() == 1 |
|
342 |
logged_error = LoggedError.select()[0] |
|
343 |
assert logged_error.summary == 'Failed to evaluate condition' |
|
344 |
assert logged_error.exception_class == 'TemplateSyntaxError' |
|
345 |
assert logged_error.exception_message == "Could not parse the remainder: '~' from '~'" |
|
346 |
assert logged_error.expression == '~ invalid ~' |
|
347 |
assert logged_error.expression_type == 'django' |
|
306 | 348 | |
307 | 349 |
def test_check_auth(pub): |
308 | 350 |
user = pub.user_class(name='foo') |
... | ... | |
3504 | 3546 |
choice.condition = {'type': 'python', 'value': 'form_name == "baz"'} |
3505 | 3547 |
workflow.store() |
3506 | 3548 |
assert len(FormDef.get(formdef.id).data_class().get_actionable_ids([role.id])) == 2 |
3549 | ||
3550 |
# bad condition |
|
3551 |
LoggedError.wipe() |
|
3552 |
choice.condition = {'type': 'python', 'value': 'foobar == barfoo'} |
|
3553 |
workflow.store() |
|
3554 |
assert len(FormDef.get(formdef.id).data_class().get_actionable_ids([role.id])) == 0 |
|
3555 |
assert LoggedError.count() == 1 |
|
3556 |
logged_error = LoggedError.select()[0] |
|
3557 |
assert logged_error.occurences_count > 1 # should be 2... == 12 with pickle, 4 with sql |
|
3558 |
assert logged_error.summary == 'Failed to evaluate condition' |
|
3559 |
assert logged_error.exception_class == 'NameError' |
|
3560 |
assert logged_error.exception_message == "name 'foobar' is not defined" |
|
3561 |
assert logged_error.expression == 'foobar == barfoo' |
|
3562 |
assert logged_error.expression_type == 'python' |
wcs/admin/logged_errors.py | ||
---|---|---|
70 | 70 |
_(status_item.description)) |
71 | 71 |
r += htmltext('</ul>') |
72 | 72 | |
73 | ||
74 |
if self.error.expression or self.error.expression_type: |
|
75 |
expression_title = { |
|
76 |
'python': N_('Python Expression'), |
|
77 |
'django': N_('Django Expression'), |
|
78 |
'template': N_('Template'), |
|
79 |
'text': N_('Text'), |
|
80 |
}.get(self.error.expression_type, N_('Unknown')) |
|
81 |
r += htmltext(' <li>%s <code>%s</code></li>') % ( |
|
82 |
_('%s:') % expression_title, self.error.expression) |
|
83 | ||
84 |
if self.error.exception_class or self.error.exception_message: |
|
85 |
r += htmltext(' <li>%s <code>%s: %s</code></li>') % (_('Error message:'), |
|
86 |
self.error.exception_class, |
|
87 |
self.error.exception_message) |
|
88 | ||
73 | 89 |
if formdef: |
74 | 90 |
formdata = self.error.get_formdata() |
75 | 91 |
if formdata: |
... | ... | |
159 | 175 |
'%(count)d error', '%(count)d errors', len(errors)) % {'count': len(errors)} |
160 | 176 |
r += htmltext('<ul>') |
161 | 177 |
for error in errors[:3]: |
162 |
r += htmltext('<li><a href="logged-errors/%s/">%s</a></li>') % (error.id, error.summary) |
|
178 |
r += htmltext('<li><a href="logged-errors/%s/">%s</a> ') % (error.id, error.summary) |
|
179 |
if error.exception_class or error.exception_message: |
|
180 |
r += htmltext(_('error %(class)s (%(message)s)')) % { |
|
181 |
'class': error.exception_class, |
|
182 |
'message': error.exception_message, |
|
183 |
} |
|
163 | 184 |
r += htmltext('</li>') |
164 | 185 |
if len(errors) > 3: |
165 | 186 |
r += htmltext('<li>...</li>') |
... | ... | |
180 | 201 |
r += htmltext('<h2>%s</h2>') % _('Logged Errors') |
181 | 202 |
r += htmltext('<ul class="biglist">') |
182 | 203 |
for error in self.get_errors(formdef_id=self.formdef_id, workflow_id=self.workflow_id): |
183 |
r += htmltext('<li><strong><a href="%s/">%s</a></strong>') % (error.id, error.summary) |
|
204 |
r += htmltext('<li><strong><a href="%s/">%s</a></strong> ') % (error.id, error.summary) |
|
205 |
if error.exception_class or error.exception_message: |
|
206 |
r += htmltext(_('error %(class)s (%(message)s)')) % { |
|
207 |
'class': error.exception_class, |
|
208 |
'message': error.exception_message, |
|
209 |
} |
|
184 | 210 |
r += htmltext('<p class="details badge">%s</p>') % error.occurences_count |
185 | 211 |
r += htmltext('</li>') |
186 | 212 |
wcs/conditions.py | ||
---|---|---|
30 | 30 |
record_errors = True |
31 | 31 |
log_errors = False |
32 | 32 | |
33 |
def __init__(self, condition, context=None):
|
|
33 |
def __init__(self, condition, context={}):
|
|
34 | 34 |
if not condition: |
35 | 35 |
condition = {} |
36 | 36 |
self.type = condition.get('type') |
... | ... | |
53 | 53 |
if self.log_errors: |
54 | 54 |
get_logger().warn('failed to evaluate %r (%r)', self, e) |
55 | 55 |
if self.record_errors: |
56 |
get_publisher().notify_of_exception(sys.exc_info()) |
|
56 |
from wcs.logged_errors import LoggedError |
|
57 |
summary = _('Failed to evaluate condition') |
|
58 |
LoggedError.record(summary, |
|
59 |
formdata=self.context.get('formdata'), |
|
60 |
status_item=self.context.get('status_item'), |
|
61 |
expression=self.value, expression_type=self.type, |
|
62 |
exception=e) |
|
57 | 63 |
raise RuntimeError() |
58 | 64 | |
59 | 65 |
def evaluate_python(self, local_variables): |
wcs/logged_errors.py | ||
---|---|---|
33 | 33 |
workflow_id = None |
34 | 34 |
status_id = None |
35 | 35 |
status_item_id = None |
36 |
expression = None |
|
37 |
expression_type = None |
|
36 | 38 |
traceback = None |
39 |
exception_class = None |
|
40 |
exception_message = None |
|
37 | 41 |
occurences_count = 0 |
38 | 42 |
first_occurence_timestamp = None |
39 | 43 |
latest_occurence_timestamp = None |
... | ... | |
42 | 46 |
# declarations for serialization |
43 | 47 |
XML_NODES = [ |
44 | 48 |
('summary', 'str'), ('traceback', 'str'), |
49 |
('exception_class', 'str'), ('exception_message', 'str'), |
|
50 |
('expression', 'str'), ('expression_type', 'str'), |
|
45 | 51 |
('formdata_id', 'str'), ('formdef_id', 'str'), ('workflow_id', 'str'), |
46 | 52 |
('status_id', 'str'), ('status_item_id', 'str'), |
47 | 53 |
('occurences_count', 'int'), |
... | ... | |
51 | 57 | |
52 | 58 |
@classmethod |
53 | 59 |
def record(cls, error_summary, plain_error_msg=None, formdata=None, |
54 |
formdef=None, workflow=None, status=None, status_item=None): |
|
60 |
formdef=None, workflow=None, status=None, status_item=None, |
|
61 |
expression=None, expression_type=None, exception=None): |
|
55 | 62 |
error = cls() |
56 | 63 |
error.summary = error_summary |
57 | 64 |
error.traceback = plain_error_msg |
65 |
error.expression = expression |
|
66 |
error.expression_type = expression_type |
|
67 | ||
68 |
if exception: |
|
69 |
error.exception_class = exception.__class__.__name__ |
|
70 |
error.exception_message = str(exception) |
|
58 | 71 | |
59 | 72 |
if formdata: |
60 | 73 |
error.formdata_id = str(formdata.id) |
... | ... | |
74 | 87 | |
75 | 88 |
if status_item: |
76 | 89 |
error.status_item_id = status_item.id |
77 |
if status_item.parent:
|
|
90 |
if getattr(status_item, 'parent', None):
|
|
78 | 91 |
error.status_id = status_item.parent.id |
79 | 92 |
if status: |
80 | 93 |
error.status_id = status.id |
... | ... | |
110 | 123 | |
111 | 124 |
@property |
112 | 125 |
def tech_id(self): |
113 |
return ('%s-%s-%s-%s-%s' % (self.formdef_id, self.workflow_id, self.status_id, |
|
114 |
self.status_item_id, simplify(self.summary)))[:200] |
|
126 |
tech_id = '%s-%s-' % (self.formdef_id, self.workflow_id) |
|
127 |
if self.status_id: |
|
128 |
tech_id += '%s-' % self.status_id |
|
129 |
if self.status_item_id: |
|
130 |
tech_id += '%s-' % self.status_item_id |
|
131 |
tech_id += '%s' % simplify(self.summary) |
|
132 |
if self.exception_class: |
|
133 |
tech_id += '-%s' % self.exception_class |
|
134 |
if self.exception_message: |
|
135 |
tech_id += '-%s' % simplify(self.exception_message) |
|
136 |
return tech_id[:200] |
|
115 | 137 | |
116 | 138 |
def get_formdef(self): |
117 | 139 |
return FormDef.get(self.formdef_id, ignore_errors=True) |
wcs/wf/jump.py | ||
---|---|---|
218 | 218 |
must_jump = True |
219 | 219 | |
220 | 220 |
if self.condition: |
221 |
context = {'formdata': formdata, 'status_item': self} |
|
221 | 222 |
try: |
222 |
must_jump = Condition(self.condition).evaluate() |
|
223 |
must_jump = Condition(self.condition, context).evaluate()
|
|
223 | 224 |
except RuntimeError: |
224 | 225 |
must_jump = False |
225 | 226 |
wcs/workflows.py | ||
---|---|---|
1583 | 1583 |
return False |
1584 | 1584 | |
1585 | 1585 |
def check_condition(self, formdata): |
1586 |
context = {'formdata': formdata, 'status_item': self} |
|
1586 | 1587 |
try: |
1587 |
return Condition(self.condition).evaluate() |
|
1588 |
return Condition(self.condition, context).evaluate()
|
|
1588 | 1589 |
except RuntimeError: |
1589 | 1590 |
return False |
1590 | 1591 | |
1591 |
- |