Skip to content

Add V2 UI packaging and Docker integration#88

Open
tomerqodo wants to merge 4 commits into
qodo_combined_100_qodo_grep_sentry_1_base_add_v2_ui_packaging_and_docker_integration_pr253from
qodo_combined_100_qodo_grep_sentry_1_head_add_v2_ui_packaging_and_docker_integration_pr253
Open

Add V2 UI packaging and Docker integration#88
tomerqodo wants to merge 4 commits into
qodo_combined_100_qodo_grep_sentry_1_base_add_v2_ui_packaging_and_docker_integration_pr253from
qodo_combined_100_qodo_grep_sentry_1_head_add_v2_ui_packaging_and_docker_integration_pr253

Conversation

@tomerqodo
Copy link
Copy Markdown

Benchmark PR from qodo-benchmark#253

desertaxle and others added 4 commits December 31, 2025 11:18
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>
@qodo-code-review
Copy link
Copy Markdown

Code Review by Qodo (Alpha)

🐞 Bugs (2) 📘 Rule Violations (3) 📎 Requirement Gaps (0) 💡 Suggestions (0)

Grey Divider


Action Required

1. UI cache check too weak 🐞 Bug
Description
• 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.

correctness

Code

src/prefect/server/api/server.py[R459-481]

+    # 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":
Evidence
The server writes a cache_key containing UI variant and Prefect version into UI_SERVE_BASE, but
the new check ignores those fields and only tests the base_url suffix. Because static_dir can be
an externally configured path (PREFECT_UI_STATIC_DIRECTORY), v1 and v2 can share the same
static_dir, making the weakened check able to incorrectly treat v1 assets as valid when v2 is
enabled. Default serve_base is "/", which makes endswith("/") particularly permissive.

src/prefect/server/api/server.py[455-548]
src/prefect/settings/models/server/ui.py[37-45]

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 = &quot;v{1|2}:{prefect.__version__}:{base_url}&quot;` 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


2. ui-v2 workflow copy non-idempotent 🐞 Bug
Description
• 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.

reliability

Code

.github/workflows/python-package.yaml[R46-52]

+      - name: Build UI v2
+        working-directory: ui-v2
+        run: |
+          npm ci
+          npm run build
+          cp -r dist ../src/prefect/server/ui-v2
+
Evidence
The workflow copies the build output using cp -r dist .../ui-v2. The Docker build stage expects
the dist contents to live directly at ./src/prefect/server/ui-v2 (not nested under an extra
dist/ directory), and runtime code serves from prefect/server/ui-v2. Without cleaning the
destination, subsequent runs or pre-existing directories can produce the wrong layout and stale
assets.

.github/workflows/python-package.yaml[38-55]
Dockerfile[102-107]
src/prefect/init.py[67-72]

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



Remediation Recommended

3. `ui_v2_static_subpath` missing underscore 📘 Rule Violation
Description
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.

reliability

Code

src/prefect/init.py[R67-70]

+# 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"
+
Evidence
Compliance rule 8 requires internal-only attributes to be prefixed with an underscore. In
src/prefect/__init__.py, existing UI internals use dunder naming (e.g., __ui_static_subpath__),
but the newly added V2 counterpart is exported publicly as ui_v2_static_subpath and then used from
prefect elsewhere, indicating it is intended for internal wiring rather than a stable public API.

AGENTS.md
src/prefect/init.py[60-72]
src/prefect/server/api/server.py[459-466]

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


4. Boolean `v2_enabled` poorly named 📘 Rule Violation
Description
• 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.

correctness

Code

src/prefect/settings/models/server/ui.py[R22-25]

+    v2_enabled: bool = Field(
+        default=False,
+        description="Whether to serve the experimental V2 UI instead of the default V1 UI.",
+    )
Evidence
Compliance rule 2 requires boolean identifiers to use clear prefixes like
is_/has_/can_/should_. The PR introduces a new boolean field v2_enabled and uses the same
name in server routing logic, which violates the naming guidance.

Rule 2: Generic: Meaningful Naming and Self-Documenting Code
src/prefect/settings/models/server/ui.py[22-25]
src/prefect/server/api/server.py[459-465]

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


5. Tests not mirroring source path 📘 Rule Violation
Description
• 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.

reliability

Code

tests/test_settings.py[R490-494]

    "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},
Evidence
Compliance rule 9 requires tests to mirror source code structure under tests/. The updated setting
is implemented under src/prefect/settings/models/server/ui.py, but the corresponding test update
is placed in tests/test_settings.py, which does not mirror the source module path.

AGENTS.md
src/prefect/settings/models/server/ui.py[9-26]
tests/test_settings.py[488-494]

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


Grey Divider

Qodo Logo

Comment thread src/prefect/__init__.py
Comment on lines +67 to +70
# 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"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation Recommended

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

Comment on lines +22 to +25
v2_enabled: bool = Field(
default=False,
description="Whether to serve the experimental V2 UI instead of the default V1 UI.",
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation Recommended

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

Comment thread tests/test_settings.py
Comment on lines 490 to 494
"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},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation Recommended

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

Comment on lines +459 to 481
# 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":
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action Required

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

Comment on lines +46 to +52
- name: Build UI v2
working-directory: ui-v2
run: |
npm ci
npm run build
cp -r dist ../src/prefect/server/ui-v2

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action Required

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants