mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
TablePanel: Add cell inspect option (#45620)
* TablePanel: Add cell preview option * Review comments * Change modal title * Review * Review 2 * Docs
This commit is contained in:
parent
c014f8b806
commit
eb537e2efd
@ -96,6 +96,12 @@ If you have a field value that is an image URL or a base64 encoded image you can
|
|||||||
|
|
||||||
{{< figure src="/static/img/docs/v73/table_hover.gif" max-width="900px" caption="Table hover" >}}
|
{{< figure src="/static/img/docs/v73/table_hover.gif" max-width="900px" caption="Table hover" >}}
|
||||||
|
|
||||||
|
## Cell value inspect
|
||||||
|
|
||||||
|
Enables value inspection from table cell. The raw value is presented in a modal window.
|
||||||
|
|
||||||
|
> **Note:** Cell value inspection is only available when cell display mode is set to Auto, Color text, Color background or JSON View.
|
||||||
|
|
||||||
## Column filter
|
## Column filter
|
||||||
|
|
||||||
You can temporarily change how column data is displayed. For example, you can order values from highest to lowest or hide specific values. For more information, refer to [Filter table columns]({{< relref "./filter-table-columns.md" >}}).
|
You can temporarily change how column data is displayed. For example, you can order values from highest to lowest or hide specific values. For more information, refer to [Filter table columns]({{< relref "./filter-table-columns.md" >}}).
|
||||||
|
@ -283,6 +283,7 @@ export enum BarGaugeDisplayMode {
|
|||||||
export interface TableFieldOptions {
|
export interface TableFieldOptions {
|
||||||
align: string;
|
align: string;
|
||||||
displayMode: TableCellDisplayMode;
|
displayMode: TableCellDisplayMode;
|
||||||
|
inspect: boolean;
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
minWidth?: number;
|
minWidth?: number;
|
||||||
width?: number;
|
width?: number;
|
||||||
@ -292,6 +293,7 @@ export interface TableFieldOptions {
|
|||||||
export const defaultTableFieldOptions: TableFieldOptions = {
|
export const defaultTableFieldOptions: TableFieldOptions = {
|
||||||
align: 'auto',
|
align: 'auto',
|
||||||
displayMode: TableCellDisplayMode.Auto,
|
displayMode: TableCellDisplayMode.Auto,
|
||||||
|
inspect: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface VizTooltipOptions {
|
export interface VizTooltipOptions {
|
||||||
|
70
packages/grafana-ui/src/components/Table/CellActions.tsx
Normal file
70
packages/grafana-ui/src/components/Table/CellActions.tsx
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
|
import { IconSize } from '../../types/icon';
|
||||||
|
import { IconButton } from '../IconButton/IconButton';
|
||||||
|
import { HorizontalGroup } from '../Layout/Layout';
|
||||||
|
import { TooltipPlacement } from '../Tooltip';
|
||||||
|
import { TableCellInspectModal } from './TableCellInspectModal';
|
||||||
|
import { FILTER_FOR_OPERATOR, FILTER_OUT_OPERATOR, TableCellProps, TableFieldOptions } from './types';
|
||||||
|
import { getTextAlign } from './utils';
|
||||||
|
|
||||||
|
interface CellActionProps extends TableCellProps {
|
||||||
|
previewMode: 'text' | 'code';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CellActions({ field, cell, previewMode, onCellFilterAdded }: CellActionProps) {
|
||||||
|
const [isInspecting, setIsInspecting] = useState(false);
|
||||||
|
|
||||||
|
const isRightAligned = getTextAlign(field) === 'flex-end';
|
||||||
|
const showFilters = Boolean(field.config.filterable) && cell.value !== undefined;
|
||||||
|
const inspectEnabled = Boolean((field.config.custom as TableFieldOptions)?.inspect);
|
||||||
|
const commonButtonProps = {
|
||||||
|
size: 'sm' as IconSize,
|
||||||
|
tooltipPlacement: 'top' as TooltipPlacement,
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFilterFor = useCallback(
|
||||||
|
(event: React.MouseEvent<HTMLButtonElement>) =>
|
||||||
|
onCellFilterAdded({ key: field.name, operator: FILTER_FOR_OPERATOR, value: cell.value }),
|
||||||
|
[cell, field, onCellFilterAdded]
|
||||||
|
);
|
||||||
|
const onFilterOut = useCallback(
|
||||||
|
(event: React.MouseEvent<HTMLButtonElement>) =>
|
||||||
|
onCellFilterAdded({ key: field.name, operator: FILTER_OUT_OPERATOR, value: cell.value }),
|
||||||
|
[cell, field, onCellFilterAdded]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={`cellActions ${isRightAligned ? 'cellActionsLeft' : ''}`}>
|
||||||
|
<HorizontalGroup spacing="xs">
|
||||||
|
{inspectEnabled && (
|
||||||
|
<IconButton
|
||||||
|
name="eye"
|
||||||
|
tooltip="Inspect value"
|
||||||
|
onClick={() => {
|
||||||
|
setIsInspecting(true);
|
||||||
|
}}
|
||||||
|
{...commonButtonProps}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{showFilters && (
|
||||||
|
<IconButton name={'search-plus'} onClick={onFilterFor} tooltip="Filter for value" {...commonButtonProps} />
|
||||||
|
)}
|
||||||
|
{showFilters && (
|
||||||
|
<IconButton name={'search-minus'} onClick={onFilterOut} tooltip="Filter out value" {...commonButtonProps} />
|
||||||
|
)}
|
||||||
|
</HorizontalGroup>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isInspecting && (
|
||||||
|
<TableCellInspectModal
|
||||||
|
mode={previewMode}
|
||||||
|
value={cell.value}
|
||||||
|
onDismiss={() => {
|
||||||
|
setIsInspecting(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -1,15 +1,16 @@
|
|||||||
import React, { FC, ReactElement } from 'react';
|
import React, { FC, ReactElement } from 'react';
|
||||||
import { DisplayValue, Field, formattedValueToString } from '@grafana/data';
|
import { DisplayValue, Field, formattedValueToString } from '@grafana/data';
|
||||||
|
|
||||||
import { TableCellDisplayMode, TableCellProps } from './types';
|
import { TableCellDisplayMode, TableCellProps, TableFieldOptions } from './types';
|
||||||
import tinycolor from 'tinycolor2';
|
import tinycolor from 'tinycolor2';
|
||||||
import { TableStyles } from './styles';
|
import { TableStyles } from './styles';
|
||||||
import { FilterActions } from './FilterActions';
|
|
||||||
import { getTextColorForBackground, getCellLinks } from '../../utils';
|
import { getTextColorForBackground, getCellLinks } from '../../utils';
|
||||||
|
import { CellActions } from './CellActions';
|
||||||
|
|
||||||
export const DefaultCell: FC<TableCellProps> = (props) => {
|
export const DefaultCell: FC<TableCellProps> = (props) => {
|
||||||
const { field, cell, tableStyles, row, cellProps } = props;
|
const { field, cell, tableStyles, row, cellProps } = props;
|
||||||
|
|
||||||
|
const inspectEnabled = Boolean((field.config.custom as TableFieldOptions)?.inspect);
|
||||||
const displayValue = field.display!(cell.value);
|
const displayValue = field.display!(cell.value);
|
||||||
|
|
||||||
let value: string | ReactElement;
|
let value: string | ReactElement;
|
||||||
@ -19,8 +20,9 @@ export const DefaultCell: FC<TableCellProps> = (props) => {
|
|||||||
value = formattedValueToString(displayValue);
|
value = formattedValueToString(displayValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
const cellStyle = getCellStyle(tableStyles, field, displayValue);
|
|
||||||
const showFilters = field.config.filterable;
|
const showFilters = field.config.filterable;
|
||||||
|
const showActions = (showFilters && cell.value !== undefined) || inspectEnabled;
|
||||||
|
const cellStyle = getCellStyle(tableStyles, field, displayValue, inspectEnabled);
|
||||||
|
|
||||||
const { link, onClick } = getCellLinks(field, row);
|
const { link, onClick } = getCellLinks(field, row);
|
||||||
|
|
||||||
@ -32,20 +34,25 @@ export const DefaultCell: FC<TableCellProps> = (props) => {
|
|||||||
{value}
|
{value}
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
{showFilters && cell.value !== undefined && <FilterActions {...props} />}
|
{showActions && <CellActions {...props} previewMode="text" />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function getCellStyle(tableStyles: TableStyles, field: Field, displayValue: DisplayValue) {
|
function getCellStyle(
|
||||||
|
tableStyles: TableStyles,
|
||||||
|
field: Field,
|
||||||
|
displayValue: DisplayValue,
|
||||||
|
disableOverflowOnHover = false
|
||||||
|
) {
|
||||||
if (field.config.custom?.displayMode === TableCellDisplayMode.ColorText) {
|
if (field.config.custom?.displayMode === TableCellDisplayMode.ColorText) {
|
||||||
return tableStyles.buildCellContainerStyle(displayValue.color);
|
return tableStyles.buildCellContainerStyle(displayValue.color, undefined, !disableOverflowOnHover);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.config.custom?.displayMode === TableCellDisplayMode.ColorBackgroundSolid) {
|
if (field.config.custom?.displayMode === TableCellDisplayMode.ColorBackgroundSolid) {
|
||||||
const bgColor = tinycolor(displayValue.color);
|
const bgColor = tinycolor(displayValue.color);
|
||||||
const textColor = getTextColorForBackground(displayValue.color!);
|
const textColor = getTextColorForBackground(displayValue.color!);
|
||||||
return tableStyles.buildCellContainerStyle(textColor, bgColor.toRgbString());
|
return tableStyles.buildCellContainerStyle(textColor, bgColor.toRgbString(), !disableOverflowOnHover);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.config.custom?.displayMode === TableCellDisplayMode.ColorBackground) {
|
if (field.config.custom?.displayMode === TableCellDisplayMode.ColorBackground) {
|
||||||
@ -59,9 +66,10 @@ function getCellStyle(tableStyles: TableStyles, field: Field, displayValue: Disp
|
|||||||
|
|
||||||
return tableStyles.buildCellContainerStyle(
|
return tableStyles.buildCellContainerStyle(
|
||||||
textColor,
|
textColor,
|
||||||
`linear-gradient(120deg, ${bgColor2}, ${displayValue.color})`
|
`linear-gradient(120deg, ${bgColor2}, ${displayValue.color})`,
|
||||||
|
!disableOverflowOnHover
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return tableStyles.cellContainer;
|
return disableOverflowOnHover ? tableStyles.cellContainerNoOverflow : tableStyles.cellContainer;
|
||||||
}
|
}
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
import React, { FC, useCallback } from 'react';
|
|
||||||
import { FILTER_FOR_OPERATOR, FILTER_OUT_OPERATOR, TableCellProps } from './types';
|
|
||||||
import { Icon, Tooltip } from '..';
|
|
||||||
|
|
||||||
export const FilterActions: FC<TableCellProps> = ({ cell, field, tableStyles, onCellFilterAdded }) => {
|
|
||||||
const onFilterFor = useCallback(
|
|
||||||
(event: React.MouseEvent<HTMLDivElement>) =>
|
|
||||||
onCellFilterAdded({ key: field.name, operator: FILTER_FOR_OPERATOR, value: cell.value }),
|
|
||||||
[cell, field, onCellFilterAdded]
|
|
||||||
);
|
|
||||||
const onFilterOut = useCallback(
|
|
||||||
(event: React.MouseEvent<HTMLDivElement>) =>
|
|
||||||
onCellFilterAdded({ key: field.name, operator: FILTER_OUT_OPERATOR, value: cell.value }),
|
|
||||||
[cell, field, onCellFilterAdded]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={tableStyles.filterWrapper}>
|
|
||||||
<div className={tableStyles.filterItem}>
|
|
||||||
<Tooltip content="Filter for value" placement="top">
|
|
||||||
<Icon name={'search-plus'} onClick={onFilterFor} />
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
<div className={tableStyles.filterItem}>
|
|
||||||
<Tooltip content="Filter out value" placement="top">
|
|
||||||
<Icon name={'search-minus'} onClick={onFilterOut} />
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,15 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { css, cx } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
import { isString } from 'lodash';
|
import { isString } from 'lodash';
|
||||||
import { Tooltip } from '../Tooltip/Tooltip';
|
import { TableCellProps, TableFieldOptions } from './types';
|
||||||
import { JSONFormatter } from '../JSONFormatter/JSONFormatter';
|
import { CellActions } from './CellActions';
|
||||||
import { useStyles2 } from '../../themes';
|
|
||||||
import { TableCellProps } from './types';
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
|
||||||
|
|
||||||
export function JSONViewCell(props: TableCellProps): JSX.Element {
|
export function JSONViewCell(props: TableCellProps): JSX.Element {
|
||||||
const { cell, tableStyles, cellProps } = props;
|
const { cell, tableStyles, cellProps, field } = props;
|
||||||
|
const inspectEnabled = Boolean((field.config.custom as TableFieldOptions)?.inspect);
|
||||||
const txt = css`
|
const txt = css`
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
@ -26,41 +23,10 @@ export function JSONViewCell(props: TableCellProps): JSX.Element {
|
|||||||
displayValue = JSON.stringify(value, null, ' ');
|
displayValue = JSON.stringify(value, null, ' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = <JSONTooltip value={value} />;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip placement="auto-start" content={content} theme="info" interactive>
|
<div {...cellProps} className={inspectEnabled ? tableStyles.cellContainerNoOverflow : tableStyles.cellContainer}>
|
||||||
<div {...cellProps} className={tableStyles.cellContainer}>
|
<div className={cx(tableStyles.cellText, txt)}>{displayValue}</div>
|
||||||
<div className={cx(tableStyles.cellText, txt)}>{displayValue}</div>
|
{inspectEnabled && <CellActions {...props} previewMode="code" />}
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PopupProps {
|
|
||||||
value: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
function JSONTooltip(props: PopupProps): JSX.Element {
|
|
||||||
const styles = useStyles2(getStyles);
|
|
||||||
return (
|
|
||||||
<div className={styles.container}>
|
|
||||||
<div>
|
|
||||||
<JSONFormatter json={props.value} open={4} className={styles.json} />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStyles(theme: GrafanaTheme2) {
|
|
||||||
return {
|
|
||||||
container: css`
|
|
||||||
padding: ${theme.spacing(0.5)};
|
|
||||||
`,
|
|
||||||
json: css`
|
|
||||||
width: fit-content;
|
|
||||||
max-height: 70vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,75 @@
|
|||||||
|
import { isString } from 'lodash';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { ClipboardButton } from '../ClipboardButton/ClipboardButton';
|
||||||
|
import { Icon } from '../Icon/Icon';
|
||||||
|
import { Modal } from '../Modal/Modal';
|
||||||
|
import { CodeEditor } from '../Monaco/CodeEditor';
|
||||||
|
|
||||||
|
interface TableCellInspectModalProps {
|
||||||
|
value: any;
|
||||||
|
onDismiss: () => void;
|
||||||
|
mode: 'code' | 'text';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TableCellInspectModal({ value, onDismiss, mode }: TableCellInspectModalProps) {
|
||||||
|
const [isInClipboard, setIsInClipboard] = useState(false);
|
||||||
|
const timeoutRef = React.useRef<number>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isInClipboard) {
|
||||||
|
timeoutRef.current = window.setTimeout(() => {
|
||||||
|
setIsInClipboard(false);
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (timeoutRef.current) {
|
||||||
|
window.clearTimeout(timeoutRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [isInClipboard]);
|
||||||
|
|
||||||
|
let displayValue = value;
|
||||||
|
if (isString(value)) {
|
||||||
|
try {
|
||||||
|
value = JSON.parse(value);
|
||||||
|
} catch {} // ignore errors
|
||||||
|
} else {
|
||||||
|
displayValue = JSON.stringify(value, null, ' ');
|
||||||
|
}
|
||||||
|
let text = displayValue;
|
||||||
|
|
||||||
|
if (mode === 'code') {
|
||||||
|
text = JSON.stringify(value, null, ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal onDismiss={onDismiss} isOpen={true} title="Inspect value">
|
||||||
|
{mode === 'code' ? (
|
||||||
|
<CodeEditor
|
||||||
|
width="100%"
|
||||||
|
height={500}
|
||||||
|
language="json"
|
||||||
|
showLineNumbers={true}
|
||||||
|
showMiniMap={(text && text.length) > 100}
|
||||||
|
value={text}
|
||||||
|
readOnly={true}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<pre>{text}</pre>
|
||||||
|
)}
|
||||||
|
<Modal.ButtonRow>
|
||||||
|
<ClipboardButton getText={() => text} onClipboardCopy={() => setIsInClipboard(true)}>
|
||||||
|
{!isInClipboard ? (
|
||||||
|
'Copy to Clipboard'
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Icon name="check" />
|
||||||
|
Copied to clipboard
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</ClipboardButton>
|
||||||
|
</Modal.ButtonRow>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { css, cx } from '@emotion/css';
|
import { css, CSSObject } from '@emotion/css';
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { getScrollbarWidth } from '../../utils';
|
import { getScrollbarWidth } from '../../utils';
|
||||||
|
|
||||||
@ -14,8 +14,27 @@ export const getTableStyles = (theme: GrafanaTheme2) => {
|
|||||||
const rowHoverBg = theme.colors.emphasize(theme.colors.background.primary, 0.03);
|
const rowHoverBg = theme.colors.emphasize(theme.colors.background.primary, 0.03);
|
||||||
const lastChildExtraPadding = Math.max(getScrollbarWidth(), cellPadding);
|
const lastChildExtraPadding = Math.max(getScrollbarWidth(), cellPadding);
|
||||||
|
|
||||||
const buildCellContainerStyle = (color?: string, background?: string) => {
|
const buildCellContainerStyle = (color?: string, background?: string, overflowOnHover?: boolean) => {
|
||||||
|
const cellActionsOverflow: CSSObject = {
|
||||||
|
margin: theme.spacing(0, -0.5, 0, 0.5),
|
||||||
|
};
|
||||||
|
const cellActionsNoOverflow: CSSObject = {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
margin: 'auto',
|
||||||
|
};
|
||||||
|
|
||||||
|
const onHoverOverflow: CSSObject = {
|
||||||
|
overflow: 'visible',
|
||||||
|
width: 'auto !important',
|
||||||
|
boxShadow: `0 0 2px ${theme.colors.primary.main}`,
|
||||||
|
background: background ?? rowHoverBg,
|
||||||
|
zIndex: 1,
|
||||||
|
};
|
||||||
|
|
||||||
return css`
|
return css`
|
||||||
|
label: ${overflowOnHover ? 'cellContainerOverflow' : 'cellContainerNoOverflow'};
|
||||||
padding: ${cellPadding}px;
|
padding: ${cellPadding}px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -33,19 +52,42 @@ export const getTableStyles = (theme: GrafanaTheme2) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
overflow: visible;
|
${overflowOnHover && onHoverOverflow};
|
||||||
width: auto !important;
|
.cellActions {
|
||||||
box-shadow: 0 0 2px ${theme.colors.primary.main};
|
visibility: visible;
|
||||||
background: ${background ?? rowHoverBg};
|
opacity: 1;
|
||||||
z-index: 1;
|
width: auto;
|
||||||
|
|
||||||
.cell-filter-actions {
|
|
||||||
display: inline-flex;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cellActions {
|
||||||
|
display: flex;
|
||||||
|
${overflowOnHover ? cellActionsOverflow : cellActionsNoOverflow}
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
padding: ${theme.spacing(1, 0.5, 1, 0.5)};
|
||||||
|
background: ${background ? 'none' : theme.colors.emphasize(theme.colors.background.primary, 0.03)};
|
||||||
|
|
||||||
|
svg {
|
||||||
|
color: ${color};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cellActionsLeft {
|
||||||
|
right: auto !important;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cellActionsTransparent {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -102,7 +144,8 @@ export const getTableStyles = (theme: GrafanaTheme2) => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
margin-right: ${theme.spacing(0.5)};
|
margin-right: ${theme.spacing(0.5)};
|
||||||
`,
|
`,
|
||||||
cellContainer: buildCellContainerStyle(),
|
cellContainer: buildCellContainerStyle(undefined, undefined, true),
|
||||||
|
cellContainerNoOverflow: buildCellContainerStyle(undefined, undefined, false),
|
||||||
cellText: css`
|
cellText: css`
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@ -161,22 +204,6 @@ export const getTableStyles = (theme: GrafanaTheme2) => {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
filterWrapper: cx(
|
|
||||||
css`
|
|
||||||
label: filterWrapper;
|
|
||||||
display: none;
|
|
||||||
justify-content: flex-end;
|
|
||||||
flex-grow: 1;
|
|
||||||
opacity: 0.6;
|
|
||||||
padding-left: ${theme.spacing(0.25)};
|
|
||||||
`,
|
|
||||||
'cell-filter-actions'
|
|
||||||
),
|
|
||||||
filterItem: css`
|
|
||||||
label: filterItem;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0 ${theme.spacing(0.025)};
|
|
||||||
`,
|
|
||||||
typeIcon: css`
|
typeIcon: css`
|
||||||
margin-right: ${theme.spacing(1)};
|
margin-right: ${theme.spacing(1)};
|
||||||
color: ${theme.colors.text.secondary};
|
color: ${theme.colors.text.secondary};
|
||||||
|
@ -39,4 +39,5 @@ export const defaultPanelOptions: PanelOptions = {
|
|||||||
export const defaultPanelFieldConfig: TableFieldOptions = {
|
export const defaultPanelFieldConfig: TableFieldOptions = {
|
||||||
displayMode: TableCellDisplayMode.Auto,
|
displayMode: TableCellDisplayMode.Auto,
|
||||||
align: 'auto',
|
align: 'auto',
|
||||||
|
inspect: false,
|
||||||
};
|
};
|
||||||
|
@ -75,6 +75,21 @@ export const plugin = new PanelPlugin<PanelOptions, TableFieldOptions>(TablePane
|
|||||||
},
|
},
|
||||||
defaultValue: defaultPanelFieldConfig.displayMode,
|
defaultValue: defaultPanelFieldConfig.displayMode,
|
||||||
})
|
})
|
||||||
|
.addBooleanSwitch({
|
||||||
|
path: 'inspect',
|
||||||
|
name: 'Cell value inspect',
|
||||||
|
description: 'Enable cell value inspection in a modal window',
|
||||||
|
defaultValue: false,
|
||||||
|
showIf: (cfg) => {
|
||||||
|
return (
|
||||||
|
cfg.displayMode === TableCellDisplayMode.Auto ||
|
||||||
|
cfg.displayMode === TableCellDisplayMode.JSONView ||
|
||||||
|
cfg.displayMode === TableCellDisplayMode.ColorText ||
|
||||||
|
cfg.displayMode === TableCellDisplayMode.ColorBackground ||
|
||||||
|
cfg.displayMode === TableCellDisplayMode.ColorBackgroundSolid
|
||||||
|
);
|
||||||
|
},
|
||||||
|
})
|
||||||
.addBooleanSwitch({
|
.addBooleanSwitch({
|
||||||
path: 'filterable',
|
path: 'filterable',
|
||||||
name: 'Column filter',
|
name: 'Column filter',
|
||||||
|
Loading…
Reference in New Issue
Block a user