From 300026cbc842d899682673535e4be198796a4ff8 Mon Sep 17 00:00:00 2001 From: Dan Funk Date: Tue, 28 Jul 2020 10:16:48 -0400 Subject: [PATCH] Expanding the task events endpoint to accept workflow and study ids as additional filters. Removing events from the study endpoint, too noisy. --- crc/api.yml | 12 ++++++++++ crc/api/workflow.py | 6 ++++- crc/models/study.py | 1 - crc/models/task_event.py | 1 + crc/services/file_service.py | 3 ++- crc/services/study_service.py | 9 -------- tests/study/test_study_api.py | 16 +------------ tests/test_events.py | 43 +++++++++++++++++++++++++++++++++++ tests/test_user_roles.py | 1 + 9 files changed, 65 insertions(+), 27 deletions(-) create mode 100644 tests/test_events.py diff --git a/crc/api.yml b/crc/api.yml index 4c6ebd1b..f23f0ace 100644 --- a/crc/api.yml +++ b/crc/api.yml @@ -572,6 +572,18 @@ paths: description: The type of action the event documents, options include "ASSIGNMENT" for tasks that are waiting on you, "COMPLETE" for things have completed. schema: type: string + - name: workflow + in: query + required: false + description: Restrict results to the given workflow. + schema: + type: number + - name: study + in: query + required: false + description: Restrict results to the given study. + schema: + type: number get: operationId: crc.api.workflow.get_task_events summary: Returns a list of task events related to the current user. Can be filtered by type. diff --git a/crc/api/workflow.py b/crc/api/workflow.py index a290d340..3418d50a 100644 --- a/crc/api/workflow.py +++ b/crc/api/workflow.py @@ -103,11 +103,15 @@ def get_workflow(workflow_id, soft_reset=False, hard_reset=False): return WorkflowApiSchema().dump(workflow_api_model) -def get_task_events(action): +def get_task_events(action = None, workflow = None, study = None): """Provides a way to see a history of what has happened, or get a list of tasks that need your attention.""" query = session.query(TaskEventModel).filter(TaskEventModel.user_uid == g.user.uid) if action: query = query.filter(TaskEventModel.action == action) + if workflow: + query = query.filter(TaskEventModel.workflow_id == workflow) + if study: + query = query.filter(TaskEventModel.study_id == study) events = query.all() # Turn the database records into something a little richer for the UI to use. diff --git a/crc/models/study.py b/crc/models/study.py index 669ca535..e14fe0a6 100644 --- a/crc/models/study.py +++ b/crc/models/study.py @@ -159,7 +159,6 @@ class StudySchema(ma.Schema): files = fields.List(fields.Nested(FileSchema), dump_only=True) approvals = fields.List(fields.Nested('ApprovalSchema'), dump_only=True) enrollment_date = fields.Date(allow_none=True) - events = fields.List(fields.Nested('TaskEventSchema'), dump_only=True) class Meta: model = Study diff --git a/crc/models/task_event.py b/crc/models/task_event.py index c696bc26..aa05a4f7 100644 --- a/crc/models/task_event.py +++ b/crc/models/task_event.py @@ -57,6 +57,7 @@ class TaskEventSchema(ma.Schema): study = fields.Nested(StudySchema, dump_only=True) workflow = fields.Nested(WorkflowMetadataSchema, dump_only=True) + task_lane = fields.String(allow_none=True, required=False) class Meta: model = TaskEvent additional = ["id", "user_uid", "action", "task_id", "task_title", diff --git a/crc/services/file_service.py b/crc/services/file_service.py index 6ba2e1ad..8b5665c6 100644 --- a/crc/services/file_service.py +++ b/crc/services/file_service.py @@ -78,7 +78,8 @@ class FileService(object): """ Opens a reference file (assumes that it is xls file) and returns the data as a dictionary, each row keyed on the given index_column name. If there are columns that should be represented as integers, pass these as an array of int_columns, lest - you get '1.0' rather than '1' """ + you get '1.0' rather than '1' + fixme: This is stupid stupid slow. Place it in the database and just check if it is up to date.""" data_model = FileService.get_reference_file_data(reference_file_name) xls = ExcelFile(data_model.data) df = xls.parse(xls.sheet_names[0]) diff --git a/crc/services/study_service.py b/crc/services/study_service.py index cbf3434d..4eb8dde7 100644 --- a/crc/services/study_service.py +++ b/crc/services/study_service.py @@ -61,7 +61,6 @@ class StudyService(object): files = (File.from_models(model, FileService.get_file_data(model.id), FileService.get_doc_dictionary()) for model in files) study.files = list(files) - study.events = StudyService.get_events(study_id) # Calling this line repeatedly is very very slow. It creates the # master spec and runs it. Don't execute this for Abandoned studies, as # we don't have the information to process them. @@ -75,14 +74,6 @@ class StudyService(object): return study - @staticmethod - def get_events(study_id): - event_models = db.session.query(TaskEventModel).filter(TaskEventModel.study_id == study_id).all() - events = [] - for event_model in event_models: - events.append(TaskEvent(event_model, None, WorkflowMetadata(id=event_model.workflow_id))) - return events - @staticmethod def delete_study(study_id): session.query(TaskEventModel).filter_by(study_id=study_id).delete() diff --git a/tests/study/test_study_api.py b/tests/study/test_study_api.py index 9ed7bb2c..fb0a4dcf 100644 --- a/tests/study/test_study_api.py +++ b/tests/study/test_study_api.py @@ -1,4 +1,5 @@ import json +from profile import Profile from tests.base_test import BaseTest @@ -114,21 +115,6 @@ class TestStudyApi(BaseTest): for approval in study.approvals: self.assertEqual(full_study['study'].title, approval['title']) - def test_get_study_has_details_about_events(self): - # Set up the study and attach a file to it. - self.load_example_data() - workflow = self.create_workflow('file_upload_form') - processor = WorkflowProcessor(workflow) - task = processor.next_task() - WorkflowService.log_task_action('dhf8r', processor, task, 'my_action') - api_response = self.app.get('/v1.0/study/%i' % workflow.study_id, - headers=self.logged_in_headers(), - content_type="application/json") - self.assert_success(api_response) - study = json.loads(api_response.get_data(as_text=True)) - self.assertEqual(1, len(study['events'])) - self.assertEqual('my_action', study['events'][0]['action']) - def test_add_study(self): self.load_example_data() study = self.add_test_study() diff --git a/tests/test_events.py b/tests/test_events.py new file mode 100644 index 00000000..06005ee1 --- /dev/null +++ b/tests/test_events.py @@ -0,0 +1,43 @@ +import json + +from tests.base_test import BaseTest +from crc.models.workflow import WorkflowStatus +from crc import db +from crc.api.common import ApiError +from crc.models.task_event import TaskEventModel, TaskEventSchema +from crc.services.workflow_service import WorkflowService + + +class TestEvents(BaseTest): + + + def test_list_events_by_workflow(self): + workflow_one = self.create_workflow('exclusive_gateway') + + # Start a the workflow. + first_task = self.get_workflow_api(workflow_one).next_task + self.complete_form(workflow_one, first_task, {"has_bananas": True}) + workflow_one = self.get_workflow_api(workflow_one) + self.assertEqual('Task_Num_Bananas', workflow_one.next_task.name) + + # Start a second workflow + workflow_two = self.create_workflow('subprocess') + workflow_api_two = self.get_workflow_api(workflow_two) + + # Get all action events across workflows + rv = self.app.get('/v1.0/task_events?action=ASSIGNMENT', + headers=self.logged_in_headers(), + content_type="application/json") + self.assert_success(rv) + json_data = json.loads(rv.get_data(as_text=True)) + tasks = TaskEventSchema(many=True).load(json_data) + self.assertEqual(2, len(tasks)) + + # Get action events for a single workflow + rv = self.app.get(f'/v1.0/task_events?action=ASSIGNMENT&workflow={workflow_one.id}', + headers=self.logged_in_headers(), + content_type="application/json") + self.assert_success(rv) + json_data = json.loads(rv.get_data(as_text=True)) + tasks = TaskEventSchema(many=True).load(json_data) + self.assertEqual(1, len(tasks)) diff --git a/tests/test_user_roles.py b/tests/test_user_roles.py index 74871476..ce3b03b5 100644 --- a/tests/test_user_roles.py +++ b/tests/test_user_roles.py @@ -111,6 +111,7 @@ class TestTasksApi(BaseTest): data['approval'] = True self.complete_form(workflow, workflow_api.next_task, data, user_uid=supervisor.uid) + def test_navigation_and_current_task_updates_through_workflow(self): submitter = self.create_user(uid='lje5u')