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
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,39 @@ describe("LoggingCallbacksTable", () => {
);
expect(getByText("custom_callback_x")).toBeInTheDocument();
});

// Regression: `/get_callbacks` returns the same `name` twice when a
// callback is registered for both success and failure (e.g. `generic_api`
// → POST to spend-log on both 200 and 4xx/5xx). The UI used to ignore
// the `type` field and render every row as "Success", masking the
// failure registration. Reading `record.type` fixes the badge AND
// composing the rowKey with type avoids React's duplicate-key warning.
it("renders distinct Success and Failure badges for same-name dual registration", () => {
const baseVars = {
SLACK_WEBHOOK_URL: null,
LANGFUSE_PUBLIC_KEY: null,
LANGFUSE_SECRET_KEY: null,
LANGFUSE_HOST: null,
OPENMETER_API_KEY: null,
};
const { getAllByText, getByText } = render(
<LoggingCallbacksTable
callbacks={[
{ name: "generic_api", type: "success", variables: baseVars },
{ name: "generic_api", type: "failure", variables: baseVars },
]}
availableCallbacks={{
generic_api: {
litellm_callback_name: "generic_api",
litellm_callback_params: [],
ui_callback_name: "Custom Callback API",
},
}}
/>,
);
// Both rows show the same display name, but distinct mode badges.
expect(getAllByText("Custom Callback API")).toHaveLength(2);
expect(getByText("Success")).toBeInTheDocument();
expect(getByText("Failure")).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ export const LoggingCallbacksTable: React.FC<LoggingCallbacksProps> = ({
key: "name",
render: (_: string, record: CallbackRow) => {
const id = record.name;
console.log("availableCallbacks", availableCallbacks);
const displayName = availableCallbacks[id]?.ui_callback_name || id;
return <div className="font-medium text-gray-800">{displayName}</div>;
},
Expand All @@ -57,7 +56,10 @@ export const LoggingCallbacksTable: React.FC<LoggingCallbacksProps> = ({
title: <span className="font-medium text-gray-700">Mode</span>,
key: "mode",
render: (_: unknown, record: CallbackRow) => {
const mode = record.mode || "success";
// Backend sends `type` (success | failure); legacy in-memory rows
// from add-callback flow set `mode`. Read both so newly-added rows
// and server-fetched rows both render correctly.
const mode = record.type || record.mode || "success";
const label = CALLBACK_MODES.find((m) => m.value === mode)?.label || mode;
const badgeClass =
mode === "success"
Expand Down Expand Up @@ -109,7 +111,10 @@ export const LoggingCallbacksTable: React.FC<LoggingCallbacksProps> = ({
<Table
columns={columns}
dataSource={callbacks as CallbackRow[]}
rowKey={(record) => record.name}
// `generic_api` can appear as both a success and a failure
// callback simultaneously — keying by `name` alone produced
// duplicate React keys. Compose with type to keep keys unique.
rowKey={(record) => `${record.name}-${record.type || record.mode || "success"}`}
pagination={false}
rowClassName={() => "hover:bg-gray-50"}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
export interface AlertingObject {
name: string;
// Backend distinguishes success vs failure callback registrations
// (`/get_callbacks` returns `type: "success" | "failure"`). Same callback
// (e.g. `generic_api`) can appear twice — once per event class — and
// those entries fire on disjoint events, not double-fire on one event.
// UI must read this to render the correct badge; missing it caused
// every row to render as "Success".
type?: "success" | "failure" | "success_and_failure";
variables: AlertingVariables;
}

Expand Down