Looks up enum options from task data

This commit is contained in:
Aaron Louie 2020-07-13 17:46:28 -04:00
parent 32343c04d6
commit 07066b8a16
4 changed files with 165 additions and 9 deletions

View File

@ -86,10 +86,11 @@ class LookupService(object):
processor = WorkflowProcessor(workflow_model) # VERY expensive, Ludicrous for lookup / type ahead
spiff_task, field = processor.find_task_and_field_by_field_id(field_id)
# Use the contents of a file to populate enum field options
if field.has_property(Task.PROP_OPTIONS_FILE_NAME):
if not (field.has_property(Task.PROP_OPTIONS_FILE_VALUE_COLUMN) or
field.has_property(Task.PROP_OPTIONS_FILE_LABEL_COLUMN)):
raise ApiError.from_task("invalid_emum",
raise ApiError.from_task("invalid_enum",
"For enumerations based on an xls file, you must include 3 properties: %s, "
"%s, and %s" % (Task.PROP_OPTIONS_FILE_NAME,
Task.PROP_OPTIONS_FILE_VALUE_COLUMN,
@ -111,30 +112,49 @@ class LookupService(object):
lookup_model = LookupService.build_lookup_table(data_model, value_column, label_column,
workflow_model.workflow_spec_id, field_id)
# Use the value of some other task data field to populate enum field options
elif field.has_property(Task.PROP_OPTIONS_DATA_NAME):
if not (field.has_property(Task.PROP_OPTIONS_DATA_VALUE_COLUMN) or
field.has_property(Task.PROP_OPTIONS_DATA_LABEL_COLUMN)):
raise ApiError.from_task("invalid_emum",
raise ApiError.from_task("invalid_enum",
"For enumerations based on task data, you must include 3 properties: %s, "
"%s, and %s" % (Task.PROP_OPTIONS_DATA_NAME,
Task.PROP_OPTIONS_DATA_VALUE_COLUMN,
Task.PROP_OPTIONS_DATA_LABEL_COLUMN),
task=spiff_task)
prop = field.get_property(Task.PROP_OPTIONS_DATA_NAME)
if prop not in spiff_task.data:
raise ApiError.from_task("invalid_enum", "For enumerations based on task data, task data must have "
"a property called '%s'" % prop,
task=spiff_task)
# Get the enum options from the task data
data_model = spiff_task.data.__getattribute__(Task.PROP_OPTIONS_DATA_NAME)
data_model = spiff_task.data[prop]
value_column = field.get_property(Task.PROP_OPTIONS_DATA_VALUE_COLUMN)
label_column = field.get_property(Task.PROP_OPTIONS_DATA_LABEL_COLUMN)
lookup_model = LookupService.build_lookup_table(data_model, value_column, label_column,
workflow_model.workflow_spec_id, field_id)
lookup_model = LookupFileModel(workflow_spec_id=workflow_model.workflow_spec_id,
field_id=field_id,
is_ldap=False)
db.session.add(lookup_model)
items = data_model.items() if isinstance(data_model, dict) else data_model
for item in items:
lookup_data = LookupDataModel(lookup_file_model=lookup_model,
value=item[value_column],
label=item[label_column],
data=item)
db.session.add(lookup_data)
db.session.commit()
# Use the results of an LDAP request to populate enum field options
elif field.has_property(Task.PROP_LDAP_LOOKUP):
lookup_model = LookupFileModel(workflow_spec_id=workflow_model.workflow_spec_id,
field_id=field_id,
is_ldap=True)
else:
raise ApiError("unknown_lookup_option",
"Lookup supports using spreadsheet options or ldap options, and neither was provided.")
"Lookup supports using spreadsheet, task data, or LDAP options, "
"and none of those was provided.")
db.session.add(lookup_model)
db.session.commit()
return lookup_model
@ -149,11 +169,11 @@ class LookupService(object):
df = xls.parse(xls.sheet_names[0]) # Currently we only look at the fist sheet.
df = pd.DataFrame(df).replace({np.nan: None})
if value_column not in df:
raise ApiError("invalid_emum",
raise ApiError("invalid_enum",
"The file %s does not contain a column named % s" % (data_model.file_model.name,
value_column))
if label_column not in df:
raise ApiError("invalid_emum",
raise ApiError("invalid_enum",
"The file %s does not contain a column named % s" % (data_model.file_model.name,
label_column))

View File

@ -394,7 +394,7 @@ class WorkflowService(object):
# If this is an auto-complete field, do not populate options, a lookup will happen later.
if field.type == Task.FIELD_TYPE_AUTO_COMPLETE:
pass
elif field.has_property(Task.PROP_OPTIONS_FILE_NAME):
elif field.has_property(Task.PROP_OPTIONS_FILE_NAME) or field.has_property(Task.PROP_OPTIONS_DATA_NAME):
lookup_model = LookupService.get_lookup_model(spiff_task, field)
data = db.session.query(LookupDataModel).filter(LookupDataModel.lookup_file_model == lookup_model).all()
if not hasattr(field, 'options'):

View File

@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_1v1rp1q" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.0.0">
<bpmn:process id="Process_1vu5nxl" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>SequenceFlow_0lvudp8</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="SequenceFlow_0lvudp8" sourceRef="StartEvent_1" targetRef="Activity_0oa736e" />
<bpmn:endEvent id="EndEvent_0q4qzl9">
<bpmn:incoming>SequenceFlow_02vev7n</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="SequenceFlow_02vev7n" sourceRef="Task_14svgcu" targetRef="EndEvent_0q4qzl9" />
<bpmn:userTask id="Task_14svgcu" name="Enum Lookup Form" camunda:formKey="EnumForm">
<bpmn:extensionElements>
<camunda:formData>
<camunda:formField id="guest_of_honor" label="Who is the guest of honor?" type="enum">
<camunda:properties>
<camunda:property id="data.name" value="invitees" />
<camunda:property id="data.value.column" value="secret_id" />
<camunda:property id="data.label.column" value="display_name" />
</camunda:properties>
</camunda:formField>
</camunda:formData>
</bpmn:extensionElements>
<bpmn:incoming>Flow_1yet4a9</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_02vev7n</bpmn:outgoing>
</bpmn:userTask>
<bpmn:sequenceFlow id="Flow_1yet4a9" sourceRef="Activity_0oa736e" targetRef="Task_14svgcu" />
<bpmn:userTask id="Activity_0oa736e" name="Who do you want to invite to your tea party?" camunda:formKey="task_1_form">
<bpmn:extensionElements>
<camunda:formData>
<camunda:formField id="first_name" label="What is their first name?" type="string">
<camunda:properties>
<camunda:property id="repeat" value="invitees" />
</camunda:properties>
</camunda:formField>
<camunda:formField id="last_name" label="What is their last name?" type="string">
<camunda:properties>
<camunda:property id="repeat" value="invitees" />
</camunda:properties>
</camunda:formField>
<camunda:formField id="age" label="How old are they?" type="long">
<camunda:properties>
<camunda:property id="repeat" value="invitees" />
</camunda:properties>
</camunda:formField>
<camunda:formField id="likes_pie" label="Do they like pie?" type="boolean">
<camunda:properties>
<camunda:property id="repeat" value="invitees" />
</camunda:properties>
</camunda:formField>
<camunda:formField id="num_lumps" label="How many lumps of sugar in their tea?" type="long">
<camunda:properties>
<camunda:property id="repeat" value="invitees" />
</camunda:properties>
</camunda:formField>
<camunda:formField id="secret_id" label="What is their secret identity?" type="string">
<camunda:properties>
<camunda:property id="repeat" value="invitees" />
</camunda:properties>
</camunda:formField>
<camunda:formField id="display_name" label="What&#39;s their nickname?" type="string">
<camunda:properties>
<camunda:property id="repeat" value="invitees" />
</camunda:properties>
</camunda:formField>
</camunda:formData>
</bpmn:extensionElements>
<bpmn:incoming>SequenceFlow_0lvudp8</bpmn:incoming>
<bpmn:outgoing>Flow_1yet4a9</bpmn:outgoing>
</bpmn:userTask>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1vu5nxl">
<bpmndi:BPMNEdge id="SequenceFlow_02vev7n_di" bpmnElement="SequenceFlow_02vev7n">
<di:waypoint x="500" y="117" />
<di:waypoint x="542" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="SequenceFlow_0lvudp8_di" bpmnElement="SequenceFlow_0lvudp8">
<di:waypoint x="215" y="117" />
<di:waypoint x="250" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1yet4a9_di" bpmnElement="Flow_1yet4a9">
<di:waypoint x="350" y="117" />
<di:waypoint x="400" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0k8rwp9_di" bpmnElement="Activity_0oa736e">
<dc:Bounds x="250" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="UserTask_18ly1yq_di" bpmnElement="Task_14svgcu">
<dc:Bounds x="400" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="EndEvent_0q4qzl9_di" bpmnElement="EndEvent_0q4qzl9">
<dc:Bounds x="542" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -403,6 +403,42 @@ class TestTasksApi(BaseTest):
self.assert_options_populated(results, ['CUSTOMER_NUMBER', 'CUSTOMER_NAME', 'CUSTOMER_CLASS_MEANING'])
self.assertIsInstance(results[0]['data'], dict)
def test_lookup_endpoint_enum_in_task_data(self):
self.load_example_data()
workflow = self.create_workflow('enum_options_from_task_data')
# get the first form in the two form workflow.
workflow_api = self.get_workflow_api(workflow)
task = workflow_api.next_task
workflow_api = self.complete_form(workflow, task, {'invitees': [
{'first_name': 'Alistair', 'last_name': 'Aardvark', 'age': 43, 'likes_pie': True, 'num_lumps': 21, 'secret_id': 'Antimony', 'display_name': 'Professor Alistair A. Aardvark'},
{'first_name': 'Berthilda', 'last_name': 'Binturong', 'age': 12, 'likes_pie': False, 'num_lumps': 34, 'secret_id': 'Beryllium', 'display_name': 'Dr. Berthilda B. Binturong'},
{'first_name': 'Chesterfield', 'last_name': 'Capybara', 'age': 32, 'likes_pie': True, 'num_lumps': 1, 'secret_id': 'Cadmium', 'display_name': 'The Honorable C. C. Capybara'},
]})
task = workflow_api.next_task
field_id = task.form['fields'][0]['id']
options = task.form['fields'][0]['options']
self.assertEqual(3, len(options))
option_id = options[0]['id']
self.assertEqual('Professor Alistair A. Aardvark', options[0]['name'])
self.assertEqual('Dr. Berthilda B. Binturong', options[1]['name'])
self.assertEqual('The Honorable C. C. Capybara', options[2]['name'])
self.assertEqual('Alistair', options[0]['data']['first_name'])
self.assertEqual('Berthilda', options[1]['data']['first_name'])
self.assertEqual('Chesterfield', options[2]['data']['first_name'])
rv = self.app.get('/v1.0/workflow/%i/lookup/%s?value=%s' %
(workflow.id, field_id, option_id),
headers=self.logged_in_headers(),
content_type="application/json")
self.assert_success(rv)
results = json.loads(rv.get_data(as_text=True))
self.assertEqual(1, len(results))
self.assert_options_populated(results, ['first_name', 'last_name', 'age', 'likes_pie', 'num_lumps',
'secret_id', 'display_name'])
self.assertIsInstance(results[0]['data'], dict)
def test_lookup_endpoint_for_task_ldap_field_lookup(self):
self.load_example_data()
workflow = self.create_workflow('ldap_lookup')