Feat: Resolve Incidents All and One

This commit is contained in:
Br0wnHammer committed 2025-07-03 02:15:37 +05:30
1 parent 731f2e12fb
commit d9ccc72a38
7 files changed
+126 -41

No files matched your search

+17 -3
View File
@@ -12,6 +12,7 @@ const useFetchChecksTeam = ({
page,
rowsPerPage,
enabled = true,
updateTrigger,
}) => {
const [checks, setChecks] = useState(undefined);
const [checksCount, setChecksCount] = useState(undefined);
@@ -49,7 +50,18 @@ const useFetchChecksTeam = ({
};
fetchChecks();
}, [status, sortOrder, limit, dateRange, filter, ack, page, rowsPerPage, enabled]);
}, [
status,
sortOrder,
limit,
dateRange,
filter,
ack,
page,
rowsPerPage,
enabled,
updateTrigger,
]);
return [checks, checksCount, isLoading, networkError];
};
@@ -66,6 +78,7 @@ const useFetchChecksByMonitor = ({
page,
rowsPerPage,
enabled = true,
updateTrigger,
}) => {
const [checks, setChecks] = useState(undefined);
const [checksCount, setChecksCount] = useState(undefined);
@@ -117,12 +130,13 @@ const useFetchChecksByMonitor = ({
page,
rowsPerPage,
enabled,
updateTrigger,
]);
return [checks, checksCount, isLoading, networkError];
};
const useFetchChecksSummaryByTeamId = () => {
const useFetchChecksSummaryByTeamId = ({ updateTrigger } = {}) => {
const [summary, setSummary] = useState(undefined);
const [isLoading, setIsLoading] = useState(false);
const [networkError, setNetworkError] = useState(false);
@@ -143,7 +157,7 @@ const useFetchChecksSummaryByTeamId = () => {
};
fetchSummary();
}, []);
}, [updateTrigger]);
return [summary, isLoading, networkError];
};
@@ -15,13 +15,18 @@ import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
import { useFetchChecksTeam } from "../../../../Hooks/checkHooks";
import { useFetchChecksByMonitor } from "../../../../Hooks/checkHooks";
import { Button, Typography } from "@mui/material";
import { networkService } from "../../../../Utils/NetworkService";
import { createToast } from "../../../../Utils/toastUtils";
const IncidentTable = ({
shouldRender,
isLoading,
monitors,
selectedMonitor,
filter,
dateRange,
updateTrigger,
setUpdateTrigger,
}) => {
//Redux state
const uiTimezone = useSelector((state) => state.ui.timezone);
@@ -31,6 +36,7 @@ const IncidentTable = ({
const [rowsPerPage, setRowsPerPage] = useState(10);
const selectedMonitorDetails = monitors?.[selectedMonitor];
const selectedMonitorType = selectedMonitorDetails?.type;
const [resolveLoading, setResolveLoading] = useState(false);
const [checksMonitor, checksCountMonitor, isLoadingMonitor, networkErrorMonitor] =
useFetchChecksByMonitor({
@@ -45,6 +51,7 @@ const IncidentTable = ({
page: page,
rowsPerPage: rowsPerPage,
enabled: selectedMonitor !== "0",
updateTrigger,
});
const [checksTeam, checksCountTeam, isLoadingTeam, networkErrorTeam] =
@@ -58,11 +65,12 @@ const IncidentTable = ({
page: page,
rowsPerPage: rowsPerPage,
enabled: selectedMonitor === "0",
updateTrigger,
});
const checks = selectedMonitor === "0" ? checksTeam : checksMonitor;
const checksCount = selectedMonitor === "0" ? checksCountTeam : checksCountMonitor;
const isLoading = isLoadingTeam || isLoadingMonitor;
isLoading = isLoadingTeam || isLoadingMonitor;
const networkError = selectedMonitor === "0" ? networkErrorTeam : networkErrorMonitor;
const { t } = useTranslation();
@@ -76,6 +84,21 @@ const IncidentTable = ({
setRowsPerPage(event.target.value);
};
const handleResolveIncident = async (checkId) => {
try {
setResolveLoading(true);
await networkService.updateCheckStatus({
checkId,
ack: true,
});
setUpdateTrigger((prev) => !prev);
} catch (error) {
createToast({ body: "Failed to resolve incident." });
} finally {
setResolveLoading(false);
}
};
const headers = [
{
id: "monitorName",
@@ -114,9 +137,31 @@ const IncidentTable = ({
render: (row) => <HttpStatusLabel status={row.statusCode} />,
},
{ id: "message", content: t("incidentsTableMessage"), render: (row) => row.message },
{
id: "action",
content: t("actions"),
render: (row) => {
return row.ack === false ? (
<Button
variant="contained"
color="accent"
onClick={() => {
handleResolveIncident(row._id);
}}
>
{t("incidentsTableActionResolve")}
</Button>
) : (
<Typography>
{t("incidentsTableResolvedAt")}{" "}
{formatDateWithTz(row.ackAt, "YYYY-MM-DD HH:mm:ss A", uiTimezone)}
</Typography>
);
},
},
];
if (!shouldRender || isLoading) return <TableSkeleton />;
if (isLoading || resolveLoading) return <TableSkeleton />;
if (networkError) {
return (
@@ -149,10 +194,12 @@ const IncidentTable = ({
};
IncidentTable.propTypes = {
shouldRender: PropTypes.bool,
isLoading: PropTypes.bool,
monitors: PropTypes.object,
selectedMonitor: PropTypes.string,
filter: PropTypes.string,
dateRange: PropTypes.string,
updateTrigger: PropTypes.bool,
setUpdateTrigger: PropTypes.func,
};
export default IncidentTable;
@@ -21,6 +21,12 @@ const OptionsHeader = ({
const theme = useTheme();
const { t } = useTranslation();
const monitorNames = typeof monitors !== "undefined" ? Object.values(monitors) : [];
const filterOptions = [
{ _id: "all", name: t("incidentsOptionsHeaderFilterAll") },
{ _id: "down", name: t("incidentsOptionsHeaderFilterDown") },
{ _id: "resolve", name: t("incidentsOptionsHeaderFilterCannotResolve") },
{ _id: "resolved", name: t("incidentsOptionsHeaderFilterResolved") },
];
// The stacks below which are three in number have the same style so
const stackStyles = {
@@ -65,36 +71,16 @@ const OptionsHeader = ({
>
{t("incidentsOptionsHeaderFilterBy")}
</Typography>
<ButtonGroup>
<Button
variant="group"
filled={(filter === "all").toString()}
onClick={() => setFilter("all")}
>
{t("incidentsOptionsHeaderFilterAll")}
</Button>
<Button
variant="group"
filled={(filter === "down").toString()}
onClick={() => setFilter("down")}
>
{t("incidentsOptionsHeaderFilterDown")}
</Button>
<Button
variant="group"
filled={(filter === "resolve").toString()}
onClick={() => setFilter("resolve")}
>
{t("incidentsOptionsHeaderFilterCannotResolve")}
</Button>
<Button
variant="group"
filled={(filter === "resolved").toString()}
onClick={() => setFilter("resolved")}
>
{t("incidentsOptionsHeaderFilterResolved")}
</Button>
</ButtonGroup>
<Select
id="incidents-select-filter"
value={filter}
onChange={(e) => setFilter(e.target.value)}
items={filterOptions}
sx={{
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastTextSecondary,
}}
/>
</Stack>
<Stack {...stackStyles}>
<Typography
+28 -3
View File
@@ -5,6 +5,8 @@ import GenericFallback from "../../Components/GenericFallback";
import IncidentTable from "./Components/IncidentTable";
import OptionsHeader from "./Components/OptionsHeader";
import StatusBoxes from "./Components/StatusBoxes";
import { Box, Button } from "@mui/material";
import { createToast } from "../../Utils/toastUtils";
//Utils
import { useTheme } from "@emotion/react";
@@ -14,6 +16,7 @@ import { useState, useEffect } from "react";
import NetworkError from "../../Components/GenericFallback/NetworkError";
import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom";
import { networkService } from "../../Utils/NetworkService";
//Constants
const Incidents = () => {
@@ -29,12 +32,14 @@ const Incidents = () => {
const [filter, setFilter] = useState(undefined);
const [dateRange, setDateRange] = useState(undefined);
const [monitorLookup, setMonitorLookup] = useState(undefined);
const [updateTrigger, setUpdateTrigger] = useState(false);
//Utils
const theme = useTheme();
const [monitors, , isLoading, networkError] = useFetchMonitorsByTeamId({});
const [summary, isLoadingSummary, networkErrorSummary] =
useFetchChecksSummaryByTeamId();
const [summary, isLoadingSummary, networkErrorSummary] = useFetchChecksSummaryByTeamId({
updateTrigger,
});
const { monitorId } = useParams();
useEffect(() => {
@@ -55,6 +60,15 @@ const Incidents = () => {
setMonitorLookup(monitorLookup);
}, [monitors]);
const handleAckAllChecks = async () => {
try {
await networkService.updateAllChecksStatus({ ack: true });
setUpdateTrigger((prev) => !prev);
} catch (error) {
createToast({ body: "Failed to resolve all incidents." });
}
};
if (networkError || networkErrorSummary) {
return (
<GenericFallback>
@@ -66,6 +80,15 @@ const Incidents = () => {
return (
<Stack gap={theme.spacing(10)}>
<Breadcrumbs list={BREADCRUMBS} />
<Box alignSelf="flex-end">
<Button
variant="contained"
color="accent"
onClick={handleAckAllChecks}
>
{t("incidentsPageActionResolve")}
</Button>
</Box>
<StatusBoxes
isLoading={isLoadingSummary}
summary={summary}
@@ -81,11 +104,13 @@ const Incidents = () => {
setDateRange={setDateRange}
/>
<IncidentTable
shouldRender={!isLoading}
isLoading={isLoading}
monitors={monitorLookup ? monitorLookup : {}}
selectedMonitor={selectedMonitor}
filter={filter}
dateRange={dateRange}
updateTrigger={updateTrigger}
setUpdateTrigger={setUpdateTrigger}
/>
</Stack>
);
+6
View File
@@ -631,6 +631,12 @@ class NetworkService {
});
}
async updateAllChecksStatus(config) {
return this.axiosInstance.put(`/checks/team/`, {
ack: config.ack,
});
}
/**
* ************************************
* Get all checks for a given user
+3
View File
@@ -390,7 +390,10 @@
"incidentsOptionsHeaderFilterBy": "Filter by:",
"incidentsOptionsHeaderTotalIncidents": "Total Incidents",
"incidentsOptionsHeaderFilterCannotResolve": "Cannot Resolve",
"incidentsTableResolvedAt": "Resolved at",
"incidentsOptionsHeaderFilterDown": "Down",
"incidentsTableActionResolve": "Resolve",
"incidentsPageActionResolve": "Resolve all incidents",
"incidentsOptionsHeaderFilterResolved": "Resolved",
"incidentsOptionsHeaderLastDay": "Last day",
"incidentsOptionsHeaderLastHour": "Last hour",
@@ -10,7 +10,11 @@ const buildChecksSummaryByTeamIdPipeline = ({ matchStage }) => {
totalChecks: { $sum: { $cond: [{ $eq: ["$status", false] }, 1, 0] } },
resolvedChecks: {
$sum: {
$cond: [{ $eq: ["$ack", true] }, 1, 0],
$cond: [
{ $and: [{ $eq: ["$ack", true] }, { $eq: ["$status", false] }] },
1,
0,
],
},
},
downChecks: {