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 { .healthUnknown {
background-color: light-dark( background-color: light-dark(
var(--mantine-color-gray-2), var(--mantine-color-gray-2),

View file

@ -14,7 +14,12 @@ import {
import { Suspense } from "react"; import { Suspense } from "react";
import { useAppDispatch, useAppSelector } from "../../state/hooks"; 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 ErrorBoundary from "../../components/ErrorBoundary";
import ScrapePoolList from "./ServiceDiscoveryPoolsList"; import ScrapePoolList from "./ServiceDiscoveryPoolsList";
import { useSuspenseAPIQuery } from "../../api/api"; import { useSuspenseAPIQuery } from "../../api/api";
@ -23,6 +28,8 @@ import {
setCollapsedPools, setCollapsedPools,
setShowLimitAlert, setShowLimitAlert,
} from "../../state/serviceDiscoveryPageSlice"; } from "../../state/serviceDiscoveryPageSlice";
import { StateMultiSelect } from "../../components/StateMultiSelect";
import badgeClasses from "../../Badge.module.css";
export const targetPoolDisplayLimit = 20; export const targetPoolDisplayLimit = 20;
@ -38,9 +45,13 @@ export default function ServiceDiscoveryPage() {
const dispatch = useAppDispatch(); 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( const [searchFilter, setSearchFilter] = useQueryParam(
"searchFilter", "search",
withDefault(StringParam, "") withDefault(StringParam, "")
); );
@ -76,6 +87,15 @@ export default function ServiceDiscoveryPage() {
}} }}
searchable 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 <TextInput
flex={1} flex={1}
leftSection={<IconSearch size={14} />} leftSection={<IconSearch size={14} />}
@ -118,6 +138,7 @@ export default function ServiceDiscoveryPage() {
<ScrapePoolList <ScrapePoolList
poolNames={scrapePools} poolNames={scrapePools}
selectedPool={(limited && scrapePools[0]) || scrapePool || null} selectedPool={(limited && scrapePools[0]) || scrapePool || null}
stateFilter={stateFilter as string[]}
searchFilter={searchFilter} searchFilter={searchFilter}
/> />
</Suspense> </Suspense>

View file

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

View file

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