feat(ui): add admin flag to disable in-product UI nudges for everyone

Admins can now suppress the survey and Claude Code feedback popups for
all users via a single disable_ui_nudges UI setting, instead of relying
on each user dismissing them individually.
This commit is contained in:
ryan-crabbe-berri 2026-06-05 10:53:24 -07:00
parent a4f57032e0
commit 96d819ee7e
4 changed files with 90 additions and 3 deletions

View file

@ -172,6 +172,11 @@ class UISettings(BaseModel):
description="If true, org admins cannot generate API keys via /key/generate.",
)
disable_ui_nudges: bool = Field(
default=False,
description="If true, suppresses in-product UI nudges (survey and Claude Code feedback popups) for all users.",
)
class UISettingsResponse(SettingsResponse):
"""Response model for UI settings"""
@ -195,6 +200,7 @@ ALLOWED_UI_SETTINGS_FIELDS = {
"scope_user_search_to_org",
"disable_custom_api_keys",
"disable_key_generate_for_org_admin",
"disable_ui_nudges",
}
# Flags that must be synced from the persisted UISettings into

View file

@ -1032,6 +1032,45 @@ class TestProxySettingEndpoints:
stored_settings = json.loads(create_data["ui_settings"])
assert stored_settings["disable_model_add_for_internal_users"] is True
def test_update_ui_settings_persists_disable_ui_nudges(
self, mock_auth, monkeypatch
):
"""disable_ui_nudges must be allowlisted so admins can suppress UI popups for everyone"""
from unittest.mock import AsyncMock, MagicMock
from litellm.proxy._types import UserAPIKeyAuth
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
mock_user_auth = UserAPIKeyAuth(
user_id="test-user-123",
user_role=LitellmUserRoles.PROXY_ADMIN,
)
app.dependency_overrides[user_api_key_auth] = lambda: mock_user_auth
monkeypatch.setattr("litellm.proxy.proxy_server.store_model_in_db", True)
mock_prisma = MagicMock()
mock_prisma.db.litellm_uisettings.upsert = AsyncMock()
mock_prisma.db.litellm_uisettings.find_unique = AsyncMock(return_value=None)
monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma)
try:
response = client.patch(
"/update/ui_settings", json={"disable_ui_nudges": True}
)
finally:
app.dependency_overrides.clear()
assert response.status_code == 200
data = response.json()
assert data["status"] == "success"
assert data["settings"]["disable_ui_nudges"] is True
create_data = mock_prisma.db.litellm_uisettings.upsert.call_args.kwargs["data"][
"create"
]
stored_settings = json.loads(create_data["ui_settings"])
assert stored_settings["disable_ui_nudges"] is True
def test_update_ui_settings_ignores_non_allowlisted_value(
self, mock_auth, monkeypatch
):

View file

@ -10,6 +10,7 @@ import BudgetPanel from "@/components/budgets/budget_panel";
import CacheDashboard from "@/components/cache_dashboard";
import ClaudeCodePluginsPanel from "@/components/claude_code_plugins";
import { teamListCall as v2TeamListCall } from "@/app/(dashboard)/hooks/teams/useTeams";
import { useUISettings } from "@/app/(dashboard)/hooks/uiSettings/useUISettings";
import LoadingScreen from "@/components/common_components/LoadingScreen";
import { CostTrackingSettings } from "@/components/CostTrackingSettings";
import GeneralSettings from "@/components/general_settings";
@ -90,6 +91,9 @@ function CreateKeyPageContent() {
const [modelData, setModelData] = useState<any>({ data: [] });
const [createClicked, setCreateClicked] = useState<boolean>(false);
const { data: uiSettingsData } = useUISettings();
const nudgesDisabled = Boolean(uiSettingsData?.values?.disable_ui_nudges);
// Survey state - always show by default
const [showSurveyPrompt, setShowSurveyPrompt] = useState(true);
const [showSurveyModal, setShowSurveyModal] = useState(false);
@ -265,6 +269,9 @@ function CreateKeyPageContent() {
// Fetch in-product nudges configuration from backend
useEffect(() => {
if (nudgesDisabled) {
return;
}
if (accessToken && token) {
(async () => {
try {
@ -284,7 +291,7 @@ function CreateKeyPageContent() {
}
})();
}
}, [accessToken, token]);
}, [accessToken, token, nudgesDisabled]);
// Auto-dismiss survey prompt after 15 seconds
useEffect(() => {
@ -550,7 +557,7 @@ function CreateKeyPageContent() {
{/* Survey Components */}
<SurveyPrompt
isVisible={showSurveyPrompt}
isVisible={showSurveyPrompt && !nudgesDisabled}
onOpen={handleOpenSurvey}
onDismiss={handleDismissSurveyPrompt}
/>
@ -562,7 +569,7 @@ function CreateKeyPageContent() {
{/* Claude Code Components */}
<ClaudeCodePrompt
isVisible={showClaudeCodePrompt}
isVisible={showClaudeCodePrompt && !nudgesDisabled}
onOpen={handleOpenClaudeCode}
onDismiss={handleDismissClaudeCodePrompt}
/>

View file

@ -26,6 +26,7 @@ export default function UISettings() {
const allowVectorStoresTeamAdminsProperty = schema?.properties?.allow_vector_stores_for_team_admins;
const scopeUserSearchProperty = schema?.properties?.scope_user_search_to_org;
const disableCustomApiKeysProperty = schema?.properties?.disable_custom_api_keys;
const disableUINudgesProperty = schema?.properties?.disable_ui_nudges;
const values = data?.values ?? {};
const isDisabledForInternalUsers = Boolean(values.disable_model_add_for_internal_users);
const isDisabledTeamAdminDeleteTeamUser = Boolean(values.disable_team_admin_delete_team_user);
@ -60,6 +61,20 @@ export default function UISettings() {
);
};
const handleToggleDisableUINudges = (checked: boolean) => {
updateSettings(
{ disable_ui_nudges: checked },
{
onSuccess: () => {
NotificationManager.success("UI settings updated successfully");
},
onError: (error) => {
NotificationManager.fromBackend(error);
},
},
);
};
const handleUpdatePageVisibility = (settings: { enabled_ui_pages_internal_users: string[] | null }) => {
updateSettings(settings, {
onSuccess: () => {
@ -451,6 +466,26 @@ export default function UISettings() {
<Divider />
{/* Disable in-product UI nudges */}
<Space align="start" size="middle">
<Switch
checked={Boolean(values.disable_ui_nudges)}
disabled={isUpdating}
loading={isUpdating}
onChange={handleToggleDisableUINudges}
aria-label={disableUINudgesProperty?.description ?? "Disable UI nudges"}
/>
<Space direction="vertical" size={4}>
<Typography.Text strong>Disable UI nudges</Typography.Text>
<Typography.Text type="secondary">
{disableUINudgesProperty?.description ??
"If true, suppresses in-product UI nudges (survey and Claude Code feedback popups) for all users."}
</Typography.Text>
</Space>
</Space>
<Divider />
{/* Page Visibility for Internal Users */}
<PageVisibilitySettings
enabledPagesInternalUsers={values.enabled_ui_pages_internal_users}