mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Prometheus: Fix exemplar fill color to match series color in time series. (#59908)
* Implementing new optional color property to ExemplarMarker component * Refactor some changes from #59743
This commit is contained in:
parent
76601f3ae7
commit
dfc15163da
@ -133,7 +133,7 @@ export const TimeSeriesPanel: React.FC<TimeSeriesPanelProps> = ({
|
||||
)}
|
||||
{data.annotations && (
|
||||
<ExemplarsPlugin
|
||||
visibleLabels={getVisibleLabels(config, frames)}
|
||||
visibleSeries={getVisibleLabels(config, frames)}
|
||||
config={config}
|
||||
exemplars={data.annotations}
|
||||
timeZone={timeZone}
|
||||
|
@ -22,6 +22,7 @@ interface ExemplarMarkerProps {
|
||||
dataFrameFieldIndex: DataFrameFieldIndex;
|
||||
config: UPlotConfigBuilder;
|
||||
getFieldLinks: (field: Field, rowIndex: number) => Array<LinkModel<Field>>;
|
||||
exemplarColor?: string;
|
||||
}
|
||||
|
||||
export const ExemplarMarker: React.FC<ExemplarMarkerProps> = ({
|
||||
@ -30,6 +31,7 @@ export const ExemplarMarker: React.FC<ExemplarMarkerProps> = ({
|
||||
dataFrameFieldIndex,
|
||||
config,
|
||||
getFieldLinks,
|
||||
exemplarColor,
|
||||
}) => {
|
||||
const styles = useStyles2(getExemplarMarkerStyles);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
@ -40,19 +42,33 @@ export const ExemplarMarker: React.FC<ExemplarMarkerProps> = ({
|
||||
|
||||
const getSymbol = () => {
|
||||
const symbols = [
|
||||
<rect key="diamond" x="3.38672" width="4.78985" height="4.78985" transform="rotate(45 3.38672 0)" />,
|
||||
<rect
|
||||
fill={exemplarColor}
|
||||
key="diamond"
|
||||
x="3.38672"
|
||||
width="4.78985"
|
||||
height="4.78985"
|
||||
transform="rotate(45 3.38672 0)"
|
||||
/>,
|
||||
<path
|
||||
fill={exemplarColor}
|
||||
key="x"
|
||||
d="M1.94444 3.49988L0 5.44432L1.55552 6.99984L3.49996 5.05539L5.4444 6.99983L6.99992 5.44431L5.05548 3.49988L6.99983 1.55552L5.44431 0L3.49996 1.94436L1.5556 0L8.42584e-05 1.55552L1.94444 3.49988Z"
|
||||
/>,
|
||||
<path key="triangle" d="M4 0L7.4641 6H0.535898L4 0Z" />,
|
||||
<rect key="rectangle" width="5" height="5" />,
|
||||
<path key="pentagon" d="M3 0.5L5.85317 2.57295L4.76336 5.92705H1.23664L0.146831 2.57295L3 0.5Z" />,
|
||||
<path fill={exemplarColor} key="triangle" d="M4 0L7.4641 6H0.535898L4 0Z" />,
|
||||
<rect fill={exemplarColor} key="rectangle" width="5" height="5" />,
|
||||
<path
|
||||
fill={exemplarColor}
|
||||
key="pentagon"
|
||||
d="M3 0.5L5.85317 2.57295L4.76336 5.92705H1.23664L0.146831 2.57295L3 0.5Z"
|
||||
/>,
|
||||
<path
|
||||
fill={exemplarColor}
|
||||
key="plus"
|
||||
d="m2.35672,4.2425l0,2.357l1.88558,0l0,-2.357l2.3572,0l0,-1.88558l-2.3572,0l0,-2.35692l-1.88558,0l0,2.35692l-2.35672,0l0,1.88558l2.35672,0z"
|
||||
/>,
|
||||
];
|
||||
|
||||
return symbols[dataFrameFieldIndex.frameIndex % symbols.length];
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Field, Labels, MutableDataFrame } from '@grafana/data/src';
|
||||
import { MutableDataFrame, Field } from '@grafana/data/src';
|
||||
import { UPlotConfigBuilder } from '@grafana/ui/src';
|
||||
|
||||
import { getVisibleLabels } from './ExemplarsPlugin';
|
||||
import { getVisibleLabels, VisibleExemplarLabels } from './ExemplarsPlugin';
|
||||
|
||||
describe('getVisibleLabels()', () => {
|
||||
const dataFrameSeries1 = new MutableDataFrame({
|
||||
@ -84,9 +84,22 @@ describe('getVisibleLabels()', () => {
|
||||
} as UPlotConfigBuilder;
|
||||
|
||||
it('function should only return labels associated with actively visible series', () => {
|
||||
const expected: { labels: Labels[]; totalSeriesCount: number } = {
|
||||
const expected: VisibleExemplarLabels = {
|
||||
totalSeriesCount: 3,
|
||||
labels: [{ job: 'tns/app' }, { job: 'tns/db' }],
|
||||
labels: [
|
||||
{
|
||||
color: '',
|
||||
labels: {
|
||||
job: 'tns/app',
|
||||
},
|
||||
},
|
||||
{
|
||||
color: '',
|
||||
labels: {
|
||||
job: 'tns/db',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// Base case
|
||||
|
@ -20,7 +20,7 @@ interface ExemplarsPluginProps {
|
||||
exemplars: DataFrame[];
|
||||
timeZone: TimeZone;
|
||||
getFieldLinks: (field: Field, rowIndex: number) => Array<LinkModel<Field>>;
|
||||
visibleLabels?: { labels: Labels[]; totalSeriesCount: number };
|
||||
visibleSeries?: VisibleExemplarLabels;
|
||||
}
|
||||
|
||||
export const ExemplarsPlugin: React.FC<ExemplarsPluginProps> = ({
|
||||
@ -28,7 +28,7 @@ export const ExemplarsPlugin: React.FC<ExemplarsPluginProps> = ({
|
||||
timeZone,
|
||||
getFieldLinks,
|
||||
config,
|
||||
visibleLabels,
|
||||
visibleSeries,
|
||||
}) => {
|
||||
const plotInstance = useRef<uPlot>();
|
||||
|
||||
@ -71,9 +71,11 @@ export const ExemplarsPlugin: React.FC<ExemplarsPluginProps> = ({
|
||||
|
||||
const renderMarker = useCallback(
|
||||
(dataFrame: DataFrame, dataFrameFieldIndex: DataFrameFieldIndex) => {
|
||||
// If the parent provided series/labels: filter the exemplars, otherwise default to show all exemplars
|
||||
let showMarker =
|
||||
visibleLabels !== undefined ? showExemplarMarker(visibleLabels, dataFrame, dataFrameFieldIndex) : true;
|
||||
const showMarker =
|
||||
visibleSeries !== undefined ? showExemplarMarker(visibleSeries, dataFrame, dataFrameFieldIndex) : true;
|
||||
|
||||
const markerColor =
|
||||
visibleSeries !== undefined ? getExemplarColor(dataFrame, dataFrameFieldIndex, visibleSeries) : undefined;
|
||||
|
||||
if (!showMarker) {
|
||||
return <></>;
|
||||
@ -86,10 +88,11 @@ export const ExemplarsPlugin: React.FC<ExemplarsPluginProps> = ({
|
||||
dataFrame={dataFrame}
|
||||
dataFrameFieldIndex={dataFrameFieldIndex}
|
||||
config={config}
|
||||
exemplarColor={markerColor}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[config, timeZone, getFieldLinks, visibleLabels]
|
||||
[config, timeZone, getFieldLinks, visibleSeries]
|
||||
);
|
||||
|
||||
return (
|
||||
@ -103,15 +106,13 @@ export const ExemplarsPlugin: React.FC<ExemplarsPluginProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export type VisibleExemplarLabels = { labels: LabelWithExemplarUIData[]; totalSeriesCount: number };
|
||||
/**
|
||||
* Function to get labels that are currently displayed in the legend
|
||||
* Get labels that are currently visible/active in the legend
|
||||
*/
|
||||
export const getVisibleLabels = (
|
||||
config: UPlotConfigBuilder,
|
||||
frames: DataFrame[] | null
|
||||
): { labels: Labels[]; totalSeriesCount: number } => {
|
||||
export const getVisibleLabels = (config: UPlotConfigBuilder, frames: DataFrame[] | null): VisibleExemplarLabels => {
|
||||
const visibleSeries = config.series.filter((series) => series.props.show);
|
||||
const visibleLabels: Labels[] = [];
|
||||
const visibleLabels: LabelWithExemplarUIData[] = [];
|
||||
if (frames?.length) {
|
||||
visibleSeries.forEach((plotInstance) => {
|
||||
const frameIndex = plotInstance.props?.dataFrameFieldIndex?.frameIndex;
|
||||
@ -121,7 +122,10 @@ export const getVisibleLabels = (
|
||||
const field = frames[frameIndex].fields[fieldIndex];
|
||||
if (field.labels) {
|
||||
// Note that this may be an empty object in the case of a metric being rendered with no labels
|
||||
visibleLabels.push(field.labels);
|
||||
visibleLabels.push({
|
||||
labels: field.labels,
|
||||
color: plotInstance.props?.lineColor ?? '',
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -130,22 +134,59 @@ export const getVisibleLabels = (
|
||||
return { labels: visibleLabels, totalSeriesCount: config.series.length };
|
||||
};
|
||||
|
||||
interface LabelWithExemplarUIData {
|
||||
labels: Labels;
|
||||
color?: string;
|
||||
}
|
||||
/**
|
||||
* Get color of active series in legend
|
||||
*/
|
||||
const getExemplarColor = (
|
||||
dataFrame: DataFrame,
|
||||
dataFrameFieldIndex: DataFrameFieldIndex,
|
||||
visibleLabels: VisibleExemplarLabels
|
||||
) => {
|
||||
let exemplarColor;
|
||||
visibleLabels.labels.some((visibleLabel) => {
|
||||
const labelKeys = Object.keys(visibleLabel.labels);
|
||||
const fields = dataFrame.fields.filter((field) => {
|
||||
return labelKeys.find((labelKey) => labelKey === field.name);
|
||||
});
|
||||
if (fields.length) {
|
||||
const hasMatch = fields.every((field, index, fields) => {
|
||||
const value = field.values.get(dataFrameFieldIndex.fieldIndex);
|
||||
return visibleLabel.labels[field.name] === value;
|
||||
});
|
||||
|
||||
if (hasMatch) {
|
||||
exemplarColor = visibleLabel.color;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return exemplarColor;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine if the current exemplar marker is filtered by what series are selected in the legend UI
|
||||
*/
|
||||
const showExemplarMarker = (
|
||||
visibleLabels: { labels: Labels[]; totalSeriesCount: number },
|
||||
visibleSeries: VisibleExemplarLabels,
|
||||
dataFrame: DataFrame,
|
||||
dataFrameFieldIndex: DataFrameFieldIndex
|
||||
) => {
|
||||
let showMarker = false;
|
||||
if (visibleLabels.labels.length === visibleLabels.totalSeriesCount) {
|
||||
// If all series are visible, don't filter any exemplars
|
||||
if (visibleSeries.labels.length === visibleSeries.totalSeriesCount) {
|
||||
showMarker = true;
|
||||
} else {
|
||||
visibleLabels.labels.forEach((visibleLabel) => {
|
||||
const labelKeys = Object.keys(visibleLabel);
|
||||
visibleSeries.labels.forEach((visibleLabel) => {
|
||||
// Get the label names
|
||||
const labelKeys = Object.keys(visibleLabel.labels);
|
||||
|
||||
// If there aren't any labels, the graph is only displaying a single series with exemplars, let's show all exemplars in this case as well
|
||||
if (Object.keys(visibleLabel).length === 0) {
|
||||
if (Object.keys(visibleLabel.labels).length === 0) {
|
||||
showMarker = true;
|
||||
} else {
|
||||
// If there are labels, lets only show the exemplars with labels associated with series that are currently visible
|
||||
@ -153,11 +194,11 @@ const showExemplarMarker = (
|
||||
return labelKeys.find((labelKey) => labelKey === field.name);
|
||||
});
|
||||
|
||||
// Check to see if at least one value matches each field
|
||||
if (fields.length) {
|
||||
showMarker = visibleLabels.labels.some((series) => {
|
||||
return Object.keys(series).every((label) => {
|
||||
const value = series[label];
|
||||
// Check to see if at least one value matches each field
|
||||
showMarker = visibleSeries.labels.some((series) => {
|
||||
return Object.keys(series.labels).every((label) => {
|
||||
const value = series.labels[label];
|
||||
return fields.find((field) => field.values.get(dataFrameFieldIndex.fieldIndex) === value);
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user