mirror of
https://github.com/prymitive/karma
synced 2026-08-17 11:27:01 +00:00
fix(ui): tweak alert history ui
This commit is contained in:
committed by
Łukasz Mierzwa
parent
1d53d8cd4a
commit
bd25c6c178
+1
-1
@@ -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
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user