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

Add commands for plan collaborators #85

Merged
merged 4 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions src/aerie_cli/aerie_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1761,3 +1761,75 @@ def delete_directive_metadata_schema(self, key) -> list:
key=key
)
return resp["key"]

def list_plan_collaborators(self, plan_id: int) -> list:
"""List plan collaborators

Args:
plan_id (int): ID of Plan to list collaborators of

Returns:
list[str]: List of collaborator usernames
"""
query = """
query GetPlanCollaborators($plan_id: Int!) {
plan_by_pk(id: $plan_id) {
collaborators {
collaborator
}
}
}
"""

resp = self.aerie_host.post_to_graphql(
query,
plan_id=plan_id
)
return [c["collaborator"] for c in resp["collaborators"]]

def add_plan_collaborator(self, plan_id: int, user: str):
"""Add a plan collaborator

Args:
plan_id (int): ID of plan to add collaborator to
user (str): Username of collaborator
"""
query = """
mutation addPlanCollaborator($plan_id: Int!, $collaborator: String!) {
insert_plan_collaborators_one(object: {plan_id: $plan_id, collaborator: $collaborator}) {
collaborator
}
}
"""

self.aerie_host.post_to_graphql(
query,
plan_id=plan_id,
collaborator=user
)

def delete_plan_collaborator(self, plan_id: int, user: str):
"""Delete a plan collaborator

Args:
plan_id (int): ID of the plan to delete a collaborator from
user (str): Username of the collaborator
"""

query = """
mutation DeletePlanCollaborator($plan_id: Int!, $collaborator: String!) {
delete_plan_collaborators_by_pk(collaborator: $collaborator, plan_id: $plan_id) {
collaborator
plan_id
}
}
"""

resp = self.aerie_host.post_to_graphql(
query,
plan_id=plan_id,
collaborator=user
)

if resp is None:
raise RuntimeError(f"Failed to delete plan collaborator")
7 changes: 5 additions & 2 deletions src/aerie_cli/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from aerie_cli.utils.configurations import find_configuration

