Service Discovery Page rework (#10131)

* rework the target page

Signed-off-by: Augustin Husson <husson.augustin@gmail.com>

* put back the URL of the endpoint

Signed-off-by: Augustin Husson <husson.augustin@gmail.com>

* replace old code by the new one and change function style

Signed-off-by: Augustin Husson <husson.augustin@gmail.com>

* align filter and search bar on the same row

Signed-off-by: Augustin Husson <husson.augustin@gmail.com>

* remove unnecessary return

Signed-off-by: Augustin Husson <husson.augustin@gmail.com>

* upgrade kvsearch to v0.3.0

Signed-off-by: Augustin Husson <husson.augustin@gmail.com>

* fix unit test

Signed-off-by: Augustin Husson <husson.augustin@gmail.com>

* add missing style on column

Signed-off-by: Augustin Husson <husson.augustin@gmail.com>

* add placeholder and autofocus

Signed-off-by: Augustin Husson <husson.augustin@gmail.com>

* put back the previous table design

Signed-off-by: Augustin Husson <husson.augustin@gmail.com>

* fix issue relative to the position of the tooltip

Signed-off-by: Augustin Husson <husson.augustin@gmail.com>

* fix health filter

Signed-off-by: Augustin Husson <husson.augustin@gmail.com>

* fix test on label tooltip

Signed-off-by: Augustin Husson <husson.augustin@gmail.com>

* simplify filter condition

Signed-off-by: Augustin Husson <husson.augustin@gmail.com>

* rework service discovery page

Signed-off-by: Augustin Husson <husson.augustin@gmail.com>

* introduced generic custom infinite scroll component

Signed-off-by: Augustin Husson <husson.augustin@gmail.com>

* adjust the placeholder in discovery page

Signed-off-by: Augustin Husson <husson.augustin@gmail.com>

* ignore returning type missing

Signed-off-by: Augustin Husson <husson.augustin@gmail.com>

* apply fix required by the review

Signed-off-by: Augustin Husson <husson.augustin@gmail.com>

* index discoveredLabels

Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
This commit is contained in:
Augustin Husson 2022-01-10 17:39:51 +01:00 committed by GitHub
parent 0f4a1e6eac
commit bff9d06874
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 168 additions and 101 deletions

View file

@ -0,0 +1,50 @@
import { ComponentType, useEffect, useState } from 'react';
import InfiniteScroll from 'react-infinite-scroll-component';
const initialNumberOfItemsDisplayed = 50;
export interface InfiniteScrollItemsProps<T> {
items: T[];
}
interface CustomInfiniteScrollProps<T> {
allItems: T[];
child: ComponentType<InfiniteScrollItemsProps<T>>;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
const CustomInfiniteScroll = <T extends unknown>({ allItems, child }: CustomInfiniteScrollProps<T>) => {
const [items, setItems] = useState<T[]>(allItems.slice(0, 50));
const [index, setIndex] = useState<number>(initialNumberOfItemsDisplayed);
const [hasMore, setHasMore] = useState<boolean>(allItems.length > initialNumberOfItemsDisplayed);
const Child = child;
useEffect(() => {
setItems(allItems.slice(0, initialNumberOfItemsDisplayed));
setHasMore(allItems.length > initialNumberOfItemsDisplayed);
}, [allItems]);
const fetchMoreData = () => {
if (items.length === allItems.length) {
setHasMore(false);
} else {
const newIndex = index + initialNumberOfItemsDisplayed;
setIndex(newIndex);
setItems(allItems.slice(0, newIndex));
}
};
return (
<InfiniteScroll
next={fetchMoreData}
hasMore={hasMore}
loader={<h4>loading...</h4>}
dataLength={items.length}
height={items.length > 25 ? '75vh' : ''}
>
<Child items={items} />
</InfiniteScroll>
);
};
export default CustomInfiniteScroll;

View file

@ -2,6 +2,7 @@ import React, { FC, useState } from 'react';
import { Badge, Table } from 'reactstrap'; import { Badge, Table } from 'reactstrap';
import { TargetLabels } from './Services'; import { TargetLabels } from './Services';
import { ToggleMoreLess } from '../../components/ToggleMoreLess'; import { ToggleMoreLess } from '../../components/ToggleMoreLess';
import CustomInfiniteScroll, { InfiniteScrollItemsProps } from '../../components/CustomInfiniteScroll';
interface LabelProps { interface LabelProps {
value: TargetLabels[]; value: TargetLabels[];
@ -20,6 +21,33 @@ const formatLabels = (labels: Record<string, string> | string) => {
}); });
}; };
const LabelsTableContent: FC<InfiniteScrollItemsProps<TargetLabels>> = ({ items }) => {
return (
<Table size="sm" bordered hover striped>
<thead>
<tr>
<th>Discovered Labels</th>
<th>Target Labels</th>
</tr>
</thead>
<tbody>
{items.map((_, i) => {
return (
<tr key={i}>
<td>{formatLabels(items[i].discoveredLabels)}</td>
{items[i].isDropped ? (
<td style={{ fontWeight: 'bold' }}>Dropped</td>
) : (
<td>{formatLabels(items[i].labels)}</td>
)}
</tr>
);
})}
</tbody>
</Table>
);
};
export const LabelsTable: FC<LabelProps> = ({ value, name }) => { export const LabelsTable: FC<LabelProps> = ({ value, name }) => {
const [showMore, setShowMore] = useState(false); const [showMore, setShowMore] = useState(false);
@ -35,30 +63,7 @@ export const LabelsTable: FC<LabelProps> = ({ value, name }) => {
<span className="target-head">{name}</span> <span className="target-head">{name}</span>
</ToggleMoreLess> </ToggleMoreLess>
</div> </div>
{showMore ? ( {showMore ? <CustomInfiniteScroll allItems={value} child={LabelsTableContent} /> : null}
<Table size="sm" bordered hover striped>
<thead>
<tr>
<th>Discovered Labels</th>
<th>Target Labels</th>
</tr>
</thead>
<tbody>
{value.map((_, i) => {
return (
<tr key={i}>
<td>{formatLabels(value[i].discoveredLabels)}</td>
{value[i].isDropped ? (
<td style={{ fontWeight: 'bold' }}>Dropped</td>
) : (
<td>{formatLabels(value[i].labels)}</td>
)}
</tr>
);
})}
</tbody>
</Table>
) : null}
</> </>
); );
}; };

