diff --git a/packages/manager/.changeset/pr-13301-upcoming-features-1768997478204.md b/packages/manager/.changeset/pr-13301-upcoming-features-1768997478204.md new file mode 100644 index 00000000000..b2ac5efb976 --- /dev/null +++ b/packages/manager/.changeset/pr-13301-upcoming-features-1768997478204.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Upcoming Features +--- + +CloudPulse-Alerts: Exclude account/region alerts in api payload while updating alerts for a linode and fix state reset issue on save ([#13301](https://github.com/linode/manager/pull/13301)) diff --git a/packages/manager/src/features/CloudPulse/Alerts/ContextualView/AlertInformationActionTable.test.tsx b/packages/manager/src/features/CloudPulse/Alerts/ContextualView/AlertInformationActionTable.test.tsx index e1191701576..ca42245978a 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/ContextualView/AlertInformationActionTable.test.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/ContextualView/AlertInformationActionTable.test.tsx @@ -13,6 +13,18 @@ import type { TableColumnHeader, } from './AlertInformationActionTable'; +const mockUpdateAlerts = vi.fn(); + +vi.mock('src/queries/cloudpulse/useAlertsMutation', async () => { + const actual = await vi.importActual( + 'src/queries/cloudpulse/useAlertsMutation' + ); + return { + ...actual, + useAlertsMutation: () => mockUpdateAlerts, + }; +}); + const serviceType = 'linode'; const entityId = '123'; const entityName = 'test-instance'; @@ -22,6 +34,27 @@ const alerts = [ service_type: serviceType, status: 'enabled', }), + alertFactory.build({ + id: 9, + entity_ids: [], + service_type: serviceType, + type: 'user', + scope: 'entity', + }), + alertFactory.build({ + id: 10, + entity_ids: [], + service_type: serviceType, + type: 'user', + scope: 'account', + }), + alertFactory.build({ + id: 11, + entity_ids: [], + service_type: serviceType, + type: 'user', + scope: 'region', + }), ]; const columns: TableColumnHeader[] = [ { columnName: 'Alert Name', label: 'label' }, @@ -39,6 +72,11 @@ const props: AlertInformationActionTableProps = { }; describe('Alert Listing Reusable Table for contextual view', () => { + beforeEach(() => { + mockUpdateAlerts.mockClear(); + mockUpdateAlerts.mockResolvedValue({}); + }); + it('Should render alert table', async () => { renderWithTheme(); @@ -112,4 +150,24 @@ describe('Alert Listing Reusable Table for contextual view', () => { const saveButton = screen.getByTestId('save-alerts'); expect(saveButton).toBeDisabled(); }); + + it('Should send correct payload to the API when save button is clicked in edit mode', async () => { + renderWithTheme(); + + // Toggle entity-level user alert with ID 2 to enable it + const userAlertRow = await screen.findByTestId('9'); + await userEvent.click(await within(userAlertRow).findByRole('checkbox')); + + const saveButton = screen.getByTestId('save-alerts'); + expect(saveButton).not.toBeDisabled(); + await userEvent.click(saveButton); + + // Verify that account and region level alerts are not included in the payload + expect(mockUpdateAlerts).toHaveBeenCalledWith({ + alerts: { + system_alerts: [1, 2, 3, 4, 5, 6, 7], + user_alerts: [9], + }, + }); + }); }); diff --git a/packages/manager/src/features/CloudPulse/Alerts/ContextualView/AlertInformationActionTable.tsx b/packages/manager/src/features/CloudPulse/Alerts/ContextualView/AlertInformationActionTable.tsx index a8968c2b6f2..bb8c301df1e 100644 --- a/packages/manager/src/features/CloudPulse/Alerts/ContextualView/AlertInformationActionTable.tsx +++ b/packages/manager/src/features/CloudPulse/Alerts/ContextualView/AlertInformationActionTable.tsx @@ -149,23 +149,16 @@ export const AlertInformationActionTable = ( const payloadAlertType = (alert: Alert) => alert.type === 'system' ? 'system_alerts' : 'user_alerts'; - const { - enabledAlerts, - setEnabledAlerts, - hasUnsavedChanges, - initialState, - resetToInitialState, - } = useContextualAlertsState(alerts, entityId); + const { enabledAlerts, setEnabledAlerts, hasUnsavedChanges, initialState } = + useContextualAlertsState(alerts, entityId); + + const isAccountOrRegionAlert = (alert: Alert) => + alert.scope === 'region' || alert.scope === 'account'; // Mutation to update alerts as per service type const updateAlerts = useAlertsMutation(serviceType, entityId ?? ''); React.useEffect(() => { - // To send initial state of alerts through toggle handler function (For Create Flow) - if (!isEditMode && onToggleAlert) { - onToggleAlert(enabledAlerts); - } - return () => { // Cleanup on unmount (For Edit flow) if (isEditMode && onToggleAlert) { @@ -195,8 +188,7 @@ export const AlertInformationActionTable = ( enqueueSnackbar('Your settings for alerts have been saved.', { variant: 'success', }); - // Reset the state to sync with the updated alerts from API - resetToInitialState(); + onToggleAlert?.({}, false); invalidateAclpAlerts(queryClient, serviceType, entityId, payload); }) .catch(() => { @@ -209,7 +201,7 @@ export const AlertInformationActionTable = ( setIsDialogOpen(false); }); }, - [updateAlerts, enqueueSnackbar, resetToInitialState] + [updateAlerts, enqueueSnackbar, onToggleAlert] ); const handleToggleAlert = React.useCallback( @@ -325,9 +317,12 @@ export const AlertInformationActionTable = ( if (!(isEditMode || isCreateMode)) { return null; } - const status = enabledAlerts[ - payloadAlertType(alert) - ]?.includes(alert.id); + // If alert is account or region level, enable it by default and if it is entity type alert, check if it is enabled for that entity + const status = + isAccountOrRegionAlert(alert) || + enabledAlerts[payloadAlertType(alert)]?.includes( + alert.id + ); return ( { }); }); - it('should include alerts that match entityId or account/region level alerts in initial states', () => { + it('should include alerts that match entityId in initial states', () => { const entityId = '123'; const alerts = [ alertFactory.build({ @@ -275,13 +275,6 @@ describe('useContextualAlertsState', () => { entity_ids: [entityId], scope: 'entity', }), - alertFactory.build({ - id: 3, - label: 'alert3', - type: 'system', - entity_ids: ['456'], - scope: 'region', - }), ]; const { result } = renderHook(() => @@ -289,7 +282,6 @@ describe('useContextualAlertsState', () => { ); expect(result.current.initialState.system_alerts).toContain(1); - expect(result.current.initialState.system_alerts).toContain(3); expect(result.current.initialState.user_alerts).toContain(2); }); diff --git a/packages/manager/src/features/CloudPulse/Utils/utils.ts b/packages/manager/src/features/CloudPulse/Utils/utils.ts index b18b3a8bf0e..f5dcb3dde17 100644 --- a/packages/manager/src/features/CloudPulse/Utils/utils.ts +++ b/packages/manager/src/features/CloudPulse/Utils/utils.ts @@ -127,13 +127,10 @@ export const useContextualAlertsState = ( }; alerts.forEach((alert) => { - const isAccountOrRegion = - alert.scope === 'region' || alert.scope === 'account'; - - // include alerts which has either account or region level scope or entityId is present in the alert's entity_ids + // include alerts for which entityId is present in the alert's entity_ids const shouldInclude = entityId - ? isAccountOrRegion || alert.entity_ids.includes(entityId) - : isAccountOrRegion; + ? alert.entity_ids.includes(entityId) + : false; if (shouldInclude) { const payloadAlertType =