From 0901ddc40ac9e52d5f963a2b1aaf9453f7a19986 Mon Sep 17 00:00:00 2001 From: Anan Zhuang Date: Fri, 2 Feb 2024 11:04:48 -0800 Subject: [PATCH] Resolve sort, default sort and short dot (#5771) Signed-off-by: Anan Z Signed-off-by: Anan Zhuang --- .../components/data_grid/data_grid_table.tsx | 47 +++++--- .../data_grid/data_grid_table_cell_value.tsx | 7 +- .../default_discover_table.tsx | 46 ++++---- .../default_discover_table/helper.tsx | 101 ++++++++++++++++++ .../default_discover_table/table_header.tsx | 72 ++++--------- .../table_header_column.tsx | 51 +++------ .../default_discover_table/table_rows.tsx | 29 +++-- .../components/doc_views/context_app.tsx | 1 + .../utils/state_management/common.test.ts | 39 ++++++- .../utils/state_management/common.ts | 10 ++ .../state_management/discover_slice.test.tsx | 52 +++++++++ .../utils/state_management/discover_slice.tsx | 14 ++- .../view_components/canvas/discover_table.tsx | 6 ++ .../search_embeddable_component.tsx | 1 + 14 files changed, 333 insertions(+), 143 deletions(-) create mode 100644 src/plugins/discover/public/application/components/default_discover_table/helper.tsx 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 f2ee6d078e2b..864a2be1077a 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 @@ -5,7 +5,7 @@ import React, { useState, useMemo, useCallback } from 'react'; import { EuiDataGrid, EuiDataGridSorting, EuiPanel } from '@elastic/eui'; -import { IndexPattern } from '../../../opensearch_dashboards_services'; +import { IndexPattern, getServices } from '../../../opensearch_dashboards_services'; import { fetchTableDataCell } from './data_grid_table_cell_value'; import { buildDataGridColumns, computeVisibleColumns } from './data_grid_table_columns'; import { DocViewInspectButton } from './data_grid_table_docview_inspect_button'; @@ -13,24 +13,30 @@ import { DataGridFlyout } from './data_grid_table_flyout'; import { DiscoverGridContextProvider } from './data_grid_table_context'; import { DocViewFilterFn, OpenSearchSearchHit } from '../../doc_views/doc_views_types'; import { usePagination } from '../utils/use_pagination'; -import { SortOrder } from '../../../saved_searches/types'; import { buildColumns } from '../../utils/columns'; import { useOpenSearchDashboards } from '../../../../../opensearch_dashboards_react/public'; import { DiscoverServices } from '../../../build_services'; -import { SAMPLE_SIZE_SETTING } from '../../../../common'; +import { + DOC_HIDE_TIME_COLUMN_SETTING, + SAMPLE_SIZE_SETTING, + SORT_DEFAULT_ORDER_SETTING, +} from '../../../../common'; +import { UI_SETTINGS } from '../../../../../data/common'; import { LegacyDiscoverTable } from '../default_discover_table/default_discover_table'; import { toolbarVisibility } from './constants'; import { getDataGridTableSetting } from '../utils/local_storage'; import { Storage } from '../../../../../opensearch_dashboards_utils/public'; +import { SortOrder } from '../default_discover_table/helper'; export interface DataGridTableProps { columns: string[]; indexPattern: IndexPattern; onAddColumn: (column: string) => void; onFilter: DocViewFilterFn; + onMoveColumn: (colName: string, destination: number) => void; onRemoveColumn: (column: string) => void; onReorderColumn: (col: string, source: number, destination: number) => void; - onSort: (sort: SortOrder[]) => void; + onSort: (s: SortOrder[]) => void; rows: OpenSearchSearchHit[]; onSetColumns: (columns: string[]) => void; sort: SortOrder[]; @@ -49,6 +55,7 @@ export const DataGridTable = ({ indexPattern, onAddColumn, onFilter, + onMoveColumn, onRemoveColumn, onReorderColumn, onSetColumns, @@ -64,11 +71,17 @@ export const DataGridTable = ({ storage, showPagination, }: DataGridTableProps) => { - const { services } = useOpenSearchDashboards(); - + const services = getServices(); const [inspectedHit, setInspectedHit] = useState(); const rowCount = useMemo(() => (rows ? rows.length : 0), [rows]); - const pageSizeLimit = services.uiSettings?.get(SAMPLE_SIZE_SETTING); + const [pageSizeLimit, isShortDots, hideTimeColumn, defaultSortOrder] = useMemo(() => { + return [ + services.uiSettings.get(SAMPLE_SIZE_SETTING), + services.uiSettings.get(UI_SETTINGS.SHORT_DOTS_ENABLE), + services.uiSettings.get(DOC_HIDE_TIME_COLUMN_SETTING), + services.uiSettings.get(SORT_DEFAULT_ORDER_SETTING, 'desc'), + ]; + }, [services.uiSettings]); const pagination = usePagination({ rowCount, pageSizeLimit }); let adjustedColumns = buildColumns(columns); @@ -151,34 +164,38 @@ export const DataGridTable = ({ const legacyDiscoverTable = useMemo( () => ( setInspectedHit(undefined)} sampleSize={pageSizeLimit} showPagination={showPagination} + isShortDots={isShortDots} + hideTimeColumn={hideTimeColumn} + defaultSortOrder={defaultSortOrder} /> ), [ - displayedTableColumns, adjustedColumns, rows, indexPattern, - sortingColumns, - onColumnSort, + sort, + onSort, onRemoveColumn, - onReorderColumn, + onMoveColumn, onAddColumn, onFilter, pageSizeLimit, showPagination, + defaultSortOrder, + hideTimeColumn, + isShortDots, ] ); diff --git a/src/plugins/discover/public/application/components/data_grid/data_grid_table_cell_value.tsx b/src/plugins/discover/public/application/components/data_grid/data_grid_table_cell_value.tsx index 7c4201f93127..3369e23a7a8a 100644 --- a/src/plugins/discover/public/application/components/data_grid/data_grid_table_cell_value.tsx +++ b/src/plugins/discover/public/application/components/data_grid/data_grid_table_cell_value.tsx @@ -16,18 +16,21 @@ import { import { stringify } from '@osd/std'; import { IndexPattern } from '../../../opensearch_dashboards_services'; import { OpenSearchSearchHit } from '../../doc_views/doc_views_types'; +import { shortenDottedString } from '../../helpers'; export function fetchSourceTypeDataCell( idxPattern: IndexPattern, row: Record, columnId: string, - isDetails: boolean + isDetails: boolean, + isShortDots: boolean ) { if (isDetails) { return {stringify(row[columnId], null, 2)}; } const formattedRow = idxPattern.formatHit(row); - const keys = Object.keys(formattedRow); + const rawKeys = Object.keys(formattedRow); + const keys = isShortDots ? rawKeys.map((k) => shortenDottedString(k)) : rawKeys; return ( diff --git a/src/plugins/discover/public/application/components/default_discover_table/default_discover_table.tsx b/src/plugins/discover/public/application/components/default_discover_table/default_discover_table.tsx index 496237d7ca8d..7a3dffea94c7 100644 --- a/src/plugins/discover/public/application/components/default_discover_table/default_discover_table.tsx +++ b/src/plugins/discover/public/application/components/default_discover_table/default_discover_table.tsx @@ -9,8 +9,6 @@ import React, { useEffect, useRef, useState } from 'react'; import { EuiButtonEmpty, EuiCallOut, - EuiDataGridColumn, - EuiDataGridSorting, EuiProgress, EuiPagination, EuiFlexGroup, @@ -21,41 +19,51 @@ import { TableHeader } from './table_header'; import { DocViewFilterFn, OpenSearchSearchHit } from '../../doc_views/doc_views_types'; import { TableRow } from './table_rows'; import { IndexPattern } from '../../../opensearch_dashboards_services'; +import { SortOrder } from './helper'; +import { getLegacyDisplayedColumns } from './helper'; export interface DefaultDiscoverTableProps { - displayedTableColumns: EuiDataGridColumn[]; columns: string[]; rows: OpenSearchSearchHit[]; indexPattern: IndexPattern; - sortOrder: Array<{ - id: string; - direction: 'asc' | 'desc'; - }>; - onChangeSortOrder: (cols: EuiDataGridSorting['columns']) => void; + sort: SortOrder[]; + onSort: (s: SortOrder[]) => void; onRemoveColumn: (column: string) => void; - onReorderColumn: (col: string, source: number, destination: number) => void; + onReorderColumn: (colName: string, destination: number) => void; onAddColumn: (column: string) => void; onFilter: DocViewFilterFn; onClose: () => void; sampleSize: number; + isShortDots: boolean; + hideTimeColumn: boolean; + defaultSortOrder: string; showPagination?: boolean; } export const LegacyDiscoverTable = ({ - displayedTableColumns, columns, rows, indexPattern, - sortOrder, - onChangeSortOrder, + sort, + onSort, onRemoveColumn, onReorderColumn, onAddColumn, onFilter, onClose, sampleSize, + isShortDots, + hideTimeColumn, + defaultSortOrder, showPagination, }: DefaultDiscoverTableProps) => { + const displayedColumns = getLegacyDisplayedColumns( + columns, + indexPattern, + hideTimeColumn, + isShortDots + ); + const displayedColumnNames = displayedColumns.map((column) => column.name); const pageSize = 50; const [renderedRowCount, setRenderedRowCount] = useState(50); // Start with 50 rows const [displayedRows, setDisplayedRows] = useState(rows.slice(0, pageSize)); @@ -137,15 +145,13 @@ export const LegacyDiscoverTable = ({ @@ -155,13 +161,13 @@ export const LegacyDiscoverTable = ({ column.id)} - columns={columns} + columns={displayedColumnNames} indexPattern={indexPattern} onRemoveColumn={onRemoveColumn} onAddColumn={onAddColumn} onFilter={onFilter} onClose={onClose} + isShortDots={isShortDots} /> ); } diff --git a/src/plugins/discover/public/application/components/default_discover_table/helper.tsx b/src/plugins/discover/public/application/components/default_discover_table/helper.tsx new file mode 100644 index 000000000000..71df56fe8383 --- /dev/null +++ b/src/plugins/discover/public/application/components/default_discover_table/helper.tsx @@ -0,0 +1,101 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IndexPattern } from '../../../opensearch_dashboards_services'; +import { shortenDottedString } from '../../helpers'; + +export type SortOrder = [string, string]; +export interface LegacyDisplayedColumn { + name: string; + displayName: string; + isSortable: boolean; + isRemoveable: boolean; + colLeftIdx: number; + colRightIdx: number; +} + +export interface ColumnProps { + name: string; + displayName: string; + isSortable: boolean; + isRemoveable: boolean; + colLeftIdx: number; + colRightIdx: number; +} + +/** + * Returns properties necessary to display the time column + * If it's an IndexPattern with timefield, the time column is + * prepended, not moveable and removeable + * @param timeFieldName + */ +export function getTimeColumn(timeFieldName: string): ColumnProps { + return { + name: timeFieldName, + displayName: 'Time', + isSortable: true, + isRemoveable: false, + colLeftIdx: -1, + colRightIdx: -1, + }; +} +/** + * A given array of column names returns an array of properties + * necessary to display the columns. If the given indexPattern + * has a timefield, a time column is prepended + * @param columns + * @param indexPattern + * @param hideTimeField + * @param isShortDots + */ +export function getLegacyDisplayedColumns( + columns: string[], + indexPattern: IndexPattern, + hideTimeField: boolean, + isShortDots: boolean +): LegacyDisplayedColumn[] { + if (!Array.isArray(columns) || typeof indexPattern !== 'object' || !indexPattern.getFieldByName) { + return []; + } + const columnProps = columns.map((column, idx) => { + const field = indexPattern.getFieldByName(column); + return { + name: column, + displayName: isShortDots ? shortenDottedString(column) : column, + isSortable: field && field.sortable ? true : false, + isRemoveable: column !== '_source' || columns.length > 1, + colLeftIdx: idx - 1 < 0 ? -1 : idx - 1, + colRightIdx: idx + 1 >= columns.length ? -1 : idx + 1, + }; + }); + return !hideTimeField && indexPattern.timeFieldName + ? [getTimeColumn(indexPattern.timeFieldName), ...columnProps] + : columnProps; +} diff --git a/src/plugins/discover/public/application/components/default_discover_table/table_header.tsx b/src/plugins/discover/public/application/components/default_discover_table/table_header.tsx index 6715910e8346..75ae73a82434 100644 --- a/src/plugins/discover/public/application/components/default_discover_table/table_header.tsx +++ b/src/plugins/discover/public/application/components/default_discover_table/table_header.tsx @@ -12,77 +12,47 @@ import './_table_header.scss'; import React from 'react'; -import { EuiDataGridColumn, EuiDataGridSorting } from '@elastic/eui'; import { IndexPattern } from '../../../opensearch_dashboards_services'; import { TableHeaderColumn } from './table_header_column'; +import { SortOrder, LegacyDisplayedColumn } from './helper'; +import { getDefaultSort } from '../../view_components/utils/get_default_sort'; interface Props { - displayedTableColumns: EuiDataGridColumn[]; - // columns: string[]; + displayedColumns: LegacyDisplayedColumn[]; defaultSortOrder: string; - // hideTimeColumn: boolean; indexPattern: IndexPattern; - // isShortDots: boolean; - onChangeSortOrder?: (cols: EuiDataGridSorting['columns']) => void; - onMoveColumn?: (name: string, index: number) => void; + onChangeSortOrder?: (sortOrder: SortOrder[]) => void; onRemoveColumn?: (name: string) => void; - onReorderColumn?: (col: string, source: number, destination: number) => void; - sortOrder: Array<{ - id: string; - direction: 'desc' | 'asc'; - }>; + onReorderColumn?: (colName: string, destination: number) => void; + sortOrder: SortOrder[]; } export function TableHeader({ - displayedTableColumns, + displayedColumns, defaultSortOrder, - // hideTimeColumn, indexPattern, - // isShortDots, onChangeSortOrder, onReorderColumn, onRemoveColumn, sortOrder, }: Props) { - const timeColName = indexPattern.timeFieldName; return ( ); } diff --git a/src/plugins/discover/public/application/components/default_discover_table/table_header_column.tsx b/src/plugins/discover/public/application/components/default_discover_table/table_header_column.tsx index b6bc25b2afa7..44eba17f1dfe 100644 --- a/src/plugins/discover/public/application/components/default_discover_table/table_header_column.tsx +++ b/src/plugins/discover/public/application/components/default_discover_table/table_header_column.tsx @@ -13,24 +13,20 @@ import './_table_header.scss'; import React, { ReactNode } from 'react'; import { i18n } from '@osd/i18n'; -import { EuiButtonIcon, EuiDataGridSorting, EuiToolTip } from '@elastic/eui'; +import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; +import { SortOrder } from './helper'; interface Props { - currentIdx: number; colLeftIdx: number; // idx of the column to the left, -1 if moving is not possible colRightIdx: number; // idx of the column to the right, -1 if moving is not possible displayName: ReactNode; isRemoveable: boolean; isSortable?: boolean; name: string; - onChangeSortOrder?: (cols: EuiDataGridSorting['columns']) => void; - onMoveColumn?: (name: string, idx: number) => void; - onReorderColumn?: (col: string, source: number, destination: number) => void; + onChangeSortOrder?: (sortOrder: SortOrder[]) => void; + onReorderColumn?: (colName: string, destination: number) => void; onRemoveColumn?: (name: string) => void; - sortOrder: Array<{ - id: string; - direction: 'desc' | 'asc'; - }>; + sortOrder: SortOrder[]; } const sortDirectionToIcon: Record = { @@ -40,7 +36,6 @@ const sortDirectionToIcon: Record = { }; export function TableHeaderColumn({ - currentIdx, colLeftIdx, colRightIdx, displayName, @@ -52,9 +47,9 @@ export function TableHeaderColumn({ onRemoveColumn, sortOrder, }: Props) { - const currentSortWithoutColumn = sortOrder.filter((pair) => pair.id !== name); - const currentColumnSort = sortOrder.find((pair) => pair.id === name); - const currentColumnSortDirection = (currentColumnSort && currentColumnSort.direction) || ''; + const currentSortWithoutColumn = sortOrder.filter((pair) => pair[0] !== name); + const currentColumnSort = sortOrder.find((pair) => pair[0] === name); + const currentColumnSortDirection = (currentColumnSort && currentColumnSort[1]) || ''; const btnSortIcon = sortDirectionToIcon[currentColumnSortDirection]; const btnSortClassName = @@ -65,35 +60,15 @@ export function TableHeaderColumn({ const handleChangeSortOrder = () => { if (!onChangeSortOrder) return; - let currentSortOrder; - let newSortOrder: { - id: string; - direction: 'desc' | 'asc'; - }; // Cycle goes Unsorted -> Asc -> Desc -> Unsorted if (currentColumnSort === undefined) { - newSortOrder = { - id: name, - direction: 'asc', - }; - currentSortOrder = [...currentSortWithoutColumn, newSortOrder]; - onChangeSortOrder(currentSortOrder); + onChangeSortOrder([...currentSortWithoutColumn, [name, 'asc']]); } else if (currentColumnSortDirection === 'asc') { - newSortOrder = { - id: name, - direction: 'desc', - }; - currentSortOrder = [...currentSortWithoutColumn, newSortOrder]; - onChangeSortOrder(currentSortOrder); + onChangeSortOrder([...currentSortWithoutColumn, [name, 'desc']]); } else if (currentColumnSortDirection === 'desc' && currentSortWithoutColumn.length === 0) { // If we're at the end of the cycle and this is the only existing sort, we switch // back to ascending sort instead of removing it. - newSortOrder = { - id: name, - direction: 'asc', - }; - currentSortOrder = [...currentSortWithoutColumn, newSortOrder]; - onChangeSortOrder(currentSortOrder); + onChangeSortOrder([...currentSortWithoutColumn, [name, 'asc']]); } else { onChangeSortOrder(currentSortWithoutColumn); } @@ -168,7 +143,7 @@ export function TableHeaderColumn({ values: { columnName: name }, }), className: 'fa fa-angle-double-left osdDocTableHeader__move', - onClick: () => onReorderColumn && onReorderColumn(name, currentIdx, colLeftIdx), + onClick: () => onReorderColumn && onReorderColumn(name, colLeftIdx), testSubject: `docTableMoveLeftHeader-${name}`, tooltip: i18n.translate('discover.docTable.tableHeader.moveColumnLeftButtonTooltip', { defaultMessage: 'Move column to the left', @@ -183,7 +158,7 @@ export function TableHeaderColumn({ values: { columnName: name }, }), className: 'fa fa-angle-double-right osdDocTableHeader__move', - onClick: () => onReorderColumn && onReorderColumn(name, currentIdx, colRightIdx), + onClick: () => onReorderColumn && onReorderColumn(name, colRightIdx), testSubject: `docTableMoveRightHeader-${name}`, tooltip: i18n.translate('discover.docTable.tableHeader.moveColumnRightButtonTooltip', { defaultMessage: 'Move column to the right', diff --git a/src/plugins/discover/public/application/components/default_discover_table/table_rows.tsx b/src/plugins/discover/public/application/components/default_discover_table/table_rows.tsx index 9170d97921d2..7da2865e24d9 100644 --- a/src/plugins/discover/public/application/components/default_discover_table/table_rows.tsx +++ b/src/plugins/discover/public/application/components/default_discover_table/table_rows.tsx @@ -21,24 +21,24 @@ import { fetchSourceTypeDataCell } from '../data_grid/data_grid_table_cell_value export interface TableRowProps { row: OpenSearchSearchHit; - columnIds: string[]; columns: string[]; indexPattern: IndexPattern; onRemoveColumn: (column: string) => void; onAddColumn: (column: string) => void; onFilter: DocViewFilterFn; onClose: () => void; + isShortDots: boolean; } export const TableRow = ({ row, - columnIds, columns, indexPattern, onRemoveColumn, onAddColumn, onFilter, onClose, + isShortDots, }: TableRowProps) => { const flattened = indexPattern.flattenHit(row); const [isExpanded, setIsExpanded] = useState(false); @@ -53,14 +53,14 @@ export const TableRow = ({ data-test-subj="docTableExpandToggleColumn" /> - {columnIds.map((columnId) => { - const fieldInfo = indexPattern.fields.getByName(columnId); - const fieldMapping = flattened[columnId]; + {columns.map((colName) => { + const fieldInfo = indexPattern.fields.getByName(colName); + const fieldMapping = flattened[colName]; if (typeof row === 'undefined') { return ( ); } - const formattedValue = indexPattern.formatField(row, columnId); + const formattedValue = indexPattern.formatField(row, colName); if (typeof formattedValue === 'undefined') { return (
- {displayedTableColumns.map( - (col: EuiDataGridColumn, idx: number, cols: EuiDataGridColumn[]) => { - const colLeftIdx = - !col.actions || !col.actions!.showMoveLeft || idx - 1 <= 0 || col.id === timeColName - ? -1 - : idx - 1; - const colRightIdx = - !col.actions || - !col.actions!.showMoveRight || - idx + 1 >= cols.length || - col.id === timeColName - ? -1 - : idx + 1; - - return ( - ({ id, direction })) - } - onReorderColumn={onReorderColumn} - onRemoveColumn={onRemoveColumn} - onChangeSortOrder={onChangeSortOrder} - /> - ); - } - )} + {displayedColumns.map((col) => { + return ( + + ); + })}
@@ -72,23 +72,23 @@ export const TableRow = ({ if (fieldInfo?.type === '_source') { return (
- {fetchSourceTypeDataCell(indexPattern, row, columnId, false)} + {fetchSourceTypeDataCell(indexPattern, row, colName, false, isShortDots)}
@@ -102,7 +102,7 @@ export const TableRow = ({ if (!fieldInfo?.filterable) { return ( @@ -116,11 +116,10 @@ export const TableRow = ({ return ( diff --git a/src/plugins/discover/public/application/components/doc_views/context_app.tsx b/src/plugins/discover/public/application/components/doc_views/context_app.tsx index eea981fe88d7..f2777f41076a 100644 --- a/src/plugins/discover/public/application/components/doc_views/context_app.tsx +++ b/src/plugins/discover/public/application/components/doc_views/context_app.tsx @@ -101,6 +101,7 @@ export function ContextApp({ indexPattern={indexPattern} onAddColumn={() => {}} onFilter={onAddFilter} + onMoveColumn={() => {}} onRemoveColumn={() => {}} onReorderColumn={() => {}} onSetColumns={() => {}} 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 index 64a2dba99dd4..c9c41a914c74 100644 --- a/src/plugins/discover/public/application/utils/state_management/common.test.ts +++ b/src/plugins/discover/public/application/utils/state_management/common.test.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { addColumn, removeColumn, reorderColumn } from './common'; +import { addColumn, removeColumn, reorderColumn, moveColumn } from './common'; describe('commonUtils', () => { it('should handle addColumn', () => { @@ -22,4 +22,41 @@ describe('commonUtils', () => { 'column1', ]); }); + + it('should handle moveColumn', () => { + // test moving a column within the array + expect(moveColumn(['column1', 'column2', 'column3'], 'column2', 0)).toEqual([ + 'column2', + 'column1', + 'column3', + ]); + + // test moving a column to the same index (should result in no change) + expect(moveColumn(['column1', 'column2', 'column3'], 'column2', 1)).toEqual([ + 'column1', + 'column2', + 'column3', + ]); + + // test moving a column to the end + expect(moveColumn(['column1', 'column2', 'column3'], 'column1', 2)).toEqual([ + 'column2', + 'column3', + 'column1', + ]); + + // test trying to move a column to an index out of bounds (should return original array) + expect(moveColumn(['column1', 'column2', 'column3'], 'column1', 3)).toEqual([ + 'column1', + 'column2', + 'column3', + ]); + + // test trying to move a column that doesn't exist (should return original array) + expect(moveColumn(['column1', 'column2', 'column3'], 'column4', 1)).toEqual([ + 'column1', + 'column2', + 'column3', + ]); + }); }); diff --git a/src/plugins/discover/public/application/utils/state_management/common.ts b/src/plugins/discover/public/application/utils/state_management/common.ts index 800753fb4a36..1acba05ecb75 100644 --- a/src/plugins/discover/public/application/utils/state_management/common.ts +++ b/src/plugins/discover/public/application/utils/state_management/common.ts @@ -21,3 +21,13 @@ export const reorderColumn = (columns: string[], source: number, destination: nu newColumns.splice(destination, 0, removed); return newColumns; }; + +export function moveColumn(columns: string[], columnName: string, newIndex: number) { + if (newIndex < 0 || newIndex >= columns.length || !columns.includes(columnName)) { + return columns; + } + const modifiedColumns = [...columns]; + modifiedColumns.splice(modifiedColumns.indexOf(columnName), 1); // remove at old index + modifiedColumns.splice(newIndex, 0, columnName); // insert before new index + return modifiedColumns; +} 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 cbfc9c3769b0..ba94236e1492 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 @@ -82,4 +82,56 @@ describe('discoverSlice', () => { const result = discoverSlice.reducer(initialState, action); expect(result.sort).toEqual([['field2', 'desc']]); }); + + it('should handle moveColumn', () => { + initialState = { + columns: ['column1', 'column2', 'column3'], + sort: [], + }; + const action = { + type: 'discover/moveColumn', + payload: { columnName: 'column2', destination: 0 }, + }; + const result = discoverSlice.reducer(initialState, action); + expect(result.columns).toEqual(['column2', 'column1', 'column3']); + }); + + it('should maintain columns order when moving a column to its current position', () => { + initialState = { + columns: ['column1', 'column2', 'column3'], + sort: [], + }; + const action = { + type: 'discover/moveColumn', + payload: { columnName: 'column2', destination: 1 }, + }; + const result = discoverSlice.reducer(initialState, action); + expect(result.columns).toEqual(['column1', 'column2', 'column3']); + }); + + it('should handle moveColumn when destination is out of range', () => { + initialState = { + columns: ['column1', 'column2', 'column3'], + sort: [], + }; + const action = { + type: 'discover/moveColumn', + payload: { columnName: 'column1', destination: 5 }, + }; + const result = discoverSlice.reducer(initialState, action); + expect(result.columns).toEqual(['column1', 'column2', 'column3']); + }); + + it('should not change columns if column to move does not exist', () => { + initialState = { + columns: ['column1', 'column2', 'column3'], + sort: [], + }; + const action = { + type: 'discover/moveColumn', + payload: { columnName: 'nonExistingColumn', destination: 0 }, + }; + const result = discoverSlice.reducer(initialState, action); + expect(result.columns).toEqual(['column1', 'column2', 'column3']); + }); }); 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 f90d400ff3d5..c493086c4d45 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 @@ -42,7 +42,7 @@ export interface DiscoverState { * dirty flag to indicate if the saved search has been modified * since the last save */ - isDirty: boolean; + isDirty?: boolean; } export interface DiscoverRootState extends RootState { @@ -128,6 +128,17 @@ export const discoverSlice = createSlice({ isDirty: true, }; }, + moveColumn(state, action: PayloadAction<{ columnName: string; destination: number }>) { + const columns = utils.moveColumn( + state.columns, + action.payload.columnName, + action.payload.destination + ); + return { + ...state, + columns, + }; + }, setColumns(state, action: PayloadAction<{ columns: string[] }>) { return { ...state, @@ -167,6 +178,7 @@ export const { addColumn, removeColumn, reorderColumn, + moveColumn, setColumns, setSort, setInterval, 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 e42585b068fb..7f3bdfb0b924 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 @@ -10,6 +10,7 @@ import { DataGridTable } from '../../components/data_grid/data_grid_table'; import { useDiscoverContext } from '../context'; import { addColumn, + moveColumn, removeColumn, reorderColumn, setColumns, @@ -65,6 +66,10 @@ export const DiscoverTable = ({ rows }: Props) => { dispatch(reorderColumn({ source: source - 1, destination: destination - 1 })); }; + const onMoveColumn = (colName: string, destination: number) => { + dispatch(moveColumn({ columnName: colName, destination })); + }; + const onSetColumns = (cols: string[]) => dispatch(setColumns({ columns: cols })); const onSetSort = (s: SortOrder[]) => { dispatch(setSort(s)); @@ -106,6 +111,7 @@ export const DiscoverTable = ({ rows }: Props) => { indexPattern={indexPattern} onAddColumn={onAddColumn} onFilter={onAddFilter as DocViewFilterFn} + onMoveColumn={onMoveColumn} onRemoveColumn={onRemoveColumn} onReorderColumn={onReorderColumn} onSetColumns={onSetColumns} diff --git a/src/plugins/discover/public/embeddable/search_embeddable_component.tsx b/src/plugins/discover/public/embeddable/search_embeddable_component.tsx index 4a6078be745b..16e91abdf173 100644 --- a/src/plugins/discover/public/embeddable/search_embeddable_component.tsx +++ b/src/plugins/discover/public/embeddable/search_embeddable_component.tsx @@ -33,6 +33,7 @@ export function SearchEmbeddableComponent({ searchProps }: SearchEmbeddableProps indexPattern: searchProps.indexPattern, onAddColumn: searchProps.onAddColumn, onFilter: searchProps.onFilter, + onMoveColumn: searchProps.onMoveColumn, onRemoveColumn: searchProps.onRemoveColumn, onReorderColumn: searchProps.onReorderColumn, onSort: searchProps.onSort,