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 && (
|
{data.annotations && (
|
||||||
<ExemplarsPlugin
|
<ExemplarsPlugin
|
||||||
visibleLabels={getVisibleLabels(config, frames)}
|
visibleSeries={getVisibleLabels(config, frames)}
|
||||||
config={config}
|
config={config}
|
||||||
exemplars={data.annotations}
|
exemplars={data.annotations}
|
||||||
timeZone={timeZone}
|
timeZone={timeZone}
|
||||||
|
@ -22,6 +22,7 @@ interface ExemplarMarkerProps {
|
|||||||
dataFrameFieldIndex: DataFrameFieldIndex;
|
dataFrameFieldIndex: DataFrameFieldIndex;
|
||||||
config: UPlotConfigBuilder;
|
config: UPlotConfigBuilder;
|
||||||
getFieldLinks: (field: Field, rowIndex: number) => Array<LinkModel<Field>>;
|
getFieldLinks: (field: Field, rowIndex: number) => Array<LinkModel<Field>>;
|
||||||
|
exemplarColor?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ExemplarMarker: React.FC<ExemplarMarkerProps> = ({
|
export const ExemplarMarker: React.FC<ExemplarMarkerProps> = ({
|
||||||
@ -30,6 +31,7 @@ export const ExemplarMarker: React.FC<ExemplarMarkerProps> = ({
|
|||||||
dataFrameFieldIndex,
|
dataFrameFieldIndex,
|
||||||
config,
|
config,
|
||||||
getFieldLinks,
|
getFieldLinks,
|
||||||
|
exemplarColor,
|
||||||
}) => {
|
}) => {
|
||||||
const styles = useStyles2(getExemplarMarkerStyles);
|
const styles = useStyles2(getExemplarMarkerStyles);
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
@ -40,19 +42,33 @@ export const ExemplarMarker: React.FC<ExemplarMarkerProps> = ({
|
|||||||
|
|
||||||
const getSymbol = () => {
|
const getSymbol = () => {
|
||||||
const symbols = [
|
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
|
<path
|
||||||
|
fill={exemplarColor}
|
||||||
key="x"
|
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"
|
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" />,
|
<path fill={exemplarColor} key="triangle" d="M4 0L7.4641 6H0.535898L4 0Z" />,
|
||||||
<rect key="rectangle" width="5" height="5" />,
|
<rect fill={exemplarColor} 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
|
<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"
|
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"
|
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];
|
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 { UPlotConfigBuilder } from '@grafana/ui/src';
|
||||||
|
|
||||||
import { getVisibleLabels } from './ExemplarsPlugin';
|
import { getVisibleLabels, VisibleExemplarLabels } from './ExemplarsPlugin';
|
||||||
|
|
||||||
describe('getVisibleLabels()', () => {
|
describe('getVisibleLabels()', () => {
|
||||||
const dataFrameSeries1 = new MutableDataFrame({
|
const dataFrameSeries1 = new MutableDataFrame({
|
||||||
@ -84,9 +84,22 @@ describe('getVisibleLabels()', () => {
|
|||||||
} as UPlotConfigBuilder;
|
} as UPlotConfigBuilder;
|
||||||
|
|
||||||
it('function should only return labels associated with actively visible series', () => {
|
it('function should only return labels associated with actively visible series', () => {
|
||||||
const expected: { labels: Labels[]; totalSeriesCount: number } = {
|
const expected: VisibleExemplarLabels = {
|
||||||
totalSeriesCount: 3,
|
totalSeriesCount: 3,
|
||||||
labels: [{ job: 'tns/app' }, { job: 'tns/db' }],
|
labels: [
|
||||||
|
{
|
||||||
|
color: '',
|
||||||
|
labels: {
|
||||||
|
job: 'tns/app',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: '',
|
||||||
|
labels: {
|
||||||
|
job: 'tns/db',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Base case
|
// Base case
|
||||||
|
@ -20,7 +20,7 @@ interface ExemplarsPluginProps {
|
|||||||
exemplars: DataFrame[];
|
exemplars: DataFrame[];
|
||||||
timeZone: TimeZone;
|
timeZone: TimeZone;
|
||||||
getFieldLinks: (field: Field, rowIndex: number) => Array<LinkModel<Field>>;
|
getFieldLinks: (field: Field, rowIndex: number) => Array<LinkModel<Field>>;
|
||||||
visibleLabels?: { labels: Labels[]; totalSeriesCount: number };
|
visibleSeries?: VisibleExemplarLabels;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ExemplarsPlugin: React.FC<ExemplarsPluginProps> = ({
|
export const ExemplarsPlugin: React.FC<ExemplarsPluginProps> = ({
|
||||||
@ -28,7 +28,7 @@ export const ExemplarsPlugin: React.FC<ExemplarsPluginProps> = ({
|
|||||||
timeZone,
|
timeZone,
|
||||||
getFieldLinks,
|
getFieldLinks,
|
||||||
config,
|
config,
|
||||||
visibleLabels,
|
visibleSeries,
|
||||||
}) => {
|
}) => {
|
||||||
const plotInstance = useRef<uPlot>();
|
const plotInstance = useRef<uPlot>();
|
||||||
|
|
||||||
@ -71,9 +71,11 @@ export const ExemplarsPlugin: React.FC<ExemplarsPluginProps> = ({
|
|||||||
|
|
||||||
const renderMarker = useCallback(
|
const renderMarker = useCallback(
|
||||||
(dataFrame: DataFrame, dataFrameFieldIndex: DataFrameFieldIndex) => {
|
(dataFrame: DataFrame, dataFrameFieldIndex: DataFrameFieldIndex) => {
|
||||||
// If the parent provided series/labels: filter the exemplars, otherwise default to show all exemplars
|
const showMarker =
|
||||||
let showMarker =
|
visibleSeries !== undefined ? showExemplarMarker(visibleSeries, dataFrame, dataFrameFieldIndex) : true;
|
||||||
visibleLabels !== undefined ? showExemplarMarker(visibleLabels, dataFrame, dataFrameFieldIndex) : true;
|
|
||||||
|
const markerColor =
|
||||||
|
visibleSeries !== undefined ? getExemplarColor(dataFrame, dataFrameFieldIndex, visibleSeries) : undefined;
|
||||||
|
|
||||||
if (!showMarker) {
|
if (!showMarker) {
|
||||||
return <></>;
|
return <></>;
|
||||||
@ -86,10 +88,11 @@ export const ExemplarsPlugin: React.FC<ExemplarsPluginProps> = ({
|
|||||||
dataFrame={dataFrame}
|
dataFrame={dataFrame}
|
||||||
dataFrameFieldIndex={dataFrameFieldIndex}
|
dataFrameFieldIndex={dataFrameFieldIndex}
|
||||||
config={config}
|
config={config}
|
||||||
|
exemplarColor={markerColor}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[config, timeZone, getFieldLinks, visibleLabels]
|
[config, timeZone, getFieldLinks, visibleSeries]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
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 = (
|
export const getVisibleLabels = (config: UPlotConfigBuilder, frames: DataFrame[] | null): VisibleExemplarLabels => {
|
||||||
config: UPlotConfigBuilder,
|
|
||||||
frames: DataFrame[] | null
|
|
||||||
): { labels: Labels[]; totalSeriesCount: number } => {
|
|
||||||
const visibleSeries = config.series.filter((series) => series.props.show);
|
const visibleSeries = config.series.filter((series) => series.props.show);
|
||||||
const visibleLabels: Labels[] = [];
|
const visibleLabels: LabelWithExemplarUIData[] = [];
|
||||||
if (frames?.length) {
|
if (frames?.length) {
|
||||||
visibleSeries.forEach((plotInstance) => {
|
visibleSeries.forEach((plotInstance) => {
|
||||||
const frameIndex = plotInstance.props?.dataFrameFieldIndex?.frameIndex;
|
const frameIndex = plotInstance.props?.dataFrameFieldIndex?.frameIndex;
|
||||||
@ -121,7 +122,10 @@ export const getVisibleLabels = (
|
|||||||
const field = frames[frameIndex].fields[fieldIndex];
|
const field = frames[frameIndex].fields[fieldIndex];
|
||||||
if (field.labels) {
|
if (field.labels) {
|
||||||
// Note that this may be an empty object in the case of a metric being rendered with no 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 };
|
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
|
* Determine if the current exemplar marker is filtered by what series are selected in the legend UI
|
||||||
*/
|
*/
|
||||||
const showExemplarMarker = (
|
const showExemplarMarker = (
|
||||||
visibleLabels: { labels: Labels[]; totalSeriesCount: number },
|
visibleSeries: VisibleExemplarLabels,
|
||||||
dataFrame: DataFrame,
|
dataFrame: DataFrame,
|
||||||
dataFrameFieldIndex: DataFrameFieldIndex
|
dataFrameFieldIndex: DataFrameFieldIndex
|
||||||
) => {
|
) => {
|
||||||
let showMarker = false;
|
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;
|
showMarker = true;
|
||||||
} else {
|
} else {
|
||||||
visibleLabels.labels.forEach((visibleLabel) => {
|
visibleSeries.labels.forEach((visibleLabel) => {
|
||||||
const labelKeys = Object.keys(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 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;
|
showMarker = true;
|
||||||
} else {
|
} else {
|
||||||
// If there are labels, lets only show the exemplars with labels associated with series that are currently visible
|
// 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);
|
return labelKeys.find((labelKey) => labelKey === field.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check to see if at least one value matches each field
|
|
||||||
if (fields.length) {
|
if (fields.length) {
|
||||||
showMarker = visibleLabels.labels.some((series) => {
|
// Check to see if at least one value matches each field
|
||||||
return Object.keys(series).every((label) => {
|
showMarker = visibleSeries.labels.some((series) => {
|
||||||
const value = series[label];
|
return Object.keys(series.labels).every((label) => {
|
||||||
|
const value = series.labels[label];
|
||||||
return fields.find((field) => field.values.get(dataFrameFieldIndex.fieldIndex) === value);
|
return fields.find((field) => field.values.get(dataFrameFieldIndex.fieldIndex) === value);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user