-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add
prefect dashboard open
to open dashboard from CLI (#14985)
- Loading branch information
Showing
4 changed files
with
145 additions
and
66 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |