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