Table panel: Add multiple data links support to Default, Image and JSONView cells (#51162)

* Table panel: Support multiple data links in default cell

* Table panel: Show data links for Image and JSONView cells

* Simplify DataLinksContextMenu api

* Betterer
pull/51297/head^2
Dominik Prokop 3 years ago committed by GitHub
parent 4a397c9c24
commit b2b0be7b93
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 25
      .betterer.results
  2. 20
      packages/grafana-ui/src/components/DataLinks/DataLinksContextMenu.test.tsx
  3. 7
      packages/grafana-ui/src/components/DataLinks/DataLinksContextMenu.tsx
  4. 6
      packages/grafana-ui/src/components/Table/BarGaugeCell.tsx
  5. 20
      packages/grafana-ui/src/components/Table/DefaultCell.tsx
  6. 24
      packages/grafana-ui/src/components/Table/ImageCell.tsx
  7. 23
      packages/grafana-ui/src/components/Table/JSONViewCell.tsx
  8. 3
      packages/grafana-ui/src/utils/dataLinks.ts
  9. 29
      packages/grafana-ui/src/utils/table.ts
  10. 4
      public/app/plugins/panel/bargauge/BarGaugePanel.tsx
  11. 2
      public/app/plugins/panel/gauge/GaugePanel.tsx
  12. 2
      public/app/plugins/panel/piechart/PieChart.tsx
  13. 2
      public/app/plugins/panel/stat/StatPanel.tsx

@ -2092,7 +2092,7 @@ exports[`better eslint`] = {
"packages/grafana-ui/src/components/Switch/Switch.story.tsx:3337756944": [
[11, 15, 258, "Do not use any type assertions.", "1871090638"]
],
"packages/grafana-ui/src/components/Table/BarGaugeCell.tsx:2469721145": [
"packages/grafana-ui/src/components/Table/BarGaugeCell.tsx:1481110243": [
[46, 13, 17, "Do not use any type assertions.", "3640560184"]
],
"packages/grafana-ui/src/components/Table/CellActions.tsx:3266589396": [
@ -2100,8 +2100,8 @@ exports[`better eslint`] = {
[22, 10, 16, "Do not use any type assertions.", "737436615"],
[23, 22, 25, "Do not use any type assertions.", "1478996352"]
],
"packages/grafana-ui/src/components/Table/DefaultCell.tsx:1844923424": [
[14, 34, 40, "Do not use any type assertions.", "91092480"]
"packages/grafana-ui/src/components/Table/DefaultCell.tsx:3618905143": [
[16, 34, 40, "Do not use any type assertions.", "91092480"]
],
"packages/grafana-ui/src/components/Table/Filter.tsx:1102571026": [
[13, 10, 3, "Unexpected any. Specify a different type.", "193409811"]
@ -2121,8 +2121,8 @@ exports[`better eslint`] = {
[53, 38, 13, "Do not use any type assertions.", "947160887"],
[53, 48, 3, "Unexpected any. Specify a different type.", "193409811"]
],
"packages/grafana-ui/src/components/Table/JSONViewCell.tsx:4022130242": [
[11, 34, 40, "Do not use any type assertions.", "91092480"]
"packages/grafana-ui/src/components/Table/JSONViewCell.tsx:2181157412": [
[12, 34, 40, "Do not use any type assertions.", "91092480"]
],
"packages/grafana-ui/src/components/Table/Table.story.tsx:4272567078": [
[23, 15, 364, "Do not use any type assertions.", "4121186137"]
@ -2486,9 +2486,6 @@ exports[`better eslint`] = {
"packages/grafana-ui/src/utils/colors.ts:349987544": [
[110, 25, 3, "Unexpected any. Specify a different type.", "193409811"]
],
"packages/grafana-ui/src/utils/dataLinks.ts:2916992126": [
[16, 12, 71, "Do not use any type assertions.", "1504235577"]
],
"packages/grafana-ui/src/utils/debug.ts:2900904491": [
[6, 56, 3, "Unexpected any. Specify a different type.", "193409811"]
],
@ -2516,9 +2513,9 @@ exports[`better eslint`] = {
[38, 72, 3, "Unexpected any. Specify a different type.", "193409811"],
[38, 85, 3, "Unexpected any. Specify a different type.", "193409811"]
],
"packages/grafana-ui/src/utils/table.ts:3564715667": [
[8, 52, 3, "Unexpected any. Specify a different type.", "193409811"],
[9, 22, 3, "Unexpected any. Specify a different type.", "193409811"]
"packages/grafana-ui/src/utils/table.ts:2798596296": [
[7, 52, 3, "Unexpected any. Specify a different type.", "193409811"],
[8, 29, 3, "Unexpected any. Specify a different type.", "193409811"]
],
"packages/grafana-ui/src/utils/useAsyncDependency.ts:4089662838": [
[3, 60, 3, "Unexpected any. Specify a different type.", "193409811"]
@ -10930,8 +10927,8 @@ exports[`better eslint`] = {
[102, 16, 9, "Do not use any type assertions.", "3692209159"],
[102, 22, 3, "Unexpected any. Specify a different type.", "193409811"]
],
"public/app/plugins/panel/bargauge/BarGaugePanel.tsx:3184205061": [
[114, 75, 3, "Unexpected any. Specify a different type.", "193409811"]
"public/app/plugins/panel/bargauge/BarGaugePanel.tsx:2584207123": [
[112, 75, 3, "Unexpected any. Specify a different type.", "193409811"]
],
"public/app/plugins/panel/candlestick/CandlestickPanel.tsx:3928129934": [
[116, 26, 33, "Do not use any type assertions.", "1940217301"],
@ -11877,7 +11874,7 @@ exports[`better eslint`] = {
[194, 16, 3, "Unexpected any. Specify a different type.", "193409811"],
[256, 16, 3, "Unexpected any. Specify a different type.", "193409811"]
],
"public/app/plugins/panel/piechart/PieChart.tsx:1361550264": [
"public/app/plugins/panel/piechart/PieChart.tsx:287896203": [
[201, 12, 3, "Unexpected any. Specify a different type.", "193409811"],
[217, 12, 3, "Unexpected any. Specify a different type.", "193409811"]
],

@ -24,18 +24,6 @@ describe('DataLinksContextMenu', () => {
origin: {},
},
]}
config={{
links: [
{
title: 'Link1',
url: '/link1',
},
{
title: 'Link2',
url: '/link2',
},
],
}}
>
{() => {
return <div aria-label="fake aria label" />;
@ -58,14 +46,6 @@ describe('DataLinksContextMenu', () => {
origin: {},
},
]}
config={{
links: [
{
title: 'Link1',
url: '/link1',
},
],
}}
>
{() => {
return <div aria-label="fake aria label" />;

@ -1,7 +1,7 @@
import { css } from '@emotion/css';
import React from 'react';
import { FieldConfig, LinkModel } from '@grafana/data';
import { LinkModel } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { linkModelToContextMenuItems } from '../../utils/dataLinks';
@ -12,7 +12,6 @@ import { MenuItem } from '../Menu/MenuItem';
interface DataLinksContextMenuProps {
children: (props: DataLinksContextMenuApi) => JSX.Element;
links: () => LinkModel[];
config: FieldConfig;
}
export interface DataLinksContextMenuApi {
@ -20,9 +19,9 @@ export interface DataLinksContextMenuApi {
targetClassName?: string;
}
export const DataLinksContextMenu: React.FC<DataLinksContextMenuProps> = ({ children, links, config }) => {
const linksCounter = config.links!.length;
export const DataLinksContextMenu: React.FC<DataLinksContextMenuProps> = ({ children, links }) => {
const itemsGroup: MenuItemsGroup[] = [{ items: linkModelToContextMenuItems(links), label: 'Data links' }];
const linksCounter = itemsGroup[0].items.length;
const renderMenuGroupItems = () => {
return itemsGroup.map((group, index) => (
<MenuGroup key={`${group.label}${index}`} label={group.label}>

@ -76,11 +76,7 @@ export const BarGaugeCell: FC<TableCellProps> = (props) => {
return (
<div {...cellProps} className={tableStyles.cellContainer}>
{hasLinks && (
<DataLinksContextMenu links={getLinks} config={config}>
{(api) => renderComponent(api)}
</DataLinksContextMenu>
)}
{hasLinks && <DataLinksContextMenu links={getLinks}>{(api) => renderComponent(api)}</DataLinksContextMenu>}
{!hasLinks && (
<BarGauge
width={innerWidth}

@ -1,9 +1,11 @@
import { cx } from '@emotion/css';
import React, { FC, ReactElement } from 'react';
import tinycolor from 'tinycolor2';
import { DisplayValue, Field, formattedValueToString } from '@grafana/data';
import { getTextColorForBackground, getCellLinks } from '../../utils';
import { DataLinksContextMenu } from '../DataLinks/DataLinksContextMenu';
import { CellActions } from './CellActions';
import { TableStyles } from './styles';
@ -26,16 +28,24 @@ export const DefaultCell: FC<TableCellProps> = (props) => {
const showActions = (showFilters && cell.value !== undefined) || inspectEnabled;
const cellStyle = getCellStyle(tableStyles, field, displayValue, inspectEnabled);
const { link, onClick } = getCellLinks(field, row);
const hasLinks = Boolean(getCellLinks(field, row)?.length);
return (
<div {...cellProps} className={cellStyle}>
{!link && <div className={tableStyles.cellText}>{value}</div>}
{link && (
<a href={link.href} onClick={onClick} target={link.target} title={link.title} className={tableStyles.cellLink}>
{!hasLinks && <div className={tableStyles.cellText}>{value}</div>}
{hasLinks && (
<DataLinksContextMenu links={() => getCellLinks(field, row) || []}>
{(api) => {
return (
<div onClick={api.openMenu} className={cx(tableStyles.cellLink, api.targetClassName)}>
{value}
</a>
</div>
);
}}
</DataLinksContextMenu>
)}
{showActions && <CellActions {...props} previewMode="text" />}
</div>
);

@ -1,6 +1,8 @@
import { cx } from '@emotion/css';
import React, { FC } from 'react';
import { getCellLinks } from '../../utils';
import { DataLinksContextMenu } from '../DataLinks/DataLinksContextMenu';
import { TableCellProps } from './types';
@ -9,21 +11,21 @@ export const ImageCell: FC<TableCellProps> = (props) => {
const displayValue = field.display!(cell.value);
const { link, onClick } = getCellLinks(field, row);
const hasLinks = getCellLinks(field, row)?.length;
return (
<div {...cellProps} className={tableStyles.cellContainer}>
{!link && <img src={displayValue.text} className={tableStyles.imageCell} />}
{link && (
<a
href={link.href}
onClick={onClick}
target={link.target}
title={link.title}
className={tableStyles.imageCellLink}
>
{!hasLinks && <img src={displayValue.text} className={tableStyles.imageCell} />}
{hasLinks && (
<DataLinksContextMenu links={() => getCellLinks(field, row) || []}>
{(api) => {
return (
<div onClick={api.openMenu} className={cx(tableStyles.imageCellLink, api.targetClassName)}>
<img src={displayValue.text} className={tableStyles.imageCell} />
</a>
</div>
);
}}
</DataLinksContextMenu>
)}
</div>
);

@ -3,6 +3,7 @@ import { isString } from 'lodash';
import React from 'react';
import { getCellLinks } from '../../utils';
import { DataLinksContextMenu } from '../DataLinks/DataLinksContextMenu';
import { CellActions } from './CellActions';
import { TableCellProps, TableFieldOptions } from './types';
@ -26,22 +27,22 @@ export function JSONViewCell(props: TableCellProps): JSX.Element {
displayValue = JSON.stringify(value, null, ' ');
}
const { link, onClick } = getCellLinks(field, row);
const hasLinks = getCellLinks(field, row)?.length;
return (
<div {...cellProps} className={inspectEnabled ? tableStyles.cellContainerNoOverflow : tableStyles.cellContainer}>
<div className={cx(tableStyles.cellText, txt)}>
{!link && <div className={tableStyles.cellText}>{displayValue}</div>}
{link && (
<a
href={link.href}
onClick={onClick}
target={link.target}
title={link.title}
className={tableStyles.cellLink}
>
{!hasLinks && <div className={tableStyles.cellText}>{displayValue}</div>}
{hasLinks && (
<DataLinksContextMenu links={() => getCellLinks(field, row) || []}>
{(api) => {
return (
<div onClick={api.openMenu} className={api.targetClassName}>
{displayValue}
</a>
</div>
);
}}
</DataLinksContextMenu>
)}
</div>
{inspectEnabled && <CellActions {...props} previewMode="code" />}

@ -1,7 +1,6 @@
import { LinkModel } from '@grafana/data';
import { MenuItemProps } from '../components/Menu/MenuItem';
import { IconName } from '../types';
/**
* Delays creating links until we need to open the ContextMenu
@ -14,7 +13,7 @@ export const linkModelToContextMenuItems: (links: () => LinkModel[]) => MenuItem
// TODO: rename to href
url: link.href,
target: link.target,
icon: `${link.target === '_self' ? 'link' : 'external-link-alt'}` as IconName,
icon: `${link.target === '_blank' ? 'external-link-alt' : 'link'}`,
onClick: link.onClick,
};
});

@ -1,4 +1,3 @@
import { MouseEventHandler } from 'react';
import { Row } from 'react-table';
import { Field, LinkModel } from '@grafana/data';
@ -7,29 +6,33 @@ import { Field, LinkModel } from '@grafana/data';
* @internal
*/
export const getCellLinks = (field: Field, row: Row<any>) => {
let link: LinkModel<any> | undefined;
let onClick: MouseEventHandler<HTMLAnchorElement> | undefined;
let links: Array<LinkModel<any>> | undefined;
if (field.getLinks) {
link = field.getLinks({
links = field.getLinks({
valueRowIndex: row.index,
})[0];
});
}
if (!links) {
return;
}
//const fieldLink = link?.onClick;
if (link?.onClick) {
onClick = (event) => {
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();
link!.onClick!(event, {
origOnClick!(event, {
field,
rowIndex: row.index,
});
}
};
}
return {
link,
onClick,
};
}
return links;
};

@ -60,9 +60,7 @@ export class BarGaugePanel extends PureComponent<PanelProps<PanelOptions>> {
if (hasLinks && getLinks) {
return (
<div style={{ width: '100%', display: orientation === VizOrientation.Vertical ? 'flex' : 'initial' }}>
<DataLinksContextMenu links={getLinks} config={value.field}>
{(api) => this.renderComponent(valueProps, api)}
</DataLinksContextMenu>
<DataLinksContextMenu links={getLinks}>{(api) => this.renderComponent(valueProps, api)}</DataLinksContextMenu>
</div>
);
}

@ -41,7 +41,7 @@ export class GaugePanel extends PureComponent<PanelProps<PanelOptions>> {
if (hasLinks && getLinks) {
return (
<DataLinksContextMenu links={getLinks} config={value.field}>
<DataLinksContextMenu links={getLinks}>
{(api) => {
return this.renderComponent(valueProps, api);
}}

@ -118,7 +118,7 @@ export const PieChart: FC<PieChartProps> = ({
if (arc.data.hasLinks && arc.data.getLinks) {
return (
<DataLinksContextMenu config={arc.data.field} key={arc.index} links={arc.data.getLinks}>
<DataLinksContextMenu key={arc.index} links={arc.data.getLinks}>
{(api) => (
<PieSlice
tooltip={tooltip}

@ -68,7 +68,7 @@ export class StatPanel extends PureComponent<PanelProps<PanelOptions>> {
if (hasLinks && getLinks) {
return (
<DataLinksContextMenu links={getLinks} config={value.field}>
<DataLinksContextMenu links={getLinks}>
{(api) => {
return this.renderComponent(valueProps, api);
}}

Loading…
Cancel
Save