From d85316818deecddf54e048c3209e264d84cef188 Mon Sep 17 00:00:00 2001 From: Jimmy Zhu Date: Tue, 13 Oct 2026 18:01:21 -0700 Subject: [PATCH 1/3] feat(billing): remove billing alert settings the platform ignores Co-Authored-By: Claude Opus 5.5 --- .../billing/frontend/generated/api.schemas.ts | 34 --------- .../billing/frontend/generated/api.zod.ts | 69 ------------------- products/billing_alerts/backend/models.py | 2 +- .../backend/presentation/serializers.py | 27 -------- .../billing_alerts/backend/test/test_api.py | 23 ++++--- .../frontend/BillingAlertEditor.tsx | 44 +----------- .../frontend/BillingAlertHistory.test.ts | 18 +---- .../frontend/BillingAlertHistory.tsx | 11 ++- .../frontend/billingAlertFormLogic.test.ts | 3 - .../frontend/billingAlertFormLogic.ts | 28 -------- services/mcp/src/api/generated.ts | 34 --------- 11 files changed, 23 insertions(+), 270 deletions(-) diff --git a/products/billing/frontend/generated/api.schemas.ts b/products/billing/frontend/generated/api.schemas.ts index 46fa6a440c43..657421819742 100644 --- a/products/billing/frontend/generated/api.schemas.ts +++ b/products/billing/frontend/generated/api.schemas.ts @@ -343,23 +343,12 @@ export interface BillingAlertConfigurationApi { * @pattern ^-?\d{0,14}(?:\.\d{0,6})?$ */ threshold_value?: string | null - /** - * Minimum current value before the alert can fire. - * @pattern ^-?\d{0,14}(?:\.\d{0,6})?$ - */ - minimum_value?: string /** * Reserved for future increase-over-baseline rules. Not used by absolute value alerts. * @minimum 1 * @maximum 90 */ baseline_window_days?: number - /** - * Hours after a UTC billing date ends before it becomes eligible for evaluation. - * @minimum 0 - * @maximum 72 - */ - evaluation_delay_hours?: number /** Current lifecycle state of this alert. * * * `not_firing` - Not firing @@ -368,12 +357,6 @@ export interface BillingAlertConfigurationApi { * * `snoozed` - Snoozed * * `broken` - Broken */ readonly state: BillingAlertConfigurationStateEnumApi - /** - * Minimum hours between repeated firing notifications. - * @minimum 0 - * @maximum 720 - */ - cooldown_hours?: number /** * ISO 8601 timestamp until which evaluation and notifications are snoozed, or null to resume. * @nullable @@ -476,23 +459,12 @@ export interface PatchedBillingAlertConfigurationApi { * @pattern ^-?\d{0,14}(?:\.\d{0,6})?$ */ threshold_value?: string | null - /** - * Minimum current value before the alert can fire. - * @pattern ^-?\d{0,14}(?:\.\d{0,6})?$ - */ - minimum_value?: string /** * Reserved for future increase-over-baseline rules. Not used by absolute value alerts. * @minimum 1 * @maximum 90 */ baseline_window_days?: number - /** - * Hours after a UTC billing date ends before it becomes eligible for evaluation. - * @minimum 0 - * @maximum 72 - */ - evaluation_delay_hours?: number /** Current lifecycle state of this alert. * * * `not_firing` - Not firing @@ -501,12 +473,6 @@ export interface PatchedBillingAlertConfigurationApi { * * `snoozed` - Snoozed * * `broken` - Broken */ readonly state?: BillingAlertConfigurationStateEnumApi - /** - * Minimum hours between repeated firing notifications. - * @minimum 0 - * @maximum 720 - */ - cooldown_hours?: number /** * ISO 8601 timestamp until which evaluation and notifications are snoozed, or null to resume. * @nullable diff --git a/products/billing/frontend/generated/api.zod.ts b/products/billing/frontend/generated/api.zod.ts index c3a391316169..af46fbd5d885 100644 --- a/products/billing/frontend/generated/api.zod.ts +++ b/products/billing/frontend/generated/api.zod.ts @@ -85,15 +85,8 @@ export const billingAlertsCreateBodyNameMax = 160 export const billingAlertsCreateBodyThresholdPercentageRegExp = new RegExp('^-?\\d{0,6}(?:\\.\\d{0,2})?$') export const billingAlertsCreateBodyThresholdValueRegExp = new RegExp('^-?\\d{0,14}(?:\\.\\d{0,6})?$') -export const billingAlertsCreateBodyMinimumValueRegExp = new RegExp('^-?\\d{0,14}(?:\\.\\d{0,6})?$') export const billingAlertsCreateBodyBaselineWindowDaysMax = 90 -export const billingAlertsCreateBodyEvaluationDelayHoursMin = 0 -export const billingAlertsCreateBodyEvaluationDelayHoursMax = 72 - -export const billingAlertsCreateBodyCooldownHoursMin = 0 -export const billingAlertsCreateBodyCooldownHoursMax = 720 - export const billingAlertsCreateBodyDestinationChangesOneDeleteItemMax = 100 export const BillingAlertsCreateBody = /* @__PURE__ */ zod.object({ @@ -124,28 +117,12 @@ export const BillingAlertsCreateBody = /* @__PURE__ */ zod.object({ .stringFormat('decimal', billingAlertsCreateBodyThresholdValueRegExp) .nullish() .describe('Absolute value or absolute increase that triggers absolute threshold alerts.'), - minimum_value: zod - .stringFormat('decimal', billingAlertsCreateBodyMinimumValueRegExp) - .optional() - .describe('Minimum current value before the alert can fire.'), baseline_window_days: zod .number() .min(1) .max(billingAlertsCreateBodyBaselineWindowDaysMax) .optional() .describe('Reserved for future increase-over-baseline rules. Not used by absolute value alerts.'), - evaluation_delay_hours: zod - .number() - .min(billingAlertsCreateBodyEvaluationDelayHoursMin) - .max(billingAlertsCreateBodyEvaluationDelayHoursMax) - .optional() - .describe('Hours after a UTC billing date ends before it becomes eligible for evaluation.'), - cooldown_hours: zod - .number() - .min(billingAlertsCreateBodyCooldownHoursMin) - .max(billingAlertsCreateBodyCooldownHoursMax) - .optional() - .describe('Minimum hours between repeated firing notifications.'), snoozed_until: zod.iso .datetime({ offset: true }) .nullish() @@ -189,15 +166,8 @@ export const billingAlertsUpdateBodyNameMax = 160 export const billingAlertsUpdateBodyThresholdPercentageRegExp = new RegExp('^-?\\d{0,6}(?:\\.\\d{0,2})?$') export const billingAlertsUpdateBodyThresholdValueRegExp = new RegExp('^-?\\d{0,14}(?:\\.\\d{0,6})?$') -export const billingAlertsUpdateBodyMinimumValueRegExp = new RegExp('^-?\\d{0,14}(?:\\.\\d{0,6})?$') export const billingAlertsUpdateBodyBaselineWindowDaysMax = 90 -export const billingAlertsUpdateBodyEvaluationDelayHoursMin = 0 -export const billingAlertsUpdateBodyEvaluationDelayHoursMax = 72 - -export const billingAlertsUpdateBodyCooldownHoursMin = 0 -export const billingAlertsUpdateBodyCooldownHoursMax = 720 - export const billingAlertsUpdateBodyDestinationChangesOneDeleteItemMax = 100 export const BillingAlertsUpdateBody = /* @__PURE__ */ zod.object({ @@ -228,28 +198,12 @@ export const BillingAlertsUpdateBody = /* @__PURE__ */ zod.object({ .stringFormat('decimal', billingAlertsUpdateBodyThresholdValueRegExp) .nullish() .describe('Absolute value or absolute increase that triggers absolute threshold alerts.'), - minimum_value: zod - .stringFormat('decimal', billingAlertsUpdateBodyMinimumValueRegExp) - .optional() - .describe('Minimum current value before the alert can fire.'), baseline_window_days: zod .number() .min(1) .max(billingAlertsUpdateBodyBaselineWindowDaysMax) .optional() .describe('Reserved for future increase-over-baseline rules. Not used by absolute value alerts.'), - evaluation_delay_hours: zod - .number() - .min(billingAlertsUpdateBodyEvaluationDelayHoursMin) - .max(billingAlertsUpdateBodyEvaluationDelayHoursMax) - .optional() - .describe('Hours after a UTC billing date ends before it becomes eligible for evaluation.'), - cooldown_hours: zod - .number() - .min(billingAlertsUpdateBodyCooldownHoursMin) - .max(billingAlertsUpdateBodyCooldownHoursMax) - .optional() - .describe('Minimum hours between repeated firing notifications.'), snoozed_until: zod.iso .datetime({ offset: true }) .nullish() @@ -293,15 +247,8 @@ export const billingAlertsPartialUpdateBodyNameMax = 160 export const billingAlertsPartialUpdateBodyThresholdPercentageRegExp = new RegExp('^-?\\d{0,6}(?:\\.\\d{0,2})?$') export const billingAlertsPartialUpdateBodyThresholdValueRegExp = new RegExp('^-?\\d{0,14}(?:\\.\\d{0,6})?$') -export const billingAlertsPartialUpdateBodyMinimumValueRegExp = new RegExp('^-?\\d{0,14}(?:\\.\\d{0,6})?$') export const billingAlertsPartialUpdateBodyBaselineWindowDaysMax = 90 -export const billingAlertsPartialUpdateBodyEvaluationDelayHoursMin = 0 -export const billingAlertsPartialUpdateBodyEvaluationDelayHoursMax = 72 - -export const billingAlertsPartialUpdateBodyCooldownHoursMin = 0 -export const billingAlertsPartialUpdateBodyCooldownHoursMax = 720 - export const billingAlertsPartialUpdateBodyDestinationChangesOneDeleteItemMax = 100 export const BillingAlertsPartialUpdateBody = /* @__PURE__ */ zod.object({ @@ -336,28 +283,12 @@ export const BillingAlertsPartialUpdateBody = /* @__PURE__ */ zod.object({ .stringFormat('decimal', billingAlertsPartialUpdateBodyThresholdValueRegExp) .nullish() .describe('Absolute value or absolute increase that triggers absolute threshold alerts.'), - minimum_value: zod - .stringFormat('decimal', billingAlertsPartialUpdateBodyMinimumValueRegExp) - .optional() - .describe('Minimum current value before the alert can fire.'), baseline_window_days: zod .number() .min(1) .max(billingAlertsPartialUpdateBodyBaselineWindowDaysMax) .optional() .describe('Reserved for future increase-over-baseline rules. Not used by absolute value alerts.'), - evaluation_delay_hours: zod - .number() - .min(billingAlertsPartialUpdateBodyEvaluationDelayHoursMin) - .max(billingAlertsPartialUpdateBodyEvaluationDelayHoursMax) - .optional() - .describe('Hours after a UTC billing date ends before it becomes eligible for evaluation.'), - cooldown_hours: zod - .number() - .min(billingAlertsPartialUpdateBodyCooldownHoursMin) - .max(billingAlertsPartialUpdateBodyCooldownHoursMax) - .optional() - .describe('Minimum hours between repeated firing notifications.'), snoozed_until: zod.iso .datetime({ offset: true }) .nullish() diff --git a/products/billing_alerts/backend/models.py b/products/billing_alerts/backend/models.py index f8cf7ff37748..9801c8bc55a4 100644 --- a/products/billing_alerts/backend/models.py +++ b/products/billing_alerts/backend/models.py @@ -167,7 +167,7 @@ def validate_threshold_configuration( threshold_type: str, threshold_percentage: Decimal | None, threshold_value: Decimal | None, - minimum_value: Decimal, + minimum_value: Decimal = Decimal("0"), ) -> dict[str, str]: """Threshold policy shared by model validation and the DRF serializer.""" errors: dict[str, str] = {} diff --git a/products/billing_alerts/backend/presentation/serializers.py b/products/billing_alerts/backend/presentation/serializers.py index 94197380268f..55721e2c9e3c 100644 --- a/products/billing_alerts/backend/presentation/serializers.py +++ b/products/billing_alerts/backend/presentation/serializers.py @@ -1,6 +1,5 @@ from __future__ import annotations -from decimal import Decimal from typing import Any, cast from urllib.parse import urlparse @@ -205,30 +204,12 @@ class BillingAlertConfigurationSerializer(serializers.ModelSerializer): allow_null=True, help_text="Absolute value or absolute increase that triggers absolute threshold alerts.", ) - minimum_value = serializers.DecimalField( - max_digits=20, - decimal_places=6, - required=False, - help_text="Minimum current value before the alert can fire.", - ) baseline_window_days = serializers.IntegerField( required=False, min_value=1, max_value=90, help_text="Reserved for future increase-over-baseline rules. Not used by absolute value alerts.", ) - evaluation_delay_hours = serializers.IntegerField( - required=False, - min_value=0, - max_value=72, - help_text="Hours after a UTC billing date ends before it becomes eligible for evaluation.", - ) - cooldown_hours = serializers.IntegerField( - required=False, - min_value=0, - max_value=24 * 30, - help_text="Minimum hours between repeated firing notifications.", - ) snoozed_until = serializers.DateTimeField( required=False, allow_null=True, @@ -260,11 +241,8 @@ class Meta: "threshold_type", "threshold_percentage", "threshold_value", - "minimum_value", "baseline_window_days", - "evaluation_delay_hours", "state", - "cooldown_hours", "snoozed_until", "next_check_at", "last_checked_at", @@ -334,13 +312,11 @@ def validate(self, attrs: dict[str, Any]) -> dict[str, Any]: current.threshold_percentage if current else None, ) threshold_value = attrs.get("threshold_value", current.threshold_value if current else None) - minimum_value = attrs.get("minimum_value", current.minimum_value if current else Decimal("0")) errors = validate_threshold_configuration( threshold_type=threshold_type, threshold_percentage=threshold_percentage, threshold_value=threshold_value, - minimum_value=minimum_value, ) if errors: raise ValidationError(errors) @@ -382,14 +358,11 @@ def update( "threshold_type", "threshold_percentage", "threshold_value", - "minimum_value", "baseline_window_days", - "evaluation_delay_hours", } revision_fields = evaluation_fields | { "enabled", "snoozed_until", - "cooldown_hours", } with transaction.atomic(): locked = BillingAlertConfiguration.objects.select_for_update().get( diff --git a/products/billing_alerts/backend/test/test_api.py b/products/billing_alerts/backend/test/test_api.py index 9b785a981b6b..2811eef6057b 100644 --- a/products/billing_alerts/backend/test/test_api.py +++ b/products/billing_alerts/backend/test/test_api.py @@ -45,9 +45,6 @@ def _payload(self, **overrides) -> dict: "metric": "spend", "threshold_type": "absolute_value", "threshold_value": "100.00", - "minimum_value": "0", - "evaluation_delay_hours": 6, - "cooldown_hours": 24, } payload.update(overrides) return payload @@ -234,7 +231,7 @@ def test_copy_only_edit_does_not_bump_configuration_revision(self) -> None: def test_evaluation_rule_edits_reset_firing_state_and_failures(self) -> None: updates = [ ("baseline_window_days", 14), - ("evaluation_delay_hours", 12), + ("threshold_value", Decimal("150.000000")), ] for field, value in updates: @@ -262,13 +259,13 @@ def test_threshold_edit_preserves_snoozed_state(self) -> None: response = self.client.patch( f"{self.url}{alert.id}/", - {"minimum_value": "10.00"}, + {"threshold_value": "150.00"}, format="json", ) assert response.status_code == status.HTTP_200_OK, response.json() alert.refresh_from_db() - assert alert.minimum_value == Decimal("10.00") + assert alert.threshold_value == Decimal("150.00") assert alert.state == BillingAlertConfiguration.State.SNOOZED assert alert.snoozed_until == snoozed_until assert alert.consecutive_failures == 3 @@ -411,11 +408,17 @@ def test_teams_destinations_require_a_microsoft_workflow_webhook(self) -> None: assert "webhook_url" in invalid.errors assert valid.is_valid(), valid.errors - def test_rejects_negative_minimum(self) -> None: - negative_minimum = self.client.post(self.url, self._payload(minimum_value="-1"), format="json") + def test_fields_the_platform_ignores_are_not_writable(self) -> None: + response = self.client.post( + self.url, + self._payload(minimum_value="200", evaluation_delay_hours=24, cooldown_hours=48), + format="json", + ) - assert negative_minimum.status_code == status.HTTP_400_BAD_REQUEST - assert negative_minimum.json()["attr"] == "minimum_value" + assert response.status_code == status.HTTP_201_CREATED, response.json() + assert not {"minimum_value", "evaluation_delay_hours", "cooldown_hours"} & response.json().keys() + alert = BillingAlertConfiguration.objects.get(id=response.json()["id"]) + assert (alert.minimum_value, alert.evaluation_delay_hours, alert.cooldown_hours) == (Decimal("0"), 6, 24) def test_check_now_uses_shared_organization_object_permissions(self) -> None: alert = BillingAlertConfiguration.objects.create( diff --git a/products/billing_alerts/frontend/BillingAlertEditor.tsx b/products/billing_alerts/frontend/BillingAlertEditor.tsx index 5d8a73b01758..db460d4eebe0 100644 --- a/products/billing_alerts/frontend/BillingAlertEditor.tsx +++ b/products/billing_alerts/frontend/BillingAlertEditor.tsx @@ -8,7 +8,6 @@ import { NextScheduledRun } from 'lib/components/ScheduledRunStatus' import { dayjs } from 'lib/dayjs' import { LemonField } from 'lib/lemon-ui/LemonField' -import { AlertAdvancedOptions } from 'products/alerts/frontend/components/AlertAdvancedOptions' import { AlertDefinitionRow } from 'products/alerts/frontend/components/AlertDefinition' import { AlertEditor, @@ -16,7 +15,7 @@ import { AlertEditorSection, } from 'products/alerts/frontend/components/AlertEditor' -import { ADVANCED_OPTION_DEFAULTS, billingAlertFormLogic, BillingAlertFormLogicProps } from './billingAlertFormLogic' +import { billingAlertFormLogic, BillingAlertFormLogicProps } from './billingAlertFormLogic' import { BillingAlertHistory } from './BillingAlertHistory' import { billingAlertNotificationLogic } from './billingAlertNotificationLogic' import { BillingAlertNotifications } from './BillingAlertNotifications' @@ -40,10 +39,6 @@ function BillingAlertEditorContent(props: BillingAlertFormLogicProps): JSX.Eleme const { pendingDestinations } = useValues(billingAlertNotificationLogic) const { closeEditor, checkNow } = useActions(billingAlertsLogic) const { checkingAlertId } = useValues(billingAlertsLogic) - const enabledAdvancedOptionsCount = - Number(alertForm.minimumValue > ADVANCED_OPTION_DEFAULTS.minimumValue) + - Number(alertForm.evaluationDelayHours !== ADVANCED_OPTION_DEFAULTS.evaluationDelayHours) + - Number(alertForm.cooldownHours !== ADVANCED_OPTION_DEFAULTS.cooldownHours) const isPaused = props.alert !== null && !props.alert.enabled return ( @@ -145,43 +140,6 @@ function BillingAlertEditorContent(props: BillingAlertFormLogicProps): JSX.Eleme - -
- - setAlertFormValue('minimumValue', value ?? 0)} - prefix={$} - /> - - - setAlertFormValue('evaluationDelayHours', value ?? 0)} - suffix={hours} - /> - - - setAlertFormValue('cooldownHours', value ?? 0)} - suffix={hours} - /> - -
-
- {props.alert ? : null} diff --git a/products/billing_alerts/frontend/BillingAlertHistory.test.ts b/products/billing_alerts/frontend/BillingAlertHistory.test.ts index 06e8fd1c395d..96e027967b79 100644 --- a/products/billing_alerts/frontend/BillingAlertHistory.test.ts +++ b/products/billing_alerts/frontend/BillingAlertHistory.test.ts @@ -1,15 +1,11 @@ import { eventValue, historyPoint, wouldFire } from './BillingAlertHistory' import type { BillingAlertConfigurationApi, BillingAlertEventApi } from './generated/api.schemas' -function alert( - thresholdType: BillingAlertConfigurationApi['threshold_type'], - minimumValue = '0' -): BillingAlertConfigurationApi { +function alert(thresholdType: BillingAlertConfigurationApi['threshold_type']): BillingAlertConfigurationApi { return { threshold_type: thresholdType, threshold_percentage: '50', threshold_value: '100', - minimum_value: minimumValue, configuration_revision: 2, } as unknown as BillingAlertConfigurationApi } @@ -41,16 +37,8 @@ describe('billing alert history classification', () => { ).toBe(false) }) - it.each([ - ['relative_increase', { current_value: '99', relative_delta_percentage: '500' }], - ['absolute_value', { current_value: '99' }], - ['absolute_increase', { current_value: '99', absolute_delta: '500' }], - ] as const)('applies the minimum current value to %s alerts', (thresholdType, values) => { - expect(wouldFire(alert(thresholdType, '100'), event(values))).toBe(false) - }) - - it('classifies a complete value that clears both gates', () => { - expect(wouldFire(alert('relative_increase', '100'), event({ current_value: '150' }))).toBe(true) + it('classifies a complete value that clears the threshold', () => { + expect(wouldFire(alert('relative_increase'), event({ current_value: '150' }))).toBe(true) }) it.each(['check', 'errored'] as const)('does not mark %s events as firing edges', (kind) => { diff --git a/products/billing_alerts/frontend/BillingAlertHistory.tsx b/products/billing_alerts/frontend/BillingAlertHistory.tsx index ffe29950a218..6d3df59de8e4 100644 --- a/products/billing_alerts/frontend/BillingAlertHistory.tsx +++ b/products/billing_alerts/frontend/BillingAlertHistory.tsx @@ -21,12 +21,11 @@ export function eventValue(alert: BillingAlertConfigurationApi, event: BillingAl } export function wouldFire(alert: BillingAlertConfigurationApi, event: BillingAlertEventApi): boolean { - if (event.current_value === null || event.current_value === undefined) { - return false - } - const current = Number(event.current_value) - const minimum = Number(alert.minimum_value ?? 0) - if (!Number.isFinite(current) || current < minimum) { + if ( + event.current_value === null || + event.current_value === undefined || + !Number.isFinite(Number(event.current_value)) + ) { return false } const view = thresholdView(alert) diff --git a/products/billing_alerts/frontend/billingAlertFormLogic.test.ts b/products/billing_alerts/frontend/billingAlertFormLogic.test.ts index d34a1fb9b67b..53fd241ea6f3 100644 --- a/products/billing_alerts/frontend/billingAlertFormLogic.test.ts +++ b/products/billing_alerts/frontend/billingAlertFormLogic.test.ts @@ -23,9 +23,6 @@ describe('billing alert form server errors', () => { enabled: true, metric: 'projected_spend', thresholdValue: 100, - minimumValue: 0, - evaluationDelayHours: 6, - cooldownHours: 24, }, [ { diff --git a/products/billing_alerts/frontend/billingAlertFormLogic.ts b/products/billing_alerts/frontend/billingAlertFormLogic.ts index 1806ad913e68..d13085620414 100644 --- a/products/billing_alerts/frontend/billingAlertFormLogic.ts +++ b/products/billing_alerts/frontend/billingAlertFormLogic.ts @@ -27,9 +27,6 @@ export interface BillingAlertFormValues { metric: BillingAlertMetricEnumApi /** null while the input is cleared, so an empty threshold fails validation instead of saving as 0. */ thresholdValue: number | null - minimumValue: number - evaluationDelayHours: number - cooldownHours: number } export interface BillingAlertFormLogicProps { @@ -42,9 +39,6 @@ const API_FIELD_TO_FORM_FIELD: Record = { enabled: 'enabled', metric: 'metric', threshold_value: 'thresholdValue', - minimum_value: 'minimumValue', - evaluation_delay_hours: 'evaluationDelayHours', - cooldown_hours: 'cooldownHours', } function firstErrorMessage(value: unknown): string | undefined { @@ -90,13 +84,6 @@ function numberValue(value: string | null | undefined, fallback: number): number return Number.isFinite(parsed) ? parsed : fallback } -// Shared with the editor's advanced-options badge, which counts fields diverging from these. -export const ADVANCED_OPTION_DEFAULTS = { - minimumValue: 0, - evaluationDelayHours: 6, - cooldownHours: 24, -} as const - function formDefaults(alert: BillingAlertConfigurationApi | null): BillingAlertFormValues { return { name: alert?.name ?? '', @@ -104,9 +91,6 @@ function formDefaults(alert: BillingAlertConfigurationApi | null): BillingAlertF enabled: alert?.enabled ?? true, metric: alert?.metric ?? 'spend', thresholdValue: numberValue(alert?.threshold_value, 100), - minimumValue: numberValue(alert?.minimum_value, ADVANCED_OPTION_DEFAULTS.minimumValue), - evaluationDelayHours: alert?.evaluation_delay_hours ?? ADVANCED_OPTION_DEFAULTS.evaluationDelayHours, - cooldownHours: alert?.cooldown_hours ?? ADVANCED_OPTION_DEFAULTS.cooldownHours, } } @@ -122,9 +106,6 @@ export function billingAlertWritePayload( threshold_type: 'absolute_value', // Validation rejects a null threshold before submit, so fall back to 0 only for typing. threshold_value: String(form.thresholdValue ?? 0), - minimum_value: String(form.minimumValue), - evaluation_delay_hours: form.evaluationDelayHours, - cooldown_hours: form.cooldownHours, ...(pending.length > 0 ? { destination_changes: { create: pending.map(({ payload }) => payload) } } : {}), } } @@ -234,15 +215,6 @@ export const billingAlertFormLogic = kea([ : form.thresholdValue < 0 ? 'Enter a value of 0 or more.' : undefined, - minimumValue: form.minimumValue < 0 ? 'Enter a value of 0 or more.' : undefined, - evaluationDelayHours: - form.evaluationDelayHours < 0 || form.evaluationDelayHours > 72 - ? 'Use a delay between 0 and 72 hours.' - : undefined, - cooldownHours: - form.cooldownHours < 0 || form.cooldownHours > 720 - ? 'Use a cooldown between 0 and 720 hours.' - : undefined, }), submit: async (form) => { const organizationId = values.currentOrganization?.id diff --git a/services/mcp/src/api/generated.ts b/services/mcp/src/api/generated.ts index a437d8995114..cdb79d74c8d0 100644 --- a/services/mcp/src/api/generated.ts +++ b/services/mcp/src/api/generated.ts @@ -18069,23 +18069,12 @@ export namespace Schemas { * @pattern ^-?\d{0,14}(?:\.\d{0,6})?$ */ threshold_value?: string | null; - /** - * Minimum current value before the alert can fire. - * @pattern ^-?\d{0,14}(?:\.\d{0,6})?$ - */ - minimum_value?: string; /** * Reserved for future increase-over-baseline rules. Not used by absolute value alerts. * @minimum 1 * @maximum 90 */ baseline_window_days?: number; - /** - * Hours after a UTC billing date ends before it becomes eligible for evaluation. - * @minimum 0 - * @maximum 72 - */ - evaluation_delay_hours?: number; /** Current lifecycle state of this alert. * * * `not_firing` - Not firing @@ -18094,12 +18083,6 @@ export namespace Schemas { * * `snoozed` - Snoozed * * `broken` - Broken */ readonly state: BillingAlertConfigurationStateEnum; - /** - * Minimum hours between repeated firing notifications. - * @minimum 0 - * @maximum 720 - */ - cooldown_hours?: number; /** * ISO 8601 timestamp until which evaluation and notifications are snoozed, or null to resume. * @nullable @@ -79267,23 +79250,12 @@ export namespace Schemas { * @pattern ^-?\d{0,14}(?:\.\d{0,6})?$ */ threshold_value?: string | null; - /** - * Minimum current value before the alert can fire. - * @pattern ^-?\d{0,14}(?:\.\d{0,6})?$ - */ - minimum_value?: string; /** * Reserved for future increase-over-baseline rules. Not used by absolute value alerts. * @minimum 1 * @maximum 90 */ baseline_window_days?: number; - /** - * Hours after a UTC billing date ends before it becomes eligible for evaluation. - * @minimum 0 - * @maximum 72 - */ - evaluation_delay_hours?: number; /** Current lifecycle state of this alert. * * * `not_firing` - Not firing @@ -79292,12 +79264,6 @@ export namespace Schemas { * * `snoozed` - Snoozed * * `broken` - Broken */ readonly state?: BillingAlertConfigurationStateEnum; - /** - * Minimum hours between repeated firing notifications. - * @minimum 0 - * @maximum 720 - */ - cooldown_hours?: number; /** * ISO 8601 timestamp until which evaluation and notifications are snoozed, or null to resume. * @nullable From d1b2abc3e32e4c9f79af25091e3ed66f2bb80e5e Mon Sep 17 00:00:00 2001 From: Jimmy Zhu Date: Fri, 16 Oct 2026 11:32:21 -0700 Subject: [PATCH 2/3] chore(billing): drop removed alert settings from the readme and MCP description Co-Authored-By: Claude Opus 5.5 --- products/billing_alerts/README.md | 2 +- products/billing_alerts/mcp/tools.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/products/billing_alerts/README.md b/products/billing_alerts/README.md index 64d7620082d4..fbb2286d797f 100644 --- a/products/billing_alerts/README.md +++ b/products/billing_alerts/README.md @@ -12,4 +12,4 @@ The MCP server exposes atomic create and partial-update tools for billing alert Billing alerts evaluate one UTC billing date every 24 hours. Alert configuration and destination changes are committed atomically through the generated billing API, while the shared alerting backend owns destination persistence and delivery. -The Billing tab uses the shared alert editor, destination editor, advanced options, next-evaluation status, and evaluation-history chart from `products/alerts`. Billing supplies its own form, threshold labels, value formatting, API calls, and notification destination payloads. +The Billing tab uses the shared alert editor, destination editor, next-evaluation status, and evaluation-history chart from `products/alerts`. Billing supplies its own form, threshold labels, value formatting, API calls, and notification destination payloads. diff --git a/products/billing_alerts/mcp/tools.yaml b/products/billing_alerts/mcp/tools.yaml index 9283dc40f878..7d1b946291b6 100644 --- a/products/billing_alerts/mcp/tools.yaml +++ b/products/billing_alerts/mcp/tools.yaml @@ -39,7 +39,7 @@ tools: title: Update billing alert description: > Partially update a billing alert by ID in the active organization. The caller must be an organization Admin - or Owner. Change only the supplied trigger fields, alert copy, enabled state, snooze, or cooldown. The + or Owner. Change only the supplied trigger fields, alert copy, enabled state, or snooze. The metric picks the billing-period total to watch (spend or projected_spend); the first version supports only the absolute_value threshold rule with threshold_value, in USD. Optional destination_changes applies destination creation or complete-group deletion in the same backend transaction as the configuration update. From 293383f0198b6c303916f2e44ca18ae2bc44b375 Mon Sep 17 00:00:00 2001 From: "tests-posthog[bot]" <250237707+tests-posthog[bot]@users.noreply.github.com> Date: Fri, 16 Oct 2026 18:37:47 +0000 Subject: [PATCH 3/3] chore: update OpenAPI generated types --- products/billing_alerts/mcp/tools.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/products/billing_alerts/mcp/tools.yaml b/products/billing_alerts/mcp/tools.yaml index 7d1b946291b6..2d03394ed62a 100644 --- a/products/billing_alerts/mcp/tools.yaml +++ b/products/billing_alerts/mcp/tools.yaml @@ -39,10 +39,10 @@ tools: title: Update billing alert description: > Partially update a billing alert by ID in the active organization. The caller must be an organization Admin - or Owner. Change only the supplied trigger fields, alert copy, enabled state, or snooze. The - metric picks the billing-period total to watch (spend or projected_spend); the first version supports only - the absolute_value threshold rule with threshold_value, in USD. Optional destination_changes applies - destination creation or complete-group deletion in the same backend transaction as the configuration update. - Repeating a request that creates a destination of the same type is rejected, because an alert allows one - destination group per type. + or Owner. Change only the supplied trigger fields, alert copy, enabled state, or snooze. The metric picks + the billing-period total to watch (spend or projected_spend); the first version supports only the + absolute_value threshold rule with threshold_value, in USD. Optional destination_changes applies destination + creation or complete-group deletion in the same backend transaction as the configuration update. Repeating a + request that creates a destination of the same type is rejected, because an alert allows one destination + group per type. feature_flag: billing-alerts