import { css } from '@emotion/css'; import React from 'react'; import { arrayUtils, DataFrame, Field, formattedValueToString, getFieldDisplayName, GrafanaTheme2, LinkModel, } from '@grafana/data'; import { SortOrder, TooltipDisplayMode } from '@grafana/schema'; import { TextLink, useStyles2 } from '@grafana/ui'; import { renderValue } from 'app/plugins/panel/geomap/utils/uiUtils'; export interface Props { data?: DataFrame; // source data rowIndex?: number | null; // the hover row columnIndex?: number | null; // the hover column sortOrder?: SortOrder; mode?: TooltipDisplayMode | null; header?: string; padding?: number; } export interface DisplayValue { name: string; value: unknown; valueString: string; highlight: boolean; } export function getDisplayValuesAndLinks( data: DataFrame, rowIndex: number, columnIndex?: number | null, sortOrder?: SortOrder, mode?: TooltipDisplayMode | null ) { const fields = data.fields.map((f, idx) => { return { ...f, hovered: idx === columnIndex }; }); const visibleFields = fields.filter((f) => !Boolean(f.config.custom?.hideFrom?.tooltip)); const traceIDField = visibleFields.find((field) => field.name === 'traceID') || fields[0]; const orderedVisibleFields = []; // Only include traceID if it's visible and put it in front. if (visibleFields.filter((field) => traceIDField === field).length > 0) { orderedVisibleFields.push(traceIDField); } orderedVisibleFields.push(...visibleFields.filter((field) => traceIDField !== field)); if (orderedVisibleFields.length === 0) { return null; } const displayValues: DisplayValue[] = []; const links: Array> = []; const linkLookup = new Set(); for (const field of orderedVisibleFields) { if (mode === TooltipDisplayMode.Single && columnIndex != null && !field.hovered) { continue; } const value = field.values[rowIndex]; const fieldDisplay = field.display ? field.display(value) : { text: `${value}`, numeric: +value }; if (field.getLinks) { field.getLinks({ calculatedValue: fieldDisplay, valueRowIndex: rowIndex }).forEach((link) => { const key = `${link.title}/${link.href}`; if (!linkLookup.has(key)) { links.push(link); linkLookup.add(key); } }); } // Sanitize field by removing hovered property to fix unique display name issue const { hovered, ...sanitizedField } = field; displayValues.push({ name: getFieldDisplayName(sanitizedField, data), value, valueString: formattedValueToString(fieldDisplay), highlight: field.hovered, }); } if (sortOrder && sortOrder !== SortOrder.None) { displayValues.sort((a, b) => arrayUtils.sortValues(sortOrder)(a.value, b.value)); } return { displayValues, links }; } export const DataHoverView = ({ data, rowIndex, columnIndex, sortOrder, mode, header, padding = 0 }: Props) => { const styles = useStyles2(getStyles, padding); if (!data || rowIndex == null) { return null; } const dispValuesAndLinks = getDisplayValuesAndLinks(data, rowIndex, columnIndex, sortOrder, mode); if (dispValuesAndLinks == null) { return null; } const { displayValues, links } = dispValuesAndLinks; return (
{header && (
{header}
)} {displayValues.map((displayValue, i) => ( ))} {links.map((link, i) => ( ))}
{displayValue.name} {renderValue(displayValue.valueString)}
Link {link.title}
); }; const getStyles = (theme: GrafanaTheme2, padding = 0) => { return { wrapper: css` padding: ${padding}px; background: ${theme.components.tooltip.background}; border-radius: ${theme.shape.borderRadius(2)}; `, header: css` background: ${theme.colors.background.secondary}; align-items: center; align-content: center; display: flex; padding-bottom: ${theme.spacing(1)}; `, title: css` font-weight: ${theme.typography.fontWeightMedium}; overflow: hidden; display: inline-block; white-space: nowrap; text-overflow: ellipsis; flex-grow: 1; `, infoWrap: css` padding: ${theme.spacing(1)}; background: transparent; border: none; th { font-weight: ${theme.typography.fontWeightMedium}; padding: ${theme.spacing(0.25, 2, 0.25, 0)}; } tr { border-bottom: 1px solid ${theme.colors.border.weak}; &:last-child { border-bottom: none; } } `, highlight: css``, link: css` color: ${theme.colors.text.link}; `, }; };