fix(ui): tweak alert history ui

This commit is contained in:
Łukasz Mierzwa
2021-05-11 15:36:23 +01:00
committed by Łukasz Mierzwa
parent 1d53d8cd4a
commit bd25c6c178
3 changed files with 172 additions and 15 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ def generateSeries():
for i in range(24):
value = 0
if random.randint(0, 100) > 75:
value = random.randint(0, 100)
value = random.randint(0, 10)
series.append([now, str(value)])
now = now - 3600
return series
+132 -1
View File
@@ -14,7 +14,7 @@ import {
RainbowHistoryResponse,
FailedHistoryResponse,
} from "__fixtures__/AlertHistory";
import { APIAlertGroupT } from "Models/APITypes";
import { APIAlertGroupT, HistoryResponseT } from "Models/APITypes";
import { AlertHistory } from ".";
let group: APIAlertGroupT;
@@ -160,4 +160,135 @@ describe("<AlertHistory />", () => {
expect(fetchMock.calls()).toHaveLength(2);
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
});
interface testCasesT {
title: string;
response: HistoryResponseT;
values: string[];
}
const testCases: testCasesT[] = [
{
title: "EmptyHistoryResponse",
response: EmptyHistoryResponse,
values: new Array(24).fill("inactive"),
},
{
title: "RainbowHistoryResponse",
response: RainbowHistoryResponse,
values: [
"inactive",
"firing firing-1",
"firing firing-2",
"firing firing-3",
"firing firing-4",
"firing firing-5",
"inactive",
"firing firing-1",
"firing firing-2",
"firing firing-3",
"firing firing-4",
"firing firing-5",
"inactive",
"firing firing-1",
"firing firing-2",
"firing firing-3",
"firing firing-4",
"firing firing-5",
"inactive",
"firing firing-1",
"firing firing-2",
"firing firing-3",
"firing firing-4",
"firing firing-5",
],
},
{
title: "FailedHistoryResponse",
response: FailedHistoryResponse,
values: ["error"],
},
{
title: "Single alert",
response: {
error: "",
samples: [
...Array(12).fill({ timestamp: "", value: 0 }),
{ timestamp: "", value: 1 },
...Array(11).fill({ timestamp: "", value: 0 }),
],
},
values: [
...new Array(12).fill("inactive"),
"firing firing-1",
...new Array(11).fill("inactive"),
],
},
{
title: "2 alerts in a single hour",
response: {
error: "",
samples: [
{ timestamp: "", value: 2 },
...Array(23).fill({ timestamp: "", value: 0 }),
],
},
values: ["firing firing-2", ...new Array(23).fill("inactive")],
},
{
title: "5 alerts in a single hour",
response: {
error: "",
samples: [
{ timestamp: "", value: 5 },
...Array(23).fill({ timestamp: "", value: 0 }),
],
},
values: ["firing firing-5", ...new Array(23).fill("inactive")],
},
{
title: "20 alerts in a single hour",
response: {
error: "",
samples: [
{ timestamp: "", value: 20 },
...Array(23).fill({ timestamp: "", value: 0 }),
],
},
values: ["firing firing-5", ...new Array(23).fill("inactive")],
},
];
for (const testCase of testCases) {
const g = MockGroup("fakeGroup");
for (let i = 1; i <= 5; i++) {
const alert = MockAlert([], { instance: `instance${i}` }, "active");
const startsAt = new Date();
alert.startsAt = startsAt.toISOString();
alert.alertmanager[0].startsAt = startsAt.toISOString();
g.alerts.push(alert);
}
it(`${testCase.title}`, async () => {
fetchMock.resetHistory();
fetchMock.mock(
"*",
{
headers: { "Content-Type": "application/json" },
body: JSON.stringify(testCase.response),
},
{
overwriteRoutes: true,
}
);
const tree = mount(<AlertHistory group={g}></AlertHistory>);
await act(async () => {
await fetchMock.flush(true);
});
tree.update();
const rects = tree.find("rect").map((r) => r.props().className);
expect(rects).toStrictEqual(testCase.values);
tree.unmount();
});
}
});
+39 -13
View File
@@ -7,26 +7,48 @@ import { APIAlertGroupT, HistoryResponseT } from "Models/APITypes";
import { useFetchAny, UpstreamT } from "Hooks/useFetchAny";
import { TooltipWrapper } from "Components/TooltipWrapper";
interface minMaxT {
minValue: number;
maxValue: number;
}
const responseStub: HistoryResponseT = {
error: "",
samples: Array(24).fill({ timestamp: "", value: 0 }),
};
const promURIRe = new RegExp(/(https?:\/\/.+)\/graph?.+/);
const promURIRe = new RegExp(/^(https?:\/\/.+)\//);
export const AlertHistory: FC<{ group: APIAlertGroupT }> = ({ group }) => {
const [ref, inView] = useInView({ triggerOnce: true });
const [epoch, setEpoch] = useState<number>(0);
const [sources, setSources] = useState<string[]>([]);
const [upstreams, setUpstreams] = useState<UpstreamT[]>([]);
const [labels] = useState({ ...group.labels, ...group.shared.labels });
const { response, error, inProgress } =
useFetchAny<HistoryResponseT>(upstreams);
const [maxValue, setMaxValue] = useState<number>(0);
const { response, error } = useFetchAny<HistoryResponseT>(upstreams);
const [cachedResponse, setCachedResponse] =
useState<HistoryResponseT | null>(null);
const [minMaxValue, setMinMaxValue] = useState<minMaxT>({
minValue: 0,
maxValue: 0,
});
useEffect(() => {
const timer = window.setInterval(() => {
setEpoch((val) => val + 1);
}, 5 * 60 * 1000);
return () => clearInterval(timer);
}, [inView]);
useEffect(() => {
if (response !== null) {
setMaxValue(Math.max(...response.samples.map((s) => s.value)));
setCachedResponse(response);
const max = Math.max(...response.samples.map((s) => s.value));
const min = Math.min(
...response.samples.filter((s) => s.value > 0).map((s) => s.value)
);
setMinMaxValue({ minValue: min === Infinity ? 0 : min, maxValue: max });
}
}, [response]);
@@ -60,7 +82,7 @@ export const AlertHistory: FC<{ group: APIAlertGroupT }> = ({ group }) => {
},
},
]);
}, [inView, labels, sources]);
}, [inView, labels, sources, epoch]);
return (
<div className="w-100 d-flex">
@@ -68,9 +90,9 @@ export const AlertHistory: FC<{ group: APIAlertGroupT }> = ({ group }) => {
ref={ref}
className="w-100 d-flex justify-content-between align-self-center"
>
{error || (response && response.error !== "") ? (
{error || (cachedResponse && cachedResponse.error !== "") ? (
<TooltipWrapper
title={error || response?.error}
title={error || cachedResponse?.error}
className="alert-history-tooltip"
>
<svg className="alert-history">
@@ -78,18 +100,22 @@ export const AlertHistory: FC<{ group: APIAlertGroupT }> = ({ group }) => {
</svg>
</TooltipWrapper>
) : (
(response || responseStub).samples.map((sample, i) => (
(cachedResponse || responseStub).samples.map((sample, i) => (
<svg key={i} className="alert-history">
<rect
rx={2}
ry={2}
className={
inProgress || response === null
cachedResponse === null
? "fetching"
: sample.value > 0
? `firing firing-${Math.round(
(sample.value / maxValue) * 5
)}`
? `firing firing-${
minMaxValue.minValue === minMaxValue.maxValue
? Math.min(minMaxValue.maxValue, 5)
: Math.round(
(sample.value / minMaxValue.maxValue) * 5
)
}`
: "inactive"
}
></rect>