From 10564efbb7acc3aa95030ce49f8ec806421ba2b0 Mon Sep 17 00:00:00 2001 From: Julian Gernun Date: Fri, 23 Sep 2022 08:47:46 +0200 Subject: [PATCH] 141119 remove visibility toogle + use_columns refactor (#141250) * remove columns hide selector * refactor use_column hook * add testing * add getColumnIds fn --- .../alerts_table/alerts_table_state.test.tsx | 99 +++++++++++++++- .../alerts_table/hooks/use_columns/index.ts | 8 ++ .../hooks/use_columns/toggle_column.ts | 63 ++++++++++ .../hooks/{ => use_columns}/use_columns.ts | 111 ++++++------------ .../toolbar/toolbar_visibility.tsx | 4 +- 5 files changed, 205 insertions(+), 80 deletions(-) create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/index.ts create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/toggle_column.ts rename x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/{ => use_columns}/use_columns.ts (62%) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table_state.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table_state.test.tsx index d76644a28ddc6..fbe2b25437d88 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table_state.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table_state.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import userEvent from '@testing-library/user-event'; import { get } from 'lodash'; -import { render } from '@testing-library/react'; +import { fireEvent, render, waitFor } from '@testing-library/react'; import { AlertConsumers } from '@kbn/rule-data-utils'; import { EcsFieldsResponse } from '@kbn/rule-registry-plugin/common/search_strategy'; import { Storage } from '@kbn/kibana-utils-plugin/public'; @@ -24,6 +24,7 @@ import { useFetchAlerts } from './hooks/use_fetch_alerts'; import { useFetchBrowserFieldCapabilities } from './hooks/use_fetch_browser_fields_capabilities'; import { DefaultSort } from './hooks'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; +import { BrowserFields } from '@kbn/rule-registry-plugin/common'; jest.mock('./hooks/use_fetch_alerts'); jest.mock('./hooks/use_fetch_browser_fields_capabilities'); @@ -81,10 +82,12 @@ const alerts = [ { [AlertsField.name]: ['one'], [AlertsField.reason]: ['two'], + [AlertsField.uuid]: ['1047d115-670d-469e-af7a-86fdd2b2f814'], }, { [AlertsField.name]: ['three'], [AlertsField.reason]: ['four'], + [AlertsField.uuid]: ['bf5f6d63-5afd-48e0-baf6-f28c2b68db46'], }, ] as unknown as EcsFieldsResponse[]; @@ -245,6 +248,100 @@ describe('AlertsTableState', () => { }); }); + describe('field browser', () => { + const browserFields: BrowserFields = { + kibana: { + fields: { + [AlertsField.uuid]: { + category: 'kibana', + name: AlertsField.uuid, + }, + [AlertsField.name]: { + category: 'kibana', + name: AlertsField.name, + }, + [AlertsField.reason]: { + category: 'kibana', + name: AlertsField.reason, + }, + }, + }, + }; + + beforeEach(() => { + hookUseFetchBrowserFieldCapabilities.mockClear(); + hookUseFetchBrowserFieldCapabilities.mockImplementation(() => [true, browserFields]); + }); + + it('should show field browser', () => { + const { queryByTestId } = render(); + expect(queryByTestId('show-field-browser')).not.toBe(null); + }); + + it('should remove an already existing element when selected', async () => { + const { getByTestId, queryByTestId } = render(); + + expect(queryByTestId(`dataGridHeaderCell-${AlertsField.name}`)).not.toBe(null); + fireEvent.click(getByTestId('show-field-browser')); + const fieldCheckbox = getByTestId(`field-${AlertsField.name}-checkbox`); + fireEvent.click(fieldCheckbox); + fireEvent.click(getByTestId('close')); + + await waitFor(() => { + expect(queryByTestId(`dataGridHeaderCell-${AlertsField.name}`)).toBe(null); + }); + }); + + it('should restore a default element that has been removed previously', async () => { + storageMock.mockClear(); + storageMock.mockImplementation(() => ({ + get: () => { + return { + columns: [{ displayAsText: 'Reason', id: 'kibana.alert.reason', schema: undefined }], + sort: [], + visibleColumns: ['kibana.alert.reason'], + }; + }, + set: jest.fn(), + })); + const { getByTestId, queryByTestId } = render(); + + expect(queryByTestId(`dataGridHeaderCell-${AlertsField.name}`)).toBe(null); + fireEvent.click(getByTestId('show-field-browser')); + const fieldCheckbox = getByTestId(`field-${AlertsField.name}-checkbox`); + fireEvent.click(fieldCheckbox); + fireEvent.click(getByTestId('close')); + + await waitFor(() => { + expect(queryByTestId(`dataGridHeaderCell-${AlertsField.name}`)).not.toBe(null); + expect( + getByTestId('dataGridHeader') + .querySelectorAll('.euiDataGridHeaderCell__content')[1] + .getAttribute('title') + ).toBe('Name'); + }); + }); + + it('should insert a new field as column when its not a default one', async () => { + const { getByTestId, queryByTestId } = render(); + + expect(queryByTestId(`dataGridHeaderCell-${AlertsField.uuid}`)).toBe(null); + fireEvent.click(getByTestId('show-field-browser')); + const fieldCheckbox = getByTestId(`field-${AlertsField.uuid}-checkbox`); + fireEvent.click(fieldCheckbox); + fireEvent.click(getByTestId('close')); + + await waitFor(() => { + expect(queryByTestId(`dataGridHeaderCell-${AlertsField.uuid}`)).not.toBe(null); + expect( + getByTestId('dataGridHeader') + .querySelectorAll('.euiDataGridHeaderCell__content')[2] + .getAttribute('title') + ).toBe(AlertsField.uuid); + }); + }); + }); + describe('empty state', () => { beforeEach(() => { refecthMock.mockClear(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/index.ts new file mode 100644 index 0000000000000..a8fb587516fe2 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export * from './use_columns'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/toggle_column.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/toggle_column.ts new file mode 100644 index 0000000000000..f7f181c60b024 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/toggle_column.ts @@ -0,0 +1,63 @@ +/* + * 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 { EuiDataGridColumn } from '@elastic/eui'; + +const remove = ({ columnIds, index }: { columnIds: string[]; index: number }) => { + return [...columnIds.slice(0, index), ...columnIds.slice(index + 1)]; +}; + +const insert = ({ + columnId, + columnIds, + defaultColumns, +}: { + columnId: string; + columnIds: string[]; + defaultColumns: EuiDataGridColumn[]; +}) => { + const defaultIndex = defaultColumns.findIndex( + (column: EuiDataGridColumn) => column.id === columnId + ); + const isInDefaultConfig = defaultIndex >= 0; + + // if the column isn't shown but it's part of the default config + // insert into the same position as in the default config + if (isInDefaultConfig) { + return [...columnIds.slice(0, defaultIndex), columnId, ...columnIds.slice(defaultIndex)]; + } + + // if the column isn't shown and it's not part of the default config + // push it into the second position. Behaviour copied by t_grid, security + // does this to insert right after the timestamp column + return [columnIds[0], columnId, ...columnIds.slice(1)]; +}; + +/** + * @param param.columnId id of the column to be removed/inserted + * @param param.columnIds Current array of columnIds in the grid + * @param param.defaultColumns Those initial columns set up in the configuration before being modified by the user + * @returns the new list of columns to be shown + */ +export const toggleColumn = ({ + columnId, + columnIds, + defaultColumns, +}: { + columnId: string; + columnIds: string[]; + defaultColumns: EuiDataGridColumn[]; +}): string[] => { + const currentIndex = columnIds.indexOf(columnId); + const isVisible = currentIndex >= 0; + + if (isVisible) { + return remove({ columnIds, index: currentIndex }); + } + + return insert({ defaultColumns, columnId, columnIds }); +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/use_columns.ts similarity index 62% rename from x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns.ts rename to x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/use_columns.ts index a23fc758afeed..eb6f6adad1cc5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/hooks/use_columns/use_columns.ts @@ -10,8 +10,9 @@ import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import { AlertConsumers } from '@kbn/rule-data-utils'; import { BrowserField, BrowserFields } from '@kbn/rule-registry-plugin/common'; import { useCallback, useEffect, useState } from 'react'; -import { AlertsTableStorage } from '../alerts_table_state'; -import { useFetchBrowserFieldCapabilities } from './use_fetch_browser_fields_capabilities'; +import { AlertsTableStorage } from '../../alerts_table_state'; +import { useFetchBrowserFieldCapabilities } from '../use_fetch_browser_fields_capabilities'; +import { toggleColumn } from './toggle_column'; interface UseColumnsArgs { featureIds: AlertConsumers[]; @@ -79,27 +80,34 @@ const populateColumns = ( }); }; +const getColumnIds = (columns: EuiDataGridColumn[]): string[] => { + return columns.map((column: EuiDataGridColumn) => column.id); +}; + const getColumnByColumnId = (columns: EuiDataGridColumn[], columnId: string) => { return columns.find(({ id }: { id: string }) => id === columnId); }; +const getColumnsByColumnIds = (columns: EuiDataGridColumn[], columnIds: string[]) => { + return columnIds + .map((columnId: string) => columns.find((column: EuiDataGridColumn) => column.id === columnId)) + .filter(Boolean) as EuiDataGridColumn[]; +}; + const persist = ({ id, storageAlertsTable, columns, - visibleColumns, storage, }: { id: string; storageAlertsTable: React.MutableRefObject; storage: React.MutableRefObject; columns: EuiDataGridColumn[]; - visibleColumns: string[]; }) => { storageAlertsTable.current = { ...storageAlertsTable.current, columns, - visibleColumns, }; storage.current.set(id, storageAlertsTable.current); }; @@ -116,9 +124,6 @@ export const useColumns = ({ }); const [columns, setColumns] = useState(storageAlertsTable.current.columns); const [isColumnsPopulated, setColumnsPopulated] = useState(false); - const [visibleColumns, setVisibleColumns] = useState( - storageAlertsTable.current.visibleColumns ?? [] - ); useEffect(() => { if (isBrowserFieldDataLoading !== false || isColumnsPopulated) return; @@ -128,108 +133,58 @@ export const useColumns = ({ setColumns(populatedColumns); }, [browserFields, columns, isBrowserFieldDataLoading, isColumnsPopulated]); - const onColumnsChange = useCallback( - (newColumns: EuiDataGridColumn[], newVisibleColumns: string[]) => { + const setColumnsAndSave = useCallback( + (newColumns: EuiDataGridColumn[]) => { setColumns(newColumns); persist({ id, storage, storageAlertsTable, columns: newColumns, - visibleColumns: newVisibleColumns, }); }, [id, storage, storageAlertsTable] ); - const onChangeVisibleColumns = useCallback( - (newColumns: string[]) => { - setVisibleColumns(newColumns); - onColumnsChange( - columns.sort((a, b) => newColumns.indexOf(a.id) - newColumns.indexOf(b.id)), - newColumns - ); + const setColumnsByColumnIds = useCallback( + (columnIds: string[]) => { + const newColumns = getColumnsByColumnIds(columns, columnIds); + setColumnsAndSave(newColumns); }, - [onColumnsChange, columns] + [setColumnsAndSave, columns] ); const onToggleColumn = useCallback( (columnId: string): void => { - const visibleIndex = visibleColumns.indexOf(columnId); - const defaultIndex = defaultColumns.findIndex( - (column: EuiDataGridColumn) => column.id === columnId - ); - - const isVisible = visibleIndex >= 0; - const isInDefaultConfig = defaultIndex >= 0; - - let newColumnIds: string[] = []; - - // if the column is shown, remove it - if (isVisible) { - newColumnIds = [ - ...visibleColumns.slice(0, visibleIndex), - ...visibleColumns.slice(visibleIndex + 1), - ]; - } - - // if the column isn't shown but it's part of the default config - // insert into the same position as in the default config - if (!isVisible && isInDefaultConfig) { - newColumnIds = [ - ...visibleColumns.slice(0, defaultIndex), - columnId, - ...visibleColumns.slice(defaultIndex), - ]; - } - - // if the column isn't shown and it's not part of the default config - // push it into the second position. Behaviour copied by t_grid, security - // does this to insert right after the timestamp column - if (!isVisible && !isInDefaultConfig) { - newColumnIds = [visibleColumns[0], columnId, ...visibleColumns.slice(1)]; - } + const newColumnIds = toggleColumn({ + columnId, + columnIds: getColumnIds(columns), + defaultColumns, + }); - const newColumns = newColumnIds.map((_columnId) => { + const newColumns = newColumnIds.map((_columnId: string) => { const column = getColumnByColumnId(defaultColumns, _columnId); return euiColumnFactory(column ? column : { id: _columnId }, browserFields); }); - setVisibleColumns(newColumnIds); - setColumns(newColumns); - persist({ - id, - storage, - storageAlertsTable, - columns: newColumns, - visibleColumns: newColumnIds, - }); + setColumnsAndSave(newColumns); }, - [browserFields, defaultColumns, id, storage, storageAlertsTable, visibleColumns] + [browserFields, columns, defaultColumns, setColumnsAndSave] ); const onResetColumns = useCallback(() => { - const newVisibleColumns = defaultColumns.map((column) => column.id); const populatedDefaultColumns = populateColumns(defaultColumns, browserFields); - setVisibleColumns(newVisibleColumns); - setColumns(populatedDefaultColumns); - persist({ - id, - storage, - storageAlertsTable, - columns: populatedDefaultColumns, - visibleColumns: newVisibleColumns, - }); - }, [browserFields, defaultColumns, id, storage, storageAlertsTable]); + setColumnsAndSave(populatedDefaultColumns); + }, [browserFields, defaultColumns, setColumnsAndSave]); return { columns, + visibleColumns: getColumnIds(columns), isBrowserFieldDataLoading, browserFields, - visibleColumns, - onColumnsChange, + onColumnsChange: setColumnsAndSave, onToggleColumn, onResetColumns, - onChangeVisibleColumns, + onChangeVisibleColumns: setColumnsByColumnIds, }; }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/toolbar_visibility.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/toolbar_visibility.tsx index d51b84aa84aeb..84e5eb308b4f4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/toolbar_visibility.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/toolbar_visibility.tsx @@ -53,7 +53,9 @@ const getDefaultVisibility = ({ return { additionalControls, - showColumnSelector: true, + showColumnSelector: { + allowHide: false, + }, showSortSelector: true, }; };