diff --git a/application/single_app/config.py b/application/single_app/config.py index 657017bd0..bc094a2aa 100644 --- a/application/single_app/config.py +++ b/application/single_app/config.py @@ -96,7 +96,7 @@ EXECUTOR_TYPE = 'thread' EXECUTOR_MAX_WORKERS = 30 SESSION_TYPE = 'filesystem' -VERSION = "0.250.210" +VERSION = "0.250.211" IS_DEVELOPMENT = is_development_env_enabled() SESSION_COOKIE_SAMESITE = os.getenv('SESSION_COOKIE_SAMESITE', 'Lax') diff --git a/application/single_app/templates/_sidebar_nav.html b/application/single_app/templates/_sidebar_nav.html index a19406c23..96e89d150 100644 --- a/application/single_app/templates/_sidebar_nav.html +++ b/application/single_app/templates/_sidebar_nav.html @@ -120,7 +120,7 @@ - {% if sidebar_settings.allow_user_workflows %} + {% if file_sync_enabled or sidebar_settings.enable_semantic_kernel %} {% endif %} - {% if sidebar_settings.per_user_semantic_kernel and sidebar_settings.enable_semantic_kernel %} - + {% if file_sync_enabled %} {% endif %} @@ -161,24 +156,31 @@ {% endif %} - {% if file_sync_enabled %} + {% if sidebar_settings.per_user_semantic_kernel and sidebar_settings.enable_semantic_kernel and sidebar_settings.allow_user_agents %} + {% if sidebar_settings.allow_user_plugins %} {% endif %} - {% if file_sync_enabled or sidebar_settings.enable_semantic_kernel or sidebar_settings.enable_multi_model_endpoints %} + {% endif %} + {% if sidebar_settings.allow_user_workflows %} + {% endif %} {% else %} - + Personal {% endif %} @@ -188,7 +190,7 @@ - {% if sidebar_settings.allow_group_agents and sidebar_settings.enable_semantic_kernel %} - + {% endif %} + {% if file_sync_enabled %} {% endif %} @@ -222,24 +226,31 @@ {% endif %} - {% if file_sync_enabled %} + {% if sidebar_settings.per_user_semantic_kernel and sidebar_settings.enable_semantic_kernel and sidebar_settings.allow_group_agents %} + {% if sidebar_settings.allow_group_plugins %} + + {% endif %} {% endif %} - {% if file_sync_enabled or sidebar_settings.enable_semantic_kernel or sidebar_settings.enable_multi_model_endpoints %} - {% endif %} {% else %} - + Groups {% endif %} diff --git a/application/single_app/templates/group_workspaces.html b/application/single_app/templates/group_workspaces.html index 6fbffd10f..017374ca0 100644 --- a/application/single_app/templates/group_workspaces.html +++ b/application/single_app/templates/group_workspaces.html @@ -491,26 +491,26 @@

No active group selected

@@ -533,23 +533,21 @@

No active group selected

Documents - {% if file_sync_enabled %} - {% endif %} {% if group_workspace_identities_enabled %} {% endif %} + {% if file_sync_enabled %} - {% if settings.allow_group_workflows %} - {% endif %} {% if settings.per_user_semantic_kernel and settings.enable_semantic_kernel %} - {% if settings.allow_group_agents %} + {% if settings.enable_semantic_kernel and settings.allow_group_custom_endpoints and settings.enable_multi_model_endpoints %} - + {% endif %} + {% if settings.allow_group_agents %} {% if settings.enable_semantic_kernel and settings.allow_group_plugins %} {% endif %} - {% endif %} - {% if settings.enable_semantic_kernel and settings.allow_group_custom_endpoints and settings.enable_multi_model_endpoints %} + {% endif %} {% endif %} + {% if settings.allow_group_workflows %} + + {% endif %} diff --git a/application/single_app/templates/workspace.html b/application/single_app/templates/workspace.html index 8e39062f8..e4f72124e 100644 --- a/application/single_app/templates/workspace.html +++ b/application/single_app/templates/workspace.html @@ -604,27 +604,27 @@

Personal Workspace

