Add V2 UI packaging and Docker integration#88
Conversation
Add support for building and serving the V2 React UI alongside V1: - Add base path placeholder to V2 vite.config.ts for dynamic serving - Add PREFECT_UI_V2_ENABLED setting to switch between UIs - Add V2 UI path constants in prefect.__init__ - Update Dockerfile to build both UIs (V1 with Node 20, V2 with Node 22) - Update pyproject.toml to include ui-v2 as build artifact - Update server to serve V2 UI when setting enabled - Update GitHub Actions workflow to build V2 UI V1 remains the default. Enable V2 with PREFECT_UI_V2_ENABLED=true. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Code Review by Qodo (Alpha)
1. UI cache check too weak
|
| # The absolute path to the built V2 UI within the Python module, used by | ||
| # `prefect server start` to serve a dynamic build of the V2 UI | ||
| ui_v2_static_subpath: pathlib.Path = __module_path__ / "server" / "ui_v2_build" | ||
|
|
There was a problem hiding this comment.
1. ui_v2_static_subpath missing underscore 📘 Rule Violation
• ui_v2_static_subpath appears to be an internal implementation detail (a module-level path constant), but it is introduced as a public attribute without a leading underscore/dunder. • This expands the public surface area of prefect and makes accidental external usage more likely, reducing maintainability when refactoring UI internals.
Agent Prompt
## Issue description
A new internal module-level attribute `ui_v2_static_subpath` was added to `prefect` without an underscore/dunder prefix, unintentionally suggesting a public/stable API.
## Issue Context
Existing UI internal path constants in `prefect.__init__` use dunder naming (e.g., `__ui_static_subpath__`). The new V2 equivalent should follow the same convention to avoid expanding the public surface area.
## Fix Focus Areas
- src/prefect/__init__.py[60-72]
- src/prefect/server/api/server.py[459-469]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| v2_enabled: bool = Field( | ||
| default=False, | ||
| description="Whether to serve the experimental V2 UI instead of the default V1 UI.", | ||
| ) |
There was a problem hiding this comment.
2. Boolean v2_enabled poorly named 📘 Rule Violation
• The new boolean setting/variable name v2_enabled does not follow the prescribed boolean-prefix naming convention (is_, has_, can_, should_). • This reduces self-documentation and increases ambiguity about what exactly is enabled (V2 UI? V2 API? etc.), especially as the setting is propagated through server logic.
Agent Prompt
## Issue description
The new boolean setting/variable `v2_enabled` does not follow the codebase naming guidance for boolean identifiers (expected `is_`/`has_`/`can_`/`should_` prefixes).
## Issue Context
This setting determines which UI version is served, so clarity matters. Renaming may require maintaining compatibility with the existing env var name/aliases.
## Fix Focus Areas
- src/prefect/settings/models/server/ui.py[22-25]
- src/prefect/server/api/server.py[459-478]
- tests/test_settings.py[488-494]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| "PREFECT_SERVER_UI_SERVE_BASE": {"test_value": "/base"}, | ||
| "PREFECT_SERVER_UI_SHOW_PROMOTIONAL_CONTENT": {"test_value": False}, | ||
| "PREFECT_SERVER_UI_STATIC_DIRECTORY": {"test_value": "/path/to/static"}, | ||
| "PREFECT_SERVER_UI_V2_ENABLED": {"test_value": True}, | ||
| "PREFECT_SILENCE_API_URL_MISCONFIGURATION": {"test_value": True}, |
There was a problem hiding this comment.
3. Tests not mirroring source path 📘 Rule Violation
• The change adds/updates test coverage for the new server UI setting in a top-level tests/test_settings.py module rather than under a tests/ path mirroring src/prefect/settings/models/server/ui.py. • This breaks the source-to-tests structural mapping requirement, making related tests harder to discover and maintain as the settings code evolves.
Agent Prompt
## Issue description
Tests related to the new server UI V2 setting are updated in a non-mirroring test location (`tests/test_settings.py`) instead of a `tests/` path that mirrors the source module.
## Issue Context
The source change is in `src/prefect/settings/models/server/ui.py`. To keep tests discoverable, add/relocate the relevant assertions to a corresponding mirrored test module under `tests/`.
## Fix Focus Areas
- src/prefect/settings/models/server/ui.py[9-26]
- tests/test_settings.py[488-494]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| # Determine which UI to serve based on setting | ||
| v2_enabled = prefect.settings.get_current_settings().server.ui.v2_enabled | ||
|
|
||
| if v2_enabled: | ||
| source_static_path = prefect.__ui_v2_static_path__ | ||
| static_subpath = prefect.ui_v2_static_subpath | ||
| cache_key = f"v2:{prefect.__version__}:{base_url}" | ||
| else: | ||
| source_static_path = prefect.__ui_static_path__ | ||
| static_subpath = prefect.__ui_static_subpath__ | ||
| cache_key = f"v1:{prefect.__version__}:{base_url}" | ||
|
|
||
| stripped_base_url = base_url.rstrip("/") | ||
| static_dir = ( | ||
| prefect.settings.PREFECT_UI_STATIC_DIRECTORY.value() | ||
| or prefect.__ui_static_subpath__ | ||
| ) | ||
| # For V1, use the path object directly; for V2, convert to string | ||
| if v2_enabled: | ||
| static_dir = prefect.settings.PREFECT_UI_STATIC_DIRECTORY.value() or str( | ||
| static_subpath | ||
| ) | ||
| else: | ||
| static_dir = prefect.settings.PREFECT_UI_STATIC_DIRECTORY.value() or static_subpath | ||
| reference_file_name = "UI_SERVE_BASE" | ||
|
|
||
| if os.name == "nt": |
There was a problem hiding this comment.
4. Ui cache check too weak 🐞 Bug
• The reference-file validation now checks only endswith(base_url) even though the file is written with a cache_key containing UI version (v1/v2) and Prefect version. • If PREFECT_UI_STATIC_DIRECTORY is set (shared for v1 and v2), toggling server.ui.v2_enabled can incorrectly keep serving the previously-copied UI (often v1) because the check will still pass. • This can also prevent UI refresh on Prefect upgrades, leaving stale UI assets in the configured static directory, potentially breaking UI/API compatibility.
Agent Prompt
## Issue description
The UI static-directory cache validation only checks `endswith(base_url)`, ignoring the cached UI variant (v1/v2) and Prefect version that are written into the reference file. This can cause Prefect to keep serving stale or wrong UI assets when `PREFECT_UI_STATIC_DIRECTORY` is set, after upgrades, or when toggling `server.ui.v2_enabled`.
## Issue Context
`create_ui_static_subpath()` writes `cache_key = "v{1|2}:{prefect.__version__}:{base_url}"` to `UI_SERVE_BASE`, but `reference_file_matches_base_url()` now validates only the base_url suffix.
## Fix Focus Areas
- src/prefect/server/api/server.py[455-548]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| - name: Build UI v2 | ||
| working-directory: ui-v2 | ||
| run: | | ||
| npm ci | ||
| npm run build | ||
| cp -r dist ../src/prefect/server/ui-v2 | ||
|
|
There was a problem hiding this comment.
5. Ui-v2 workflow copy non-idempotent 🐞 Bug
• The release workflow uses cp -r dist ../src/prefect/server/ui-v2 without deleting/cleaning the destination directory first. • If src/prefect/server/ui-v2 already exists (e.g., checked-in assets, previous build artifacts, or a rerun on a non-fresh workspace), this can create ui-v2/dist/... nesting and/or leave stale files, leading to packaging/serving incorrect assets.
Agent Prompt
## Issue description
The release workflow’s `cp -r dist ../src/prefect/server/ui-v2` is not idempotent if the destination directory already exists, which can create an incorrect directory structure (`ui-v2/dist/...`) and/or leave stale files.
## Issue Context
Packaging and runtime expect UI v2 assets to be located directly under `src/prefect/server/ui-v2`.
## Fix Focus Areas
- .github/workflows/python-package.yaml[46-52]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Benchmark PR from qodo-benchmark#253