View file

@ -1,4 +1,4 @@
import React, { FC } from 'react'; import React, { ChangeEvent, FC, useEffect, useState } from 'react';
import { useFetch } from '../../hooks/useFetch'; import { useFetch } from '../../hooks/useFetch';
import { LabelsTable } from './LabelsTable'; import { LabelsTable } from './LabelsTable';
import { DroppedTarget, Labels, Target } from '../targets/target'; import { DroppedTarget, Labels, Target } from '../targets/target';
@ -7,6 +7,10 @@ import { withStatusIndicator } from '../../components/withStatusIndicator';
import { mapObjEntries } from '../../utils'; import { mapObjEntries } from '../../utils';
import { usePathPrefix } from '../../contexts/PathPrefixContext'; import { usePathPrefix } from '../../contexts/PathPrefixContext';
import { API_PATH } from '../../constants/constants'; import { API_PATH } from '../../constants/constants';
import { KVSearch } from '@nexucis/kvsearch';
import { Container, Input, InputGroup, InputGroupAddon, InputGroupText } from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSearch } from '@fortawesome/free-solid-svg-icons';
interface ServiceMap { interface ServiceMap {
activeTargets: Target[]; activeTargets: Target[];
@ -19,6 +23,11 @@ export interface TargetLabels {
isDropped: boolean; isDropped: boolean;
} }
const kvSearch = new KVSearch({
shouldSort: true,
indexedKeys: ['labels', 'discoveredLabels', ['discoveredLabels', /.*/], ['labels', /.*/]],
});
export const processSummary = ( export const processSummary = (
activeTargets: Target[], activeTargets: Target[],
droppedTargets: DroppedTarget[] droppedTargets: DroppedTarget[]
@ -82,14 +91,41 @@ export const processTargets = (activeTargets: Target[], droppedTargets: DroppedT
}; };
export const ServiceDiscoveryContent: FC<ServiceMap> = ({ activeTargets, droppedTargets }) => { export const ServiceDiscoveryContent: FC<ServiceMap> = ({ activeTargets, droppedTargets }) => {
const targets = processSummary(activeTargets, droppedTargets); const [activeTargetList, setActiveTargetList] = useState(activeTargets);
const labels = processTargets(activeTargets, droppedTargets); const [targetList, setTargetList] = useState(processSummary(activeTargets, droppedTargets));
const [labelList, setLabelList] = useState(processTargets(activeTargets, droppedTargets));
const handleSearchChange = (e: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
if (e.target.value !== '') {
const result = kvSearch.filter(e.target.value.trim(), activeTargets);
setActiveTargetList(
result.map((value) => {
return value.original as unknown as Target;
})
);
} else {
setActiveTargetList(activeTargets);
}
};
useEffect(() => {
setTargetList(processSummary(activeTargetList, droppedTargets));
setLabelList(processTargets(activeTargetList, droppedTargets));
}, [activeTargetList, droppedTargets]);
return ( return (
<> <>
<h2>Service Discovery</h2> <h2>Service Discovery</h2>
<Container>
<InputGroup>
<InputGroupAddon addonType="prepend">
<InputGroupText>{<FontAwesomeIcon icon={faSearch} />}</InputGroupText>
</InputGroupAddon>
<Input autoFocus onChange={handleSearchChange} placeholder="Filter by labels" />
</InputGroup>
</Container>
<ul> <ul>
{mapObjEntries(targets, ([k, v]) => ( {mapObjEntries(targetList, ([k, v]) => (
<li key={k}> <li key={k}>
<a href={'#' + k}> <a href={'#' + k}>
{k} ({v.active} / {v.total} active targets) {k} ({v.active} / {v.total} active targets)
@ -98,7 +134,7 @@ export const ServiceDiscoveryContent: FC<ServiceMap> = ({ activeTargets, dropped
))} ))}
</ul> </ul>
<hr /> <hr />
{mapObjEntries(labels, ([k, v]) => { {mapObjEntries(labelList, ([k, v]) => {
return <LabelsTable value={v} name={k} key={k} />; return <LabelsTable value={v} name={k} key={k} />;
})} })}
</> </>

View file

@ -1,6 +1,5 @@
import React, { FC, useEffect, useState } from 'react'; import React, { FC } from 'react';
import { getColor, Target } from './target'; import { getColor, Target } from './target';
import InfiniteScroll from 'react-infinite-scroll-component';
import { Badge, Table } from 'reactstrap'; import { Badge, Table } from 'reactstrap';
import TargetLabels from './TargetLabels'; import TargetLabels from './TargetLabels';
import styles from './ScrapePoolPanel.module.css'; import styles from './ScrapePoolPanel.module.css';
@ -8,42 +7,16 @@ import { formatRelative } from '../../utils';
import { now } from 'moment'; import { now } from 'moment';
import TargetScrapeDuration from './TargetScrapeDuration'; import TargetScrapeDuration from './TargetScrapeDuration';
import EndpointLink from './EndpointLink'; import EndpointLink from './EndpointLink';
import CustomInfiniteScroll, { InfiniteScrollItemsProps } from '../../components/CustomInfiniteScroll';
const columns = ['Endpoint', 'State', 'Labels', 'Last Scrape', 'Scrape Duration', 'Error']; const columns = ['Endpoint', 'State', 'Labels', 'Last Scrape', 'Scrape Duration', 'Error'];
const initialNumberOfTargetsDisplayed = 50;
interface ScrapePoolContentProps { interface ScrapePoolContentProps {
targets: Target[]; targets: Target[];
} }
export const ScrapePoolContent: FC<ScrapePoolContentProps> = ({ targets }) => { const ScrapePoolContentTable: FC<InfiniteScrollItemsProps<Target>> = ({ items }) => {
const [items, setItems] = useState<Target[]>(targets.slice(0, 50));
const [index, setIndex] = useState<number>(initialNumberOfTargetsDisplayed);
const [hasMore, setHasMore] = useState<boolean>(targets.length > initialNumberOfTargetsDisplayed);
useEffect(() => {
setItems(targets.slice(0, initialNumberOfTargetsDisplayed));
setHasMore(targets.length > initialNumberOfTargetsDisplayed);
}, [targets]);
const fetchMoreData = () => {
if (items.length === targets.length) {
setHasMore(false);
} else {
const newIndex = index + initialNumberOfTargetsDisplayed;
setIndex(newIndex);
setItems(targets.slice(0, newIndex));
}
};
return ( return (
<InfiniteScroll
next={fetchMoreData}
hasMore={hasMore}
loader={<h4>loading...</h4>}
dataLength={items.length}
height={items.length > 25 ? '75vh' : ''}
>
<Table className={styles.table} size="sm" bordered hover striped> <Table className={styles.table} size="sm" bordered hover striped>
<thead> <thead>
<tr key="header"> <tr key="header">
@ -86,6 +59,9 @@ export const ScrapePoolContent: FC<ScrapePoolContentProps> = ({ targets }) => {
))} ))}
</tbody> </tbody>
</Table> </Table>
</InfiniteScroll>
); );
}; };
export const ScrapePoolContent: FC<ScrapePoolContentProps> = ({ targets }) => {
return <CustomInfiniteScroll allItems={targets} child={ScrapePoolContentTable} />;
};