app = typer.Typer()
app.add_typer(plans.app, name="plans")
app.add_typer(plans.plans_app, name="plans")
app.add_typer(models.app, name="models")
app.add_typer(configurations.app, name="configurations")
app.add_typer(expansion.app, name="expansion")
Expand Down Expand Up @@ -85,6 +85,9 @@ def activate_session(
name: str = typer.Option(
None, "--name", "-n", help="Name for this configuration", metavar="NAME"
),
username: str = typer.Option(
None, "--username", "-u", help="Specify/override configured Aerie username", metavar="USERNAME"
),
role: str = typer.Option(
None, "--role", "-r", help="Specify a non-default role", metavar="ROLE"
)
Expand All @@ -99,7 +102,7 @@ def activate_session(

conf = PersistentConfigurationManager.get_configuration_by_name(name)

session = start_session_from_configuration(conf)
session = start_session_from_configuration(conf, username)

if role is not None:
if role in session.aerie_jwt.allowed_roles:
Expand Down
80 changes: 68 additions & 12 deletions src/aerie_cli/commands/plans.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@

from aerie_cli.commands.command_context import CommandContext
from aerie_cli.schemas.client import ActivityPlanCreate
from aerie_cli.utils.prompts import select_from_list

app = typer.Typer()
plans_app = typer.Typer()
collaborators_app = typer.Typer()
plans_app.add_typer(collaborators_app, name="collaborators")


@app.command()
@plans_app.command()
def download(
id: int = typer.Option(..., "--plan-id", "--id", "-p", help="Plan ID", prompt=True),
full_args: str = typer.Option(
Expand All @@ -29,7 +32,7 @@ def download(
typer.echo(f"Wrote activity plan to {output}")


@app.command()
@plans_app.command()
def download_simulation(
sim_id: int = typer.Option(
..., '--sim-id', '-s',
Expand All @@ -48,7 +51,7 @@ def download_simulation(
typer.echo(f"Wrote activity plan to {output}")


@app.command()
@plans_app.command()
def download_resources(
sim_id: int = typer.Option(
..., '--sim-id', '-s',
Expand Down Expand Up @@ -157,7 +160,7 @@ def download_resources(
typer.echo(f"Wrote resource timelines to {output}")


@app.command()
@plans_app.command()
def upload(
input: str = typer.Option(
..., "--input", "-i", help="The input file from which to create an Aerie plan", prompt=True
Expand All @@ -179,7 +182,7 @@ def upload(
typer.echo(f"Created plan ID: {plan_id}")


@app.command()
@plans_app.command()
def duplicate(
id: int = typer.Option(..., "--plan-id", "--id", "-p", help="Plan ID", prompt=True),
duplicated_plan_name: str = typer.Option(
Expand All @@ -196,7 +199,7 @@ def duplicate(
typer.echo(f"Duplicate activity plan created with ID: {duplicated_plan_id}")


@app.command()
@plans_app.command()
def simulate(
id: int = typer.Option(..., help="Plan ID", prompt=True),
output: Union[str, None] = typer.Option(
Expand All @@ -223,7 +226,7 @@ def simulate(
typer.echo(f"Wrote simulation results to {output}")


@app.command()
@plans_app.command()
def list():
"""List uploaded plans."""

Expand Down Expand Up @@ -257,7 +260,7 @@ def list():
Console().print(table)


@app.command()
@plans_app.command()
def create_config(
plan_id: int = typer.Option(..., help="Plan ID", prompt=True),
arg_file: str = typer.Option(
Expand All @@ -275,7 +278,7 @@ def create_config(
typer.echo(f"(*) {arg}: {resp[arg]}")


@app.command()
@plans_app.command()
def update_config(
plan_id: int = typer.Option(..., help="Plan ID", prompt=True),
arg_file: str = typer.Option(
Expand All @@ -294,7 +297,7 @@ def update_config(
typer.echo(f"(*) {arg}: {resp[arg]}")


@app.command()
@plans_app.command()
def delete(
plan_id: int = typer.Option(..., "--plan-id", "-p", help="Plan ID to be deleted", prompt=True),
):
Expand All @@ -304,7 +307,7 @@ def delete(
typer.echo(f"Plan `{plan_name}` with ID: {plan_id} has been removed.")


@app.command()
@plans_app.command()
def clean():
"""Delete all activity plans."""
client = CommandContext.get_client()
Expand All @@ -315,3 +318,56 @@ def clean():

typer.echo(f"All activity plans have been deleted")

@collaborators_app.command("list")
def list_collaborators(
plan_id: int = typer.Option(
..., "--plan-id", "-p", help="Plan ID to list collaborators of", prompt="Plan ID"
)
):
client = CommandContext.get_client()

collaborators = client.list_plan_collaborators(plan_id)
if len(collaborators):
typer.echo("\n".join(collaborators))
else:
typer.echo("No collaborators")


@collaborators_app.command("add")
def add_collaborator(
plan_id: int = typer.Option(
..., "--plan-id", "-p", help="Plan ID to add collaborator", prompt="Plan ID"
),
user: str = typer.Option(
..., "--user", "-u", help="Username of collaborator to add", prompt="Collaborator Username"
),
):
client = CommandContext.get_client()

client.add_plan_collaborator(plan_id, user)
if user in client.list_plan_collaborators(plan_id):
typer.echo(f"Successfully added collaborator: {user}")
else:
typer.echo(f"Failed to add collaborator")


@collaborators_app.command("delete")
def delete_collaborator(
plan_id: int = typer.Option(
..., "--plan-id", "-p", help="Plan ID to delete collaborator from", prompt="Plan ID"
),
user: str = typer.Option(
None, "--user", "-u", help="Username of collaborator to delete"
),
):
client = CommandContext.get_client()

if user is None:
collaborators = client.list_plan_collaborators(plan_id)
user = select_from_list(collaborators, "Select a collaborator to remove")
client.delete_plan_collaborator(plan_id, user)

if user not in client.list_plan_collaborators(plan_id):
typer.echo("Successfully deleted collaborator")
else:
typer.echo("Failed to delete collaborator")
9 changes: 5 additions & 4 deletions src/aerie_cli/utils/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,11 @@ def start_session_from_configuration(
configuration.name,
)

if configuration.username is None:
username = typer.prompt("Aerie Username")
else:
username = configuration.username
if username is None:
if configuration.username is None:
username = typer.prompt("Aerie Username")
else:
username = configuration.username

if password is None and hs.is_auth_enabled():
password = typer.prompt("Aerie Password", hide_input=True)
Expand Down
47 changes: 31 additions & 16 deletions tests/integration_tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,50 +5,65 @@
import sys

from aerie_cli.aerie_client import AerieClient
from aerie_cli.aerie_host import AerieHost
from aerie_cli.aerie_host import AerieHost, AerieHostConfiguration
from aerie_cli.commands.configurations import (
delete_all_persistent_files,
upload_configurations,
)
from aerie_cli.app import deactivate_session, activate_session
from aerie_cli.utils.sessions import get_active_session_client
from aerie_cli.app import deactivate_session
from aerie_cli.persistent import PersistentConfigurationManager, PersistentSessionManager
from aerie_cli.utils.sessions import (
get_active_session_client,
start_session_from_configuration,
)

# in case src_path is not from aeri-cli src and from site-packages
src_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/../src")
sys.path.insert(0, src_path)

# Configuration values mirror those in the test file localhost_config.json
GRAPHQL_URL = "http://localhost:8080/v1/graphql"
GATEWAY_URL = "http://localhost:9000"
USERNAME = "a"
PASSWORD = "a"
ANONYMOUS_LOCALHOST_CONF = AerieHostConfiguration("localhost", GRAPHQL_URL, GATEWAY_URL)

# Additional usernames to register with Aerie
ADDITIONAL_USERS = ["user1", "user2", "user3"]

# This should only ever be set to the admin secret for a local instance of aerie
HASURA_ADMIN_SECRET = os.environ.get("HASURA_GRAPHQL_ADMIN_SECRET")

aerie_host = AerieHost(GRAPHQL_URL, GATEWAY_URL)
aerie_host.authenticate(USERNAME, PASSWORD)
aerie_host.change_role("aerie_admin")
client = AerieClient(aerie_host)

# Test constants
DOWNLOADED_FILE_NAME = "downloaded_file.test"

TEST_DIR = os.path.dirname(os.path.abspath(__file__))

FILES_PATH = os.path.join(TEST_DIR, "files")

# Configuration Variables
CONFIGURATIONS_PATH = os.path.join(FILES_PATH, "configuration")
CONFIGURATION_PATH = os.path.join(CONFIGURATIONS_PATH, "localhost_config.json")

# Login to add additional users to the `users` table
for username in ADDITIONAL_USERS:
start_session_from_configuration(
ANONYMOUS_LOCALHOST_CONF,
username
)

# Resets the configurations and adds localhost
deactivate_session()
delete_all_persistent_files()
upload_configurations(CONFIGURATION_PATH)
activate_session("localhost")
persisent_client = None

# Login as the main username, set role, and store session as persistent
localhost_conf = PersistentConfigurationManager.get_configuration_by_name("localhost")
aerie_host = start_session_from_configuration(localhost_conf, USERNAME, PASSWORD)
aerie_host.change_role("aerie_admin")
PersistentSessionManager.set_active_session(aerie_host)

client = None
try:
persisent_client = get_active_session_client()
client = get_active_session_client()
except:
raise RuntimeError("Configuration is not active!")
assert (
persisent_client.aerie_host.gateway_url == GATEWAY_URL
client.aerie_host.gateway_url == GATEWAY_URL
), "Aerie instances are mismatched. Ensure test URLs are the same."
2 changes: 1 addition & 1 deletion tests/integration_tests/test_constraints.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typer.testing import CliRunner

from .conftest import client, HASURA_ADMIN_SECRET
from .conftest import client
from aerie_cli.__main__ import app

from aerie_cli.schemas.client import ActivityPlanCreate
Expand Down
2 changes: 1 addition & 1 deletion tests/integration_tests/test_expansion.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from aerie_cli.__main__ import app
from aerie_cli.schemas.client import ActivityPlanCreate

from .conftest import client, HASURA_ADMIN_SECRET
from .conftest import client

runner = CliRunner(mix_stderr = False)

Expand Down
1 change: 0 additions & 1 deletion tests/integration_tests/test_metadata.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from typer.testing import CliRunner

from .conftest import client, HASURA_ADMIN_SECRET
from aerie_cli.__main__ import app

import os
Expand Down
2 changes: 1 addition & 1 deletion tests/integration_tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from aerie_cli.__main__ import app

from .conftest import client, HASURA_ADMIN_SECRET
from .conftest import client

runner = CliRunner(mix_stderr = False)

Expand Down
Loading