Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rest: add sharing parameters to get_workflows #545

Closed

Conversation

DaanRosendal
Copy link
Member

Adds new shared, shared_with and shared_by parameters to the
get_workflows endpoint to retrieve workflows along with their sharing
information.

Closes reanahub/reana-client#687

Adds a new endpoint to share a workflow with a user.

Closes reanahub/reana-client#680
Adds a new endpoint to unshare a workflow.

Closes reanahub/reana-client#681
Adds a new endpoint to retrieve whom a workflow is shared with.

Closes reanahub/reana-client#686
Adds new `shared`, `shared_with` and `shared_by` parameters to the
`get_workflows` endpoint to retrieve workflows along with their sharing
information.

Closes reanahub/reana-client#687
@@ -64,6 +65,9 @@
"user": fields.String(required=True),
"verbose": fields.Bool(missing=False),
"workflow_id_or_name": fields.String(),
"shared": fields.Bool(missing=False),
"shared_by": fields.String(),
"shared_with": fields.DelimitedList(fields.String()),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should multiple values of shared_with be interpreted as the workflows shared with all the given users or with any of them? This also changes a bit the semantics of anybody and nobody


from flask import Blueprint, jsonify, request
from reana_commons.config import WORKFLOW_TIME_FORMAT
from reana_db.database import Session
from reana_db.models import RunStatus, User, UserWorkflow, Workflow, WorkflowResource
from reana_db.utils import (
_get_workflow_by_uuid,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is not needed?

@@ -13,13 +13,14 @@
import logging
import re
from typing import Optional
from uuid import uuid4
from uuid import UUID, uuid4
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think UUID is not needed?

Comment on lines +305 to +366
if not shared and not shared_with and not shared_by:
# default case: retrieve owned workflows
query = user.workflows
else:
if shared or shared_by:
# retrieve owned workflows and unowned workflows shared by others
workflows_shared_with_user = Session.query(
user.shared_workflows.subquery().c.id_
)
query = Session.query(Workflow).filter(
or_(
Workflow.owner_id == user.id_,
Workflow.id_.in_(workflows_shared_with_user),
)
)
if shared_by and not shared:
if shared_by == "anybody":
# retrieve unowned workflows shared by anyone
query = query.filter(Workflow.id_.in_(workflows_shared_with_user))
else:
# retrieve unowned workflows shared by specific user
shared_by_user = (
Session.query(User).filter(User.email == shared_by).first()
)
if not shared_by_user:
return (
jsonify(
{
"message": f"User with email '{shared_by}' does not exist."
}
),
404,
)
query = query.filter(Workflow.owner_id == shared_by_user.id_)
if shared_with:
# starting point: retrieve owned workflows
query = user.workflows

first_shared_with = shared_with[0]
workflows_shared_by_user = Session.query(Workflow.id_).join(
UserWorkflow,
and_(
Workflow.id_ == UserWorkflow.workflow_id,
Workflow.owner_id == user.id_,
),
)

if first_shared_with == "nobody":
# retrieve owned unshared workflows
query = query.filter(Workflow.id_.notin_(workflows_shared_by_user))
elif first_shared_with == "anybody":
# retrieve exclusively owned shared workflows
query = query.filter(Workflow.id_.in_(workflows_shared_by_user))
else:
# retrieve owned workflows shared with specific users
shared_with_users = Session.query(User.id_).filter(
User.email.in_(shared_with)
)
shared_with_workflows_ids = Session.query(
UserWorkflow.workflow_id
).filter(UserWorkflow.user_id.in_(shared_with_users))
query = query.filter(Workflow.id_.in_(shared_with_workflows_ids))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can tell, we can consider shared, shared_with and shared_by as mutually exclusive, right?
If that's the case, then I would suggest changing all of this code to make it more clear, as now there are lots of different cases due to the many nested ifs.

In particular, we could first check if the user specified more than one of shared, shared_with and shared_by and if so return an error. Otherwise, we just have three cases, one for each flag. Something like this (not tested):

if shared is not None and shared_by is not None:
    return error
if shared is not None and shared_with is not None:
    return error
if shared_by is not None and shared_with is not None:
    return error

if shared;
    # ...
elif shared_by:
    # ...
elif shared_with: 
    # ...
else:
    # normal case

This should avoid most (if not all) nested ifs, making the logic a bit easier to understand.

),
)

if first_shared_with == "nobody":
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do nobody and anybody only make sense if they are provided on their own, without other emails? If so, we need to check that

Comment on lines +440 to +442
"shared_with": ",".join(shared_with_emails)
if shared_with_emails != "-"
else "-",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to make this a string, we can return an array of strings here (and it will be deserialized correctly). If it's not shared with anybody, just return an empty list.

Comment on lines +199 to +200
shared_with:
type: string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a list, not a string

@@ -258,14 +281,90 @@ def get_workflows(args, paginate=None): # noqa
include_progress: bool = args.get("include_progress", verbose)
include_workspace_size: bool = args.get("include_workspace_size", verbose)
workflow_id_or_name: Optional[str] = args.get("workflow_id_or_name")
shared: bool = args.get("shared")
shared_by: Optional[str] = args.get("shared_by")
shared_with: Optional[str] = args.get("shared_with")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be List[str] and not Optional[str]

@@ -306,6 +436,10 @@ def get_workflows(args, paginate=None): # noqa
"progress": get_workflow_progress(
workflow, include_progress=include_progress
),
"owner_email": owner_email,
Copy link
Member

@mdonadoni mdonadoni Jan 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that we already have user in the response, maybe we can call this user_email?

assert response_data[0]["owner_email"] == user1.email


def test_get_workflows_shared_with(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's also add a couple of tests to check corner cases like shared_with = nobody and shared_with = anybody?

@mdonadoni
Copy link
Member

Included in #552

@mdonadoni mdonadoni closed this Aug 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

cli: implement list --shared argument
2 participants