Merge pull request #254 from sartography/bug/225_enum_lookup_same_field_name

Bug/225 enum lookup same field name
This commit is contained in:
Dan Funk 2021-03-02 14:55:47 -05:00 committed by GitHub
commit 7d97fe107d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 279 additions and 60 deletions

View File

@ -954,7 +954,7 @@ paths:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/Workflow" $ref: "#/components/schemas/Workflow"
/workflow/{workflow_id}/lookup/{field_id}: /workflow/{workflow_id}/lookup/{task_spec_name}/{field_id}:
parameters: parameters:
- name: workflow_id - name: workflow_id
in: path in: path
@ -963,6 +963,12 @@ paths:
schema: schema:
type: integer type: integer
format: int32 format: int32
- name: task_spec_name
in: path
required: true
description: The name of the current task
schema:
type: string
- name: field_id - name: field_id
in: path in: path
required: true required: true

View File

@ -252,7 +252,7 @@ def delete_workflow_spec_category(cat_id):
session.commit() session.commit()
def lookup(workflow_id, field_id, query=None, value=None, limit=10): def lookup(workflow_id, task_spec_name, field_id, query=None, value=None, limit=10):
""" """
given a field in a task, attempts to find the lookup table or function associated given a field in a task, attempts to find the lookup table or function associated
with that field and runs a full-text query against it to locate the values and with that field and runs a full-text query against it to locate the values and
@ -260,7 +260,7 @@ def lookup(workflow_id, field_id, query=None, value=None, limit=10):
Tries to be fast, but first runs will be very slow. Tries to be fast, but first runs will be very slow.
""" """
workflow = session.query(WorkflowModel).filter(WorkflowModel.id == workflow_id).first() workflow = session.query(WorkflowModel).filter(WorkflowModel.id == workflow_id).first()
lookup_data = LookupService.lookup(workflow, field_id, query, value, limit) lookup_data = LookupService.lookup(workflow, task_spec_name, field_id, query, value, limit)
return LookupDataSchema(many=True).dump(lookup_data) return LookupDataSchema(many=True).dump(lookup_data)

View File

@ -148,6 +148,7 @@ class LookupFileModel(db.Model):
__tablename__ = 'lookup_file' __tablename__ = 'lookup_file'
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
workflow_spec_id = db.Column(db.String) workflow_spec_id = db.Column(db.String)
task_spec_id = db.Column(db.String)
field_id = db.Column(db.String) field_id = db.Column(db.String)
is_ldap = db.Column(db.Boolean) # Allows us to run an ldap query instead of a db lookup. is_ldap = db.Column(db.Boolean) # Allows us to run an ldap query instead of a db lookup.
file_data_model_id = db.Column(db.Integer, db.ForeignKey('file_data.id')) file_data_model_id = db.Column(db.Integer, db.ForeignKey('file_data.id'))

View File

