Skip to content

Commit

Permalink
141119 remove visibility toogle + use_columns refactor (#141250)
Browse files Browse the repository at this point in the history
* remove columns hide selector

* refactor use_column hook

* add testing

* add getColumnIds fn
  • Loading branch information
jcger authored Sep 23, 2022
1 parent 6786b1e commit 10564ef
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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');
Expand Down Expand Up @@ -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[];

Expand Down Expand Up @@ -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(<AlertsTableWithLocale {...tableProps} />);
expect(queryByTestId('show-field-browser')).not.toBe(null);
});

it('should remove an already existing element when selected', async () => {
const { getByTestId, queryByTestId } = render(<AlertsTableWithLocale {...tableProps} />);

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(<AlertsTableWithLocale {...tableProps} />);

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(<AlertsTableWithLocale {...tableProps} />);

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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
@@ -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 });
};
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand Down Expand Up @@ -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<AlertsTableStorage>;
storage: React.MutableRefObject<IStorageWrapper>;
columns: EuiDataGridColumn[];
visibleColumns: string[];
}) => {
storageAlertsTable.current = {
...storageAlertsTable.current,
columns,
visibleColumns,
};
storage.current.set(id, storageAlertsTable.current);
};
Expand All @@ -116,9 +124,6 @@ export const useColumns = ({
});
const [columns, setColumns] = useState<EuiDataGridColumn[]>(storageAlertsTable.current.columns);
const [isColumnsPopulated, setColumnsPopulated] = useState<boolean>(false);
const [visibleColumns, setVisibleColumns] = useState(
storageAlertsTable.current.visibleColumns ?? []
);

useEffect(() => {
if (isBrowserFieldDataLoading !== false || isColumnsPopulated) return;
Expand All @@ -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,
};
};
Loading

0 comments on commit 10564ef

Please sign in to comment.