mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
* Show different symbols for different queries
* Only run different exemplars
* Address review comment
* Do the same for dashboard + tests
(cherry picked from commit 4435895833
)
Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com>
This commit is contained in:
parent
6d6e875a84
commit
f704fa423d
@ -1,4 +1,4 @@
|
||||
import { DataFrame } from '@grafana/data';
|
||||
import { DataFrame, DataFrameFieldIndex } from '@grafana/data';
|
||||
import React, { useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { usePlotContext } from '../context';
|
||||
import { Marker } from './Marker';
|
||||
@ -9,8 +9,11 @@ interface EventsCanvasProps {
|
||||
id: string;
|
||||
config: UPlotConfigBuilder;
|
||||
events: DataFrame[];
|
||||
renderEventMarker: (dataFrame: DataFrame, index: number) => React.ReactNode;
|
||||
mapEventToXYCoords: (dataFrame: DataFrame, index: number) => { x: number; y: number } | undefined;
|
||||
renderEventMarker: (dataFrame: DataFrame, dataFrameFieldIndex: DataFrameFieldIndex) => React.ReactNode;
|
||||
mapEventToXYCoords: (
|
||||
dataFrame: DataFrame,
|
||||
dataFrameFieldIndex: DataFrameFieldIndex
|
||||
) => { x: number; y: number } | undefined;
|
||||
}
|
||||
|
||||
export function EventsCanvas({ id, events, renderEventMarker, mapEventToXYCoords, config }: EventsCanvasProps) {
|
||||
@ -35,13 +38,13 @@ export function EventsCanvas({ id, events, renderEventMarker, mapEventToXYCoords
|
||||
for (let i = 0; i < events.length; i++) {
|
||||
const frame = events[i];
|
||||
for (let j = 0; j < frame.length; j++) {
|
||||
const coords = mapEventToXYCoords(frame, j);
|
||||
const coords = mapEventToXYCoords(frame, { fieldIndex: j, frameIndex: i });
|
||||
if (!coords) {
|
||||
continue;
|
||||
}
|
||||
markers.push(
|
||||
<Marker {...coords} key={`${id}-marker-${i}-${j}`}>
|
||||
{renderEventMarker(frame, j)}
|
||||
{renderEventMarker(frame, { fieldIndex: j, frameIndex: i })}
|
||||
</Marker>
|
||||
);
|
||||
}
|
||||
|
@ -1759,7 +1759,17 @@ describe('PrometheusDatasource for POST', () => {
|
||||
});
|
||||
});
|
||||
|
||||
const getPrepareTargetsContext = (target: PromQuery, app?: CoreApp, queryOptions?: Partial<QueryOptions>) => {
|
||||
function getPrepareTargetsContext({
|
||||
targets,
|
||||
app,
|
||||
queryOptions,
|
||||
languageProvider,
|
||||
}: {
|
||||
targets: PromQuery[];
|
||||
app?: CoreApp;
|
||||
queryOptions?: Partial<QueryOptions>;
|
||||
languageProvider?: any;
|
||||
}) {
|
||||
const instanceSettings = ({
|
||||
url: 'proxied',
|
||||
directUrl: 'direct',
|
||||
@ -1771,7 +1781,7 @@ const getPrepareTargetsContext = (target: PromQuery, app?: CoreApp, queryOptions
|
||||
const end = 1;
|
||||
const panelId = '2';
|
||||
const options = ({
|
||||
targets: [target],
|
||||
targets,
|
||||
interval: '1s',
|
||||
panelId,
|
||||
app,
|
||||
@ -1779,6 +1789,9 @@ const getPrepareTargetsContext = (target: PromQuery, app?: CoreApp, queryOptions
|
||||
} as any) as DataQueryRequest<PromQuery>;
|
||||
|
||||
const ds = new PrometheusDatasource(instanceSettings, templateSrvStub as any, timeSrvStub as any);
|
||||
if (languageProvider) {
|
||||
ds.languageProvider = languageProvider;
|
||||
}
|
||||
const { queries, activeTargets } = ds.prepareTargets(options, start, end);
|
||||
|
||||
return {
|
||||
@ -1788,7 +1801,7 @@ const getPrepareTargetsContext = (target: PromQuery, app?: CoreApp, queryOptions
|
||||
end,
|
||||
panelId,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
describe('prepareTargets', () => {
|
||||
describe('when run from a Panel', () => {
|
||||
@ -1799,7 +1812,7 @@ describe('prepareTargets', () => {
|
||||
requestId: '2A',
|
||||
};
|
||||
|
||||
const { queries, activeTargets, panelId, end, start } = getPrepareTargetsContext(target);
|
||||
const { queries, activeTargets, panelId, end, start } = getPrepareTargetsContext({ targets: [target] });
|
||||
|
||||
expect(queries.length).toBe(1);
|
||||
expect(activeTargets.length).toBe(1);
|
||||
@ -1819,6 +1832,51 @@ describe('prepareTargets', () => {
|
||||
});
|
||||
expect(activeTargets[0]).toEqual(target);
|
||||
});
|
||||
|
||||
it('should give back 3 targets when multiple queries with exemplar enabled and same metric', () => {
|
||||
const targetA: PromQuery = {
|
||||
refId: 'A',
|
||||
expr: 'histogram_quantile(0.95, sum(rate(tns_request_duration_seconds_bucket[5m])) by (le))',
|
||||
exemplar: true,
|
||||
};
|
||||
const targetB: PromQuery = {
|
||||
refId: 'B',
|
||||
expr: 'histogram_quantile(0.5, sum(rate(tns_request_duration_seconds_bucket[5m])) by (le))',
|
||||
exemplar: true,
|
||||
};
|
||||
|
||||
const { queries, activeTargets } = getPrepareTargetsContext({
|
||||
targets: [targetA, targetB],
|
||||
languageProvider: {
|
||||
histogramMetrics: ['tns_request_duration_seconds_bucket'],
|
||||
},
|
||||
});
|
||||
expect(queries).toHaveLength(3);
|
||||
expect(activeTargets).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('should give back 4 targets when multiple queries with exemplar enabled', () => {
|
||||
const targetA: PromQuery = {
|
||||
refId: 'A',
|
||||
expr: 'histogram_quantile(0.95, sum(rate(tns_request_duration_seconds_bucket[5m])) by (le))',
|
||||
exemplar: true,
|
||||
};
|
||||
const targetB: PromQuery = {
|
||||
refId: 'B',
|
||||
expr: 'histogram_quantile(0.5, sum(rate(tns_request_duration_bucket[5m])) by (le))',
|
||||
exemplar: true,
|
||||
};
|
||||
|
||||
const { queries, activeTargets } = getPrepareTargetsContext({
|
||||
targets: [targetA, targetB],
|
||||
languageProvider: {
|
||||
histogramMetrics: ['tns_request_duration_seconds_bucket'],
|
||||
},
|
||||
});
|
||||
expect(queries).toHaveLength(4);
|
||||
expect(activeTargets).toHaveLength(4);
|
||||
});
|
||||
|
||||
it('should give back 2 targets when exemplar enabled', () => {
|
||||
const target: PromQuery = {
|
||||
refId: 'A',
|
||||
@ -1826,7 +1884,7 @@ describe('prepareTargets', () => {
|
||||
exemplar: true,
|
||||
};
|
||||
|
||||
const { queries, activeTargets } = getPrepareTargetsContext(target);
|
||||
const { queries, activeTargets } = getPrepareTargetsContext({ targets: [target] });
|
||||
expect(queries).toHaveLength(2);
|
||||
expect(activeTargets).toHaveLength(2);
|
||||
expect(activeTargets[0].exemplar).toBe(true);
|
||||
@ -1840,7 +1898,7 @@ describe('prepareTargets', () => {
|
||||
instant: true,
|
||||
};
|
||||
|
||||
const { queries, activeTargets } = getPrepareTargetsContext(target);
|
||||
const { queries, activeTargets } = getPrepareTargetsContext({ targets: [target] });
|
||||
expect(queries).toHaveLength(1);
|
||||
expect(activeTargets).toHaveLength(1);
|
||||
expect(activeTargets[0].instant).toBe(true);
|
||||
@ -1849,6 +1907,60 @@ describe('prepareTargets', () => {
|
||||
|
||||
describe('when run from Explore', () => {
|
||||
describe('when query type Both is selected', () => {
|
||||
it('should give back 6 targets when multiple queries with exemplar enabled', () => {
|
||||
const targetA: PromQuery = {
|
||||
refId: 'A',
|
||||
expr: 'histogram_quantile(0.95, sum(rate(tns_request_duration_seconds_bucket[5m])) by (le))',
|
||||
instant: true,
|
||||
range: true,
|
||||
exemplar: true,
|
||||
};
|
||||
const targetB: PromQuery = {
|
||||
refId: 'B',
|
||||
expr: 'histogram_quantile(0.5, sum(rate(tns_request_duration_bucket[5m])) by (le))',
|
||||
exemplar: true,
|
||||
instant: true,
|
||||
range: true,
|
||||
};
|
||||
|
||||
const { queries, activeTargets } = getPrepareTargetsContext({
|
||||
targets: [targetA, targetB],
|
||||
app: CoreApp.Explore,
|
||||
languageProvider: {
|
||||
histogramMetrics: ['tns_request_duration_seconds_bucket'],
|
||||
},
|
||||
});
|
||||
expect(queries).toHaveLength(6);
|
||||
expect(activeTargets).toHaveLength(6);
|
||||
});
|
||||
|
||||
it('should give back 5 targets when multiple queries with exemplar enabled and same metric', () => {
|
||||
const targetA: PromQuery = {
|
||||
refId: 'A',
|
||||
expr: 'histogram_quantile(0.95, sum(rate(tns_request_duration_seconds_bucket[5m])) by (le))',
|
||||
instant: true,
|
||||
range: true,
|
||||
exemplar: true,
|
||||
};
|
||||
const targetB: PromQuery = {
|
||||
refId: 'B',
|
||||
expr: 'histogram_quantile(0.5, sum(rate(tns_request_duration_seconds_bucket[5m])) by (le))',
|
||||
exemplar: true,
|
||||
instant: true,
|
||||
range: true,
|
||||
};
|
||||
|
||||
const { queries, activeTargets } = getPrepareTargetsContext({
|
||||
targets: [targetA, targetB],
|
||||
app: CoreApp.Explore,
|
||||
languageProvider: {
|
||||
histogramMetrics: ['tns_request_duration_seconds_bucket'],
|
||||
},
|
||||
});
|
||||
expect(queries).toHaveLength(5);
|
||||
expect(activeTargets).toHaveLength(5);
|
||||
});
|
||||
|
||||
it('then it should return both instant and time series related objects', () => {
|
||||
const target: PromQuery = {
|
||||
refId: 'A',
|
||||
@ -1858,7 +1970,10 @@ describe('prepareTargets', () => {
|
||||
requestId: '2A',
|
||||
};
|
||||
|
||||
const { queries, activeTargets, panelId, end, start } = getPrepareTargetsContext(target, CoreApp.Explore);
|
||||
const { queries, activeTargets, panelId, end, start } = getPrepareTargetsContext({
|
||||
targets: [target],
|
||||
app: CoreApp.Explore,
|
||||
});
|
||||
|
||||
expect(queries.length).toBe(2);
|
||||
expect(activeTargets.length).toBe(2);
|
||||
@ -1916,7 +2031,10 @@ describe('prepareTargets', () => {
|
||||
requestId: '2A',
|
||||
};
|
||||
|
||||
const { queries, activeTargets, panelId, end, start } = getPrepareTargetsContext(target, CoreApp.Explore);
|
||||
const { queries, activeTargets, panelId, end, start } = getPrepareTargetsContext({
|
||||
targets: [target],
|
||||
app: CoreApp.Explore,
|
||||
});
|
||||
|
||||
expect(queries.length).toBe(1);
|
||||
expect(activeTargets.length).toBe(1);
|
||||
@ -1949,7 +2067,10 @@ describe('prepareTargets', () => {
|
||||
requestId: '2A',
|
||||
};
|
||||
|
||||
const { queries, activeTargets, panelId, end, start } = getPrepareTargetsContext(target, CoreApp.Explore);
|
||||
const { queries, activeTargets, panelId, end, start } = getPrepareTargetsContext({
|
||||
targets: [target],
|
||||
app: CoreApp.Explore,
|
||||
});
|
||||
|
||||
expect(queries.length).toBe(1);
|
||||
expect(activeTargets.length).toBe(1);
|
||||
|
@ -206,6 +206,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
|
||||
}
|
||||
|
||||
target.requestId = options.panelId + target.refId;
|
||||
const metricName = this.languageProvider.histogramMetrics.find((m) => target.expr.includes(m));
|
||||
|
||||
// In Explore, we run both (instant and range) queries if both are true (selected) or both are undefined (legacy Explore queries)
|
||||
if (options.app === CoreApp.Explore && target.range === target.instant) {
|
||||
@ -226,14 +227,20 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
|
||||
|
||||
// Create exemplar query
|
||||
if (target.exemplar) {
|
||||
// Only create exemplar target for different metric names
|
||||
if (
|
||||
!metricName ||
|
||||
(metricName && !activeTargets.some((activeTarget) => activeTarget.expr.includes(metricName)))
|
||||
) {
|
||||
const exemplarTarget = cloneDeep(target);
|
||||
exemplarTarget.instant = false;
|
||||
exemplarTarget.requestId += '_exemplar';
|
||||
instantTarget.exemplar = false;
|
||||
rangeTarget.exemplar = false;
|
||||
queries.push(this.createQuery(exemplarTarget, options, start, end));
|
||||
activeTargets.push(exemplarTarget);
|
||||
}
|
||||
instantTarget.exemplar = false;
|
||||
rangeTarget.exemplar = false;
|
||||
}
|
||||
|
||||
// Add both targets to activeTargets and queries arrays
|
||||
activeTargets.push(instantTarget, rangeTarget);
|
||||
@ -250,13 +257,18 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
|
||||
} else {
|
||||
// It doesn't make sense to query for exemplars in dashboard if only instant is selected
|
||||
if (target.exemplar && !target.instant) {
|
||||
if (
|
||||
!metricName ||
|
||||
(metricName && !activeTargets.some((activeTarget) => activeTarget.expr.includes(metricName)))
|
||||
) {
|
||||
const exemplarTarget = cloneDeep(target);
|
||||
exemplarTarget.requestId += '_exemplar';
|
||||
target.exemplar = false;
|
||||
queries.push(this.createQuery(exemplarTarget, options, start, end));
|
||||
activeTargets.push(exemplarTarget);
|
||||
this.exemplarErrors.next();
|
||||
}
|
||||
target.exemplar = false;
|
||||
}
|
||||
if (target.exemplar && target.instant) {
|
||||
this.exemplarErrors.next('Exemplars are not available for instant queries.');
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { DataFrame, DataFrameView, getColorForTheme, TimeZone } from '@grafana/data';
|
||||
import { DataFrame, DataFrameFieldIndex, DataFrameView, getColorForTheme, TimeZone } from '@grafana/data';
|
||||
import { EventsCanvas, UPlotConfigBuilder, usePlotContext, useTheme } from '@grafana/ui';
|
||||
import React, { useCallback, useEffect, useLayoutEffect, useRef } from 'react';
|
||||
import { AnnotationMarker } from './AnnotationMarker';
|
||||
@ -65,9 +65,9 @@ export const AnnotationsPlugin: React.FC<AnnotationsPluginProps> = ({ annotation
|
||||
}, [config, theme]);
|
||||
|
||||
const mapAnnotationToXYCoords = useCallback(
|
||||
(frame: DataFrame, index: number) => {
|
||||
(frame: DataFrame, dataFrameFieldIndex: DataFrameFieldIndex) => {
|
||||
const view = new DataFrameView<AnnotationsDataFrameViewDTO>(frame);
|
||||
const annotation = view.get(index);
|
||||
const annotation = view.get(dataFrameFieldIndex.fieldIndex);
|
||||
const plotInstance = plotCtx.plot;
|
||||
if (!annotation.time || !plotInstance) {
|
||||
return undefined;
|
||||
@ -82,9 +82,9 @@ export const AnnotationsPlugin: React.FC<AnnotationsPluginProps> = ({ annotation
|
||||
);
|
||||
|
||||
const renderMarker = useCallback(
|
||||
(frame: DataFrame, index: number) => {
|
||||
(frame: DataFrame, dataFrameFieldIndex: DataFrameFieldIndex) => {
|
||||
const view = new DataFrameView<AnnotationsDataFrameViewDTO>(frame);
|
||||
const annotation = view.get(index);
|
||||
const annotation = view.get(dataFrameFieldIndex.fieldIndex);
|
||||
return <AnnotationMarker annotation={annotation} timeZone={timeZone} />;
|
||||
},
|
||||
[timeZone]
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import {
|
||||
DataFrame,
|
||||
DataFrameFieldIndex,
|
||||
dateTimeFormat,
|
||||
Field,
|
||||
FieldType,
|
||||
@ -10,18 +11,25 @@ import {
|
||||
TimeZone,
|
||||
} from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { FieldLinkList, Portal, useStyles } from '@grafana/ui';
|
||||
import { FieldLinkList, Portal, UPlotConfigBuilder, useStyles } from '@grafana/ui';
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import { usePopper } from 'react-popper';
|
||||
|
||||
interface ExemplarMarkerProps {
|
||||
timeZone: TimeZone;
|
||||
dataFrame: DataFrame;
|
||||
index: number;
|
||||
dataFrameFieldIndex: DataFrameFieldIndex;
|
||||
config: UPlotConfigBuilder;
|
||||
getFieldLinks: (field: Field, rowIndex: number) => Array<LinkModel<Field>>;
|
||||
}
|
||||
|
||||
export const ExemplarMarker: React.FC<ExemplarMarkerProps> = ({ timeZone, dataFrame, index, getFieldLinks }) => {
|
||||
export const ExemplarMarker: React.FC<ExemplarMarkerProps> = ({
|
||||
timeZone,
|
||||
dataFrame,
|
||||
dataFrameFieldIndex,
|
||||
config,
|
||||
getFieldLinks,
|
||||
}) => {
|
||||
const styles = useStyles(getExemplarMarkerStyles);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [markerElement, setMarkerElement] = React.useState<HTMLDivElement | null>(null);
|
||||
@ -29,6 +37,24 @@ export const ExemplarMarker: React.FC<ExemplarMarkerProps> = ({ timeZone, dataFr
|
||||
const { styles: popperStyles, attributes } = usePopper(markerElement, popperElement);
|
||||
const popoverRenderTimeout = useRef<NodeJS.Timer>();
|
||||
|
||||
const getSymbol = () => {
|
||||
const symbols = [
|
||||
<rect key="diamond" x="3.38672" width="4.78985" height="4.78985" transform="rotate(45 3.38672 0)" />,
|
||||
<path
|
||||
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
|
||||
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];
|
||||
};
|
||||
|
||||
const onMouseEnter = useCallback(() => {
|
||||
if (popoverRenderTimeout.current) {
|
||||
clearTimeout(popoverRenderTimeout.current);
|
||||
@ -68,8 +94,10 @@ export const ExemplarMarker: React.FC<ExemplarMarkerProps> = ({ timeZone, dataFr
|
||||
<table className={styles.exemplarsTable}>
|
||||
<tbody>
|
||||
{dataFrame.fields.map((field, i) => {
|
||||
const value = field.values.get(index);
|
||||
const links = field.config.links?.length ? getFieldLinks(field, index) : undefined;
|
||||
const value = field.values.get(dataFrameFieldIndex.fieldIndex);
|
||||
const links = field.config.links?.length
|
||||
? getFieldLinks(field, dataFrameFieldIndex.fieldIndex)
|
||||
: undefined;
|
||||
return (
|
||||
<tr key={i}>
|
||||
<td valign="top">{field.name}</td>
|
||||
@ -93,7 +121,7 @@ export const ExemplarMarker: React.FC<ExemplarMarkerProps> = ({ timeZone, dataFr
|
||||
attributes.popper,
|
||||
dataFrame.fields,
|
||||
getFieldLinks,
|
||||
index,
|
||||
dataFrameFieldIndex,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
popperStyles.popper,
|
||||
@ -101,6 +129,10 @@ export const ExemplarMarker: React.FC<ExemplarMarkerProps> = ({ timeZone, dataFr
|
||||
timeZone,
|
||||
]);
|
||||
|
||||
const seriesColor = config
|
||||
.getSeries()
|
||||
.find((s) => s.props.dataFrameFieldIndex?.frameIndex === dataFrameFieldIndex.frameIndex)?.props.lineColor;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
@ -110,8 +142,14 @@ export const ExemplarMarker: React.FC<ExemplarMarkerProps> = ({ timeZone, dataFr
|
||||
className={styles.markerWrapper}
|
||||
aria-label={selectors.components.DataSource.Prometheus.exemplarMarker}
|
||||
>
|
||||
<svg viewBox="0 0 599 599" width="8" height="8" className={cx(styles.marble, isOpen && styles.activeMarble)}>
|
||||
<path d="M 300,575 L 575,300 L 300,25 L 25,300 L 300,575 Z" />
|
||||
<svg
|
||||
viewBox="0 0 7 7"
|
||||
width="7"
|
||||
height="7"
|
||||
style={{ fill: seriesColor }}
|
||||
className={cx(styles.marble, isOpen && styles.activeMarble)}
|
||||
>
|
||||
{getSymbol()}
|
||||
</svg>
|
||||
</div>
|
||||
{isOpen && <Portal>{renderMarker()}</Portal>}
|
||||
@ -123,21 +161,8 @@ const getExemplarMarkerStyles = (theme: GrafanaTheme) => {
|
||||
const bg = theme.isDark ? theme.palette.dark2 : theme.palette.white;
|
||||
const headerBg = theme.isDark ? theme.palette.dark9 : theme.palette.gray5;
|
||||
const shadowColor = theme.isDark ? theme.palette.black : theme.palette.white;
|
||||
const marbleFill = theme.isDark ? theme.palette.gray3 : theme.palette.gray1;
|
||||
const marbleFillHover = theme.isDark ? theme.palette.blue85 : theme.palette.blue77;
|
||||
const tableBgOdd = theme.isDark ? theme.palette.dark3 : theme.palette.gray6;
|
||||
|
||||
const marble = css`
|
||||
display: block;
|
||||
fill: ${marbleFill};
|
||||
transition: transform 0.15s ease-out;
|
||||
`;
|
||||
const activeMarble = css`
|
||||
fill: ${marbleFillHover};
|
||||
transform: scale(1.3);
|
||||
filter: drop-shadow(0 0 8px rgba(0, 0, 0, 0.5));
|
||||
`;
|
||||
|
||||
return {
|
||||
markerWrapper: css`
|
||||
padding: 0 4px 4px 4px;
|
||||
@ -147,7 +172,9 @@ const getExemplarMarkerStyles = (theme: GrafanaTheme) => {
|
||||
|
||||
&:hover {
|
||||
> svg {
|
||||
${activeMarble}
|
||||
transform: scale(1.3);
|
||||
opacity: 1;
|
||||
filter: drop-shadow(0 0 8px rgba(0, 0, 0, 0.5));
|
||||
}
|
||||
}
|
||||
`,
|
||||
@ -218,7 +245,15 @@ const getExemplarMarkerStyles = (theme: GrafanaTheme) => {
|
||||
padding: ${theme.spacing.sm};
|
||||
font-weight: ${theme.typography.weight.semibold};
|
||||
`,
|
||||
marble,
|
||||
activeMarble,
|
||||
marble: css`
|
||||
display: block;
|
||||
opacity: 0.5;
|
||||
transition: transform 0.15s ease-out;
|
||||
`,
|
||||
activeMarble: css`
|
||||
transform: scale(1.3);
|
||||
opacity: 1;
|
||||
filter: drop-shadow(0 0 8px rgba(0, 0, 0, 0.5));
|
||||
`,
|
||||
};
|
||||
};
|
||||
|
@ -1,5 +1,6 @@
|
||||
import {
|
||||
DataFrame,
|
||||
DataFrameFieldIndex,
|
||||
Field,
|
||||
LinkModel,
|
||||
TimeZone,
|
||||
@ -21,7 +22,7 @@ export const ExemplarsPlugin: React.FC<ExemplarsPluginProps> = ({ exemplars, tim
|
||||
const plotCtx = usePlotContext();
|
||||
|
||||
const mapExemplarToXYCoords = useCallback(
|
||||
(dataFrame: DataFrame, index: number) => {
|
||||
(dataFrame: DataFrame, dataFrameFieldIndex: DataFrameFieldIndex) => {
|
||||
const plotInstance = plotCtx.plot;
|
||||
const time = dataFrame.fields.find((f) => f.name === TIME_SERIES_TIME_FIELD_NAME);
|
||||
const value = dataFrame.fields.find((f) => f.name === TIME_SERIES_VALUE_FIELD_NAME);
|
||||
@ -37,7 +38,7 @@ export const ExemplarsPlugin: React.FC<ExemplarsPluginProps> = ({ exemplars, tim
|
||||
const yMin = plotInstance.scales[yScale].min;
|
||||
const yMax = plotInstance.scales[yScale].max;
|
||||
|
||||
let y = value.values.get(index);
|
||||
let y = value.values.get(dataFrameFieldIndex.fieldIndex);
|
||||
// To not to show exemplars outside of the graph we set the y value to min if it is smaller and max if it is bigger than the size of the graph
|
||||
if (yMin != null && y < yMin) {
|
||||
y = yMin;
|
||||
@ -48,7 +49,7 @@ export const ExemplarsPlugin: React.FC<ExemplarsPluginProps> = ({ exemplars, tim
|
||||
}
|
||||
|
||||
return {
|
||||
x: plotInstance.valToPos(time.values.get(index), 'x'),
|
||||
x: plotInstance.valToPos(time.values.get(dataFrameFieldIndex.fieldIndex), 'x'),
|
||||
y: plotInstance.valToPos(y, yScale),
|
||||
};
|
||||
},
|
||||
@ -56,10 +57,18 @@ export const ExemplarsPlugin: React.FC<ExemplarsPluginProps> = ({ exemplars, tim
|
||||
);
|
||||
|
||||
const renderMarker = useCallback(
|
||||
(dataFrame: DataFrame, index: number) => {
|
||||
return <ExemplarMarker timeZone={timeZone} getFieldLinks={getFieldLinks} dataFrame={dataFrame} index={index} />;
|
||||
(dataFrame: DataFrame, dataFrameFieldIndex: DataFrameFieldIndex) => {
|
||||
return (
|
||||
<ExemplarMarker
|
||||
timeZone={timeZone}
|
||||
getFieldLinks={getFieldLinks}
|
||||
dataFrame={dataFrame}
|
||||
dataFrameFieldIndex={dataFrameFieldIndex}
|
||||
config={config}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[timeZone, getFieldLinks]
|
||||
[config, timeZone, getFieldLinks]
|
||||
);
|
||||
|
||||
return (
|
||||
|
Loading…
Reference in New Issue
Block a user