From bb6ab4b8e8efa2ed3ef25d34f0904617a00b2f55 Mon Sep 17 00:00:00 2001 From: Dan Funk Date: Thu, 19 Dec 2019 11:58:51 -0500 Subject: [PATCH] Tasks can have forms and they are returned correctly. --- crc/__init__.py | 2 ++ crc/api.py | 17 ++++++++++-- crc/api.yml | 57 +++++++++++++++++++++++++++------------ crc/models.py | 52 ++++++++++++++++++++++++++++++++++- crc/workflow_processor.py | 5 +++- tests/test_api.py | 20 +++++++++++++- 6 files changed, 131 insertions(+), 22 deletions(-) diff --git a/crc/__init__.py b/crc/__init__.py index f9b4289e..610db60a 100644 --- a/crc/__init__.py +++ b/crc/__init__.py @@ -2,6 +2,7 @@ import logging import os import connexion +from flask_cors import CORS from flask_marshmallow import Marshmallow from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate @@ -26,6 +27,7 @@ ma = Marshmallow(app) from crc import models connexion_app.add_api('api.yml') +cors = CORS(connexion_app.app) @app.cli.command() def load_example_data(): diff --git a/crc/api.py b/crc/api.py index 72dbfcab..6d3c3ba9 100644 --- a/crc/api.py +++ b/crc/api.py @@ -3,7 +3,7 @@ from flask_marshmallow import Schema from crc import db, ma from crc.models import WorkflowModel, WorkflowSchema, StudySchema, StudyModel, WorkflowSpecSchema, WorkflowSpecModel, \ - WorkflowStatus + WorkflowStatus, Task, TaskSchema from crc.workflow_processor import WorkflowProcessor @@ -61,11 +61,24 @@ def add_workflow_to_study(study_id, body): workflow_spec_id=workflow_spec_model.id) db.session.add(workflow) db.session.commit() + return get_study_workflows(study_id) + def get_workflow(workflow_id): - return db.session.query(WorkflowModel).filter_by(id=workflow_id).first() + schema = WorkflowSchema() + workflow = db.session.query(WorkflowModel).filter_by(id=workflow_id).first() + return schema.dump(workflow) +def get_tasks(workflow_id): + workflow = db.session.query(WorkflowModel).filter_by(id=workflow_id).first() + processor = WorkflowProcessor(workflow.workflow_spec_id, workflow.bpmn_workflow_json) + spiff_tasks = processor.get_ready_user_tasks() + tasks = [] + for st in spiff_tasks: + tasks.append(Task.from_spiff(st)) + return TaskSchema(many=True).dump(tasks) + def get_task(workflow_id, task_id): workflow = db.session.query(WorkflowModel).filter_by(id=workflow_id).first() return workflow.bpmn_workflow().get_task(task_id) diff --git a/crc/api.yml b/crc/api.yml index 19f06f67..4d2ce056 100644 --- a/crc/api.yml +++ b/crc/api.yml @@ -192,7 +192,35 @@ paths: application/json: schema: $ref: "#/components/schemas/Error" - + /workflow/{workflow_id}/tasks: + get: + operationId: crc.api.get_tasks + summary: Return a list of all tasks for this workflow + tags: + - Workflows and Tasks + parameters: + - name: workflow_id + in: path + required: true + description: The id of the workflow + schema: + type: integer + format: int32 + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Task" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" # /v1.0/workflow/0/task/0 /workflow/{workflow_id}/task/{task_id}: get: @@ -265,19 +293,10 @@ paths: components: schemas: Study: - required: - - id - - title - - last_updated - - pb_status - - pi - - sponsor - - ind_number properties: id: - type: string - format: string - example: AB1234 + type: integer + example: 1234 title: type: string example: The impact of fried pickles on beer consumption in bipedal software developers. @@ -285,11 +304,11 @@ components: type: string format: date_time example: "2019-12-25T09:12:33.001Z" - pb_status: + protocol_builder_status: type: string - enum: [started, done] + enum: [out_of_date, in_process, complete, updating] example: done - pi: + primary_investigator_id: type: string example: dhf8r sponsor: @@ -356,16 +375,20 @@ components: type: string type: type: string + status: + type: string form: $ref: "#/components/schemas/Form" example: { id: study_identification, name: "Study Identification", - type: form, + state: "ready", + type: "form", form: { + key: "oncology_form", field: { - id: adult_oncology, + id: "is_adult_patient", type: enum, label: "Is this study treating adult oncology patients?", options: ["yes", "no"], diff --git a/crc/models.py b/crc/models.py index 8626eca1..a68dd6f3 100644 --- a/crc/models.py +++ b/crc/models.py @@ -1,10 +1,12 @@ import enum +from SpiffWorkflow import Task from flask_marshmallow.sqla import ModelSchema +from marshmallow import post_load, fields, Schema from marshmallow_enum import EnumField from sqlalchemy import func -from crc import db +from crc import db, ma class ProtocolBuilderStatus(enum.Enum): @@ -56,10 +58,58 @@ class WorkflowModel(db.Model): study_id = db.Column(db.Integer, db.ForeignKey('study.id')) workflow_spec_id = db.Column(db.Integer, db.ForeignKey('workflow_spec.id')) + class WorkflowSchema(ModelSchema): class Meta: model = WorkflowModel status = EnumField(WorkflowStatus) +class Task: + def __init__(self, id, name, type, state, form): + self.id = id + self.name = name + self.type = type + self.state = state + self.form = form + + @classmethod + def from_spiff(cls, spiff_task): + instance = cls(spiff_task.id, + spiff_task.task_spec.name, + spiff_task.get_state_name(), + "task", + {}) + if hasattr(spiff_task.task_spec, "form"): + instance.type = "form" + instance.form = spiff_task.task_spec.form + return instance + + +class OptionSchema(ma.Schema): + class Meta: + fields = ["id", "name"] + + +class FieldSchema(ma.Schema): + class Meta: + fields = ["id", "type", "label", "defaultValue", "options"] + options = fields.List(fields.Nested(OptionSchema)) + + +class FormSchema(ma.Schema): + class Meta: + fields = ["key", "fields"] + fields = fields.List(fields.Nested(FieldSchema)) + + +class TaskSchema(ma.Schema): + class Meta: + fields = ["id", "name", "type", "state", "form"] + + form = fields.Nested(FormSchema) + @post_load + def make_task(self, data, **kwargs): + return Task(**data) + diff --git a/crc/workflow_processor.py b/crc/workflow_processor.py index 99a7ab6e..71ae8d99 100644 --- a/crc/workflow_processor.py +++ b/crc/workflow_processor.py @@ -67,7 +67,7 @@ class WorkflowProcessor: self.bpmn_workflow.do_engine_steps() def serialize(self): - return self.bpmn_workflow.serialize(JSONSerializer()) + return self._serializer.serialize_workflow(self.bpmn_workflow) def next_user_tasks(self): return self.bpmn_workflow.get_ready_user_tasks() @@ -77,3 +77,6 @@ class WorkflowProcessor: def get_data(self): return self.bpmn_workflow.data + + def get_ready_user_tasks(self): + return self.bpmn_workflow.get_ready_user_tasks() diff --git a/tests/test_api.py b/tests/test_api.py index a906536e..5c736864 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -4,7 +4,9 @@ import unittest from sqlalchemy import func from crc import db -from crc.models import StudyModel, StudySchema, WorkflowSpecModel, WorkflowSpecSchema, WorkflowModel, WorkflowStatus +from crc.models import StudyModel, StudySchema, WorkflowSpecModel, WorkflowSpecSchema, WorkflowModel, WorkflowStatus, \ + WorkflowSchema, TaskSchema +from crc.workflow_processor import WorkflowProcessor from tests.base_test import BaseTest @@ -62,3 +64,19 @@ class TestStudy(BaseTest, unittest.TestCase): self.assertIsNotNone(workflow.bpmn_workflow_json) self.assertEqual(spec.id, workflow.workflow_spec_id) + json_data = json.loads(rv.get_data(as_text=True)) + workflows = WorkflowSchema(many=True).load(json_data, session=db.session) + self.assertEqual(workflows[0].id, workflow.id) + + def test_get_current_user_tasks(self): + self.load_example_data() + study = db.session.query(StudyModel).first() + spec = db.session.query(WorkflowSpecModel).filter_by(id='random_fact').first() + self.app.post('/v1.0/study/%i/workflows' % study.id, content_type="application/json", + data=json.dumps(WorkflowSpecSchema().dump(spec))) + rv = self.app.get('/v1.0/workflow/%i/tasks' % study.id, content_type="application/json") + self.assert_success(rv) + json_data = json.loads(rv.get_data(as_text=True)) + tasks = TaskSchema(many=True).load(json_data) + self.assertEqual("Task_User_Select_Type", tasks[0].name) + self.assertEqual(3, len(tasks[0].form["fields"][0]["options"])) \ No newline at end of file