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:
Galen Kistler 2022-12-08 14:15:05 -06:00 committed by GitHub
parent 76601f3ae7
commit dfc15163da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 101 additions and 31 deletions

View File

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

View File

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

View File

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

View File

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