Prometheus: Add traceID link to heatmap exemplar popover (#60039)

* Add traceID link to heatmap exemplar popover
* Render links for each field individually

Co-authored-by: ismail simsek <ismailsimsek09@gmail.com>
This commit is contained in:
Galen Kistler 2022-12-08 12:02:17 -06:00 committed by GitHub
parent 551a2ac9ec
commit f37997aec0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 72 additions and 36 deletions

View File

@ -11,7 +11,7 @@ import {
LinkModel,
} from '@grafana/data';
import { SortOrder, TooltipDisplayMode } from '@grafana/schema';
import { LinkButton, useStyles2, VerticalGroup } from '@grafana/ui';
import { HorizontalGroup, LinkButton, useStyles2 } from '@grafana/ui';
export interface Props {
data?: DataFrame; // source data
@ -35,19 +35,18 @@ export const DataHoverView = ({ data, rowIndex, columnIndex, sortOrder, mode }:
}
const displayValues: Array<[string, unknown, string]> = [];
const links: Array<LinkModel<Field>> = [];
const linkLookup = new Set<string>();
const links: Record<string, Array<LinkModel<Field>>> = {};
for (const f of visibleFields) {
const v = f.values.get(rowIndex);
const disp = f.display ? f.display(v) : { text: `${v}`, numeric: +v };
if (f.getLinks) {
f.getLinks({ calculatedValue: disp, valueRowIndex: rowIndex }).forEach((link) => {
const key = `${link.title}/${link.href}`;
if (!linkLookup.has(key)) {
links.push(link);
linkLookup.add(key);
const key = getFieldDisplayName(f, data);
if (!links[key]) {
links[key] = [];
}
links[key].push(link);
});
}
@ -65,34 +64,13 @@ export const DataHoverView = ({ data, rowIndex, columnIndex, sortOrder, mode }:
displayValues.map((v, i) => (
<tr key={`${i}/${rowIndex}`} className={i === columnIndex ? styles.highlight : ''}>
<th>{v[0]}:</th>
<td>{v[2]}</td>
<td>{renderWithLinks(v[0], v[2], links)}</td>
</tr>
))}
{mode === TooltipDisplayMode.Single && columnIndex && (
<tr key={`${columnIndex}/${rowIndex}`}>
<th>{displayValues[columnIndex][0]}:</th>
<td>{displayValues[columnIndex][2]}</td>
</tr>
)}
{links.length > 0 && (
<tr>
<td colSpan={2}>
<VerticalGroup>
{links.map((link, i) => (
<LinkButton
key={i}
icon={'external-link-alt'}
target={link.target}
href={link.href}
onClick={link.onClick}
fill="text"
style={{ width: '100%' }}
>
{link.title}
</LinkButton>
))}
</VerticalGroup>
</td>
<td>{renderWithLinks(displayValues[columnIndex][0], displayValues[columnIndex][2], links)}</td>
</tr>
)}
</tbody>
@ -100,6 +78,30 @@ export const DataHoverView = ({ data, rowIndex, columnIndex, sortOrder, mode }:
);
};
const renderWithLinks = (key: string, val: string, links: Record<string, Array<LinkModel<Field>>>) =>
links[key] ? (
<HorizontalGroup>
<>
{val}
{links[key].map((link, i) => (
<LinkButton
key={i}
icon={'external-link-alt'}
target={link.target}
href={link.href}
onClick={link.onClick}
fill="text"
style={{ width: '100%' }}
>
{link.title}
</LinkButton>
))}
</>
</HorizontalGroup>
) : (
<>{val}</>
);
const getStyles = (theme: GrafanaTheme2) => ({
infoWrap: css`
padding: 8px;

View File

@ -1,6 +1,14 @@
import React, { useEffect, useRef, useState } from 'react';
import { DataFrameType, Field, FieldType, formattedValueToString, getFieldDisplayName, LinkModel } from '@grafana/data';
import {
DataFrameType,
Field,
FieldType,
formattedValueToString,
getFieldDisplayName,
LinkModel,
TimeRange,
} from '@grafana/data';
import { LinkButton, VerticalGroup } from '@grafana/ui';
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
import { isHeatmapCellsDense, readHeatmapRowsCustomMeta } from 'app/features/transformers/calculateHeatmap/heatmap';
@ -15,6 +23,7 @@ type Props = {
data: HeatmapData;
hover: HeatmapHoverEvent;
showHistogram?: boolean;
timeRange: TimeRange;
};
export const HeatmapHoverView = (props: Props) => {

View File

@ -1,7 +1,7 @@
import { css } from '@emotion/css';
import React, { useCallback, useMemo, useRef, useState } from 'react';
import { DataFrameType, GrafanaTheme2, PanelProps, TimeRange } from '@grafana/data';
import { DataFrame, DataFrameType, Field, getLinksSupplier, GrafanaTheme2, PanelProps, TimeRange } from '@grafana/data';
import { PanelDataErrorView } from '@grafana/runtime';
import { ScaleDistributionConfig } from '@grafana/schema';
import {
@ -47,13 +47,20 @@ export const HeatmapPanel: React.FC<HeatmapPanelProps> = ({
let timeRangeRef = useRef<TimeRange>(timeRange);
timeRangeRef.current = timeRange;
const getFieldLinksSupplier = useCallback(
(exemplars: DataFrame, field: Field) => {
return getLinksSupplier(exemplars, field, field.state?.scopedVars ?? {}, replaceVariables);
},
[replaceVariables]
);
const info = useMemo(() => {
try {
return prepareHeatmapData(data, options, theme);
return prepareHeatmapData(data, options, theme, getFieldLinksSupplier);
} catch (ex) {
return { warning: `${ex}` };
}
}, [data, options, theme]);
}, [data, options, theme, getFieldLinksSupplier]);
const facets = useMemo(() => {
let exemplarsXFacet: number[] = []; // "Time" field
@ -218,7 +225,12 @@ export const HeatmapPanel: React.FC<HeatmapPanelProps> = ({
/>
</div>
)}
<HeatmapHoverView data={info} hover={hover} showHistogram={options.tooltip.yHistogram} />
<HeatmapHoverView
timeRange={timeRange}
data={info}
hover={hover}
showHistogram={options.tooltip.yHistogram}
/>
</VizTooltipContainer>
)}
</Portal>

View File

@ -6,9 +6,11 @@ import {
formattedValueToString,
getDisplayProcessor,
GrafanaTheme2,
LinkModel,
outerJoinDataFrames,
PanelData,
ValueFormatter,
ValueLinkConfig,
} from '@grafana/data';
import {
calculateHeatmapFromData,
@ -52,7 +54,12 @@ export interface HeatmapData {
warning?: string;
}
export function prepareHeatmapData(data: PanelData, options: PanelOptions, theme: GrafanaTheme2): HeatmapData {
export function prepareHeatmapData(
data: PanelData,
options: PanelOptions,
theme: GrafanaTheme2,
getFieldLinks?: (exemplars: DataFrame, field: Field) => (config: ValueLinkConfig) => Array<LinkModel<Field>>
): HeatmapData {
let frames = data.series;
if (!frames?.length) {
return {};
@ -60,6 +67,12 @@ export function prepareHeatmapData(data: PanelData, options: PanelOptions, theme
const exemplars = data.annotations?.find((f) => f.name === 'exemplar');
if (getFieldLinks) {
exemplars?.fields.forEach((field, index) => {
exemplars.fields[index].getLinks = getFieldLinks(exemplars, field);
});
}
if (options.calculate) {
return getDenseHeatmapData(calculateHeatmapFromData(frames, options.calculation ?? {}), exemplars, options, theme);
}