@ -40,13 +40,14 @@ class LookupService(object):
def get_lookup_model(spiff_task, field): def get_lookup_model(spiff_task, field):
workflow_id = spiff_task.workflow.data[WorkflowProcessor.WORKFLOW_ID_KEY] workflow_id = spiff_task.workflow.data[WorkflowProcessor.WORKFLOW_ID_KEY]
workflow = db.session.query(WorkflowModel).filter(WorkflowModel.id == workflow_id).first() workflow = db.session.query(WorkflowModel).filter(WorkflowModel.id == workflow_id).first()
return LookupService.__get_lookup_model(workflow, field.id) return LookupService.__get_lookup_model(workflow, spiff_task.task_spec.name, field.id)
@staticmethod @staticmethod
def __get_lookup_model(workflow, field_id): def __get_lookup_model(workflow, task_spec_id, field_id):
lookup_model = db.session.query(LookupFileModel) \ lookup_model = db.session.query(LookupFileModel) \
.filter(LookupFileModel.workflow_spec_id == workflow.workflow_spec_id) \ .filter(LookupFileModel.workflow_spec_id == workflow.workflow_spec_id) \
.filter(LookupFileModel.field_id == field_id) \ .filter(LookupFileModel.field_id == field_id) \
.filter(LookupFileModel.task_spec_id == task_spec_id) \
.order_by(desc(LookupFileModel.id)).first() .order_by(desc(LookupFileModel.id)).first()
# one more quick query, to see if the lookup file is still related to this workflow. # one more quick query, to see if the lookup file is still related to this workflow.
@ -59,14 +60,15 @@ class LookupService(object):
if not is_current: if not is_current:
# Very very very expensive, but we don't know need this till we do. # Very very very expensive, but we don't know need this till we do.
lookup_model = LookupService.create_lookup_model(workflow, field_id) logging.warning("!!!! Making a very expensive call to update the lookup models.")
lookup_model = LookupService.create_lookup_model(workflow, task_spec_id, field_id)
return lookup_model return lookup_model
@staticmethod @staticmethod
def lookup(workflow, field_id, query, value=None, limit=10): def lookup(workflow, task_spec_id, field_id, query, value=None, limit=10):
lookup_model = LookupService.__get_lookup_model(workflow, field_id) lookup_model = LookupService.__get_lookup_model(workflow, task_spec_id, field_id)
if lookup_model.is_ldap: if lookup_model.is_ldap:
return LookupService._run_ldap_query(query, limit) return LookupService._run_ldap_query(query, limit)
@ -74,7 +76,7 @@ class LookupService(object):
return LookupService._run_lookup_query(lookup_model, query, value, limit) return LookupService._run_lookup_query(lookup_model, query, value, limit)
@staticmethod @staticmethod
def create_lookup_model(workflow_model, field_id): def create_lookup_model(workflow_model, task_spec_id, field_id):
""" """
This is all really expensive, but should happen just once (per file change). This is all really expensive, but should happen just once (per file change).
@ -84,11 +86,12 @@ class LookupService(object):
Returns: an array of LookupData, suitable for returning to the API. Returns: an array of LookupData, suitable for returning to the API.
""" """
processor = WorkflowProcessor(workflow_model) # VERY expensive, Ludicrous for lookup / type ahead processor = WorkflowProcessor(workflow_model) # VERY expensive, Ludicrous for lookup / type ahead
spec, field = processor.find_spec_and_field_by_field_id(field_id) spec, field = processor.find_spec_and_field(task_spec_id, field_id)
# Clear out all existing lookup models for this workflow and field. # Clear out all existing lookup models for this workflow and field.
existing_models = db.session.query(LookupFileModel) \ existing_models = db.session.query(LookupFileModel) \
.filter(LookupFileModel.workflow_spec_id == workflow_model.workflow_spec_id) \ .filter(LookupFileModel.workflow_spec_id == workflow_model.workflow_spec_id) \
.filter(LookupFileModel.task_spec_id == task_spec_id) \
.filter(LookupFileModel.field_id == field_id).all() .filter(LookupFileModel.field_id == field_id).all()
for model in existing_models: # Do it one at a time to cause the required cascade of deletes. for model in existing_models: # Do it one at a time to cause the required cascade of deletes.
db.session.delete(model) db.session.delete(model)
@ -117,7 +120,7 @@ class LookupService(object):
data_model = latest_files[0] data_model = latest_files[0]
lookup_model = LookupService.build_lookup_table(data_model, value_column, label_column, lookup_model = LookupService.build_lookup_table(data_model, value_column, label_column,
workflow_model.workflow_spec_id, field_id) workflow_model.workflow_spec_id, task_spec_id, field_id)
# Use the results of an LDAP request to populate enum field options # Use the results of an LDAP request to populate enum field options
elif field.has_property(Task.FIELD_PROP_LDAP_LOOKUP): elif field.has_property(Task.FIELD_PROP_LDAP_LOOKUP):
@ -134,7 +137,7 @@ class LookupService(object):
return lookup_model return lookup_model
@staticmethod @staticmethod
def build_lookup_table(data_model: FileDataModel, value_column, label_column, workflow_spec_id, field_id): def build_lookup_table(data_model: FileDataModel, value_column, label_column, workflow_spec_id, task_spec_id, field_id):
""" In some cases the lookup table can be very large. This method will add all values to the database """ In some cases the lookup table can be very large. This method will add all values to the database
in a way that can be searched and returned via an api call - rather than sending the full set of in a way that can be searched and returned via an api call - rather than sending the full set of
options along with the form. It will only open the file and process the options if something has options along with the form. It will only open the file and process the options if something has
@ -153,6 +156,7 @@ class LookupService(object):
lookup_model = LookupFileModel(workflow_spec_id=workflow_spec_id, lookup_model = LookupFileModel(workflow_spec_id=workflow_spec_id,
field_id=field_id, field_id=field_id,
task_spec_id=task_spec_id,
file_data_model_id=data_model.id, file_data_model_id=data_model.id,
is_ldap=False) is_ldap=False)

View File

@ -473,11 +473,11 @@ class WorkflowProcessor(object):
if nav_item['task_id'] == task.id: if nav_item['task_id'] == task.id:
return nav_item return nav_item
def find_spec_and_field_by_field_id(self, field_id): def find_spec_and_field(self, spec_name, field_id):
"""Tracks down a form field by name in the workflow spec, """Tracks down a form field by name in the workflow spec,
only looks at ready tasks. Returns a tuple of the task, and form""" only looks at ready tasks. Returns a tuple of the task, and form"""
for spec in self.bpmn_workflow.spec.task_specs.values(): for spec in self.bpmn_workflow.spec.task_specs.values():
if hasattr(spec, "form"): if spec.name == spec_name and hasattr(spec, "form"):
for field in spec.form.fields: for field in spec.form.fields:
if field.id == field_id: if field.id == field_id:
return spec, field return spec, field

View File

