Add filtering to SD page and improve state processing

Signed-off-by: Julius Volz <julius.volz@gmail.com>
This commit is contained in:
Julius Volz 2024-08-28 15:35:32 +02:00
parent 4ce71af1b9
commit 697327a51d
4 changed files with 114 additions and 57 deletions

View file

@ -41,6 +41,14 @@
);
}
.healthInfo {
background-color: light-dark(
var(--mantine-color-blue-1),
var(--mantine-color-blue-9)
);
color: light-dark(var(--mantine-color-blue-9), var(--mantine-color-blue-1));
}
.healthUnknown {
background-color: light-dark(
var(--mantine-color-gray-2),

View file

@ -14,7 +14,12 @@ import {
import { Suspense } from "react";
import { useAppDispatch, useAppSelector } from "../../state/hooks";
import { StringParam, useQueryParam, withDefault } from "use-query-params";
import {
ArrayParam,
StringParam,
useQueryParam,
withDefault,
} from "use-query-params";
import ErrorBoundary from "../../components/ErrorBoundary";
import ScrapePoolList from "./ServiceDiscoveryPoolsList";
import { useSuspenseAPIQuery } from "../../api/api";
@ -23,6 +28,8 @@ import {
setCollapsedPools,
setShowLimitAlert,
} from "../../state/serviceDiscoveryPageSlice";
import { StateMultiSelect } from "../../components/StateMultiSelect";
import badgeClasses from "../../Badge.module.css";
export const targetPoolDisplayLimit = 20;
@ -38,9 +45,13 @@ export default function ServiceDiscoveryPage() {
const dispatch = useAppDispatch();
const [scrapePool, setScrapePool] = useQueryParam("scrapePool", StringParam);
const [scrapePool, setScrapePool] = useQueryParam("pool", StringParam);
const [stateFilter, setStateFilter] = useQueryParam(
"state",
withDefault(ArrayParam, [])
);
const [searchFilter, setSearchFilter] = useQueryParam(
"searchFilter",
"search",
withDefault(StringParam, "")
);
@ -76,6 +87,15 @@ export default function ServiceDiscoveryPage() {
}}
searchable
/>
<StateMultiSelect
options={["active", "dropped"]}
optionClass={(o) =>
o === "active" ? badgeClasses.healthOk : badgeClasses.healthInfo
}
placeholder="Filter by state"
values={(stateFilter?.filter((v) => v !== null) as string[]) || []}
onChange={(values) => setStateFilter(values)}
/>
<TextInput
flex={1}
leftSection={<IconSearch size={14} />}
@ -118,6 +138,7 @@ export default function ServiceDiscoveryPage() {
<ScrapePoolList
poolNames={scrapePools}
selectedPool={(limited && scrapePools[0]) || scrapePool || null}
stateFilter={stateFilter as string[]}
searchFilter={searchFilter}
/>
</Suspense>

View file

@ -17,7 +17,7 @@ import {
Target,
TargetsResult,
} from "../../api/responseTypes/targets";
import { FC } from "react";
import { FC, useMemo } from "react";
import { useAppDispatch, useAppSelector } from "../../state/hooks";
import {
setCollapsedPools,
@ -62,10 +62,12 @@ const droppedTargetKVSearch = new KVSearch<DroppedTarget>({
indexedKeys: ["discoveredLabels", ["discoveredLabels", /.*/]],
});
const groupTargets = (
const buildPoolsData = (
poolNames: string[],
activeTargets: Target[],
droppedTargets: DroppedTarget[]
droppedTargets: DroppedTarget[],
search: string,
stateFilter: (string | null)[]
): ScrapePools => {
const pools: ScrapePools = {};
@ -88,8 +90,19 @@ const groupTargets = (
pool.active++;
pool.total++;
}
pool.targets.push({
const filteredActiveTargets =
stateFilter.length !== 0 && !stateFilter.includes("active")
? []
: search === ""
? activeTargets
: activeTargetKVSearch
.filter(search, activeTargets)
.map((value) => value.original);
for (const target of filteredActiveTargets) {
pools[target.scrapePool].targets.push({
discoveredLabels: target.discoveredLabels,
labels: target.labels,
isDropped: false,
@ -107,30 +120,39 @@ const groupTargets = (
}
pool.total++;
}
pool.targets.push({
const filteredDroppedTargets =
stateFilter.length !== 0 && !stateFilter.includes("dropped")
? []
: search === ""
? droppedTargets
: droppedTargetKVSearch
.filter(search, droppedTargets)
.map((value) => value.original);
for (const target of filteredDroppedTargets) {
pools[target.discoveredLabels.job].targets.push({
discoveredLabels: target.discoveredLabels,
isDropped: true,
labels: {},
});
}
// for (const target of shownTargets) {
// pools[target.scrapePool].targets.push(target);
// }
return pools;
};
type ScrapePoolListProp = {
poolNames: string[];
selectedPool: string | null;
stateFilter: string[];
searchFilter: string;
};
const ScrapePoolList: FC<ScrapePoolListProp> = ({
poolNames,
selectedPool,
stateFilter,
searchFilter,
}) => {
const dispatch = useAppDispatch();
@ -157,24 +179,23 @@ const ScrapePoolList: FC<ScrapePoolListProp> = ({
const [debouncedSearch] = useDebouncedValue<string>(searchFilter, 250);
// TODO: Memoize all this computation, especially groupTargets().
// const search = debouncedSearch.trim();
// const healthFilteredTargets = activeTargets.filter(
// (target) =>
// healthFilter.length === 0 ||
// healthFilter.includes(target.health.toLowerCase())
// );
// const filteredTargets =
// search === ""
// ? healthFilteredTargets
// : kvSearch
// .filter(search, healthFilteredTargets)
// .map((value) => value.original);
const allPools = groupTargets(
selectedPool ? [selectedPool] : poolNames,
activeTargets,
droppedTargets
const allPools = useMemo(
() =>
buildPoolsData(
selectedPool ? [selectedPool] : poolNames,
activeTargets,
droppedTargets,
debouncedSearch,
stateFilter
),
[
selectedPool,
poolNames,
activeTargets,
droppedTargets,
debouncedSearch,
stateFilter,
]
);
const allPoolNames = Object.keys(allPools);
const shownPoolNames = showEmptyPools
@ -240,17 +261,22 @@ const ScrapePoolList: FC<ScrapePoolListProp> = ({
<RingProgress
size={25}
thickness={5}
sections={[
{
value: (pool.active / pool.total) * 100,
color: "green.4",
},
{
value:
((pool.total - pool.active) / pool.total) * 100,
color: "grape.4",
},
]}
sections={
pool.total === 0
? []
: [
{
value: (pool.active / pool.total) * 100,
color: "green.4",
},
{
value:
((pool.total - pool.active) / pool.total) *
100,
color: "blue.6",
},
]
}
/>
</Group>
</Group>

View file

@ -254,22 +254,24 @@ const ScrapePoolList: FC<ScrapePoolListProp> = ({
<RingProgress
size={25}
thickness={5}
sections={[
{
value: (pool.upCount / pool.count) * 100,
color: "green.4",
},
// Important that gray is the middle one, since the middle one seems to be
// the default color if all three values are 0 (empty pool = gray).
{
value: (pool.unknownCount / pool.count) * 100,
color: "gray.4",
},
{
value: (pool.downCount / pool.count) * 100,
color: "red.5",
},
]}
sections={
pool.count === 0
? []
: [
{
value: (pool.upCount / pool.count) * 100,
color: "green.4",
},
{
value: (pool.unknownCount / pool.count) * 100,
color: "gray.4",
},
{
value: (pool.downCount / pool.count) * 100,
color: "red.5",
},
]
}
/>
</Group>
</Group>