@@ -646,23 +646,21 @@

Personal Workspace

Documents - {% if file_sync_enabled %} - {% endif %} {% if workspace_identities_enabled %} {% endif %} + {% if file_sync_enabled %} + {% endif %} + {% if settings.allow_user_custom_endpoints and settings.enable_multi_model_endpoints %} + + {% endif %} {% if settings.per_user_semantic_kernel and settings.enable_semantic_kernel %} {% if settings.allow_user_agents %} - - {% if settings.allow_user_plugins %} {% endif %} + + {% endif %} {% endif %} {% if settings.allow_user_workflows %} @@ -749,23 +766,6 @@

Personal Workspace

{% endif %} - {% if settings.allow_user_custom_endpoints and settings.enable_multi_model_endpoints %} - - {% endif %} diff --git a/docs/explanation/features/index.md b/docs/explanation/features/index.md index 9c2512908..66720f2f2 100644 --- a/docs/explanation/features/index.md +++ b/docs/explanation/features/index.md @@ -47,6 +47,7 @@ category: Version History - [Data Management Durable Backup Jobs](DATA_MANAGEMENT_BACKUP_MIGRATION.md) - [Data Management Migration Resilience](DATA_MANAGEMENT_MIGRATION_RESILIENCE.md) - [Migration Provenance](MIGRATION_PROVENANCE.md) +- [Workspace Section Order](v0.250.211/WORKSPACE_SECTION_ORDER.md) - [Microsoft Teams App SSO](v0.242.072/TEAMS_APP_SSO.md) - [Tabular SK Large Result Pagination](v0.242.067/TABULAR_SK_LARGE_RESULT_PAGINATION.md) - [Model Endpoint Model Icon Picker](v0.242.060/MODEL_ENDPOINT_MODEL_ICON_PICKER.md) diff --git a/docs/explanation/features/v0.250.211/WORKSPACE_SECTION_ORDER.md b/docs/explanation/features/v0.250.211/WORKSPACE_SECTION_ORDER.md new file mode 100644 index 000000000..d349d57c4 --- /dev/null +++ b/docs/explanation/features/v0.250.211/WORKSPACE_SECTION_ORDER.md @@ -0,0 +1,174 @@ +# Workspace Section Order + +## Overview + +Workspace sections now render in a single canonical order of operations across every surface +that lists them. Previously each surface used its own ordering, which made it hard for users and +admins to see how the pieces relate — that Identities feed both Sync and Actions, that Actions +belong to Agents, and that Workflows consume Agents. + +- **Version implemented:** 0.250.211 +- **Related issue:** [#1255](https://github.com/microsoft/simplechat/issues/1255) +- **Dependencies:** none — this change is presentation-only and adds no new settings + +## Canonical order + +Sections still appear only when their existing feature gates are enabled. When a section is +enabled it always appears in this position relative to the others: + +| # | Section | Why it sits here | +|---|---------|------------------| +| 1 | **Documents** | The knowledge you bring into the workspace | +| 2 | **Prompts** | The instructions you reuse against that knowledge | +| 3 | **Identities** | The credentials both Sync and Actions resolve against | +| 4 | **Sync** | Uses Identities to pull Documents in automatically | +| 5 | **Endpoints** | The models that Actions, Agents, and Workflows run on | +| 6 | **Actions** | Governed tools, built on Identities and Endpoints | +| 7 | **Agents** | Assemble Documents, Prompts, Actions, and Endpoints | +| 8 | **Workflows** | Run Agents manually, on a schedule, or on a trigger | + +Each entry depends on the ones above it, so reading the tab strip left to right now describes how +a workspace is actually built up. + +## Architecture + +### Surfaces kept in sync + +Three separate surfaces list workspace sections, and all three now use the canonical order: + +| Surface | Personal | Group | +|---------|----------|-------| +| Top `nav-tabs` bar | `#workspaceTab` in `templates/workspace.html` | `#groupWorkspaceTab` in `templates/group_workspaces.html` | +| Collapsed/mobile **Section** dropdown | `#workspace-section-select` | `#group-workspace-section-select` | +| Left-hand sidebar submenu | `#personal-workspace-submenu` in `templates/_sidebar_nav.html` | `#group-workspace-submenu` in `templates/_sidebar_nav.html` | + +`static/js/workspace_section_switcher.js` maps the dropdown's option values onto tab button ids, +so the dropdown and the tab strip must be edited together — the switcher does not derive one from +the other. + +### Public workspaces + +Public workspaces already satisfied the canonical relative order and were left unchanged: + +- `templates/public_workspaces.html` lists **Documents** then **Prompts**. +- `templates/manage_public_workspace.html` lists **Identities** before **Sync** among its + management tabs (General, Membership, Stats, Identities, Sync, Settings). + +### Tab panes are deliberately not reordered + +Bootstrap resolves tab panes by `id` / `data-bs-target`, and inactive panes are hidden, so pane +DOM order has no visual or accessibility effect. Panes were left in place to keep the change +reviewable — `group_workspaces.html` alone is roughly 389 KB. + +### Gate restructuring + +Moving Actions ahead of Agents, and Endpoints ahead of both, required unwinding nested Jinja +conditionals without changing their effective conditions. For example, the personal workspace +Actions tab was nested inside the Agents gate: + +```jinja +{% if settings.per_user_semantic_kernel and settings.enable_semantic_kernel %} + {% if settings.allow_user_agents %} + {% if settings.allow_user_plugins %} + {# Actions #} + {% endif %} + {# Agents #} + {% endif %} +{% endif %} +``` + +Actions still requires `per_user_semantic_kernel`, `enable_semantic_kernel`, `allow_user_agents`, +and `allow_user_plugins` — only its render position changed. + +## Navigation gating fixes + +The sidebar previously used gates that did not match the tabs they linked to, so a sidebar link +could point at a tab that was never rendered. Those gates are now aligned: + +| Navigation item | Previous gate | Corrected gate | +|-----------------|---------------|----------------| +| Personal → Your Agents | `per_user_semantic_kernel and enable_semantic_kernel` | added `allow_user_agents` | +| Personal → Your Actions | `per_user_semantic_kernel and enable_semantic_kernel` | added `allow_user_agents and allow_user_plugins` | +| Personal → Identities | `file_sync_enabled or enable_semantic_kernel or enable_multi_model_endpoints` | `file_sync_enabled or enable_semantic_kernel` | +| Group → Group Agents | `allow_group_agents and enable_semantic_kernel` | added `per_user_semantic_kernel` | +| Group → Group Actions | `allow_group_agents and enable_semantic_kernel` | added `per_user_semantic_kernel and allow_group_plugins` | +| Group → Identities | `file_sync_enabled or enable_semantic_kernel or enable_multi_model_endpoints` | `file_sync_enabled or enable_semantic_kernel` | +| Group → Group Workflows | *link did not exist* | added, gated by `allow_group_workflows` | + +The group sidebar had no Workflows entry at all even though the group Workflows tab has shipped +for some time, so group workflows were unreachable from the left-hand navigation. + +## Configuration + +No new configuration. The sections that appear continue to be controlled by the existing settings: + +| Section | Personal gate | Group gate | +|---------|---------------|------------| +| Documents | always | always | +| Prompts | always | always | +| Identities | `enable_file_sync*` or `enable_semantic_kernel` | `enable_file_sync*` or `enable_semantic_kernel` | +| Sync | `enable_file_sync*` | `enable_file_sync*` | +| Endpoints | `allow_user_custom_endpoints` + `enable_multi_model_endpoints` | `per_user_semantic_kernel` + `enable_semantic_kernel` + `allow_group_custom_endpoints` + `enable_multi_model_endpoints` | +| Actions | `per_user_semantic_kernel` + `enable_semantic_kernel` + `allow_user_agents` + `allow_user_plugins` | `per_user_semantic_kernel` + `enable_semantic_kernel` + `allow_group_agents` + `allow_group_plugins` | +| Agents | `per_user_semantic_kernel` + `enable_semantic_kernel` + `allow_user_agents` | `per_user_semantic_kernel` + `enable_semantic_kernel` + `allow_group_agents` | +| Workflows | `allow_user_workflows` | `allow_group_workflows` | + +`enable_file_sync*` is the resolved per-scope File Sync availability computed by +`is_file_sync_enabled_for_user` / `is_file_sync_enabled_for_group`. + +## File structure + +| File | Change | +|------|--------| +| `application/single_app/templates/workspace.html` | Reordered `#workspaceTab` and `#workspace-section-select` | +| `application/single_app/templates/group_workspaces.html` | Reordered `#groupWorkspaceTab` and `#group-workspace-section-select` | +| `application/single_app/templates/_sidebar_nav.html` | Reordered both submenus, added the group Workflows link, aligned gates, refreshed parent tooltips | +| `application/single_app/config.py` | `VERSION` bumped to `0.250.211` | +| `functional_tests/test_workspace_section_order.py` | New coverage for the canonical order and gating parity | +| `functional_tests/test_endpoints_tab_order_visibility.py` | Updated for the new Endpoints → Actions → Agents order | + +## Usage + +Nothing to enable. Users see the new order the next time they open a workspace page. Sections that +an admin has disabled remain hidden, and the remaining sections close up while preserving their +relative positions. + +## Testing and validation + +`functional_tests/test_workspace_section_order.py` renders each of the six section lists in +isolation with Jinja and checks: + +1. **Canonical order when everything is enabled** — all six surfaces render exactly the eight + sections in the canonical order. +2. **Disabled features only remove sections** — every combination of the eight relevant feature + flags (256 per scope) still yields a subsequence of the canonical order, with no duplicates and + with Documents and Prompts always leading. +3. **Gating lockstep** — for every flag combination, the tab strip, the Section dropdown, and the + sidebar submenu expose an identical set of sections. This is what guards the navigation gating + fixes above. +4. **Permission markers survive** — the permission-aware group Identities markers + (`data-group-identities-tab-nav`, `data-group-identities-sidebar-nav`, + `data-group-identities-section-option`) are still present after the reordering. +5. **Public workspaces** — Documents precedes Prompts, and Identities precedes Sync on the manage + page. + +The test was verified against the pre-change templates and fails there, confirming it is not +vacuous. + +### Known limitations + +- Tab pane DOM order still reflects the historical ordering. This is intentional and has no user + impact, but it means pane order and tab order no longer visually correspond when reading the + template source. +- The personal workspace guided tutorial (`static/js/workspace/workspace-tutorial.js`) still walks + Documents → Prompts → Agents → Actions. Its selectors are id-based so it functions correctly, + but its narrative sequence no longer mirrors the tab order. +- Section grouping (dividers or group labels such as Content / Connections / Automation) is a + possible follow-up; this change establishes the ordering those groups would build on. + +## Related documentation + +- `docs/explanation/fixes/v0.241.001/ENDPOINTS_TAB_ORDER_VISIBILITY_FIX.md` — earlier work that + introduced the endpoints tab gating this change reorders +- `docs/explanation/features/v0.229.001/LEFT_HAND_NAVIGATION_MENU.md` — the left-hand navigation + this change keeps in sync with the tab strip diff --git a/docs/explanation/release_notes.md b/docs/explanation/release_notes.md index 6fd25b120..9b7490e80 100644 --- a/docs/explanation/release_notes.md +++ b/docs/explanation/release_notes.md @@ -2,6 +2,28 @@ For feature-focused and fix-focused drill-downs by version, see [Features by Version](/explanation/features/) and [Fixes by Version](/explanation/fixes/). +### **(v0.250.211)** + +#### User Interface Enhancements + +* **Consistent Workspace Section Order** + * Workspace sections now follow a single order of operations everywhere they are listed: Documents, Prompts, Identities, Sync, Endpoints, Actions, Agents, Workflows. + * The order reflects how a workspace is actually built up, so it is clearer that Identities feed both Sync and Actions, that Actions belong to Agents, and that Workflows run Agents. + * Applied to the tab strip, the collapsed Section dropdown, and the left-hand sidebar submenus for personal and group workspaces. Public workspaces already matched this order and were left unchanged. + * Sections that an admin has disabled stay hidden; the remaining sections simply close up while keeping their relative positions. + * (Ref: #1255, `workspace.html`, `group_workspaces.html`, `_sidebar_nav.html`, `WORKSPACE_SECTION_ORDER.md`) + +#### Bug Fixes + +* **Group Workflows Missing From Sidebar Navigation** + * Added the missing Group Workflows link to the left-hand group workspace submenu. Group workflows previously had a working tab but no way to reach it from the sidebar. + * (Ref: #1255, `_sidebar_nav.html`, group workflows navigation) + +* **Sidebar Links Pointing At Unrendered Workspace Tabs** + * Fixed left-hand navigation links whose visibility rules did not match the tabs they opened, so a link could appear for a section that was never rendered. + * Personal Agents and Actions links now respect the user agent and plugin permissions, group Agents and Actions links now respect per-user Semantic Kernel and group plugin permissions, and both Identities links now match their tab's File Sync and Semantic Kernel conditions. + * (Ref: #1255, `_sidebar_nav.html`, `test_workspace_section_order.py`) + ### **(v0.250.210)** #### Bug Fixes diff --git a/functional_tests/test_endpoints_tab_order_visibility.py b/functional_tests/test_endpoints_tab_order_visibility.py index cde0fd669..90eed4254 100644 --- a/functional_tests/test_endpoints_tab_order_visibility.py +++ b/functional_tests/test_endpoints_tab_order_visibility.py @@ -2,13 +2,20 @@ # test_endpoints_tab_order_visibility.py """ Functional test for workspace/group endpoints tab order and visibility. -Version: 0.239.197 +Version: 0.250.211 Implemented in: 0.239.197 +Reordered in: 0.250.211 This test ensures endpoints tabs only render when multi-endpoint management is enabled, the personal and group admin-controlled toggles stay hidden, and the admin multi-endpoint toggle is removed after migration while its script keeps the endpoint editor initialized. + +Since 0.250.211 the workspace sections follow a canonical order of operations +(Documents, Prompts, Identities, Sync, Endpoints, Actions, Agents, Workflows), so +Endpoints now renders ahead of Actions and Agents. Tab pane DOM order is +deliberately not asserted: Bootstrap resolves panes by id and hides inactive ones, +so pane order has no visual or accessibility effect. """ import os @@ -29,20 +36,17 @@ def read_file_text(file_path): def test_workspace_endpoints_tab_order_visibility(): content = read_file_text(WORKSPACE_TEMPLATE) - agents_idx = content.find('id="agents-tab-btn"') - actions_idx = content.find('id="plugins-tab-btn"') endpoints_idx = content.find('id="endpoints-tab-btn"') + actions_idx = content.find('id="plugins-tab-btn"') + agents_idx = content.find('id="agents-tab-btn"') - assert agents_idx != -1, "Agents tab button missing in workspace template." - assert actions_idx != -1, "Actions tab button missing in workspace template." assert endpoints_idx != -1, "Endpoints tab button missing in workspace template." - assert agents_idx < actions_idx < endpoints_idx, "Workspace tab order should be Agents -> Actions -> Endpoints." + assert actions_idx != -1, "Actions tab button missing in workspace template." + assert agents_idx != -1, "Agents tab button missing in workspace template." + assert endpoints_idx < actions_idx < agents_idx, "Workspace tab order should be Endpoints -> Actions -> Agents." - actions_pane_idx = content.find('id="plugins-tab"') - endpoints_pane_idx = content.find('id="endpoints-tab"') - assert actions_pane_idx != -1, "Actions tab pane missing in workspace template." - assert endpoints_pane_idx != -1, "Endpoints tab pane missing in workspace template." - assert actions_pane_idx < endpoints_pane_idx, "Endpoints tab pane should appear after actions pane." + assert content.find('id="plugins-tab"') != -1, "Actions tab pane missing in workspace template." + assert content.find('id="endpoints-tab"') != -1, "Endpoints tab pane missing in workspace template." expected_gate = "{% if settings.allow_user_custom_endpoints and settings.enable_multi_model_endpoints %}" assert content.count(expected_gate) >= 2, "Workspace endpoints tab button and pane should share the multi-endpoint gate." @@ -55,20 +59,17 @@ def test_workspace_endpoints_tab_order_visibility(): def test_group_endpoints_tab_order_visibility(): content = read_file_text(GROUP_TEMPLATE) - agents_idx = content.find('id="group-agents-tab-btn"') - actions_idx = content.find('id="group-plugins-tab-btn"') endpoints_idx = content.find('id="group-endpoints-tab-btn"') + actions_idx = content.find('id="group-plugins-tab-btn"') + agents_idx = content.find('id="group-agents-tab-btn"') - assert agents_idx != -1, "Group agents tab button missing in group workspace template." - assert actions_idx != -1, "Group actions tab button missing in group workspace template." assert endpoints_idx != -1, "Group endpoints tab button missing in group workspace template." - assert agents_idx < actions_idx < endpoints_idx, "Group tab order should be Agents -> Actions -> Endpoints." + assert actions_idx != -1, "Group actions tab button missing in group workspace template." + assert agents_idx != -1, "Group agents tab button missing in group workspace template." + assert endpoints_idx < actions_idx < agents_idx, "Group tab order should be Endpoints -> Actions -> Agents." - actions_pane_idx = content.find('id="group-plugins-tab"') - endpoints_pane_idx = content.find('id="group-endpoints-tab"') - assert actions_pane_idx != -1, "Group actions tab pane missing in group workspace template." - assert endpoints_pane_idx != -1, "Group endpoints tab pane missing in group workspace template." - assert actions_pane_idx < endpoints_pane_idx, "Group endpoints tab pane should appear after actions pane." + assert content.find('id="group-plugins-tab"') != -1, "Group actions tab pane missing in group workspace template." + assert content.find('id="group-endpoints-tab"') != -1, "Group endpoints tab pane missing in group workspace template." expected_gate = "{% if settings.enable_semantic_kernel and settings.allow_group_custom_endpoints and settings.enable_multi_model_endpoints %}" assert content.count(expected_gate) >= 2, "Group endpoints tab button and pane should share the multi-endpoint gate." diff --git a/functional_tests/test_workspace_section_order.py b/functional_tests/test_workspace_section_order.py new file mode 100644 index 000000000..272d5b173 --- /dev/null +++ b/functional_tests/test_workspace_section_order.py @@ -0,0 +1,404 @@ +#!/usr/bin/env python3 +# test_workspace_section_order.py +""" +Functional test for the canonical workspace section order. +Version: 0.250.211 +Implemented in: 0.250.211 + +This test ensures every surface that lists workspace sections renders them in the +canonical order of operations (Documents, Prompts, Identities, Sync, Endpoints, +Actions, Agents, Workflows), and that each surface shows exactly the same sections +for a given set of feature flags so a navigation link can never point at a tab that +was never rendered. +""" + +import itertools +import re +import traceback +from pathlib import Path + +from jinja2 import Environment + +from test_support.versioning import assert_app_version_at_least + +IMPLEMENTED_VERSION = "0.250.211" + +REPO_ROOT = Path(__file__).resolve().parents[1] +TEMPLATE_DIR = REPO_ROOT / "application" / "single_app" / "templates" +WORKSPACE_TEMPLATE = TEMPLATE_DIR / "workspace.html" +GROUP_TEMPLATE = TEMPLATE_DIR / "group_workspaces.html" +SIDEBAR_TEMPLATE = TEMPLATE_DIR / "_sidebar_nav.html" +PUBLIC_TEMPLATE = TEMPLATE_DIR / "public_workspaces.html" +MANAGE_PUBLIC_TEMPLATE = TEMPLATE_DIR / "manage_public_workspace.html" + +CANONICAL_ORDER = [ + "documents", + "prompts", + "identities", + "sync", + "endpoints", + "actions", + "agents", + "workflows", +] + +NAV_BUTTON_RE = re.compile(r'\bid="([a-z-]+-tab-btn)"') +SELECT_OPTION_RE = re.compile(r'