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>
pull/10146/head
Augustin Husson 3 years ago committed by GitHub
parent 0f4a1e6eac
commit bff9d06874
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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;

@ -2,6 +2,7 @@ import React, { FC, useState } from 'react';
import { Badge, Table } from 'reactstrap';
import { TargetLabels } from './Services';
import { ToggleMoreLess } from '../../components/ToggleMoreLess';
import CustomInfiniteScroll, { InfiniteScrollItemsProps } from '../../components/CustomInfiniteScroll';
interface LabelProps {
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 }) => {
const [showMore, setShowMore] = useState(false);
@ -35,30 +63,7 @@ export const LabelsTable: FC<LabelProps> = ({ value, name }) => {
<span className="target-head">{name}</span>
</ToggleMoreLess>
</div>
{showMore ? (
<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}
{showMore ? <CustomInfiniteScroll allItems={value} child={LabelsTableContent} /> : null}
</>
);
};

@ -1,4 +1,4 @@
import React, { FC } from 'react';
import React, { ChangeEvent, FC, useEffect, useState } from 'react';
import { useFetch } from '../../hooks/useFetch';
import { LabelsTable } from './LabelsTable';
import { DroppedTarget, Labels, Target } from '../targets/target';
@ -7,6 +7,10 @@ import { withStatusIndicator } from '../../components/withStatusIndicator';
import { mapObjEntries } from '../../utils';
import { usePathPrefix } from '../../contexts/PathPrefixContext';
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 {
activeTargets: Target[];
@ -19,6 +23,11 @@ export interface TargetLabels {
isDropped: boolean;
}
const kvSearch = new KVSearch({
shouldSort: true,
indexedKeys: ['labels', 'discoveredLabels', ['discoveredLabels', /.*/], ['labels', /.*/]],
});
export const processSummary = (
activeTargets: Target[],
droppedTargets: DroppedTarget[]
@ -82,14 +91,41 @@ export const processTargets = (activeTargets: Target[], droppedTargets: DroppedT
};
export const ServiceDiscoveryContent: FC<ServiceMap> = ({ activeTargets, droppedTargets }) => {
const targets = processSummary(activeTargets, droppedTargets);
const labels = processTargets(activeTargets, droppedTargets);
const [activeTargetList, setActiveTargetList] = useState(activeTargets);
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 (
<>
<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>
{mapObjEntries(targets, ([k, v]) => (
{mapObjEntries(targetList, ([k, v]) => (
<li key={k}>
<a href={'#' + k}>
{k} ({v.active} / {v.total} active targets)
@ -98,7 +134,7 @@ export const ServiceDiscoveryContent: FC<ServiceMap> = ({ activeTargets, dropped
))}
</ul>
<hr />
{mapObjEntries(labels, ([k, v]) => {
{mapObjEntries(labelList, ([k, v]) => {
return <LabelsTable value={v} name={k} key={k} />;
})}
</>

@ -1,6 +1,5 @@
import React, { FC, useEffect, useState } from 'react';
import React, { FC } from 'react';
import { getColor, Target } from './target';
import InfiniteScroll from 'react-infinite-scroll-component';
import { Badge, Table } from 'reactstrap';
import TargetLabels from './TargetLabels';
import styles from './ScrapePoolPanel.module.css';
@ -8,84 +7,61 @@ import { formatRelative } from '../../utils';
import { now } from 'moment';
import TargetScrapeDuration from './TargetScrapeDuration';
import EndpointLink from './EndpointLink';
import CustomInfiniteScroll, { InfiniteScrollItemsProps } from '../../components/CustomInfiniteScroll';
const columns = ['Endpoint', 'State', 'Labels', 'Last Scrape', 'Scrape Duration', 'Error'];
const initialNumberOfTargetsDisplayed = 50;
interface ScrapePoolContentProps {
targets: Target[];
}
export const ScrapePoolContent: FC<ScrapePoolContentProps> = ({ targets }) => {
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));
}
};
const ScrapePoolContentTable: FC<InfiniteScrollItemsProps<Target>> = ({ items }) => {
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>
<thead>
<tr key="header">
{columns.map((column) => (
<th key={column}>{column}</th>
))}
</tr>
</thead>
<tbody>
{items.map((target, index) => (
<tr key={index}>
<td className={styles.endpoint}>
<EndpointLink endpoint={target.scrapeUrl} globalUrl={target.globalUrl} />
</td>
<td className={styles.state}>
<Badge color={getColor(target.health)}>{target.health.toUpperCase()}</Badge>
</td>
<td className={styles.labels}>
<TargetLabels
discoveredLabels={target.discoveredLabels}
labels={target.labels}
scrapePool={target.scrapePool}
idx={index}
/>
</td>
<td className={styles['last-scrape']}>{formatRelative(target.lastScrape, now())}</td>
<td className={styles['scrape-duration']}>
<TargetScrapeDuration
duration={target.lastScrapeDuration}
scrapePool={target.scrapePool}
idx={index}
interval={target.scrapeInterval}
timeout={target.scrapeTimeout}
/>
</td>
<td className={styles.errors}>
{target.lastError ? <span className="text-danger">{target.lastError}</span> : null}
</td>
</tr>
<Table className={styles.table} size="sm" bordered hover striped>
<thead>
<tr key="header">
{columns.map((column) => (
<th key={column}>{column}</th>
))}
</tbody>
</Table>
</InfiniteScroll>
</tr>
</thead>
<tbody>
{items.map((target, index) => (
<tr key={index}>
<td className={styles.endpoint}>
<EndpointLink endpoint={target.scrapeUrl} globalUrl={target.globalUrl} />
</td>
<td className={styles.state}>
<Badge color={getColor(target.health)}>{target.health.toUpperCase()}</Badge>
</td>
<td className={styles.labels}>
<TargetLabels
discoveredLabels={target.discoveredLabels}
labels={target.labels}
scrapePool={target.scrapePool}
idx={index}
/>
</td>
<td className={styles['last-scrape']}>{formatRelative(target.lastScrape, now())}</td>
<td className={styles['scrape-duration']}>
<TargetScrapeDuration
duration={target.lastScrapeDuration}
scrapePool={target.scrapePool}
idx={index}
interval={target.scrapeInterval}
timeout={target.scrapeTimeout}
/>
</td>
<td className={styles.errors}>
{target.lastError ? <span className="text-danger">{target.lastError}</span> : null}
</td>
</tr>
))}
</tbody>
</Table>
);
};
export const ScrapePoolContent: FC<ScrapePoolContentProps> = ({ targets }) => {
return <CustomInfiniteScroll allItems={targets} child={ScrapePoolContentTable} />;
};

Loading…
Cancel
Save