Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion application/single_app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
EXECUTOR_TYPE = 'thread'
EXECUTOR_MAX_WORKERS = 30
SESSION_TYPE = 'filesystem'
VERSION = "0.241.006"
VERSION = "0.241.007"

SECRET_KEY = os.getenv('SECRET_KEY', 'dev-secret-key-change-in-production')

Expand Down
39 changes: 22 additions & 17 deletions application/single_app/templates/group_workspaces.html
Original file line number Diff line number Diff line change
Expand Up @@ -324,25 +324,27 @@ <h2>Group Workspace</h2>
Group Actions
</button>
</li>
{% endif %}
{% endif %}
{% if settings.enable_semantic_kernel and settings.allow_group_custom_endpoints and settings.enable_multi_model_endpoints %}
<li class="nav-item" role="presentation">
<button
class="nav-link"
id="group-endpoints-tab-btn"
data-bs-toggle="tab"
data-bs-target="#group-endpoints-tab"
type="button"
role="tab"
aria-controls="group-endpoints-tab"
aria-selected="false"
>
Group Endpoints
</button>
</li>
{% endif %}
{% endif %}
{% if settings.enable_semantic_kernel and settings.allow_group_custom_endpoints and settings.enable_multi_model_endpoints %}
<li class="nav-item" role="presentation">
<button
class="nav-link"
id="group-endpoints-tab-btn"
data-bs-toggle="tab"
data-bs-target="#group-endpoints-tab"
type="button"
role="tab"
aria-controls="group-endpoints-tab"
aria-selected="false"
>
Group Endpoints
</button>
</li>
{% endif %}
</ul>


<!-- Tab Panes -->
<div class="tab-content" id="groupWorkspaceTabContent">
Expand Down Expand Up @@ -818,7 +820,6 @@ <h2>Group Workspace</h2>
<div id="group-agents-error"></div>
</div>
<!-- End GROUP AGENTS TAB -->
{% endif %}

{% if settings.enable_semantic_kernel and settings.allow_group_plugins %}
<!-- ============= GROUP ACTIONS TAB ============= -->
Expand Down Expand Up @@ -4115,6 +4116,10 @@ <h5 class="alert-heading">

