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 @@ -101,7 +101,7 @@
EXECUTOR_TYPE = 'thread'
EXECUTOR_MAX_WORKERS = 30
SESSION_TYPE = 'filesystem'
VERSION = "0.261.295"
VERSION = "0.261.296"
IS_DEVELOPMENT = is_development_env_enabled()

# Opt-out for deployments where App Service Easy Auth is active but the platform
Expand Down
23 changes: 23 additions & 0 deletions application/single_app/functions_support_latest_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
visibility. Shortcuts keep their ``requires_settings`` so the V2 preview can
show or hide them as the draft changes; the documentation guide buttons
depend on a switch in the same section.

``build_user_latest_features_payload`` serves the end-user page itself rather than
the admin preview: only the announcements an administrator shared, with each
shortcut already filtered against the stored settings, exactly as the classic
Support page renders them.
"""

import re
Expand All @@ -31,6 +36,7 @@
get_admin_latest_feature_release_groups_for_settings,
get_default_support_latest_features_visibility,
get_support_latest_feature_release_groups_for_preview,
get_visible_support_latest_feature_groups,
)


Expand Down Expand Up @@ -233,3 +239,20 @@ def build_latest_features_payload(settings, *, resolve_endpoint_url, resolve_sta
for group in get_support_latest_feature_release_groups_for_preview(settings)
],
}


def build_user_latest_features_payload(settings, *, resolve_endpoint_url, resolve_static_url, version):
"""Return the Latest Features announcements shared with end users, for the V2 page.

Mirrors the classic Support page: only the announcements an administrator left
visible, each shortcut already filtered against the stored settings, and a
release group left out entirely when nothing in it is shared. The resolvers are
the same ones the admin payload takes, so both apply one set of URL rules.
"""
return {
"version": _text(version),
"groups": [
_serialize_group(group, resolve_endpoint_url, resolve_static_url)
for group in get_visible_support_latest_feature_groups(settings)
],
}
100 changes: 87 additions & 13 deletions application/single_app/route_backend_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,10 @@
record_terms_of_use_decline,
)
from functions_workspace_sections import build_workspace_section_availability
from functions_support_latest_features import build_latest_features_payload
from functions_support_latest_features import (
build_latest_features_payload,
build_user_latest_features_payload,
)
from functions_workspace_context import (
WorkspaceContextError,
build_group_workspace_context,
Expand Down Expand Up @@ -442,6 +445,7 @@
"items": external_links,
},
"latest_features": _build_latest_features_nav(raw_settings, user_roles),
"send_feedback": _build_send_feedback_nav(raw_settings, user_roles),

Check warning on line 448 in application/single_app/route_backend_v2.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
}


Expand Down Expand Up @@ -479,6 +483,48 @@
}


def _build_send_feedback_nav(raw_settings, user_roles):

Check warning on line 486 in application/single_app/route_backend_v2.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
"""Describe the Support menu's Send Feedback destination for the SPA rail.

Mirrors the gate in ``_sidebar_nav.html``: a real application role, the Support menu

Check warning on line 489 in application/single_app/route_backend_v2.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
and its Send Feedback destination switched on, and a recipient mailbox configured.
The recipient must also look like an address, because that is what
``/api/support/send_feedback_email`` accepts; offering a form whose every submission
is refused would be worse than not offering it. The address itself never leaves the
server here -- the browser receives it only in reply to a submission.
"""
may_see_support = any(role in ("Admin", "User") for role in user_roles or [])

Check warning on line 496 in application/single_app/route_backend_v2.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
recipient = str(raw_settings.get("support_feedback_recipient_email") or "").strip()
available = bool(
may_see_support
and raw_settings.get("enable_support_menu")
and raw_settings.get("enable_support_send_feedback", True)
and "@" in recipient
)
return {
"available": available,
"url": "/support/send-feedback",
"menu_name": _menu_name(raw_settings.get("support_menu_name"), "Support"),
}


def _latest_feature_endpoint_url(endpoint):
"""Resolve a Latest Features shortcut's Flask endpoint, or '' when it is not registered.

Shared by the admin catalogue and the end-user page, so a shortcut naming an endpoint
that no longer exists is dropped the same way on both.
"""
try:

Check warning on line 517 in application/single_app/route_backend_v2.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
return url_for(endpoint)
except BuildError:
log_event(

Check warning on line 520 in application/single_app/route_backend_v2.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
"[SUPPORT_LATEST_FEATURES] A Latest Features shortcut names an unknown endpoint.",
extra={"endpoint": endpoint},
level=logging.WARNING,
)
return ""


def _build_feature_flags(public_settings, per_user_overrides):
"""Collapse the settings document into the boolean flags the SPA branches on.

Expand Down Expand Up @@ -851,6 +897,46 @@
{"Cache-Control": "no-store"},
)

@bp.route("/api/v2/support/latest-features", methods=["GET"])
@swagger_route(security=get_auth_security())

Check warning on line 901 in application/single_app/route_backend_v2.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
@login_required

Check warning on line 902 in application/single_app/route_backend_v2.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
@user_required

