Move /targets page discovered labels to expandable section (#12824)

* Move /targets page discovered labels to expandable section

The current tooltip for showing the pre-relabeling discovered labels for a
target is notoriously unreliable and can get cut off when there are many
labels. This PR introduces a (hopefully unobtuse enough) expander/collapser
button for the discovered labels of each target, and then the discovered labels
are shown in a more persistent way underneath the final target labels, instead
of using a tooltip.

Fixes https://github.com/prometheus/prometheus/issues/9175#issuecomment-1713074341

Signed-off-by: Julius Volz <julius.volz@gmail.com>

* Remove obsolete test snapshot

Signed-off-by: Julius Volz <julius.volz@gmail.com>

---------

Signed-off-by: Julius Volz <julius.volz@gmail.com>
This commit is contained in:
Julius Volz 2023-09-11 11:36:44 +02:00 committed by GitHub
parent 7c75d233d0
commit aa7bf083e9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 29 additions and 133 deletions

View file

@ -35,12 +35,7 @@ const ScrapePoolContentTable: FC<InfiniteScrollItemsProps<Target>> = ({ items })
<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}
/>
<TargetLabels discoveredLabels={target.discoveredLabels} labels={target.labels} />
</td>
<td className={styles['last-scrape']}>{formatRelative(target.lastScrape, now())}</td>
<td className={styles['scrape-duration']}>

View file

@ -1,3 +0,0 @@
.discovered {
white-space: nowrap;
}

View file

@ -17,15 +17,12 @@ describe('targetLabels', () => {
job: 'node_exporter',
foo: 'bar',
},
idx: 1,
scrapePool: 'cortex/node-exporter_group/0',
};
const targetLabels = shallow(<TargetLabels {...defaultProps} />);
it('renders a div of series labels', () => {
const div = targetLabels.find('div').filterWhere((elem) => elem.hasClass('series-labels-container'));
expect(div).toHaveLength(1);
expect(div.prop('id')).toEqual('series-labels-cortex/node-exporter_group/0-1');
});
it('wraps each label in a label badge', () => {
@ -38,15 +35,4 @@ describe('targetLabels', () => {
});
expect(targetLabels.find(Badge)).toHaveLength(3);
});
it('renders a tooltip for discovered labels', () => {
const tooltip = targetLabels.find(Tooltip);
expect(tooltip).toHaveLength(1);
expect(tooltip.prop('isOpen')).toBe(false);
expect(tooltip.prop('target')).toEqual('series-labels-cortex\\/node-exporter_group\\/0-1');
});
it('renders discovered labels', () => {
expect(toJson(targetLabels)).toMatchSnapshot();
});
});

View file

@ -1,7 +1,7 @@
import React, { FC, Fragment, useState } from 'react';
import { Badge, Tooltip } from 'reactstrap';
import 'css.escape';
import styles from './TargetLabels.module.css';
import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import React, { FC, useState } from 'react';
import { Badge, Button } from 'reactstrap';
interface Labels {
[key: string]: string;
@ -10,21 +10,14 @@ interface Labels {
export interface TargetLabelsProps {
discoveredLabels: Labels;
labels: Labels;
idx: number;
scrapePool: string;
}
const formatLabels = (labels: Labels): string[] => Object.keys(labels).map((key) => `${key}="${labels[key]}"`);
const TargetLabels: FC<TargetLabelsProps> = ({ discoveredLabels, labels, idx, scrapePool }) => {
const [tooltipOpen, setTooltipOpen] = useState(false);
const toggle = (): void => setTooltipOpen(!tooltipOpen);
const id = `series-labels-${scrapePool}-${idx}`;
const TargetLabels: FC<TargetLabelsProps> = ({ discoveredLabels, labels }) => {
const [showDiscovered, setShowDiscovered] = useState(false);
return (
<>
<div id={id} className="series-labels-container">
<div className="series-labels-container">
{Object.keys(labels).map((labelName) => {
return (
<Badge color="primary" className="mr-1" key={labelName}>
@ -32,22 +25,28 @@ const TargetLabels: FC<TargetLabelsProps> = ({ discoveredLabels, labels, idx, sc
</Badge>
);
})}
<Button
size="sm"
color="link"
title={`${showDiscovered ? 'Hide' : 'Show'} discovered (pre-relabeling) labels`}
onClick={() => setShowDiscovered(!showDiscovered)}
style={{ fontSize: '0.8rem' }}
>
<FontAwesomeIcon icon={showDiscovered ? faChevronUp : faChevronDown} />
</Button>
</div>
<Tooltip
isOpen={tooltipOpen}
target={CSS.escape(id)}
toggle={toggle}
placement={'right-end'}
style={{ maxWidth: 'none', textAlign: 'left' }}
>
<b>Before relabeling:</b>
{formatLabels(discoveredLabels).map((s: string, labelIndex: number) => (
<Fragment key={labelIndex}>
<br />
<span className={styles.discovered}>{s}</span>
</Fragment>
))}
</Tooltip>
{showDiscovered && (
<>
<div className="mt-3 font-weight-bold">Discovered labels:</div>
{Object.keys(discoveredLabels).map((labelName) => (
<div key={labelName}>
<Badge color="info" className="mr-1">
{`${labelName}="${discoveredLabels[labelName]}"`}
</Badge>
</div>
))}
</>
)}
</>
);
};

View file

@ -1,81 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`targetLabels renders discovered labels 1`] = `
<Fragment>
<div
className="series-labels-container"
id="series-labels-cortex/node-exporter_group/0-1"
>
<Badge
className="mr-1"
color="primary"
key="instance"
pill={false}
tag="span"
>
instance="localhost:9100"
</Badge>
<Badge
className="mr-1"
color="primary"
key="job"
pill={false}
tag="span"
>
job="node_exporter"
</Badge>
<Badge
className="mr-1"
color="primary"
key="foo"
pill={false}
tag="span"
>
foo="bar"
</Badge>
</div>
<Tooltip
autohide={true}
isOpen={false}
placement="right-end"
placementPrefix="bs-tooltip"
style={
Object {
"maxWidth": "none",
"textAlign": "left",
}
}
target="series-labels-cortex\\\\/node-exporter_group\\\\/0-1"
toggle={[Function]}
trigger="hover focus"
>
<b>
Before relabeling:
</b>
<br />
<span
className="discovered"
>
__address__="localhost:9100"
</span>
<br />
<span
className="discovered"
>
__metrics_path__="/metrics"
</span>
<br />
<span
className="discovered"
>
__scheme__="http"
</span>
<br />
<span
className="discovered"
>
job="node_exporter"
</span>
</Tooltip>
</Fragment>
`;