// — Fetch & render group prompts —
function fetchGroupPrompts() {
if (!groupPromptsTableBody || !groupPromptsPagination) {
return;
}

groupPromptsTableBody.innerHTML = `
<tr class="table-loading-row">
<td colspan="2">
Expand Down
46 changes: 46 additions & 0 deletions docs/explanation/fixes/GROUP_PROMPTS_ROLE_UI_NULL_GUARD_FIX.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Group Prompts Role UI Null Guard Fix

Fixed/Implemented in version: **0.241.003**

## Header Information

Issue description:
The group workspace page could throw a client-side exception while refreshing active group state: `Cannot read properties of null (reading 'style')` from `updateGroupPromptsRoleUI()`.

Root cause analysis:
The prompt role-toggle logic assumed `create-group-prompt-section` and `group-prompts-role-warning` always existed in the DOM and wrote directly to `.style.display` without checking for missing nodes.

Version implemented:
`config.py` was updated to `VERSION = "0.241.003"` for this fix.

## Technical Details

Files modified:
- `application/single_app/templates/group_workspaces.html`
- `application/single_app/config.py`
- `ui_tests/test_group_workspace_prompt_role_ui_resilience.py`

Code changes summary:
- Switched the prompt role warning and create button containers to Bootstrap `d-none` state classes instead of inline `display: none` styles.
- Updated `updateGroupPromptsRoleUI()` to tolerate missing prompt DOM nodes and toggle visibility via `classList` instead of unsafe `.style` access.
- Added an early return in `fetchGroupPrompts()` when the prompt table container is unavailable.
- Added a UI regression test that removes the prompt role UI containers before changing the active group and asserts that no page error is raised.

Testing approach:
- Added a Playwright UI regression test covering group changes with missing prompt role containers.
- Targeted validation should include the new UI test file plus a syntax/error pass on the updated template and config version bump.

Impact analysis:
The group workspace now keeps loading and switching groups even when prompt role UI fragments are omitted, customized, or temporarily unavailable.

## Validation

Test results:
The regression test is designed to fail on the old `.style` access and pass once the null-safe toggle logic is present.

Before/after comparison:
- Before: group changes could throw an uncaught promise error from `updateGroupPromptsRoleUI()`.
- After: group changes skip missing prompt role nodes safely and continue updating the rest of the workspace.

User experience improvements:
Users no longer see the prompt role UI exception interrupt the group workspace load flow when those prompt elements are missing.
175 changes: 175 additions & 0 deletions ui_tests/test_group_workspace_prompt_role_ui_resilience.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# test_group_workspace_prompt_role_ui_resilience.py
"""
UI test for group workspace prompt role UI resilience.
Version: 0.241.003
Implemented in: 0.241.003

This test ensures the group workspace can refresh active group context without
raising client-side errors when the prompt role warning and create button
containers are absent from the DOM.
"""

import json
import os
from pathlib import Path

import pytest


BASE_URL = os.getenv("SIMPLECHAT_UI_BASE_URL", "").rstrip("/")
STORAGE_STATE = os.getenv("SIMPLECHAT_UI_STORAGE_STATE", "")


def _require_ui_env():
if not BASE_URL:
pytest.skip("Set SIMPLECHAT_UI_BASE_URL to run this UI test.")
if not STORAGE_STATE or not Path(STORAGE_STATE).exists():
pytest.skip(
"Set SIMPLECHAT_UI_STORAGE_STATE to a valid authenticated Playwright storage state file."
)


def _fulfill_json(route, payload, status=200):
route.fulfill(
status=status,
content_type="application/json",
body=json.dumps(payload),
)


@pytest.mark.ui
def test_group_workspace_group_change_tolerates_missing_prompt_role_elements(playwright):
"""Validate that a group change does not raise prompt role UI null errors."""
_require_ui_env()

browser = playwright.chromium.launch()
context = browser.new_context(
storage_state=STORAGE_STATE,
viewport={"width": 1440, "height": 900},
)
page = context.new_page()

page_errors = []
page.on("pageerror", lambda error: page_errors.append(str(error)))

group_payloads = [
{
"groups": [
{
"id": "group-alpha",
"name": "Alpha Team",
"isActive": True,
"userRole": "Owner",
"status": "active",
},
{
"id": "group-beta",
"name": "Beta Team",
"isActive": False,
"userRole": "Admin",
"status": "active",
},
]
},
{
"groups": [
{
"id": "group-alpha",
"name": "Alpha Team",
"isActive": False,
"userRole": "Owner",
"status": "active",
},
{
"id": "group-beta",
"name": "Beta Team",
"isActive": True,
"userRole": "Admin",
"status": "active",
},
]
},
]

def handle_groups(route):
payload = group_payloads[0]
if len(group_payloads) > 1:
payload = group_payloads.pop(0)
_fulfill_json(route, payload)

page.route("**/api/groups?page_size=1000", handle_groups)
page.route(
"**/api/groups/setActive",
lambda route: _fulfill_json(route, {"success": True}),
)
page.route(
"**/api/group_documents?*",
lambda route: _fulfill_json(
route,
{
"documents": [],
"page": 1,
"page_size": 10,
"total_count": 0,
},
),
)
page.route(
"**/api/group_documents/tags?*",
lambda route: _fulfill_json(route, {"tags": []}),
)

try:
response = page.goto(f"{BASE_URL}/group_workspaces", wait_until="networkidle")

assert response is not None, "Expected a navigation response when loading /group_workspaces."
assert response.ok, f"Expected /group_workspaces to load successfully, got HTTP {response.status}."

page.wait_for_function(
"""
() => {
const tbody = document.querySelector('#group-documents-table tbody');
return tbody && tbody.textContent.includes('No documents found in this group.');
}
"""
)

page.evaluate(
"""
() => {
document.getElementById('create-group-prompt-section')?.remove();
document.getElementById('group-prompts-role-warning')?.remove();

const select = document.getElementById('group-select');
if (select) {
select.value = 'group-beta';
}

const selectedText = document.querySelector('#group-dropdown-button .selected-group-text');
if (selectedText) {
selectedText.textContent = 'Beta Team';
}
}
"""
)

page.locator("#btn-change-group").click()
page.wait_for_function(
"""
() => {
const role = document.getElementById('user-role');
return role && role.textContent.trim() === 'Admin';
}
"""
)

null_style_errors = [
error
for error in page_errors
if "Cannot read properties of null (reading 'style')" in error
]

assert not null_style_errors, f"Unexpected prompt role UI errors: {null_style_errors}"
finally:
context.close()
browser.close()
Loading