From 5936b2e8eff378b039d1bd2d90e95e10e7164711 Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Mon, 26 Apr 2021 08:27:24 -0400 Subject: [PATCH 01/20] Created 2 api endpoints; one to get a workflow from a workflow spec, and one to list all standalone workflow specs. Also added `standalone` argument to workflow_spec schema properties --- crc/api.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/crc/api.yml b/crc/api.yml index 6dc1afa6..bf1a573d 100644 --- a/crc/api.yml +++ b/crc/api.yml @@ -411,6 +411,18 @@ paths: application/json: schema: $ref: "#/components/schemas/WorkflowSpec" + post: + operationId: crc.api.workflow.get_workflow_from_spec + summary: Creates a workflow from a workflow spec and returns the workflow + tags: + - Workflow Specifications + responses: + '200': + description: Workflow generated successfully + content: + application/json: + schema: + $ref: "#/components/schemas/Workflow" put: operationId: crc.api.workflow.update_workflow_specification security: @@ -440,6 +452,21 @@ paths: responses: '204': description: The workflow specification has been removed. + /workflow-specification/standalone: + get: + operationId: crc.api.workflow.standalone_workflow_specs + summary: Provides a list of workflow specifications that can be run outside a study. + tags: + - Workflow Specifications + responses: + '200': + description: A list of workflow specifications + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/WorkflowSpec" /workflow-specification/{spec_id}/validate: parameters: - name: spec_id @@ -1472,6 +1499,9 @@ components: category_id: type: integer nullable: true + standalone: + type: boolean + example: false workflow_spec_category: $ref: "#/components/schemas/WorkflowSpecCategory" is_status: From fa818bd751d029188e8777c2e56b7565b3797c88 Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Mon, 26 Apr 2021 08:31:12 -0400 Subject: [PATCH 02/20] Code for the 2 new api endpoints Also modified `update_task` so that it no longer requires a study_id --- crc/api/workflow.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/crc/api/workflow.py b/crc/api/workflow.py index 46398904..4bdc9185 100644 --- a/crc/api/workflow.py +++ b/crc/api/workflow.py @@ -101,6 +101,24 @@ def delete_workflow_specification(spec_id): session.commit() +def get_workflow_from_spec(spec_id): + workflow_model = WorkflowService.get_workflow_from_spec(spec_id, g.user) + processor = WorkflowProcessor(workflow_model) + + processor.do_engine_steps() + processor.save() + WorkflowService.update_task_assignments(processor) + + workflow_api_model = WorkflowService.processor_to_workflow_api(processor) + return WorkflowApiSchema().dump(workflow_api_model) + + +def standalone_workflow_specs(): + schema = WorkflowSpecModelSchema(many=True) + specs = WorkflowService.get_standalone_workflow_specs() + return schema.dump(specs) + + def get_workflow(workflow_id, do_engine_steps=True): """Retrieve workflow based on workflow_id, and return it in the last saved State. If do_engine_steps is False, return the workflow without running any engine tasks or logging any events. """ @@ -184,9 +202,6 @@ def update_task(workflow_id, task_id, body, terminate_loop=None, update_all=Fals if workflow_model is None: raise ApiError("invalid_workflow_id", "The given workflow id is not valid.", status_code=404) - elif workflow_model.study is None: - raise ApiError("invalid_study", "There is no study associated with the given workflow.", status_code=404) - processor = WorkflowProcessor(workflow_model) task_id = uuid.UUID(task_id) spiff_task = processor.bpmn_workflow.get_task(task_id) From c0655c9d03aeb2c9e659ab554a5376322ab85b33 Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Mon, 26 Apr 2021 08:33:55 -0400 Subject: [PATCH 03/20] Added `standalone` column to workflow_spec Added `user_id` column to workflow --- crc/models/workflow.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crc/models/workflow.py b/crc/models/workflow.py index 0da32aec..10805a9a 100644 --- a/crc/models/workflow.py +++ b/crc/models/workflow.py @@ -33,6 +33,7 @@ class WorkflowSpecModel(db.Model): category_id = db.Column(db.Integer, db.ForeignKey('workflow_spec_category.id'), nullable=True) category = db.relationship("WorkflowSpecCategoryModel") is_master_spec = db.Column(db.Boolean, default=False) + standalone = db.Column(db.Boolean, default=False) class WorkflowSpecModelSchema(SQLAlchemyAutoSchema): @@ -88,6 +89,7 @@ class WorkflowModel(db.Model): total_tasks = db.Column(db.Integer, default=0) completed_tasks = db.Column(db.Integer, default=0) last_updated = db.Column(db.DateTime) + user_id = db.Column(db.String, default=None) # Order By is important or generating hashes on reviews. dependencies = db.relationship(WorkflowSpecDependencyFile, cascade="all, delete, delete-orphan", order_by="WorkflowSpecDependencyFile.file_data_id") From 22432aaf2c611c0d1ed2d3f63d7b1e9e2ee7e281 Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Mon, 26 Apr 2021 08:36:09 -0400 Subject: [PATCH 04/20] study_id is no longer required for task events --- crc/models/task_event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crc/models/task_event.py b/crc/models/task_event.py index 21a239ee..70bb1be5 100644 --- a/crc/models/task_event.py +++ b/crc/models/task_event.py @@ -10,7 +10,7 @@ from crc.services.ldap_service import LdapService class TaskEventModel(db.Model): __tablename__ = 'task_event' id = db.Column(db.Integer, primary_key=True) - study_id = db.Column(db.Integer, db.ForeignKey('study.id'), nullable=False) + study_id = db.Column(db.Integer, db.ForeignKey('study.id')) user_uid = db.Column(db.String, nullable=False) # In some cases the unique user id may not exist in the db yet. workflow_id = db.Column(db.Integer, db.ForeignKey('workflow.id'), nullable=False) workflow_spec_id = db.Column(db.String, db.ForeignKey('workflow_spec.id')) From 1f50bdcb8107cc9b9ce3c57ad7531aa9c07b4e26 Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Mon, 26 Apr 2021 08:36:56 -0400 Subject: [PATCH 05/20] Migration script for database changes --- migrations/versions/8b976945a54e_.py | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 migrations/versions/8b976945a54e_.py diff --git a/migrations/versions/8b976945a54e_.py b/migrations/versions/8b976945a54e_.py new file mode 100644 index 00000000..7805e31b --- /dev/null +++ b/migrations/versions/8b976945a54e_.py @@ -0,0 +1,30 @@ +"""empty message + +Revision ID: 8b976945a54e +Revises: c872232ebdcb +Create Date: 2021-04-18 11:42:41.894378 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '8b976945a54e' +down_revision = 'c872232ebdcb' +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column('workflow', sa.Column('user_id', sa.String(), nullable=True)) + op.add_column('workflow_spec', sa.Column('standalone', sa.String(), default=False)) + op.execute("UPDATE workflow_spec SET standalone=False WHERE standalone is null;") + op.execute("ALTER TABLE task_event ALTER COLUMN study_id DROP NOT NULL") + + +def downgrade(): + op.execute("UPDATE workflow SET user_id=NULL WHERE user_id is not NULL") + op.drop_column('workflow', 'user_id') + op.drop_column('workflow_spec', 'standalone') + op.execute("ALTER TABLE task_event ALTER COLUMN study_id SET NOT NULL ") From b6f500168764d686caecdb90a655742bb1d2bfb8 Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Mon, 26 Apr 2021 08:43:14 -0400 Subject: [PATCH 06/20] Added methods for the 2 new api endpoints --- crc/services/workflow_service.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/crc/services/workflow_service.py b/crc/services/workflow_service.py index 265b61e7..a08087a1 100644 --- a/crc/services/workflow_service.py +++ b/crc/services/workflow_service.py @@ -783,3 +783,19 @@ class WorkflowService(object): for workflow in workflows: if workflow.status == WorkflowStatus.user_input_required or workflow.status == WorkflowStatus.waiting: WorkflowProcessor.reset(workflow, clear_data=False) + + @staticmethod + def get_workflow_from_spec(workflow_spec_id, user): + workflow_model = WorkflowModel(status=WorkflowStatus.not_started, + study=None, + user_id=user.uid, + workflow_spec_id=workflow_spec_id, + last_updated=datetime.now()) + db.session.add(workflow_model) + db.session.commit() + return workflow_model + + @staticmethod + def get_standalone_workflow_specs(): + specs = db.session.query(WorkflowSpecModel).filter_by(standalone=True).all() + return specs From f17a9dc0f44e4f9db35bde8847d30860bfb1ec2f Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Mon, 26 Apr 2021 08:45:08 -0400 Subject: [PATCH 07/20] Modified `get_users_assigned_to_task`. If we are running a standalone workflow, only return the current user. --- crc/services/workflow_service.py | 51 +++++++++++++++++++------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/crc/services/workflow_service.py b/crc/services/workflow_service.py index a08087a1..9edaf856 100644 --- a/crc/services/workflow_service.py +++ b/crc/services/workflow_service.py @@ -667,30 +667,39 @@ class WorkflowService(object): @staticmethod def get_users_assigned_to_task(processor, spiff_task) -> List[str]: - if not hasattr(spiff_task.task_spec, 'lane') or spiff_task.task_spec.lane is None: - associated = StudyService.get_study_associates(processor.workflow_model.study.id) - return [user['uid'] for user in associated if user['access']] - if spiff_task.task_spec.lane not in spiff_task.data: - return [] # No users are assignable to the task at this moment - lane_users = spiff_task.data[spiff_task.task_spec.lane] - if not isinstance(lane_users, list): - lane_users = [lane_users] + if processor.workflow_model.study_id is None and processor.workflow_model.user_id is None: + raise ApiError.from_task(code='invalid_workflow', + message='A workflow must have either a study_id or a user_id.', + task=spiff_task) + # Standalone workflow - we only care about the current user + elif processor.workflow_model.study_id is None and processor.workflow_model.user_id is not None: + return [processor.workflow_model.user_id] + # Workflow associated with a study - get all the users + else: + if not hasattr(spiff_task.task_spec, 'lane') or spiff_task.task_spec.lane is None: + associated = StudyService.get_study_associates(processor.workflow_model.study.id) + return [user['uid'] for user in associated if user['access']] + if spiff_task.task_spec.lane not in spiff_task.data: + return [] # No users are assignable to the task at this moment + lane_users = spiff_task.data[spiff_task.task_spec.lane] + if not isinstance(lane_users, list): + lane_users = [lane_users] - lane_uids = [] - for user in lane_users: - if isinstance(user, dict): - if 'value' in user and user['value'] is not None: - lane_uids.append(user['value']) + lane_uids = [] + for user in lane_users: + if isinstance(user, dict): + if 'value' in user and user['value'] is not None: + lane_uids.append(user['value']) + else: + raise ApiError.from_task(code="task_lane_user_error", message="Spiff Task %s lane user dict must have a key called 'value' with the user's uid in it." % + spiff_task.task_spec.name, task=spiff_task) + elif isinstance(user, str): + lane_uids.append(user) else: - raise ApiError.from_task(code="task_lane_user_error", message="Spiff Task %s lane user dict must have a key called 'value' with the user's uid in it." % - spiff_task.task_spec.name, task=spiff_task) - elif isinstance(user, str): - lane_uids.append(user) - else: - raise ApiError.from_task(code="task_lane_user_error", message="Spiff Task %s lane user is not a string or dict" % - spiff_task.task_spec.name, task=spiff_task) + raise ApiError.from_task(code="task_lane_user_error", message="Spiff Task %s lane user is not a string or dict" % + spiff_task.task_spec.name, task=spiff_task) - return lane_uids + return lane_uids @staticmethod def log_task_action(user_uid, processor, spiff_task, action): From 077b68c1e2ef11d0bb4abafa1b6fb09699be1203 Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Mon, 26 Apr 2021 08:46:19 -0400 Subject: [PATCH 08/20] When creating a workflow for a study, set the user_id to None --- crc/services/study_service.py | 1 + 1 file changed, 1 insertion(+) diff --git a/crc/services/study_service.py b/crc/services/study_service.py index 749a4e14..9bc5a598 100644 --- a/crc/services/study_service.py +++ b/crc/services/study_service.py @@ -495,6 +495,7 @@ class StudyService(object): def _create_workflow_model(study: StudyModel, spec): workflow_model = WorkflowModel(status=WorkflowStatus.not_started, study=study, + user_id=None, workflow_spec_id=spec.id, last_updated=datetime.now()) session.add(workflow_model) From 6c98e8a2f40245e0492cf545ffe60ead66bff9e1 Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Mon, 26 Apr 2021 08:47:03 -0400 Subject: [PATCH 09/20] removed duplicate code --- tests/base_test.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/base_test.py b/tests/base_test.py index a9fcfce1..d50831c9 100644 --- a/tests/base_test.py +++ b/tests/base_test.py @@ -175,11 +175,6 @@ class BaseTest(unittest.TestCase): specs = session.query(WorkflowSpecModel).all() self.assertIsNotNone(specs) - for spec in specs: - files = session.query(FileModel).filter_by(workflow_spec_id=spec.id).all() - self.assertIsNotNone(files) - self.assertGreater(len(files), 0) - for spec in specs: files = session.query(FileModel).filter_by(workflow_spec_id=spec.id).all() self.assertIsNotNone(files) From a17e1bfacaa50d53a55ff3b0b42bf22fc0c53377 Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Mon, 26 Apr 2021 08:50:46 -0400 Subject: [PATCH 10/20] When completing a form in a test, check whether workflow is standalone. Do not set study_id when workflow is standalone. *** Note the comment about passing in workflow_spec. We should be passing in a workflow, not a workflow_spec. --- tests/base_test.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/base_test.py b/tests/base_test.py index d50831c9..a7f5d25b 100644 --- a/tests/base_test.py +++ b/tests/base_test.py @@ -373,6 +373,10 @@ class BaseTest(unittest.TestCase): def complete_form(self, workflow_in, task_in, dict_data, update_all=False, error_code=None, terminate_loop=None, user_uid="dhf8r"): + # workflow_in should be a workflow, not a workflow_api + # we were passing in workflow_api in many of our tests, and + # this caused problems testing standalone workflows + standalone = getattr(workflow_in.workflow_spec, 'standalone', False) prev_completed_task_count = workflow_in.completed_tasks if isinstance(task_in, dict): task_id = task_in["id"] @@ -415,7 +419,8 @@ class BaseTest(unittest.TestCase): .order_by(TaskEventModel.date.desc()).all() self.assertGreater(len(task_events), 0) event = task_events[0] - self.assertIsNotNone(event.study_id) + if not standalone: + self.assertIsNotNone(event.study_id) self.assertEqual(user_uid, event.user_uid) self.assertEqual(workflow.id, event.workflow_id) self.assertEqual(workflow.workflow_spec_id, event.workflow_spec_id) From e836242f10a593ca15981f7e623b132585861e34 Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Mon, 26 Apr 2021 08:52:12 -0400 Subject: [PATCH 11/20] Added standalone argument when adding a workflow_spec --- example_data.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/example_data.py b/example_data.py index f3918846..bc7c438c 100644 --- a/example_data.py +++ b/example_data.py @@ -266,7 +266,7 @@ class ExampleDataLoader: from_tests=True) def create_spec(self, id, name, display_name="", description="", filepath=None, master_spec=False, - category_id=None, display_order=None, from_tests=False): + category_id=None, display_order=None, from_tests=False, standalone=False): """Assumes that a directory exists in static/bpmn with the same name as the given id. further assumes that the [id].bpmn is the primary file for the workflow. returns an array of data models to be added to the database.""" @@ -278,7 +278,8 @@ class ExampleDataLoader: description=description, is_master_spec=master_spec, category_id=category_id, - display_order=display_order) + display_order=display_order, + standalone=standalone) db.session.add(spec) db.session.commit() if not filepath and not from_tests: From 54ab7bd2f68eecbc40afebae6eed94c724e7d71e Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Mon, 26 Apr 2021 08:53:25 -0400 Subject: [PATCH 12/20] Test and workflow for launching a standalone workflow --- tests/data/hello_world/hello_world.bpmn | 58 +++++++++++++++++++++ tests/test_launch_workflow_outside_study.py | 23 ++++++++ 2 files changed, 81 insertions(+) create mode 100644 tests/data/hello_world/hello_world.bpmn create mode 100644 tests/test_launch_workflow_outside_study.py diff --git a/tests/data/hello_world/hello_world.bpmn b/tests/data/hello_world/hello_world.bpmn new file mode 100644 index 00000000..168aee1a --- /dev/null +++ b/tests/data/hello_world/hello_world.bpmn @@ -0,0 +1,58 @@ + + + + This workflow asks for a name and says hello + + SequenceFlow_0qyd2b7 + + + + Hello + + + + + + SequenceFlow_0qyd2b7 + SequenceFlow_1h46b40 + + + + Hello {{name}} + SequenceFlow_1h46b40 + SequenceFlow_0lqrc6e + + + SequenceFlow_0lqrc6e + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/test_launch_workflow_outside_study.py b/tests/test_launch_workflow_outside_study.py new file mode 100644 index 00000000..fbf51cc9 --- /dev/null +++ b/tests/test_launch_workflow_outside_study.py @@ -0,0 +1,23 @@ +from tests.base_test import BaseTest + +from crc import session +from crc.models.user import UserModel +from crc.services.workflow_service import WorkflowService + +from example_data import ExampleDataLoader + + +class TestNoStudyWorkflow(BaseTest): + + def test_no_study_workflow(self): + self.load_example_data() + spec = ExampleDataLoader().create_spec('hello_world', 'Hello World', standalone=True, from_tests=True) + user = session.query(UserModel).first() + self.assertIsNotNone(user) + workflow_model = WorkflowService.get_workflow_from_spec(spec.id, user) + workflow_api = self.get_workflow_api(workflow_model) + first_task = workflow_api.next_task + self.complete_form(workflow_model, first_task, {'name': 'Big Guy'}) + workflow_api = self.get_workflow_api(workflow_model) + second_task = workflow_api.next_task + self.assertEqual(second_task.documentation, 'Hello Big Guy') From c002ef1e76a1368fe4fb79dfa650de5d88e1a139 Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Mon, 26 Apr 2021 08:56:21 -0400 Subject: [PATCH 13/20] Added test for new api endpoint to get all standalone workflow_specs. Modified test_add_new_workflow_specification to include new standalone argument --- tests/workflow/test_workflow_spec_api.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/workflow/test_workflow_spec_api.py b/tests/workflow/test_workflow_spec_api.py index d54fbbf1..25c9ef6a 100644 --- a/tests/workflow/test_workflow_spec_api.py +++ b/tests/workflow/test_workflow_spec_api.py @@ -5,6 +5,8 @@ from crc import session from crc.models.file import FileModel from crc.models.workflow import WorkflowSpecModel, WorkflowSpecModelSchema, WorkflowModel, WorkflowSpecCategoryModel +from example_data import ExampleDataLoader + class TestWorkflowSpec(BaseTest): @@ -28,7 +30,8 @@ class TestWorkflowSpec(BaseTest): category_id = session.query(WorkflowSpecCategoryModel).first().id category_count = session.query(WorkflowSpecModel).filter_by(category_id=category_id).count() spec = WorkflowSpecModel(id='make_cookies', name='make_cookies', display_name='Cooooookies', - description='Om nom nom delicious cookies', category_id=category_id) + description='Om nom nom delicious cookies', category_id=category_id, + standalone=False) rv = self.app.post('/v1.0/workflow-specification', headers=self.logged_in_headers(), content_type="application/json", @@ -101,3 +104,16 @@ class TestWorkflowSpec(BaseTest): num_workflows_after = session.query(WorkflowModel).filter_by(workflow_spec_id=spec_id).count() self.assertEqual(num_files_after + num_workflows_after, 0) + def test_get_standalone_workflow_specs(self): + self.load_example_data() + category = session.query(WorkflowSpecCategoryModel).first() + ExampleDataLoader().create_spec('hello_world', 'Hello World', category_id=category.id, + standalone=True, from_tests=True) + rv = self.app.get('/v1.0/workflow-specification/standalone', headers=self.logged_in_headers()) + self.assertEqual(1, len(rv.json)) + + ExampleDataLoader().create_spec('email_script', 'Email Script', category_id=category.id, + standalone=True, from_tests=True) + + rv = self.app.get('/v1.0/workflow-specification/standalone', headers=self.logged_in_headers()) + self.assertEqual(2, len(rv.json)) From 7cca559747bef3bedf6eadb8247c8679910bb5ab Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Mon, 26 Apr 2021 08:58:09 -0400 Subject: [PATCH 14/20] Fixed issue where we were passing in a workflow_api instead of a workflow when completing a form in tests. --- tests/study/test_study_cancellations.py | 12 ++++---- tests/test_email_script.py | 3 -- tests/test_message_event.py | 6 ++-- tests/test_multi_instance_tasks_api.py | 10 +++---- tests/test_tasks_api.py | 22 +++++++------- .../workflow/test_workflow_boolean_default.py | 2 +- ..._workflow_enum_default_value_expression.py | 16 +++++----- .../workflow/test_workflow_form_field_name.py | 2 +- .../test_workflow_hidden_required_field.py | 5 ++-- tests/workflow/test_workflow_restart.py | 29 +++++++++---------- .../test_workflow_value_expression.py | 4 +-- 11 files changed, 52 insertions(+), 59 deletions(-) diff --git a/tests/study/test_study_cancellations.py b/tests/study/test_study_cancellations.py index 4b0d61f9..df0ba2a2 100644 --- a/tests/study/test_study_cancellations.py +++ b/tests/study/test_study_cancellations.py @@ -72,7 +72,7 @@ class TestStudyCancellations(BaseTest): workflow, study_id = self.load_workflow() workflow_api, first_task = self.get_first_task(workflow) - self.complete_form(workflow_api, first_task, {}) + self.complete_form(workflow, first_task, {}) study_result = self.put_study_on_hold(study_id) self.assertEqual('New Title', study_result.title) @@ -82,10 +82,10 @@ class TestStudyCancellations(BaseTest): workflow, study_id = self.load_workflow() workflow_api, first_task = self.get_first_task(workflow) - self.complete_form(workflow_api, first_task, {}) + self.complete_form(workflow, first_task, {}) workflow_api, next_task = self.get_second_task(workflow) - self.complete_form(workflow_api, next_task, {'how_many': 3}) + self.complete_form(workflow, next_task, {'how_many': 3}) study_result = self.put_study_on_hold(study_id) self.assertEqual('Second Title', study_result.title) @@ -95,13 +95,13 @@ class TestStudyCancellations(BaseTest): workflow, study_id = self.load_workflow() workflow_api, first_task = self.get_first_task(workflow) - self.complete_form(workflow_api, first_task, {}) + self.complete_form(workflow, first_task, {}) workflow_api, second_task = self.get_second_task(workflow) - self.complete_form(workflow_api, second_task, {'how_many': 3}) + self.complete_form(workflow, second_task, {'how_many': 3}) workflow_api, third_task = self.get_third_task(workflow) - self.complete_form(workflow_api, third_task, {}) + self.complete_form(workflow, third_task, {}) study_result = self.put_study_on_hold(study_id) self.assertEqual('Beer consumption in the bipedal software engineer', study_result.title) diff --git a/tests/test_email_script.py b/tests/test_email_script.py index 980d0c06..7786b045 100644 --- a/tests/test_email_script.py +++ b/tests/test_email_script.py @@ -23,7 +23,6 @@ class TestEmailScript(BaseTest): first_task = self.get_workflow_api(workflow).next_task - workflow = self.get_workflow_api(workflow) self.complete_form(workflow, first_task, {'subject': 'My Email Subject', 'recipients': 'test@example.com'}) self.assertEqual(1, len(outbox)) @@ -49,7 +48,6 @@ class TestEmailScript(BaseTest): def test_bad_email_address_1(self): workflow = self.create_workflow('email_script') first_task = self.get_workflow_api(workflow).next_task - workflow = self.get_workflow_api(workflow) with self.assertRaises(AssertionError): self.complete_form(workflow, first_task, {'recipients': 'test@example'}) @@ -57,7 +55,6 @@ class TestEmailScript(BaseTest): def test_bad_email_address_2(self): workflow = self.create_workflow('email_script') first_task = self.get_workflow_api(workflow).next_task - workflow = self.get_workflow_api(workflow) with self.assertRaises(AssertionError): self.complete_form(workflow, first_task, {'recipients': 'test'}) diff --git a/tests/test_message_event.py b/tests/test_message_event.py index f761871b..3d769095 100644 --- a/tests/test_message_event.py +++ b/tests/test_message_event.py @@ -13,10 +13,10 @@ class TestMessageEvent(BaseTest): # Start the workflow. first_task = self.get_workflow_api(workflow).next_task self.assertEqual('Activity_GetData', first_task.name) - workflow = self.get_workflow_api(workflow) + self.complete_form(workflow, first_task, {'formdata': 'asdf'}) - workflow = self.get_workflow_api(workflow) - self.assertEqual('Activity_HowMany', workflow.next_task.name) + workflow_api = self.get_workflow_api(workflow) + self.assertEqual('Activity_HowMany', workflow_api.next_task.name) # reset the workflow # this ultimately calls crc.api.workflow.set_current_task diff --git a/tests/test_multi_instance_tasks_api.py b/tests/test_multi_instance_tasks_api.py index d4f26297..12036e2d 100644 --- a/tests/test_multi_instance_tasks_api.py +++ b/tests/test_multi_instance_tasks_api.py @@ -67,14 +67,14 @@ class TestMultiinstanceTasksApi(BaseTest): content_type="application/json") self.assert_success(rv) json_data = json.loads(rv.get_data(as_text=True)) - workflow = WorkflowApiSchema().load(json_data) - data = workflow.next_task.data + workflow_api = WorkflowApiSchema().load(json_data) + data = workflow_api.next_task.data data['investigator']['email'] = "dhf8r@virginia.edu" - self.complete_form(workflow, workflow.next_task, data) + self.complete_form(workflow, workflow_api.next_task, data) #tasks = self.get_workflow_api(workflow).user_tasks - workflow = self.get_workflow_api(workflow) - self.assertEqual(WorkflowStatus.complete, workflow.status) + workflow_api = self.get_workflow_api(workflow) + self.assertEqual(WorkflowStatus.complete, workflow_api.status) @patch('crc.services.protocol_builder.requests.get') diff --git a/tests/test_tasks_api.py b/tests/test_tasks_api.py index f2742960..9c6f0b41 100644 --- a/tests/test_tasks_api.py +++ b/tests/test_tasks_api.py @@ -386,15 +386,15 @@ class TestTasksApi(BaseTest): # Start the workflow. first_task = self.get_workflow_api(workflow).next_task self.complete_form(workflow, first_task, {"has_bananas": True}) - workflow = self.get_workflow_api(workflow) - self.assertEqual('Task_Num_Bananas', workflow.next_task.name) + workflow_api = self.get_workflow_api(workflow) + self.assertEqual('Task_Num_Bananas', workflow_api.next_task.name) # Trying to re-submit the initial task, and answer differently, should result in an error. self.complete_form(workflow, first_task, {"has_bananas": False}, error_code="invalid_state") # Go ahead and set the number of bananas. - workflow = self.get_workflow_api(workflow) - task = workflow.next_task + workflow_api = self.get_workflow_api(workflow) + task = workflow_api.next_task self.complete_form(workflow, task, {"num_bananas": 4}) # We are now at the end of the workflow. @@ -405,19 +405,19 @@ class TestTasksApi(BaseTest): content_type="application/json") self.assert_success(rv) json_data = json.loads(rv.get_data(as_text=True)) - workflow = WorkflowApiSchema().load(json_data) + workflow_api = WorkflowApiSchema().load(json_data) # Assure the Next Task is the one we just reset the token to be on. - self.assertEqual("Task_Has_Bananas", workflow.next_task.name) + self.assertEqual("Task_Has_Bananas", workflow_api.next_task.name) # Go ahead and get that workflow one more time, it should still be right. - workflow = self.get_workflow_api(workflow) + workflow_api = self.get_workflow_api(workflow) # Assure the Next Task is the one we just reset the token to be on. - self.assertEqual("Task_Has_Bananas", workflow.next_task.name) + self.assertEqual("Task_Has_Bananas", workflow_api.next_task.name) # The next task should be a different value. - self.complete_form(workflow, workflow.next_task, {"has_bananas": False}) - workflow = self.get_workflow_api(workflow) - self.assertEqual('Task_Why_No_Bananas', workflow.next_task.name) + self.complete_form(workflow, workflow_api.next_task, {"has_bananas": False}) + workflow_api = self.get_workflow_api(workflow) + self.assertEqual('Task_Why_No_Bananas', workflow_api.next_task.name) diff --git a/tests/workflow/test_workflow_boolean_default.py b/tests/workflow/test_workflow_boolean_default.py index 1773295a..a2b04228 100644 --- a/tests/workflow/test_workflow_boolean_default.py +++ b/tests/workflow/test_workflow_boolean_default.py @@ -7,7 +7,7 @@ class TestBooleanDefault(BaseTest): workflow = self.create_workflow('boolean_default_value') workflow_api = self.get_workflow_api(workflow) set_default_task = workflow_api.next_task - result = self.complete_form(workflow_api, set_default_task, {'yes_no': yes_no}) + result = self.complete_form(workflow, set_default_task, {'yes_no': yes_no}) return result def test_boolean_true_string(self): diff --git a/tests/workflow/test_workflow_enum_default_value_expression.py b/tests/workflow/test_workflow_enum_default_value_expression.py index 00c2abf6..644d52f8 100644 --- a/tests/workflow/test_workflow_enum_default_value_expression.py +++ b/tests/workflow/test_workflow_enum_default_value_expression.py @@ -7,35 +7,35 @@ class TestWorkflowEnumDefault(BaseTest): def test_enum_default_from_value_expression(self): workflow = self.create_workflow('enum_value_expression') - first_task = self.get_workflow_api(workflow).next_task - self.assertEqual('Activity_UserInput', first_task.name) workflow_api = self.get_workflow_api(workflow) + first_task = workflow_api.next_task + self.assertEqual('Activity_UserInput', first_task.name) - result = self.complete_form(workflow_api, first_task, {'user_input': True}) + result = self.complete_form(workflow, first_task, {'user_input': True}) self.assertIn('user_input', result.next_task.data) self.assertEqual(True, result.next_task.data['user_input']) self.assertIn('lookup_output', result.next_task.data) self.assertEqual('black', result.next_task.data['lookup_output']) workflow_api = self.get_workflow_api(workflow) - self.assertEqual('Activity_PickColor', self.get_workflow_api(workflow_api).next_task.name) + self.assertEqual('Activity_PickColor', workflow_api.next_task.name) self.assertEqual({'value': 'black', 'label': 'Black'}, workflow_api.next_task.data['color_select']) # workflow = self.create_workflow('enum_value_expression') - first_task = self.get_workflow_api(workflow).next_task - self.assertEqual('Activity_UserInput', first_task.name) workflow_api = self.get_workflow_api(workflow) + first_task = workflow_api.next_task + self.assertEqual('Activity_UserInput', first_task.name) - result = self.complete_form(workflow_api, first_task, {'user_input': False}) + result = self.complete_form(workflow, first_task, {'user_input': False}) self.assertIn('user_input', result.next_task.data) self.assertEqual(False, result.next_task.data['user_input']) self.assertIn('lookup_output', result.next_task.data) self.assertEqual('white', result.next_task.data['lookup_output']) workflow_api = self.get_workflow_api(workflow) - self.assertEqual('Activity_PickColor', self.get_workflow_api(workflow_api).next_task.name) + self.assertEqual('Activity_PickColor', workflow_api.next_task.name) self.assertEqual({'value': 'white', 'label': 'White'}, workflow_api.next_task.data['color_select']) def test_enum_value_expression_and_default(self): diff --git a/tests/workflow/test_workflow_form_field_name.py b/tests/workflow/test_workflow_form_field_name.py index 8a6aead2..6e38a816 100644 --- a/tests/workflow/test_workflow_form_field_name.py +++ b/tests/workflow/test_workflow_form_field_name.py @@ -18,7 +18,7 @@ class TestFormFieldName(BaseTest): workflow_api = self.get_workflow_api(workflow) first_task = workflow_api.next_task - self.complete_form(workflow_api, first_task, {}) + self.complete_form(workflow, first_task, {}) workflow_api = self.get_workflow_api(workflow) second_task = workflow_api.next_task diff --git a/tests/workflow/test_workflow_hidden_required_field.py b/tests/workflow/test_workflow_hidden_required_field.py index b77dbb24..610c917d 100644 --- a/tests/workflow/test_workflow_hidden_required_field.py +++ b/tests/workflow/test_workflow_hidden_required_field.py @@ -34,14 +34,13 @@ class TestWorkflowHiddenRequiredField(BaseTest): first_task = workflow_api.next_task self.assertEqual('Activity_Hello', first_task.name) - workflow_api = self.get_workflow_api(workflow) - self.complete_form(workflow_api, first_task, {}) + self.complete_form(workflow, first_task, {}) workflow_api = self.get_workflow_api(workflow) second_task = workflow_api.next_task self.assertEqual('Activity_HiddenField', second_task.name) - self.complete_form(workflow_api, second_task, {}) + self.complete_form(workflow, second_task, {}) workflow_api = self.get_workflow_api(workflow) # The color field is hidden and required. Make sure we use the default value diff --git a/tests/workflow/test_workflow_restart.py b/tests/workflow/test_workflow_restart.py index 98688964..0aafb492 100644 --- a/tests/workflow/test_workflow_restart.py +++ b/tests/workflow/test_workflow_restart.py @@ -9,20 +9,20 @@ class TestWorkflowRestart(BaseTest): workflow = self.create_workflow('message_event') - first_task = self.get_workflow_api(workflow).next_task - self.assertEqual('Activity_GetData', first_task.name) workflow_api = self.get_workflow_api(workflow) + first_task = workflow_api.next_task + self.assertEqual('Activity_GetData', first_task.name) - result = self.complete_form(workflow_api, first_task, {'formdata': 'asdf'}) + result = self.complete_form(workflow, first_task, {'formdata': 'asdf'}) self.assertIn('formdata', result.next_task.data) self.assertEqual('asdf', result.next_task.data['formdata']) workflow_api = self.get_workflow_api(workflow) - self.assertEqual('Activity_HowMany', self.get_workflow_api(workflow_api).next_task.name) + self.assertEqual('Activity_HowMany', workflow_api.next_task.name) # restart with data. should land at beginning with data workflow_api = self.restart_workflow_api(result) - first_task = self.get_workflow_api(workflow_api).next_task + first_task = workflow_api.next_task self.assertEqual('Activity_GetData', first_task.name) self.assertIn('formdata', workflow_api.next_task.data) self.assertEqual('asdf', workflow_api.next_task.data['formdata']) @@ -33,21 +33,19 @@ class TestWorkflowRestart(BaseTest): self.assertEqual('Activity_GetData', first_task.name) self.assertNotIn('formdata', workflow_api.next_task.data) - print('Nice Test') - def test_workflow_restart_on_cancel_notify(self): workflow = self.create_workflow('message_event') study_id = workflow.study_id # Start the workflow. - first_task = self.get_workflow_api(workflow).next_task - self.assertEqual('Activity_GetData', first_task.name) workflow_api = self.get_workflow_api(workflow) - self.complete_form(workflow_api, first_task, {'formdata': 'asdf'}) + first_task = workflow_api.next_task + self.assertEqual('Activity_GetData', first_task.name) + self.complete_form(workflow, first_task, {'formdata': 'asdf'}) workflow_api = self.get_workflow_api(workflow) self.assertEqual('Activity_HowMany', workflow_api.next_task.name) - workflow_api = self.restart_workflow_api(workflow) + self.restart_workflow_api(workflow) study_result = session.query(StudyModel).filter(StudyModel.id == study_id).first() self.assertEqual('New Title', study_result.title) @@ -66,17 +64,16 @@ class TestWorkflowRestart(BaseTest): study_id = workflow.study_id # Start the workflow. - first_task = self.get_workflow_api(workflow).next_task - self.assertEqual('Activity_GetData', first_task.name) workflow_api = self.get_workflow_api(workflow) - self.complete_form(workflow_api, first_task, {'formdata': 'asdf'}) + first_task = workflow_api.next_task + self.assertEqual('Activity_GetData', first_task.name) + self.complete_form(workflow, first_task, {'formdata': 'asdf'}) workflow_api = self.get_workflow_api(workflow) next_task = workflow_api.next_task self.assertEqual('Activity_HowMany', next_task.name) - self.complete_form(workflow_api, next_task, {'how_many': 3}) + self.complete_form(workflow, next_task, {'how_many': 3}) - workflow_api = self.restart_workflow_api(workflow) study_result = session.query(StudyModel).filter(StudyModel.id == study_id).first() self.assertEqual('Beer consumption in the bipedal software engineer', study_result.title) diff --git a/tests/workflow/test_workflow_value_expression.py b/tests/workflow/test_workflow_value_expression.py index d527c694..47751b1c 100644 --- a/tests/workflow/test_workflow_value_expression.py +++ b/tests/workflow/test_workflow_value_expression.py @@ -9,7 +9,7 @@ class TestValueExpression(BaseTest): workflow_api = self.get_workflow_api(workflow) first_task = workflow_api.next_task - self.complete_form(workflow_api, first_task, {'value_expression_value': ''}) + self.complete_form(workflow, first_task, {'value_expression_value': ''}) workflow_api = self.get_workflow_api(workflow) second_task = workflow_api.next_task @@ -26,7 +26,7 @@ class TestValueExpression(BaseTest): workflow_api = self.get_workflow_api(workflow) first_task = workflow_api.next_task - self.complete_form(workflow_api, first_task, {'value_expression_value': 'black'}) + self.complete_form(workflow, first_task, {'value_expression_value': 'black'}) workflow_api = self.get_workflow_api(workflow) second_task = workflow_api.next_task From 1a44f50de6f7e0d0f039681565b22e0f73598a01 Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Mon, 26 Apr 2021 11:21:35 -0400 Subject: [PATCH 15/20] Added `standalone` argument to fix failing test. --- tests/test_auto_set_primary_bpmn.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_auto_set_primary_bpmn.py b/tests/test_auto_set_primary_bpmn.py index d6280b69..6179d2f4 100644 --- a/tests/test_auto_set_primary_bpmn.py +++ b/tests/test_auto_set_primary_bpmn.py @@ -13,7 +13,8 @@ class TestAutoSetPrimaryBPMN(BaseTest): category_id = session.query(WorkflowSpecCategoryModel).first().id # Add a workflow spec spec = WorkflowSpecModel(id='make_cookies', name='make_cookies', display_name='Cooooookies', - description='Om nom nom delicious cookies', category_id=category_id) + description='Om nom nom delicious cookies', category_id=category_id, + standalone=False) rv = self.app.post('/v1.0/workflow-specification', headers=self.logged_in_headers(), content_type="application/json", From b73c2ff1cb0150bb4fc8567880ef0e316dfddf29 Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Mon, 26 Apr 2021 12:00:06 -0400 Subject: [PATCH 16/20] Added missing test for `get_workflow_from_workflow_spec` --- tests/workflow/test_workflow_spec_api.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/workflow/test_workflow_spec_api.py b/tests/workflow/test_workflow_spec_api.py index 25c9ef6a..2cb7e153 100644 --- a/tests/workflow/test_workflow_spec_api.py +++ b/tests/workflow/test_workflow_spec_api.py @@ -117,3 +117,11 @@ class TestWorkflowSpec(BaseTest): rv = self.app.get('/v1.0/workflow-specification/standalone', headers=self.logged_in_headers()) self.assertEqual(2, len(rv.json)) + + def test_get_workflow_from_workflow_spec(self): + self.load_example_data() + spec = ExampleDataLoader().create_spec('hello_world', 'Hello World', standalone=True, from_tests=True) + rv = self.app.post(f'/v1.0/workflow-specification/{spec.id}', headers=self.logged_in_headers()) + self.assert_success(rv) + self.assertEqual('hello_world', rv.json['workflow_spec_id']) + self.assertEqual('Task_GetName', rv.json['next_task']['name']) From 0aa3e8f586becdca0e5a57d8b0bd2fc2bb3ba63a Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Mon, 26 Apr 2021 12:41:50 -0400 Subject: [PATCH 17/20] added missing test for add_workflow_spec_category --- tests/workflow/test_workflow_spec_api.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/tests/workflow/test_workflow_spec_api.py b/tests/workflow/test_workflow_spec_api.py index 2cb7e153..3d93ffc1 100644 --- a/tests/workflow/test_workflow_spec_api.py +++ b/tests/workflow/test_workflow_spec_api.py @@ -3,7 +3,7 @@ import json from tests.base_test import BaseTest from crc import session from crc.models.file import FileModel -from crc.models.workflow import WorkflowSpecModel, WorkflowSpecModelSchema, WorkflowModel, WorkflowSpecCategoryModel +from crc.models.workflow import WorkflowSpecModel, WorkflowSpecModelSchema, WorkflowModel, WorkflowSpecCategoryModel, WorkflowSpecCategoryModelSchema from example_data import ExampleDataLoader @@ -125,3 +125,22 @@ class TestWorkflowSpec(BaseTest): self.assert_success(rv) self.assertEqual('hello_world', rv.json['workflow_spec_id']) self.assertEqual('Task_GetName', rv.json['next_task']['name']) + + def test_add_workflow_spec_category(self): + self.load_example_data() + count = session.query(WorkflowSpecCategoryModel).count() + category = WorkflowSpecCategoryModel( + id=count, + name='another_test_category', + display_name='Another Test Category', + display_order=0 + ) + rv = self.app.post(f'/v1.0/workflow-specification-category', + headers=self.logged_in_headers(), + content_type="application/json", + data=json.dumps(WorkflowSpecCategoryModelSchema().dump(category)) + ) + self.assert_success(rv) + result = session.query(WorkflowSpecCategoryModel).filter(WorkflowSpecCategoryModel.name=='another_test_category').first() + self.assertEqual('Another Test Category', result.display_name) + self.assertEqual(count, result.id) From 09d8c8bcbabaa1ad1595ebc910592a1169f268c9 Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Mon, 26 Apr 2021 12:57:08 -0400 Subject: [PATCH 18/20] added missing test for update_workflow_spec_category --- tests/workflow/test_workflow_spec_api.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/workflow/test_workflow_spec_api.py b/tests/workflow/test_workflow_spec_api.py index 3d93ffc1..498ea37c 100644 --- a/tests/workflow/test_workflow_spec_api.py +++ b/tests/workflow/test_workflow_spec_api.py @@ -144,3 +144,20 @@ class TestWorkflowSpec(BaseTest): result = session.query(WorkflowSpecCategoryModel).filter(WorkflowSpecCategoryModel.name=='another_test_category').first() self.assertEqual('Another Test Category', result.display_name) self.assertEqual(count, result.id) + + def test_update_workflow_spec_category(self): + self.load_example_data() + category = session.query(WorkflowSpecCategoryModel).first() + category_name_before = category.name + new_category_name = category_name_before + '_asdf' + self.assertNotEqual(category_name_before, new_category_name) + + category.name = new_category_name + + rv = self.app.put(f'/v1.0/workflow-specification-category/{category.id}', + content_type="application/json", + headers=self.logged_in_headers(), + data=json.dumps(WorkflowSpecCategoryModelSchema().dump(category))) + self.assert_success(rv) + json_data = json.loads(rv.get_data(as_text=True)) + self.assertEqual(new_category_name, json_data['name']) From 24c818bf311454339ed7629c54ff1e2d4314dfaf Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Thu, 29 Apr 2021 14:29:21 -0400 Subject: [PATCH 19/20] Added study_id to workflow_api --- crc/api.yml | 4 +++- crc/models/api_models.py | 7 ++++--- crc/services/workflow_service.py | 3 ++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/crc/api.yml b/crc/api.yml index b9ed7df1..be95c2dd 100644 --- a/crc/api.yml +++ b/crc/api.yml @@ -427,7 +427,7 @@ paths: - name: spec_id in: path required: true - description: The unique id of an existing workflow specification to modify. + description: The unique id of an existing workflow specification. schema: type: string get: @@ -1640,6 +1640,8 @@ components: type: integer num_tasks_incomplete: type: integer + study_id: + type: integer example: id: 291234 diff --git a/crc/models/api_models.py b/crc/models/api_models.py index 2bb4abc6..308823fc 100644 --- a/crc/models/api_models.py +++ b/crc/models/api_models.py @@ -191,7 +191,7 @@ class DocumentDirectory(object): class WorkflowApi(object): def __init__(self, id, status, next_task, navigation, spec_version, is_latest_spec, workflow_spec_id, total_tasks, completed_tasks, - last_updated, is_review, title): + last_updated, is_review, title, study_id): self.id = id self.status = status self.next_task = next_task # The next task that requires user input. @@ -204,13 +204,14 @@ class WorkflowApi(object): self.last_updated = last_updated self.title = title self.is_review = is_review + self.study_id = study_id or '' class WorkflowApiSchema(ma.Schema): class Meta: model = WorkflowApi fields = ["id", "status", "next_task", "navigation", "workflow_spec_id", "spec_version", "is_latest_spec", "total_tasks", "completed_tasks", - "last_updated", "is_review", "title"] + "last_updated", "is_review", "title", "study_id"] unknown = INCLUDE status = EnumField(WorkflowStatus) @@ -221,7 +222,7 @@ class WorkflowApiSchema(ma.Schema): def make_workflow(self, data, **kwargs): keys = ['id', 'status', 'next_task', 'navigation', 'workflow_spec_id', 'spec_version', 'is_latest_spec', "total_tasks", "completed_tasks", - "last_updated", "is_review", "title"] + "last_updated", "is_review", "title", "study_id"] filtered_fields = {key: data[key] for key in keys} filtered_fields['next_task'] = TaskSchema().make_task(data['next_task']) return WorkflowApi(**filtered_fields) diff --git a/crc/services/workflow_service.py b/crc/services/workflow_service.py index 9edaf856..2369ceaf 100644 --- a/crc/services/workflow_service.py +++ b/crc/services/workflow_service.py @@ -408,7 +408,8 @@ class WorkflowService(object): completed_tasks=processor.workflow_model.completed_tasks, last_updated=processor.workflow_model.last_updated, is_review=is_review, - title=spec.display_name + title=spec.display_name, + study_id=processor.workflow_model.study_id or None ) if not next_task: # The Next Task can be requested to be a certain task, useful for parallel tasks. # This may or may not work, sometimes there is no next task to complete. From eb153c3ffce6909f98b3ba5e434530bef5a8efd9 Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Thu, 29 Apr 2021 14:29:43 -0400 Subject: [PATCH 20/20] Fixed column definition for standalone --- migrations/versions/8b976945a54e_.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/versions/8b976945a54e_.py b/migrations/versions/8b976945a54e_.py index 7805e31b..0973f193 100644 --- a/migrations/versions/8b976945a54e_.py +++ b/migrations/versions/8b976945a54e_.py @@ -18,7 +18,7 @@ depends_on = None def upgrade(): op.add_column('workflow', sa.Column('user_id', sa.String(), nullable=True)) - op.add_column('workflow_spec', sa.Column('standalone', sa.String(), default=False)) + op.add_column('workflow_spec', sa.Column('standalone', sa.Boolean(), default=False)) op.execute("UPDATE workflow_spec SET standalone=False WHERE standalone is null;") op.execute("ALTER TABLE task_event ALTER COLUMN study_id DROP NOT NULL")