From 949f3be40345145e76c3cdab5f69c1ec6f8c9736 Mon Sep 17 00:00:00 2001 From: Kelly McDonald Date: Mon, 12 Apr 2021 12:23:33 -0400 Subject: [PATCH 1/4] give an endpoint to return all files associated with a study --- crc/api.yml | 6 ++++++ crc/api/file.py | 9 ++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/crc/api.yml b/crc/api.yml index 6dc1afa6..da466058 100644 --- a/crc/api.yml +++ b/crc/api.yml @@ -565,6 +565,12 @@ paths: description: The unique key of a workflow task form field. Make sure this matches a document in the irb_documents.xslx reference sheet. schema: type: string + - name: study_id + in: query + required: false + description: The study that the files are related to + schema: + type: integer get: operationId: crc.api.file.get_files summary: Provides a list of files that match the given parameters (such as a spec id) IMPORTANT, only includes metadata, not the file content. diff --git a/crc/api/file.py b/crc/api/file.py index 861f9f04..035b8086 100644 --- a/crc/api/file.py +++ b/crc/api/file.py @@ -17,13 +17,16 @@ def to_file_api(file_model): FileService.get_doc_dictionary()) -def get_files(workflow_spec_id=None, workflow_id=None, form_field_key=None): - if all(v is None for v in [workflow_spec_id, workflow_id, form_field_key]): +def get_files(workflow_spec_id=None, workflow_id=None, form_field_key=None,study_id=None): + if all(v is None for v in [workflow_spec_id, workflow_id, form_field_key,study_id]): raise ApiError('missing_parameter', 'Please specify either a workflow_spec_id or a ' 'workflow_id with an optional form_field_key') - file_models = FileService.get_files(workflow_spec_id=workflow_spec_id, + if study_id is not None: + file_models = FileService.get_files_for_study(study_id=study_id) + else: + file_models = FileService.get_files(workflow_spec_id=workflow_spec_id, workflow_id=workflow_id, irb_doc_code=form_field_key) From e2197ddab9fe231a1d3b69690a56acf5bc5c98b7 Mon Sep 17 00:00:00 2001 From: Kelly McDonald Date: Tue, 20 Apr 2021 08:12:27 -0400 Subject: [PATCH 2/4] Changes to support the frontend data pane fixes #277 and requires frontend branch 277-document-pane --- crc/api.yml | 61 ++++++++++++++++++++++++++++++ crc/api/file.py | 52 +++++++++++++++++++++++++ crc/models/api_models.py | 22 ++++++++++- crc/models/file.py | 2 +- crc/services/file_service.py | 32 ++++++++++++++++ crc/services/workflow_processor.py | 4 -- tests/study/test_study_api.py | 2 +- 7 files changed, 168 insertions(+), 7 deletions(-) diff --git a/crc/api.yml b/crc/api.yml index da466058..289ad153 100644 --- a/crc/api.yml +++ b/crc/api.yml @@ -67,6 +67,39 @@ paths: type: array items: $ref: "#/components/schemas/User" + # /v1.0/document_directory/{study_id} + /document_directory/{study_id}: + parameters : + - name : study_id + required : true + in : path + description : The unique id of a study. + schema : + type : integer + - name : workflow_id + in : query + required : false + schema : + type : integer + get: + operationId: crc.api.file.get_document_directory + security: + - auth_admin: ['secret'] + summary: Returns a directory of all files for study in a nested structure + tags: + - Document Categories + responses: + '200': + description: All IRB Categories defined in the file document. + content: + application/json: + schema: + type: object + properties: + Admin: + schema: + $ref: "#/components/schemas/DocumentCategories" + # /v1.0/study /study: get: @@ -1429,6 +1462,34 @@ components: type: string x-nullable: true example: "27b-6-1212" + DocumentCategories: + properties: + category1: + type: string + x-nullable: true + example: "Ancillary Document" + category2: + type: string + x-nullable: true + example: "Lab Manual" + category3: + type: string + x-nullable: true + example: "" + Who Uploads?: + type: string + x-nullable: true + example: "CRC" + description: + type: string + x-nullable: true + example: "some description" + + + + + + DataStore: properties: id: diff --git a/crc/api/file.py b/crc/api/file.py index 035b8086..edbd9cf8 100644 --- a/crc/api/file.py +++ b/crc/api/file.py @@ -7,9 +7,61 @@ from flask import send_file from crc import session from crc.api.common import ApiError from crc.models.file import FileSchema, FileModel, File, FileModelSchema, FileDataModel +from crc.models.api_models import DocumentDirectory, DocumentDirectorySchema from crc.models.workflow import WorkflowSpecModel from crc.services.file_service import FileService +def ensure_exists(output,categories,expanded): + """ + This is a recursive function, it expects a list of + levels with a file object at the end (kinda like duck,duck,duck,goose) + + for each level, it makes sure that level is already in the structure and if it is not + it will add it + + function terminates upon getting an entry that is a file object ( or really anything but string) + """ + currentitem = categories[0] + found = False + if isinstance(currentitem,str): + for item in output: + if item.level == currentitem: + found = True + item.filecount = item.filecount + 1 + item.expanded = expanded + ensure_exists(item.children,categories[1:],expanded) + if not found: + newlevel = DocumentDirectory(level=currentitem) + newlevel.filecount = 1 + newlevel.expanded = expanded + output.append(newlevel) + ensure_exists(newlevel.children,categories[1:],expanded) + else: + newlevel = DocumentDirectory(file=currentitem) + newlevel.expanded = expanded + output.append(newlevel) + + +def get_document_directory(study_id,workflow_id=None): + """ + return a nested list of files arranged according to the category hirearchy + defined in the doc dictionary + """ + output = [] + doc_dict = FileService.get_doc_dictionary() + file_models = FileService.get_files_for_study(study_id=study_id) + files = (to_file_api(model) for model in file_models) + for file in files: + doc_code = doc_dict[file.irb_doc_code] + if workflow_id: + expand = file.workflow_id == int(workflow_id) + else: + expand = False + print(expand) + categories = [x for x in [doc_code['category1'],doc_code['category2'],doc_code['category3'],file] if x != ''] + ensure_exists(output,categories,expanded=expand) + return DocumentDirectorySchema(many=True).dump(output) + def to_file_api(file_model): """Converts a FileModel object to something we can return via the api""" diff --git a/crc/models/api_models.py b/crc/models/api_models.py index 74913bef..74c0bc2b 100644 --- a/crc/models/api_models.py +++ b/crc/models/api_models.py @@ -7,7 +7,7 @@ from marshmallow_enum import EnumField from crc import ma from crc.models.workflow import WorkflowStatus - +from crc.models.file import FileSchema class MultiInstanceType(enum.Enum): none = "none" @@ -167,6 +167,26 @@ class NavigationItemSchema(ma.Schema): item.spec_type = spec_type return item +class DocumentDirectorySchema(ma.Schema): + level = marshmallow.fields.String() + file = marshmallow.fields.Nested(FileSchema) + filecount = marshmallow.fields.Integer() + expanded = marshmallow.fields.Boolean() + children = marshmallow.fields.Nested("self",many=True) + +class DocumentDirectory(object): + def __init__(self, level=None, file=None, children=None): + + self.level = level + self.file = file + self.expanded = False + self.filecount = 0 + if children is None: + self.children = list() + else: + self.children=children + + class WorkflowApi(object): def __init__(self, id, status, next_task, navigation, spec_version, is_latest_spec, workflow_spec_id, total_tasks, completed_tasks, diff --git a/crc/models/file.py b/crc/models/file.py index b1269f78..5be8bccd 100644 --- a/crc/models/file.py +++ b/crc/models/file.py @@ -108,7 +108,7 @@ class File(object): doc_dictionary[model.irb_doc_code]['category2'], doc_dictionary[model.irb_doc_code]['category3']])) instance.description = doc_dictionary[model.irb_doc_code]['description'] - instance.download_name = ".".join([instance.category, model.type.value]) + instance.download_name = "/".join([instance.category, model.name]) else: instance.category = "" instance.description = "" diff --git a/crc/services/file_service.py b/crc/services/file_service.py index 805a8e81..14e1b20e 100644 --- a/crc/services/file_service.py +++ b/crc/services/file_service.py @@ -17,6 +17,21 @@ from crc.api.common import ApiError from crc.models.file import FileType, FileDataModel, FileModel, LookupFileModel, LookupDataModel from crc.models.workflow import WorkflowSpecModel, WorkflowModel, WorkflowSpecDependencyFile from crc.services.cache_service import cache +import re + + +def camel_to_snake(camel): + """ + make a camelcase from a snakecase + with a few things thrown in - we had a case where + we were parsing a spreadsheet and using the headings as keys in an object + one of the headings was "Who Uploads?" + """ + camel = camel.strip() + camel = re.sub(' ', '', camel) + camel = re.sub('?', '', camel) + return re.sub(r'(? Date: Tue, 20 Apr 2021 11:11:11 -0400 Subject: [PATCH 3/4] Fix slight bug in expansion code, a non-expanded node was causing all parent levels to not expand. --- crc/api/file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crc/api/file.py b/crc/api/file.py index edbd9cf8..1fd8349a 100644 --- a/crc/api/file.py +++ b/crc/api/file.py @@ -28,7 +28,7 @@ def ensure_exists(output,categories,expanded): if item.level == currentitem: found = True item.filecount = item.filecount + 1 - item.expanded = expanded + item.expanded = expanded | item.expanded ensure_exists(item.children,categories[1:],expanded) if not found: newlevel = DocumentDirectory(level=currentitem) From ecff7d20d3ff085a1da4fc948bdf39de8eb7bf83 Mon Sep 17 00:00:00 2001 From: Dan Date: Wed, 21 Apr 2021 14:00:19 -0400 Subject: [PATCH 4/4] Adding a test for the new endpoint and cleaning up the API documenation --- crc/api.yml | 45 ++- crc/api/file.py | 31 +- crc/models/api_models.py | 1 + postgres/package-lock.json | 480 ++++++++++++++++++++++++++++- tests/test_document_directories.py | 41 +++ 5 files changed, 556 insertions(+), 42 deletions(-) create mode 100644 tests/test_document_directories.py diff --git a/crc/api.yml b/crc/api.yml index 289ad153..24e4efdf 100644 --- a/crc/api.yml +++ b/crc/api.yml @@ -94,11 +94,9 @@ paths: content: application/json: schema: - type: object - properties: - Admin: - schema: - $ref: "#/components/schemas/DocumentCategories" + type: array + items: + $ref: "#/components/schemas/DocumentDirectory" # /v1.0/study /study: @@ -1462,34 +1460,29 @@ components: type: string x-nullable: true example: "27b-6-1212" - DocumentCategories: + DocumentDirectory: properties: - category1: + level: type: string x-nullable: true example: "Ancillary Document" - category2: - type: string - x-nullable: true - example: "Lab Manual" - category3: - type: string - x-nullable: true - example: "" - Who Uploads?: - type: string - x-nullable: true - example: "CRC" description: type: string x-nullable: true - example: "some description" - - - - - - + example: "Documents that are ancillary to the study" + file: + $ref: "#/components/schemas/File" + x-nullable: true + expanded: + type: boolean + example: False + filecount: + type: integer + example: 1 + children: + type: array + items: + $ref: "#/components/schemas/File" DataStore: properties: id: diff --git a/crc/api/file.py b/crc/api/file.py index 1fd8349a..33977533 100644 --- a/crc/api/file.py +++ b/crc/api/file.py @@ -11,7 +11,8 @@ from crc.models.api_models import DocumentDirectory, DocumentDirectorySchema from crc.models.workflow import WorkflowSpecModel from crc.services.file_service import FileService -def ensure_exists(output,categories,expanded): + +def ensure_exists(output, categories, expanded): """ This is a recursive function, it expects a list of levels with a file object at the end (kinda like duck,duck,duck,goose) @@ -21,28 +22,28 @@ def ensure_exists(output,categories,expanded): function terminates upon getting an entry that is a file object ( or really anything but string) """ - currentitem = categories[0] + current_item = categories[0] found = False - if isinstance(currentitem,str): + if isinstance(current_item, str): for item in output: - if item.level == currentitem: + if item.level == current_item: found = True item.filecount = item.filecount + 1 item.expanded = expanded | item.expanded - ensure_exists(item.children,categories[1:],expanded) + ensure_exists(item.children, categories[1:], expanded) if not found: - newlevel = DocumentDirectory(level=currentitem) - newlevel.filecount = 1 - newlevel.expanded = expanded - output.append(newlevel) - ensure_exists(newlevel.children,categories[1:],expanded) + new_level = DocumentDirectory(level=current_item) + new_level.filecount = 1 + new_level.expanded = expanded + output.append(new_level) + ensure_exists(new_level.children, categories[1:], expanded) else: - newlevel = DocumentDirectory(file=currentitem) - newlevel.expanded = expanded - output.append(newlevel) + new_level = DocumentDirectory(file=current_item) + new_level.expanded = expanded + output.append(new_level) -def get_document_directory(study_id,workflow_id=None): +def get_document_directory(study_id, workflow_id=None): """ return a nested list of files arranged according to the category hirearchy defined in the doc dictionary @@ -59,7 +60,7 @@ def get_document_directory(study_id,workflow_id=None): expand = False print(expand) categories = [x for x in [doc_code['category1'],doc_code['category2'],doc_code['category3'],file] if x != ''] - ensure_exists(output,categories,expanded=expand) + ensure_exists(output, categories, expanded=expand) return DocumentDirectorySchema(many=True).dump(output) diff --git a/crc/models/api_models.py b/crc/models/api_models.py index 74c0bc2b..2bb4abc6 100644 --- a/crc/models/api_models.py +++ b/crc/models/api_models.py @@ -174,6 +174,7 @@ class DocumentDirectorySchema(ma.Schema): expanded = marshmallow.fields.Boolean() children = marshmallow.fields.Nested("self",many=True) + class DocumentDirectory(object): def __init__(self, level=None, file=None, children=None): diff --git a/postgres/package-lock.json b/postgres/package-lock.json index 48e341a0..88665a72 100644 --- a/postgres/package-lock.json +++ b/postgres/package-lock.json @@ -1,3 +1,481 @@ { - "lockfileVersion": 1 + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "@types/node": { + "version": "14.14.41", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.41.tgz", + "integrity": "sha512-dueRKfaJL4RTtSa7bWeTK1M+VH+Gns73oCgzvYfHZywRCoPSd8EkXBL0mZ9unPTveBn+D9phZBaxuzpwjWkW0g==", + "dev": true, + "optional": true + }, + "@types/yauzl": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", + "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", + "dev": true, + "optional": true, + "requires": { + "@types/node": "*" + } + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "requires": { + "debug": "4" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "devtools-protocol": { + "version": "0.0.854822", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.854822.tgz", + "integrity": "sha512-xd4D8kHQtB0KtWW0c9xBZD5LVtm9chkMOfs/3Yn01RhT/sFIsVtzTtypfKoFfWBaL+7xCYLxjOLkhwPXaX/Kcg==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "requires": { + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + } + }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "requires": { + "pend": "~1.2.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "karma-chrome-launcher": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.0.tgz", + "integrity": "sha512-3dPs/n7vgz1rxxtynpzZTvb9y/GIaW8xjAwcIGttLbycqoFtI7yo1NGnQi6oFTherRE+GIhCAHZC4vEqWGhNvg==", + "dev": true, + "requires": { + "which": "^1.2.1" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "puppeteer": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-8.0.0.tgz", + "integrity": "sha512-D0RzSWlepeWkxPPdK3xhTcefj8rjah1791GE82Pdjsri49sy11ci/JQsAO8K2NRukqvwEtcI+ImP5F4ZiMvtIQ==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "devtools-protocol": "0.0.854822", + "extract-zip": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "pkg-dir": "^4.2.0", + "progress": "^2.0.1", + "proxy-from-env": "^1.1.0", + "rimraf": "^3.0.2", + "tar-fs": "^2.0.0", + "unbzip2-stream": "^1.3.3", + "ws": "^7.2.3" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "requires": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "ws": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz", + "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==", + "dev": true + }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + } + } } diff --git a/tests/test_document_directories.py b/tests/test_document_directories.py new file mode 100644 index 00000000..151729d9 --- /dev/null +++ b/tests/test_document_directories.py @@ -0,0 +1,41 @@ +import json +from tests.base_test import BaseTest +from crc.services.file_service import FileService + + + + + +class TestDocumentDirectories(BaseTest): + + def test_directory_list(self): + self.load_example_data() + irb_code_1 = 'UVACompl_PRCAppr' + irb_code_2 = 'Study_App_Doc' + + workflow = self.create_workflow('empty_workflow') + first_task = self.get_workflow_api(workflow).next_task + study_id = workflow.study_id + + # Add a file + FileService.add_workflow_file(workflow_id=workflow.id, + name="something.png", content_type="text", + binary_data=b'1234', irb_doc_code=irb_code_1) + # Add second file + FileService.add_workflow_file(workflow_id=workflow.id, + name="anything.png", content_type="text", + binary_data=b'5678', irb_doc_code=irb_code_2) + + # Get back the list of documents and their directories. + rv = self.app.get('/v1.0/document_directory/%i' % study_id, headers=self.logged_in_headers()) + self.assert_success(rv) + json_data = json.loads(rv.get_data(as_text=True)) + print(json_data) + self.assertEquals(2, len(json_data)) + self.assertEquals('UVA Compliance', json_data[0]['level']) + self.assertEquals('PRC Approval', json_data[0]['children'][0]['level']) + self.assertEquals('something.png', json_data[0]['children'][0]['children'][0]['file']['name']) + self.assertEquals('Study', json_data[1]['level']) + self.assertEquals('Application', json_data[1]['children'][0]['level']) + self.assertEquals('Document', json_data[1]['children'][0]['children'][0]['level']) + self.assertEquals('anything.png', json_data[1]['children'][0]['children'][0]['children'][0]['file']['name'])