Skip to content

Commit

Permalink
add prefect dashboard open to open dashboard from CLI (#14985)
Browse files Browse the repository at this point in the history
  • Loading branch information
zzstoatzz authored Aug 26, 2024
1 parent d867dd0 commit 491af65
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 66 deletions.
1 change: 1 addition & 0 deletions src/prefect/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import prefect.cli.shell
import prefect.cli.concurrency_limit
import prefect.cli.config
import prefect.cli.dashboard
import prefect.cli.deploy
import prefect.cli.deployment
import prefect.cli.dev
Expand Down
40 changes: 40 additions & 0 deletions src/prefect/cli/dashboard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import webbrowser

from prefect.cli._types import PrefectTyper
from prefect.cli._utilities import exit_with_success
from prefect.cli.cloud import get_current_workspace
from prefect.cli.root import app
from prefect.client.cloud import get_cloud_client
from prefect.settings import PREFECT_UI_URL
from prefect.utilities.asyncutils import run_sync_in_worker_thread

dashboard_app = PrefectTyper(
name="dashboard",
help="Commands for interacting with the Prefect UI.",
)
app.add_typer(dashboard_app)


@dashboard_app.command()
async def open():
"""
Open the Prefect UI in the browser.
"""

if not (ui_url := PREFECT_UI_URL.value()):
raise RuntimeError(
"`PREFECT_UI_URL` must be set to the URL of a running Prefect server or Prefect Cloud workspace."
)

await run_sync_in_worker_thread(webbrowser.open_new_tab, ui_url)

async with get_cloud_client() as client:
current_workspace = get_current_workspace(await client.read_workspaces())

destination = (
f"{current_workspace.account_handle}/{current_workspace.workspace_handle}"
if current_workspace
else ui_url
)

exit_with_success(f"Opened {destination!r} in browser.")
66 changes: 0 additions & 66 deletions tests/cli/test_cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -1599,69 +1599,3 @@ def test_webhook_methods_with_invalid_uuid():
["cloud", "webhook", cmd, bad_webhook_id],
expected_code=2,
)


def test_open_current_workspace_in_browser_success(mock_webbrowser, respx_mock):
foo_workspace = gen_test_workspace(account_handle="test", workspace_handle="foo")

save_profiles(
ProfilesCollection(
[
Profile(
name="logged-in-profile",
settings={
PREFECT_API_URL: foo_workspace.api_url(),
PREFECT_API_KEY: "foo",
},
)
],
active="logged-in-profile",
)
)

respx_mock.get(PREFECT_CLOUD_API_URL.value() + "/me/workspaces").mock(
return_value=httpx.Response(
status.HTTP_200_OK,
json=[foo_workspace.model_dump(mode="json")],
)
)

with use_profile("logged-in-profile"):
invoke_and_assert(
["cloud", "open"],
expected_code=0,
expected_output_contains=f"Opened {foo_workspace.handle!r} in browser.",
)

mock_webbrowser.open_new_tab.assert_called_with(foo_workspace.ui_url())


def test_open_current_workspace_in_browser_failure_no_workspace_set(respx_mock):
save_profiles(
ProfilesCollection(
[
Profile(
name="logged-in-profile",
settings={
PREFECT_API_URL: "https://api.prefect.io",
PREFECT_API_KEY: "foo",
},
)
],
active="logged-in-profile",
)
)

respx_mock.get(PREFECT_CLOUD_API_URL.value() + "/me/workspaces").mock(
return_value=httpx.Response(
status.HTTP_200_OK,
json=[],
)
)

with use_profile("logged-in-profile"):
invoke_and_assert(
["cloud", "open"],
expected_code=1,
expected_output_contains="There is no current workspace set - set one with",
)
104 changes: 104 additions & 0 deletions tests/cli/test_dashboard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import uuid
from unittest.mock import MagicMock

import httpx
import pytest
from starlette import status

from prefect.client.schemas import Workspace
from prefect.context import use_profile
from prefect.settings import (
PREFECT_API_KEY,
PREFECT_API_URL,
PREFECT_CLOUD_API_URL,
Profile,
ProfilesCollection,
save_profiles,
)
from prefect.testing.cli import invoke_and_assert


def gen_test_workspace(**kwargs) -> Workspace:
defaults = {
"account_id": uuid.uuid4(),
"account_name": "account name",
"account_handle": "account-handle",
"workspace_id": uuid.uuid4(),
"workspace_name": "workspace name",
"workspace_handle": "workspace-handle",
"workspace_description": "workspace description",
}
defaults.update(kwargs)
return Workspace(**defaults)


@pytest.fixture
def mock_webbrowser(monkeypatch):
mock = MagicMock()
monkeypatch.setattr("prefect.cli.dashboard.webbrowser", mock)
yield mock


def test_open_current_workspace_in_browser_success(mock_webbrowser, respx_mock):
foo_workspace = gen_test_workspace(account_handle="test", workspace_handle="foo")

save_profiles(
ProfilesCollection(
[
Profile(
name="logged-in-profile",
settings={
PREFECT_API_URL: foo_workspace.api_url(),
PREFECT_API_KEY: "foo",
},
)
],
active="logged-in-profile",
)
)

respx_mock.get(PREFECT_CLOUD_API_URL.value() + "/me/workspaces").mock(
return_value=httpx.Response(
status.HTTP_200_OK,
json=[foo_workspace.model_dump(mode="json")],
)
)
with use_profile("logged-in-profile"):
invoke_and_assert(
["dashboard", "open"],
expected_code=0,
expected_output_contains=f"Opened {foo_workspace.handle!r} in browser.",
)

mock_webbrowser.open_new_tab.assert_called_with(foo_workspace.ui_url())


@pytest.mark.usefixtures("mock_webbrowser")
@pytest.mark.parametrize("api_url", ["http://localhost:4200", "https://api.prefect.io"])
def test_open_current_workspace_in_browser_failure_no_workspace_set(
respx_mock, api_url
):
save_profiles(
ProfilesCollection(
[
Profile(
name="logged-in-profile",
settings={
PREFECT_API_URL: api_url,
PREFECT_API_KEY: "foo",
},
)
],
active="logged-in-profile",
)
)

respx_mock.get(PREFECT_CLOUD_API_URL.value() + "/me/workspaces").mock(
return_value=httpx.Response(
status.HTTP_200_OK,
json=[],
)
)

with use_profile("logged-in-profile"):
invoke_and_assert(["dashboard", "open"], expected_code=0)

0 comments on commit 491af65

Please sign in to comment.