From 50495fc8a2eca691357eaa307ed3580ed7aac91d Mon Sep 17 00:00:00 2001 From: Marco Donadoni Date: Thu, 22 Aug 2024 15:28:22 +0200 Subject: [PATCH] fix(rest): fix workflow sharing endpoints after changes (#658) --- docs/openapi.json | 109 +++++++++++++-------------------- reana_server/rest/users.py | 41 ++----------- reana_server/rest/workflows.py | 76 ++++++++++++----------- requirements.txt | 2 +- setup.py | 4 +- tests/test_views.py | 10 +-- 6 files changed, 94 insertions(+), 148 deletions(-) diff --git a/docs/openapi.json b/docs/openapi.json index 2e5de75c..d7444d3a 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -1332,9 +1332,7 @@ "application/json": { "users_shared_with_you": [ { - "email": "john.doe@example.org", - "full_name": "John Doe", - "username": "jdoe" + "email": "john.doe@example.org" } ] } @@ -1346,12 +1344,6 @@ "properties": { "email": { "type": "string" - }, - "full_name": { - "type": "string" - }, - "username": { - "type": "string" } }, "type": "object" @@ -1437,9 +1429,7 @@ "application/json": { "users_you_shared_with": [ { - "email": "john.doe@example.org", - "full_name": "John Doe", - "username": "jdoe" + "email": "john.doe@example.org" } ] } @@ -1451,12 +1441,6 @@ "properties": { "email": { "type": "string" - }, - "full_name": { - "type": "string" - }, - "username": { - "type": "string" } }, "type": "object" @@ -1610,14 +1594,14 @@ "type": "boolean" }, { - "description": "Optional argument to list workflows shared by the specified user(s).", + "description": "Optional argument to list workflows shared by the specified user.", "in": "query", "name": "shared_by", "required": false, "type": "string" }, { - "description": "Optional argument to list workflows shared with the specified user(s).", + "description": "Optional argument to list workflows shared with the specified user.", "in": "query", "name": "shared_with", "required": false, @@ -1790,7 +1774,10 @@ "type": "string" }, "shared_with": { - "type": "string" + "items": { + "type": "string" + }, + "type": "array" }, "size": { "properties": { @@ -3351,7 +3338,15 @@ } }, "400": { - "description": "Request failed. The incoming data seems malformed." + "description": "Request failed. The incoming data seems malformed.", + "schema": { + "properties": { + "message": { + "type": "string" + } + }, + "type": "object" + } }, "401": { "description": "Request failed. User not signed in.", @@ -3391,6 +3386,14 @@ "application/json": { "message": "Workflow cdcf48b1-c2f3-4693-8230-b066e088c6ac does not exist" } + }, + "schema": { + "properties": { + "message": { + "type": "string" + } + }, + "type": "object" } }, "409": { @@ -3399,6 +3402,14 @@ "application/json": { "message": "The workflow is already shared with the user." } + }, + "schema": { + "properties": { + "message": { + "type": "string" + } + }, + "type": "object" } }, "500": { @@ -3407,6 +3418,14 @@ "application/json": { "message": "Internal controller error." } + }, + "schema": { + "properties": { + "message": { + "type": "string" + } + }, + "type": "object" } } }, @@ -4397,20 +4416,11 @@ "description": "Request failed. The incoming data specification seems malformed.", "examples": { "application/json": { - "errors": [ - "Missing data for required field." - ], "message": "Malformed request." } }, "schema": { "properties": { - "errors": { - "items": { - "type": "string" - }, - "type": "array" - }, "message": { "type": "string" } @@ -4422,19 +4432,11 @@ "description": "Request failed. User is not allowed to unshare the workflow.", "examples": { "application/json": { - "errors": [ - "User is not allowed to unshare the workflow." - ] + "message": "User is not allowed to unshare the workflow." } }, "schema": { "properties": { - "errors": { - "items": { - "type": "string" - }, - "type": "array" - }, "message": { "type": "string" } @@ -4446,20 +4448,11 @@ "description": "Request failed. Workflow does not exist or user does not exist.", "examples": { "application/json": { - "errors": [ - "Workflow cdcf48b1-c2f3-4693-8230-b066e088c6ac does not exist" - ], "message": "Workflow cdcf48b1-c2f3-4693-8230-b066e088c6ac does not exist" } }, "schema": { "properties": { - "errors": { - "items": { - "type": "string" - }, - "type": "array" - }, "message": { "type": "string" } @@ -4471,20 +4464,11 @@ "description": "Request failed. The workflow is not shared with the user.", "examples": { "application/json": { - "errors": [ - "The workflow is not shared with the user." - ], "message": "The workflow is not shared with the user." } }, "schema": { "properties": { - "errors": { - "items": { - "type": "string" - }, - "type": "array" - }, "message": { "type": "string" } @@ -4496,20 +4480,11 @@ "description": "Request failed. Internal controller error.", "examples": { "application/json": { - "errors": [ - "Internal controller error." - ], "message": "Internal controller error." } }, "schema": { "properties": { - "errors": { - "items": { - "type": "string" - }, - "type": "array" - }, "message": { "type": "string" } diff --git a/reana_server/rest/users.py b/reana_server/rest/users.py index 7bd726f8..6efaf67c 100644 --- a/reana_server/rest/users.py +++ b/reana_server/rest/users.py @@ -392,18 +392,12 @@ def get_users_shared_with_you(user): properties: email: type: string - full_name: - type: string - username: - type: string examples: application/json: { "users_shared_with_you": [ { "email": "john.doe@example.org", - "full_name": "John Doe", - "username": "jdoe" } ] } @@ -457,27 +451,16 @@ def get_users_shared_with_you(user): shared_workflow_owners_ids = ( Session.query(Workflow.owner_id) .filter(Workflow.id_.in_(shared_workflows_ids)) - .distinct() .subquery() ) users = ( - Session.query(User.email, User.full_name, User.username) + Session.query(User.email) .filter(User.id_.in_(shared_workflow_owners_ids)) .all() ) - response = {"users_shared_with_you": []} - - for email, full_name, username in users: - response["users_shared_with_you"].append( - { - "email": email, - "full_name": full_name, - "username": username, - } - ) - + response = {"users_shared_with_you": [{"email": user.email} for user in users]} return jsonify(response), 200 except HTTPError as e: logging.error(traceback.format_exc()) @@ -524,18 +507,12 @@ def get_users_you_shared_with(user): properties: email: type: string - full_name: - type: string - username: - type: string examples: application/json: { "users_you_shared_with": [ { "email": "john.doe@example.org", - "full_name": "John Doe", - "username": "jdoe" } ] } @@ -592,22 +569,12 @@ def get_users_you_shared_with(user): ) users = ( - Session.query(User.email, User.full_name, User.username) + Session.query(User.email) .filter(User.id_.in_(users_you_shared_with_ids)) .all() ) - response = {"users_you_shared_with": []} - - for email, full_name, username in users: - response["users_you_shared_with"].append( - { - "email": email, - "full_name": full_name, - "username": username, - } - ) - + response = {"users_you_shared_with": [{"email": user.email} for user in users]} return jsonify(response), 200 except HTTPError as e: logging.error(traceback.format_exc()) diff --git a/reana_server/rest/workflows.py b/reana_server/rest/workflows.py index daba6dc0..fad86ecf 100644 --- a/reana_server/rest/workflows.py +++ b/reana_server/rest/workflows.py @@ -149,12 +149,12 @@ def get_workflows(user, **kwargs): # noqa type: boolean - name: shared_by in: query - description: Optional argument to list workflows shared by the specified user(s). + description: Optional argument to list workflows shared by the specified user. required: false type: string - name: shared_with in: query - description: Optional argument to list workflows shared with the specified user(s). + description: Optional argument to list workflows shared with the specified user. required: false type: string responses: @@ -192,7 +192,9 @@ def get_workflows(user, **kwargs): # noqa owner_email: type: string shared_with: - type: string + type: array + items: + type: string created: type: string session_status: @@ -3289,7 +3291,14 @@ def prune_workspace( @blueprint.route("/workflows//share", methods=["POST"]) @signin_required() -def share_workflow(workflow_id_or_name, user): +@use_kwargs( + { + "user_email_to_share_with": fields.Str(required=True, location="json"), + "message": fields.Str(location="json"), + "valid_until": fields.Str(location="json"), + }, +) +def share_workflow(workflow_id_or_name, user, **kwargs): r"""Share a workflow with another user. --- @@ -3352,6 +3361,11 @@ def share_workflow(workflow_id_or_name, user): 400: description: >- Request failed. The incoming data seems malformed. + schema: + type: object + properties: + message: + type: string 401: description: >- Request failed. User not signed in. @@ -3381,6 +3395,11 @@ def share_workflow(workflow_id_or_name, user): 404: description: >- Request failed. Workflow does not exist or user does not exist. + schema: + type: object + properties: + message: + type: string examples: application/json: { @@ -3390,6 +3409,11 @@ def share_workflow(workflow_id_or_name, user): 409: description: >- Request failed. The workflow is already shared with the user. + schema: + type: object + properties: + message: + type: string examples: application/json: { @@ -3398,6 +3422,11 @@ def share_workflow(workflow_id_or_name, user): 500: description: >- Request failed. Internal controller error. + schema: + type: object + properties: + message: + type: string examples: application/json: { @@ -3408,7 +3437,7 @@ def share_workflow(workflow_id_or_name, user): response, http_response = current_rwc_api_client.api.share_workflow( workflow_id_or_name=workflow_id_or_name, user=str(user.id_), - share_details=request.json, + share_details=kwargs, ).result() return jsonify(response), 200 @@ -3484,15 +3513,10 @@ def unshare_workflow(workflow_id_or_name, user, user_email_to_unshare_with): properties: message: type: string - errors: - type: array - items: - type: string examples: application/json: { - "message": "Malformed request.", - "errors": ["Missing data for required field."] + "message": "Malformed request." } 403: description: >- @@ -3502,14 +3526,10 @@ def unshare_workflow(workflow_id_or_name, user, user_email_to_unshare_with): properties: message: type: string - errors: - type: array - items: - type: string examples: application/json: { - "errors": ["User is not allowed to unshare the workflow."] + "message": "User is not allowed to unshare the workflow." } 404: description: >- @@ -3519,17 +3539,11 @@ def unshare_workflow(workflow_id_or_name, user, user_email_to_unshare_with): properties: message: type: string - errors: - type: array - items: - type: string examples: application/json: { "message": "Workflow cdcf48b1-c2f3-4693-8230-b066e088c6ac does not exist", - "errors": ["Workflow cdcf48b1-c2f3-4693-8230-b066e088c6ac does - not exist"] } 409: description: >- @@ -3539,15 +3553,10 @@ def unshare_workflow(workflow_id_or_name, user, user_email_to_unshare_with): properties: message: type: string - errors: - type: array - items: - type: string examples: application/json: { - "message": "The workflow is not shared with the user.", - "errors": ["The workflow is not shared with the user."] + "message": "The workflow is not shared with the user." } 500: description: >- @@ -3557,22 +3566,17 @@ def unshare_workflow(workflow_id_or_name, user, user_email_to_unshare_with): properties: message: type: string - errors: - type: array - items: - type: string examples: application/json: { - "message": "Internal controller error.", - "errors": ["Internal controller error."] + "message": "Internal controller error." } """ try: unshare_params = { "workflow_id_or_name": workflow_id_or_name, "user_email_to_unshare_with": user_email_to_unshare_with, - "user_id": str(user.id_), + "user": str(user.id_), } response, http_response = current_rwc_api_client.api.unshare_workflow( @@ -3705,7 +3709,7 @@ def get_workflow_share_status(workflow_id_or_name, user): try: share_status_params = { "workflow_id_or_name": workflow_id_or_name, - "user_id": str(user.id_), + "user": str(user.id_), } response, http_response = current_rwc_api_client.api.get_workflow_share_status( diff --git a/requirements.txt b/requirements.txt index ee2d1f96..1fb21c50 100644 --- a/requirements.txt +++ b/requirements.txt @@ -164,7 +164,7 @@ pywebpack==2.0.0 # via flask-webpackext, invenio-assets pyyaml==6.0.1 # via bravado, bravado-core, kubernetes, packtivity, reana-commons, snakemake, swagger-spec-validator, yadage, yadage-schemas, yte rdflib==5.0.0 # via cwltool, prov, schema-salad reana-commons[cwl,kubernetes,snakemake,yadage]==0.95.0a3 # via reana-db, reana-server (setup.py) -reana-db==0.95.0a3 # via reana-server (setup.py) +reana-db==0.95.0a4 # via reana-server (setup.py) redis==5.0.7 # via invenio-celery requests[security]==2.32.3 # via bravado, bravado-core, cachecontrol, cwltool, github3-py, kubernetes, packtivity, reana-server (setup.py), requests-oauthlib, schema-salad, snakemake, yadage, yadage-schemas requests-oauthlib==1.1.0 # via flask-oauthlib, invenio-oauth2server, invenio-oauthclient, kubernetes diff --git a/setup.py b/setup.py index cf8de2b9..3c402400 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ "sphinx-click>=1.0.4", ], "tests": [ - "pytest-reana>=0.95.0a2,<0.96.0", + "pytest-reana>=0.95.0a4,<0.96.0", ], } @@ -49,7 +49,7 @@ "gitpython>=3.1", "marshmallow>2.13.0,<3.0.0", "reana-commons[kubernetes,yadage,snakemake,cwl] @ git+https://github.com/reanahub/reana-commons.git@0.95.0a4", - "reana-db>=0.95.0a3,<0.96.0", + "reana-db>=0.95.0a4,<0.96.0", "requests>=2.25.0", "tablib>=0.12.1", "uWSGI>=2.0.17", diff --git a/tests/test_views.py b/tests/test_views.py index 42deb5b2..e974c50f 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -788,7 +788,7 @@ def test_prune_workspace(app, user0, sample_serial_workflow_in_db): assert "The workspace has been correctly pruned." in res.json["message"] -def test_gitlab_projects(app: Flask, default_user): +def test_gitlab_projects(app: Flask, user0): """Test fetching of GitLab projects.""" with app.test_client() as client: # token not provided @@ -804,7 +804,7 @@ def test_gitlab_projects(app: Flask, default_user): # missing GitLab token fetch_mock = Mock() fetch_mock.return_value = UserSecrets( - user_id=str(default_user.id_), + user_id=str(user0.id_), k8s_secret_name="k8s_secret_name", ) with patch( @@ -813,7 +813,7 @@ def test_gitlab_projects(app: Flask, default_user): ): res = client.get( "/api/gitlab/projects", - query_string={"access_token": default_user.access_token}, + query_string={"access_token": user0.access_token}, ) assert res.status_code == 401 @@ -854,7 +854,7 @@ def test_gitlab_projects(app: Flask, default_user): mock_fetch = Mock() mock_fetch.return_value = UserSecrets( - user_id=str(default_user.id_), + user_id=str(user0.id_), k8s_secret_name="gitlab_token", secrets=[ Secret(name="gitlab_access_token", type_="env", value="gitlab_token") @@ -868,7 +868,7 @@ def test_gitlab_projects(app: Flask, default_user): ): res = client.get( "/api/gitlab/projects", - query_string={"access_token": default_user.access_token}, + query_string={"access_token": user0.access_token}, ) assert res.status_code == 200