Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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) => (
<button
onClick={() => {
onClick();
toggle();
}}
>
{label}
</button>
);
return (
<div data-testid="alert-sidebar">
<div data-testid="alert-sidebar-content">
<h3>{alert?.name || 'Alert Details'}</h3>
<p>Severity: {alert?.severity}</p>
</div>
{action('Run Workflow', () => setRunWorkflowModalAlert?.(alert))}
{action('Dismiss', () => setDismissModalAlert?.([alert]))}
{action('Change Status', () => setChangeStatusAlert?.(alert))}
<button onClick={toggle} data-testid="close-sidebar">
Close
</button>
Expand All @@ -189,6 +210,36 @@ jest.mock('@/features/alerts/alert-detail-sidebar', () => ({
},
}));

jest.mock('@/features/workflows/manual-run-workflow', () => ({
ManualRunWorkflowModal: ({ alert, onClose }: any) =>
alert ? (
<div data-testid="run-workflow-modal">
{alert.name}
<button onClick={onClose}>Close modal</button>
</div>
) : null,
}));

jest.mock('@/features/alerts/dismiss-alert', () => ({
AlertDismissModal: ({ alert, handleClose }: any) =>
alert ? (
<div data-testid="dismiss-modal">
{alert[0].name}
<button onClick={handleClose}>Close modal</button>
</div>
) : null,
}));

jest.mock('@/features/alerts/alert-change-status', () => ({
AlertChangeStatusModal: ({ alert, handleClose }: any) =>
alert ? (
<div data-testid="change-status-modal">
{alert.name}
<button onClick={handleClose}>Close modal</button>
</div>
) : null,
}));

// Mock ViewAlertModal
jest.mock("@/features/alerts/view-raw-alert", () => ({
ViewAlertModal: ({ alert, handleClose }: any) =>
Expand Down Expand Up @@ -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(<IncidentAlerts incident={mockIncident} />);

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(<IncidentAlerts incident={mockIncident} />);

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 },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => [
Expand Down
41 changes: 37 additions & 4 deletions keep-ui/app/(keep)/incidents/[id]/alerts/incident-alerts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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<AlertDto | null>(null);
const [dismissModalAlert, setDismissModalAlert] = useState<
AlertDto[] | null
>(null);
const [changeStatusAlert, setChangeStatusAlert] = useState<AlertDto | null>(
null
);

const extraColumns = [
columnHelper.accessor("is_created_by_ai", {
id: "is_created_by_ai",
Expand Down Expand Up @@ -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}
/>

<ManualRunWorkflowModal
alert={runWorkflowModalAlert}
onClose={() => setRunWorkflowModalAlert(null)}
/>
{/* The modals revalidate only /alerts keys, so refetch the incident alerts on close */}
<AlertDismissModal
alert={dismissModalAlert}
preset="incident-alerts"
handleClose={() => {
setDismissModalAlert(null);
mutateAlerts();
}}
/>
<AlertChangeStatusModal
alert={changeStatusAlert}
presetName="incident-alerts"
handleClose={() => {
setChangeStatusAlert(null);
mutateAlerts();
}}
/>
</>
);
}
Loading