@ -0,0 +1,31 @@
"""empty message
Revision ID: c872232ebdcb
Revises: f28ee3722c49
Create Date: 2021-03-02 14:35:13.911050
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'c872232ebdcb'
down_revision = 'f28ee3722c49'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.execute('delete from lookup_data')
op.execute('delete from lookup_file')
op.add_column('lookup_file', sa.Column('task_spec_id', sa.String(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('lookup_file', 'task_spec_id')
# ### end Alembic commands ###

View File

@ -25,12 +25,6 @@ def upgrade():
def downgrade(): def downgrade():
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.create_table('approval_file',
sa.Column('approval_id', sa.INTEGER(), autoincrement=False, nullable=False),
sa.Column('file_data_id', sa.INTEGER(), autoincrement=False, nullable=False),
sa.ForeignKeyConstraint(['approval_id'], ['approval.id'], name='approval_file_approval_id_fkey'),
sa.ForeignKeyConstraint(['file_data_id'], ['file_data.id'], name='approval_file_file_data_id_fkey')
)
op.create_table('approval', op.create_table('approval',
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False), sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
sa.Column('study_id', sa.INTEGER(), autoincrement=False, nullable=False), sa.Column('study_id', sa.INTEGER(), autoincrement=False, nullable=False),
@ -45,4 +39,10 @@ def downgrade():
sa.ForeignKeyConstraint(['workflow_id'], ['workflow.id'], name='approval_workflow_id_fkey'), sa.ForeignKeyConstraint(['workflow_id'], ['workflow.id'], name='approval_workflow_id_fkey'),
sa.PrimaryKeyConstraint('id', name='approval_pkey') sa.PrimaryKeyConstraint('id', name='approval_pkey')
) )
op.create_table('approval_file',
sa.Column('approval_id', sa.INTEGER(), autoincrement=False, nullable=False),
sa.Column('file_data_id', sa.INTEGER(), autoincrement=False, nullable=False),
sa.ForeignKeyConstraint(['approval_id'], ['approval.id'], name='approval_file_approval_id_fkey'),
sa.ForeignKeyConstraint(['file_data_id'], ['file_data.id'], name='approval_file_file_data_id_fkey')
)
# ### end Alembic commands ### # ### end Alembic commands ###

Binary file not shown.

View File

@ -0,0 +1,145 @@
<?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:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_13oadue" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.7.3">
<bpmn:process id="Process_1e56be7" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_07vc55t</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_07vc55t" sourceRef="StartEvent_1" targetRef="Activity_0s5qx04" />
<bpmn:exclusiveGateway id="Gateway_1j2ytgn">
<bpmn:incoming>Flow_1m73p95</bpmn:incoming>
<bpmn:outgoing>Flow_0gb1k4g</bpmn:outgoing>
<bpmn:outgoing>Flow_08kr305</bpmn:outgoing>
</bpmn:exclusiveGateway>
<bpmn:sequenceFlow id="Flow_0gb1k4g" sourceRef="Gateway_1j2ytgn" targetRef="animals">
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">type == 'fruit'</bpmn:conditionExpression>
</bpmn:sequenceFlow>
<bpmn:sequenceFlow id="Flow_08kr305" sourceRef="Gateway_1j2ytgn" targetRef="fruits">
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">type=="fruit"</bpmn:conditionExpression>
</bpmn:sequenceFlow>
<bpmn:exclusiveGateway id="Gateway_14eyj6z">
<bpmn:incoming>Flow_09ik0zr</bpmn:incoming>
<bpmn:incoming>Flow_1tzwe06</bpmn:incoming>
<bpmn:outgoing>Flow_1ym0gex</bpmn:outgoing>
</bpmn:exclusiveGateway>
<bpmn:sequenceFlow id="Flow_09ik0zr" sourceRef="animals" targetRef="Gateway_14eyj6z" />
<bpmn:sequenceFlow id="Flow_1tzwe06" sourceRef="fruits" targetRef="Gateway_14eyj6z" />
<bpmn:endEvent id="Event_0guzzkq">
<bpmn:incoming>Flow_1ym0gex</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1ym0gex" sourceRef="Gateway_14eyj6z" targetRef="Event_0guzzkq" />
<bpmn:userTask id="animals" name="Animals" camunda:formKey="FormA">
<bpmn:extensionElements>
<camunda:formData>
<camunda:formField id="selectedItem" label="Select An Item" type="enum">
<camunda:properties>
<camunda:property id="spreadsheet.name" value="animals.xlsx" />
<camunda:property id="spreadsheet.value.column" value="Value" />
<camunda:property id="spreadsheet.label.column" value="Label" />
</camunda:properties>
</camunda:formField>
</camunda:formData>
</bpmn:extensionElements>
<bpmn:incoming>Flow_0gb1k4g</bpmn:incoming>
<bpmn:outgoing>Flow_09ik0zr</bpmn:outgoing>
</bpmn:userTask>
<bpmn:userTask id="fruits" name="Fruits" camunda:formKey="FromB">
<bpmn:extensionElements>
<camunda:formData>
<camunda:formField id="selectedItem" type="enum">
<camunda:properties>
<camunda:property id="spreadsheet.name" value="fruits.xlsx" />
<camunda:property id="spreadsheet.value.column" value="Value" />
<camunda:property id="spreadsheet.label.column" value="Label" />
</camunda:properties>
</camunda:formField>
</camunda:formData>
</bpmn:extensionElements>
<bpmn:incoming>Flow_08kr305</bpmn:incoming>
<bpmn:outgoing>Flow_1tzwe06</bpmn:outgoing>
</bpmn:userTask>
<bpmn:sequenceFlow id="Flow_1m73p95" sourceRef="Activity_0s5qx04" targetRef="Gateway_1j2ytgn" />
<bpmn:userTask id="Activity_0s5qx04" name="What do you like" camunda:formKey="choose">
<bpmn:extensionElements>
<camunda:formData>
<camunda:formField id="type" type="enum">
<camunda:validation>
<camunda:constraint name="required" config="True" />
</camunda:validation>
<camunda:value id="animals" name="Animals" />
<camunda:value id="fruits" name="Fruits" />
</camunda:formField>
</camunda:formData>
</bpmn:extensionElements>
<bpmn:incoming>Flow_07vc55t</bpmn:incoming>
<bpmn:outgoing>Flow_1m73p95</bpmn:outgoing>
</bpmn:userTask>
<bpmn:textAnnotation id="TextAnnotation_1vfpzfh">
<bpmn:text>Sheet one and sheet two each reference different spread sheets, but  they use  the same form name.</bpmn:text>
</bpmn:textAnnotation>
<bpmn:association id="Association_0w3ioqq" sourceRef="animals" targetRef="TextAnnotation_1vfpzfh" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1e56be7">
<bpmndi:BPMNShape id="TextAnnotation_1vfpzfh_di" bpmnElement="TextAnnotation_1vfpzfh">
<dc:Bounds x="640" y="80" width="200" height="60" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_07vc55t_di" bpmnElement="Flow_07vc55t">
<di:waypoint x="188" y="307" />
<di:waypoint x="250" y="307" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0gb1k4g_di" bpmnElement="Flow_0gb1k4g">
<di:waypoint x="410" y="282" />
<di:waypoint x="410" y="240" />
<di:waypoint x="490" y="240" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_08kr305_di" bpmnElement="Flow_08kr305">
<di:waypoint x="410" y="332" />
<di:waypoint x="410" y="370" />
<di:waypoint x="490" y="370" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_09ik0zr_di" bpmnElement="Flow_09ik0zr">
<di:waypoint x="590" y="240" />
<di:waypoint x="670" y="240" />
<di:waypoint x="670" y="282" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1tzwe06_di" bpmnElement="Flow_1tzwe06">
<di:waypoint x="590" y="370" />
<di:waypoint x="670" y="370" />
<di:waypoint x="670" y="332" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1ym0gex_di" bpmnElement="Flow_1ym0gex">
<di:waypoint x="695" y="307" />
<di:waypoint x="752" y="307" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1m73p95_di" bpmnElement="Flow_1m73p95">
<di:waypoint x="350" y="307" />
<di:waypoint x="385" y="307" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Gateway_1j2ytgn_di" bpmnElement="Gateway_1j2ytgn" isMarkerVisible="true">
<dc:Bounds x="385" y="282" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_14eyj6z_di" bpmnElement="Gateway_14eyj6z" isMarkerVisible="true">
<dc:Bounds x="645" y="282" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0guzzkq_di" bpmnElement="Event_0guzzkq">
<dc:Bounds x="752" y="289" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0eua66n_di" bpmnElement="animals">
<dc:Bounds x="490" y="200" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0kcbe51_di" bpmnElement="fruits">
<dc:Bounds x="490" y="330" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="152" y="289" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1fixobo_di" bpmnElement="Activity_0s5qx04">
<dc:Bounds x="250" y="267" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Association_0w3ioqq_di" bpmnElement="Association_0w3ioqq">
<di:waypoint x="589" y="208" />
<di:waypoint x="694" y="140" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

Binary file not shown.

View File

@ -4,12 +4,12 @@
<bpmn:startEvent id="StartEvent_1"> <bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>SequenceFlow_0lvudp8</bpmn:outgoing> <bpmn:outgoing>SequenceFlow_0lvudp8</bpmn:outgoing>
</bpmn:startEvent> </bpmn:startEvent>
<bpmn:sequenceFlow id="SequenceFlow_0lvudp8" sourceRef="StartEvent_1" targetRef="Task_14svgcu" /> <bpmn:sequenceFlow id="SequenceFlow_0lvudp8" sourceRef="StartEvent_1" targetRef="TaskEnumLookup" />
<bpmn:endEvent id="EndEvent_0q4qzl9"> <bpmn:endEvent id="EndEvent_0q4qzl9">
<bpmn:incoming>SequenceFlow_02vev7n</bpmn:incoming> <bpmn:incoming>SequenceFlow_02vev7n</bpmn:incoming>
</bpmn:endEvent> </bpmn:endEvent>
<bpmn:sequenceFlow id="SequenceFlow_02vev7n" sourceRef="Task_14svgcu" targetRef="EndEvent_0q4qzl9" /> <bpmn:sequenceFlow id="SequenceFlow_02vev7n" sourceRef="TaskEnumLookup" targetRef="EndEvent_0q4qzl9" />
<bpmn:userTask id="Task_14svgcu" name="Enum Lookup Form" camunda:formKey="EnumForm"> <bpmn:userTask id="TaskEnumLookup" name="Enum Lookup Form" camunda:formKey="EnumForm">
<bpmn:extensionElements> <bpmn:extensionElements>
<camunda:formData> <camunda:formData>
<camunda:formField id="AllTheNames" label="Select a value" type="enum"> <camunda:formField id="AllTheNames" label="Select a value" type="enum">
@ -41,7 +41,7 @@
<bpmndi:BPMNShape id="EndEvent_0q4qzl9_di" bpmnElement="EndEvent_0q4qzl9"> <bpmndi:BPMNShape id="EndEvent_0q4qzl9_di" bpmnElement="EndEvent_0q4qzl9">
<dc:Bounds x="432" y="99" width="36" height="36" /> <dc:Bounds x="432" y="99" width="36" height="36" />
</bpmndi:BPMNShape> </bpmndi:BPMNShape>
<bpmndi:BPMNShape id="UserTask_18ly1yq_di" bpmnElement="Task_14svgcu"> <bpmndi:BPMNShape id="UserTask_18ly1yq_di" bpmnElement="TaskEnumLookup">
<dc:Bounds x="270" y="77" width="100" height="80" /> <dc:Bounds x="270" y="77" width="100" height="80" />
</bpmndi:BPMNShape> </bpmndi:BPMNShape>
</bpmndi:BPMNPlane> </bpmndi:BPMNPlane>

View File

@ -4,12 +4,12 @@
<bpmn:startEvent id="StartEvent_1"> <bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>SequenceFlow_0lvudp8</bpmn:outgoing> <bpmn:outgoing>SequenceFlow_0lvudp8</bpmn:outgoing>
</bpmn:startEvent> </bpmn:startEvent>
<bpmn:sequenceFlow id="SequenceFlow_0lvudp8" sourceRef="StartEvent_1" targetRef="Task_14svgcu" /> <bpmn:sequenceFlow id="SequenceFlow_0lvudp8" sourceRef="StartEvent_1" targetRef="Task_Enum_Lookup" />
<bpmn:endEvent id="EndEvent_0q4qzl9"> <bpmn:endEvent id="EndEvent_0q4qzl9">
<bpmn:incoming>SequenceFlow_02vev7n</bpmn:incoming> <bpmn:incoming>SequenceFlow_02vev7n</bpmn:incoming>
</bpmn:endEvent> </bpmn:endEvent>
<bpmn:sequenceFlow id="SequenceFlow_02vev7n" sourceRef="Task_14svgcu" targetRef="EndEvent_0q4qzl9" /> <bpmn:sequenceFlow id="SequenceFlow_02vev7n" sourceRef="Task_Enum_Lookup" targetRef="EndEvent_0q4qzl9" />
<bpmn:userTask id="Task_14svgcu" name="Enum Lookup Form" camunda:formKey="EnumForm"> <bpmn:userTask id="Task_Enum_Lookup" name="Enum Lookup Form" camunda:formKey="EnumForm">
<bpmn:extensionElements> <bpmn:extensionElements>
<camunda:formData> <camunda:formData>
<camunda:formField id="sponsor" label="Select a value" type="autocomplete"> <camunda:formField id="sponsor" label="Select a value" type="autocomplete">
@ -41,7 +41,7 @@
<bpmndi:BPMNShape id="EndEvent_0q4qzl9_di" bpmnElement="EndEvent_0q4qzl9"> <bpmndi:BPMNShape id="EndEvent_0q4qzl9_di" bpmnElement="EndEvent_0q4qzl9">
<dc:Bounds x="432" y="99" width="36" height="36" /> <dc:Bounds x="432" y="99" width="36" height="36" />
</bpmndi:BPMNShape> </bpmndi:BPMNShape>
<bpmndi:BPMNShape id="UserTask_18ly1yq_di" bpmnElement="Task_14svgcu"> <bpmndi:BPMNShape id="UserTask_18ly1yq_di" bpmnElement="Task_Enum_Lookup">
<dc:Bounds x="270" y="77" width="100" height="80" /> <dc:Bounds x="270" y="77" width="100" height="80" />
</bpmndi:BPMNShape> </bpmndi:BPMNShape>
</bpmndi:BPMNPlane> </bpmndi:BPMNPlane>

View File

@ -18,14 +18,14 @@ class TestLookupService(BaseTest):
file_model = session.query(FileModel).filter(FileModel.name == "customer_list.xls").first() file_model = session.query(FileModel).filter(FileModel.name == "customer_list.xls").first()
file_data_model = session.query(FileDataModel).filter(FileDataModel.file_model == file_model).first() file_data_model = session.query(FileDataModel).filter(FileDataModel.file_model == file_model).first()
with self.assertRaises(ApiError): with self.assertRaises(ApiError):
LookupService.lookup(workflow, "not_the_right_field", "sam", limit=10) LookupService.lookup(workflow, "Task_Enum_Lookup", "not_the_right_field", "sam", limit=10)
def test_lookup_table_is_not_created_more_than_once(self): def test_lookup_table_is_not_created_more_than_once(self):
spec = BaseTest.load_test_spec('enum_options_with_search') spec = BaseTest.load_test_spec('enum_options_with_search')
workflow = self.create_workflow('enum_options_with_search') workflow = self.create_workflow('enum_options_with_search')
LookupService.lookup(workflow, "sponsor", "sam", limit=10) LookupService.lookup(workflow, "Task_Enum_Lookup", "sponsor", "sam", limit=10)
LookupService.lookup(workflow, "sponsor", "something", limit=10) LookupService.lookup(workflow, "Task_Enum_Lookup", "sponsor", "something", limit=10)
LookupService.lookup(workflow, "sponsor", "blah", limit=10) LookupService.lookup(workflow, "Task_Enum_Lookup", "sponsor", "blah", limit=10)
lookup_records = session.query(LookupFileModel).all() lookup_records = session.query(LookupFileModel).all()
self.assertIsNotNone(lookup_records) self.assertIsNotNone(lookup_records)
self.assertEqual(1, len(lookup_records)) self.assertEqual(1, len(lookup_records))
@ -37,7 +37,7 @@ class TestLookupService(BaseTest):
spec = BaseTest.load_test_spec('enum_options_with_search') spec = BaseTest.load_test_spec('enum_options_with_search')
workflow = self.create_workflow('enum_options_with_search') workflow = self.create_workflow('enum_options_with_search')
file_model = session.query(FileModel).filter(FileModel.name == "sponsors.xls").first() file_model = session.query(FileModel).filter(FileModel.name == "sponsors.xls").first()
LookupService.lookup(workflow, "sponsor", "sam", limit=10) LookupService.lookup(workflow, "Task_Enum_Lookup", "sponsor", "sam", limit=10)
lookup_records = session.query(LookupFileModel).all() lookup_records = session.query(LookupFileModel).all()
self.assertIsNotNone(lookup_records) self.assertIsNotNone(lookup_records)
self.assertEqual(1, len(lookup_records)) self.assertEqual(1, len(lookup_records))
@ -58,7 +58,7 @@ class TestLookupService(BaseTest):
processor.reset(workflow) processor.reset(workflow)
workflow = processor.workflow_model workflow = processor.workflow_model
LookupService.lookup(workflow, "sponsor", "sam", limit=10) LookupService.lookup(workflow, "Task_Enum_Lookup", "sponsor", "sam", limit=10)
lookup_records = session.query(LookupFileModel).all() lookup_records = session.query(LookupFileModel).all()
lookup_record = lookup_records[0] lookup_record = lookup_records[0]
lookup_data = session.query(LookupDataModel).filter(LookupDataModel.lookup_file_model == lookup_record).all() lookup_data = session.query(LookupDataModel).filter(LookupDataModel.lookup_file_model == lookup_record).all()
@ -69,71 +69,103 @@ class TestLookupService(BaseTest):
workflow = self.create_workflow('enum_options_from_file') workflow = self.create_workflow('enum_options_from_file')
processor = WorkflowProcessor(workflow) processor = WorkflowProcessor(workflow)
processor.do_engine_steps() processor.do_engine_steps()
results = LookupService.lookup(workflow, "AllTheNames", "", value="1000", limit=10) results = LookupService.lookup(workflow, "TaskEnumLookup", "AllTheNames", "", value="1000", limit=10)
self.assertEqual(1, len(results), "It is possible to find an item based on the id, rather than as a search") self.assertEqual(1, len(results), "It is possible to find an item based on the id, rather than as a search")
self.assertIsNotNone(results[0].data) self.assertIsNotNone(results[0].data)
self.assertIsInstance(results[0].data, dict) self.assertIsInstance(results[0].data, dict)
def test_lookup_with_two_spreadsheets_with_the_same_field_name_in_different_forms(self):
spec = BaseTest.load_test_spec('enum_options_competing_files')
workflow = self.create_workflow('enum_options_competing_files')
processor = WorkflowProcessor(workflow)
processor.do_engine_steps()
task = processor.get_ready_user_tasks()[0]
task.data = {"type": "animal"}
processor.complete_task(task)
processor.do_engine_steps()
task = processor.get_ready_user_tasks()[0]
results = LookupService.lookup(workflow, task.task_spec.name, "selectedItem", "", value="pigs", limit=10)
self.assertEqual(1, len(results), "It is possible to find an item based on the id, rather than as a search")
self.assertIsNotNone(results[0].data)
results = LookupService.lookup(workflow, task.task_spec.name, "selectedItem", "", value="apples", limit=10)
self.assertEqual(0, len(results), "We shouldn't find our fruits mixed in with our animals.")
processor.reset(workflow, clear_data=True)
processor.do_engine_steps()
task = processor.get_ready_user_tasks()[0]
task.data = {"type": "fruit"}
processor.complete_task(task)
processor.do_engine_steps()
task = processor.get_ready_user_tasks()[0]
results = LookupService.lookup(workflow, task.task_spec.name, "selectedItem", "", value="apples", limit=10)
self.assertEqual(1, len(results), "It is possible to find an item based on the id, rather than as a search")
self.assertIsNotNone(results[0].data)
results = LookupService.lookup(workflow, task.task_spec.name, "selectedItem", "", value="pigs", limit=10)
self.assertEqual(0, len(results), "We shouldn't find our animals mixed in with our fruits.")
def test_some_full_text_queries(self): def test_some_full_text_queries(self):
spec = BaseTest.load_test_spec('enum_options_from_file') spec = BaseTest.load_test_spec('enum_options_from_file')
workflow = self.create_workflow('enum_options_from_file') workflow = self.create_workflow('enum_options_from_file')
processor = WorkflowProcessor(workflow) processor = WorkflowProcessor(workflow)
processor.do_engine_steps() processor.do_engine_steps()
results = LookupService.lookup(workflow, "AllTheNames", "", limit=10) results = LookupService.lookup(workflow, "TaskEnumLookup", "AllTheNames", "", limit=10)
self.assertEqual(10, len(results), "Blank queries return everything, to the limit") self.assertEqual(10, len(results), "Blank queries return everything, to the limit")
results = LookupService.lookup(workflow, "AllTheNames", "medicines", limit=10) results = LookupService.lookup(workflow, "TaskEnumLookup", "AllTheNames", "medicines", limit=10)
self.assertEqual(1, len(results), "words in the middle of label are detected.") self.assertEqual(1, len(results), "words in the middle of label are detected.")
self.assertEqual("The Medicines Company", results[0].label) self.assertEqual("The Medicines Company", results[0].label)
results = LookupService.lookup(workflow, "AllTheNames", "UVA", limit=10) results = LookupService.lookup(workflow,"TaskEnumLookup", "AllTheNames", "UVA", limit=10)
self.assertEqual(1, len(results), "Beginning of label is found.") self.assertEqual(1, len(results), "Beginning of label is found.")
self.assertEqual("UVA - INTERNAL - GM USE ONLY", results[0].label) self.assertEqual("UVA - INTERNAL - GM USE ONLY", results[0].label)
results = LookupService.lookup(workflow, "AllTheNames", "uva", limit=10) results = LookupService.lookup(workflow, "TaskEnumLookup","AllTheNames", "uva", limit=10)
self.assertEqual(1, len(results), "case does not matter.") self.assertEqual(1, len(results), "case does not matter.")
self.assertEqual("UVA - INTERNAL - GM USE ONLY", results[0].label) self.assertEqual("UVA - INTERNAL - GM USE ONLY", results[0].label)
results = LookupService.lookup(workflow, "AllTheNames", "medici", limit=10) results = LookupService.lookup(workflow, "TaskEnumLookup", "AllTheNames", "medici", limit=10)
self.assertEqual(1, len(results), "partial words are picked up.") self.assertEqual(1, len(results), "partial words are picked up.")
self.assertEqual("The Medicines Company", results[0].label) self.assertEqual("The Medicines Company", results[0].label)
results = LookupService.lookup(workflow, "AllTheNames", "Genetics Savings", limit=10) results = LookupService.lookup(workflow, "TaskEnumLookup", "AllTheNames", "Genetics Savings", limit=10)
self.assertEqual(1, len(results), "multiple terms are picked up..") self.assertEqual(1, len(results), "multiple terms are picked up..")
self.assertEqual("Genetics Savings & Clone, Inc.", results[0].label) self.assertEqual("Genetics Savings & Clone, Inc.", results[0].label)
results = LookupService.lookup(workflow, "AllTheNames", "Genetics Sav", limit=10) results = LookupService.lookup(workflow, "TaskEnumLookup", "AllTheNames", "Genetics Sav", limit=10)
self.assertEqual(1, len(results), "prefix queries still work with partial terms") self.assertEqual(1, len(results), "prefix queries still work with partial terms")
self.assertEqual("Genetics Savings & Clone, Inc.", results[0].label) self.assertEqual("Genetics Savings & Clone, Inc.", results[0].label)
results = LookupService.lookup(workflow, "AllTheNames", "Gen Sav", limit=10) results = LookupService.lookup(workflow, "TaskEnumLookup", "AllTheNames", "Gen Sav", limit=10)
self.assertEqual(1, len(results), "prefix queries still work with ALL the partial terms") self.assertEqual(1, len(results), "prefix queries still work with ALL the partial terms")
self.assertEqual("Genetics Savings & Clone, Inc.", results[0].label) self.assertEqual("Genetics Savings & Clone, Inc.", results[0].label)
results = LookupService.lookup(workflow, "AllTheNames", "Inc", limit=10) results = LookupService.lookup(workflow, "TaskEnumLookup", "AllTheNames", "Inc", limit=10)
self.assertEqual(7, len(results), "short terms get multiple correct results.") self.assertEqual(7, len(results), "short terms get multiple correct results.")
self.assertEqual("Genetics Savings & Clone, Inc.", results[0].label) self.assertEqual("Genetics Savings & Clone, Inc.", results[0].label)
results = LookupService.lookup(workflow, "AllTheNames", "reaction design", limit=10) results = LookupService.lookup(workflow, "TaskEnumLookup", "AllTheNames", "reaction design", limit=10)
self.assertEqual(3, len(results), "all results come back for two terms.") self.assertEqual(3, len(results), "all results come back for two terms.")
self.assertEqual("Reaction Design", results[0].label, "Exact matches come first.") self.assertEqual("Reaction Design", results[0].label, "Exact matches come first.")
results = LookupService.lookup(workflow, "AllTheNames", "1 Something", limit=10) results = LookupService.lookup(workflow, "TaskEnumLookup", "AllTheNames", "1 Something", limit=10)
self.assertEqual("1 Something", results[0].label, "Exact matches are preferred") self.assertEqual("1 Something", results[0].label, "Exact matches are preferred")
results = LookupService.lookup(workflow, "AllTheNames", "1 (!-Something", limit=10) results = LookupService.lookup(workflow, "TaskEnumLookup", "AllTheNames", "1 (!-Something", limit=10)
self.assertEqual("1 Something", results[0].label, "special characters don't flake out") self.assertEqual("1 Something", results[0].label, "special characters don't flake out")
results = LookupService.lookup(workflow, "AllTheNames", "1 Something", limit=10) results = LookupService.lookup(workflow, "TaskEnumLookup", "AllTheNames", "1 Something", limit=10)
self.assertEqual("1 Something", results[0].label, "double spaces should not be an issue.") self.assertEqual("1 Something", results[0].label, "double spaces should not be an issue.")
results = LookupService.lookup(workflow, "AllTheNames", "in", limit=10) results = LookupService.lookup(workflow, "TaskEnumLookup", "AllTheNames", "in", limit=10)
self.assertEqual(10, len(results), "stop words are not removed.") self.assertEqual(10, len(results), "stop words are not removed.")
self.assertEqual("Genetics Savings & Clone, Inc.", results[0].label) self.assertEqual("Genetics Savings & Clone, Inc.", results[0].label)
results = LookupService.lookup(workflow, "AllTheNames", "other", limit=10) results = LookupService.lookup(workflow, "TaskEnumLookup", "AllTheNames", "other", limit=10)
self.assertEqual("Other", results[0].label, "Can't find the word 'other', which is an english stop word") self.assertEqual("Other", results[0].label, "Can't find the word 'other', which is an english stop word")

View File

@ -266,8 +266,8 @@ class TestTasksApi(BaseTest):
workflow = self.get_workflow_api(workflow) workflow = self.get_workflow_api(workflow)
task = workflow.next_task task = workflow.next_task
field_id = task.form['fields'][0]['id'] field_id = task.form['fields'][0]['id']
rv = self.app.get('/v1.0/workflow/%i/lookup/%s?query=%s&limit=5' % rv = self.app.get('/v1.0/workflow/%i/lookup/%s/%s?query=%s&limit=5' %
(workflow.id, field_id, 'c'), # All records with a word that starts with 'c' (workflow.id, task.name, field_id, 'c'), # All records with a word that starts with 'c'
headers=self.logged_in_headers(), headers=self.logged_in_headers(),
content_type="application/json") content_type="application/json")
self.assert_success(rv) self.assert_success(rv)
@ -281,8 +281,8 @@ class TestTasksApi(BaseTest):
workflow = self.get_workflow_api(workflow) workflow = self.get_workflow_api(workflow)
task = workflow.next_task task = workflow.next_task
field_id = task.form['fields'][0]['id'] field_id = task.form['fields'][0]['id']
rv = self.app.get('/v1.0/workflow/%i/lookup/%s?query=%s&limit=5' % rv = self.app.get('/v1.0/workflow/%i/lookup/%s/%s?query=%s&limit=5' %
(workflow.id, field_id, 'c'), # All records with a word that starts with 'c' (workflow.id, task.name, field_id, 'c'), # All records with a word that starts with 'c'
headers=self.logged_in_headers(), headers=self.logged_in_headers(),
content_type="application/json") content_type="application/json")
self.assert_success(rv) self.assert_success(rv)
@ -290,8 +290,8 @@ class TestTasksApi(BaseTest):
self.assertEqual(5, len(results)) self.assertEqual(5, len(results))
self.assert_options_populated(results, ['CUSTOMER_NUMBER', 'CUSTOMER_NAME', 'CUSTOMER_CLASS_MEANING']) self.assert_options_populated(results, ['CUSTOMER_NUMBER', 'CUSTOMER_NAME', 'CUSTOMER_CLASS_MEANING'])
rv = self.app.get('/v1.0/workflow/%i/lookup/%s?value=%s' % rv = self.app.get('/v1.0/workflow/%i/lookup/%s/%s?value=%s' %
(workflow.id, field_id, results[0]['value']), # All records with a word that starts with 'c' (workflow.id, task.name, field_id, results[0]['value']), # All records with a word that starts with 'c'
headers=self.logged_in_headers(), headers=self.logged_in_headers(),
content_type="application/json") content_type="application/json")
results = json.loads(rv.get_data(as_text=True)) results = json.loads(rv.get_data(as_text=True))
@ -311,8 +311,8 @@ class TestTasksApi(BaseTest):
task = workflow.next_task task = workflow.next_task
field_id = task.form['fields'][0]['id'] field_id = task.form['fields'][0]['id']
option_id = task.form['fields'][0]['options'][0]['id'] option_id = task.form['fields'][0]['options'][0]['id']
rv = self.app.get('/v1.0/workflow/%i/lookup/%s?value=%s' % rv = self.app.get('/v1.0/workflow/%i/lookup/%s/%s?value=%s' %
(workflow.id, field_id, option_id), # All records with a word that starts with 'c' (workflow.id, task.name, field_id, option_id), # All records with a word that starts with 'c'
headers=self.logged_in_headers(), headers=self.logged_in_headers(),
content_type="application/json") content_type="application/json")
self.assert_success(rv) self.assert_success(rv)
@ -352,8 +352,8 @@ class TestTasksApi(BaseTest):
task = workflow.next_task task = workflow.next_task
field_id = task.form['fields'][0]['id'] field_id = task.form['fields'][0]['id']
# lb3dp is a user record in the mock ldap responses for tests. # lb3dp is a user record in the mock ldap responses for tests.
rv = self.app.get('/v1.0/workflow/%s/lookup/%s?query=%s&limit=5' % rv = self.app.get('/v1.0/workflow/%s/lookup/%s/%s?query=%s&limit=5' %
(workflow.id, field_id, 'lb3dp'), (workflow.id, task.name, field_id, 'lb3dp'),
headers=self.logged_in_headers(), headers=self.logged_in_headers(),
content_type="application/json") content_type="application/json")
self.assert_success(rv) self.assert_success(rv)