mirror of https://github.com/grafana/grafana
Table: Adds adhoc filtering (#25467)
* Table: Adds adhoc filtering * Refactor: changes after PR comments * Refactor: hides filtering for data sources that do not support modifyQuery in Explore * Refactor: fixes strict null error * Changed tooltip position to above icon Co-authored-by: Torkel Ödegaard <torkel@grafana.com>pull/25511/head
parent
1040d824c5
commit
72b8300571
@ -0,0 +1,86 @@ |
|||||||
|
import React, { FC, useCallback, useState } from 'react'; |
||||||
|
import { TableCellProps } from 'react-table'; |
||||||
|
import { GrafanaTheme } from '@grafana/data'; |
||||||
|
import { css, cx } from 'emotion'; |
||||||
|
|
||||||
|
import { stylesFactory, useTheme } from '../../themes'; |
||||||
|
import { TableStyles } from './styles'; |
||||||
|
import { FILTER_FOR_OPERATOR, FILTER_OUT_OPERATOR, TableFilterActionCallback } from './types'; |
||||||
|
import { Icon, Tooltip } from '..'; |
||||||
|
import { Props, renderCell } from './TableCell'; |
||||||
|
|
||||||
|
interface FilterableTableCellProps extends Pick<Props, 'cell' | 'field' | 'tableStyles'> { |
||||||
|
onCellFilterAdded: TableFilterActionCallback; |
||||||
|
cellProps: TableCellProps; |
||||||
|
} |
||||||
|
|
||||||
|
export const FilterableTableCell: FC<FilterableTableCellProps> = ({ |
||||||
|
cell, |
||||||
|
field, |
||||||
|
tableStyles, |
||||||
|
onCellFilterAdded, |
||||||
|
cellProps, |
||||||
|
}) => { |
||||||
|
const [showFilters, setShowFilter] = useState(false); |
||||||
|
const onMouseOver = useCallback((event: React.MouseEvent<HTMLDivElement>) => setShowFilter(true), [setShowFilter]); |
||||||
|
const onMouseLeave = useCallback((event: React.MouseEvent<HTMLDivElement>) => setShowFilter(false), [setShowFilter]); |
||||||
|
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] |
||||||
|
); |
||||||
|
const theme = useTheme(); |
||||||
|
const styles = getFilterableTableCellStyles(theme, tableStyles); |
||||||
|
|
||||||
|
return ( |
||||||
|
<div |
||||||
|
{...cellProps} |
||||||
|
className={showFilters ? styles.tableCellWrapper : tableStyles.tableCellWrapper} |
||||||
|
onMouseOver={onMouseOver} |
||||||
|
onMouseLeave={onMouseLeave} |
||||||
|
> |
||||||
|
{renderCell(cell, field, tableStyles)} |
||||||
|
{showFilters && cell.value && ( |
||||||
|
<div className={styles.filterWrapper}> |
||||||
|
<div className={styles.filterItem}> |
||||||
|
<Tooltip content="Filter for value" placement="top"> |
||||||
|
<Icon name={'search-plus'} onClick={onFilterFor} /> |
||||||
|
</Tooltip> |
||||||
|
</div> |
||||||
|
<div className={styles.filterItem}> |
||||||
|
<Tooltip content="Filter out value" placement="top"> |
||||||
|
<Icon name={'search-minus'} onClick={onFilterOut} /> |
||||||
|
</Tooltip> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
)} |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
const getFilterableTableCellStyles = stylesFactory((theme: GrafanaTheme, tableStyles: TableStyles) => ({ |
||||||
|
tableCellWrapper: cx( |
||||||
|
tableStyles.tableCellWrapper, |
||||||
|
css` |
||||||
|
display: inline-flex; |
||||||
|
justify-content: space-between; |
||||||
|
align-items: center; |
||||||
|
` |
||||||
|
), |
||||||
|
filterWrapper: css` |
||||||
|
label: filterWrapper; |
||||||
|
display: inline-flex; |
||||||
|
justify-content: space-around; |
||||||
|
cursor: pointer; |
||||||
|
`,
|
||||||
|
filterItem: css` |
||||||
|
label: filterItem; |
||||||
|
color: ${theme.colors.textSemiWeak}; |
||||||
|
padding: 0 ${theme.spacing.xxs}; |
||||||
|
`,
|
||||||
|
})); |
@ -1,38 +1,45 @@ |
|||||||
import React, { FC } from 'react'; |
import React, { FC } from 'react'; |
||||||
import { Cell } from 'react-table'; |
import { Cell } from 'react-table'; |
||||||
import { Field } from '@grafana/data'; |
import { Field } from '@grafana/data'; |
||||||
|
|
||||||
import { getTextAlign } from './utils'; |
import { getTextAlign } from './utils'; |
||||||
import { TableFilterActionCallback } from './types'; |
import { TableFilterActionCallback } from './types'; |
||||||
import { TableStyles } from './styles'; |
import { TableStyles } from './styles'; |
||||||
|
import { FilterableTableCell } from './FilterableTableCell'; |
||||||
|
|
||||||
interface Props { |
export interface Props { |
||||||
cell: Cell; |
cell: Cell; |
||||||
field: Field; |
field: Field; |
||||||
tableStyles: TableStyles; |
tableStyles: TableStyles; |
||||||
onCellClick?: TableFilterActionCallback; |
onCellFilterAdded?: TableFilterActionCallback; |
||||||
} |
} |
||||||
|
|
||||||
export const TableCell: FC<Props> = ({ cell, field, tableStyles, onCellClick }) => { |
export const TableCell: FC<Props> = ({ cell, field, tableStyles, onCellFilterAdded }) => { |
||||||
const filterable = field.config.filterable; |
const filterable = field.config.filterable; |
||||||
const cellProps = cell.getCellProps(); |
const cellProps = cell.getCellProps(); |
||||||
|
|
||||||
let onClick: ((event: React.SyntheticEvent) => void) | undefined = undefined; |
|
||||||
|
|
||||||
if (filterable && onCellClick) { |
|
||||||
if (cellProps.style) { |
|
||||||
cellProps.style.cursor = 'pointer'; |
|
||||||
} |
|
||||||
|
|
||||||
onClick = () => onCellClick(cell.column.Header as string, cell.value); |
|
||||||
} |
|
||||||
|
|
||||||
if (cellProps.style) { |
if (cellProps.style) { |
||||||
cellProps.style.textAlign = getTextAlign(field); |
cellProps.style.textAlign = getTextAlign(field); |
||||||
} |
} |
||||||
|
|
||||||
|
if (filterable && onCellFilterAdded) { |
||||||
|
return ( |
||||||
|
<FilterableTableCell |
||||||
|
cell={cell} |
||||||
|
field={field} |
||||||
|
tableStyles={tableStyles} |
||||||
|
onCellFilterAdded={onCellFilterAdded} |
||||||
|
cellProps={cellProps} |
||||||
|
/> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
return ( |
return ( |
||||||
<div {...cellProps} onClick={onClick} className={tableStyles.tableCellWrapper}> |
<div {...cellProps} className={tableStyles.tableCellWrapper}> |
||||||
{cell.render('Cell', { field, tableStyles })} |
{renderCell(cell, field, tableStyles)} |
||||||
</div> |
</div> |
||||||
); |
); |
||||||
}; |
}; |
||||||
|
|
||||||
|
export const renderCell = (cell: Cell, field: Field, tableStyles: TableStyles) => |
||||||
|
cell.render('Cell', { field, tableStyles }); |
||||||
|
Loading…
Reference in new issue