diff --git a/Pipfile.lock b/Pipfile.lock
index d9c2bfab..ce620efc 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -428,10 +428,10 @@
},
"mako": {
"hashes": [
- "sha256:3139c5d64aa5d175dbafb95027057128b5fbd05a40c53999f3905ceb53366d9d",
- "sha256:8e8b53c71c7e59f3de716b6832c4e401d903af574f6962edbbbf6ecc2a5fe6c9"
+ "sha256:8195c8c1400ceb53496064314c6736719c6f25e7479cd24c77be3d9361cddc27",
+ "sha256:93729a258e4ff0747c876bd9e20df1b9758028946e976324ccd2d68245c7b6a9"
],
- "version": "==1.1.2"
+ "version": "==1.1.3"
},
"markupsafe": {
"hashes": [
@@ -489,11 +489,11 @@
},
"marshmallow-sqlalchemy": {
"hashes": [
- "sha256:3247e41e424146340b03a369f2b7c6f0364477ccedc4e2481e84d5f3a8d3c67f",
- "sha256:dbbe51d28bb28e7ee2782e51310477f7a2c5a111a301f6dd8e264e11ab820427"
+ "sha256:03a555b610bb307689b821b64e2416593ec21a85925c8c436c2cd08ebc6bb85e",
+ "sha256:0ef59c8da8da2e18e808e3880158049e9d72f3031c84cc804b6c533a0eb668a9"
],
"index": "pypi",
- "version": "==0.23.0"
+ "version": "==0.23.1"
},
"numpy": {
"hashes": [
@@ -778,7 +778,7 @@
"spiffworkflow": {
"editable": true,
"git": "https://github.com/sartography/SpiffWorkflow.git",
- "ref": "c8d87826d496af825a184bdc3f0a751e603cfe44"
+ "ref": "b8a064a0bb76c705a1be04ee9bb8ac7beee56eb0"
},
"sqlalchemy": {
"hashes": [
@@ -876,11 +876,11 @@
},
"xlsxwriter": {
"hashes": [
- "sha256:488e1988ab16ff3a9cd58c7656d0a58f8abe46ee58b98eecea78c022db28656b",
- "sha256:97ab487b81534415c5313154203f3e8a637d792b1e6a8201e8f7f71da0203c2a"
+ "sha256:828b3285fc95105f5b1946a6a015b31cf388bd5378fdc6604e4d1b7839df2e77",
+ "sha256:82a3b0e73e3913483da23791d1a25e4d2dbb3837d1be4129473526b9a270a5cc"
],
"index": "pypi",
- "version": "==1.2.8"
+ "version": "==1.2.9"
},
"zipp": {
"hashes": [
diff --git a/crc/api/common.py b/crc/api/common.py
index b89dd8d5..f8673a5b 100644
--- a/crc/api/common.py
+++ b/crc/api/common.py
@@ -1,3 +1,6 @@
+from SpiffWorkflow import WorkflowException
+from SpiffWorkflow.exceptions import WorkflowTaskExecException
+
from crc import ma, app
@@ -34,6 +37,16 @@ class ApiError(Exception):
instance.file_name = task_spec._wf_spec.file
return instance
+ @classmethod
+ def from_workflow_exception(cls, code, message, exp: WorkflowException):
+ """We catch a lot of workflow exception errors,
+ so consolidating the code, and doing the best things
+ we can with the data we have."""
+ if isinstance(exp, WorkflowTaskExecException):
+ return ApiError.from_task(code, message, exp.task)
+ else:
+ return ApiError.from_task_spec(code, message, exp.sender)
+
class ApiErrorSchema(ma.Schema):
class Meta:
diff --git a/crc/api/workflow.py b/crc/api/workflow.py
index efcccc26..9d6e4680 100644
--- a/crc/api/workflow.py
+++ b/crc/api/workflow.py
@@ -42,7 +42,9 @@ def validate_workflow_specification(spec_id):
errors = []
try:
+ # Run the validation twice, the second time, just populate the required fields.
WorkflowService.test_spec(spec_id)
+ WorkflowService.test_spec(spec_id, required_only=True)
except ApiError as ae:
errors.append(ae)
return ApiErrorSchema(many=True).dump(errors)
diff --git a/crc/models/api_models.py b/crc/models/api_models.py
index d53e43bc..eee6d5f5 100644
--- a/crc/models/api_models.py
+++ b/crc/models/api_models.py
@@ -36,6 +36,7 @@ class Task(object):
PROP_OPTIONS_VALUE_COLUMN = "spreadsheet.value.column"
PROP_OPTIONS_LABEL_COL = "spreadsheet.label.column"
PROP_LDAP_LOOKUP = "ldap.lookup"
+ VALIDATION_REQUIRED = "required"
FIELD_TYPE_AUTO_COMPLETE = "autocomplete"
diff --git a/crc/services/workflow_service.py b/crc/services/workflow_service.py
index cf40b84d..dc900400 100644
--- a/crc/services/workflow_service.py
+++ b/crc/services/workflow_service.py
@@ -7,9 +7,7 @@ from SpiffWorkflow import Task as SpiffTask, WorkflowException
from SpiffWorkflow.bpmn.specs.ManualTask import ManualTask
from SpiffWorkflow.bpmn.specs.ScriptTask import ScriptTask
from SpiffWorkflow.bpmn.specs.UserTask import UserTask
-from SpiffWorkflow.camunda.specs.UserTask import EnumFormField
from SpiffWorkflow.dmn.specs.BusinessRuleTask import BusinessRuleTask
-from SpiffWorkflow.exceptions import WorkflowTaskExecException
from SpiffWorkflow.specs import CancelTask, StartTask
from flask import g
from jinja2 import Template
@@ -69,23 +67,22 @@ class WorkflowService(object):
db.session.delete(user)
@staticmethod
- def test_spec(spec_id):
+ def test_spec(spec_id, required_only=False):
"""Runs a spec through it's paces to see if it results in any errors.
Not fool-proof, but a good sanity check. Returns the final data
- output form the last task if successful. """
+ output form the last task if successful.
+
+ required_only can be set to true, in which case this will run the
+ spec, only completing the required fields, rather than everything.
+ """
workflow_model = WorkflowService.make_test_workflow(spec_id)
try:
processor = WorkflowProcessor(workflow_model, validate_only=True)
- except WorkflowTaskExecException as wtee:
- WorkflowService.delete_test_data()
- raise ApiError.from_task("workflow_execution_exception", str(wtee),
- wtee.task)
except WorkflowException as we:
WorkflowService.delete_test_data()
- raise ApiError.from_task_spec("workflow_execution_exception", str(we),
- we.sender)
+ raise ApiError.from_workflow_exception("workflow_execution_exception", str(we), we)
while not processor.bpmn_workflow.is_completed():
try:
@@ -95,28 +92,26 @@ class WorkflowService(object):
task_api = WorkflowService.spiff_task_to_api_task(
task,
add_docs_and_forms=True) # Assure we try to process the documenation, and raise those errors.
- WorkflowService.populate_form_with_random_data(task, task_api)
+ WorkflowService.populate_form_with_random_data(task, task_api, required_only)
task.complete()
- except WorkflowTaskExecException as wtee:
- WorkflowService.delete_test_data()
- raise ApiError.from_task("workflow_execution_exception", str(wtee),
- wtee.task)
except WorkflowException as we:
WorkflowService.delete_test_data()
- raise ApiError.from_task_spec("workflow_execution_exception", str(we),
- we.sender)
+ raise ApiError.from_workflow_exception("workflow_execution_exception", str(we), we)
WorkflowService.delete_test_data()
return processor.bpmn_workflow.last_task.data
@staticmethod
- def populate_form_with_random_data(task, task_api):
+ def populate_form_with_random_data(task, task_api, required_only):
"""populates a task with random data - useful for testing a spec."""
if not hasattr(task.task_spec, 'form'): return
form_data = {}
for field in task_api.form.fields:
+ if required_only and (not field.has_validation(Task.VALIDATION_REQUIRED) or
+ field.get_validation(Task.VALIDATION_REQUIRED).lower().strip() != "true"):
+ continue # Don't include any fields that aren't specifically marked as required.
if field.has_property(Task.PROP_OPTIONS_REPEAT):
group = field.get_property(Task.PROP_OPTIONS_REPEAT)
if group not in form_data:
diff --git a/tests/data/decision_table/decision_table.bpmn b/tests/data/decision_table/decision_table.bpmn
index 796233e5..82bcb385 100644
--- a/tests/data/decision_table/decision_table.bpmn
+++ b/tests/data/decision_table/decision_table.bpmn
@@ -1,5 +1,5 @@
-
+
SequenceFlow_1ma1wxb
@@ -8,7 +8,11 @@
-
+
+
+
+
+
SequenceFlow_1ma1wxb
@@ -26,38 +30,37 @@ Based on the information you provided (Ginger left {{num_presents}}, we recommen
## {{message}}
-We hope you both have an excellent day!
-
+We hope you both have an excellent day!
SequenceFlow_0grui6f
-
-
-
-
-
-
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
diff --git a/tests/data/exclusive_gateway/exclusive_gateway.bpmn b/tests/data/exclusive_gateway/exclusive_gateway.bpmn
index 1c7e55fe..8467c954 100644
--- a/tests/data/exclusive_gateway/exclusive_gateway.bpmn
+++ b/tests/data/exclusive_gateway/exclusive_gateway.bpmn
@@ -8,7 +8,11 @@
-
+
+
+
+
+
SequenceFlow_1pnq3kg
diff --git a/tests/data/random_fact/random_fact.bpmn b/tests/data/random_fact/random_fact.bpmn
index 81f355c3..628f1bd4 100644
--- a/tests/data/random_fact/random_fact.bpmn
+++ b/tests/data/random_fact/random_fact.bpmn
@@ -1,5 +1,5 @@
-
+
SequenceFlow_0c7wlth
@@ -108,6 +108,9 @@ Autoconverted link https://github.com/nodeca/pica (enable linkify to see)
+
+
+
@@ -121,8 +124,7 @@ Autoconverted link https://github.com/nodeca/pica (enable linkify to see)
SequenceFlow_0641sh6
-
-
+
@@ -155,6 +157,18 @@ Your random fact is:
+
+
+
+
+
+
+
+
+
+
+
+
@@ -164,35 +178,23 @@ Your random fact is:
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/tests/data/required_fields/required_fields.bpmn b/tests/data/required_fields/required_fields.bpmn
new file mode 100644
index 00000000..7612f69b
--- /dev/null
+++ b/tests/data/required_fields/required_fields.bpmn
@@ -0,0 +1,48 @@
+
+
+
+
+ SequenceFlow_0lvudp8
+
+
+
+ SequenceFlow_02vev7n
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SequenceFlow_0lvudp8
+ SequenceFlow_02vev7n
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/test_workflow_processor.py b/tests/test_workflow_processor.py
index 36d23755..b3f6c374 100644
--- a/tests/test_workflow_processor.py
+++ b/tests/test_workflow_processor.py
@@ -25,7 +25,7 @@ class TestWorkflowProcessor(BaseTest):
def _populate_form_with_random_data(self, task):
api_task = WorkflowService.spiff_task_to_api_task(task, add_docs_and_forms=True)
- WorkflowService.populate_form_with_random_data(task, api_task)
+ WorkflowService.populate_form_with_random_data(task, api_task, required_only=False)
def get_processor(self, study_model, spec_model):
workflow_model = StudyService._create_workflow_model(study_model, spec_model)
diff --git a/tests/test_workflow_service.py b/tests/test_workflow_service.py
index 281d1756..f509f642 100644
--- a/tests/test_workflow_service.py
+++ b/tests/test_workflow_service.py
@@ -77,5 +77,5 @@ class TestWorkflowService(BaseTest):
processor.do_engine_steps()
task = processor.next_task()
task_api = WorkflowService.spiff_task_to_api_task(task, add_docs_and_forms=True)
- WorkflowService.populate_form_with_random_data(task, task_api)
+ WorkflowService.populate_form_with_random_data(task, task_api, required_only=False)
self.assertTrue(isinstance(task.data["sponsor"], dict))
\ No newline at end of file
diff --git a/tests/test_workflow_spec_validation_api.py b/tests/test_workflow_spec_validation_api.py
index d46746dc..1594d681 100644
--- a/tests/test_workflow_spec_validation_api.py
+++ b/tests/test_workflow_spec_validation_api.py
@@ -95,4 +95,17 @@ class TestWorkflowSpecValidation(BaseTest):
spec_model = self.load_test_spec('repeat_form')
final_data = WorkflowService.test_spec(spec_model.id)
self.assertIsNotNone(final_data)
- self.assertIn('cats', final_data)
\ No newline at end of file
+ self.assertIn('cats', final_data)
+
+ def test_required_fields(self):
+ self.load_example_data()
+ spec_model = self.load_test_spec('required_fields')
+ final_data = WorkflowService.test_spec(spec_model.id)
+ self.assertIsNotNone(final_data)
+ self.assertIn('string_required', final_data)
+ self.assertIn('string_not_required', final_data)
+
+ final_data = WorkflowService.test_spec(spec_model.id, required_only=True)
+ self.assertIsNotNone(final_data)
+ self.assertIn('string_required', final_data)
+ self.assertNotIn('string_not_required', final_data)