From 6c916629c9672fcfe56537d7a105315224333157 Mon Sep 17 00:00:00 2001 From: TJ Date: Mon, 27 Jul 2026 19:56:47 -0700 Subject: [PATCH] feat(site): rename "Dismiss warnings" to "Mute warnings" and make health callouts dismissible (#27554) Fixes a mismatch between the header button's label and its behavior on the Health pages. Today the button reads **Dismiss warnings**, which suggests it will close the in-page callout, but it actually toggles whether the health check surfaces in the top-nav status indicator and shows a bell-off icon in the sidebar. The callout itself has no way to be closed. ### Changes - Rename the toggle to **Mute warnings** / **Unmute warnings** (with matching toast copy) and rename the component + file from `DismissWarningButton` to `MuteWarningsButton`. - Set `dismissible` on the **warning** ``s across the Health pages (Access URL, Database, DERP, DERP region, Provisioner Daemons, Websocket, Workspace Proxy) so users can close the callout from the callout itself. `Alert` already supports this via a built-in close button. - Error-severity ``s are intentionally **not** dismissible: `HealthLayout` refetches every 30s and reuses the mounted subpage, so allowing dismissal would suppress subsequent (possibly different) error messages until reload. Diagnostics pages should not hide active faults. - Align ProvisionerDaemonsPage's warning callout with the other five pages by setting `prominent`. ### Notes - Callout dismissal is client-side only (matches `Alert`'s existing `useState` behavior). Warning ``s are keyed by `warning.code`, so a dismissed warning reappears on reload/remount but survives a refetch. The mute toggle continues to persist server-side via `dismissed_healthchecks`. - Follow-up filed for a pre-existing UX mismatch: the mute also silently drops error-severity sections from the top-nav banner (#27557). Kept out of scope here per requester. - No API or backend changes. --- _This PR was generated by Coder Agents on behalf of @tracyjohnsonux._ --- site/src/pages/HealthPage/AccessURLPage.tsx | 5 ++- site/src/pages/HealthPage/DERPPage.tsx | 5 ++- site/src/pages/HealthPage/DERPRegionPage.tsx | 1 + site/src/pages/HealthPage/DatabasePage.tsx | 5 ++- ...rningButton.tsx => MuteWarningsButton.tsx} | 37 +++++++++---------- .../HealthPage/ProvisionerDaemonsPage.tsx | 6 ++- site/src/pages/HealthPage/WebsocketPage.tsx | 6 +-- .../pages/HealthPage/WorkspaceProxyPage.tsx | 5 ++- 8 files changed, 38 insertions(+), 32 deletions(-) rename site/src/pages/HealthPage/{DismissWarningButton.tsx => MuteWarningsButton.tsx} (55%) diff --git a/site/src/pages/HealthPage/AccessURLPage.tsx b/site/src/pages/HealthPage/AccessURLPage.tsx index 5cb885c534..b35bb77487 100644 --- a/site/src/pages/HealthPage/AccessURLPage.tsx +++ b/site/src/pages/HealthPage/AccessURLPage.tsx @@ -12,7 +12,7 @@ import { HealthyDot, Main, } from "./Content"; -import { DismissWarningButton } from "./DismissWarningButton"; +import { MuteWarningsButton } from "./MuteWarningsButton"; const AccessURLPage = () => { const healthStatus = useOutletContext(); @@ -27,7 +27,7 @@ const AccessURLPage = () => { Access URL - +
@@ -40,6 +40,7 @@ const AccessURLPage = () => { key={warning.code} severity="warning" prominent + dismissible > {warning.message} diff --git a/site/src/pages/HealthPage/DERPPage.tsx b/site/src/pages/HealthPage/DERPPage.tsx index 5874f8d83e..f732c27ac0 100644 --- a/site/src/pages/HealthPage/DERPPage.tsx +++ b/site/src/pages/HealthPage/DERPPage.tsx @@ -25,7 +25,7 @@ import { SectionLabel, StatusIcon, } from "./Content"; -import { DismissWarningButton } from "./DismissWarningButton"; +import { MuteWarningsButton } from "./MuteWarningsButton"; type BooleanKeys = { [K in keyof T]: T[K] extends boolean | null ? K : never; @@ -148,7 +148,7 @@ const DERPPage: FC = () => { DERP - +
@@ -159,6 +159,7 @@ const DERPPage: FC = () => { key={warning.code} severity="warning" prominent + dismissible > {warning.message} diff --git a/site/src/pages/HealthPage/DERPRegionPage.tsx b/site/src/pages/HealthPage/DERPRegionPage.tsx index 4f3e8e9299..9acc304ca8 100644 --- a/site/src/pages/HealthPage/DERPRegionPage.tsx +++ b/site/src/pages/HealthPage/DERPRegionPage.tsx @@ -83,6 +83,7 @@ const DERPRegionPage: FC = () => { key={warning.code} severity="warning" prominent + dismissible > {warning.message} diff --git a/site/src/pages/HealthPage/DatabasePage.tsx b/site/src/pages/HealthPage/DatabasePage.tsx index b892f924a1..0413e169ec 100644 --- a/site/src/pages/HealthPage/DatabasePage.tsx +++ b/site/src/pages/HealthPage/DatabasePage.tsx @@ -12,7 +12,7 @@ import { HealthyDot, Main, } from "./Content"; -import { DismissWarningButton } from "./DismissWarningButton"; +import { MuteWarningsButton } from "./MuteWarningsButton"; const DatabasePage = () => { const healthStatus = useOutletContext(); @@ -27,7 +27,7 @@ const DatabasePage = () => { Database - +
@@ -38,6 +38,7 @@ const DatabasePage = () => { key={warning.code} severity="warning" prominent + dismissible > {warning.message} diff --git a/site/src/pages/HealthPage/DismissWarningButton.tsx b/site/src/pages/HealthPage/MuteWarningsButton.tsx similarity index 55% rename from site/src/pages/HealthPage/DismissWarningButton.tsx rename to site/src/pages/HealthPage/MuteWarningsButton.tsx index 43f48ac869..025fd6dafe 100644 --- a/site/src/pages/HealthPage/DismissWarningButton.tsx +++ b/site/src/pages/HealthPage/MuteWarningsButton.tsx @@ -7,63 +7,62 @@ import { Button } from "#/components/Button/Button"; import { Skeleton } from "#/components/Skeleton/Skeleton"; import { Spinner } from "#/components/Spinner/Spinner"; -export const DismissWarningButton = (props: { healthcheck: HealthSection }) => { +export const MuteWarningsButton = (props: { healthcheck: HealthSection }) => { const queryClient = useQueryClient(); const healthSettingsQuery = useQuery(healthSettings()); - // They call the same mutation but are used in diff contexts so we don't want - // to merge their states. Eg. You dismiss a warning and when it is done it - // will show the enable button but since the mutation is still invalidating - // other queries it will be in the loading state when it should be idle. - const enableMutation = useMutation(updateHealthSettings(queryClient)); - const dismissMutation = useMutation(updateHealthSettings(queryClient)); + // Separate mutation instances so unmuting isn't stuck pending while + // muting's query invalidation resolves (a shared mutation would share + // isPending and spin the wrong button). + const unmuteMutation = useMutation(updateHealthSettings(queryClient)); + const muteMutation = useMutation(updateHealthSettings(queryClient)); if (!healthSettingsQuery.data) { return ; } const { dismissed_healthchecks } = healthSettingsQuery.data; - const isDismissed = dismissed_healthchecks.includes(props.healthcheck); + const isMuted = dismissed_healthchecks.includes(props.healthcheck); - if (isDismissed) { + if (isMuted) { return ( ); } return ( ); }; diff --git a/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx b/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx index 71b2d6a319..d3466241ed 100644 --- a/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx +++ b/site/src/pages/HealthPage/ProvisionerDaemonsPage.tsx @@ -11,7 +11,7 @@ import { HealthyDot, Main, } from "./Content"; -import { DismissWarningButton } from "./DismissWarningButton"; +import { MuteWarningsButton } from "./MuteWarningsButton"; const ProvisionerDaemonsPage: FC = () => { const healthStatus = useOutletContext(); @@ -26,7 +26,7 @@ const ProvisionerDaemonsPage: FC = () => { Provisioner Daemons - +
@@ -41,6 +41,8 @@ const ProvisionerDaemonsPage: FC = () => { actions={} key={warning.code} severity="warning" + prominent + dismissible > {warning.message} diff --git a/site/src/pages/HealthPage/WebsocketPage.tsx b/site/src/pages/HealthPage/WebsocketPage.tsx index 5a2c5928b2..ca70478326 100644 --- a/site/src/pages/HealthPage/WebsocketPage.tsx +++ b/site/src/pages/HealthPage/WebsocketPage.tsx @@ -16,7 +16,7 @@ import { Pill, SectionLabel, } from "./Content"; -import { DismissWarningButton } from "./DismissWarningButton"; +import { MuteWarningsButton } from "./MuteWarningsButton"; const WebsocketPage = () => { const healthStatus = useOutletContext(); @@ -31,7 +31,7 @@ const WebsocketPage = () => { Websocket - +
@@ -43,7 +43,7 @@ const WebsocketPage = () => { {websocket.warnings.map((warning) => { return ( - + {warning.message} ); diff --git a/site/src/pages/HealthPage/WorkspaceProxyPage.tsx b/site/src/pages/HealthPage/WorkspaceProxyPage.tsx index fba577132d..b6422d8ec3 100644 --- a/site/src/pages/HealthPage/WorkspaceProxyPage.tsx +++ b/site/src/pages/HealthPage/WorkspaceProxyPage.tsx @@ -21,7 +21,7 @@ import { Main, Pill, } from "./Content"; -import { DismissWarningButton } from "./DismissWarningButton"; +import { MuteWarningsButton } from "./MuteWarningsButton"; const WorkspaceProxyPage: FC = () => { const healthStatus = useOutletContext(); @@ -37,7 +37,7 @@ const WorkspaceProxyPage: FC = () => { Workspace Proxy - +
@@ -53,6 +53,7 @@ const WorkspaceProxyPage: FC = () => { key={warning.code} severity="warning" prominent + dismissible > {warning.message}