Check warning on line 903 in application/single_app/route_backend_v2.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
@enabled_required("enable_support_menu")
def v2_support_latest_features():
"""Return the Latest Features announcements administrators shared with end users.

The V2 counterpart of the classic ``/support/latest-features`` page, behind the
same gates: an application role, the Support menu, and its Latest Features

Check warning on line 909 in application/single_app/route_backend_v2.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
destination. Whether this user hid the rail shortcut until the next release is
not a gate here, as it is not on the classic page: hiding the shortcut is not
losing the page.
"""
session_user = session.get("user") or {}
roles = session_user.get("roles") if isinstance(session_user, dict) else None
if not isinstance(roles, list) or not any(role in ("Admin", "User") for role in roles):
return jsonify({"error": "The Support menu is available to signed-in app users only."}), 403

settings = get_settings()
if not settings.get("enable_support_latest_features", True):
return jsonify({"error": "Latest Features is not available."}), 404

try:
payload = build_user_latest_features_payload(
settings,
resolve_endpoint_url=_latest_feature_endpoint_url,
resolve_static_url=lambda path: url_for("static", filename=path),
version=VERSION,
)
return jsonify(payload), 200
except Exception as exc:
log_event(
"[SUPPORT_LATEST_FEATURES] Failed to load the shared Latest Features announcements.",
extra={"error_type": type(exc).__name__},
level=logging.ERROR,
exceptionTraceback=True,
)
return jsonify({"error": "Failed to load Latest Features."}), 500

@bp.route("/api/v2/bootstrap", methods=["GET"])
@swagger_route(security=get_auth_security())
@login_required
Expand Down Expand Up @@ -1843,18 +1929,6 @@
)
return jsonify({"error": "Unable to check for application updates."}), 500

def _latest_feature_endpoint_url(endpoint):
"""Resolve a catalogue shortcut's Flask endpoint, or '' when it is not registered."""
try:
return url_for(endpoint)
except BuildError:
log_event(
"[V2_ADMIN_SETTINGS] A Latest Features shortcut names an unknown endpoint.",
extra={"endpoint": endpoint},
level=logging.WARNING,
)
return ""

@bp.route("/api/v2/admin/latest-features", methods=["GET"])
@swagger_route(security=get_auth_security())
@login_required
Expand Down
5 changes: 5 additions & 0 deletions application/v2_ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import { ContentReviewPage } from './pages/ContentReviewPage';
import { TermsOfUsePage } from './pages/TermsOfUsePage';
import { ApprovalsPage } from './pages/ApprovalsPage';
import { ControlCenterPage } from './pages/ControlCenterPage';
import { SupportLatestFeaturesPage } from './pages/SupportLatestFeaturesPage';
import { SupportSendFeedbackPage } from './pages/SupportSendFeedbackPage';

// Dev only: the workflow alert lab. Its one dynamic import is created only when
// import.meta.env.DEV, which a production build replaces with false, so production never
Expand Down Expand Up @@ -273,6 +275,9 @@ export function App() {
<Route path="/public/:workspaceId" element={<PublicWorkspacePage />} />
<Route path="/public/:workspaceId/:section" element={<PublicWorkspacePage />} />
<Route path="/public/:workspaceId/:section/:resourceId" element={<PublicWorkspacePage />} />
{/* The Support menu's destinations, offered from the rail. */}
<Route path="/support/latest-features" element={<SupportLatestFeaturesPage />} />
<Route path="/support/send-feedback" element={<SupportSendFeedbackPage />} />
{/* The workflow alert lab (dev/AlertLabPage.tsx). Never in a production build. */}
{import.meta.env.DEV && AlertLabPage ? <Route path="/dev/alert-lab" element={<Suspense fallback={<BootScreen />}><AlertLabPage /></Suspense>} /> : null}
<Route path="*" element={<Navigate to="/" replace />} />
Expand Down
78 changes: 0 additions & 78 deletions application/v2_ui/src/components/layout/LatestFeaturesLink.tsx

This file was deleted.

7 changes: 4 additions & 3 deletions application/v2_ui/src/components/layout/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import { classicChatHref } from '../../lib/conversationUrl';
import { DEFAULT_PUBLIC_WORKSPACE_LABELS, usePublicWorkspaceLabels } from '../../lib/publicWorkspaceLabels';
import { ConversationRail } from '../chat/ConversationRail';
import { NavExtras } from './NavExtras';
import { LatestFeaturesLink } from './LatestFeaturesLink';
import { SupportMenu } from './SupportMenu';
import { NotificationBell } from './NotificationBell';
import { UserAvatar } from './UserAvatar';
import { useWorkflowAlertCalloutShown, WorkflowAlertRowSlot } from '../notifications/WorkflowAlertNotice';
Expand Down Expand Up @@ -527,8 +527,9 @@ export function Sidebar({ mobile = false }: { mobile?: boolean }) {
nothing when neither is enabled, which is the default. */}
<NavExtras collapsed={collapsed} />

{/* The Support menu's Latest Features shortcut, until the user hides it. */}
<LatestFeaturesLink collapsed={collapsed} />
{/* The Support menu: Latest Features, until the user hides it, and Send
Feedback. Renders nothing when an administrator has offered neither. */}
<SupportMenu collapsed={collapsed} />

{/* The conversation list only belongs in the rail while the chat page is open,
so other pages get the full rail height for their own navigation. */}
Expand Down
Loading
Loading