diff --git a/docs/openapi.json b/docs/openapi.json index 2b74a24b..325d8d7b 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -337,6 +337,81 @@ "summary": "Returns the list of input files for a specific workflow." } }, + "/api/workflows/{workflow_id}/workspace/outputs": { + "get": { + "description": "This resource is expecting a workflow UUID and a filename to return its outputs.", + "operationId": "get_workflow_outputs", + "parameters": [ + { + "description": "Required. Organization which the worklow belongs to.", + "in": "query", + "name": "organization", + "required": true, + "type": "string" + }, + { + "description": "Required. UUID of workflow owner.", + "in": "query", + "name": "user", + "required": true, + "type": "string" + }, + { + "description": "Required. Workflow UUID.", + "in": "path", + "name": "workflow_id", + "required": true, + "type": "string" + } + ], + "produces": [ + "multipart/form-data" + ], + "responses": { + "200": { + "description": "Requests succeeded. The list of output files has been retrieved.", + "schema": { + "items": { + "properties": { + "last-modified": { + "format": "date-time", + "type": "string" + }, + "name": { + "type": "string" + }, + "size": { + "type": "integer" + } + }, + "type": "object" + }, + "type": "array" + } + }, + "400": { + "description": "Request failed. The incoming data specification seems malformed." + }, + "404": { + "description": "Request failed. User doesn't exist.", + "examples": { + "application/json": { + "message": "User 00000000-0000-0000-0000-000000000000 doesn't exist" + } + } + }, + "500": { + "description": "Request failed. Internal controller error.", + "examples": { + "application/json": { + "message": "Either organization or user doesn't exist." + } + } + } + }, + "summary": "Returns the list of output files for a specific workflow." + } + }, "/api/workflows/{workflow_id}/workspace/outputs/{file_name}": { "get": { "description": "This resource is expecting a workflow UUID and a filename to return its content.", diff --git a/reana_workflow_controller/config.py b/reana_workflow_controller/config.py index c5f26ced..3620ab8c 100644 --- a/reana_workflow_controller/config.py +++ b/reana_workflow_controller/config.py @@ -40,3 +40,6 @@ INPUTS_RELATIVE_PATH = 'inputs' """Inputs directory name.""" + +OUTPUTS_RELATIVE_PATH = 'outputs' +"""Outputs directory name.""" diff --git a/reana_workflow_controller/rest.py b/reana_workflow_controller/rest.py index 1c6c6e16..77c2aeb7 100644 --- a/reana_workflow_controller/rest.py +++ b/reana_workflow_controller/rest.py @@ -543,6 +543,99 @@ def get_workflow_inputs(workflow_id): # noqa return jsonify({"message": str(e)}), 500 +@restapi_blueprint.route('/workflows//workspace/outputs', + methods=['GET']) +def get_workflow_outputs(workflow_id): # noqa + r"""List all workflow output files. + + --- + get: + summary: Returns the list of output files for a specific workflow. + description: >- + This resource is expecting a workflow UUID and a filename to return + its outputs. + operationId: get_workflow_outputs + produces: + - multipart/form-data + parameters: + - name: organization + in: query + description: Required. Organization which the worklow belongs to. + required: true + type: string + - name: user + in: query + description: Required. UUID of workflow owner. + required: true + type: string + - name: workflow_id + in: path + description: Required. Workflow UUID. + required: true + type: string + responses: + 200: + description: >- + Requests succeeded. The list of output files has been retrieved. + schema: + type: array + items: + type: object + properties: + name: + type: string + last-modified: + type: string + format: date-time + size: + type: integer + 400: + description: >- + Request failed. The incoming data specification seems malformed. + 404: + description: >- + Request failed. User doesn't exist. + examples: + application/json: + { + "message": "User 00000000-0000-0000-0000-000000000000 doesn't + exist" + } + 500: + description: >- + Request failed. Internal controller error. + examples: + application/json: + { + "message": "Either organization or user doesn't exist." + } + """ + try: + user_uuid = request.args['user'] + user = User.query.filter(User.id_ == user_uuid).first() + if not user: + return jsonify( + {'message': 'User {} does not exist'.format(user)}), 404 + + workflow = Workflow.query.filter(Workflow.id_ == workflow_id).first() + if workflow: + outputs_directory = os.path.join( + current_app.config['SHARED_VOLUME_PATH'], + workflow.workspace_path, + current_app.config['OUTPUTS_RELATIVE_PATH']) + + outputs_list = list_directory_files(outputs_directory) + return jsonify(outputs_list), 200 + else: + return jsonify({'message': 'The workflow {} doesn\'t exist'. + format(str(workflow.id_))}), 404 + + except KeyError: + return jsonify({"message": "Malformed request."}), 400 + except Exception as e: + return jsonify({"message": str(e)}), 500 + + @restapi_blueprint.route('/yadage/remote', methods=['POST']) def run_yadage_workflow_from_remote_endpoint(): # noqa r"""Create a new yadage workflow from a remote repository. diff --git a/tests/test_views.py b/tests/test_views.py index 8e493c9d..d20e61bb 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -318,3 +318,52 @@ def test_get_workflow_inputs_list(app, db_session, default_user, data=json.dumps(data)) for file_ in json.loads(res.data.decode()): assert file_.get('name') in test_files + + +def test_get_workflow_outputs_list(app, db_session, default_user, + tmp_shared_volume_path): + """Test get list of output files.""" + with app.test_client() as client: + # create workflow + organization = 'default' + data = {'parameters': {'min_year': '1991', + 'max_year': '2001'}, + 'specification': {'first': 'do this', + 'second': 'do that'}, + 'type': 'cwl'} + res = client.post(url_for('api.create_workflow'), + query_string={ + "user": default_user.id_, + "organization": organization}, + content_type='application/json', + data=json.dumps(data)) + + response_data = json.loads(res.get_data(as_text=True)) + workflow_uuid = response_data.get('workflow_id') + workflow = Workflow.query.filter( + Workflow.id_ == workflow_uuid).first() + # create file + absolute_path_workflow_workspace = \ + os.path.join(tmp_shared_volume_path, + workflow.workspace_path) + fs_ = fs.open_fs(absolute_path_workflow_workspace) + # from config + outputs_realative_path = app.config['OUTPUTS_RELATIVE_PATH'] + fs_.makedirs(outputs_realative_path) + test_files = [] + for i in range(5): + file_name = '{0}.csv'.format(i) + subdir_name = str(uuid.uuid4()) + subdir = fs.path.join(outputs_realative_path, subdir_name) + fs_.makedirs(subdir) + fs_.touch('{0}/{1}'.format(subdir, file_name)) + test_files.append(os.path.join(subdir_name, file_name)) + + res = client.get( + url_for('api.get_workflow_outputs', workflow_id=workflow_uuid), + query_string={"user": default_user.id_, + "organization": organization}, + content_type='application/json', + data=json.dumps(data)) + for file_ in json.loads(res.data.decode()): + assert file_.get('name') in test_files