Feat: Resolve Incidents All and One
This commit is contained in:
1 parent
731f2e12fb
commit
d9ccc72a38
7 files changed
+126
-41
No files matched your search
@@ -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
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in new issue
Block a user