feat: add notification suppression rules (#1525)

* feat: add notification suppression rules

* fix: restore label routing and routing test mocks for suppression

* fix: allow bell mute shortcuts for history-only notification categories

Suppression rule validation used the routable category whitelist, which rejected history-only categories such as update_started that appear in the bell during stack updates.

* feat: expand Mute Rules UX with compose-first entry points and activity badges

* fix: add missing NodeContext mocks for notification suppression tests
This commit is contained in:
Anso
2026-07-02 15:26:48 -04:00
committed by GitHub
parent bc111d28f3
commit b65daf6845
52 changed files with 2794 additions and 51 deletions
@@ -27,6 +27,8 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '../ui/dropdown-menu';
import { StackMuteSubmenu } from '@/components/mute/MuteMenuItems';
import type { useStackMuteActions } from '@/hooks/useMuteRuleActions';
import { Sparkline } from '../ui/sparkline';
import { ImageSourceMenu } from '../ImageSourceMenu';
import { cn } from '@/lib/utils';
@@ -129,6 +131,7 @@ export interface StackIdentityHeaderProps {
rollbackStack: () => Promise<void>;
scanStackConfig: () => Promise<void>;
requestDeleteStack: () => void;
stackMuteActions?: ReturnType<typeof useStackMuteActions>;
}
// Breadcrumb + serif title + state pill + image ref + action bar. The action
@@ -154,6 +157,7 @@ export function StackIdentityHeader({
rollbackStack,
scanStackConfig,
requestDeleteStack,
stackMuteActions,
}: StackIdentityHeaderProps) {
return (
<div className="flex flex-col gap-3">
@@ -228,8 +232,9 @@ export function StackIdentityHeader({
const canDelete = can('stack:delete', 'stack', stackName);
const canRollback = canDeploy && backupInfo.exists;
const canScan = trivy.available && isAdmin;
const canMute = stackMuteActions?.canMute ?? false;
const hasOverflowExtras = canRollback || canScan;
const hasOverflow = hasOverflowExtras || canDelete;
const hasOverflow = hasOverflowExtras || canDelete || canMute;
if (!canDeploy && !hasOverflow) return null;
return (
<div className="flex items-center gap-2 flex-wrap">
@@ -287,7 +292,8 @@ export function StackIdentityHeader({
{stackMisconfigScanning ? 'Scanning...' : 'Scan config'}
</DropdownMenuItem>
)}
{hasOverflowExtras && canDelete && <DropdownMenuSeparator />}
{stackMuteActions && <StackMuteSubmenu actions={stackMuteActions} />}
{(canRollback || canScan || stackMuteActions?.canMute) && canDelete && <DropdownMenuSeparator />}
{canDelete && (
<DropdownMenuItem
className="text-destructive focus:text-destructive focus:bg-destructive/10"