[Security Solutions][Endpoint] Fixes weird 'flash' when entries does not exists on event filters page (#100203)

* Fixes weird 'flash' when entries does not exists on event filters page. Also fixes a multilang and query when empty string

* Removes old comment

* Use function to retrieve async resource state

* Fix unit test

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
David Sánchez 2021-05-18 18:37:48 +02:00 committed by GitHub
parent 307c98c039
commit ebc4cfc246
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 31 additions and 50 deletions

View file

@ -203,8 +203,7 @@ export const PaginatedContent = memo(
return <Item {...itemComponentProps(item)} key={key} />; return <Item {...itemComponentProps(item)} key={key} />;
}); });
} }
if (!loading) return noItemsMessage || <DefaultNoItemsFound />;
return noItemsMessage || <DefaultNoItemsFound />;
}, [ }, [
ItemComponent, ItemComponent,
error, error,
@ -214,6 +213,7 @@ export const PaginatedContent = memo(
itemKeys, itemKeys,
items, items,
noItemsMessage, noItemsMessage,
loading,
]); ]);
return ( return (

View file

@ -7,7 +7,7 @@
import { MANAGEMENT_DEFAULT_PAGE, MANAGEMENT_DEFAULT_PAGE_SIZE } from '../../../common/constants'; import { MANAGEMENT_DEFAULT_PAGE, MANAGEMENT_DEFAULT_PAGE_SIZE } from '../../../common/constants';
import { EventFiltersListPageState } from '../types'; import { EventFiltersListPageState } from '../types';
import { createLoadedResourceState, createUninitialisedResourceState } from '../../../state'; import { createUninitialisedResourceState } from '../../../state';
export const initialEventFiltersPageState = (): EventFiltersListPageState => ({ export const initialEventFiltersPageState = (): EventFiltersListPageState => ({
entries: [], entries: [],
@ -28,8 +28,7 @@ export const initialEventFiltersPageState = (): EventFiltersListPageState => ({
active: false, active: false,
forceRefresh: false, forceRefresh: false,
data: createUninitialisedResourceState(), data: createUninitialisedResourceState(),
/** We started off assuming data exists, until we can confirm otherwise */ dataExist: createUninitialisedResourceState(),
dataExist: createLoadedResourceState(true),
deletion: { deletion: {
item: undefined, item: undefined,
status: createUninitialisedResourceState(), status: createUninitialisedResourceState(),

View file

@ -254,24 +254,24 @@ const refreshListDataIfNeeded: MiddlewareActionHandler = async (store, eventFilt
dispatch({ dispatch({
type: 'eventFiltersListPageDataChanged', type: 'eventFiltersListPageDataChanged',
payload: createLoadedResourceState({ payload: createLoadedResourceState({
query, query: { ...query, filter },
content: results, content: results,
}), }),
}); });
dispatch({
type: 'eventFiltersListPageDataExistsChanged',
payload: {
type: 'LoadedResourceState',
data: Boolean(results.total),
},
});
// If no results were returned, then just check to make sure data actually exists for // If no results were returned, then just check to make sure data actually exists for
// event filters. This is used to drive the UI between showing "empty state" and "no items found" // event filters. This is used to drive the UI between showing "empty state" and "no items found"
// messages to the user // messages to the user
if (results.total === 0) { if (results.total === 0) {
await checkIfEventFilterDataExist(store, eventFiltersService); await checkIfEventFilterDataExist(store, eventFiltersService);
} else {
dispatch({
type: 'eventFiltersListPageDataExistsChanged',
payload: {
type: 'LoadedResourceState',
data: Boolean(results.total),
},
});
} }
} catch (error) { } catch (error) {
dispatch({ dispatch({

View file

@ -88,24 +88,21 @@ export const getListFetchError: EventFiltersSelector<
return (isFailedResourceState(listPageDataState) && listPageDataState.error) || undefined; return (isFailedResourceState(listPageDataState) && listPageDataState.error) || undefined;
}); });
export const getListIsLoading: EventFiltersSelector<boolean> = createSelector(
getCurrentListPageDataState,
(listDataState) => isLoadingResourceState(listDataState)
);
export const getListPageDataExistsState: EventFiltersSelector< export const getListPageDataExistsState: EventFiltersSelector<
StoreState['listPage']['dataExist'] StoreState['listPage']['dataExist']
> = ({ listPage: { dataExist } }) => dataExist; > = ({ listPage: { dataExist } }) => dataExist;
export const getListIsLoading: EventFiltersSelector<boolean> = createSelector(
getCurrentListPageDataState,
getListPageDataExistsState,
(listDataState, dataExists) =>
isLoadingResourceState(listDataState) || isLoadingResourceState(dataExists)
);
export const getListPageDoesDataExist: EventFiltersSelector<boolean> = createSelector( export const getListPageDoesDataExist: EventFiltersSelector<boolean> = createSelector(
getListPageDataExistsState, getListPageDataExistsState,
(dataExistsState) => { (dataExistsState) => {
if (isLoadedResourceState(dataExistsState)) { return !!getLastLoadedResourceState(dataExistsState)?.data;
return dataExistsState.data;
}
// Until we know for sure that data exists (LoadedState), we assume `true`
return true;
} }
); );
@ -179,7 +176,7 @@ export const listDataNeedsRefresh: EventFiltersSelector<boolean> = createSelecto
forceRefresh || forceRefresh ||
location.page_index + 1 !== currentQuery.page || location.page_index + 1 !== currentQuery.page ||
location.page_size !== currentQuery.perPage || location.page_size !== currentQuery.perPage ||
(!!location.filter && location.filter !== currentQuery.filter) location.filter !== currentQuery.filter
); );
} }
); );

View file

@ -186,14 +186,14 @@ describe('event filters selectors', () => {
}); });
describe('getListPageDoesDataExist()', () => { describe('getListPageDoesDataExist()', () => {
it('should return true (default) until we get a Loaded Resource state', () => { it('should return false (default) until we get a Loaded Resource state', () => {
expect(getListPageDoesDataExist(initialState)).toBe(true); expect(getListPageDoesDataExist(initialState)).toBe(false);
// Set DataExists to Loading // Set DataExists to Loading
// ts-ignore will be fixed when AsyncResourceState is refactored (#830) // ts-ignore will be fixed when AsyncResourceState is refactored (#830)
// @ts-ignore // @ts-ignore
initialState.listPage.dataExist = createLoadingResourceState(initialState.listPage.dataExist); initialState.listPage.dataExist = createLoadingResourceState(initialState.listPage.dataExist);
expect(getListPageDoesDataExist(initialState)).toBe(true); expect(getListPageDoesDataExist(initialState)).toBe(false);
// Set DataExists to Failure // Set DataExists to Failure
initialState.listPage.dataExist = createFailedResourceState({ initialState.listPage.dataExist = createFailedResourceState({
@ -201,7 +201,7 @@ describe('event filters selectors', () => {
error: 'Internal Server Error', error: 'Internal Server Error',
message: 'Something is not right', message: 'Something is not right',
}); });
expect(getListPageDoesDataExist(initialState)).toBe(true); expect(getListPageDoesDataExist(initialState)).toBe(false);
}); });
it('should return false if no data exists', () => { it('should return false if no data exists', () => {

View file

@ -17,7 +17,7 @@ export const getCreationSuccessMessage = (
entry: CreateExceptionListItemSchema | UpdateExceptionListItemSchema | undefined entry: CreateExceptionListItemSchema | UpdateExceptionListItemSchema | undefined
) => { ) => {
return i18n.translate('xpack.securitySolution.eventFilter.form.creationSuccessToastTitle', { return i18n.translate('xpack.securitySolution.eventFilter.form.creationSuccessToastTitle', {
defaultMessage: '"{name}" has been added to the event exceptions list.', defaultMessage: '"{name}" has been added to the event filters list.',
values: { name: entry?.name }, values: { name: entry?.name },
}); });
}; };
@ -33,21 +33,21 @@ export const getUpdateSuccessMessage = (
export const getCreationErrorMessage = (creationError: ServerApiError) => { export const getCreationErrorMessage = (creationError: ServerApiError) => {
return i18n.translate('xpack.securitySolution.eventFilter.form.failedToastTitle.create', { return i18n.translate('xpack.securitySolution.eventFilter.form.failedToastTitle.create', {
defaultMessage: 'There was an error creating the new exception: "{error}"', defaultMessage: 'There was an error creating the new event filter: "{error}"',
values: { error: creationError.message }, values: { error: creationError.message },
}); });
}; };
export const getUpdateErrorMessage = (updateError: ServerApiError) => { export const getUpdateErrorMessage = (updateError: ServerApiError) => {
return i18n.translate('xpack.securitySolution.eventFilter.form.failedToastTitle.update', { return i18n.translate('xpack.securitySolution.eventFilter.form.failedToastTitle.update', {
defaultMessage: 'There was an error updating the exception: "{error}"', defaultMessage: 'There was an error updating the event filter: "{error}"',
values: { error: updateError.message }, values: { error: updateError.message },
}); });
}; };
export const getGetErrorMessage = (getError: ServerApiError) => { export const getGetErrorMessage = (getError: ServerApiError) => {
return i18n.translate('xpack.securitySolution.eventFilter.form.failedToastTitle.get', { return i18n.translate('xpack.securitySolution.eventFilter.form.failedToastTitle.get', {
defaultMessage: 'Unable to edit trusted application: "{error}"', defaultMessage: 'Unable to edit event filter: "{error}"',
values: { error: getError.message }, values: { error: getError.message },
}); });
}; };

View file

@ -3360,22 +3360,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the first time
> >
<div <div
class="body-content undefined" class="body-content undefined"
> />
<div
class="euiEmptyPrompt"
>
<span
class="euiTextColor euiTextColor--subdued"
>
<span>
No items found
</span>
<div
class="euiSpacer euiSpacer--m"
/>
</span>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>