diff --git a/keep-ui/app/(keep)/incidents/[id]/alerts/__tests__/incident-alerts-sidebar.test.tsx b/keep-ui/app/(keep)/incidents/[id]/alerts/__tests__/incident-alerts-sidebar.test.tsx
index addcb44a88..b93f75fac7 100644
--- a/keep-ui/app/(keep)/incidents/[id]/alerts/__tests__/incident-alerts-sidebar.test.tsx
+++ b/keep-ui/app/(keep)/incidents/[id]/alerts/__tests__/incident-alerts-sidebar.test.tsx
@@ -169,18 +169,39 @@ let alertSidebarState = {
// Mock the AlertSidebar to track its state
jest.mock('@/features/alerts/alert-detail-sidebar', () => ({
- AlertSidebar: ({ isOpen, toggle, alert }: any) => {
+ AlertSidebar: ({
+ isOpen,
+ toggle,
+ alert,
+ setRunWorkflowModalAlert,
+ setDismissModalAlert,
+ setChangeStatusAlert,
+ }: any) => {
// Update our tracked state
alertSidebarState.isOpen = isOpen;
alertSidebarState.alert = alert;
if (!isOpen) return null;
+ // Like AlertMenu in the sidebar: call the setter, then close the sidebar
+ const action = (label: string, onClick: () => void) => (
+
+ );
return (
{alert?.name || 'Alert Details'}
Severity: {alert?.severity}
+ {action('Run Workflow', () => setRunWorkflowModalAlert?.(alert))}
+ {action('Dismiss', () => setDismissModalAlert?.([alert]))}
+ {action('Change Status', () => setChangeStatusAlert?.(alert))}
@@ -189,6 +210,36 @@ jest.mock('@/features/alerts/alert-detail-sidebar', () => ({
},
}));
+jest.mock('@/features/workflows/manual-run-workflow', () => ({
+ ManualRunWorkflowModal: ({ alert, onClose }: any) =>
+ alert ? (
+
+ {alert.name}
+
+
+ ) : null,
+}));
+
+jest.mock('@/features/alerts/dismiss-alert', () => ({
+ AlertDismissModal: ({ alert, handleClose }: any) =>
+ alert ? (
+
+ {alert[0].name}
+
+
+ ) : null,
+}));
+
+jest.mock('@/features/alerts/alert-change-status', () => ({
+ AlertChangeStatusModal: ({ alert, handleClose }: any) =>
+ alert ? (
+
+ {alert.name}
+
+
+ ) : null,
+}));
+
// Mock ViewAlertModal
jest.mock("@/features/alerts/view-raw-alert", () => ({
ViewAlertModal: ({ alert, handleClose }: any) =>
@@ -420,6 +471,49 @@ describe('IncidentAlerts - AlertSidebar Integration', () => {
expect(alertSidebarState.alert?.name).toBe('Test Alert 2');
});
+ it.each([
+ ['Run Workflow', 'run-workflow-modal'],
+ ['Dismiss', 'dismiss-modal'],
+ ['Change Status', 'change-status-modal'],
+ ])('should open the modal for the "%s" sidebar action', async (label, modalTestId) => {
+ render(
);
+
+ fireEvent.click(screen.getByTestId('alert-row-alert-1'));
+ fireEvent.click(await screen.findByRole('button', { name: label }));
+
+ await waitFor(() => {
+ expect(screen.getByTestId(modalTestId)).toHaveTextContent('Test Alert 1');
+ expect(screen.queryByTestId('alert-sidebar')).not.toBeInTheDocument();
+ });
+
+ fireEvent.click(screen.getByRole('button', { name: 'Close modal' }));
+ await waitFor(() => {
+ expect(screen.queryByTestId(modalTestId)).not.toBeInTheDocument();
+ });
+ });
+
+ it.each([
+ ['Dismiss', 'dismiss-modal'],
+ ['Change Status', 'change-status-modal'],
+ ])('should refetch incident alerts when the "%s" modal closes', async (label, modalTestId) => {
+ const mutate = jest.fn();
+ useIncidentAlerts.mockReturnValue({
+ data: mockIncidentAlerts,
+ isLoading: false,
+ error: null,
+ mutate,
+ });
+
+ render(
);
+
+ fireEvent.click(screen.getByTestId('alert-row-alert-1'));
+ fireEvent.click(await screen.findByRole('button', { name: label }));
+ await screen.findByTestId(modalTestId);
+ fireEvent.click(screen.getByRole('button', { name: 'Close modal' }));
+
+ expect(mutate).toHaveBeenCalled();
+ });
+
it('should show empty state when no alerts', () => {
useIncidentAlerts.mockReturnValue({
data: { items: [], count: 0, limit: 20, offset: 0 },
diff --git a/keep-ui/app/(keep)/incidents/[id]/alerts/__tests__/incident-alerts.test.tsx b/keep-ui/app/(keep)/incidents/[id]/alerts/__tests__/incident-alerts.test.tsx
index 754c1e2755..799dc054e3 100644
--- a/keep-ui/app/(keep)/incidents/[id]/alerts/__tests__/incident-alerts.test.tsx
+++ b/keep-ui/app/(keep)/incidents/[id]/alerts/__tests__/incident-alerts.test.tsx
@@ -89,6 +89,10 @@ jest.mock('@/features/alerts/alert-detail-sidebar', () => ({
},
}));
+jest.mock('@/features/workflows/manual-run-workflow', () => ({
+ ManualRunWorkflowModal: () => null,
+}));
+
// Mock alert table utilities
jest.mock('@/widgets/alerts-table/lib/alert-table-utils', () => ({
useAlertTableCols: jest.fn(() => [
diff --git a/keep-ui/app/(keep)/incidents/[id]/alerts/incident-alerts.tsx b/keep-ui/app/(keep)/incidents/[id]/alerts/incident-alerts.tsx
index 3efeed89f4..b51d4a9740 100644
--- a/keep-ui/app/(keep)/incidents/[id]/alerts/incident-alerts.tsx
+++ b/keep-ui/app/(keep)/incidents/[id]/alerts/incident-alerts.tsx
@@ -34,6 +34,9 @@ import { IncidentAlertsTableBodySkeleton } from "./incident-alert-table-body-ske
import { IncidentAlertsActions } from "./incident-alert-actions";
import { AlertSidebar } from "@/features/alerts/alert-detail-sidebar";
import { ViewAlertModal } from "@/features/alerts/view-raw-alert";
+import { AlertChangeStatusModal } from "@/features/alerts/alert-change-status";
+import { AlertDismissModal } from "@/features/alerts/dismiss-alert";
+import { ManualRunWorkflowModal } from "@/features/workflows/manual-run-workflow";
import { IncidentAlertActionTray } from "./incident-alert-action-tray";
import { BellAlertIcon } from "@heroicons/react/24/outline";
import { AlertsTableBody } from "@/widgets/alerts-table/ui/alerts-table-body";
@@ -109,6 +112,15 @@ export default function IncidentAlerts({ incident }: Props) {
// Add state for incident selector modal (needed by AlertSidebar)
const [isIncidentSelectorOpen, setIsIncidentSelectorOpen] = useState(false);
+ const [runWorkflowModalAlert, setRunWorkflowModalAlert] =
+ useState
(null);
+ const [dismissModalAlert, setDismissModalAlert] = useState<
+ AlertDto[] | null
+ >(null);
+ const [changeStatusAlert, setChangeStatusAlert] = useState(
+ null
+ );
+
const extraColumns = [
columnHelper.accessor("is_created_by_ai", {
id: "is_created_by_ai",
@@ -363,12 +375,33 @@ export default function IncidentAlerts({ incident }: Props) {
isOpen={isSidebarOpen}
toggle={handleSidebarClose}
alert={selectedAlert}
- // These optional props are passed to maintain feature parity with the main alerts table
- setRunWorkflowModalAlert={undefined}
- setDismissModalAlert={undefined}
- setChangeStatusAlert={undefined}
+ setRunWorkflowModalAlert={setRunWorkflowModalAlert}
+ setDismissModalAlert={setDismissModalAlert}
+ setChangeStatusAlert={setChangeStatusAlert}
setIsIncidentSelectorOpen={setIsIncidentSelectorOpen}
/>
+
+ setRunWorkflowModalAlert(null)}
+ />
+ {/* The modals revalidate only /alerts keys, so refetch the incident alerts on close */}
+ {
+ setDismissModalAlert(null);
+ mutateAlerts();
+ }}
+ />
+ {
+ setChangeStatusAlert(null);
+ mutateAlerts();
+ }}
+ />
>
);
}