From 8fe160574947360581c26d8ff28a50b2e9423290 Mon Sep 17 00:00:00 2001 From: lindicaphxag-tech Date: Fri, 18 Sep 2026 16:13:20 +0800 Subject: [PATCH 01/27] fix(proxy): show configured alerting values --- litellm/proxy/proxy_server.py | 16 +++--- .../proxy_server/test_routes_model_metrics.py | 57 +++++++++++++++++++ 2 files changed, 66 insertions(+), 7 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 80d9868ff0e..b7f9b470e1c 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -285,7 +285,6 @@ from litellm.constants import ( from litellm.exceptions import RejectedRequestError from litellm.integrations.custom_guardrail import CustomGuardrail, ModifyResponseException from litellm.integrations.custom_logger import CustomLogger -from litellm.integrations.SlackAlerting.slack_alerting import SlackAlerting from litellm.litellm_core_utils.agentic_loop_settings import ( validated_max_agentic_loops, ) @@ -15781,7 +15780,7 @@ async def model_settings(): async def alerting_settings( user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), ): - global proxy_logging_obj, prisma_client + global prisma_client """ Used by UI to generate 'alerting settings' page { @@ -15840,8 +15839,7 @@ async def alerting_settings( } ) - _slack_alerting: Final[SlackAlerting] = proxy_logging_obj.slack_alerting_instance - _slack_alerting_args_dict: Final = _slack_alerting.alerting_args.model_dump() + config_alerting_args: Final = general_settings.get("alerting_args") or {} # mutable-ok: empty fallback is read-only configuration return_val: Final = [] @@ -15864,17 +15862,21 @@ async def alerting_settings( for field_name, field_info in SlackAlertingArgs.model_fields.items(): if field_name in allowed_args: - _stored_in_db: bool | None = None if field_name in alerting_args_dict: + field_value = alerting_args_dict[field_name] _stored_in_db = True - else: + elif field_name in config_alerting_args: + field_value = config_alerting_args[field_name] _stored_in_db = False + else: + field_value = None + _stored_in_db = None _response_obj = ConfigList( field_name=field_name, field_type=allowed_args[field_name], field_description=field_info.description or "", - field_value=_slack_alerting_args_dict.get(field_name, None), + field_value=field_value, stored_in_db=_stored_in_db, field_default_value=field_info.default, premium_field=(True if field_name == "region_outage_alert_ttl" else False), diff --git a/tests/test_litellm/proxy/proxy_server/test_routes_model_metrics.py b/tests/test_litellm/proxy/proxy_server/test_routes_model_metrics.py index 246e2cbba54..c1306ef217e 100644 --- a/tests/test_litellm/proxy/proxy_server/test_routes_model_metrics.py +++ b/tests/test_litellm/proxy/proxy_server/test_routes_model_metrics.py @@ -226,3 +226,60 @@ def test_alerting_settings_happy(client, auth_as, monkeypatch): "first_field_value": False, "first_field_type": "Boolean", } + + +def test_alerting_settings_only_returns_explicitly_configured_values(client, auth_as, monkeypatch): + pc = MagicMock() + pc.db.litellm_config.find_first = AsyncMock(return_value=None) + monkeypatch.setattr(proxy_server, "prisma_client", pc) + + logging_obj = MagicMock() + args_model = MagicMock() + args_model.model_dump = MagicMock(return_value={"budget_alert_ttl": -30}) + logging_obj.slack_alerting_instance.alerting_args = args_model + monkeypatch.setattr(proxy_server, "proxy_logging_obj", logging_obj) + monkeypatch.setattr(proxy_server, "general_settings", {}) + + with auth_as(LitellmUserRoles.PROXY_ADMIN): + response = client.get("/alerting/settings") + + assert response.status_code == 200 + setting = next(item for item in response.json() if item["field_name"] == "budget_alert_ttl") + assert setting["field_value"] is None + assert setting["stored_in_db"] is None + + +def test_alerting_settings_returns_configured_values(client, auth_as, monkeypatch): + pc = MagicMock() + pc.db.litellm_config.find_first = AsyncMock(return_value=None) + monkeypatch.setattr(proxy_server, "prisma_client", pc) + monkeypatch.setattr( + proxy_server, + "general_settings", + {"alerting_args": {"budget_alert_ttl": 60}}, + ) + + with auth_as(LitellmUserRoles.PROXY_ADMIN): + response = client.get("/alerting/settings") + + assert response.status_code == 200 + setting = next(item for item in response.json() if item["field_name"] == "budget_alert_ttl") + assert setting["field_value"] == 60 + assert setting["stored_in_db"] is False + + +def test_alerting_settings_returns_database_values(client, auth_as, monkeypatch): + db_settings = MagicMock() + db_settings.param_value = {"alerting_args": {"budget_alert_ttl": 60}} + pc = MagicMock() + pc.db.litellm_config.find_first = AsyncMock(return_value=db_settings) + monkeypatch.setattr(proxy_server, "prisma_client", pc) + monkeypatch.setattr(proxy_server, "general_settings", {}) + + with auth_as(LitellmUserRoles.PROXY_ADMIN): + response = client.get("/alerting/settings") + + assert response.status_code == 200 + setting = next(item for item in response.json() if item["field_name"] == "budget_alert_ttl") + assert setting["field_value"] == 60 + assert setting["stored_in_db"] is True From 45f71d96ce65f6c0bcf5975856fa51ab7de32f21 Mon Sep 17 00:00:00 2001 From: lindicaphxag-tech Date: Fri, 18 Sep 2026 16:44:06 +0800 Subject: [PATCH 02/27] fix(ui): preserve unset alerting fields on update --- litellm/proxy/proxy_server.py | 2 + .../alerting_settings.integration.test.tsx | 71 +++++++++++++++++++ .../components/alerting/alerting_settings.tsx | 12 ++-- 3 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index b7f9b470e1c..933de6f855f 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -15862,6 +15862,8 @@ async def alerting_settings( for field_name, field_info in SlackAlertingArgs.model_fields.items(): if field_name in allowed_args: + field_value: Final + _stored_in_db: Final if field_name in alerting_args_dict: field_value = alerting_args_dict[field_name] _stored_in_db = True diff --git a/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx b/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx new file mode 100644 index 00000000000..d19d6edce41 --- /dev/null +++ b/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx @@ -0,0 +1,71 @@ +import React from "react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { renderWithProviders } from "@/../tests/test-utils"; +import AlertingSettings from "./alerting_settings"; + +const { alertingSettingsCall, updateConfigFieldSetting } = vi.hoisted(() => ({ + alertingSettingsCall: vi.fn(), + updateConfigFieldSetting: vi.fn(), +})); + +vi.mock("@/components/networking", () => ({ alertingSettingsCall, updateConfigFieldSetting })); + +vi.mock("@/lib/toast", () => ({ + toast: { error: vi.fn(), success: vi.fn() }, +})); + +const settingsResponse = [ + { + field_name: "slack_alerting", + field_type: "Boolean", + field_value: false, + field_default_value: null, + field_description: "Enable Slack alerting", + stored_in_db: null, + premium_field: false, + }, + { + field_name: "budget_alert_ttl", + field_type: "Integer", + field_value: 60, + field_default_value: 30, + field_description: "Configured threshold", + stored_in_db: true, + premium_field: false, + }, + { + field_name: "outage_alert_ttl", + field_type: "Integer", + field_value: null, + field_default_value: 10, + field_description: "Unset threshold", + stored_in_db: null, + premium_field: false, + }, +]; + +describe("AlertingSettings", () => { + beforeEach(() => { + vi.clearAllMocks(); + alertingSettingsCall.mockResolvedValue(settingsResponse); + updateConfigFieldSetting.mockResolvedValue({}); + }); + + it("does not submit unset fields when updating another alerting setting", async () => { + const user = userEvent.setup(); + renderWithProviders(); + + await screen.findByDisplayValue("60"); + await user.click(screen.getByRole("switch", { name: "slack_alerting" })); + await user.click(screen.getByRole("button", { name: "Update Settings" })); + + await waitFor(() => { + expect(updateConfigFieldSetting).toHaveBeenNthCalledWith(1, "sk-test", "alerting_args", { + budget_alert_ttl: 60, + }); + }); + expect(updateConfigFieldSetting).toHaveBeenNthCalledWith(2, "sk-test", "alerting", ["slack"]); + }); +}); diff --git a/ui/litellm-dashboard/src/components/alerting/alerting_settings.tsx b/ui/litellm-dashboard/src/components/alerting/alerting_settings.tsx index cff33546fbc..87b4258a26a 100644 --- a/ui/litellm-dashboard/src/components/alerting/alerting_settings.tsx +++ b/ui/litellm-dashboard/src/components/alerting/alerting_settings.tsx @@ -55,15 +55,15 @@ const AlertingSettings: React.FC = ({ accessToken, premiu return; } - const initialFormValues: Record = {}; - + const configuredAlertingArgs: Record = {}; alertingSettings.forEach((setting) => { - initialFormValues[setting.field_name] = setting.field_value; + if (setting.field_name !== "slack_alerting" && setting.field_value != null) { + configuredAlertingArgs[setting.field_name] = setting.field_value; + } }); - // Merge initialFormValues with actual formValues - const mergedFormValues = { ...formValues, ...initialFormValues }; - const { slack_alerting, ...alertingArgs } = mergedFormValues; + const { slack_alerting, ...updatedAlertingArgs } = formValues; + const alertingArgs = { ...configuredAlertingArgs, ...updatedAlertingArgs }; try { await updateConfigFieldSetting(accessToken, "alerting_args", alertingArgs); if (typeof slack_alerting === "boolean") { From a4bb3288c212fdb5efb604cd5141e5e40031b91f Mon Sep 17 00:00:00 2001 From: lindicaphxag-tech Date: Fri, 18 Sep 2026 16:47:40 +0800 Subject: [PATCH 03/27] style(proxy): format alerting settings --- litellm/proxy/proxy_server.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 933de6f855f..04b0286c7e1 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -15839,7 +15839,9 @@ async def alerting_settings( } ) - config_alerting_args: Final = general_settings.get("alerting_args") or {} # mutable-ok: empty fallback is read-only configuration + config_alerting_args: Final = ( + general_settings.get("alerting_args") or {} + ) # mutable-ok: empty fallback is read-only configuration return_val: Final = [] From 7c7974601b3968a3f11fac3204f712eead97c894 Mon Sep 17 00:00:00 2001 From: lindicaphxag-tech Date: Fri, 18 Sep 2026 16:58:25 +0800 Subject: [PATCH 04/27] fix(proxy): avoid mutable alerting fallback --- litellm/proxy/proxy_server.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 04b0286c7e1..988f37b1e09 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -15839,9 +15839,7 @@ async def alerting_settings( } ) - config_alerting_args: Final = ( - general_settings.get("alerting_args") or {} - ) # mutable-ok: empty fallback is read-only configuration + config_alerting_args: Final = general_settings.get("alerting_args") or MappingProxyType({}) return_val: Final = [] From 66407406d43246fcd48b329795b684cf6a01980a Mon Sep 17 00:00:00 2001 From: lindicaphxag-tech Date: Fri, 18 Sep 2026 17:28:50 +0800 Subject: [PATCH 05/27] fix(proxy): avoid reassigned Final values --- litellm/proxy/proxy_server.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 988f37b1e09..a391be8ca99 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -15839,7 +15839,7 @@ async def alerting_settings( } ) - config_alerting_args: Final = general_settings.get("alerting_args") or MappingProxyType({}) + config_alerting_args = general_settings.get("alerting_args") or MappingProxyType({}) return_val: Final = [] @@ -15862,8 +15862,8 @@ async def alerting_settings( for field_name, field_info in SlackAlertingArgs.model_fields.items(): if field_name in allowed_args: - field_value: Final - _stored_in_db: Final + field_value: Any + _stored_in_db: bool | None if field_name in alerting_args_dict: field_value = alerting_args_dict[field_name] _stored_in_db = True From 755e5f60229679f08752c412f2a0deada0806e74 Mon Sep 17 00:00:00 2001 From: lindicaphxag-tech Date: Sat, 19 Sep 2026 17:49:36 +0800 Subject: [PATCH 06/27] fix(ui): omit empty alerting values on update --- .../alerting/alerting_settings.integration.test.tsx | 3 +++ .../src/components/alerting/alerting_settings.tsx | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx b/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx index d19d6edce41..bb109f7445e 100644 --- a/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx +++ b/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx @@ -58,6 +58,9 @@ describe("AlertingSettings", () => { renderWithProviders(); await screen.findByDisplayValue("60"); + const numberInputs = screen.getAllByRole("spinbutton"); + await user.type(numberInputs[1], "5"); + await user.clear(numberInputs[1]); await user.click(screen.getByRole("switch", { name: "slack_alerting" })); await user.click(screen.getByRole("button", { name: "Update Settings" })); diff --git a/ui/litellm-dashboard/src/components/alerting/alerting_settings.tsx b/ui/litellm-dashboard/src/components/alerting/alerting_settings.tsx index 87b4258a26a..1a1006ca08b 100644 --- a/ui/litellm-dashboard/src/components/alerting/alerting_settings.tsx +++ b/ui/litellm-dashboard/src/components/alerting/alerting_settings.tsx @@ -63,7 +63,10 @@ const AlertingSettings: React.FC = ({ accessToken, premiu }); const { slack_alerting, ...updatedAlertingArgs } = formValues; - const alertingArgs = { ...configuredAlertingArgs, ...updatedAlertingArgs }; + const alertingArgs = { + ...configuredAlertingArgs, + ...Object.fromEntries(Object.entries(updatedAlertingArgs).filter(([, value]) => value != null && value !== "")), + }; try { await updateConfigFieldSetting(accessToken, "alerting_args", alertingArgs); if (typeof slack_alerting === "boolean") { From e6d6679e75f1c32a164785320eee3ecc344f727a Mon Sep 17 00:00:00 2001 From: lindicaphxag-tech Date: Sat, 19 Sep 2026 17:51:16 +0800 Subject: [PATCH 07/27] style(proxy): annotate alerting response values --- litellm/proxy/proxy_server.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index a391be8ca99..24a7a3cef72 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -15862,17 +15862,20 @@ async def alerting_settings( for field_name, field_info in SlackAlertingArgs.model_fields.items(): if field_name in allowed_args: - field_value: Any - _stored_in_db: bool | None - if field_name in alerting_args_dict: - field_value = alerting_args_dict[field_name] - _stored_in_db = True - elif field_name in config_alerting_args: - field_value = config_alerting_args[field_name] - _stored_in_db = False - else: - field_value = None - _stored_in_db = None + field_value: Final[Any] = ( + alerting_args_dict[field_name] + if field_name in alerting_args_dict + else config_alerting_args[field_name] + if field_name in config_alerting_args + else None + ) + _stored_in_db: Final[bool | None] = ( + True + if field_name in alerting_args_dict + else False + if field_name in config_alerting_args + else None + ) _response_obj = ConfigList( field_name=field_name, From f6d1e57a891a6e141854e09981bcce85c3cb8445 Mon Sep 17 00:00:00 2001 From: lindicaphxag-tech Date: Fri, 2 Oct 2026 03:33:02 +0800 Subject: [PATCH 08/27] fix(proxy): satisfy Final annotations in alerting settings --- litellm/proxy/proxy_server.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 30f111cfc56..c0f3f56d7ff 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -16725,24 +16725,25 @@ async def alerting_settings( for field_name, field_info in SlackAlertingArgs.model_fields.items(): if field_name in allowed_args: field_default: JsonValue = _get_field_default(field_info) - field_source = _nested_setting_source( + field_source: Final = _nested_setting_source( settings, alerting_args_dict, "alerting_args", field_name, field_default, ) + field_value: Final[JsonValue | None] if field_source == "config": field_value = config_alerting_args.get(field_name) elif field_source == "db": field_value = alerting_args_dict.get(field_name) else: field_value = None - _stored_in_db: bool | None = ( + _stored_in_db: Final[bool | None] = ( True if field_source == "db" else False if field_source == "config" else None ) - _response_obj = ConfigList( + _response_obj: Final = ConfigList( field_name=field_name, field_type=allowed_args[field_name], field_description=field_info.description or "", From 65b756f4660b04098ccf60d9d3ca83b6eaebbc5a Mon Sep 17 00:00:00 2001 From: lindicaphxag-tech Date: Fri, 2 Oct 2026 03:46:52 +0800 Subject: [PATCH 09/27] fix(proxy): isolate alerting field resolution for type checks --- litellm/proxy/proxy_server.py | 77 ++++++++++++++++++++++------------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index c0f3f56d7ff..635d149913b 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -16625,6 +16625,45 @@ def _nested_setting_source( return "db" if field_name in db_values else unset_source +def _alerting_field_response( + *, + settings: SettingsStore, + db_values: Mapping[str, JsonValue], + config_values: Mapping[str, JsonValue], + allowed_args: Mapping[str, str], + field_name: str, + field_info: FieldInfo, +) -> ConfigList: + field_default: Final[JsonValue] = _get_field_default(field_info) + field_source: Final[FieldSource] = _nested_setting_source( + settings, + db_values, + "alerting_args", + field_name, + field_default, + ) + field_value: Final[JsonValue | None] = ( + config_values.get(field_name) + if field_source == "config" + else db_values.get(field_name) + if field_source == "db" + else None + ) + stored_in_db: Final[bool | None] = ( + True if field_source == "db" else False if field_source == "config" else None + ) + return ConfigList( + field_name=field_name, + field_type=allowed_args[field_name], + field_description=field_info.description or "", + field_value=field_value, + stored_in_db=stored_in_db, + source=field_source, + field_default_value=field_default, + premium_field=field_name == "region_outage_alert_ttl", + ) + + @router.get( "/alerting/settings", description="Return the configurable alerting param, description, and current value", @@ -16724,36 +16763,16 @@ async def alerting_settings( for field_name, field_info in SlackAlertingArgs.model_fields.items(): if field_name in allowed_args: - field_default: JsonValue = _get_field_default(field_info) - field_source: Final = _nested_setting_source( - settings, - alerting_args_dict, - "alerting_args", - field_name, - field_default, + return_val.append( + _alerting_field_response( + settings=settings, + db_values=alerting_args_dict, + config_values=config_alerting_args, + allowed_args=allowed_args, + field_name=field_name, + field_info=field_info, + ) ) - field_value: Final[JsonValue | None] - if field_source == "config": - field_value = config_alerting_args.get(field_name) - elif field_source == "db": - field_value = alerting_args_dict.get(field_name) - else: - field_value = None - _stored_in_db: Final[bool | None] = ( - True if field_source == "db" else False if field_source == "config" else None - ) - - _response_obj: Final = ConfigList( - field_name=field_name, - field_type=allowed_args[field_name], - field_description=field_info.description or "", - field_value=field_value, - stored_in_db=_stored_in_db, - source=field_source, - field_default_value=field_default, - premium_field=(True if field_name == "region_outage_alert_ttl" else False), - ) - return_val.append(_response_obj) return return_val From 7d58b86cccd7687fd7b99784b5e71a9160aec7ab Mon Sep 17 00:00:00 2001 From: lindicaphxag-tech Date: Fri, 2 Oct 2026 03:49:42 +0800 Subject: [PATCH 10/27] style(proxy): simplify alerting field helpers --- litellm/proxy/proxy_server.py | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 635d149913b..9e3bda37563 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -16625,6 +16625,27 @@ def _nested_setting_source( return "db" if field_name in db_values else unset_source +def _alerting_field_value( + source: FieldSource, + field_name: str, + config_values: Mapping[str, JsonValue], + db_values: Mapping[str, JsonValue], +) -> JsonValue | None: + if source == "config": + return config_values.get(field_name) + if source == "db": + return db_values.get(field_name) + return None + + +def _alerting_stored_in_db(source: FieldSource) -> bool | None: + if source == "db": + return True + if source == "config": + return False + return None + + def _alerting_field_response( *, settings: SettingsStore, @@ -16642,16 +16663,8 @@ def _alerting_field_response( field_name, field_default, ) - field_value: Final[JsonValue | None] = ( - config_values.get(field_name) - if field_source == "config" - else db_values.get(field_name) - if field_source == "db" - else None - ) - stored_in_db: Final[bool | None] = ( - True if field_source == "db" else False if field_source == "config" else None - ) + field_value: Final = _alerting_field_value(field_source, field_name, config_values, db_values) + stored_in_db: Final = _alerting_stored_in_db(field_source) return ConfigList( field_name=field_name, field_type=allowed_args[field_name], From d3d3aca82cf6d470769ffbecf501e9a288e3b9a0 Mon Sep 17 00:00:00 2001 From: lindicaphxag-tech Date: Fri, 2 Oct 2026 05:18:25 +0800 Subject: [PATCH 11/27] test(proxy): cover alerting field helpers --- .../proxy_server/test_routes_model_metrics.py | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/unit/proxy/proxy_server/test_routes_model_metrics.py b/tests/unit/proxy/proxy_server/test_routes_model_metrics.py index 4c22a9a409a..3ea4fce8963 100644 --- a/tests/unit/proxy/proxy_server/test_routes_model_metrics.py +++ b/tests/unit/proxy/proxy_server/test_routes_model_metrics.py @@ -461,3 +461,50 @@ def test_alerting_settings_returns_database_values(client, auth_as, monkeypatch) assert setting["field_value"] == 60 assert setting["stored_in_db"] is True +@pytest.mark.parametrize( + ("source", "expected_value", "expected_stored_in_db"), + [ + ("config", 60, False), + ("db", 90, True), + ("unset", None, None), + ("default", None, None), + ], +) +def test_alerting_field_helpers_cover_each_source( + source, expected_value, expected_stored_in_db +): + config_values = {"budget_alert_ttl": 60} + db_values = {"budget_alert_ttl": 90} + + assert ( + proxy_server._alerting_field_value( + source, + "budget_alert_ttl", + config_values, + db_values, + ) + == expected_value + ) + assert proxy_server._alerting_stored_in_db(source) is expected_stored_in_db + + +def test_alerting_field_response_uses_explicit_config_value(): + settings = SettingsStore("general_settings") + settings.load_yaml({"alerting_args": {"budget_alert_ttl": 60}}) + field_info = proxy_server.SlackAlertingArgs.model_fields["budget_alert_ttl"] + + result = proxy_server._alerting_field_response( + settings=settings, + db_values={}, + config_values={"budget_alert_ttl": 60}, + allowed_args={"budget_alert_ttl": "Integer"}, + field_name="budget_alert_ttl", + field_info=field_info, + ) + + assert result.field_name == "budget_alert_ttl" + assert result.field_type == "Integer" + assert result.field_value == 60 + assert result.stored_in_db is False + assert result.source == "config" + From 733bcdd152521d650a9b203bdfa3853f660ff5bd Mon Sep 17 00:00:00 2001 From: lindicaphxag-tech Date: Fri, 2 Oct 2026 05:24:42 +0800 Subject: [PATCH 12/27] fix(ui): keep reset values in form state --- .../src/components/alerting/dynamic_form.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/ui/litellm-dashboard/src/components/alerting/dynamic_form.tsx b/ui/litellm-dashboard/src/components/alerting/dynamic_form.tsx index 42aa58ca0ea..be0e401fd2e 100644 --- a/ui/litellm-dashboard/src/components/alerting/dynamic_form.tsx +++ b/ui/litellm-dashboard/src/components/alerting/dynamic_form.tsx @@ -12,6 +12,7 @@ interface AlertingSetting { field_description: string; field_type: string; field_value: any; + field_default_value: any; stored_in_db: boolean | null; premium_field: boolean; } @@ -24,7 +25,7 @@ interface DynamicFormProps { premiumUser: boolean; } -type AlertingFormValues = Record; +type AlertingFormValues = Record; const DynamicForm: React.FC = ({ alertingSettings, @@ -62,6 +63,12 @@ const DynamicForm: React.FC = ({ handleInputChange(setting.field_name, checked); }; + + const handleReset = (setting: AlertingSetting, index: number) => { + form.setValue(setting.field_name, setting.field_default_value ?? ""); + handleResetField(setting.field_name, index); + }; + const renderControl = (setting: AlertingSetting) => { if (setting.field_type === "Integer" || setting.field_type === "Float") { return ( @@ -122,7 +129,7 @@ const DynamicForm: React.FC = ({ variant="ghost" size="icon-sm" aria-label={`Reset ${value.field_name}`} - onClick={() => handleResetField(value.field_name, index)} + onClick={() => handleReset(value, index)} className="text-destructive" > From 3db955f1cd32764641cf22f3e3deecf63610502d Mon Sep 17 00:00:00 2001 From: lindicaphxag-tech Date: Fri, 2 Oct 2026 05:24:46 +0800 Subject: [PATCH 13/27] test(ui): cover alert threshold reset --- .../alerting_settings.integration.test.tsx | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx b/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx index bb109f7445e..6121e9e127c 100644 --- a/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx +++ b/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx @@ -1,6 +1,6 @@ import React from "react"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { screen, waitFor } from "@testing-library/react"; +import { fireEvent, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { renderWithProviders } from "@/../tests/test-utils"; import AlertingSettings from "./alerting_settings"; @@ -59,8 +59,8 @@ describe("AlertingSettings", () => { await screen.findByDisplayValue("60"); const numberInputs = screen.getAllByRole("spinbutton"); - await user.type(numberInputs[1], "5"); - await user.clear(numberInputs[1]); + fireEvent.change(numberInputs[1], { target: { value: "5" } }); + fireEvent.change(numberInputs[1], { target: { value: "" } }); await user.click(screen.getByRole("switch", { name: "slack_alerting" })); await user.click(screen.getByRole("button", { name: "Update Settings" })); @@ -71,4 +71,20 @@ describe("AlertingSettings", () => { }); expect(updateConfigFieldSetting).toHaveBeenNthCalledWith(2, "sk-test", "alerting", ["slack"]); }); + + it("submits the reset value instead of a stale typed threshold", async () => { + const user = userEvent.setup(); + renderWithProviders(); + + const budgetInput = await screen.findByDisplayValue("60"); + fireEvent.change(budgetInput, { target: { value: "75" } }); + await user.click(screen.getByRole("button", { name: "Reset budget_alert_ttl" })); + await user.click(screen.getByRole("button", { name: "Update Settings" })); + + await waitFor(() => { + expect(updateConfigFieldSetting).toHaveBeenCalledWith("sk-test", "alerting_args", { + budget_alert_ttl: 30, + }); + }); + }); }); From 3879b05f25fcb9d5cfbae6df12894b3cc3f54ca4 Mon Sep 17 00:00:00 2001 From: lindicaphxag-tech Date: Fri, 2 Oct 2026 05:26:15 +0800 Subject: [PATCH 14/27] fix(ui): preserve alerting reset intent on submit --- .../components/alerting/alerting_settings.tsx | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/ui/litellm-dashboard/src/components/alerting/alerting_settings.tsx b/ui/litellm-dashboard/src/components/alerting/alerting_settings.tsx index 1a1006ca08b..34e67f48d54 100644 --- a/ui/litellm-dashboard/src/components/alerting/alerting_settings.tsx +++ b/ui/litellm-dashboard/src/components/alerting/alerting_settings.tsx @@ -24,6 +24,7 @@ interface AlertingSettingsProps { const AlertingSettings: React.FC = ({ accessToken, premiumUser }) => { const [alertingSettings, setAlertingSettings] = useState([]); + const [resetFields, setResetFields] = useState>(new Set()); useEffect(() => { // get values @@ -36,7 +37,15 @@ const AlertingSettings: React.FC = ({ accessToken, premiu }, [accessToken]); const handleInputChange = (fieldName: string, newValue: any) => { - // Update the value in the state + setResetFields((previous) => { + if (!previous.has(fieldName)) { + return previous; + } + const next = new Set(previous); + next.delete(fieldName); + return next; + }); + const updatedSettings = alertingSettings.map((setting) => setting.field_name === fieldName ? { ...setting, field_value: newValue } : setting, ); @@ -57,7 +66,11 @@ const AlertingSettings: React.FC = ({ accessToken, premiu const configuredAlertingArgs: Record = {}; alertingSettings.forEach((setting) => { - if (setting.field_name !== "slack_alerting" && setting.field_value != null) { + if ( + setting.field_name !== "slack_alerting" && + !resetFields.has(setting.field_name) && + setting.field_value != null + ) { configuredAlertingArgs[setting.field_name] = setting.field_value; } }); @@ -65,7 +78,11 @@ const AlertingSettings: React.FC = ({ accessToken, premiu const { slack_alerting, ...updatedAlertingArgs } = formValues; const alertingArgs = { ...configuredAlertingArgs, - ...Object.fromEntries(Object.entries(updatedAlertingArgs).filter(([, value]) => value != null && value !== "")), + ...Object.fromEntries( + Object.entries(updatedAlertingArgs).filter( + ([fieldName, value]) => !resetFields.has(fieldName) && value != null && value !== "", + ), + ), }; try { await updateConfigFieldSetting(accessToken, "alerting_args", alertingArgs); @@ -89,8 +106,11 @@ const AlertingSettings: React.FC = ({ accessToken, premiu } try { - // deleteConfigFieldSetting(accessToken, fieldName); - // update value in state + setResetFields((previous) => { + const next = new Set(previous); + next.add(fieldName); + return next; + }); const updatedSettings = alertingSettings.map((setting) => setting.field_name === fieldName From d538d199f07169ca21dd6187f654bc1b51934683 Mon Sep 17 00:00:00 2001 From: lindicaphxag-tech Date: Fri, 2 Oct 2026 05:26:32 +0800 Subject: [PATCH 15/27] test(ui): cover alerting reset submission --- .../alerting_settings.integration.test.tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx b/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx index 6121e9e127c..6bd82080aae 100644 --- a/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx +++ b/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx @@ -87,4 +87,20 @@ describe("AlertingSettings", () => { }); }); }); + + it("does not restore a stale typed value after reset", async () => { + const user = userEvent.setup(); + renderWithProviders(); + + const budgetInput = await screen.findByDisplayValue("60"); + fireEvent.change(budgetInput, { target: { value: "75" } }); + + await user.click(screen.getByRole("button", { name: "Reset budget_alert_ttl" })); + await user.click(screen.getByRole("button", { name: "Update Settings" })); + + await waitFor(() => { + expect(updateConfigFieldSetting).toHaveBeenCalledWith("sk-test", "alerting_args", {}); + }); + }); + }); From a9732ab603efe327ddbed8ced562be1f48f3104c Mon Sep 17 00:00:00 2001 From: lindicaphxag-tech Date: Fri, 2 Oct 2026 05:27:55 +0800 Subject: [PATCH 16/27] style(ui): format alerting dynamic form --- .../src/components/alerting/dynamic_form.tsx | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/ui/litellm-dashboard/src/components/alerting/dynamic_form.tsx b/ui/litellm-dashboard/src/components/alerting/dynamic_form.tsx index be0e401fd2e..1c1724f854d 100644 --- a/ui/litellm-dashboard/src/components/alerting/dynamic_form.tsx +++ b/ui/litellm-dashboard/src/components/alerting/dynamic_form.tsx @@ -53,7 +53,10 @@ const DynamicForm: React.FC = ({ handleInputChange(setting.field_name, raw === "" ? null : Number(raw)); }; - const handleTextChange = (setting: AlertingSetting, event: React.ChangeEvent) => { + const handleTextChange = ( + setting: AlertingSetting, + event: React.ChangeEvent, + ) => { form.setValue(setting.field_name, event.target.value); handleInputChange(setting.field_name, event); }; @@ -63,7 +66,6 @@ const DynamicForm: React.FC = ({ handleInputChange(setting.field_name, checked); }; - const handleReset = (setting: AlertingSetting, index: number) => { form.setValue(setting.field_name, setting.field_default_value ?? ""); handleResetField(setting.field_name, index); @@ -89,7 +91,12 @@ const DynamicForm: React.FC = ({ /> ); } - return handleTextChange(setting, event)} />; + return ( + handleTextChange(setting, event)} + /> + ); }; return ( @@ -98,7 +105,9 @@ const DynamicForm: React.FC = ({

{value.field_name}

-

{value.field_description}

+

+ {value.field_description} +

{value.premium_field && !premiumUser ? ( From 5ccad1b17878b526736caec74b9e4e7290cb8563 Mon Sep 17 00:00:00 2001 From: lindicaphxag-tech Date: Fri, 2 Oct 2026 05:30:28 +0800 Subject: [PATCH 17/27] test(ui): remove obsolete reset-to-default expectation --- .../alerting_settings.integration.test.tsx | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx b/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx index 6bd82080aae..b13686cf74b 100644 --- a/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx +++ b/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx @@ -72,22 +72,6 @@ describe("AlertingSettings", () => { expect(updateConfigFieldSetting).toHaveBeenNthCalledWith(2, "sk-test", "alerting", ["slack"]); }); - it("submits the reset value instead of a stale typed threshold", async () => { - const user = userEvent.setup(); - renderWithProviders(); - - const budgetInput = await screen.findByDisplayValue("60"); - fireEvent.change(budgetInput, { target: { value: "75" } }); - await user.click(screen.getByRole("button", { name: "Reset budget_alert_ttl" })); - await user.click(screen.getByRole("button", { name: "Update Settings" })); - - await waitFor(() => { - expect(updateConfigFieldSetting).toHaveBeenCalledWith("sk-test", "alerting_args", { - budget_alert_ttl: 30, - }); - }); - }); - it("does not restore a stale typed value after reset", async () => { const user = userEvent.setup(); renderWithProviders(); @@ -102,5 +86,4 @@ describe("AlertingSettings", () => { expect(updateConfigFieldSetting).toHaveBeenCalledWith("sk-test", "alerting_args", {}); }); }); - }); From 4246ea473af3ab5fc0ed1d7ca56b267bf53d9561 Mon Sep 17 00:00:00 2001 From: lindicaphxag-tech Date: Fri, 2 Oct 2026 05:35:58 +0800 Subject: [PATCH 18/27] fix(ui): clear alerting edit state on refetch --- .../src/components/alerting/alerting_settings.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ui/litellm-dashboard/src/components/alerting/alerting_settings.tsx b/ui/litellm-dashboard/src/components/alerting/alerting_settings.tsx index 34e67f48d54..22e1a0eda0d 100644 --- a/ui/litellm-dashboard/src/components/alerting/alerting_settings.tsx +++ b/ui/litellm-dashboard/src/components/alerting/alerting_settings.tsx @@ -32,6 +32,7 @@ const AlertingSettings: React.FC = ({ accessToken, premiu return; } alertingSettingsCall(accessToken).then((data) => { + setResetFields(new Set()); setAlertingSettings(data); }); }, [accessToken]); @@ -129,6 +130,7 @@ const AlertingSettings: React.FC = ({ accessToken, premiu return ( Date: Fri, 2 Oct 2026 05:36:11 +0800 Subject: [PATCH 19/27] test(ui): reset alerting form state on token change --- .../alerting_settings.integration.test.tsx | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx b/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx index b13686cf74b..486b8dc6648 100644 --- a/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx +++ b/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx @@ -86,4 +86,34 @@ describe("AlertingSettings", () => { expect(updateConfigFieldSetting).toHaveBeenCalledWith("sk-test", "alerting_args", {}); }); }); + + it("clears reset and form state when the access token changes", async () => { + const refreshedSettings = settingsResponse.map((setting) => + setting.field_name === "budget_alert_ttl" ? { ...setting, field_value: 90 } : setting, + ); + alertingSettingsCall.mockImplementation((token: string) => + Promise.resolve(token === "sk-new" ? refreshedSettings : settingsResponse), + ); + + const user = userEvent.setup(); + const { rerender } = renderWithProviders(); + + const budgetInput = await screen.findByDisplayValue("60"); + fireEvent.change(budgetInput, { target: { value: "75" } }); + await user.click(screen.getByRole("button", { name: "Reset budget_alert_ttl" })); + + rerender(); + await screen.findByDisplayValue("90"); + + await user.click(screen.getByRole("switch", { name: "slack_alerting" })); + await user.click(screen.getByRole("button", { name: "Update Settings" })); + + await waitFor(() => { + expect(updateConfigFieldSetting).toHaveBeenNthCalledWith(1, "sk-new", "alerting_args", { + budget_alert_ttl: 90, + }); + }); + expect(updateConfigFieldSetting).toHaveBeenNthCalledWith(2, "sk-new", "alerting", ["slack"]); + }); + }); From aecc0b7ab3422411ec56bd533d83c9b6bd9b5c5d Mon Sep 17 00:00:00 2001 From: lindicaphxag-tech Date: Fri, 2 Oct 2026 05:40:46 +0800 Subject: [PATCH 20/27] style(ui): format alerting integration test --- .../components/alerting/alerting_settings.integration.test.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx b/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx index 486b8dc6648..7a10c787056 100644 --- a/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx +++ b/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx @@ -115,5 +115,4 @@ describe("AlertingSettings", () => { }); expect(updateConfigFieldSetting).toHaveBeenNthCalledWith(2, "sk-new", "alerting", ["slack"]); }); - }); From 7e0a48ec0af134109a75e1ae748fd074f5f7f24b Mon Sep 17 00:00:00 2001 From: lindicaphxag-tech Date: Fri, 2 Oct 2026 05:43:23 +0800 Subject: [PATCH 21/27] fix(ui): ignore stale alerting settings responses --- .../src/components/alerting/alerting_settings.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ui/litellm-dashboard/src/components/alerting/alerting_settings.tsx b/ui/litellm-dashboard/src/components/alerting/alerting_settings.tsx index 22e1a0eda0d..6a94ae09dec 100644 --- a/ui/litellm-dashboard/src/components/alerting/alerting_settings.tsx +++ b/ui/litellm-dashboard/src/components/alerting/alerting_settings.tsx @@ -27,14 +27,22 @@ const AlertingSettings: React.FC = ({ accessToken, premiu const [resetFields, setResetFields] = useState>(new Set()); useEffect(() => { - // get values if (!accessToken) { return; } + + let isCurrentRequest = true; alertingSettingsCall(accessToken).then((data) => { + if (!isCurrentRequest) { + return; + } setResetFields(new Set()); setAlertingSettings(data); }); + + return () => { + isCurrentRequest = false; + }; }, [accessToken]); const handleInputChange = (fieldName: string, newValue: any) => { From ee4830b96cf02539eee9a315e09c21b4b9782d78 Mon Sep 17 00:00:00 2001 From: lindicaphxag-tech Date: Fri, 2 Oct 2026 05:45:16 +0800 Subject: [PATCH 22/27] test(ui): ignore stale alerting settings responses --- .../alerting_settings.integration.test.tsx | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx b/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx index 7a10c787056..023f9b0e3f7 100644 --- a/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx +++ b/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx @@ -1,6 +1,6 @@ import React from "react"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { fireEvent, screen, waitFor } from "@testing-library/react"; +import { act, fireEvent, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { renderWithProviders } from "@/../tests/test-utils"; import AlertingSettings from "./alerting_settings"; @@ -115,4 +115,38 @@ describe("AlertingSettings", () => { }); expect(updateConfigFieldSetting).toHaveBeenNthCalledWith(2, "sk-new", "alerting", ["slack"]); }); + + it("ignores an older settings response after the access token changes", async () => { + let resolveOldRequest!: (value: typeof settingsResponse) => void; + let resolveNewRequest!: (value: typeof settingsResponse) => void; + const oldRequest = new Promise((resolve) => { + resolveOldRequest = resolve; + }); + const newRequest = new Promise((resolve) => { + resolveNewRequest = resolve; + }); + const refreshedSettings = settingsResponse.map((setting) => + setting.field_name === "budget_alert_ttl" ? { ...setting, field_value: 90 } : setting, + ); + + alertingSettingsCall.mockImplementation((token: string) => (token === "sk-new" ? newRequest : oldRequest)); + + const { rerender } = renderWithProviders(); + rerender(); + + await act(async () => { + resolveNewRequest(refreshedSettings); + await newRequest; + }); + await screen.findByDisplayValue("90"); + + await act(async () => { + resolveOldRequest(settingsResponse); + await oldRequest; + }); + + expect(screen.getByDisplayValue("90")).toBeInTheDocument(); + expect(screen.queryByDisplayValue("60")).not.toBeInTheDocument(); + }); + }); From 97b537ff3bb9574037cd9abe9b6344cdf6fb1b01 Mon Sep 17 00:00:00 2001 From: lindicaphxag-tech Date: Fri, 2 Oct 2026 05:46:45 +0800 Subject: [PATCH 23/27] style(ui): format stale-response regression --- .../alerting/alerting_settings.integration.test.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx b/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx index 023f9b0e3f7..38bf0c7624d 100644 --- a/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx +++ b/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx @@ -129,7 +129,9 @@ describe("AlertingSettings", () => { setting.field_name === "budget_alert_ttl" ? { ...setting, field_value: 90 } : setting, ); - alertingSettingsCall.mockImplementation((token: string) => (token === "sk-new" ? newRequest : oldRequest)); + alertingSettingsCall.mockImplementation((token: string) => + token === "sk-new" ? newRequest : oldRequest, + ); const { rerender } = renderWithProviders(); rerender(); @@ -148,5 +150,4 @@ describe("AlertingSettings", () => { expect(screen.getByDisplayValue("90")).toBeInTheDocument(); expect(screen.queryByDisplayValue("60")).not.toBeInTheDocument(); }); - }); From 7c87b0658a3811f27d27b68253cd17e482408cd7 Mon Sep 17 00:00:00 2001 From: lindicaphxag-tech Date: Fri, 2 Oct 2026 05:50:16 +0800 Subject: [PATCH 24/27] style(ui): match prettier wrapping --- .../alerting/alerting_settings.integration.test.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx b/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx index 38bf0c7624d..22ecb48294a 100644 --- a/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx +++ b/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx @@ -129,9 +129,7 @@ describe("AlertingSettings", () => { setting.field_name === "budget_alert_ttl" ? { ...setting, field_value: 90 } : setting, ); - alertingSettingsCall.mockImplementation((token: string) => - token === "sk-new" ? newRequest : oldRequest, - ); + alertingSettingsCall.mockImplementation((token: string) => (token === "sk-new" ? newRequest : oldRequest)); const { rerender } = renderWithProviders(); rerender(); From 4433db7058e701b302dd33d8ce2d49b6ae74c069 Mon Sep 17 00:00:00 2001 From: lindicaphxag-tech Date: Fri, 2 Oct 2026 05:52:41 +0800 Subject: [PATCH 25/27] refactor(ui): keep alerting state updates immutable --- .../components/alerting/alerting_settings.tsx | 43 ++++++++----------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/ui/litellm-dashboard/src/components/alerting/alerting_settings.tsx b/ui/litellm-dashboard/src/components/alerting/alerting_settings.tsx index 6a94ae09dec..0a90e4b220c 100644 --- a/ui/litellm-dashboard/src/components/alerting/alerting_settings.tsx +++ b/ui/litellm-dashboard/src/components/alerting/alerting_settings.tsx @@ -31,9 +31,9 @@ const AlertingSettings: React.FC = ({ accessToken, premiu return; } - let isCurrentRequest = true; + const controller = new AbortController(); alertingSettingsCall(accessToken).then((data) => { - if (!isCurrentRequest) { + if (controller.signal.aborted) { return; } setResetFields(new Set()); @@ -41,19 +41,14 @@ const AlertingSettings: React.FC = ({ accessToken, premiu }); return () => { - isCurrentRequest = false; + controller.abort(); }; }, [accessToken]); const handleInputChange = (fieldName: string, newValue: any) => { - setResetFields((previous) => { - if (!previous.has(fieldName)) { - return previous; - } - const next = new Set(previous); - next.delete(fieldName); - return next; - }); + setResetFields((previous) => + previous.has(fieldName) ? new Set([...previous].filter((name) => name !== fieldName)) : previous, + ); const updatedSettings = alertingSettings.map((setting) => setting.field_name === fieldName ? { ...setting, field_value: newValue } : setting, @@ -73,16 +68,16 @@ const AlertingSettings: React.FC = ({ accessToken, premiu return; } - const configuredAlertingArgs: Record = {}; - alertingSettings.forEach((setting) => { - if ( - setting.field_name !== "slack_alerting" && - !resetFields.has(setting.field_name) && - setting.field_value != null - ) { - configuredAlertingArgs[setting.field_name] = setting.field_value; - } - }); + const configuredAlertingArgs: Record = Object.fromEntries( + alertingSettings + .filter( + (setting) => + setting.field_name !== "slack_alerting" && + !resetFields.has(setting.field_name) && + setting.field_value != null, + ) + .map((setting) => [setting.field_name, setting.field_value]), + ); const { slack_alerting, ...updatedAlertingArgs } = formValues; const alertingArgs = { @@ -115,11 +110,7 @@ const AlertingSettings: React.FC = ({ accessToken, premiu } try { - setResetFields((previous) => { - const next = new Set(previous); - next.add(fieldName); - return next; - }); + setResetFields((previous) => new Set([...previous, fieldName])); const updatedSettings = alertingSettings.map((setting) => setting.field_name === fieldName From 0fe655aea13241acec59279c480148cabea42f26 Mon Sep 17 00:00:00 2001 From: lindicaphxag-tech Date: Fri, 2 Oct 2026 05:53:25 +0800 Subject: [PATCH 26/27] test(ui): keep stale-response fixture immutable --- .../alerting_settings.integration.test.tsx | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx b/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx index 22ecb48294a..ac857b1a8bd 100644 --- a/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx +++ b/ui/litellm-dashboard/src/components/alerting/alerting_settings.integration.test.tsx @@ -117,32 +117,28 @@ describe("AlertingSettings", () => { }); it("ignores an older settings response after the access token changes", async () => { - let resolveOldRequest!: (value: typeof settingsResponse) => void; - let resolveNewRequest!: (value: typeof settingsResponse) => void; - const oldRequest = new Promise((resolve) => { - resolveOldRequest = resolve; - }); - const newRequest = new Promise((resolve) => { - resolveNewRequest = resolve; - }); + const oldRequest = Promise.withResolvers(); + const newRequest = Promise.withResolvers(); const refreshedSettings = settingsResponse.map((setting) => setting.field_name === "budget_alert_ttl" ? { ...setting, field_value: 90 } : setting, ); - alertingSettingsCall.mockImplementation((token: string) => (token === "sk-new" ? newRequest : oldRequest)); + alertingSettingsCall.mockImplementation((token: string) => + token === "sk-new" ? newRequest.promise : oldRequest.promise, + ); const { rerender } = renderWithProviders(); rerender(); await act(async () => { - resolveNewRequest(refreshedSettings); - await newRequest; + newRequest.resolve(refreshedSettings); + await newRequest.promise; }); await screen.findByDisplayValue("90"); await act(async () => { - resolveOldRequest(settingsResponse); - await oldRequest; + oldRequest.resolve(settingsResponse); + await oldRequest.promise; }); expect(screen.getByDisplayValue("90")).toBeInTheDocument(); From 36eac9eb2259dc17d39b6d159cc8add5d7ef7e29 Mon Sep 17 00:00:00 2001 From: lindicaphxag-tech Date: Fri, 2 Oct 2026 05:53:28 +0800 Subject: [PATCH 27/27] refactor(ui): avoid alerting submit rebinding --- .../src/components/alerting/alerting_settings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/litellm-dashboard/src/components/alerting/alerting_settings.tsx b/ui/litellm-dashboard/src/components/alerting/alerting_settings.tsx index 0a90e4b220c..406748a912e 100644 --- a/ui/litellm-dashboard/src/components/alerting/alerting_settings.tsx +++ b/ui/litellm-dashboard/src/components/alerting/alerting_settings.tsx @@ -62,7 +62,7 @@ const AlertingSettings: React.FC = ({ accessToken, premiu return; } - let fieldValue = formValues; + const fieldValue = formValues; if (fieldValue == null || fieldValue == undefined) { return;