From b7c59b933ceae31c04cd82704c04e74237cc2a35 Mon Sep 17 00:00:00 2001 From: Marina Ilina Date: Mon, 16 Feb 2026 10:13:58 +0100 Subject: [PATCH 1/3] task: set status betterstack page conditionally for staging and production --- src/aignostics/gui/_frame.py | 57 +++++++++++++++++------- tests/aignostics/gui/__init__.py | 1 + tests/aignostics/gui/frame_test.py | 71 ++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 16 deletions(-) create mode 100644 tests/aignostics/gui/__init__.py create mode 100644 tests/aignostics/gui/frame_test.py diff --git a/src/aignostics/gui/_frame.py b/src/aignostics/gui/_frame.py index f4470f5a9..8729cd73c 100644 --- a/src/aignostics/gui/_frame.py +++ b/src/aignostics/gui/_frame.py @@ -14,6 +14,7 @@ from loguru import logger from aignostics.constants import WINDOW_TITLE +from aignostics.platform import API_ROOT_PRODUCTION, API_ROOT_STAGING from aignostics.utils import __version__, open_user_data_directory from ._theme import theme @@ -29,6 +30,24 @@ CLASSES_FULL_SIZE = f"{CLASSES_FULL_WIDTH} {CLASSES_FULL_HEIGHT}" +def get_status_page_url(api_root: str) -> str | None: + """Get the status page URL based on the API root environment. + + Args: + api_root: The API root URL to determine the environment + + Returns: + The status page URL for production/staging, or None for dev/test + """ + if api_root == API_ROOT_PRODUCTION: + return "https://status.platform.aignostics.com" + elif api_root == API_ROOT_STAGING: + return "https://status.platform-staging.aignostics.com" + else: + # No status page for dev and test environments + return None + + @contextmanager def frame( # noqa: C901, PLR0915 navigation_title: str, @@ -213,7 +232,9 @@ def _update_health() -> None: coroutine=_health_load_and_render(), name="_health_load_and_render", ) - ui.run_javascript("document.getElementById('betterstack').src = document.getElementById('betterstack').src;") + # Only refresh the status iframe if it exists (production/staging) + if get_status_page_url(settings().api_root): + ui.run_javascript("document.getElementById('betterstack').src = document.getElementById('betterstack').src;") ui.timer(interval=HEALTH_UPDATE_INTERVAL, callback=_update_health, immediate=True) @@ -342,13 +363,15 @@ def toggle_dark_mode() -> None: ui.link("Get Support", "https://platform.aignostics.com/support", new_tab=True).mark( "LINK_DOCUMENTATION" ) - with ui.item().props("clickable"): - with ui.item_section().props("avatar"): - ui.icon("check_circle", color="primary") - with ui.item_section(): - ui.link("Check Platform Status", "https://status.aignostics.com", new_tab=True).mark( - "LINK_DOCUMENTATION" - ) + status_url = get_status_page_url(settings().api_root) + if status_url: + with ui.item().props("clickable"): + with ui.item_section().props("avatar"): + ui.icon("check_circle", color="primary") + with ui.item_section(): + ui.link("Check Platform Status", status_url, new_tab=True).mark( + "LINK_DOCUMENTATION" + ) with ui.item().props("clickable"): with ui.item_section().props("avatar"): ui.icon("handshake", color="primary") @@ -368,14 +391,16 @@ def toggle_dark_mode() -> None: ui.row(align_items="center").classes("justify-start w-full"), ): health_link() - with ui.row().style("padding: 0"): - ui.html( - '', - sanitize=False, - ).style("margin-left: 0px;") - ui.tooltip("Check Platform Status") + status_url = get_status_page_url(settings().api_root) + if status_url: + with ui.row().style("padding: 0"): + ui.html( + f'', + sanitize=False, + ).style("margin-left: 0px;") + ui.tooltip("Check Platform Status") ui.space() with ui.row(): flavor = " (native)" if getattr(sys, "frozen", False) else "" diff --git a/tests/aignostics/gui/__init__.py b/tests/aignostics/gui/__init__.py new file mode 100644 index 000000000..ff8b2654f --- /dev/null +++ b/tests/aignostics/gui/__init__.py @@ -0,0 +1 @@ +"""Tests for GUI module.""" diff --git a/tests/aignostics/gui/frame_test.py b/tests/aignostics/gui/frame_test.py new file mode 100644 index 000000000..b9199cf4b --- /dev/null +++ b/tests/aignostics/gui/frame_test.py @@ -0,0 +1,71 @@ +"""Tests for GUI frame module.""" + +import pytest + +from aignostics.gui._frame import get_status_page_url +from aignostics.platform import ( + API_ROOT_DEV, + API_ROOT_PRODUCTION, + API_ROOT_STAGING, + API_ROOT_TEST, +) + + +@pytest.mark.unit +def test_get_status_page_url_production(record_property) -> None: + """Test that production environment returns correct status page URL. + + Args: + record_property: pytest record_property fixture + """ + record_property("tested-item-id", "SPEC-GUI-FRAME") + url = get_status_page_url(API_ROOT_PRODUCTION) + assert url == "https://status.platform.aignostics.com" + + +@pytest.mark.unit +def test_get_status_page_url_staging(record_property) -> None: + """Test that staging environment returns correct status page URL. + + Args: + record_property: pytest record_property fixture + """ + record_property("tested-item-id", "SPEC-GUI-FRAME") + url = get_status_page_url(API_ROOT_STAGING) + assert url == "https://status.platform-staging.aignostics.com" + + +@pytest.mark.unit +def test_get_status_page_url_dev(record_property) -> None: + """Test that dev environment returns None (no status page). + + Args: + record_property: pytest record_property fixture + """ + record_property("tested-item-id", "SPEC-GUI-FRAME") + url = get_status_page_url(API_ROOT_DEV) + assert url is None + + +@pytest.mark.unit +def test_get_status_page_url_test(record_property) -> None: + """Test that test environment returns None (no status page). + + Args: + record_property: pytest record_property fixture + """ + record_property("tested-item-id", "SPEC-GUI-FRAME") + url = get_status_page_url(API_ROOT_TEST) + assert url is None + + +@pytest.mark.unit +def test_get_status_page_url_unknown(record_property) -> None: + """Test that unknown environment returns None (no status page). + + Args: + record_property: pytest record_property fixture + """ + record_property("tested-item-id", "SPEC-GUI-FRAME") + url = get_status_page_url("https://custom.example.com") + assert url is None From b2becc235a6df85e5e2a5f4f0f715b4bbd818142 Mon Sep 17 00:00:00 2001 From: Marina Ilina Date: Mon, 16 Feb 2026 11:10:38 +0100 Subject: [PATCH 2/3] Update src/aignostics/gui/_frame.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/aignostics/gui/_frame.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/aignostics/gui/_frame.py b/src/aignostics/gui/_frame.py index 8729cd73c..c770738df 100644 --- a/src/aignostics/gui/_frame.py +++ b/src/aignostics/gui/_frame.py @@ -41,11 +41,10 @@ def get_status_page_url(api_root: str) -> str | None: """ if api_root == API_ROOT_PRODUCTION: return "https://status.platform.aignostics.com" - elif api_root == API_ROOT_STAGING: + if api_root == API_ROOT_STAGING: return "https://status.platform-staging.aignostics.com" - else: - # No status page for dev and test environments - return None + # No status page for dev and test environments + return None @contextmanager From 252bf44dec931196a94c1796b9ed75b8a39e7d0f Mon Sep 17 00:00:00 2001 From: Marina Ilina Date: Mon, 16 Feb 2026 13:07:06 +0100 Subject: [PATCH 3/3] move status page logic to constants and settings --- src/aignostics/gui/_frame.py | 24 ++-------- src/aignostics/platform/__init__.py | 4 ++ src/aignostics/platform/_constants.py | 4 ++ src/aignostics/platform/_settings.py | 21 +++++++++ tests/aignostics/gui/frame_test.py | 65 ++++++++++++++++----------- 5 files changed, 71 insertions(+), 47 deletions(-) diff --git a/src/aignostics/gui/_frame.py b/src/aignostics/gui/_frame.py index c770738df..633c8484d 100644 --- a/src/aignostics/gui/_frame.py +++ b/src/aignostics/gui/_frame.py @@ -14,7 +14,6 @@ from loguru import logger from aignostics.constants import WINDOW_TITLE -from aignostics.platform import API_ROOT_PRODUCTION, API_ROOT_STAGING from aignostics.utils import __version__, open_user_data_directory from ._theme import theme @@ -30,23 +29,6 @@ CLASSES_FULL_SIZE = f"{CLASSES_FULL_WIDTH} {CLASSES_FULL_HEIGHT}" -def get_status_page_url(api_root: str) -> str | None: - """Get the status page URL based on the API root environment. - - Args: - api_root: The API root URL to determine the environment - - Returns: - The status page URL for production/staging, or None for dev/test - """ - if api_root == API_ROOT_PRODUCTION: - return "https://status.platform.aignostics.com" - if api_root == API_ROOT_STAGING: - return "https://status.platform-staging.aignostics.com" - # No status page for dev and test environments - return None - - @contextmanager def frame( # noqa: C901, PLR0915 navigation_title: str, @@ -232,7 +214,7 @@ def _update_health() -> None: name="_health_load_and_render", ) # Only refresh the status iframe if it exists (production/staging) - if get_status_page_url(settings().api_root): + if settings().status_page_url: ui.run_javascript("document.getElementById('betterstack').src = document.getElementById('betterstack').src;") ui.timer(interval=HEALTH_UPDATE_INTERVAL, callback=_update_health, immediate=True) @@ -362,7 +344,7 @@ def toggle_dark_mode() -> None: ui.link("Get Support", "https://platform.aignostics.com/support", new_tab=True).mark( "LINK_DOCUMENTATION" ) - status_url = get_status_page_url(settings().api_root) + status_url = settings().status_page_url if status_url: with ui.item().props("clickable"): with ui.item_section().props("avatar"): @@ -390,7 +372,7 @@ def toggle_dark_mode() -> None: ui.row(align_items="center").classes("justify-start w-full"), ): health_link() - status_url = get_status_page_url(settings().api_root) + status_url = settings().status_page_url if status_url: with ui.row().style("padding: 0"): ui.html( diff --git a/src/aignostics/platform/__init__.py b/src/aignostics/platform/__init__.py index 4a9d28efd..9fca31b16 100644 --- a/src/aignostics/platform/__init__.py +++ b/src/aignostics/platform/__init__.py @@ -71,6 +71,10 @@ REDIRECT_URI_TEST, REDIRECT_URI_STAGING, REDIRECT_URI_PRODUCTION, + STATUS_PAGE_URL_DEV, + STATUS_PAGE_URL_TEST, + STATUS_PAGE_URL_STAGING, + STATUS_PAGE_URL_PRODUCTION, TOKEN_URL_DEV, TOKEN_URL_TEST, TOKEN_URL_STAGING, diff --git a/src/aignostics/platform/_constants.py b/src/aignostics/platform/_constants.py index c64315b44..8e9be78b2 100644 --- a/src/aignostics/platform/_constants.py +++ b/src/aignostics/platform/_constants.py @@ -8,6 +8,7 @@ REDIRECT_URI_DEV = "http://localhost:8989/" DEVICE_URL_DEV = "https://dev-8ouohmmrbuh2h4vu.eu.auth0.com/oauth/device/code" JWS_JSON_URL_DEV = "https://dev-8ouohmmrbuh2h4vu.eu.auth0.com/.well-known/jwks.json" +STATUS_PAGE_URL_DEV = None # No dedicated status page for dev environment API_ROOT_TEST = "https://platform-test.aignostics.ai" CLIENT_ID_INTERACTIVE_TEST = "gqduveFvx7LX90drQPGzr4JGUYdh24gA" # not a secret, but a public client ID (same as dev) @@ -17,6 +18,7 @@ REDIRECT_URI_TEST = "http://localhost:8989/" DEVICE_URL_TEST = "https://dev-8ouohmmrbuh2h4vu.eu.auth0.com/oauth/device/code" JWS_JSON_URL_TEST = "https://dev-8ouohmmrbuh2h4vu.eu.auth0.com/.well-known/jwks.json" +STATUS_PAGE_URL_TEST = None # No dedicated status page for test environment API_ROOT_STAGING = "https://platform-staging.aignostics.com" CLIENT_ID_INTERACTIVE_STAGING = "fQkbvYzQPPVwLxc3uque5JsyFW00rJ7b" # not a secret, but a public client ID @@ -26,6 +28,7 @@ REDIRECT_URI_STAGING = "http://localhost:8989/" DEVICE_URL_STAGING = "https://aignostics-platform-staging.eu.auth0.com/oauth/device/code" JWS_JSON_URL_STAGING = "https://aignostics-platform-staging.eu.auth0.com/.well-known/jwks.json" +STATUS_PAGE_URL_STAGING = "https://status.platform-staging.aignostics.com" API_ROOT_PRODUCTION = "https://platform.aignostics.com" CLIENT_ID_INTERACTIVE_PRODUCTION = "YtJ7F9lAtxx16SZGQlYPe6wcjlXB78MM" # not a secret, but a public client ID @@ -35,6 +38,7 @@ REDIRECT_URI_PRODUCTION = "http://localhost:8989/" DEVICE_URL_PRODUCTION = "https://aignostics-platform.eu.auth0.com/oauth/device/code" JWS_JSON_URL_PRODUCTION = "https://aignostics-platform.eu.auth0.com/.well-known/jwks.json" +STATUS_PAGE_URL_PRODUCTION = "https://status.platform.aignostics.com" # Pipeline orchestration defaults DEFAULT_GPU_TYPE = "L4" diff --git a/src/aignostics/platform/_settings.py b/src/aignostics/platform/_settings.py index 71ca1d3a5..cf565466c 100644 --- a/src/aignostics/platform/_settings.py +++ b/src/aignostics/platform/_settings.py @@ -50,6 +50,10 @@ REDIRECT_URI_TEST, REDIRECT_URI_STAGING, REDIRECT_URI_PRODUCTION, + STATUS_PAGE_URL_DEV, + STATUS_PAGE_URL_TEST, + STATUS_PAGE_URL_STAGING, + STATUS_PAGE_URL_PRODUCTION, TOKEN_URL_DEV, TOKEN_URL_TEST, TOKEN_URL_STAGING, @@ -205,6 +209,9 @@ def profile_edit_url(self) -> str: str, BeforeValidator(_validate_url), Field(description="JWS key set URL for token verification") ] client_id_interactive: Annotated[str, Field(description="OAuth client ID for interactive flows")] + status_page_url: Annotated[ + str | None, Field(description="Status page URL for monitoring platform health", default=None) + ] = None @computed_field # type: ignore[prop-decorator] @property @@ -510,6 +517,20 @@ def pre_init(cls, values: dict) -> dict: # type: ignore[type-arg] # noqa: N805 # See https://github.com/pydantic/pydantic/issues/9789 api_root = values.get("api_root", API_ROOT_PRODUCTION) + # Set status_page_url based on environment (independent of auth fields) + if "status_page_url" not in values: + match api_root: + case x if x == API_ROOT_DEV: + values["status_page_url"] = STATUS_PAGE_URL_DEV + case x if x == API_ROOT_TEST: + values["status_page_url"] = STATUS_PAGE_URL_TEST + case x if x == API_ROOT_STAGING: + values["status_page_url"] = STATUS_PAGE_URL_STAGING + case x if x == API_ROOT_PRODUCTION: + values["status_page_url"] = STATUS_PAGE_URL_PRODUCTION + case _: + values["status_page_url"] = None + # Check if all required auth fields are already provided auth_fields = [ "audience", diff --git a/tests/aignostics/gui/frame_test.py b/tests/aignostics/gui/frame_test.py index b9199cf4b..15f010b5e 100644 --- a/tests/aignostics/gui/frame_test.py +++ b/tests/aignostics/gui/frame_test.py @@ -2,70 +2,83 @@ import pytest -from aignostics.gui._frame import get_status_page_url from aignostics.platform import ( API_ROOT_DEV, API_ROOT_PRODUCTION, API_ROOT_STAGING, API_ROOT_TEST, + STATUS_PAGE_URL_DEV, + STATUS_PAGE_URL_PRODUCTION, + STATUS_PAGE_URL_STAGING, + STATUS_PAGE_URL_TEST, + Settings, ) @pytest.mark.unit -def test_get_status_page_url_production(record_property) -> None: - """Test that production environment returns correct status page URL. +def test_status_page_url_production(record_property) -> None: + """Test that production environment has correct status page URL in settings. Args: record_property: pytest record_property fixture """ - record_property("tested-item-id", "SPEC-GUI-FRAME") - url = get_status_page_url(API_ROOT_PRODUCTION) - assert url == "https://status.platform.aignostics.com" + record_property("tested-item-id", "SPEC-PLATFORM-SETTINGS") + settings = Settings(api_root=API_ROOT_PRODUCTION) + assert settings.status_page_url == STATUS_PAGE_URL_PRODUCTION + assert settings.status_page_url == "https://status.platform.aignostics.com" @pytest.mark.unit -def test_get_status_page_url_staging(record_property) -> None: - """Test that staging environment returns correct status page URL. +def test_status_page_url_staging(record_property) -> None: + """Test that staging environment has correct status page URL in settings. Args: record_property: pytest record_property fixture """ - record_property("tested-item-id", "SPEC-GUI-FRAME") - url = get_status_page_url(API_ROOT_STAGING) - assert url == "https://status.platform-staging.aignostics.com" + record_property("tested-item-id", "SPEC-PLATFORM-SETTINGS") + settings = Settings(api_root=API_ROOT_STAGING) + assert settings.status_page_url == STATUS_PAGE_URL_STAGING + assert settings.status_page_url == "https://status.platform-staging.aignostics.com" @pytest.mark.unit -def test_get_status_page_url_dev(record_property) -> None: - """Test that dev environment returns None (no status page). +def test_status_page_url_dev(record_property) -> None: + """Test that dev environment has no status page URL in settings. Args: record_property: pytest record_property fixture """ - record_property("tested-item-id", "SPEC-GUI-FRAME") - url = get_status_page_url(API_ROOT_DEV) - assert url is None + record_property("tested-item-id", "SPEC-PLATFORM-SETTINGS") + settings = Settings(api_root=API_ROOT_DEV) + assert settings.status_page_url == STATUS_PAGE_URL_DEV + assert settings.status_page_url is None @pytest.mark.unit -def test_get_status_page_url_test(record_property) -> None: - """Test that test environment returns None (no status page). +def test_status_page_url_test(record_property) -> None: + """Test that test environment has no status page URL in settings. Args: record_property: pytest record_property fixture """ - record_property("tested-item-id", "SPEC-GUI-FRAME") - url = get_status_page_url(API_ROOT_TEST) - assert url is None + record_property("tested-item-id", "SPEC-PLATFORM-SETTINGS") + settings = Settings(api_root=API_ROOT_TEST) + assert settings.status_page_url == STATUS_PAGE_URL_TEST + assert settings.status_page_url is None @pytest.mark.unit -def test_get_status_page_url_unknown(record_property) -> None: - """Test that unknown environment returns None (no status page). +def test_status_page_url_configurable(record_property, monkeypatch) -> None: + """Test that status page URL can be explicitly configured via settings. Args: record_property: pytest record_property fixture + monkeypatch: pytest monkeypatch fixture """ - record_property("tested-item-id", "SPEC-GUI-FRAME") - url = get_status_page_url("https://custom.example.com") - assert url is None + record_property("tested-item-id", "SPEC-PLATFORM-SETTINGS") + # Test that we can override the status page URL + custom_url = "https://custom-status.example.com" + monkeypatch.setenv("AIGNOSTICS_STATUS_PAGE_URL", custom_url) + settings = Settings(api_root=API_ROOT_PRODUCTION) + assert settings.status_page_url == custom_url +