diff --git a/reana_client/api/client.py b/reana_client/api/client.py index d9f8b497..bd702073 100644 --- a/reana_client/api/client.py +++ b/reana_client/api/client.py @@ -115,6 +115,9 @@ def get_workflows( include_progress=None, include_workspace_size=None, workflow=None, + shared=None, + shared_by=None, + shared_with=None, ): """List all existing workflows. @@ -130,6 +133,9 @@ def get_workflows( :param include_progress: include progress information in the response. :param include_workspace_size: include workspace size information in the response. :param workflow: name or id of the workflow. + :param shared: list all shared (owned and unowned) workflows. + :param shared_by: list workflows shared by the specified user(s). + :param shared_with: list workflows shared with the specified user(s). :return: a list of dictionaries with the information about the workflows. The information includes the workflow ``name``, ``id``, ``status``, ``size``, @@ -148,6 +154,9 @@ def get_workflows( include_progress=include_progress, include_workspace_size=include_workspace_size, workflow_id_or_name=workflow, + shared=shared, + shared_by=shared_by, + shared_with=shared_with, ).result() if http_response.status_code == 200: return response.get("items") diff --git a/reana_client/cli/workflow.py b/reana_client/cli/workflow.py index 3bdb4dff..f45580e3 100644 --- a/reana_client/cli/workflow.py +++ b/reana_client/cli/workflow.py @@ -157,6 +157,25 @@ def workflow_sharing_group(ctx): default=False, help="Include deleted workflows in the output.", ) +@click.option( + "--shared", + "shared", + is_flag=True, + default=False, + help="List all shared (owned and unowned) workflows.", +) +@click.option( + "--shared-by", + "shared_by", + default=None, + help="List workflows shared by the specified user(s).", +) +@click.option( + "--shared-with", + "shared_with", + default=None, + help="List workflows shared with the specified user(s).", +) @add_access_token_options @add_pagination_options @check_connection @@ -179,21 +198,42 @@ def workflows_list( # noqa: C901 include_progress, include_workspace_size, show_deleted_runs: bool, + shared, + shared_by, + shared_with, ): # noqa: D301 """List all workflows and sessions. The ``list`` command lists workflows and sessions. By default, the list of workflows is returned. If you would like to see the list of your open interactive sessions, you need to pass the ``--sessions`` command-line - option. + option. If you would like to see the list of all workflows, including those + shared with you, you need to pass the ``--shared`` command-line option. - Example:\n + Along with specific user emails, you can pass the following special values + to the ``--shared-by`` and ``--shared-with`` command-line options:\n + \t - ``--shared-by anybody``: list workflows shared with you by anybody.\n + \t - ``--shared-with anybody``: list your shared workflows exclusively.\n + \t - ``--shared-with nobody``: list your unshared workflows exclusively.\n + \t - ``--shared-with bob@cern.ch,cecile@cern.ch``: list workflows shared with either bob@cern.ch or cecile@cern.ch + + Examples:\n \t $ reana-client list --all\n \t $ reana-client list --sessions\n - \t $ reana-client list --verbose --bytes + \t $ reana-client list --verbose --bytes\n + \t $ reana-client list --shared\n + \t $ reana-client list --shared-by bob@cern.ch\n + \t $ reana-client list --shared-with anybody """ from reana_client.api.client import get_workflows + if shared_by and shared_with: + display_message( + "Please provide either --shared-by or --shared-with, not both.", + msg_type="error", + ) + sys.exit(1) + logging.debug("command: {}".format(ctx.command_path.replace(" ", "."))) for p in ctx.params: logging.debug("{param}: {value}".format(param=p, value=ctx.params[p])) @@ -224,13 +264,23 @@ def workflows_list( # noqa: C901 include_progress=include_progress, include_workspace_size=include_workspace_size, workflow=workflow, + shared=shared, + shared_by=shared_by, + shared_with=shared_with, ) verbose_headers = ["id", "user"] workspace_size_header = ["size"] progress_header = ["progress"] duration_header = ["duration"] headers = { - "batch": ["name", "run_number", "created", "started", "ended", "status"], + "batch": [ + "name", + "run_number", + "created", + "started", + "ended", + "status", + ], "interactive": [ "name", "run_number", @@ -249,6 +299,14 @@ def workflows_list( # noqa: C901 if verbose or include_duration: headers[type] += duration_header + if shared: + headers[type] += ["shared_with", "shared_by"] + else: + if shared_with: + headers[type] += ["shared_with"] + if shared_by: + headers[type] += ["shared_by"] + data = [] for workflow in response: name, run_number = get_workflow_name_and_run_number(workflow["name"]) @@ -271,6 +329,10 @@ def workflows_list( # noqa: C901 "run_started_at" if header == "started" else "run_finished_at" ) value = workflow.get("progress", {}).get(_key) or "-" + if header == "shared_by": + value = workflow.get("owner_email") + if header == "shared_with": + value = workflow.get("shared_with") if not value: value = workflow.get(header) row.append(value) diff --git a/tests/test_cli_workflows.py b/tests/test_cli_workflows.py index 824050c7..2b3d652d 100644 --- a/tests/test_cli_workflows.py +++ b/tests/test_cli_workflows.py @@ -487,6 +487,155 @@ def test_workflows_filter(): assert "running" in json_response[0]["status"] +def test_workflows_shared(): + """Test workflow list command with --shared flag.""" + response = { + "items": [ + { + "status": "running", + "created": "2018-06-13T09:47:35.66097", + "user": "00000000-0000-0000-0000-000000000000", + "name": "mytest.1", + "id": "256b25f4-4cfb-4684-b7a8-73872ef455a1", + "size": {"raw": 0, "human_readable": "0 Bytes"}, + "progress": {}, + } + ] + } + status_code = 200 + mock_http_response, mock_response = Mock(), Mock() + mock_http_response.status_code = status_code + mock_response = response + env = {"REANA_SERVER_URL": "localhost"} + reana_token = "000000" + runner = CliRunner(env=env) + with runner.isolation(): + with patch( + "reana_client.api.client.current_rs_api_client", + make_mock_api_client("reana-server")(mock_response, mock_http_response), + ): + result = runner.invoke(cli, ["list", "--shared", "-t", reana_token]) + assert result.exit_code == 0 + assert "SHARED_WITH" in result.output + assert "SHARED_BY" in result.output + + +def test_workflows_shared_with(): + """Test workflow list command with --shared-with flag.""" + response = { + "items": [ + { + "status": "running", + "created": "2018-06-13T09:47:35.66097", + "user": "00000000-0000-0000-0000-000000000000", + "name": "mytest.1", + "id": "256b25f4-4cfb-4684-b7a8-73872ef455a1", + "size": {"raw": 0, "human_readable": "0 Bytes"}, + "progress": {}, + } + ] + } + status_code = 200 + mock_http_response, mock_response = Mock(), Mock() + mock_http_response.status_code = status_code + mock_response = response + env = {"REANA_SERVER_URL": "localhost"} + reana_token = "000000" + runner = CliRunner(env=env) + with runner.isolation(): + with patch( + "reana_client.api.client.current_rs_api_client", + make_mock_api_client("reana-server")(mock_response, mock_http_response), + ): + result = runner.invoke( + cli, ["list", "--shared-with", "anybody", "-t", reana_token] + ) + assert result.exit_code == 0 + assert "SHARED_WITH" in result.output + assert "SHARED_BY" not in result.output + + +def test_workflows_shared_by(): + """Test workflow list command with --shared-by flag.""" + response = { + "items": [ + { + "status": "running", + "created": "2018-06-13T09:47:35.66097", + "user": "00000000-0000-0000-0000-000000000000", + "name": "mytest.1", + "id": "256b25f4-4cfb-4684-b7a8-73872ef455a1", + "size": {"raw": 0, "human_readable": "0 Bytes"}, + "progress": {}, + } + ] + } + status_code = 200 + mock_http_response, mock_response = Mock(), Mock() + mock_http_response.status_code = status_code + mock_response = response + env = {"REANA_SERVER_URL": "localhost"} + reana_token = "000000" + runner = CliRunner(env=env) + with runner.isolation(): + with patch( + "reana_client.api.client.current_rs_api_client", + make_mock_api_client("reana-server")(mock_response, mock_http_response), + ): + result = runner.invoke( + cli, ["list", "--shared-by", "anybody", "-t", reana_token] + ) + assert result.exit_code == 0 + assert "SHARED_WITH" not in result.output + assert "SHARED_BY" in result.output + + +def test_workflows_shared_with_and_shared_by(): + """Test workflow list command with --shared-with and --shared-by flags.""" + response = { + "items": [ + { + "status": "running", + "created": "2018-06-13T09:47:35.66097", + "user": "00000000-0000-0000-0000-000000000000", + "name": "mytest.1", + "id": "256b25f4-4cfb-4684-b7a8-73872ef455a1", + "size": {"raw": 0, "human_readable": "0 Bytes"}, + "progress": {}, + } + ] + } + status_code = 200 + mock_http_response, mock_response = Mock(), Mock() + mock_http_response.status_code = status_code + mock_response = response + env = {"REANA_SERVER_URL": "localhost"} + reana_token = "000000" + runner = CliRunner(env=env) + with runner.isolation(): + with patch( + "reana_client.api.client.current_rs_api_client", + make_mock_api_client("reana-server")(mock_response, mock_http_response), + ): + result = runner.invoke( + cli, + [ + "list", + "--shared-with", + "anybody", + "--shared-by", + "anybody", + "-t", + reana_token, + ], + ) + assert result.exit_code == 1 + assert ( + "Please provide either --shared-by or --shared-with, not both" + in result.output + ) + + def test_workflow_create_failed(): """Test workflow create when creation fails.""" env = {"REANA_SERVER_URL": "localhost"}