From 5cbe8209f614d27a8a89b7d9105591031e2f0c65 Mon Sep 17 00:00:00 2001 From: Ravi Kumar L Date: Fri, 10 Jul 2026 16:36:25 +0200 Subject: [PATCH] fix(langfuse): refine tenant connection settings --- .../Nav/Settings/__tests__/registry.spec.ts | 52 ++++++++ .../Integrations/LangfuseConnection.tsx | 115 ++++++++++++++---- .../__tests__/LangfuseConnection.spec.tsx | 112 +++++++++++++++-- 3 files changed, 241 insertions(+), 38 deletions(-) diff --git a/client/src/components/Nav/Settings/__tests__/registry.spec.ts b/client/src/components/Nav/Settings/__tests__/registry.spec.ts index 46a57908e8..da19a19688 100644 --- a/client/src/components/Nav/Settings/__tests__/registry.spec.ts +++ b/client/src/components/Nav/Settings/__tests__/registry.spec.ts @@ -1,10 +1,28 @@ import { isValidElementType } from 'react-is'; +import type { SettingsContextValue } from '../types'; import en from '~/locales/en/translation.json'; import { registry } from '../registry'; import { TABS } from '../types'; const validTabSections = new Map(TABS.map((t) => [t.id, new Set(t.sections.map((s) => s.id))])); +const settingsContext: SettingsContextValue = { + balanceEnabled: false, + hasAnyPersonalizationFeature: false, + hasMemoryOptOut: false, + hasRemoteAgents: false, + hasUserProvidedEndpoints: false, + hasMultiConvo: false, + hasPrompts: false, + isLocalProvider: true, + twoFactorEnabled: false, + allowAccountDeletion: true, + aboutEnabled: false, + engineTTS: 'browser', + isAdmin: false, + langfuseFanoutEnabled: false, +}; + describe('settings registry', () => { it('has unique ids', () => { const ids = registry.map((e) => e.id); @@ -30,4 +48,38 @@ describe('settings registry', () => { expect(isValidElementType(entry.Component)).toBe(true); } }); + + describe('Langfuse connection visibility', () => { + const langfuseEntry = registry.find((entry) => entry.id === 'langfuseConnection'); + + it('shows the connection to admins when fanout is enabled', () => { + expect( + langfuseEntry?.show?.({ + ...settingsContext, + isAdmin: true, + langfuseFanoutEnabled: true, + }), + ).toBe(true); + }); + + it('hides the connection from non-admins when fanout is enabled', () => { + expect( + langfuseEntry?.show?.({ + ...settingsContext, + isAdmin: false, + langfuseFanoutEnabled: true, + }), + ).toBe(false); + }); + + it('hides the connection from admins when fanout is disabled', () => { + expect( + langfuseEntry?.show?.({ + ...settingsContext, + isAdmin: true, + langfuseFanoutEnabled: false, + }), + ).toBe(false); + }); + }); }); diff --git a/client/src/components/Nav/SettingsTabs/Integrations/LangfuseConnection.tsx b/client/src/components/Nav/SettingsTabs/Integrations/LangfuseConnection.tsx index b7fd89642f..c955dded53 100644 --- a/client/src/components/Nav/SettingsTabs/Integrations/LangfuseConnection.tsx +++ b/client/src/components/Nav/SettingsTabs/Integrations/LangfuseConnection.tsx @@ -2,13 +2,13 @@ import { useState, useEffect, useRef } from 'react'; import { Button, CircleHelpIcon, + Dropdown, HoverCard, HoverCardContent, HoverCardPortal, HoverCardTrigger, Input, Label, - SecretInput, Spinner, Switch, useToastContext, @@ -30,7 +30,7 @@ function getStoredConnectionTestKey(status?: TLangfuseConnectionStatus): string return undefined; } - return [status.destination, status.publicKey, status.updatedAt ?? ''].join('\u0000'); + return [status.destination, status.publicKey].join('\u0000'); } function getConnectionStatusLabelKey(state: ConnectionTestState): TranslationKeys { @@ -88,6 +88,21 @@ export default function LangfuseConnection() { const [connectionTestMessage, setConnectionTestMessage] = useState(''); const autoTestedConnectionRef = useRef(); const connectionTestRequestRef = useRef(0); + const skipConnectionStatusSyncRef = useRef(false); + const publicKeyInputRef = useRef(null); + const secretKeyInputRef = useRef(null); + + useEffect(() => { + if (isEditingPublicKey) { + publicKeyInputRef.current?.focus(); + } + }, [isEditingPublicKey]); + + useEffect(() => { + if (isEditingSecretKey) { + secretKeyInputRef.current?.focus(); + } + }, [isEditingSecretKey]); useEffect(() => { if (!status) { @@ -101,6 +116,10 @@ export default function LangfuseConnection() { return; } setEnabled(connectionStatus.enabled === true); + if (skipConnectionStatusSyncRef.current) { + skipConnectionStatusSyncRef.current = false; + return; + } const availableDestinations = connectionStatus.destinations ?? []; const storedDestination = availableDestinations.some( (option) => option.key === connectionStatus.destination, @@ -113,13 +132,17 @@ export default function LangfuseConnection() { const secretConfigured = connectionStatus?.configured === true; const destinations = connectionStatus?.destinations ?? []; + const destinationOptions = destinations.map(({ key, baseUrl }) => ({ + value: key, + label: `${key} - ${baseUrl}`, + })); const trimmedPublicKey = publicKey.trim(); const trimmedSecretKey = secretKey.trim(); const publicKeyInputVisible = !secretConfigured || isEditingPublicKey; const secretInputVisible = !secretConfigured || isEditingSecretKey; const displayPublicKey = getDisplayPublicKey(publicKey); const hasUnsavedChanges = - enabled !== (connectionStatus?.enabled === true) || + (!secretConfigured && enabled !== (connectionStatus?.enabled === true)) || destination !== (connectionStatus?.destination ?? '') || trimmedPublicKey !== (connectionStatus?.publicKey ?? '') || trimmedSecretKey !== ''; @@ -301,6 +324,45 @@ export default function LangfuseConnection() { ); }; + const handleEnabledChange = (nextEnabled: boolean) => { + setEnabled(nextEnabled); + if (!secretConfigured || !connectionStatus?.destination || !connectionStatus.publicKey) { + return; + } + + const previousEnabled = connectionStatus.enabled === true; + const requestId = ++connectionTestRequestRef.current; + const saveEnabledState = () => { + updateMutation.mutate( + { + enabled: nextEnabled, + destination: connectionStatus.destination ?? '', + publicKey: connectionStatus.publicKey ?? '', + }, + { + onSuccess: (nextStatus) => { + if (requestId !== connectionTestRequestRef.current) { + return; + } + autoTestedConnectionRef.current = getStoredConnectionTestKey(nextStatus); + skipConnectionStatusSyncRef.current = true; + setConnectionStatus(nextStatus); + showToast({ message: localize('com_ui_langfuse_saved'), status: 'success' }); + }, + onError: () => { + if (requestId !== connectionTestRequestRef.current) { + return; + } + setEnabled(previousEnabled); + showToast({ message: localize('com_ui_langfuse_save_error'), status: 'error' }); + }, + }, + ); + }; + + saveEnabledState(); + }; + return (
@@ -324,7 +386,8 @@ export default function LangfuseConnection() {
@@ -350,25 +413,22 @@ export default function LangfuseConnection() {
- - + className="w-full" + sizeClasses="z-50 w-[var(--popover-anchor-width)]" + testId="langfuse-destination" + aria-labelledby="langfuse-destination-label" + />
- + {secretConfigured && !isEditingPublicKey && (
- + {secretConfigured && !isEditingSecretKey && (