mirror of https://github.com/prometheus/prometheus
Browse Source
* 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>pull/10144/head
Augustin Husson
3 years ago
committed by
GitHub
10 changed files with 258 additions and 265 deletions
@ -0,0 +1,91 @@ |
|||||||
|
import React, { FC, useEffect, useState } 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'; |
||||||
|
import { formatRelative } from '../../utils'; |
||||||
|
import { now } from 'moment'; |
||||||
|
import TargetScrapeDuration from './TargetScrapeDuration'; |
||||||
|
import EndpointLink from './EndpointLink'; |
||||||
|
|
||||||
|
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)); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
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> |
||||||
|
))} |
||||||
|
</tbody> |
||||||
|
</Table> |
||||||
|
</InfiniteScroll> |
||||||
|
); |
||||||
|
}; |
@ -1,137 +0,0 @@ |
|||||||
import React from 'react'; |
|
||||||
import { mount, shallow } from 'enzyme'; |
|
||||||
import { targetGroups } from './__testdata__/testdata'; |
|
||||||
import ScrapePoolPanel, { columns } from './ScrapePoolPanel'; |
|
||||||
import { Button, Collapse, Table, Badge } from 'reactstrap'; |
|
||||||
import { Target, getColor } from './target'; |
|
||||||
import EndpointLink from './EndpointLink'; |
|
||||||
import TargetLabels from './TargetLabels'; |
|
||||||
import sinon from 'sinon'; |
|
||||||
|
|
||||||
describe('ScrapePoolPanel', () => { |
|
||||||
const defaultProps = { |
|
||||||
scrapePool: 'blackbox', |
|
||||||
targetGroup: targetGroups.blackbox, |
|
||||||
expanded: true, |
|
||||||
toggleExpanded: sinon.spy(), |
|
||||||
}; |
|
||||||
const scrapePoolPanel = shallow(<ScrapePoolPanel {...defaultProps} />); |
|
||||||
|
|
||||||
it('renders a container', () => { |
|
||||||
const div = scrapePoolPanel.find('div').filterWhere((elem) => elem.hasClass('container')); |
|
||||||
expect(div).toHaveLength(1); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('Header', () => { |
|
||||||
it('renders an anchor with up count and danger color if upCount < targetsCount', () => { |
|
||||||
const anchor = scrapePoolPanel.find('a'); |
|
||||||
expect(anchor).toHaveLength(1); |
|
||||||
expect(anchor.prop('id')).toEqual('pool-blackbox'); |
|
||||||
expect(anchor.prop('href')).toEqual('#pool-blackbox'); |
|
||||||
expect(anchor.text()).toEqual('blackbox (2/3 up)'); |
|
||||||
expect(anchor.prop('className')).toEqual('danger'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('renders an anchor with up count and normal color if upCount == targetsCount', () => { |
|
||||||
const props = { |
|
||||||
...defaultProps, |
|
||||||
scrapePool: 'prometheus', |
|
||||||
targetGroup: targetGroups.prometheus, |
|
||||||
}; |
|
||||||
const scrapePoolPanel = shallow(<ScrapePoolPanel {...props} />); |
|
||||||
const anchor = scrapePoolPanel.find('a'); |
|
||||||
expect(anchor).toHaveLength(1); |
|
||||||
expect(anchor.prop('id')).toEqual('pool-prometheus'); |
|
||||||
expect(anchor.prop('href')).toEqual('#pool-prometheus'); |
|
||||||
expect(anchor.text()).toEqual('prometheus (1/1 up)'); |
|
||||||
expect(anchor.prop('className')).toEqual('normal'); |
|
||||||
}); |
|
||||||
|
|
||||||
it('renders a show more btn if collapsed', () => { |
|
||||||
const props = { |
|
||||||
...defaultProps, |
|
||||||
scrapePool: 'prometheus', |
|
||||||
targetGroup: targetGroups.prometheus, |
|
||||||
toggleExpanded: sinon.spy(), |
|
||||||
}; |
|
||||||
const div = document.createElement('div'); |
|
||||||
div.id = `series-labels-prometheus-0`; |
|
||||||
document.body.appendChild(div); |
|
||||||
const div2 = document.createElement('div'); |
|
||||||
div2.id = `scrape-duration-prometheus-0`; |
|
||||||
document.body.appendChild(div2); |
|
||||||
const scrapePoolPanel = mount(<ScrapePoolPanel {...props} />); |
|
||||||
|
|
||||||
const btn = scrapePoolPanel.find(Button); |
|
||||||
btn.simulate('click'); |
|
||||||
expect(props.toggleExpanded.calledOnce).toBe(true); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
it('renders a Collapse component', () => { |
|
||||||
const collapse = scrapePoolPanel.find(Collapse); |
|
||||||
expect(collapse.prop('isOpen')).toBe(true); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('Table', () => { |
|
||||||
it('renders a table', () => { |
|
||||||
const table = scrapePoolPanel.find(Table); |
|
||||||
const headers = table.find('th'); |
|
||||||
expect(table).toHaveLength(1); |
|
||||||
expect(headers).toHaveLength(6); |
|
||||||
columns.forEach((col) => { |
|
||||||
expect(headers.contains(col)); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('for each target', () => { |
|
||||||
const table = scrapePoolPanel.find(Table); |
|
||||||
defaultProps.targetGroup.targets.forEach( |
|
||||||
({ discoveredLabels, labels, scrapeUrl, lastError, health }: Target, idx: number) => { |
|
||||||
const row = table.find('tr').at(idx + 1); |
|
||||||
|
|
||||||
it('renders an EndpointLink with the scrapeUrl', () => { |
|
||||||
const link = row.find(EndpointLink); |
|
||||||
expect(link).toHaveLength(1); |
|
||||||
expect(link.prop('endpoint')).toEqual(scrapeUrl); |
|
||||||
}); |
|
||||||
|
|
||||||
it('renders a badge for health', () => { |
|
||||||
const td = row.find('td').filterWhere((elem) => Boolean(elem.hasClass('state'))); |
|
||||||
const badge = td.find(Badge); |
|
||||||
expect(badge).toHaveLength(1); |
|
||||||
expect(badge.prop('color')).toEqual(getColor(health)); |
|
||||||
expect(badge.children().text()).toEqual(health.toUpperCase()); |
|
||||||
}); |
|
||||||
|
|
||||||
it('renders series labels', () => { |
|
||||||
const targetLabels = row.find(TargetLabels); |
|
||||||
expect(targetLabels).toHaveLength(1); |
|
||||||
expect(targetLabels.prop('discoveredLabels')).toEqual(discoveredLabels); |
|
||||||
expect(targetLabels.prop('labels')).toEqual(labels); |
|
||||||
}); |
|
||||||
|
|
||||||
it('renders last scrape time', () => { |
|
||||||
const lastScrapeCell = row.find('td').filterWhere((elem) => Boolean(elem.hasClass('last-scrape'))); |
|
||||||
expect(lastScrapeCell).toHaveLength(1); |
|
||||||
}); |
|
||||||
|
|
||||||
it('renders last scrape duration', () => { |
|
||||||
const lastScrapeCell = row.find('td').filterWhere((elem) => Boolean(elem.hasClass('scrape-duration'))); |
|
||||||
expect(lastScrapeCell).toHaveLength(1); |
|
||||||
}); |
|
||||||
|
|
||||||
it('renders a badge for Errors', () => { |
|
||||||
const td = row.find('td').filterWhere((elem) => Boolean(elem.hasClass('errors'))); |
|
||||||
const badge = td.find(Badge); |
|
||||||
expect(badge).toHaveLength(lastError ? 1 : 0); |
|
||||||
if (lastError) { |
|
||||||
expect(badge.prop('color')).toEqual('danger'); |
|
||||||
expect(badge.children().text()).toEqual(lastError); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
@ -1,95 +0,0 @@ |
|||||||
import React, { FC } from 'react'; |
|
||||||
import { ScrapePool, getColor } from './target'; |
|
||||||
import { Collapse, Table, Badge } from 'reactstrap'; |
|
||||||
import styles from './ScrapePoolPanel.module.css'; |
|
||||||
import { Target } from './target'; |
|
||||||
import EndpointLink from './EndpointLink'; |
|
||||||
import TargetLabels from './TargetLabels'; |
|
||||||
import TargetScrapeDuration from './TargetScrapeDuration'; |
|
||||||
import { now } from 'moment'; |
|
||||||
import { ToggleMoreLess } from '../../components/ToggleMoreLess'; |
|
||||||
import { formatRelative } from '../../utils'; |
|
||||||
|
|
||||||
interface PanelProps { |
|
||||||
scrapePool: string; |
|
||||||
targetGroup: ScrapePool; |
|
||||||
expanded: boolean; |
|
||||||
toggleExpanded: () => void; |
|
||||||
} |
|
||||||
|
|
||||||
export const columns = ['Endpoint', 'State', 'Labels', 'Last Scrape', 'Scrape Duration', 'Error']; |
|
||||||
|
|
||||||
const ScrapePoolPanel: FC<PanelProps> = ({ scrapePool, targetGroup, expanded, toggleExpanded }) => { |
|
||||||
const modifier = targetGroup.upCount < targetGroup.targets.length ? 'danger' : 'normal'; |
|
||||||
const id = `pool-${scrapePool}`; |
|
||||||
const anchorProps = { |
|
||||||
href: `#${id}`, |
|
||||||
id, |
|
||||||
}; |
|
||||||
|
|
||||||
return ( |
|
||||||
<div className={styles.container}> |
|
||||||
<ToggleMoreLess event={toggleExpanded} showMore={expanded}> |
|
||||||
<a className={styles[modifier]} {...anchorProps}> |
|
||||||
{`${scrapePool} (${targetGroup.upCount}/${targetGroup.targets.length} up)`} |
|
||||||
</a> |
|
||||||
</ToggleMoreLess> |
|
||||||
<Collapse isOpen={expanded}> |
|
||||||
<Table className={styles.table} size="sm" bordered hover striped> |
|
||||||
<thead> |
|
||||||
<tr key="header"> |
|
||||||
{columns.map((column) => ( |
|
||||||
<th key={column}>{column}</th> |
|
||||||
))} |
|
||||||
</tr> |
|
||||||
</thead> |
|
||||||
<tbody> |
|
||||||
{targetGroup.targets.map((target: Target, idx: number) => { |
|
||||||
const { |
|
||||||
discoveredLabels, |
|
||||||
labels, |
|
||||||
scrapePool, |
|
||||||
scrapeUrl, |
|
||||||
globalUrl, |
|
||||||
lastError, |
|
||||||
lastScrape, |
|
||||||
lastScrapeDuration, |
|
||||||
health, |
|
||||||
scrapeInterval, |
|
||||||
scrapeTimeout, |
|
||||||
} = target; |
|
||||||
const color = getColor(health); |
|
||||||
|
|
||||||
return ( |
|
||||||
<tr key={scrapeUrl}> |
|
||||||
<td className={styles.endpoint}> |
|
||||||
<EndpointLink endpoint={scrapeUrl} globalUrl={globalUrl} /> |
|
||||||
</td> |
|
||||||
<td className={styles.state}> |
|
||||||
<Badge color={color}>{health.toUpperCase()}</Badge> |
|
||||||
</td> |
|
||||||
<td className={styles.labels}> |
|
||||||
<TargetLabels discoveredLabels={discoveredLabels} labels={labels} scrapePool={scrapePool} idx={idx} /> |
|
||||||
</td> |
|
||||||
<td className={styles['last-scrape']}>{formatRelative(lastScrape, now())}</td> |
|
||||||
<td className={styles['scrape-duration']}> |
|
||||||
<TargetScrapeDuration |
|
||||||
duration={lastScrapeDuration} |
|
||||||
scrapePool={scrapePool} |
|
||||||
idx={idx} |
|
||||||
interval={scrapeInterval} |
|
||||||
timeout={scrapeTimeout} |
|
||||||
/> |
|
||||||
</td> |
|
||||||
<td className={styles.errors}>{lastError ? <span className="text-danger">{lastError}</span> : null}</td> |
|
||||||
</tr> |
|
||||||
); |
|
||||||
})} |
|
||||||
</tbody> |
|
||||||
</Table> |
|
||||||
</Collapse> |
|
||||||
</div> |
|
||||||
); |
|
||||||
}; |
|
||||||
|
|
||||||
export default ScrapePoolPanel; |
|
Loading…
Reference in new issue