From 9a46a7c39a3ddca8d4091c4baa5c89aaaf2a0571 Mon Sep 17 00:00:00 2001 From: Anan Zhuang Date: Thu, 17 Aug 2023 16:23:05 -0700 Subject: [PATCH] [Data Explorer][Discover 2.0] Implement column actions (#4756) * [Data Explorer][Discover 2.0] Emplement column actions Signed-off-by: ananzh * Update discover_slice.tsx Signed-off-by: Ashwin P Chandran --------- Signed-off-by: ananzh Signed-off-by: Ashwin P Chandran Co-authored-by: Ashwin P Chandran Co-authored-by: Ashwin P Chandran --- .../components/data_grid/data_grid_table.tsx | 10 ++-- .../utils/state_management/common.test.ts | 33 ++++++++++++ .../utils/state_management/common.ts | 28 ++++++++++ .../state_management/discover_slice.test.tsx | 32 +++++++++--- .../utils/state_management/discover_slice.tsx | 37 +++++++++---- .../view_components/canvas/discover_table.tsx | 52 +++++++++++++------ 6 files changed, 156 insertions(+), 36 deletions(-) create mode 100644 src/plugins/discover/public/application/utils/state_management/common.test.ts create mode 100644 src/plugins/discover/public/application/utils/state_management/common.ts diff --git a/src/plugins/discover/public/application/components/data_grid/data_grid_table.tsx b/src/plugins/discover/public/application/components/data_grid/data_grid_table.tsx index 02df3aaa814c..46b8bb32708e 100644 --- a/src/plugins/discover/public/application/components/data_grid/data_grid_table.tsx +++ b/src/plugins/discover/public/application/components/data_grid/data_grid_table.tsx @@ -23,12 +23,13 @@ export interface DataGridTableProps { onAddColumn: (column: string) => void; onFilter: DocViewFilterFn; onRemoveColumn: (column: string) => void; - onSort: (sort: string[][]) => void; + onSort: (sort: Array<[string, string]>) => void; rows: OpenSearchSearchHit[]; onSetColumns: (columns: string[]) => void; sort: Array<[string, string]>; displayTimeColumn: boolean; services: DiscoverServices; + isToolbarVisible?: boolean; } export const DataGridTable = ({ @@ -43,6 +44,7 @@ export const DataGridTable = ({ rows, displayTimeColumn, services, + isToolbarVisible = true, }: DataGridTableProps) => { const [expandedHit, setExpandedHit] = useState(); const rowCount = useMemo(() => (rows ? rows.length : 0), [rows]); @@ -70,8 +72,8 @@ export const DataGridTable = ({ const dataGridTableColumnsVisibility = useMemo( () => ({ visibleColumns: computeVisibleColumns(columns, indexPattern, displayTimeColumn) as string[], - setVisibleColumns: (newColumns: string[]) => { - onSetColumns(newColumns); + setVisibleColumns: (cols: string[]) => { + onSetColumns(cols); }, }), [columns, indexPattern, displayTimeColumn, onSetColumns] @@ -116,7 +118,7 @@ export const DataGridTable = ({ renderCellValue={renderCellValue} rowCount={rowCount} sorting={sorting} - toolbarVisibility={toolbarVisibility} + toolbarVisibility={isToolbarVisible ? toolbarVisibility : false} /> diff --git a/src/plugins/discover/public/application/utils/state_management/common.test.ts b/src/plugins/discover/public/application/utils/state_management/common.test.ts new file mode 100644 index 000000000000..c1190f891648 --- /dev/null +++ b/src/plugins/discover/public/application/utils/state_management/common.test.ts @@ -0,0 +1,33 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { addColumn, removeColumn, reorderColumn, setColumns } from './common'; + +describe('commonUtils', () => { + it('should handle addColumn', () => { + expect(addColumn(['column1'], { column: 'column2' })).toEqual(['column1', 'column2']); + expect(addColumn(['column1'], { column: 'column2', index: 0 })).toEqual(['column2', 'column1']); + }); + + it('should handle removeColumn', () => { + expect(removeColumn(['column1', 'column2'], 'column1')).toEqual(['column2']); + }); + + it('should handle reorderColumn', () => { + expect(reorderColumn(['column1', 'column2', 'column3'], 0, 2)).toEqual([ + 'column2', + 'column3', + 'column1', + ]); + }); + + it('should handle setColumns', () => { + expect(setColumns('timeField', ['timeField', 'column1', 'column2'])).toEqual([ + 'column1', + 'column2', + ]); + expect(setColumns(undefined, ['column1', 'column2'])).toEqual(['column1', 'column2']); + }); +}); diff --git a/src/plugins/discover/public/application/utils/state_management/common.ts b/src/plugins/discover/public/application/utils/state_management/common.ts new file mode 100644 index 000000000000..566ee6e468ff --- /dev/null +++ b/src/plugins/discover/public/application/utils/state_management/common.ts @@ -0,0 +1,28 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export const addColumn = (columns: string[], action: { column: string; index?: number }) => { + const { column, index } = action; + const newColumns = [...(columns || [])]; + if (index !== undefined) newColumns.splice(index, 0, column); + else newColumns.push(column); + return newColumns; +}; + +export const removeColumn = (columns: string[], actionColumn: string) => { + return (columns || []).filter((column) => column !== actionColumn); +}; + +export const reorderColumn = (columns: string[], source: number, destination: number) => { + const newColumns = [...(columns || [])]; + const [removed] = newColumns.splice(source, 1); + newColumns.splice(destination, 0, removed); + return newColumns; +}; + +export const setColumns = (timeField: string | undefined, columns: string[]) => { + const newColumns = timeField && timeField === columns[0] ? columns.slice(1) : columns; + return newColumns; +}; diff --git a/src/plugins/discover/public/application/utils/state_management/discover_slice.test.tsx b/src/plugins/discover/public/application/utils/state_management/discover_slice.test.tsx index 71e848bac15e..1e2e4ab46a55 100644 --- a/src/plugins/discover/public/application/utils/state_management/discover_slice.test.tsx +++ b/src/plugins/discover/public/application/utils/state_management/discover_slice.test.tsx @@ -29,20 +29,17 @@ describe('discoverSlice', () => { const action1 = { type: 'discover/addColumn', payload: { column: 'column1' } }; const result1 = discoverSlice.reducer(initialState, action1); expect(result1.columns).toEqual(['column1']); - - const action2 = { type: 'discover/addColumn', payload: { column: 'column2', index: 0 } }; - const result2 = discoverSlice.reducer(result1, action2); - expect(result2.columns).toEqual(['column2', 'column1']); }); it('should handle removeColumn', () => { initialState = { columns: ['column1', 'column2'], - sort: [], + sort: [['column1', 'asc']], }; const action = { type: 'discover/removeColumn', payload: 'column1' }; const result = discoverSlice.reducer(initialState, action); expect(result.columns).toEqual(['column2']); + expect(result.sort).toEqual([]); }); it('should handle reorderColumn', () => { @@ -50,17 +47,38 @@ describe('discoverSlice', () => { columns: ['column1', 'column2', 'column3'], sort: [], }; - const action = { type: 'discover/reorderColumn', payload: { source: 0, destination: 2 } }; + const action = { + type: 'discover/reorderColumn', + payload: { source: 0, destination: 2 }, + }; const result = discoverSlice.reducer(initialState, action); expect(result.columns).toEqual(['column2', 'column3', 'column1']); }); + it('should handle setColumns', () => { + const action = { + type: 'discover/setColumns', + payload: { timeField: 'timeField', columns: ['timeField', 'column1', 'column2'] }, + }; + const result = discoverSlice.reducer(initialState, action); + expect(result.columns).toEqual(['column1', 'column2']); + }); + + it('should handle setSort', () => { + const action = { type: 'discover/setSort', payload: [['field1', 'asc']] }; + const result = discoverSlice.reducer(initialState, action); + expect(result.sort).toEqual([['field1', 'asc']]); + }); + it('should handle updateState', () => { initialState = { columns: ['column1', 'column2'], sort: [['field1', 'asc']], }; - const action = { type: 'discover/updateState', payload: { sort: [['field2', 'desc']] } }; + const action = { + type: 'discover/updateState', + payload: { sort: [['field2', 'desc']] }, + }; const result = discoverSlice.reducer(initialState, action); expect(result.sort).toEqual([['field2', 'desc']]); }); diff --git a/src/plugins/discover/public/application/utils/state_management/discover_slice.tsx b/src/plugins/discover/public/application/utils/state_management/discover_slice.tsx index ecb5a9b59d6b..38c8092d5f73 100644 --- a/src/plugins/discover/public/application/utils/state_management/discover_slice.tsx +++ b/src/plugins/discover/public/application/utils/state_management/discover_slice.tsx @@ -9,6 +9,7 @@ import { Filter, Query } from '../../../../../data/public'; import { DiscoverServices } from '../../../build_services'; import { RootState, DefaultViewState } from '../../../../../data_explorer/public'; import { buildColumns } from '../columns'; +import * as utils from './common'; export interface DiscoverState { /** @@ -95,31 +96,45 @@ export const discoverSlice = createSlice({ return action.payload; }, addColumn(state, action: PayloadAction<{ column: string; index?: number }>) { - const { column, index } = action.payload; - const columns = [...(state.columns || [])]; - if (index !== undefined) columns.splice(index, 0, column); - else columns.push(column); - return { ...state, columns: buildColumns(columns), isDirty: true }; + const columns = utils.addColumn(state.columns || [], action.payload); + return { ...state, columns: buildColumns(columns) }; }, removeColumn(state, action: PayloadAction) { - const columns = (state.columns || []).filter((column) => column !== action.payload); + const columns = utils.removeColumn(state.columns, action.payload); + const sort = + state.sort && state.sort.length ? state.sort.filter((s) => s[0] !== action.payload) : []; return { ...state, columns: buildColumns(columns), + sort, isDirty: true, }; }, reorderColumn(state, action: PayloadAction<{ source: number; destination: number }>) { - const { source, destination } = action.payload; - const columns = [...(state.columns || [])]; - const [removed] = columns.splice(source, 1); - columns.splice(destination, 0, removed); + const columns = utils.reorderColumn( + state.columns, + action.payload.source, + action.payload.destination + ); return { ...state, columns, isDirty: true, }; }, + setColumns(state, action: PayloadAction<{ timeField: string | undefined; columns: string[] }>) { + const columns = utils.setColumns(action.payload.timeField, action.payload.columns); + return { + ...state, + columns, + }; + }, + setSort(state, action: PayloadAction>) { + return { + ...state, + sort: action.payload, + }; + }, updateState(state, action: PayloadAction>) { return { ...state, @@ -141,6 +156,8 @@ export const { addColumn, removeColumn, reorderColumn, + setColumns, + setSort, setState, updateState, setSavedSearchId, diff --git a/src/plugins/discover/public/application/view_components/canvas/discover_table.tsx b/src/plugins/discover/public/application/view_components/canvas/discover_table.tsx index 308b3b6fe68d..814788406f7c 100644 --- a/src/plugins/discover/public/application/view_components/canvas/discover_table.tsx +++ b/src/plugins/discover/public/application/view_components/canvas/discover_table.tsx @@ -3,14 +3,23 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import { History } from 'history'; import { DiscoverViewServices } from '../../../build_services'; import { useOpenSearchDashboards } from '../../../../../opensearch_dashboards_react/public'; import { DataGridTable } from '../../components/data_grid/data_grid_table'; import { useDiscoverContext } from '../context'; -import { addColumn, removeColumn, useDispatch, useSelector } from '../../utils/state_management'; +import { + addColumn, + removeColumn, + setColumns, + setSort, + useDispatch, + useSelector, +} from '../../utils/state_management'; import { ResultStatus, SearchData } from '../utils/use_search'; +import { IndexPatternField, opensearchFilters } from '../../../../../data/public'; +import { DocViewFilterFn } from '../../doc_views/doc_views_types'; interface Props { history: History; @@ -18,14 +27,33 @@ interface Props { export const DiscoverTable = ({ history }: Props) => { const { services } = useOpenSearchDashboards(); + const { filterManager } = services.data.query; const { data$, indexPattern } = useDiscoverContext(); const [fetchState, setFetchState] = useState({ status: data$.getValue().status, rows: [], }); - const { columns } = useSelector((state) => state.discover); + const { columns, sort } = useSelector((state) => state.discover); const dispatch = useDispatch(); + const onAddColumn = (col: string) => dispatch(addColumn({ column: col })); + const onRemoveColumn = (col: string) => dispatch(removeColumn(col)); + const onSetColumns = (cols: string[]) => + dispatch(setColumns({ timefield: indexPattern.timeFieldName, columns: cols })); + const onSetSort = (s: Array<[string, string]>) => dispatch(setSort(s)); + const onAddFilter = useCallback( + (field: IndexPatternField, values: string, operation: '+' | '-') => { + const newFilters = opensearchFilters.generateFilters( + filterManager, + field, + values, + operation, + indexPattern.id + ); + return filterManager.addFilters(newFilters); + }, + [filterManager, indexPattern] + ); const { rows } = fetchState || {}; @@ -55,18 +83,12 @@ export const DiscoverTable = ({ history }: Props) => { - dispatch( - addColumn({ - column, - }) - ) - } - onFilter={() => {}} - onRemoveColumn={(column) => dispatch(removeColumn(column))} - onSetColumns={() => {}} - onSort={() => {}} - sort={[]} + onAddColumn={onAddColumn} + onFilter={onAddFilter as DocViewFilterFn} + onRemoveColumn={onRemoveColumn} + onSetColumns={onSetColumns} + onSort={onSetSort} + sort={sort} rows={rows} displayTimeColumn={true} services={services}