fix(langfuse): refine tenant connection settings

This commit is contained in:
Ravi Kumar L 2026-07-10 16:36:25 +02:00
parent 58974aa75d
commit 5cbe8209f6
3 changed files with 241 additions and 38 deletions

View file

@ -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);
});
});
});

View file

@ -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<string>();
const connectionTestRequestRef = useRef(0);
const skipConnectionStatusSyncRef = useRef(false);
const publicKeyInputRef = useRef<HTMLInputElement>(null);
const secretKeyInputRef = useRef<HTMLInputElement>(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 (
<div className="flex flex-col gap-4">
<HoverCard openDelay={50}>
@ -324,7 +386,8 @@ export default function LangfuseConnection() {
</div>
<Switch
checked={enabled}
onCheckedChange={setEnabled}
disabled={testMutation.isLoading || updateMutation.isLoading}
onCheckedChange={handleEnabledChange}
aria-labelledby="langfuse-enabled-label"
/>
</div>
@ -350,25 +413,22 @@ export default function LangfuseConnection() {
</HoverCard>
<div className="flex flex-col gap-1.5">
<Label htmlFor="langfuse-destination">{localize('com_ui_langfuse_destination')}</Label>
<select
id="langfuse-destination"
className="flex h-9 w-full rounded-lg border border-border-light bg-transparent px-3 py-2 text-sm text-text-primary shadow-sm outline-none focus:ring-2 focus:ring-ring-primary disabled:cursor-not-allowed disabled:opacity-50"
<Label id="langfuse-destination-label">{localize('com_ui_langfuse_destination')}</Label>
<Dropdown
value={destination}
label={destination === '' ? localize('com_ui_select') : ''}
onChange={handleDestinationChange}
options={destinationOptions}
disabled={destinations.length === 0}
onChange={(e) => handleDestinationChange(e.target.value)}
>
<option value="">{localize('com_ui_select')}</option>
{destinations.map((option) => (
<option key={option.key} value={option.key}>
{option.key} - {option.baseUrl}
</option>
))}
</select>
className="w-full"
sizeClasses="z-50 w-[var(--popover-anchor-width)]"
testId="langfuse-destination"
aria-labelledby="langfuse-destination-label"
/>
</div>
<div className="flex flex-col gap-1.5">
<Label htmlFor="langfuse-public-key">{localize('com_ui_langfuse_public_key')}</Label>
<Label htmlFor="langfuse-public-token">{localize('com_ui_langfuse_public_key')}</Label>
{secretConfigured && !isEditingPublicKey && (
<button
type="button"
@ -383,10 +443,13 @@ export default function LangfuseConnection() {
)}
{publicKeyInputVisible && (
<Input
id="langfuse-public-key"
ref={publicKeyInputRef}
id="langfuse-public-token"
autoComplete="off"
data-lpignore="true"
data-1p-ignore="true"
data-bwignore="true"
data-form-type="other"
value={publicKey}
placeholder="pk-lf-..."
onChange={(e) => setPublicKey(e.target.value)}
@ -395,7 +458,7 @@ export default function LangfuseConnection() {
</div>
<div className="flex flex-col gap-1.5">
<Label htmlFor="langfuse-secret-key">{localize('com_ui_langfuse_secret_key')}</Label>
<Label htmlFor="langfuse-private-token">{localize('com_ui_langfuse_secret_key')}</Label>
{secretConfigured && !isEditingSecretKey && (
<button
type="button"
@ -409,15 +472,17 @@ export default function LangfuseConnection() {
</button>
)}
{secretInputVisible && (
<SecretInput
id="langfuse-secret-key"
autoComplete="new-password"
<Input
ref={secretKeyInputRef}
id="langfuse-private-token"
type="text"
autoComplete="off"
data-lpignore="true"
data-1p-ignore="true"
controlsOnHover
data-bwignore="true"
data-form-type="other"
value={secretKey}
placeholder="sk-lf-..."
containerClassName="w-full"
onChange={(e) => setSecretKey(e.target.value)}
/>
)}

View file

@ -5,6 +5,15 @@ import LangfuseConnection from '../LangfuseConnection';
const mockGet = jest.fn();
const mockUpdate = jest.fn();
const mockTest = jest.fn();
const destinationLabels = {
eu: 'eu - https://cloud.langfuse.com',
us: 'us - https://us.cloud.langfuse.com',
};
async function selectDestination(destination: keyof typeof destinationLabels) {
await userEvent.click(screen.getByTestId('langfuse-destination'));
await userEvent.click(screen.getByRole('option', { name: destinationLabels[destination] }));
}
jest.mock('~/data-provider', () => ({
useGetLangfuseConnectionQuery: () => mockGet(),
@ -22,6 +31,11 @@ jest.mock('@librechat/client', () => ({
}));
beforeEach(() => {
global.ResizeObserver = class MockedResizeObserver {
observe = jest.fn();
unobserve = jest.fn();
disconnect = jest.fn();
};
mockGet.mockReset();
mockUpdate.mockReset();
mockTest.mockReset();
@ -43,7 +57,7 @@ beforeEach(() => {
describe('LangfuseConnection', () => {
it('renders the connection form fields', () => {
render(<LangfuseConnection />);
expect(screen.getByLabelText('com_ui_langfuse_destination')).toHaveValue('');
expect(screen.getByTestId('langfuse-destination')).toHaveTextContent('com_ui_select');
expect(screen.getByLabelText('com_ui_langfuse_public_key')).toHaveAttribute(
'data-lpignore',
'true',
@ -52,6 +66,14 @@ describe('LangfuseConnection', () => {
'data-1p-ignore',
'true',
);
expect(screen.getByLabelText('com_ui_langfuse_public_key')).toHaveAttribute(
'data-form-type',
'other',
);
expect(screen.getByLabelText('com_ui_langfuse_public_key')).toHaveAttribute(
'data-bwignore',
'true',
);
expect(screen.getByLabelText(/com_ui_langfuse_secret_key/)).toHaveAttribute(
'data-lpignore',
'true',
@ -60,6 +82,20 @@ describe('LangfuseConnection', () => {
'data-1p-ignore',
'true',
);
expect(screen.getByLabelText(/com_ui_langfuse_secret_key/)).toHaveAttribute(
'data-form-type',
'other',
);
expect(screen.getByLabelText(/com_ui_langfuse_secret_key/)).toHaveAttribute(
'data-bwignore',
'true',
);
expect(screen.getByLabelText(/com_ui_langfuse_secret_key/)).toHaveAttribute(
'autocomplete',
'off',
);
expect(screen.getByLabelText(/com_ui_langfuse_secret_key/)).toHaveAttribute('type', 'text');
expect(screen.queryByRole('button', { name: 'Show secret' })).not.toBeInTheDocument();
expect(screen.queryByText('com_ui_langfuse_test')).not.toBeInTheDocument();
expect(screen.getByText('com_ui_langfuse_status_not_configured')).toBeInTheDocument();
expect(mockTest).not.toHaveBeenCalled();
@ -81,13 +117,13 @@ describe('LangfuseConnection', () => {
});
render(<LangfuseConnection />);
expect(screen.getByLabelText('com_ui_langfuse_destination')).toHaveValue('us');
expect(screen.getByTestId('langfuse-destination')).toHaveTextContent(destinationLabels.us);
expect(screen.queryByLabelText('com_ui_langfuse_public_key')).not.toBeInTheDocument();
expect(screen.getByText('pk-lf-...515f')).toBeInTheDocument();
expect(screen.queryByLabelText('com_ui_langfuse_secret_key')).not.toBeInTheDocument();
expect(screen.getByText('sk-lf-...515f')).toBeInTheDocument();
expect(screen.queryByText('com_ui_save')).not.toBeInTheDocument();
expect(screen.getByLabelText('com_ui_langfuse_destination')).toBeEnabled();
expect(screen.getByTestId('langfuse-destination')).toBeEnabled();
expect(screen.getByRole('switch', { name: 'com_ui_langfuse_title' })).toBeEnabled();
await waitFor(() => expect(mockTest).toHaveBeenCalledTimes(1));
expect(mockTest.mock.calls[0][0]).toEqual({
@ -126,7 +162,7 @@ describe('LangfuseConnection', () => {
it('tests and saves the typed secret key when enabling a new connection', async () => {
render(<LangfuseConnection />);
await userEvent.click(screen.getByRole('switch', { name: 'com_ui_langfuse_title' }));
await userEvent.selectOptions(screen.getByLabelText('com_ui_langfuse_destination'), 'us');
await selectDestination('us');
fireEvent.change(screen.getByLabelText('com_ui_langfuse_public_key'), {
target: { value: 'pk-lf-1' },
});
@ -168,7 +204,7 @@ describe('LangfuseConnection', () => {
render(<LangfuseConnection />);
await userEvent.click(screen.getByRole('switch', { name: 'com_ui_langfuse_title' }));
await userEvent.selectOptions(screen.getByLabelText('com_ui_langfuse_destination'), 'us');
await selectDestination('us');
fireEvent.change(screen.getByLabelText('com_ui_langfuse_public_key'), {
target: { value: 'pk-lf-1' },
});
@ -201,7 +237,7 @@ describe('LangfuseConnection', () => {
await waitFor(() => expect(mockTest).toHaveBeenCalledTimes(1));
mockTest.mockClear();
await userEvent.selectOptions(screen.getByLabelText('com_ui_langfuse_destination'), 'us');
await selectDestination('us');
expect(mockTest).toHaveBeenCalledTimes(1);
expect(mockTest.mock.calls[0][0]).toMatchObject({
@ -244,6 +280,7 @@ describe('LangfuseConnection', () => {
);
expect(screen.getByLabelText('com_ui_langfuse_public_key')).toHaveValue('pk-lf-1');
expect(screen.getByLabelText('com_ui_langfuse_public_key')).toHaveFocus();
expect(
screen.queryByLabelText(/com_ui_langfuse_secret_key/, { selector: 'input' }),
).not.toBeInTheDocument();
@ -256,7 +293,8 @@ describe('LangfuseConnection', () => {
const secretKeyInput = screen.getByLabelText(/com_ui_langfuse_secret_key/);
expect(secretKeyInput).toHaveValue('');
expect(secretKeyInput.parentElement).toHaveClass('w-full');
expect(secretKeyInput).toHaveClass('w-full');
expect(secretKeyInput).toHaveFocus();
fireEvent.change(secretKeyInput, {
target: { value: 'sk-lf-replacement' },
});
@ -293,8 +331,7 @@ describe('LangfuseConnection', () => {
render(<LangfuseConnection />);
await waitFor(() => expect(mockTest).toHaveBeenCalledTimes(1));
await userEvent.click(screen.getByRole('switch', { name: 'com_ui_langfuse_title' }));
await userEvent.selectOptions(screen.getByLabelText('com_ui_langfuse_destination'), 'us');
await selectDestination('us');
await userEvent.click(
screen.getByRole('button', {
name: 'com_ui_edit com_ui_langfuse_public_key',
@ -315,7 +352,7 @@ describe('LangfuseConnection', () => {
expect(screen.getByRole('switch', { name: 'com_ui_langfuse_title' })).toBeChecked();
expect(screen.getByRole('switch', { name: 'com_ui_langfuse_title' })).toBeEnabled();
expect(screen.getByLabelText('com_ui_langfuse_destination')).toHaveValue('eu');
expect(screen.getByTestId('langfuse-destination')).toHaveTextContent(destinationLabels.eu);
expect(screen.getByText('pk-lf-...inal')).toBeInTheDocument();
expect(screen.getByText('sk-lf-...515f')).toBeInTheDocument();
expect(screen.queryByText('com_ui_save')).not.toBeInTheDocument();
@ -328,7 +365,7 @@ describe('LangfuseConnection', () => {
});
render(<LangfuseConnection />);
await userEvent.click(screen.getByRole('switch', { name: 'com_ui_langfuse_title' }));
await userEvent.selectOptions(screen.getByLabelText('com_ui_langfuse_destination'), 'us');
await selectDestination('us');
fireEvent.change(screen.getByLabelText('com_ui_langfuse_public_key'), {
target: { value: 'pk-lf-1' },
});
@ -383,7 +420,7 @@ describe('LangfuseConnection', () => {
expect(mockUpdate).not.toHaveBeenCalled();
});
it('skips the connection test when disabling an already-configured connection', async () => {
it('saves immediately without testing when disabling a configured connection', async () => {
mockGet.mockReturnValue({
data: {
configured: true,
@ -397,9 +434,19 @@ describe('LangfuseConnection', () => {
render(<LangfuseConnection />);
await waitFor(() => expect(mockTest).toHaveBeenCalledTimes(1));
mockTest.mockClear();
mockUpdate.mockImplementation((_payload, options) => {
options?.onSuccess?.({
configured: true,
enabled: false,
destinations: [{ key: 'eu', baseUrl: 'https://cloud.langfuse.com' }],
destination: 'eu',
publicKey: 'pk-lf-1',
displaySecretKey: 'sk-lf-...515f',
updatedAt: '2026-07-10T15:30:00.000Z',
});
});
await userEvent.click(screen.getByRole('switch', { name: 'com_ui_langfuse_title' }));
await userEvent.click(screen.getByText('com_ui_save'));
expect(mockTest).not.toHaveBeenCalled();
expect(mockUpdate).toHaveBeenCalledTimes(1);
@ -408,5 +455,44 @@ describe('LangfuseConnection', () => {
destination: 'eu',
publicKey: 'pk-lf-1',
});
expect(screen.queryByText('com_ui_save')).not.toBeInTheDocument();
expect(screen.getByRole('switch', { name: 'com_ui_langfuse_title' })).not.toBeChecked();
});
it('saves immediately without testing when enabling a configured connection', async () => {
mockGet.mockReturnValue({
data: {
configured: true,
enabled: false,
destinations: [{ key: 'eu', baseUrl: 'https://cloud.langfuse.com' }],
destination: 'eu',
publicKey: 'pk-lf-1',
displaySecretKey: 'sk-lf-...515f',
},
});
mockUpdate.mockImplementation((_payload, options) => {
options?.onSuccess?.({
configured: true,
enabled: true,
destinations: [{ key: 'eu', baseUrl: 'https://cloud.langfuse.com' }],
destination: 'eu',
publicKey: 'pk-lf-1',
displaySecretKey: 'sk-lf-...515f',
updatedAt: '2026-07-10T15:31:00.000Z',
});
});
render(<LangfuseConnection />);
await waitFor(() => expect(mockTest).toHaveBeenCalledTimes(1));
mockTest.mockClear();
await userEvent.click(screen.getByRole('switch', { name: 'com_ui_langfuse_title' }));
expect(mockTest).not.toHaveBeenCalled();
expect(mockUpdate).toHaveBeenCalledWith(
{ enabled: true, destination: 'eu', publicKey: 'pk-lf-1' },
expect.any(Object),
);
expect(screen.queryByText('com_ui_save')).not.toBeInTheDocument();
expect(screen.getByRole('switch', { name: 'com_ui_langfuse_title' })).toBeChecked();
});
});