From f2bd00f73b9b63662c149fbdb15f468840d3b966 Mon Sep 17 00:00:00 2001 From: Yulia Cech Date: Fri, 2 Feb 2024 15:00:53 +0000 Subject: [PATCH 1/4] [Index Management] Add sorting to the columns added via extensions service --- .../application/services/sort_table.test.ts | 210 ++++++++++++++++++ .../public/application/services/sort_table.ts | 82 ++++--- .../application/store/selectors/index.js | 3 +- .../public/services/extensions_service.ts | 1 + 4 files changed, 267 insertions(+), 29 deletions(-) create mode 100644 x-pack/plugins/index_management/public/application/services/sort_table.test.ts diff --git a/x-pack/plugins/index_management/public/application/services/sort_table.test.ts b/x-pack/plugins/index_management/public/application/services/sort_table.test.ts new file mode 100644 index 0000000000000..a9abc8dd538aa --- /dev/null +++ b/x-pack/plugins/index_management/public/application/services/sort_table.test.ts @@ -0,0 +1,210 @@ +/* + * 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 { Index } from '../../../common'; +import { ExtensionsService } from '../../services/extensions_service'; +import { sortTable } from './sort_table'; +describe('sortTable', () => { + describe('sorts by name', () => { + const indices = [{ name: 'test1' }, { name: 'test2' }] as Index[]; + it('ascending', () => { + const sorted = sortTable(indices, 'name', true); + expect(sorted).toEqual([{ name: 'test1' }, { name: 'test2' }]); + }); + it('descending', () => { + const sorted = sortTable(indices, 'name', false); + expect(sorted).toEqual([{ name: 'test2' }, { name: 'test1' }]); + }); + }); + + describe('sorts by status', () => { + const indices = [{ status: 'open' }, { status: 'close' }] as Index[]; + it('ascending', () => { + const sorted = sortTable(indices, 'status', true); + expect(sorted).toEqual([{ status: 'close' }, { status: 'open' }]); + }); + it('descending', () => { + const sorted = sortTable(indices, 'status', false); + expect(sorted).toEqual([{ status: 'open' }, { status: 'close' }]); + }); + }); + + describe('sorts by health', () => { + const indices = [{ health: 'green' }, { health: 'yellow' }, { health: 'red' }] as Index[]; + it('ascending', () => { + const sorted = sortTable(indices, 'health', true); + expect(sorted).toEqual([{ health: 'green' }, { health: 'red' }, { health: 'yellow' }]); + }); + it('descending', () => { + const sorted = sortTable(indices, 'health', false); + expect(sorted).toEqual([{ health: 'yellow' }, { health: 'red' }, { health: 'green' }]); + }); + }); + + describe('sorts by primary', () => { + const indices = [{ primary: '1' }, { primary: '12' }, { primary: '2' }] as Index[]; + it('ascending', () => { + const sorted = sortTable(indices, 'primary', true); + expect(sorted).toEqual([{ primary: '1' }, { primary: '2' }, { primary: '12' }]); + }); + it('descending', () => { + const sorted = sortTable(indices, 'primary', false); + expect(sorted).toEqual([{ primary: '12' }, { primary: '2' }, { primary: '1' }]); + }); + }); + + describe('sorts by replica', () => { + const indices = [{ replica: '1' }, { replica: '12' }, { replica: '2' }] as Index[]; + it('ascending', () => { + const sorted = sortTable(indices, 'replica', true); + expect(sorted).toEqual([{ replica: '1' }, { replica: '2' }, { replica: '12' }]); + }); + it('descending', () => { + const sorted = sortTable(indices, 'replica', false); + expect(sorted).toEqual([{ replica: '12' }, { replica: '2' }, { replica: '1' }]); + }); + }); + + describe('sorts by documents', () => { + const indices = [{ documents: 1 }, { documents: 12 }, { documents: 2 }] as Index[]; + it('ascending', () => { + const sorted = sortTable(indices, 'documents', true); + expect(sorted).toEqual([{ documents: 1 }, { documents: 2 }, { documents: 12 }]); + }); + it('descending', () => { + const sorted = sortTable(indices, 'documents', false); + expect(sorted).toEqual([{ documents: 12 }, { documents: 2 }, { documents: 1 }]); + }); + }); + + describe('sorts by size', () => { + const indices = [{ size: '248b' }, { size: '2.35mb' }, { size: '6.36kb' }] as Index[]; + it('ascending', () => { + const sorted = sortTable(indices, 'size', true); + expect(sorted).toEqual([{ size: '248b' }, { size: '6.36kb' }, { size: '2.35mb' }]); + }); + it('descending', () => { + const sorted = sortTable(indices, 'size', false); + expect(sorted).toEqual([{ size: '2.35mb' }, { size: '6.36kb' }, { size: '248b' }]); + }); + }); + + describe('sorts by primary_size', () => { + const indices = [ + { primary_size: '248b' }, + { primary_size: '2.35mb' }, + { primary_size: '6.36kb' }, + ] as Index[]; + it('ascending', () => { + const sorted = sortTable(indices, 'primary_size', true); + expect(sorted).toEqual([ + { primary_size: '248b' }, + { primary_size: '6.36kb' }, + { primary_size: '2.35mb' }, + ]); + }); + it('descending', () => { + const sorted = sortTable(indices, 'primary_size', false); + expect(sorted).toEqual([ + { primary_size: '2.35mb' }, + { primary_size: '6.36kb' }, + { primary_size: '248b' }, + ]); + }); + }); + + describe('sorts by data_stream', () => { + const indices = [ + { data_stream: 'test1' }, + { data_stream: undefined }, + { data_stream: 'test2' }, + ] as Index[]; + it('ascending', () => { + const sorted = sortTable(indices, 'data_stream', true); + expect(sorted).toEqual([ + { data_stream: 'test1' }, + { data_stream: 'test2' }, + { data_stream: undefined }, + ]); + }); + it('descending', () => { + const sorted = sortTable(indices, 'data_stream', false); + expect(sorted).toEqual([ + { data_stream: undefined }, + { data_stream: 'test2' }, + { data_stream: 'test1' }, + ]); + }); + }); + + describe('sorts by a column added via extensions service', () => { + const indices = [ + { ilm: { phase: 'hot' } }, + { ilm: { phase: 'warm' } }, + { ilm: { phase: undefined } }, + ] as Index[]; + const extensionsService = { + columns: [ + { + fieldName: 'ilm.phase', + label: 'ILM phase', + order: 10, + render: (index: Index) => (index.ilm?.managed ? index.ilm.phase : ''), + sort: (index: Index) => (index.ilm?.managed ? index.ilm.phase : ''), + }, + ], + } as ExtensionsService; + it('ascending', () => { + const sorted = sortTable(indices, 'ilm.phase', true, extensionsService); + expect(sorted).toEqual([ + { ilm: { phase: 'hot' } }, + { ilm: { phase: 'warm' } }, + { ilm: { phase: undefined } }, + ]); + }); + it('descending', () => { + const sorted = sortTable(indices, 'ilm.phase', false, extensionsService); + expect(sorted).toEqual([ + { ilm: { phase: undefined } }, + { ilm: { phase: 'warm' } }, + { ilm: { phase: 'hot' } }, + ]); + }); + }); + describe('sorting by a column without a sorter', () => { + const indices = [ + { test: 'test1' }, + { test: 'test2' }, + { test: undefined }, + { test: 'test5' }, + { test: 'test3' }, + { test: undefined }, + ] as unknown as Index[]; + it('ascending direction keeps the original array order', () => { + const sorted = sortTable(indices, 'test', true); + expect(sorted).toEqual([ + { test: 'test1' }, + { test: 'test2' }, + { test: undefined }, + { test: 'test5' }, + { test: 'test3' }, + { test: undefined }, + ]); + }); + it('descending direction reverts the original array order', () => { + const sorted = sortTable(indices, 'test', false); + expect(sorted).toEqual([ + { test: undefined }, + { test: 'test3' }, + { test: 'test5' }, + { test: undefined }, + { test: 'test2' }, + { test: 'test1' }, + ]); + }); + }); +}); diff --git a/x-pack/plugins/index_management/public/application/services/sort_table.ts b/x-pack/plugins/index_management/public/application/services/sort_table.ts index 92257391e335a..acf67e8f1b3a3 100644 --- a/x-pack/plugins/index_management/public/application/services/sort_table.ts +++ b/x-pack/plugins/index_management/public/application/services/sort_table.ts @@ -6,6 +6,8 @@ */ import { sortBy } from 'lodash'; +import { Index } from '../../../common'; +import type { ExtensionsService } from '../../services'; type SortField = | 'name' @@ -28,39 +30,63 @@ const unitMagnitude = { pb: 5, }; -const stringSort = (fieldName: SortField) => (item: { [key: string]: any }) => { - return item[fieldName]; -}; +type SortFunction = (index: Index) => any; -const numericSort = (fieldName: SortField) => (item: { [key: string]: any }) => +item[fieldName]; +const stringSort = + (fieldName: SortField): SortFunction => + (item) => { + return item[fieldName]; + }; -const byteSort = (fieldName: SortField) => (item: { [key: string]: any }) => { - const rawValue = item[fieldName]; - // raw value can be missing if index is closed - if (!rawValue) { - return 0; - } - const matchResult = rawValue.match(/(.*)([kmgtp]b)/); - if (!matchResult) { - return 0; - } - const [, number, unit]: [string, string, Unit] = matchResult; - return +number * Math.pow(1024, unitMagnitude[unit]); -}; +const numericSort = + (fieldName: SortField): SortFunction => + (item) => + +item[fieldName]!; + +const byteSort = + (fieldName: SortField): SortFunction => + (item) => { + const rawValue = String(item[fieldName]); + // raw value can be missing if index is closed + if (!rawValue) { + return 0; + } + const matchResult = rawValue.match(/(.*)([kmgtp]b)/); + if (!matchResult) { + return 0; + } + const [, number, unit] = matchResult; + return +number * Math.pow(1024, unitMagnitude[unit as Unit]); + }; -const sorters = { - name: stringSort('name'), - status: stringSort('status'), - health: stringSort('health'), - primary: numericSort('primary'), - replica: numericSort('replica'), - documents: numericSort('documents'), - size: byteSort('size'), - primary_size: byteSort('primary_size'), - data_stream: stringSort('data_stream'), +const getSorters = (extensionsService?: ExtensionsService) => { + const sorters = { + name: stringSort('name'), + status: stringSort('status'), + health: stringSort('health'), + primary: numericSort('primary'), + replica: numericSort('replica'), + documents: numericSort('documents'), + size: byteSort('size'), + primary_size: byteSort('primary_size'), + data_stream: stringSort('data_stream'), + } as any; + const columns = extensionsService?.columns ?? []; + for (const column of columns) { + if (column.sort) { + sorters[column.fieldName] = column.sort; + } + } + return sorters; }; -export const sortTable = (array = [], sortField: SortField, isSortAscending: boolean) => { +export const sortTable = ( + array: Index[] = [], + sortField: SortField | string, + isSortAscending: boolean, + extensionsService?: ExtensionsService +) => { + const sorters = getSorters(extensionsService); const sorted = sortBy(array, sorters[sortField]); return isSortAscending ? sorted : sorted.reverse(); }; diff --git a/x-pack/plugins/index_management/public/application/store/selectors/index.js b/x-pack/plugins/index_management/public/application/store/selectors/index.js index 6ed09d8234fa4..2e1e01f3122ae 100644 --- a/x-pack/plugins/index_management/public/application/store/selectors/index.js +++ b/x-pack/plugins/index_management/public/application/store/selectors/index.js @@ -94,7 +94,8 @@ export const getPageOfIndices = createSelector( const sortedIndexes = sortTable( filteredIndices, tableState.sortField, - tableState.isSortAscending + tableState.isSortAscending, + extensionsService ); const { firstItemIndex, lastItemIndex } = pager; const pagedIndexes = sortedIndexes.slice(firstItemIndex, lastItemIndex + 1); diff --git a/x-pack/plugins/index_management/public/services/extensions_service.ts b/x-pack/plugins/index_management/public/services/extensions_service.ts index 3e8f6118bf44e..c5962d044edfc 100644 --- a/x-pack/plugins/index_management/public/services/extensions_service.ts +++ b/x-pack/plugins/index_management/public/services/extensions_service.ts @@ -44,6 +44,7 @@ export interface IndicesListColumn { label: string; order: number; render?: (index: Index) => ReactNode; + sort?: (index: Index) => any; } export interface ExtensionsSetup { From 3a527cc3e615a54e4f299b837d9097eb5d743add Mon Sep 17 00:00:00 2001 From: Yulia Cech Date: Fri, 2 Feb 2024 15:31:50 +0000 Subject: [PATCH 2/4] [Index Management] Add sorting for any index values --- .../application/services/sort_table.test.ts | 12 +++++------ .../public/application/services/sort_table.ts | 20 +++++++------------ .../public/services/extensions_service.ts | 1 + 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/index_management/public/application/services/sort_table.test.ts b/x-pack/plugins/index_management/public/application/services/sort_table.test.ts index a9abc8dd538aa..07e969190bd42 100644 --- a/x-pack/plugins/index_management/public/application/services/sort_table.test.ts +++ b/x-pack/plugins/index_management/public/application/services/sort_table.test.ts @@ -184,24 +184,24 @@ describe('sortTable', () => { { test: 'test3' }, { test: undefined }, ] as unknown as Index[]; - it('ascending direction keeps the original array order', () => { + it('ascending', () => { const sorted = sortTable(indices, 'test', true); expect(sorted).toEqual([ { test: 'test1' }, { test: 'test2' }, - { test: undefined }, - { test: 'test5' }, { test: 'test3' }, + { test: 'test5' }, + { test: undefined }, { test: undefined }, ]); }); - it('descending direction reverts the original array order', () => { + it('descending', () => { const sorted = sortTable(indices, 'test', false); expect(sorted).toEqual([ { test: undefined }, - { test: 'test3' }, - { test: 'test5' }, { test: undefined }, + { test: 'test5' }, + { test: 'test3' }, { test: 'test2' }, { test: 'test1' }, ]); diff --git a/x-pack/plugins/index_management/public/application/services/sort_table.ts b/x-pack/plugins/index_management/public/application/services/sort_table.ts index acf67e8f1b3a3..f06cd1535838d 100644 --- a/x-pack/plugins/index_management/public/application/services/sort_table.ts +++ b/x-pack/plugins/index_management/public/application/services/sort_table.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { sortBy } from 'lodash'; +import { sortBy, get } from 'lodash'; import { Index } from '../../../common'; import type { ExtensionsService } from '../../services'; @@ -32,16 +32,10 @@ const unitMagnitude = { type SortFunction = (index: Index) => any; -const stringSort = - (fieldName: SortField): SortFunction => - (item) => { - return item[fieldName]; - }; - const numericSort = (fieldName: SortField): SortFunction => (item) => - +item[fieldName]!; + Number(item[fieldName]); const byteSort = (fieldName: SortField): SortFunction => @@ -61,15 +55,11 @@ const byteSort = const getSorters = (extensionsService?: ExtensionsService) => { const sorters = { - name: stringSort('name'), - status: stringSort('status'), - health: stringSort('health'), primary: numericSort('primary'), replica: numericSort('replica'), documents: numericSort('documents'), size: byteSort('size'), primary_size: byteSort('primary_size'), - data_stream: stringSort('data_stream'), } as any; const columns = extensionsService?.columns ?? []; for (const column of columns) { @@ -87,6 +77,10 @@ export const sortTable = ( extensionsService?: ExtensionsService ) => { const sorters = getSorters(extensionsService); - const sorted = sortBy(array, sorters[sortField]); + let sorter = sorters[sortField]; + if (!sorter) { + sorter = (index: Index) => get(index, sortField); + } + const sorted = sortBy(array, sorter); return isSortAscending ? sorted : sorted.reverse(); }; diff --git a/x-pack/plugins/index_management/public/services/extensions_service.ts b/x-pack/plugins/index_management/public/services/extensions_service.ts index c5962d044edfc..e7cb4aef2282d 100644 --- a/x-pack/plugins/index_management/public/services/extensions_service.ts +++ b/x-pack/plugins/index_management/public/services/extensions_service.ts @@ -44,6 +44,7 @@ export interface IndicesListColumn { label: string; order: number; render?: (index: Index) => ReactNode; + // return a value used for sorting (only if the value is different from the original value at index[fieldName]) sort?: (index: Index) => any; } From 90bdf4788f5e3396519225ab8a3f412865f855b0 Mon Sep 17 00:00:00 2001 From: Yulia Cech Date: Fri, 2 Feb 2024 15:32:27 +0000 Subject: [PATCH 3/4] TESTING: add ilm phase column --- .../public/extend_index_management/index.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.tsx b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.tsx index 519a0606c36aa..d0f2f8ffc4a7c 100644 --- a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.tsx @@ -226,4 +226,12 @@ export const addAllExtensions = ( extensionsService.addFilter(ilmFilterExtension); extensionsService.addIndexDetailsTab(indexLifecycleTab); + + extensionsService.addColumn({ + label: 'ILM phase', + fieldName: 'ilm.phase', + order: 90, + render: (index: Index) => (index.ilm?.managed ? index.ilm.phase : ''), + sort: (index: Index) => (index.ilm?.managed ? index.ilm.phase : ''), + }); }; From 04415ce2fc3b302efdace7f391c07047839fb0f1 Mon Sep 17 00:00:00 2001 From: Yulia Cech Date: Fri, 2 Feb 2024 15:32:35 +0000 Subject: [PATCH 4/4] Revert "TESTING: add ilm phase column" This reverts commit 90bdf4788f5e3396519225ab8a3f412865f855b0. --- .../public/extend_index_management/index.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.tsx b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.tsx index d0f2f8ffc4a7c..519a0606c36aa 100644 --- a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.tsx @@ -226,12 +226,4 @@ export const addAllExtensions = ( extensionsService.addFilter(ilmFilterExtension); extensionsService.addIndexDetailsTab(indexLifecycleTab); - - extensionsService.addColumn({ - label: 'ILM phase', - fieldName: 'ilm.phase', - order: 90, - render: (index: Index) => (index.ilm?.managed ? index.ilm.phase : ''), - sort: (index: Index) => (index.ilm?.managed ? index.ilm.phase : ''), - }); };