1. Updating Personnel BPMN diagram to debug some issues.

2. Disabling the token timeout for now, to see if this corrects the issues Alex is having with lost work.
3. Raising more thoughtful error messages for unknown lookup options.
4. Providing better validation of default values and injecting the correct value for defaults related to enum lists of all types.
5. Bumping Spiffworkflow library which contains some better error messages and checks.
This commit is contained in:
Dan Funk 2020-09-01 15:58:50 -04:00
parent 5e938c4b06
commit b544334f45
9 changed files with 729 additions and 263 deletions

14
Pipfile.lock generated
View File

@ -248,11 +248,11 @@
},
"flask-cors": {
"hashes": [
"sha256:72170423eb4612f0847318afff8c247b38bd516b7737adfc10d1c2cdbb382d16",
"sha256:f4d97201660e6bbcff2d89d082b5b6d31abee04b1b3003ee073a6fd25ad1d69a"
"sha256:6bcfc100288c5d1bcb1dbb854babd59beee622ffd321e444b05f24d6d58466b8",
"sha256:cee4480aaee421ed029eaa788f4049e3e26d15b5affb6a880dade6bafad38324"
],
"index": "pypi",
"version": "==3.0.8"
"version": "==3.0.9"
},
"flask-mail": {
"hashes": [
@ -800,7 +800,7 @@
},
"spiffworkflow": {
"git": "https://github.com/sartography/SpiffWorkflow.git",
"ref": "0581d29db6fd150ebb5ac86ba114681e2e078396"
"ref": "cfffae7d5bf8aa31fdbceb37595d3131ddccff24"
},
"sqlalchemy": {
"hashes": [
@ -971,10 +971,10 @@
},
"more-itertools": {
"hashes": [
"sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5",
"sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2"
"sha256:6f83822ae94818eae2612063a5101a7311e68ae8002005b5e05f03fd74a86a20",
"sha256:9b30f12df9393f0d28af9210ff8efe48d10c94f73e5daf886f10c4b0b0b4f03c"
],
"version": "==8.4.0"
"version": "==8.5.0"
},
"packaging": {
"hashes": [

View File

@ -34,8 +34,8 @@ class UserModel(db.Model):
"""
hours = float(app.config['TOKEN_AUTH_TTL_HOURS'])
payload = {
'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=hours, minutes=0, seconds=0),
'iat': datetime.datetime.utcnow(),
# 'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=hours, minutes=0, seconds=0),
# 'iat': datetime.datetime.utcnow(),
'sub': self.uid
}
return jwt.encode(

View File

@ -213,7 +213,7 @@ Returns information specific to the protocol.
"uid": "cah3us",
"affiliation": "sponsored",
"date_cached": "2020-08-04T19:32:10.075852+00:00"
}
},
},
"pi": {
"PI": {

View File

@ -124,10 +124,11 @@ class LookupService(object):
lookup_model = LookupFileModel(workflow_spec_id=workflow_model.workflow_spec_id,
field_id=field_id,
is_ldap=True)
else:
raise ApiError("unknown_lookup_option",
raise ApiError.from_task_spec("unknown_lookup_option",
"Lookup supports using spreadsheet or LDAP options, "
"and neither of those was provided.")
"and neither of those was provided.", spec)
db.session.add(lookup_model)
db.session.commit()
return lookup_model

View File

@ -130,29 +130,32 @@ class WorkflowService(object):
# Assure field has valid properties
WorkflowService.check_field_properties(field, task)
# First, assure that all expressions can fire, even if they aren't required fields.
if field.has_property(Task.FIELD_PROP_HIDE_EXPRESSION):
if WorkflowService.evaluate_property(Task.FIELD_PROP_HIDE_EXPRESSION, field, task):
continue # Don't mess about with hidden fields.
if field.has_property(Task.FIELD_PROP_REQUIRED_EXPRESSION):
result = WorkflowService.evaluate_property(Task.FIELD_PROP_REQUIRED_EXPRESSION, field, task)
if not result and required_only:
continue # Don't complete fields that are not required.
# Process the label of the field if it is dynamic.
if field.has_property(Task.FIELD_PROP_LABEL_EXPRESSION):
result = WorkflowService.evaluate_property(Task.FIELD_PROP_LABEL_EXPRESSION, field, task)
field.label = result
if field.has_property(Task.FIELD_PROP_VALUE_EXPRESSION):
result = WorkflowService.evaluate_property(Task.FIELD_PROP_VALUE_EXPRESSION, field, task)
field.default_value = result
form_data[field.id] = field.default_value
if required_only and (not field.has_validation(Task.FIELD_CONSTRAINT_REQUIRED) or
field.get_validation(Task.FIELD_CONSTRAINT_REQUIRED).lower().strip() != "true"):
if field.default_value:
form_data[field.id] = field.default_value
continue # Don't include any fields that aren't specifically marked as required.
elif field.has_property("read_only") and field.get_property(Task.FIELD_PROP_READ_ONLY).lower().strip() == "true":
if field.default_value:
form_data[field.id] = field.default_value
# If the field is hidden, it should not produce a value.
if field.has_property(Task.FIELD_PROP_HIDE_EXPRESSION):
if WorkflowService.evaluate_property(Task.FIELD_PROP_HIDE_EXPRESSION, field, task):
continue
# If there is a default value, set it.
if field.default_value:
form_data[field.id] = WorkflowService.get_default_value(field, task)
# If we are only populating required fields, and this isn't required. stop here.
if required_only:
if (not field.has_validation(Task.FIELD_CONSTRAINT_REQUIRED) or
field.get_validation(Task.FIELD_CONSTRAINT_REQUIRED).lower().strip() != "true"):
continue # Don't include any fields that aren't specifically marked as required.
if field.has_property(Task.FIELD_PROP_REQUIRED_EXPRESSION):
result = WorkflowService.evaluate_property(Task.FIELD_PROP_REQUIRED_EXPRESSION, field, task)
if not result and required_only:
continue # Don't complete fields that are not required.
# If it is read only, stop here.
if field.has_property("read_only") and field.get_property(Task.FIELD_PROP_READ_ONLY).lower().strip() == "true":
continue # Don't mess about with read only fields.
if field.has_property(Task.FIELD_PROP_REPEAT):
@ -192,15 +195,63 @@ class WorkflowService(object):
message = f"The field {field.id} contains an invalid expression. {e}"
raise ApiError.from_task(f'invalid_{property_name}', message, task=task)
@staticmethod
def has_lookup(field):
"""Returns true if this is a lookup field."""
"""Note, this does not include enums based on task data, that
is populated when the form is created, not as a lookup from a data table. """
has_ldap_lookup = field.has_property(Task.FIELD_PROP_LDAP_LOOKUP)
has_file_lookup = field.has_property(Task.FIELD_PROP_SPREADSHEET_NAME)
return has_ldap_lookup or has_file_lookup
@staticmethod
def get_default_value(field, task):
has_lookup = WorkflowService.has_lookup(field)
default = field.default_value
# If there is a value expression, use that rather than the default value.
if field.has_property(Task.FIELD_PROP_VALUE_EXPRESSION):
result = WorkflowService.evaluate_property(Task.FIELD_PROP_VALUE_EXPRESSION, field, task)
default = result
# If no default exists, return None
if not default: return None
if field.type == "enum" and not has_lookup:
default_option = next((obj for obj in field.options if obj.id == default), None)
if not default_option:
raise ApiError.from_task("invalid_default", "You specified a default value that does not exist in "
"the enum options ", task)
return {'value': default_option.id, 'label': default_option.name}
elif field.type == "autocomplete" or field.type == "enum":
lookup_model = LookupService.get_lookup_model(task, field)
if field.has_property(Task.FIELD_PROP_LDAP_LOOKUP): # All ldap records get the same person.
return None # There is no default value for ldap.
elif lookup_model:
data = db.session.query(LookupDataModel).\
filter(LookupDataModel.lookup_file_model == lookup_model). \
filter(LookupDataModel.value == default).\
first()
if not data:
raise ApiError.from_task("invalid_default", "You specified a default value that does not exist in "
"the enum options ", task)
return {"value": data.value, "label": data.label, "data": data.data}
else:
raise ApiError.from_task("unknown_lookup_option", "The settings for this auto complete field "
"are incorrect: %s " % field.id, task)
elif field.type == "long":
return int(default)
elif field.type == 'boolean':
return bool(default)
else:
return default
@staticmethod
def get_random_data_for_field(field, task):
has_ldap_lookup = field.has_property(Task.FIELD_PROP_LDAP_LOOKUP)
has_file_lookup = field.has_property(Task.FIELD_PROP_SPREADSHEET_NAME)
has_data_lookup = field.has_property(Task.FIELD_PROP_DATA_NAME)
has_lookup = has_ldap_lookup or has_file_lookup or has_data_lookup
"""Randomly populates the field, mainly concerned with getting enums correct, as
the rest are pretty easy."""
has_lookup = WorkflowService.has_lookup(field)
if field.type == "enum" and not has_lookup:
# If it's a normal enum field with no lookup,
@ -208,17 +259,10 @@ class WorkflowService(object):
if len(field.options) > 0:
random_choice = random.choice(field.options)
if isinstance(random_choice, dict):
return {
'value': random_choice['id'],
'label': random_choice['name']
}
return {'value': random_choice['id'], 'label': random_choice['name']}
else:
# fixme: why it is sometimes an EnumFormFieldOption, and other times not?
# Assume it is an EnumFormFieldOption
return {
'value': random_choice.id,
'label': random_choice.name
}
return {'value': random_choice.id, 'label': random_choice.name}
else:
raise ApiError.from_task("invalid_enum", "You specified an enumeration field (%s),"
" with no options" % field.id, task)
@ -226,19 +270,8 @@ class WorkflowService(object):
# If it has a lookup, get the lookup model from the spreadsheet or task data, then return a random option
# from the lookup model
lookup_model = LookupService.get_lookup_model(task, field)
if has_ldap_lookup: # All ldap records get the same person.
return {
"label": "dhf8r",
"value": "Dan Funk",
"data": {
"uid": "dhf8r",
"display_name": "Dan Funk",
"given_name": "Dan",
"email_address": "dhf8r@virginia.edu",
"department": "Depertment of Psychocosmographictology",
"affiliation": "Rousabout",
"sponsor_type": "Staff"}
}
if field.has_property(Task.FIELD_PROP_LDAP_LOOKUP): # All ldap records get the same person.
return WorkflowService._random_ldap_record()
elif lookup_model:
data = db.session.query(LookupDataModel).filter(
LookupDataModel.lookup_file_model == lookup_model).limit(10).all()
@ -260,8 +293,21 @@ class WorkflowService(object):
else:
return WorkflowService._random_string()
def __get_options(self):
pass
@staticmethod
def _random_ldap_record():
return {
"label": "dhf8r",
"value": "Dan Funk",
"data": {
"uid": "dhf8r",
"display_name": "Dan Funk",
"given_name": "Dan",
"email_address": "dhf8r@virginia.edu",
"department": "Department of Psychocosmographictology",
"affiliation": "Roustabout",
"sponsor_type": "Staff"}
}
@staticmethod
def _random_string(string_length=10):

File diff suppressed because it is too large Load Diff

View File

@ -18,6 +18,11 @@
</camunda:validation>
</camunda:formField>
<camunda:formField id="string_not_required" type="string" />
<camunda:formField id="enum_with_default" label="My Enum" type="enum" defaultValue="maybe">
<camunda:value id="yes" name="Yes" />
<camunda:value id="no" name="No" />
<camunda:value id="maybe" name="Maybe so, I don&#39;t know." />
</camunda:formField>
</camunda:formData>
</bpmn:extensionElements>
<bpmn:incoming>SequenceFlow_0lvudp8</bpmn:incoming>

View File

@ -125,3 +125,12 @@ class TestWorkflowSpecValidation(BaseTest):
self.assertIsNotNone(final_data)
self.assertIn('string_required', final_data)
self.assertNotIn('string_not_required', final_data)
def test_enum_defaults_correctly_populated(self):
self.load_example_data()
spec_model = self.load_test_spec('required_fields')
final_data = WorkflowService.test_spec(spec_model.id, required_only=True)
self.assertIsNotNone(final_data)
self.assertIn('enum_with_default', final_data)
self.assertEquals('maybe', final_data['enum_with_default']['value'])