[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:
parent
7bd1452ec9
commit
ff8e0f54e4
10 changed files with 153 additions and 102 deletions
|
@ -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}
|
||||
|
|
|
@ -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';
|
|
@ -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>
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -248,6 +248,7 @@ export const EventFiltersListPage = memo(() => {
|
|||
</EuiButton>
|
||||
)
|
||||
}
|
||||
hideHeader={!doesDataExist}
|
||||
>
|
||||
{showFlyout && (
|
||||
<EventFiltersFlyout
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -171,7 +171,8 @@ export const TrustedAppsPage = memo(() => {
|
|||
}
|
||||
headerBackComponent={backButton}
|
||||
subtitle={ABOUT_TRUSTED_APPS}
|
||||
actions={canDisplayContent() ? addButton : <></>}
|
||||
actions={addButton}
|
||||
hideHeader={!canDisplayContent()}
|
||||
>
|
||||
<TrustedAppsNotifications />
|
||||
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue