fix(ui): keep config-owned OTEL callbacks visible in the logging table

The Active Logging Callbacks table filtered every callback whose name is an OTEL
trace-destination backend (arize, langfuse_otel, weave_otel, generic) via
LOGGING_BACKEND_IDS, intending to drop rows that duplicate a destination. But
config-owned global callbacks (configured proxy-wide through /config/update) live in
a separate namespace from the credential-backed destination rows, so nothing was ever
a duplicate; the filter just removed the config rows, and an admin who had set up a
global Arize/Langfuse-OTEL/Weave/generic callback lost all visibility and management
of it in the dashboard even though it kept receiving traffic.

Show config callbacks and destination rows together. Their name spaces don't overlap
(config callbacks keyed by backend name, destinations by credential name), so no
duplicate can arise. The other two LOGGING_BACKEND_IDS uses (the add-callback dropdown
routing and the availableCallbacks catalog) are unchanged.
This commit is contained in:
Yucheng Zhu 2026-07-30 17:11:51 -07:00
parent 34cfd85a40
commit 0e766a19a2
2 changed files with 33 additions and 5 deletions

View file

@ -137,10 +137,9 @@ describe("Settings", () => {
});
it("should display edit modal with fields when edit is clicked", async () => {
// Use a config callback that is NOT a logging-destination backend (arize,
// langfuse_otel, etc. are filtered from the table via LOGGING_BACKEND_IDS and
// edited through the destination flow instead). Datadog is a plain config
// callback, so it still renders a row with the legacy Test/Edit/Delete actions.
// Datadog is a plain config callback that renders a row with the legacy
// Test/Edit/Delete actions. Config-owned OTEL callbacks (arize, langfuse_otel,
// etc.) also render as their own rows now; see the regression test below.
const mockCallback = {
name: "datadog",
variables: {
@ -204,6 +203,35 @@ describe("Settings", () => {
});
});
it("should render a config-owned OTEL callback (langfuse_otel) as its own row", async () => {
// Regression: a proxy-wide langfuse_otel/arize/weave/generic callback configured
// via /config/update must stay visible and manageable in the table. It was being
// filtered out by backend id, removing config-owned rows (not just duplicates).
mockGetCallbacksCall.mockResolvedValue({
callbacks: [{ name: "langfuse_otel", variables: { LANGFUSE_PUBLIC_KEY: "pk", LANGFUSE_SECRET_KEY: "sk" } }],
available_callbacks: {
langfuse_otel: {
litellm_callback_name: "langfuse_otel",
litellm_callback_params: ["LANGFUSE_PUBLIC_KEY", "LANGFUSE_SECRET_KEY"],
ui_callback_name: "Langfuse OTEL",
},
},
alerts: [],
});
mockGetCallbackConfigsCall.mockResolvedValue([
{ id: "langfuse_otel", displayName: "Langfuse OTEL", dynamic_params: {} },
]);
const { getByText } = renderSettings(defaultProps);
await waitFor(() => {
expect(getByText("Active Logging Callbacks")).toBeInTheDocument();
});
await waitFor(() => {
expect(getByText("Langfuse OTEL")).toBeInTheDocument();
});
});
it("should hold the callbacks table in loading state until the fetch settles", async () => {
let resolveCallbacks: (value: {
callbacks: never[];

View file

@ -670,7 +670,7 @@ const Settings: React.FC<SettingsPageProps> = ({ accessToken, userRole, userID,
<TabPanels>
<TabPanel>
<LoggingCallbacksTable
callbacks={[...callbacks.filter((c) => !LOGGING_BACKEND_IDS.has(c.name)), ...destinationRows]}
callbacks={[...callbacks, ...destinationRows]}
availableCallbacks={allCallbacks}
isLoading={isLoadingCallbacks}
readOnly={!isProxyAdmin}