kibana/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx
2021-02-04 16:22:09 +01:00

280 lines
8.1 KiB
TypeScript

/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import './table_basic.scss';
import React, { useCallback, useMemo, useRef, useState, useContext } from 'react';
import { i18n } from '@kbn/i18n';
import useDeepCompareEffect from 'react-use/lib/useDeepCompareEffect';
import {
EuiButtonIcon,
EuiDataGrid,
EuiDataGridControlColumn,
EuiDataGridColumn,
EuiDataGridSorting,
EuiDataGridStyle,
} from '@elastic/eui';
import { FormatFactory, LensFilterEvent, LensTableRowContextMenuEvent } from '../../types';
import { VisualizationContainer } from '../../visualization_container';
import { EmptyPlaceholder } from '../../shared_components';
import { LensIconChartDatatable } from '../../assets/chart_datatable';
import { ColumnState } from '../visualization';
import {
DataContextType,
DatatableRenderProps,
LensSortAction,
LensResizeAction,
LensGridDirection,
LensToggleAction,
} from './types';
import { createGridColumns } from './columns';
import { createGridCell } from './cell_value';
import {
createGridFilterHandler,
createGridHideHandler,
createGridResizeHandler,
createGridSortingConfig,
} from './table_actions';
const DataContext = React.createContext<DataContextType>({});
const gridStyle: EuiDataGridStyle = {
border: 'horizontal',
header: 'underline',
};
export interface ColumnConfig {
columns: Array<
ColumnState & {
type: 'lens_datatable_column';
}
>;
sortingColumnId: string | undefined;
sortingDirection: LensGridDirection;
}
export const DatatableComponent = (props: DatatableRenderProps) => {
const [firstTable] = Object.values(props.data.tables);
const [columnConfig, setColumnConfig] = useState({
columns: props.args.columns,
sortingColumnId: props.args.sortingColumnId,
sortingDirection: props.args.sortingDirection,
});
const [firstLocalTable, updateTable] = useState(firstTable);
useDeepCompareEffect(() => {
setColumnConfig({
columns: props.args.columns,
sortingColumnId: props.args.sortingColumnId,
sortingDirection: props.args.sortingDirection,
});
}, [props.args.columns, props.args.sortingColumnId, props.args.sortingDirection]);
useDeepCompareEffect(() => {
updateTable(firstTable);
}, [firstTable]);
const firstTableRef = useRef(firstLocalTable);
firstTableRef.current = firstLocalTable;
const hasAtLeastOneRowClickAction = props.rowHasRowClickTriggerActions?.some((x) => x);
const { getType, dispatchEvent, renderMode, formatFactory } = props;
const formatters: Record<string, ReturnType<FormatFactory>> = useMemo(
() =>
firstLocalTable.columns.reduce(
(map, column) => ({
...map,
[column.id]: formatFactory(column.meta?.params),
}),
{}
),
[firstLocalTable, formatFactory]
);
const onClickValue = useCallback(
(data: LensFilterEvent['data']) => {
dispatchEvent({ name: 'filter', data });
},
[dispatchEvent]
);
const onEditAction = useCallback(
(data: LensSortAction['data'] | LensResizeAction['data'] | LensToggleAction['data']) => {
if (renderMode === 'edit') {
dispatchEvent({ name: 'edit', data });
}
},
[dispatchEvent, renderMode]
);
const onRowContextMenuClick = useCallback(
(data: LensTableRowContextMenuEvent['data']) => {
dispatchEvent({ name: 'tableRowContextMenuClick', data });
},
[dispatchEvent]
);
const handleFilterClick = useMemo(() => createGridFilterHandler(firstTableRef, onClickValue), [
firstTableRef,
onClickValue,
]);
const bucketColumns = useMemo(
() =>
columnConfig.columns
.filter((_col, index) => {
const col = firstTableRef.current.columns[index];
return (
col?.meta?.sourceParams?.type &&
getType(col.meta.sourceParams.type as string)?.type === 'buckets'
);
})
.map((col) => col.columnId),
[firstTableRef, columnConfig, getType]
);
const isEmpty =
firstLocalTable.rows.length === 0 ||
(bucketColumns.length &&
firstTable.rows.every((row) => bucketColumns.every((col) => row[col] == null)));
const visibleColumns = useMemo(
() =>
columnConfig.columns
.filter((col) => !!col.columnId && !col.hidden)
.map((col) => col.columnId),
[columnConfig]
);
const { sortingColumnId: sortBy, sortingDirection: sortDirection } = props.args;
const isReadOnlySorted = renderMode !== 'edit';
const onColumnResize = useMemo(
() => createGridResizeHandler(columnConfig, setColumnConfig, onEditAction),
[onEditAction, setColumnConfig, columnConfig]
);
const onColumnHide = useMemo(
() => createGridHideHandler(columnConfig, setColumnConfig, onEditAction),
[onEditAction, setColumnConfig, columnConfig]
);
const columns: EuiDataGridColumn[] = useMemo(
() =>
createGridColumns(
bucketColumns,
firstLocalTable,
handleFilterClick,
isReadOnlySorted,
columnConfig,
visibleColumns,
formatFactory,
onColumnResize,
onColumnHide
),
[
bucketColumns,
firstLocalTable,
handleFilterClick,
isReadOnlySorted,
columnConfig,
visibleColumns,
formatFactory,
onColumnResize,
onColumnHide,
]
);
const trailingControlColumns: EuiDataGridControlColumn[] = useMemo(() => {
if (!hasAtLeastOneRowClickAction || !onRowContextMenuClick) {
return [];
}
return [
{
headerCellRender: () => null,
width: 40,
id: 'trailingControlColumn',
rowCellRender: function RowCellRender({ rowIndex }) {
const { rowHasRowClickTriggerActions } = useContext(DataContext);
return (
<EuiButtonIcon
aria-label={i18n.translate('xpack.lens.table.actionsLabel', {
defaultMessage: 'Show actions',
})}
iconType={
!!rowHasRowClickTriggerActions && !rowHasRowClickTriggerActions[rowIndex]
? 'empty'
: 'boxesVertical'
}
color="text"
onClick={() => {
onRowContextMenuClick({
rowIndex,
table: firstTableRef.current,
columns: columnConfig.columns.map((col) => col.columnId),
});
}}
/>
);
},
},
];
}, [firstTableRef, onRowContextMenuClick, columnConfig, hasAtLeastOneRowClickAction]);
const renderCellValue = useMemo(() => createGridCell(formatters, DataContext), [formatters]);
const columnVisibility = useMemo(() => ({ visibleColumns, setVisibleColumns: () => {} }), [
visibleColumns,
]);
const sorting = useMemo<EuiDataGridSorting>(
() => createGridSortingConfig(sortBy, sortDirection as LensGridDirection, onEditAction),
[onEditAction, sortBy, sortDirection]
);
if (isEmpty) {
return <EmptyPlaceholder icon={LensIconChartDatatable} />;
}
const dataGridAriaLabel =
props.args.title ||
i18n.translate('xpack.lens.table.defaultAriaLabel', {
defaultMessage: 'Data table visualization',
});
return (
<VisualizationContainer
className="lnsDataTableContainer"
reportTitle={props.args.title}
reportDescription={props.args.description}
>
<DataContext.Provider
value={{
table: firstLocalTable,
rowHasRowClickTriggerActions: props.rowHasRowClickTriggerActions,
}}
>
<EuiDataGrid
aria-label={dataGridAriaLabel}
data-test-subj="lnsDataTable"
columns={columns}
columnVisibility={columnVisibility}
trailingControlColumns={trailingControlColumns}
rowCount={firstLocalTable.rows.length}
renderCellValue={renderCellValue}
gridStyle={gridStyle}
sorting={sorting}
onColumnResize={onColumnResize}
toolbarVisibility={false}
/>
</DataContext.Provider>
</VisualizationContainer>
);
};