diff --git a/docs/openapi.json b/docs/openapi.json index 7b5eddc5..e46ea3fd 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -1098,6 +1098,139 @@ "summary": "Share a workflow with other users." } }, + "/api/workflows/{workflow_id_or_name}/share-status": { + "get": { + "description": "This resource returns the share status of a given workflow.", + "operationId": "get_workflow_share_status", + "parameters": [ + { + "description": "Required. UUID of workflow owner.", + "in": "query", + "name": "user_id", + "required": true, + "type": "string" + }, + { + "description": "Required. Workflow UUID or name.", + "in": "path", + "name": "workflow_id_or_name", + "required": true, + "type": "string" + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "Request succeeded. The response contains the share status of the workflow.", + "examples": { + "application/json": { + "shared_with": [ + { + "user_email": "bob@example.org", + "valid_until": "2022-11-24T23:59:59" + } + ], + "workflow_id": "256b25f4-4cfb-4684-b7a8-73872ef455a1", + "workflow_name": "mytest.1" + } + }, + "schema": { + "properties": { + "shared_with": { + "items": { + "properties": { + "user_email": { + "type": "string" + }, + "valid_until": { + "type": "string", + "x-nullable": true + } + }, + "type": "object" + }, + "type": "array" + }, + "workflow_id": { + "type": "string" + }, + "workflow_name": { + "type": "string" + } + }, + "type": "object" + } + }, + "401": { + "description": "Request failed. User not signed in.", + "examples": { + "application/json": { + "message": "User not signed in." + } + }, + "schema": { + "properties": { + "message": { + "type": "string" + } + }, + "type": "object" + } + }, + "403": { + "description": "Request failed. Credentials are invalid or revoked.", + "examples": { + "application/json": { + "message": "Token not valid." + } + }, + "schema": { + "properties": { + "message": { + "type": "string" + } + }, + "type": "object" + } + }, + "404": { + "description": "Request failed. Workflow does not exist.", + "examples": { + "application/json": { + "message": "Workflow mytest.1 does not exist." + } + }, + "schema": { + "properties": { + "message": { + "type": "string" + } + }, + "type": "object" + } + }, + "500": { + "description": "Request failed. Internal server error.", + "examples": { + "application/json": { + "message": "Something went wrong." + } + }, + "schema": { + "properties": { + "message": { + "type": "string" + } + }, + "type": "object" + } + } + }, + "summary": "Get the share status of a workflow." + } + }, "/api/workflows/{workflow_id_or_name}/status": { "get": { "description": "This resource reports the status of workflow.", diff --git a/reana_workflow_controller/rest/workflows.py b/reana_workflow_controller/rest/workflows.py index d4e0276c..216d427d 100644 --- a/reana_workflow_controller/rest/workflows.py +++ b/reana_workflow_controller/rest/workflows.py @@ -1292,3 +1292,155 @@ def unshare_workflow( except Exception as e: logging.exception(str(e)) return jsonify({"message": str(e)}), 500 + + +@blueprint.route("/workflows//share-status", methods=["GET"]) +@use_kwargs( + { + "user_id": fields.Str(required=True), + }, + location="query", +) +def get_workflow_share_status( + workflow_id_or_name: str, + user_id: str, +): + r"""Get the share status of a workflow. + + --- + get: + summary: Get the share status of a workflow. + description: >- + This resource returns the share status of a given workflow. + operationId: get_workflow_share_status + produces: + - application/json + parameters: + - name: user_id + in: query + description: Required. UUID of workflow owner. + required: true + type: string + - name: workflow_id_or_name + in: path + description: Required. Workflow UUID or name. + required: true + type: string + responses: + 200: + description: >- + Request succeeded. The response contains the share status of the workflow. + schema: + type: object + properties: + workflow_id: + type: string + workflow_name: + type: string + shared_with: + type: array + items: + type: object + properties: + user_email: + type: string + valid_until: + type: string + x-nullable: true + examples: + application/json: + { + "workflow_id": "256b25f4-4cfb-4684-b7a8-73872ef455a1", + "workflow_name": "mytest.1", + "shared_with": [ + { + "user_email": "bob@example.org", + "valid_until": "2022-11-24T23:59:59" + } + ] + } + 401: + description: >- + Request failed. User not signed in. + schema: + type: object + properties: + message: + type: string + examples: + application/json: + { + "message": "User not signed in." + } + 403: + description: >- + Request failed. Credentials are invalid or revoked. + schema: + type: object + properties: + message: + type: string + examples: + application/json: + { + "message": "Token not valid." + } + 404: + description: >- + Request failed. Workflow does not exist. + schema: + type: object + properties: + message: + type: string + examples: + application/json: + { + "message": "Workflow mytest.1 does not exist." + } + 500: + description: >- + Request failed. Internal server error. + schema: + type: object + properties: + message: + type: string + examples: + application/json: + { + "message": "Something went wrong." + } + """ + try: + workflow = _get_workflow_with_uuid_or_name(workflow_id_or_name, user_id) + + shared_with = ( + Session.query(UserWorkflow) + .filter_by(workflow_id=workflow.id_) + .join(User, User.id_ == UserWorkflow.user_id) + .add_columns(User.email, UserWorkflow.valid_until) + .all() + ) + + response = { + "workflow_id": workflow.id_, + "workflow_name": workflow.get_full_workflow_name(), + "shared_with": [ + { + "user_email": share[1], + "valid_until": share[2].strftime("%Y-%m-%dT%H:%M:%S") + if share[2] + else None, + } + for share in shared_with + ], + } + + return jsonify(response), 200 + except ValueError as e: + logging.exception(str(e)) + return jsonify({"message": str(e)}), 404 + except Exception as e: + logging.exception(str(e)) + return jsonify({"message": str(e)}), 500 diff --git a/tests/test_views.py b/tests/test_views.py index 3f108977..3bc1bf0e 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -2333,3 +2333,121 @@ def test_unshare_workflow_with_message_and_valid_until( assert ( response_data["message"] == "The workflow has been unshared with the user." ) + + +def test_get_workflow_share_status( + app, user1, user2, sample_serial_workflow_in_db_owned_by_user1 +): + """Test get_workflow_share_status.""" + workflow = sample_serial_workflow_in_db_owned_by_user1 + with app.test_client() as client: + # share workflow + client.post( + url_for( + "workflows.share_workflow", + workflow_id_or_name=str(workflow.id_), + ), + query_string={ + "user_id": str(user1.id_), + "user_email_to_share_with": user2.email, + }, + ) + # get workflow share status + res = client.get( + url_for( + "workflows.get_workflow_share_status", + workflow_id_or_name=str(workflow.id_), + ), + query_string={ + "user_id": str(user1.id_), + "user_email_to_check": user2.email, + }, + ) + assert res.status_code == 200 + response_data = res.get_json() + assert response_data["shared_with"][0]["user_email"] == "user2@reana.io" + + +def test_get_workflow_share_status_not_shared( + app, user1, user2, sample_serial_workflow_in_db_owned_by_user1 +): + """Test get_workflow_share_status for a workflow that is not shared.""" + workflow = sample_serial_workflow_in_db_owned_by_user1 + with app.test_client() as client: + # get workflow share status + res = client.get( + url_for( + "workflows.get_workflow_share_status", + workflow_id_or_name=str(workflow.id_), + ), + query_string={ + "user_id": str(user1.id_), + }, + ) + assert res.status_code == 200 + response_data = res.get_json() + assert response_data["shared_with"] == [] + + +def test_get_workflow_share_status_valid_until_not_set( + app, user1, user2, sample_serial_workflow_in_db_owned_by_user1 +): + workflow = sample_serial_workflow_in_db_owned_by_user1 + with app.test_client() as client: + # Share the workflow without setting valid_until + client.post( + url_for( + "workflows.share_workflow", + workflow_id_or_name=str(workflow.id_), + ), + query_string={ + "user_id": str(user1.id_), + "user_email_to_share_with": user2.email, + }, + ) + res = client.get( + url_for( + "workflows.get_workflow_share_status", + workflow_id_or_name=str(workflow.id_), + ), + query_string={ + "user_id": str(user1.id_), + }, + ) + assert res.status_code == 200 + response_data = res.get_json() + shared_with = response_data["shared_with"][0] + assert shared_with["valid_until"] is None + + +def test_get_workflow_share_status_valid_until_set( + app, user1, user2, sample_serial_workflow_in_db_owned_by_user1 +): + workflow = sample_serial_workflow_in_db_owned_by_user1 + valid_until = "2023-12-31" + with app.test_client() as client: + # Share the workflow setting valid_until + client.post( + url_for( + "workflows.share_workflow", + workflow_id_or_name=str(workflow.id_), + ), + query_string={ + "user_id": str(user1.id_), + "user_email_to_share_with": user2.email, + "valid_until": valid_until, + }, + ) + res = client.get( + url_for( + "workflows.get_workflow_share_status", + workflow_id_or_name=str(workflow.id_), + ), + query_string={ + "user_id": str(user1.id_), + }, + ) + assert res.status_code == 200 + response_data = res.get_json() + shared_with = response_data["shared_with"][0] + assert shared_with["valid_until"] == valid_until + "T00:00:00"