mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
add datalinks to AutoCell, ImageCell, JsonCell
This commit is contained in:
parent
8f1abd652c
commit
a7fb7de084
@ -1,28 +1,94 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { Property } from 'csstype';
|
||||
|
||||
import { GrafanaTheme2, formattedValueToString } from '@grafana/data';
|
||||
import { TableCellOptions } from '@grafana/schema';
|
||||
import { TableCellDisplayMode, TableCellOptions } from '@grafana/schema';
|
||||
|
||||
import { useStyles2 } from '../../../../themes';
|
||||
import { clearLinkButtonStyles } from '../../../Button';
|
||||
import { DataLinksContextMenu } from '../../../DataLinks/DataLinksContextMenu';
|
||||
import { CellNGProps } from '../types';
|
||||
import { getCellLinks } from '../utils';
|
||||
|
||||
interface AutoCellProps extends CellNGProps {
|
||||
cellOptions: TableCellOptions;
|
||||
}
|
||||
|
||||
export default function AutoCell({ value, field, justifyContent, cellOptions }: AutoCellProps) {
|
||||
export default function AutoCell({ value, field, justifyContent, cellOptions, rowIdx }: AutoCellProps) {
|
||||
const styles = useStyles2(getStyles, justifyContent);
|
||||
|
||||
const displayValue = field.display!(value);
|
||||
const formattedValue = formattedValueToString(displayValue);
|
||||
const hasLinks = Boolean(getCellLinks(field, rowIdx)?.length);
|
||||
const clearButtonStyle = useStyles2(clearLinkButtonStyles);
|
||||
|
||||
return <div className={styles.cell}>{formattedValue}</div>;
|
||||
return (
|
||||
<div className={styles.cell}>
|
||||
{hasLinks ? (
|
||||
<DataLinksContextMenu links={() => getCellLinks(field, rowIdx) || []}>
|
||||
{(api) => {
|
||||
if (api.openMenu) {
|
||||
return (
|
||||
<button
|
||||
className={cx(clearButtonStyle, getLinkStyle(styles, cellOptions, api.targetClassName))}
|
||||
onClick={api.openMenu}
|
||||
>
|
||||
{value}
|
||||
</button>
|
||||
);
|
||||
} else {
|
||||
return <button className={getLinkStyle(styles, cellOptions, api.targetClassName)}>{value}</button>;
|
||||
}
|
||||
}}
|
||||
</DataLinksContextMenu>
|
||||
) : (
|
||||
formattedValue
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const getLinkStyle = (
|
||||
styles: ReturnType<typeof getStyles>,
|
||||
cellOptions: TableCellOptions,
|
||||
targetClassName: string | undefined
|
||||
) => {
|
||||
if (cellOptions.type === TableCellDisplayMode.Auto) {
|
||||
return cx(styles.linkCell, targetClassName);
|
||||
}
|
||||
|
||||
return cx(styles.cellLinkForColoredCell, targetClassName);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2, justifyContent: Property.JustifyContent | undefined) => ({
|
||||
cell: css({
|
||||
display: 'flex',
|
||||
justifyContent: justifyContent,
|
||||
}),
|
||||
cellLinkForColoredCell: css({
|
||||
cursor: 'pointer',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
userSelect: 'text',
|
||||
whiteSpace: 'nowrap',
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
textDecoration: 'underline',
|
||||
}),
|
||||
linkCell: css({
|
||||
cursor: 'pointer',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
userSelect: 'text',
|
||||
whiteSpace: 'nowrap',
|
||||
color: theme.colors.text.link,
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
paddingRight: theme.spacing(1.5),
|
||||
a: {
|
||||
color: theme.colors.text.link,
|
||||
},
|
||||
'&:hover': {
|
||||
textDecoration: 'underline',
|
||||
color: theme.colors.text.link,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
@ -1,5 +1,3 @@
|
||||
import { isFunction } from 'lodash';
|
||||
|
||||
import { ThresholdsConfig, ThresholdsMode, VizOrientation, getFieldConfigWithMinMax } from '@grafana/data';
|
||||
import { BarGaugeDisplayMode, BarGaugeValueMode, TableCellDisplayMode } from '@grafana/schema';
|
||||
|
||||
@ -7,6 +5,7 @@ import { BarGauge } from '../../../BarGauge/BarGauge';
|
||||
import { DataLinksContextMenu, DataLinksContextMenuApi } from '../../../DataLinks/DataLinksContextMenu';
|
||||
import { getAlignmentFactor, getCellOptions } from '../../utils';
|
||||
import { BarGaugeCellProps } from '../types';
|
||||
import { getCellLinks } from '../utils';
|
||||
|
||||
const defaultScale: ThresholdsConfig = {
|
||||
mode: ThresholdsMode.Absolute,
|
||||
@ -45,15 +44,7 @@ export const BarGaugeCell = ({ value, field, theme, height, width, rowIdx }: Bar
|
||||
cellOptions.valueDisplayMode !== undefined ? cellOptions.valueDisplayMode : BarGaugeValueMode.Text;
|
||||
}
|
||||
|
||||
const getLinks = () => {
|
||||
if (!isFunction(field.getLinks)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return field.getLinks({ valueRowIndex: rowIdx });
|
||||
};
|
||||
|
||||
const hasLinks = Boolean(getLinks().length);
|
||||
const hasLinks = Boolean(getCellLinks(field, rowIdx)?.length);
|
||||
|
||||
const alignmentFactors = getAlignmentFactor(field, displayValue, rowIdx!);
|
||||
|
||||
@ -84,7 +75,10 @@ export const BarGaugeCell = ({ value, field, theme, height, width, rowIdx }: Bar
|
||||
return (
|
||||
<>
|
||||
{hasLinks ? (
|
||||
<DataLinksContextMenu links={getLinks} style={{ display: 'flex', width: '100%' }}>
|
||||
<DataLinksContextMenu
|
||||
links={() => getCellLinks(field, rowIdx) || []}
|
||||
style={{ display: 'flex', width: '100%' }}
|
||||
>
|
||||
{(api) => renderComponent(api)}
|
||||
</DataLinksContextMenu>
|
||||
) : (
|
||||
|
@ -1,17 +1,21 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { Property } from 'csstype';
|
||||
import * as React from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { TableCellDisplayMode } from '@grafana/schema';
|
||||
|
||||
import { useStyles2 } from '../../../../themes';
|
||||
import { DataLinksContextMenu } from '../../../DataLinks/DataLinksContextMenu';
|
||||
import { ImageCellProps } from '../types';
|
||||
import { getCellLinks } from '../utils';
|
||||
|
||||
const DATALINKS_HEIGHT_OFFSET = 10;
|
||||
|
||||
export const ImageCell = ({ cellOptions, field, height, justifyContent, value }: ImageCellProps) => {
|
||||
export const ImageCell = ({ cellOptions, field, height, justifyContent, value, rowIdx }: ImageCellProps) => {
|
||||
const calculatedHeight = height - DATALINKS_HEIGHT_OFFSET;
|
||||
const styles = useStyles2(getStyles, calculatedHeight, justifyContent);
|
||||
const hasLinks = Boolean(getCellLinks(field, rowIdx)?.length);
|
||||
|
||||
const { text } = field.display!(value);
|
||||
const { alt, title } =
|
||||
@ -19,8 +23,38 @@ export const ImageCell = ({ cellOptions, field, height, justifyContent, value }:
|
||||
|
||||
const img = <img alt={alt} src={text} className={styles.image} title={title} />;
|
||||
|
||||
// TODO: Implement DataLinksContextMenu + actions
|
||||
return <div className={styles.imageContainer}>{img}</div>;
|
||||
// TODO: Implement actions
|
||||
return (
|
||||
<div className={styles.imageContainer}>
|
||||
{hasLinks ? (
|
||||
<DataLinksContextMenu links={() => getCellLinks(field, rowIdx) || []}>
|
||||
{(api) => {
|
||||
if (api.openMenu) {
|
||||
return (
|
||||
<div
|
||||
onClick={api.openMenu}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter' && api.openMenu) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/consistent-type-assertions
|
||||
api.openMenu(e as any);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{img}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return img;
|
||||
}
|
||||
}}
|
||||
</DataLinksContextMenu>
|
||||
) : (
|
||||
img
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2, height: number, justifyContent: Property.JustifyContent) => ({
|
||||
|
@ -1,14 +1,18 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { Property } from 'csstype';
|
||||
import { isString } from 'lodash';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
|
||||
import { useStyles2 } from '../../../../themes';
|
||||
import { Button, clearLinkButtonStyles } from '../../../Button';
|
||||
import { DataLinksContextMenu } from '../../../DataLinks/DataLinksContextMenu';
|
||||
import { CellNGProps } from '../types';
|
||||
import { getCellLinks } from '../utils';
|
||||
|
||||
export const JSONCell = ({ value, justifyContent }: Omit<CellNGProps, 'theme' | 'field'>) => {
|
||||
export const JSONCell = ({ value, justifyContent, field, rowIdx }: Omit<CellNGProps, 'theme'>) => {
|
||||
const styles = useStyles2(getStyles, justifyContent);
|
||||
const clearButtonStyle = useStyles2(clearLinkButtonStyles);
|
||||
|
||||
let localValue = value;
|
||||
let displayValue = localValue;
|
||||
@ -21,8 +25,30 @@ export const JSONCell = ({ value, justifyContent }: Omit<CellNGProps, 'theme' |
|
||||
displayValue = JSON.stringify(localValue, null, ' ');
|
||||
}
|
||||
|
||||
// TODO: Implement DataLinksContextMenu + actions
|
||||
return <div className={styles.jsonText}>{displayValue}</div>;
|
||||
const hasLinks = Boolean(getCellLinks(field, rowIdx)?.length);
|
||||
|
||||
// TODO: Implement actions
|
||||
return (
|
||||
<div className={styles.jsonText}>
|
||||
{hasLinks ? (
|
||||
<DataLinksContextMenu links={() => getCellLinks(field, rowIdx) || []}>
|
||||
{(api) => {
|
||||
if (api.openMenu) {
|
||||
return (
|
||||
<Button className={cx(clearButtonStyle)} onClick={api.openMenu}>
|
||||
{displayValue}
|
||||
</Button>
|
||||
);
|
||||
} else {
|
||||
return <>{displayValue}</>;
|
||||
}
|
||||
}}
|
||||
</DataLinksContextMenu>
|
||||
) : (
|
||||
displayValue
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2, justifyContent: Property.JustifyContent) => ({
|
||||
|
@ -92,6 +92,7 @@ export function TableCellNG(props: any) {
|
||||
timeRange={timeRange}
|
||||
height={height}
|
||||
width={divWidth}
|
||||
rowIdx={rowIdx}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
@ -103,11 +104,12 @@ export function TableCellNG(props: any) {
|
||||
height={height}
|
||||
justifyContent={justifyContent}
|
||||
value={value}
|
||||
rowIdx={rowIdx}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case TableCellDisplayMode.JSONView:
|
||||
cell = <JSONCell value={value} justifyContent={justifyContent} />;
|
||||
cell = <JSONCell value={value} justifyContent={justifyContent} field={field} rowIdx={rowIdx} />;
|
||||
break;
|
||||
case TableCellDisplayMode.Auto:
|
||||
default:
|
||||
@ -118,6 +120,7 @@ export function TableCellNG(props: any) {
|
||||
theme={theme}
|
||||
justifyContent={justifyContent}
|
||||
cellOptions={fieldConfig.custom.cellOptions}
|
||||
rowIdx={rowIdx}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ export interface CellNGProps {
|
||||
theme?: GrafanaTheme2;
|
||||
height?: number;
|
||||
justifyContent: Property.JustifyContent;
|
||||
rowIdx?: number;
|
||||
rowIdx: number;
|
||||
}
|
||||
|
||||
export interface RowExpanderNGProps {
|
||||
|
@ -1,6 +1,14 @@
|
||||
import tinycolor from 'tinycolor2';
|
||||
|
||||
import { FieldType, Field, formattedValueToString, reduceField, GrafanaTheme2, DisplayValue } from '@grafana/data';
|
||||
import {
|
||||
FieldType,
|
||||
Field,
|
||||
formattedValueToString,
|
||||
reduceField,
|
||||
GrafanaTheme2,
|
||||
DisplayValue,
|
||||
LinkModel,
|
||||
} from '@grafana/data';
|
||||
import { TableCellBackgroundDisplayMode, TableCellDisplayMode, TableCellOptions } from '@grafana/schema';
|
||||
|
||||
import { getTextColorForAlphaBackground } from '../../../utils';
|
||||
@ -207,3 +215,47 @@ export function getCellColors(
|
||||
|
||||
return { textColor, bgColor, bgHoverColor };
|
||||
}
|
||||
|
||||
export const getLinks = (field: Field, rowIdx: number) => {
|
||||
if (field.getLinks) {
|
||||
return field.getLinks({ valueRowIndex: rowIdx });
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* TODO: unify with the existing getCellLinks
|
||||
*/
|
||||
export const getCellLinks = (field: Field, rowIndex: number) => {
|
||||
let links: Array<LinkModel<unknown>> | undefined;
|
||||
if (field.getLinks) {
|
||||
links = field.getLinks({
|
||||
valueRowIndex: rowIndex,
|
||||
});
|
||||
}
|
||||
|
||||
if (!links) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < links?.length; i++) {
|
||||
if (links[i].onClick) {
|
||||
const origOnClick = links[i].onClick;
|
||||
|
||||
links[i].onClick = (event) => {
|
||||
// Allow opening in new tab
|
||||
if (!(event.ctrlKey || event.metaKey || event.shiftKey)) {
|
||||
event.preventDefault();
|
||||
origOnClick!(event, {
|
||||
field,
|
||||
rowIndex: rowIndex,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return links;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user