[Security Solutions][Endpoint] Hide headers when there is no data on exceptions list (#115526)

* hide headers when there is no data or loading for trusted apps, event filters and host isolation exceptions list pages

* Fix ts error

* Fix integration test

* Create a wrapper to set a margin-top in order to center content. Also fix a bug when switching between exceptions pages main menu wasn't updated.

* Remove unused import

* Update trusted apps text and changed testId for host isolation add button

* Use flex instead margin to vertically center content

* Remove wrong prop to fix ts types

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
David Sánchez 2021-10-26 16:30:33 +02:00 committed by GitHub
parent 7bd1452ec9
commit ff8e0f54e4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 153 additions and 102 deletions

View file

@ -26,6 +26,7 @@ interface AdministrationListPageProps {
actions?: React.ReactNode;
restrictWidth?: boolean | number;
hasBottomBorder?: boolean;
hideHeader?: boolean;
headerBackComponent?: React.ReactNode;
}
@ -37,6 +38,7 @@ export const AdministrationListPage: FC<AdministrationListPageProps & CommonProp
children,
restrictWidth = false,
hasBottomBorder = true,
hideHeader = false,
headerBackComponent,
...otherProps
}) => {
@ -63,15 +65,20 @@ export const AdministrationListPage: FC<AdministrationListPageProps & CommonProp
return (
<div {...otherProps}>
<EuiPageHeader
pageTitle={header}
description={description}
bottomBorder={hasBottomBorder}
rightSideItems={actions ? [actions] : undefined}
restrictWidth={restrictWidth}
data-test-subj={getTestId('header')}
/>
<EuiSpacer size="l" />
{!hideHeader && (
<>
<EuiPageHeader
pageTitle={header}
description={description}
bottomBorder={hasBottomBorder}
rightSideItems={actions ? [actions] : undefined}
restrictWidth={restrictWidth}
data-test-subj={getTestId('header')}
/>
<EuiSpacer size="l" />
</>
)}
<EuiPageContent
hasBorder={false}
hasShadow={false}

View file

@ -0,0 +1,24 @@
/*
* 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 React, { memo } from 'react';
import { EuiFlexGroup, EuiPageTemplate } from '@elastic/eui';
import styled from 'styled-components';
export const StyledEuiFlexGroup = styled(EuiFlexGroup)`
min-height: calc(100vh - 140px);
`;
export const ManagementEmptyStateWraper = memo(({ children }) => {
return (
<StyledEuiFlexGroup direction="column" alignItems="center">
<EuiPageTemplate template="centeredContent">{children}</EuiPageTemplate>
</StyledEuiFlexGroup>
);
});
ManagementEmptyStateWraper.displayName = 'ManagementEmptyStateWraper';

View file

@ -9,6 +9,7 @@ import React, { memo } from 'react';
import styled, { css } from 'styled-components';
import { EuiButton, EuiEmptyPrompt } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { ManagementEmptyStateWraper } from '../../../../../components/management_empty_state_wraper';
const EmptyPrompt = styled(EuiEmptyPrompt)`
${() => css`
@ -22,37 +23,39 @@ export const EventFiltersListEmptyState = memo<{
isAddDisabled?: boolean;
}>(({ onAdd, isAddDisabled = false }) => {
return (
<EmptyPrompt
data-test-subj="eventFiltersEmpty"
iconType="plusInCircle"
title={
<h2>
<ManagementEmptyStateWraper>
<EmptyPrompt
data-test-subj="eventFiltersEmpty"
iconType="plusInCircle"
title={
<h2>
<FormattedMessage
id="xpack.securitySolution.eventFilters.listEmpty.title"
defaultMessage="Add your first event filter"
/>
</h2>
}
body={
<FormattedMessage
id="xpack.securitySolution.eventFilters.listEmpty.title"
defaultMessage="Add your first event filter"
id="xpack.securitySolution.eventFilters.listEmpty.message"
defaultMessage="There are currently no event filters on your endpoint."
/>
</h2>
}
body={
<FormattedMessage
id="xpack.securitySolution.eventFilters.listEmpty.message"
defaultMessage="There are currently no event filters on your endpoint."
/>
}
actions={
<EuiButton
fill
isDisabled={isAddDisabled}
onClick={onAdd}
data-test-subj="eventFiltersListEmptyStateAddButton"
>
<FormattedMessage
id="xpack.securitySolution.eventFilters.listEmpty.addButton"
defaultMessage="Add event filter"
/>
</EuiButton>
}
/>
}
actions={
<EuiButton
fill
isDisabled={isAddDisabled}
onClick={onAdd}
data-test-subj="eventFiltersListEmptyStateAddButton"
>
<FormattedMessage
id="xpack.securitySolution.eventFilters.listEmpty.addButton"
defaultMessage="Add event filter"
/>
</EuiButton>
}
/>
</ManagementEmptyStateWraper>
);
});

View file

@ -248,6 +248,7 @@ export const EventFiltersListPage = memo(() => {
</EuiButton>
)
}
hideHeader={!doesDataExist}
>
{showFlyout && (
<EventFiltersFlyout

View file

@ -9,6 +9,7 @@ import React, { memo } from 'react';
import styled, { css } from 'styled-components';
import { EuiButton, EuiEmptyPrompt } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { ManagementEmptyStateWraper } from '../../../../components/management_empty_state_wraper';
const EmptyPrompt = styled(EuiEmptyPrompt)`
${() => css`
@ -18,32 +19,38 @@ const EmptyPrompt = styled(EuiEmptyPrompt)`
export const HostIsolationExceptionsEmptyState = memo<{ onAdd: () => void }>(({ onAdd }) => {
return (
<EmptyPrompt
data-test-subj="hostIsolationExceptionsEmpty"
iconType="plusInCircle"
title={
<h2>
<ManagementEmptyStateWraper>
<EmptyPrompt
data-test-subj="hostIsolationExceptionsEmpty"
iconType="plusInCircle"
title={
<h2>
<FormattedMessage
id="xpack.securitySolution.hostIsolationExceptions.listEmpty.title"
defaultMessage="Add your first Host isolation exception"
/>
</h2>
}
body={
<FormattedMessage
id="xpack.securitySolution.hostIsolationExceptions.listEmpty.title"
defaultMessage="Add your first Host isolation exception"
id="xpack.securitySolution.hostIsolationExceptions.listEmpty.message"
defaultMessage="There are currently no host isolation exceptions"
/>
</h2>
}
body={
<FormattedMessage
id="xpack.securitySolution.hostIsolationExceptions.listEmpty.message"
defaultMessage="There are currently no host isolation exceptions"
/>
}
actions={
<EuiButton fill onClick={onAdd} data-test-subj="hostIsolationExceptions">
<FormattedMessage
id="xpack.securitySolution.hostIsolationExceptions.listEmpty.addButton"
defaultMessage="Add Host isolation exception"
/>
</EuiButton>
}
/>
}
actions={
<EuiButton
fill
onClick={onAdd}
data-test-subj="hostIsolationExceptionsEmptyStateAddButton"
>
<FormattedMessage
id="xpack.securitySolution.hostIsolationExceptions.listEmpty.addButton"
defaultMessage="Add Host isolation exception"
/>
</EuiButton>
}
/>
</ManagementEmptyStateWraper>
);
});

View file

@ -130,10 +130,11 @@ describe('When on the host isolation exceptions page', () => {
beforeEach(() => {
isPlatinumPlusMock.mockReturnValue(true);
});
it('should show the create flyout when the add button is pressed', () => {
it('should show the create flyout when the add button is pressed', async () => {
render();
await dataReceived();
act(() => {
userEvent.click(renderResult.getByTestId('hostIsolationExceptionsListAddButton'));
userEvent.click(renderResult.getByTestId('hostIsolationExceptionsEmptyStateAddButton'));
});
expect(renderResult.getByTestId('hostIsolationExceptionsCreateEditFlyout')).toBeTruthy();
});

View file

@ -8,7 +8,7 @@
import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import { i18n } from '@kbn/i18n';
import React, { Dispatch, useCallback, useEffect } from 'react';
import { EuiButton, EuiSpacer } from '@elastic/eui';
import { EuiButton } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { useDispatch } from 'react-redux';
import { useHistory } from 'react-router-dom';
@ -148,12 +148,13 @@ export const HostIsolationExceptionsList = () => {
[]
)
}
hideHeader={isLoading || listItems.length === 0}
>
{showFlyout && <HostIsolationExceptionsFormFlyout />}
{itemToDelete ? <HostIsolationExceptionDeleteModal /> : null}
{listItems.length ? (
{!isLoading && listItems.length ? (
<SearchExceptions
defaultValue={location.filter}
onSearch={handleOnSearch}
@ -166,8 +167,6 @@ export const HostIsolationExceptionsList = () => {
/>
) : null}
<EuiSpacer size="l" />
<PaginatedContent<ExceptionListItemSchema, typeof ArtifactEntryCard>
items={listItems}
ItemComponent={ArtifactEntryCard}

View file

@ -8,6 +8,7 @@
import React, { memo } from 'react';
import { EuiButton, EuiEmptyPrompt } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { ManagementEmptyStateWraper } from '../../../../components/management_empty_state_wraper';
export const EmptyState = memo<{
onAdd: () => void;
@ -15,37 +16,39 @@ export const EmptyState = memo<{
isAddDisabled?: boolean;
}>(({ onAdd, isAddDisabled = false }) => {
return (
<EuiEmptyPrompt
data-test-subj="trustedAppEmptyState"
iconType="plusInCircle"
title={
<h2>
<ManagementEmptyStateWraper>
<EuiEmptyPrompt
data-test-subj="trustedAppEmptyState"
iconType="plusInCircle"
title={
<h2>
<FormattedMessage
id="xpack.securitySolution.trustedapps.listEmptyState.title"
defaultMessage="Add your first trusted application"
/>
</h2>
}
body={
<FormattedMessage
id="xpack.securitySolution.trustedapps.listEmptyState.title"
defaultMessage="Add your first trusted application"
id="xpack.securitySolution.trustedapps.listEmptyState.message"
defaultMessage="There are currently no trusted applications on your endpoint."
/>
</h2>
}
body={
<FormattedMessage
id="xpack.securitySolution.trustedapps.listEmptyState.message"
defaultMessage="There are currently no trusted applications on your endpoint."
/>
}
actions={
<EuiButton
fill
isDisabled={isAddDisabled}
onClick={onAdd}
data-test-subj="trustedAppsListAddButton"
>
<FormattedMessage
id="xpack.securitySolution.trustedapps.list.addButton"
defaultMessage="Add trusted application"
/>
</EuiButton>
}
/>
}
actions={
<EuiButton
fill
isDisabled={isAddDisabled}
onClick={onAdd}
data-test-subj="trustedAppsListAddButton"
>
<FormattedMessage
id="xpack.securitySolution.trustedapps.list.addButton"
defaultMessage="Add trusted application"
/>
</EuiButton>
}
/>
</ManagementEmptyStateWraper>
);
});

View file

@ -171,7 +171,8 @@ export const TrustedAppsPage = memo(() => {
}
headerBackComponent={backButton}
subtitle={ABOUT_TRUSTED_APPS}
actions={canDisplayContent() ? addButton : <></>}
actions={addButton}
hideHeader={!canDisplayContent()}
>
<TrustedAppsNotifications />

View file

@ -29,10 +29,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await endpointTestResources.unloadEndpointData(indexedData);
});
it('should show page title', async () => {
expect(await testSubjects.getVisibleText('header-page-title')).to.equal(
'Trusted applications'
);
it('should not show page title if there is no trusted app', async () => {
await testSubjects.missingOrFail('header-page-title');
});
it('should be able to add a new trusted app and remove it', async () => {
@ -56,6 +54,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
);
await pageObjects.common.closeToast();
// Title is shown after adding an item
expect(await testSubjects.getVisibleText('header-page-title')).to.equal(
'Trusted applications'
);
// Remove it
await pageObjects.trustedApps.clickCardActionMenu();
await testSubjects.click('deleteTrustedAppAction');
@ -63,6 +66,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await testSubjects.waitForDeleted('trustedAppDeletionConfirm');
// We only expect one trusted app to have been visible
await testSubjects.missingOrFail('trustedAppCard');
// Header has gone because there is no trusted app
await testSubjects.missingOrFail('header-page-title');
});
});
};