mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[TableListView] Fix regression when resetting search (#162034)
This commit is contained in:
parent
72907cfe1e
commit
c6deb252b2
8 changed files with 370 additions and 80 deletions
|
@ -47,23 +47,23 @@ export const TabbedTableListView = ({
|
||||||
[activeTabId, tabs]
|
[activeTabId, tabs]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onFetchSuccess = useCallback(() => {
|
||||||
|
setHasInitialFetchReturned(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [tableList, setTableList] = useState<React.ReactNode>(null);
|
const [tableList, setTableList] = useState<React.ReactNode>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function loadTableList() {
|
async function loadTableList() {
|
||||||
const newTableList = await getActiveTab().getTableList({
|
const newTableList = await getActiveTab().getTableList({
|
||||||
onFetchSuccess: () => {
|
onFetchSuccess,
|
||||||
if (!hasInitialFetchReturned) {
|
|
||||||
setHasInitialFetchReturned(true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setPageDataTestSubject,
|
setPageDataTestSubject,
|
||||||
});
|
});
|
||||||
setTableList(newTableList);
|
setTableList(newTableList);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadTableList();
|
loadTableList();
|
||||||
}, [hasInitialFetchReturned, activeTabId, tabs, getActiveTab]);
|
}, [activeTabId, tabs, getActiveTab, onFetchSuccess]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KibanaPageTemplate panelled data-test-subj={pageDataTestSubject}>
|
<KibanaPageTemplate panelled data-test-subj={pageDataTestSubject}>
|
||||||
|
|
|
@ -82,10 +82,8 @@ export const TableListView = <T extends UserContentCommonSchema>({
|
||||||
const [pageDataTestSubject, setPageDataTestSubject] = useState<string>();
|
const [pageDataTestSubject, setPageDataTestSubject] = useState<string>();
|
||||||
|
|
||||||
const onFetchSuccess = useCallback(() => {
|
const onFetchSuccess = useCallback(() => {
|
||||||
if (!hasInitialFetchReturned) {
|
setHasInitialFetchReturned(true);
|
||||||
setHasInitialFetchReturned(true);
|
}, []);
|
||||||
}
|
|
||||||
}, [hasInitialFetchReturned]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageTemplate panelled data-test-subj={pageDataTestSubject}>
|
<PageTemplate panelled data-test-subj={pageDataTestSubject}>
|
||||||
|
|
|
@ -39,11 +39,21 @@ export function getReducer<T extends UserContentCommonSchema>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let hasNoItems = state.hasNoItems;
|
||||||
|
|
||||||
|
const hasQuery = state.searchQuery.text !== '';
|
||||||
|
if (hasQuery) {
|
||||||
|
hasNoItems = undefined;
|
||||||
|
} else {
|
||||||
|
hasNoItems = items.length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
hasInitialFetchReturned: true,
|
hasInitialFetchReturned: true,
|
||||||
isFetchingItems: false,
|
isFetchingItems: false,
|
||||||
items,
|
items,
|
||||||
|
hasNoItems,
|
||||||
totalItems: action.data.response.total,
|
totalItems: action.data.response.total,
|
||||||
hasUpdatedAtMetadata,
|
hasUpdatedAtMetadata,
|
||||||
tableSort: tableSort ?? state.tableSort,
|
tableSort: tableSort ?? state.tableSort,
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* 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 and the Server Side Public License, v 1; you may not use this file except
|
||||||
|
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||||
|
* Side Public License, v 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { TestBed } from '@kbn/test-jest-helpers';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
|
|
||||||
|
export const getActions = ({ find, form, component }: TestBed) => {
|
||||||
|
/** Open the sort select drop down menu */
|
||||||
|
const openSortSelect = () => {
|
||||||
|
find('tableSortSelectBtn').at(0).simulate('click');
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Search Box ---
|
||||||
|
|
||||||
|
/** Set the search box value */
|
||||||
|
const updateSearchText = async (value: string) => {
|
||||||
|
await act(async () => {
|
||||||
|
find('tableListSearchBox').simulate('keyup', {
|
||||||
|
key: 'Enter',
|
||||||
|
target: { value },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
component.update();
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Get the Search box value */
|
||||||
|
const getSearchBoxValue = () => find('tableListSearchBox').props().defaultValue;
|
||||||
|
|
||||||
|
// --- Row Actions ---
|
||||||
|
const selectRow = (rowId: string) => {
|
||||||
|
act(() => {
|
||||||
|
form.selectCheckBox(`checkboxSelectRow-${rowId}`);
|
||||||
|
});
|
||||||
|
component.update();
|
||||||
|
};
|
||||||
|
|
||||||
|
const clickDeleteSelectedItemsButton = () => {
|
||||||
|
act(() => {
|
||||||
|
find('deleteSelectedItems').simulate('click');
|
||||||
|
});
|
||||||
|
component.update();
|
||||||
|
};
|
||||||
|
|
||||||
|
const clickConfirmModalButton = async () => {
|
||||||
|
await act(async () => {
|
||||||
|
find('confirmModalConfirmButton').simulate('click');
|
||||||
|
});
|
||||||
|
component.update();
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
openSortSelect,
|
||||||
|
updateSearchText,
|
||||||
|
getSearchBoxValue,
|
||||||
|
selectRow,
|
||||||
|
clickDeleteSelectedItemsButton,
|
||||||
|
clickConfirmModalButton,
|
||||||
|
};
|
||||||
|
};
|
|
@ -22,6 +22,7 @@ import {
|
||||||
type TableListViewTableProps,
|
type TableListViewTableProps,
|
||||||
type UserContentCommonSchema,
|
type UserContentCommonSchema,
|
||||||
} from './table_list_view_table';
|
} from './table_list_view_table';
|
||||||
|
import { getActions } from './table_list_view.test.helpers';
|
||||||
|
|
||||||
const mockUseEffect = useEffect;
|
const mockUseEffect = useEffect;
|
||||||
|
|
||||||
|
@ -54,12 +55,6 @@ const twoDaysAgoToString = new Date(twoDaysAgo.getTime()).toDateString();
|
||||||
const yesterday = new Date(new Date().setDate(new Date().getDate() - 1));
|
const yesterday = new Date(new Date().setDate(new Date().getDate() - 1));
|
||||||
const yesterdayToString = new Date(yesterday.getTime()).toDateString();
|
const yesterdayToString = new Date(yesterday.getTime()).toDateString();
|
||||||
|
|
||||||
const getActions = (testBed: TestBed) => ({
|
|
||||||
openSortSelect() {
|
|
||||||
testBed.find('tableSortSelectBtn').at(0).simulate('click');
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('TableListView', () => {
|
describe('TableListView', () => {
|
||||||
const requiredProps: TableListViewTableProps = {
|
const requiredProps: TableListViewTableProps = {
|
||||||
entityName: 'test',
|
entityName: 'test',
|
||||||
|
@ -91,50 +86,102 @@ describe('TableListView', () => {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
test('render default empty prompt', async () => {
|
describe('empty prompt', () => {
|
||||||
let testBed: TestBed;
|
test('render default empty prompt', async () => {
|
||||||
|
let testBed: TestBed;
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
testBed = await setup();
|
testBed = await setup();
|
||||||
|
});
|
||||||
|
|
||||||
|
const { component, exists } = testBed!;
|
||||||
|
component.update();
|
||||||
|
|
||||||
|
expect(component.find(EuiEmptyPrompt).length).toBe(1);
|
||||||
|
expect(exists('newItemButton')).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
const { component, exists } = testBed!;
|
// avoid trapping users in empty prompt that can not create new items
|
||||||
component.update();
|
test('render default empty prompt with create action when createItem supplied', async () => {
|
||||||
|
let testBed: TestBed;
|
||||||
|
|
||||||
expect(component.find(EuiEmptyPrompt).length).toBe(1);
|
await act(async () => {
|
||||||
expect(exists('newItemButton')).toBe(false);
|
testBed = await setup({ createItem: () => undefined });
|
||||||
});
|
});
|
||||||
|
|
||||||
// avoid trapping users in empty prompt that can not create new items
|
const { component, exists } = testBed!;
|
||||||
test('render default empty prompt with create action when createItem supplied', async () => {
|
component.update();
|
||||||
let testBed: TestBed;
|
|
||||||
|
|
||||||
await act(async () => {
|
expect(component.find(EuiEmptyPrompt).length).toBe(1);
|
||||||
testBed = await setup({ createItem: () => undefined });
|
expect(exists('newItemButton')).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
const { component, exists } = testBed!;
|
test('render custom empty prompt', async () => {
|
||||||
component.update();
|
let testBed: TestBed;
|
||||||
|
|
||||||
expect(component.find(EuiEmptyPrompt).length).toBe(1);
|
const CustomEmptyPrompt = () => {
|
||||||
expect(exists('newItemButton')).toBe(true);
|
return <EuiEmptyPrompt data-test-subj="custom-empty-prompt" title={<h1>Table empty</h1>} />;
|
||||||
});
|
};
|
||||||
|
|
||||||
test('render custom empty prompt', async () => {
|
await act(async () => {
|
||||||
let testBed: TestBed;
|
testBed = await setup({ emptyPrompt: <CustomEmptyPrompt /> });
|
||||||
|
});
|
||||||
|
|
||||||
const CustomEmptyPrompt = () => {
|
const { component, exists } = testBed!;
|
||||||
return <EuiEmptyPrompt data-test-subj="custom-empty-prompt" title={<h1>Table empty</h1>} />;
|
component.update();
|
||||||
};
|
|
||||||
|
|
||||||
await act(async () => {
|
expect(exists('custom-empty-prompt')).toBe(true);
|
||||||
testBed = await setup({ emptyPrompt: <CustomEmptyPrompt /> });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { component, exists } = testBed!;
|
test('render empty prompt after deleting all items from table', async () => {
|
||||||
component.update();
|
// NOTE: this test is using helpers that are being tested in the
|
||||||
|
// "should allow select items to be deleted" test below.
|
||||||
|
// If this test fails, check that one first.
|
||||||
|
|
||||||
expect(exists('custom-empty-prompt')).toBe(true);
|
const hits: UserContentCommonSchema[] = [
|
||||||
|
{
|
||||||
|
id: 'item-1',
|
||||||
|
type: 'dashboard',
|
||||||
|
updatedAt: '2020-01-01T00:00:00Z',
|
||||||
|
attributes: {
|
||||||
|
title: 'Item 1',
|
||||||
|
},
|
||||||
|
references: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const findItems = jest.fn().mockResolvedValue({ total: 1, hits });
|
||||||
|
const deleteItems = jest.fn();
|
||||||
|
|
||||||
|
let testBed: TestBed;
|
||||||
|
|
||||||
|
const EmptyPrompt = () => {
|
||||||
|
return <EuiEmptyPrompt data-test-subj="custom-empty-prompt" title={<h1>Table empty</h1>} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
testBed = await setup({ emptyPrompt: <EmptyPrompt />, findItems, deleteItems });
|
||||||
|
});
|
||||||
|
|
||||||
|
const { component, exists, table } = testBed!;
|
||||||
|
const { selectRow, clickConfirmModalButton, clickDeleteSelectedItemsButton } = getActions(
|
||||||
|
testBed!
|
||||||
|
);
|
||||||
|
component.update();
|
||||||
|
|
||||||
|
expect(exists('custom-empty-prompt')).toBe(false);
|
||||||
|
const { tableCellsValues } = table.getMetaData('itemsInMemTable');
|
||||||
|
const [row] = tableCellsValues;
|
||||||
|
expect(row[1]).toBe('Item 1'); // Note: row[0] is the checkbox
|
||||||
|
|
||||||
|
// We delete the item in the table and expect the empty prompt to show
|
||||||
|
findItems.mockResolvedValue({ total: 0, hits: [] });
|
||||||
|
selectRow('item-1');
|
||||||
|
clickDeleteSelectedItemsButton();
|
||||||
|
await clickConfirmModalButton();
|
||||||
|
|
||||||
|
expect(exists('custom-empty-prompt')).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('default columns', () => {
|
describe('default columns', () => {
|
||||||
|
@ -798,7 +845,20 @@ describe('TableListView', () => {
|
||||||
let testBed: TestBed;
|
let testBed: TestBed;
|
||||||
|
|
||||||
const initialFilter = 'tag:(tag-1)';
|
const initialFilter = 'tag:(tag-1)';
|
||||||
const findItems = jest.fn().mockResolvedValue({ total: 0, hits: [] });
|
const findItems = jest.fn().mockResolvedValue({
|
||||||
|
total: 1,
|
||||||
|
hits: [
|
||||||
|
{
|
||||||
|
id: 'item-1',
|
||||||
|
type: 'dashboard',
|
||||||
|
updatedAt: new Date('2023-07-15').toISOString(),
|
||||||
|
attributes: {
|
||||||
|
title: 'Item 1',
|
||||||
|
},
|
||||||
|
references: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
testBed = await setupInitialFilter({
|
testBed = await setupInitialFilter({
|
||||||
|
@ -824,6 +884,173 @@ describe('TableListView', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('search', () => {
|
||||||
|
const updatedAt = new Date('2023-07-15').toISOString();
|
||||||
|
|
||||||
|
const hits: UserContentCommonSchema[] = [
|
||||||
|
{
|
||||||
|
id: 'item-1',
|
||||||
|
type: 'dashboard',
|
||||||
|
updatedAt,
|
||||||
|
attributes: {
|
||||||
|
title: 'Item 1',
|
||||||
|
},
|
||||||
|
references: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'item-2',
|
||||||
|
type: 'dashboard',
|
||||||
|
updatedAt,
|
||||||
|
attributes: {
|
||||||
|
title: 'Item 2',
|
||||||
|
},
|
||||||
|
references: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const findItems = jest.fn();
|
||||||
|
|
||||||
|
const setupSearch = (...args: Parameters<ReturnType<typeof registerTestBed>>) => {
|
||||||
|
const testBed = registerTestBed<string, TableListViewTableProps>(
|
||||||
|
WithServices<TableListViewTableProps>(TableListViewTable),
|
||||||
|
{
|
||||||
|
defaultProps: {
|
||||||
|
...requiredProps,
|
||||||
|
findItems,
|
||||||
|
urlStateEnabled: false,
|
||||||
|
entityName: 'Foo',
|
||||||
|
entityNamePlural: 'Foos',
|
||||||
|
},
|
||||||
|
memoryRouter: { wrapComponent: true },
|
||||||
|
}
|
||||||
|
)(...args);
|
||||||
|
|
||||||
|
const { updateSearchText, getSearchBoxValue } = getActions(testBed);
|
||||||
|
|
||||||
|
return {
|
||||||
|
testBed,
|
||||||
|
updateSearchText,
|
||||||
|
getSearchBoxValue,
|
||||||
|
getLastCallArgsFromFindItems: () => findItems.mock.calls[findItems.mock.calls.length - 1],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
findItems.mockReset().mockResolvedValue({ total: hits.length, hits });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should search the table items', async () => {
|
||||||
|
let testBed: TestBed;
|
||||||
|
let updateSearchText: (value: string) => Promise<void>;
|
||||||
|
let getLastCallArgsFromFindItems: () => Parameters<typeof findItems>;
|
||||||
|
let getSearchBoxValue: () => string;
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
({ testBed, getLastCallArgsFromFindItems, getSearchBoxValue, updateSearchText } =
|
||||||
|
await setupSearch());
|
||||||
|
});
|
||||||
|
|
||||||
|
const { component, table } = testBed!;
|
||||||
|
component.update();
|
||||||
|
|
||||||
|
let searchTerm = '';
|
||||||
|
let expected = '';
|
||||||
|
[searchTerm] = getLastCallArgsFromFindItems!();
|
||||||
|
expect(getSearchBoxValue!()).toBe(expected);
|
||||||
|
expect(searchTerm).toBe(expected);
|
||||||
|
|
||||||
|
const { tableCellsValues } = table.getMetaData('itemsInMemTable');
|
||||||
|
expect(tableCellsValues).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
Array [
|
||||||
|
"Item 1",
|
||||||
|
"Sat Jul 15 2023",
|
||||||
|
],
|
||||||
|
Array [
|
||||||
|
"Item 2",
|
||||||
|
"Sat Jul 15 2023",
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
|
||||||
|
findItems.mockResolvedValueOnce({
|
||||||
|
total: 1,
|
||||||
|
hits: [
|
||||||
|
{
|
||||||
|
id: 'item-from-search',
|
||||||
|
type: 'dashboard',
|
||||||
|
updatedAt: new Date('2023-07-01').toISOString(),
|
||||||
|
attributes: {
|
||||||
|
title: 'Item from search',
|
||||||
|
},
|
||||||
|
references: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expected = 'foo';
|
||||||
|
await updateSearchText!(expected);
|
||||||
|
[searchTerm] = getLastCallArgsFromFindItems!();
|
||||||
|
expect(getSearchBoxValue!()).toBe(expected);
|
||||||
|
expect(searchTerm).toBe(expected);
|
||||||
|
|
||||||
|
expect(table.getMetaData('itemsInMemTable').tableCellsValues).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
Array [
|
||||||
|
"Item from search",
|
||||||
|
"July 1, 2023",
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should search and render empty list if no result', async () => {
|
||||||
|
let testBed: TestBed;
|
||||||
|
let updateSearchText: (value: string) => Promise<void>;
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
({ testBed, updateSearchText } = await setupSearch());
|
||||||
|
});
|
||||||
|
|
||||||
|
const { component, table, find } = testBed!;
|
||||||
|
component.update();
|
||||||
|
|
||||||
|
findItems.mockResolvedValueOnce({
|
||||||
|
total: 0,
|
||||||
|
hits: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await updateSearchText!('unknown items');
|
||||||
|
|
||||||
|
expect(table.getMetaData('itemsInMemTable').tableCellsValues).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
Array [
|
||||||
|
"No Foos matched your search.",
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
find('clearSearchButton').simulate('click');
|
||||||
|
});
|
||||||
|
component.update();
|
||||||
|
|
||||||
|
// We should get back the initial 2 items (Item 1 and Item 2)
|
||||||
|
expect(table.getMetaData('itemsInMemTable').tableCellsValues).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
Array [
|
||||||
|
"Item 1",
|
||||||
|
"Sat Jul 15 2023",
|
||||||
|
],
|
||||||
|
Array [
|
||||||
|
"Item 2",
|
||||||
|
"Sat Jul 15 2023",
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('url state', () => {
|
describe('url state', () => {
|
||||||
let router: Router | undefined;
|
let router: Router | undefined;
|
||||||
|
|
||||||
|
@ -1153,10 +1380,11 @@ describe('TableListView', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
test('should allow select items to be deleted', async () => {
|
test('should allow select items to be deleted', async () => {
|
||||||
const {
|
const { testBed, deleteItems } = await setupTest();
|
||||||
testBed: { table, find, exists, component, form },
|
|
||||||
deleteItems,
|
const { table, exists, component } = testBed;
|
||||||
} = await setupTest();
|
const { selectRow, clickDeleteSelectedItemsButton, clickConfirmModalButton } =
|
||||||
|
getActions(testBed);
|
||||||
|
|
||||||
const { tableCellsValues } = table.getMetaData('itemsInMemTable');
|
const { tableCellsValues } = table.getMetaData('itemsInMemTable');
|
||||||
|
|
||||||
|
@ -1165,28 +1393,21 @@ describe('TableListView', () => {
|
||||||
['', 'Item 1Item 1 description', twoDaysAgoToString],
|
['', 'Item 1Item 1 description', twoDaysAgoToString],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Select the second item
|
||||||
const selectedHit = hits[1];
|
const selectedHit = hits[1];
|
||||||
|
|
||||||
expect(exists('deleteSelectedItems')).toBe(false);
|
expect(exists('deleteSelectedItems')).toBe(false);
|
||||||
act(() => {
|
|
||||||
// Select the second item
|
selectRow(selectedHit.id);
|
||||||
form.selectCheckBox(`checkboxSelectRow-${selectedHit.id}`);
|
|
||||||
});
|
|
||||||
component.update();
|
|
||||||
// Delete button is now visible
|
// Delete button is now visible
|
||||||
expect(exists('deleteSelectedItems')).toBe(true);
|
expect(exists('deleteSelectedItems')).toBe(true);
|
||||||
|
|
||||||
// Click delete and validate that confirm modal opens
|
// Click delete and validate that confirm modal opens
|
||||||
expect(component.exists('.euiModal--confirmation')).toBe(false);
|
expect(component.exists('.euiModal--confirmation')).toBe(false);
|
||||||
act(() => {
|
clickDeleteSelectedItemsButton();
|
||||||
find('deleteSelectedItems').simulate('click');
|
|
||||||
});
|
|
||||||
component.update();
|
|
||||||
expect(component.exists('.euiModal--confirmation')).toBe(true);
|
expect(component.exists('.euiModal--confirmation')).toBe(true);
|
||||||
|
|
||||||
await act(async () => {
|
await clickConfirmModalButton();
|
||||||
find('confirmModalConfirmButton').simulate('click');
|
|
||||||
});
|
|
||||||
expect(deleteItems).toHaveBeenCalledWith([selectedHit]);
|
expect(deleteItems).toHaveBeenCalledWith([selectedHit]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -108,6 +108,7 @@ export interface TableListViewTableProps<
|
||||||
contentEditor?: ContentEditorConfig;
|
contentEditor?: ContentEditorConfig;
|
||||||
|
|
||||||
tableCaption: string;
|
tableCaption: string;
|
||||||
|
/** Flag to force a new fetch of the table items. Whenever it changes, the `findItems()` will be called. */
|
||||||
refreshListBouncer?: boolean;
|
refreshListBouncer?: boolean;
|
||||||
onFetchSuccess: () => void;
|
onFetchSuccess: () => void;
|
||||||
setPageDataTestSubject: (subject: string) => void;
|
setPageDataTestSubject: (subject: string) => void;
|
||||||
|
@ -115,6 +116,12 @@ export interface TableListViewTableProps<
|
||||||
|
|
||||||
export interface State<T extends UserContentCommonSchema = UserContentCommonSchema> {
|
export interface State<T extends UserContentCommonSchema = UserContentCommonSchema> {
|
||||||
items: T[];
|
items: T[];
|
||||||
|
/**
|
||||||
|
* Flag to indicate if there aren't any item when **no filteres are applied**.
|
||||||
|
* When there are no item we render an empty prompt.
|
||||||
|
* Default to `undefined` to indicate that we don't know yet if there are items or not.
|
||||||
|
*/
|
||||||
|
hasNoItems: boolean | undefined;
|
||||||
hasInitialFetchReturned: boolean;
|
hasInitialFetchReturned: boolean;
|
||||||
isFetchingItems: boolean;
|
isFetchingItems: boolean;
|
||||||
isDeletingItems: boolean;
|
isDeletingItems: boolean;
|
||||||
|
@ -293,6 +300,7 @@ function TableListViewTableComp<T extends UserContentCommonSchema>({
|
||||||
|
|
||||||
const isMounted = useRef(false);
|
const isMounted = useRef(false);
|
||||||
const fetchIdx = useRef(0);
|
const fetchIdx = useRef(0);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The "onTableSearchChange()" handler has an async behavior. We want to be able to discard
|
* The "onTableSearchChange()" handler has an async behavior. We want to be able to discard
|
||||||
* previsous search changes and only handle the last one. For that we keep a counter of the changes.
|
* previsous search changes and only handle the last one. For that we keep a counter of the changes.
|
||||||
|
@ -335,9 +343,10 @@ function TableListViewTableComp<T extends UserContentCommonSchema>({
|
||||||
const initialState = useMemo<State<T>>(
|
const initialState = useMemo<State<T>>(
|
||||||
() => ({
|
() => ({
|
||||||
items: [],
|
items: [],
|
||||||
|
hasNoItems: undefined,
|
||||||
totalItems: 0,
|
totalItems: 0,
|
||||||
hasInitialFetchReturned: false,
|
hasInitialFetchReturned: false,
|
||||||
isFetchingItems: false,
|
isFetchingItems: true,
|
||||||
isDeletingItems: false,
|
isDeletingItems: false,
|
||||||
showDeleteModal: false,
|
showDeleteModal: false,
|
||||||
hasUpdatedAtMetadata: false,
|
hasUpdatedAtMetadata: false,
|
||||||
|
@ -364,6 +373,7 @@ function TableListViewTableComp<T extends UserContentCommonSchema>({
|
||||||
hasInitialFetchReturned,
|
hasInitialFetchReturned,
|
||||||
isFetchingItems,
|
isFetchingItems,
|
||||||
items,
|
items,
|
||||||
|
hasNoItems,
|
||||||
fetchError,
|
fetchError,
|
||||||
showDeleteModal,
|
showDeleteModal,
|
||||||
isDeletingItems,
|
isDeletingItems,
|
||||||
|
@ -374,8 +384,6 @@ function TableListViewTableComp<T extends UserContentCommonSchema>({
|
||||||
tableSort,
|
tableSort,
|
||||||
} = state;
|
} = state;
|
||||||
|
|
||||||
const hasQuery = searchQuery.text !== '';
|
|
||||||
const hasNoItems = hasInitialFetchReturned && items.length === 0 && !hasQuery;
|
|
||||||
const showFetchError = Boolean(fetchError);
|
const showFetchError = Boolean(fetchError);
|
||||||
const showLimitError = !showFetchError && totalItems > listingLimit;
|
const showLimitError = !showFetchError && totalItems > listingLimit;
|
||||||
|
|
||||||
|
@ -857,17 +865,7 @@ function TableListViewTableComp<T extends UserContentCommonSchema>({
|
||||||
// ------------
|
// ------------
|
||||||
// Effects
|
// Effects
|
||||||
// ------------
|
// ------------
|
||||||
useDebounce(
|
useDebounce(fetchItems, 300, [fetchItems, refreshListBouncer]);
|
||||||
() => {
|
|
||||||
// Do not call fetchItems on dependency changes when initial fetch does not load any items
|
|
||||||
// to avoid flashing between empty table and no items view
|
|
||||||
if (!hasNoItems) {
|
|
||||||
fetchItems();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
300,
|
|
||||||
[fetchItems, refreshListBouncer]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!urlStateEnabled) {
|
if (!urlStateEnabled) {
|
||||||
|
|
|
@ -77,8 +77,8 @@ export const EventAnnotationGroupTableList = ({
|
||||||
const [refreshListBouncer, setRefreshListBouncer] = useState(false);
|
const [refreshListBouncer, setRefreshListBouncer] = useState(false);
|
||||||
|
|
||||||
const refreshList = useCallback(() => {
|
const refreshList = useCallback(() => {
|
||||||
setRefreshListBouncer(!refreshListBouncer);
|
setRefreshListBouncer((prev) => !prev);
|
||||||
}, [refreshListBouncer]);
|
}, []);
|
||||||
|
|
||||||
const fetchItems = useCallback(
|
const fetchItems = useCallback(
|
||||||
(
|
(
|
||||||
|
|
|
@ -13,8 +13,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||||
const PageObjects = getPageObjects(['common', 'filesManagement']);
|
const PageObjects = getPageObjects(['common', 'filesManagement']);
|
||||||
const testSubjects = getService('testSubjects');
|
const testSubjects = getService('testSubjects');
|
||||||
|
|
||||||
// FLAKY: https://github.com/elastic/kibana/issues/160178
|
describe('Files management', () => {
|
||||||
describe.skip('Files management', () => {
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
await PageObjects.filesManagement.navigateTo();
|
await PageObjects.filesManagement.navigateTo();
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue