From 65181fb0894665ac254c09a439878d99fca6d4e9 Mon Sep 17 00:00:00 2001 From: Anan Zhuang Date: Mon, 3 Oct 2022 18:23:52 +0000 Subject: [PATCH] [Table Visualization] make table vis column sortable * add sort state (asc | desc) to column Partially resolve: https://github.com/opensearch-project/OpenSearch-Dashboards/issues/2305 Signed-off-by: Anan Zhuang --- .../public/components/table_vis_component.tsx | 127 +++++++++--------- .../components/table_vis_grid_columns.tsx | 21 ++- .../vis_type_table/public/table_vis_fn.ts | 3 + src/plugins/vis_type_table/public/types.ts | 5 + 4 files changed, 85 insertions(+), 71 deletions(-) diff --git a/src/plugins/vis_type_table/public/components/table_vis_component.tsx b/src/plugins/vis_type_table/public/components/table_vis_component.tsx index 1a597fa15ddb..5d4739c4eaa5 100644 --- a/src/plugins/vis_type_table/public/components/table_vis_component.tsx +++ b/src/plugins/vis_type_table/public/components/table_vis_component.tsx @@ -4,14 +4,15 @@ */ import React, { useState, useCallback, useMemo, useRef, useEffect } from 'react'; -import { isEqual } from 'lodash'; -import { EuiDataGridProps, EuiDataGrid } from '@elastic/eui'; +import { isEqual, orderBy } from 'lodash'; +import { EuiDataGridProps, EuiDataGrid, EuiDataGridSorting } from '@elastic/eui'; import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; import { Table } from '../table_vis_response_handler'; -import { TableVisConfig, ColumnWidth } from '../types'; +import { TableVisConfig, ColumnWidth, SortColumn } from '../types'; import { getDataGridColumns } from './table_vis_grid_columns'; import { usePagination } from '../utils'; +import { convertToFormattedData } from '../utils/convert_to_formatted_data'; interface TableVisComponentProps { table: Table; @@ -20,77 +21,82 @@ interface TableVisComponentProps { } export const TableVisComponent = ({ table, visConfig, handlers }: TableVisComponentProps) => { - const { rows, columns } = table; + const { formattedRows: rows, formattedColumns: columns } = convertToFormattedData( + table, + visConfig + ); const pagination = usePagination(visConfig, rows.length); - // toDo: it is a sample renderCellValue to render a data grid component - // will check on it and it might be replaced - const renderCellValue = useMemo(() => { - return (({ rowIndex, columnId }) => { - let adjustedRowIndex = rowIndex; + // store current state + const currentColState = useRef<{ + columnsWidth: ColumnWidth[]; + }>({ + columnsWidth: handlers.uiState.get('vis.columnsWidth'), + }); - // If we are doing the pagination (instead of leaving that to the grid) - // then the row index must be adjusted as `data` has already been pruned to the page size - adjustedRowIndex = rowIndex - pagination!.pageIndex * pagination!.pageSize; + const sortedRows = useMemo(() => { + const sort = handlers.uiState.get('vis.sortColumn'); + return sort && sort.colIndex !== null && sort.direction + ? orderBy(rows, columns[sort.colIndex]?.id, sort.direction) + : rows; + }, [columns, rows, handlers.uiState]); - return rows.hasOwnProperty(adjustedRowIndex) - ? rows[adjustedRowIndex][columnId] || null - : null; + const renderCellValue = useMemo(() => { + return (({ rowIndex, columnId }) => { + return sortedRows.hasOwnProperty(rowIndex) ? sortedRows[rowIndex][columnId] || null : null; }) as EuiDataGridProps['renderCellValue']; - }, [rows, pagination]); + }, [sortedRows]); + + const dataGridColumns = getDataGridColumns( + sortedRows, + columns, + table, + handlers, + currentColState.current.columnsWidth + ); - // resize column - const [columnsWidth, setColumnsWidth] = useState( - handlers.uiState.get('vis.columnsWidth') || [] + const sortedColumns = useMemo(() => { + const sort = handlers.uiState.get('vis.sortColumn'); + return sort && sort.colIndex !== null && sort.direction + ? [{ id: dataGridColumns[sort.colIndex]?.id, direction: sort.direction }] + : []; + }, [handlers.uiState, dataGridColumns]); + + const onSort = useCallback( + (sortingCols: EuiDataGridSorting['columns']) => { + const nextSortValue = sortingCols[sortingCols.length - 1]; + const nextSort = { + colIndex: dataGridColumns.findIndex((col) => col.id === nextSortValue?.id), + direction: nextSortValue.direction, + }; + handlers.uiState.set('vis.sortColumn', nextSort); + handlers.uiState?.emit('reload'); + return nextSort; + }, + [dataGridColumns, handlers.uiState] ); - const curColumnsWidth = useRef<{ - columnsWidth: ColumnWidth[]; - }>({ - columnsWidth: handlers.uiState?.get('vis.columnsWidth'), - }); const onColumnResize: EuiDataGridProps['onColumnResize'] = useCallback( ({ columnId, width }) => { - setColumnsWidth((prevState) => { - const nextColIndex = columns.findIndex((c) => c.id === columnId); - const prevColIndex = prevState.findIndex((c) => c.colIndex === nextColIndex); - const nextState = [...prevState]; - const updatedColWidth = { colIndex: nextColIndex, width }; - - // if updated column index is not found, then add it to nextState - // else reset it in nextState - if (prevColIndex < 0) nextState.push(updatedColWidth); - else nextState[prevColIndex] = updatedColWidth; - - // update uiState - handlers.uiState?.set('vis.columnsWidth', nextState); - return nextState; - }); + const prevState: ColumnWidth[] = currentColState.current.columnsWidth; + const nextColIndex = columns.findIndex((col) => col.id === columnId); + const prevColIndex = prevState.findIndex((col) => col.colIndex === nextColIndex); + const nextState = [...prevState]; + const updatedColWidth = { colIndex: nextColIndex, width }; + + // if updated column index is not found, then add it to nextState + // else reset it in nextState + if (prevColIndex < 0) nextState.push(updatedColWidth); + else nextState[prevColIndex] = updatedColWidth; + + // update uiState + currentColState.current.columnsWidth = nextState; + handlers.uiState.set('vis.columnsWidth', nextState); }, - [columns, setColumnsWidth, handlers.uiState] + [columns, currentColState, handlers.uiState] ); - useEffect(() => { - const updateTable = () => { - const updatedVisState = handlers.uiState?.getChanges()?.vis; - if (!isEqual(updatedVisState?.columnsWidth, curColumnsWidth.current.columnsWidth)) { - curColumnsWidth.current.columnsWidth = updatedVisState?.columnsWidth; - setColumnsWidth(updatedVisState?.columnsWidth || []); - } - }; - - if (handlers.uiState) { - handlers.uiState.on('change', updateTable); - } - - return () => { - handlers.uiState?.off('change', updateTable); - }; - }, [handlers.uiState]); - - const dataGridColumns = getDataGridColumns(table, visConfig, handlers, columnsWidth); - return ( diff --git a/src/plugins/vis_type_table/public/components/table_vis_grid_columns.tsx b/src/plugins/vis_type_table/public/components/table_vis_grid_columns.tsx index b1a9443fe059..3f8d7bcd112e 100644 --- a/src/plugins/vis_type_table/public/components/table_vis_grid_columns.tsx +++ b/src/plugins/vis_type_table/public/components/table_vis_grid_columns.tsx @@ -7,20 +7,19 @@ import React from 'react'; import { i18n } from '@osd/i18n'; import { EuiDataGridColumn, EuiDataGridColumnCellActionProps } from '@elastic/eui'; import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import { OpenSearchDashboardsDatatableRow } from 'src/plugins/expressions'; import { Table } from '../table_vis_response_handler'; -import { TableVisConfig, ColumnWidth } from '../types'; -import { convertToFormattedData } from '../utils'; +import { ColumnWidth, FormattedColumn } from '../types'; export const getDataGridColumns = ( + rows: OpenSearchDashboardsDatatableRow[], + cols: FormattedColumn[], table: Table, - visConfig: TableVisConfig, handlers: IInterpreterRenderHandlers, columnsWidth: ColumnWidth[] ) => { - const { formattedRows, formattedColumns } = convertToFormattedData(table, visConfig); - const filterBucket = (rowIndex: number, columnIndex: number, negate: boolean) => { - const foramttedColumnId = formattedColumns[columnIndex].id; + const foramttedColumnId = cols[columnIndex].id; const rawColumnIndex = table.columns.findIndex((col) => col.id === foramttedColumnId); handlers.event({ name: 'filterBucket', @@ -29,7 +28,7 @@ export const getDataGridColumns = ( { table: { columns: table.columns, - rows: formattedRows, + rows, }, row: rowIndex, column: rawColumnIndex, @@ -40,12 +39,12 @@ export const getDataGridColumns = ( }); }; - return formattedColumns.map((col, colIndex) => { + return cols.map((col, colIndex) => { const cellActions = col.filterable ? [ ({ rowIndex, columnId, Component, closePopover }: EuiDataGridColumnCellActionProps) => { - const filterValue = formattedRows[rowIndex][columnId]; - const filterContent = col.formatter?.convert(formattedRows[rowIndex][columnId]); + const filterValue = rows[rowIndex][columnId]; + const filterContent = col.formatter?.convert(filterValue); const filterForValueText = i18n.translate( 'visTypeTable.tableVisFilter.filterForValue', @@ -80,7 +79,7 @@ export const getDataGridColumns = ( ); }, ({ rowIndex, columnId, Component, closePopover }: EuiDataGridColumnCellActionProps) => { - const filterValue = formattedRows[rowIndex][columnId]; + const filterValue = rows[rowIndex][columnId]; const filterContent = col.formatter?.convert(filterValue); const filterOutValueText = i18n.translate( diff --git a/src/plugins/vis_type_table/public/table_vis_fn.ts b/src/plugins/vis_type_table/public/table_vis_fn.ts index 4f0fb2c0ba1f..3e3bd38dba47 100644 --- a/src/plugins/vis_type_table/public/table_vis_fn.ts +++ b/src/plugins/vis_type_table/public/table_vis_fn.ts @@ -57,6 +57,9 @@ export const createTableVisFn = (): TableVisExpressionFunctionDefinition => ({ visType: 'table', visConfig, }, + params: { + listenOnChange: true, + }, }; }, }); diff --git a/src/plugins/vis_type_table/public/types.ts b/src/plugins/vis_type_table/public/types.ts index 2a8cd5f9108e..f93f338893ca 100644 --- a/src/plugins/vis_type_table/public/types.ts +++ b/src/plugins/vis_type_table/public/types.ts @@ -69,3 +69,8 @@ export interface ColumnWidth { colIndex: number; width: number; } + +export interface SortColumn { + colIndex: number; + direction: 'asc' | 'desc'; +}