mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat(site): dismiss health section warnings (#11059)
This commit is contained in:
@@ -1602,3 +1602,19 @@ export const getHealth = async (force: boolean = false) => {
|
||||
);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getHealthSettings = async () => {
|
||||
return (
|
||||
await axios.get<TypesGen.HealthSettings>(`/api/v2/debug/health/settings`)
|
||||
).data;
|
||||
};
|
||||
|
||||
export const updateHealthSettings = async (
|
||||
data: TypesGen.UpdateHealthSettings,
|
||||
) => {
|
||||
const response = await axios.put<TypesGen.HealthSettings>(
|
||||
`/api/v2/debug/health/settings`,
|
||||
data,
|
||||
);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
@@ -1,17 +1,45 @@
|
||||
import * as API from "api/api";
|
||||
import { QueryClient } from "react-query";
|
||||
import { HealthSettings, UpdateHealthSettings } from "api/typesGenerated";
|
||||
import { QueryClient, UseMutationOptions } from "react-query";
|
||||
|
||||
export const HEALTH_QUERY_KEY = ["health"];
|
||||
export const HEALTH_QUERY_SETTINGS_KEY = ["health", "settings"];
|
||||
|
||||
export const health = () => ({
|
||||
queryKey: ["health"],
|
||||
queryKey: HEALTH_QUERY_KEY,
|
||||
queryFn: async () => API.getHealth(),
|
||||
});
|
||||
|
||||
export const refreshHealth = (queryClient: QueryClient) => {
|
||||
return {
|
||||
mutationFn: async () => {
|
||||
await queryClient.cancelQueries(["health"]);
|
||||
await queryClient.cancelQueries(HEALTH_QUERY_KEY);
|
||||
const newHealthData = await API.getHealth(true);
|
||||
queryClient.setQueryData(["health"], newHealthData);
|
||||
queryClient.setQueryData(HEALTH_QUERY_KEY, newHealthData);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const healthSettings = () => {
|
||||
return {
|
||||
queryKey: HEALTH_QUERY_SETTINGS_KEY,
|
||||
queryFn: API.getHealthSettings,
|
||||
};
|
||||
};
|
||||
|
||||
export const updateHealthSettings = (
|
||||
queryClient: QueryClient,
|
||||
): UseMutationOptions<
|
||||
HealthSettings,
|
||||
unknown,
|
||||
UpdateHealthSettings,
|
||||
unknown
|
||||
> => {
|
||||
return {
|
||||
mutationFn: API.updateHealthSettings,
|
||||
onSuccess: async (_, newSettings) => {
|
||||
await queryClient.invalidateQueries(HEALTH_QUERY_KEY);
|
||||
queryClient.setQueryData(HEALTH_QUERY_SETTINGS_KEY, newSettings);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@ import { HealthcheckReport } from "api/typesGenerated";
|
||||
import { Alert } from "components/Alert/Alert";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { pageTitle } from "utils/page";
|
||||
import { DismissWarningButton } from "./DismissWarningButton";
|
||||
|
||||
export const AccessURLPage = () => {
|
||||
const healthStatus = useOutletContext<HealthcheckReport>();
|
||||
@@ -28,6 +29,7 @@ export const AccessURLPage = () => {
|
||||
<HealthyDot severity={accessUrl.severity} />
|
||||
Access URL
|
||||
</HeaderTitle>
|
||||
<DismissWarningButton healthcheck="AccessURL" />
|
||||
</Header>
|
||||
|
||||
<Main>
|
||||
|
||||
@@ -20,6 +20,7 @@ import { Alert } from "components/Alert/Alert";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { pageTitle } from "utils/page";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { DismissWarningButton } from "./DismissWarningButton";
|
||||
|
||||
const flags = [
|
||||
"UDP",
|
||||
@@ -52,6 +53,7 @@ export const DERPPage = () => {
|
||||
<HealthyDot severity={derp.severity as HealthSeverity} />
|
||||
DERP
|
||||
</HeaderTitle>
|
||||
<DismissWarningButton healthcheck="DERP" />
|
||||
</Header>
|
||||
|
||||
<Main>
|
||||
|
||||
@@ -12,6 +12,7 @@ import { HealthcheckReport } from "api/typesGenerated";
|
||||
import { Alert } from "components/Alert/Alert";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { pageTitle } from "utils/page";
|
||||
import { DismissWarningButton } from "./DismissWarningButton";
|
||||
|
||||
export const DatabasePage = () => {
|
||||
const healthStatus = useOutletContext<HealthcheckReport>();
|
||||
@@ -28,6 +29,7 @@ export const DatabasePage = () => {
|
||||
<HealthyDot severity={database.severity} />
|
||||
Database
|
||||
</HeaderTitle>
|
||||
<DismissWarningButton healthcheck="Database" />
|
||||
</Header>
|
||||
|
||||
<Main>
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import NotificationOutlined from "@mui/icons-material/NotificationsOutlined";
|
||||
import NotificationsOffOutlined from "@mui/icons-material/NotificationsOffOutlined";
|
||||
import { healthSettings, updateHealthSettings } from "api/queries/debug";
|
||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||
import { displaySuccess } from "components/GlobalSnackbar/utils";
|
||||
import LoadingButton from "@mui/lab/LoadingButton";
|
||||
import Skeleton from "@mui/material/Skeleton";
|
||||
import { HealthSection } from "api/typesGenerated";
|
||||
|
||||
export const DismissWarningButton = (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));
|
||||
|
||||
if (!healthSettingsQuery.data) {
|
||||
return (
|
||||
<Skeleton
|
||||
variant="rectangular"
|
||||
height={36}
|
||||
width={170}
|
||||
css={{ borderRadius: 8 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const { dismissed_healthchecks } = healthSettingsQuery.data;
|
||||
const isDismissed = dismissed_healthchecks.includes(props.healthcheck);
|
||||
|
||||
if (isDismissed) {
|
||||
return (
|
||||
<LoadingButton
|
||||
disabled={healthSettingsQuery.isLoading}
|
||||
loading={enableMutation.isLoading}
|
||||
loadingPosition="start"
|
||||
startIcon={<NotificationsOffOutlined />}
|
||||
onClick={async () => {
|
||||
const updatedSettings = dismissed_healthchecks.filter(
|
||||
(dismissedHealthcheck) =>
|
||||
dismissedHealthcheck !== props.healthcheck,
|
||||
);
|
||||
await enableMutation.mutateAsync({
|
||||
dismissed_healthchecks: updatedSettings,
|
||||
});
|
||||
displaySuccess("Warnings enabled successfully!");
|
||||
}}
|
||||
>
|
||||
Enable warnings
|
||||
</LoadingButton>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<LoadingButton
|
||||
disabled={healthSettingsQuery.isLoading}
|
||||
loading={dismissMutation.isLoading}
|
||||
loadingPosition="start"
|
||||
startIcon={<NotificationOutlined />}
|
||||
onClick={async () => {
|
||||
const updatedSettings = [...dismissed_healthchecks, props.healthcheck];
|
||||
await dismissMutation.mutateAsync({
|
||||
dismissed_healthchecks: updatedSettings,
|
||||
});
|
||||
displaySuccess("Warnings dismissed successfully!");
|
||||
}}
|
||||
>
|
||||
Dismiss warnings
|
||||
</LoadingButton>
|
||||
);
|
||||
};
|
||||
@@ -16,6 +16,7 @@ import { kebabCase } from "lodash/fp";
|
||||
import { Suspense } from "react";
|
||||
import { HealthIcon } from "./Content";
|
||||
import { HealthSeverity } from "api/typesGenerated";
|
||||
import NotificationsOffOutlined from "@mui/icons-material/NotificationsOffOutlined";
|
||||
|
||||
const sections = {
|
||||
derp: "DERP",
|
||||
@@ -189,6 +190,15 @@ export function HealthLayout() {
|
||||
severity={healthSection.severity as HealthSeverity}
|
||||
/>
|
||||
{label}
|
||||
{healthSection.dismissed && (
|
||||
<NotificationsOffOutlined
|
||||
css={{
|
||||
fontSize: 14,
|
||||
marginLeft: "auto",
|
||||
color: theme.palette.text.disabled,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</NavLink>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -15,6 +15,7 @@ import { MONOSPACE_FONT_FAMILY } from "theme/constants";
|
||||
import { Alert } from "components/Alert/Alert";
|
||||
import { pageTitle } from "utils/page";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { DismissWarningButton } from "./DismissWarningButton";
|
||||
|
||||
export const WebsocketPage = () => {
|
||||
const healthStatus = useOutletContext<HealthcheckReport>();
|
||||
@@ -32,6 +33,7 @@ export const WebsocketPage = () => {
|
||||
<HealthyDot severity={websocket.severity} />
|
||||
Websocket
|
||||
</HeaderTitle>
|
||||
<DismissWarningButton healthcheck="Websocket" />
|
||||
</Header>
|
||||
|
||||
<Main>
|
||||
|
||||
@@ -16,6 +16,7 @@ import TagOutlined from "@mui/icons-material/TagOutlined";
|
||||
import { Alert } from "components/Alert/Alert";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { pageTitle } from "utils/page";
|
||||
import { DismissWarningButton } from "./DismissWarningButton";
|
||||
|
||||
export const WorkspaceProxyPage = () => {
|
||||
const healthStatus = useOutletContext<HealthcheckReport>();
|
||||
@@ -34,6 +35,7 @@ export const WorkspaceProxyPage = () => {
|
||||
<HealthyDot severity={workspace_proxy.severity} />
|
||||
Workspace Proxy
|
||||
</HeaderTitle>
|
||||
<DismissWarningButton healthcheck="WorkspaceProxy" />
|
||||
</Header>
|
||||
|
||||
<Main>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { useQueryClient } from "react-query";
|
||||
import {
|
||||
reactRouterParameters,
|
||||
reactRouterOutlet,
|
||||
RouteDefinition,
|
||||
} from "storybook-addon-react-router-v6";
|
||||
import { MockHealth, MockHealthSettings } from "testHelpers/entities";
|
||||
import { Meta } from "@storybook/react";
|
||||
import { HEALTH_QUERY_KEY, HEALTH_QUERY_SETTINGS_KEY } from "api/queries/debug";
|
||||
|
||||
type MetaOptions = {
|
||||
element: RouteDefinition;
|
||||
path: string;
|
||||
};
|
||||
|
||||
export const generateMeta = ({ element, path }: MetaOptions): Meta => {
|
||||
return {
|
||||
parameters: {
|
||||
layout: "fullscreen",
|
||||
reactRouter: reactRouterParameters({
|
||||
routing: reactRouterOutlet({ path: `/health/${path}` }, element),
|
||||
}),
|
||||
},
|
||||
decorators: [
|
||||
(Story) => {
|
||||
const queryClient = useQueryClient();
|
||||
queryClient.setQueryData(HEALTH_QUERY_KEY, MockHealth);
|
||||
queryClient.setQueryData(HEALTH_QUERY_SETTINGS_KEY, MockHealthSettings);
|
||||
return <Story />;
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
@@ -3149,6 +3149,10 @@ export const DeploymentHealthUnhealthy: TypesGen.HealthcheckReport = {
|
||||
},
|
||||
};
|
||||
|
||||
export const MockHealthSettings: TypesGen.HealthSettings = {
|
||||
dismissed_healthchecks: [],
|
||||
};
|
||||
|
||||
export const MockGithubExternalProvider: TypesGen.ExternalAuthLinkProvider = {
|
||||
id: "github",
|
||||
type: "github",
|
||||
|
||||
Reference in New Issue
Block a user