-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Endpoint] add policy empty state #69449
Changes from 6 commits
56a4916
a638909
b9628d3
71ce810
17ad610
e427de5
399752c
57c2c1d
f67bb35
0ff87cd
4fc8aef
3336958
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,7 @@ | |
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
|
||
import React, { useCallback, useEffect, useMemo, CSSProperties, useState } from 'react'; | ||
import React, { useCallback, useEffect, useMemo, CSSProperties, useState, MouseEvent } from 'react'; | ||
import { | ||
EuiBasicTable, | ||
EuiText, | ||
|
@@ -22,6 +22,8 @@ import { | |
EuiCallOut, | ||
EuiSpacer, | ||
EuiButton, | ||
EuiSteps, | ||
EuiTitle, | ||
} from '@elastic/eui'; | ||
import { i18n } from '@kbn/i18n'; | ||
import { FormattedMessage } from '@kbn/i18n/react'; | ||
|
@@ -61,6 +63,10 @@ const NO_WRAP_TRUNCATE_STYLE: CSSProperties = Object.freeze({ | |
whiteSpace: 'nowrap', | ||
}); | ||
|
||
const TEXT_ALIGN_CENTER: CSSProperties = Object.freeze({ | ||
textAlign: 'center', | ||
}); | ||
|
||
const DangerEuiContextMenuItem = styled(EuiContextMenuItem)` | ||
color: ${(props) => props.theme.eui.textColors.danger}; | ||
`; | ||
|
@@ -410,24 +416,50 @@ export const PolicyList = React.memo(() => { | |
</EuiButton> | ||
} | ||
bodyHeader={ | ||
<EuiText color="subdued" data-test-subj="policyTotalCount"> | ||
<FormattedMessage | ||
id="xpack.securitySolution.endpoint.policyList.viewTitleTotalCount" | ||
defaultMessage="{totalItemCount, plural, one {# Policy} other {# Policies}}" | ||
values={{ totalItemCount }} | ||
/> | ||
</EuiText> | ||
policyItems && | ||
policyItems.length > 0 && ( | ||
<EuiText color="subdued" data-test-subj="policyTotalCount"> | ||
<FormattedMessage | ||
id="xpack.securitySolution.endpoint.policyList.viewTitleTotalCount" | ||
defaultMessage="{totalItemCount, plural, one {# Policy} other {# Policies}}" | ||
values={{ totalItemCount }} | ||
/> | ||
</EuiText> | ||
) | ||
} | ||
> | ||
<EuiBasicTable | ||
items={useMemo(() => [...policyItems], [policyItems])} | ||
columns={columns} | ||
loading={loading} | ||
pagination={paginationSetup} | ||
onChange={handleTableChange} | ||
data-test-subj="policyTable" | ||
hasActions={false} | ||
/> | ||
{useMemo(() => { | ||
return ( | ||
<> | ||
{policyItems && policyItems.length > 0 ? ( | ||
<EuiBasicTable | ||
items={[...policyItems]} | ||
columns={columns} | ||
loading={loading} | ||
pagination={paginationSetup} | ||
onChange={handleTableChange} | ||
data-test-subj="policyTable" | ||
hasActions={false} | ||
/> | ||
) : ( | ||
<EmptyPolicyTable | ||
loading={loading} | ||
onActionClick={handleCreatePolicyClick} | ||
actionDisabled={isFetchingPackageInfo} | ||
dataTestSubj="emptyPolicyTable" | ||
/> | ||
)} | ||
</> | ||
); | ||
}, [ | ||
policyItems, | ||
loading, | ||
isFetchingPackageInfo, | ||
columns, | ||
handleCreatePolicyClick, | ||
handleTableChange, | ||
paginationSetup, | ||
])} | ||
<SpyRoute /> | ||
</ManagementPageView> | ||
</> | ||
|
@@ -436,6 +468,107 @@ export const PolicyList = React.memo(() => { | |
|
||
PolicyList.displayName = 'PolicyList'; | ||
|
||
const EmptyPolicyTable = React.memo<{ | ||
loading: boolean; | ||
onActionClick: (event: MouseEvent<HTMLAnchorElement | HTMLButtonElement>) => void; | ||
actionDisabled: boolean; | ||
dataTestSubj: string; | ||
}>(({ loading, onActionClick, actionDisabled, dataTestSubj }) => { | ||
const policySteps = [ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion to memoize this array using |
||
{ | ||
title: i18n.translate('xpack.securitySolution.endpoint.policyList.stepOneTitle', { | ||
defaultMessage: 'Head over to Ingest Manager.', | ||
}), | ||
children: ( | ||
<EuiText color="subdued" size="xs"> | ||
<FormattedMessage | ||
id="xpack.securitySolution.endpoint.policyList.stepOne" | ||
defaultMessage="Here, you’ll add the Elastic Endpoint Security Integration to your Agent Configuration." | ||
/> | ||
</EuiText> | ||
), | ||
}, | ||
{ | ||
title: i18n.translate('xpack.securitySolution.endpoint.policyList.stepTwoTitle', { | ||
defaultMessage: 'We’ll create a recommended security policy for you.', | ||
}), | ||
children: ( | ||
<EuiText color="subdued" size="xs"> | ||
<FormattedMessage | ||
id="xpack.securitySolution.endpoint.policyList.stepTwo" | ||
defaultMessage="You can edit this policy in the “Policies” tab after you’ve added the Elastic Endpoint integration." | ||
/> | ||
</EuiText> | ||
), | ||
}, | ||
{ | ||
title: i18n.translate('xpack.securitySolution.endpoint.policyList.stepThreeTitle', { | ||
defaultMessage: 'Enroll your agents through Fleet.', | ||
}), | ||
children: ( | ||
<EuiText color="subdued" size="xs"> | ||
<FormattedMessage | ||
id="xpack.securitySolution.endpoint.policyList.stepThree" | ||
defaultMessage="If you haven’t already, enroll your agents through Fleet using the same agent configuration." | ||
/> | ||
</EuiText> | ||
), | ||
}, | ||
]; | ||
return ( | ||
<div data-test-subj={dataTestSubj}> | ||
{loading ? ( | ||
<FormattedMessage | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
id="xpack.securitySolution.endpoint.policyList.loading" | ||
defaultMessage="Loading..." | ||
/> | ||
) : ( | ||
<> | ||
<EuiSpacer size="xxl" /> | ||
<EuiTitle size="m"> | ||
<h2 style={TEXT_ALIGN_CENTER}> | ||
<FormattedMessage | ||
id="xpack.securitySolution.endpoint.policyList.noPoliciesPrompt" | ||
defaultMessage="Looks like you're not using Elastic Endpoint" | ||
/> | ||
</h2> | ||
</EuiTitle> | ||
<EuiSpacer size="xxl" /> | ||
<EuiText textAlign="center" color="subdued" size="s"> | ||
<FormattedMessage | ||
id="xpack.securitySolution.endpoint.policyList.noPoliciesInstructions" | ||
defaultMessage="Elastic Endpoint Security gives you the power to keep your endpoints safe from attack, as well as unparalleled visibility into any threat in your environment." | ||
/> | ||
</EuiText> | ||
<EuiSpacer size="xxl" /> | ||
<EuiFlexGroup alignItems="center" justifyContent="center"> | ||
<EuiFlexItem grow={false}> | ||
<EuiSteps steps={policySteps} data-test-subj={'onboardingSteps'} /> | ||
</EuiFlexItem> | ||
</EuiFlexGroup> | ||
<EuiFlexGroup alignItems="center" justifyContent="center"> | ||
<EuiFlexItem grow={false}> | ||
<EuiButton | ||
fill | ||
onClick={onActionClick} | ||
isDisabled={actionDisabled} | ||
data-test-subj="onboardingStartButton" | ||
> | ||
<FormattedMessage | ||
id="xpack.securitySolution.endpoint.policyList.emptyCreateNewButton" | ||
defaultMessage="Click here to get started" | ||
/> | ||
</EuiButton> | ||
</EuiFlexItem> | ||
</EuiFlexGroup> | ||
</> | ||
)} | ||
</div> | ||
); | ||
}); | ||
|
||
EmptyPolicyTable.displayName = 'EmptyPolicyTable'; | ||
|
||
const ConfirmDelete = React.memo<{ | ||
hostCount: number; | ||
isDeleting: boolean; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { | |
const testSubjects = getService('testSubjects'); | ||
const policyTestResources = getService('policyTestResources'); | ||
const RELATIVE_DATE_FORMAT = /\d (?:seconds|minutes) ago/i; | ||
const retry = getService('retry'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did you forget to delete this? (you might get a ESLint error There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yep, I sure did |
||
|
||
describe('When on the Endpoint Policy List', function () { | ||
this.tags(['ciGroup7']); | ||
|
@@ -36,29 +37,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { | |
const createButtonTitle = await testSubjects.getVisibleText('headerCreateNewPolicyButton'); | ||
expect(createButtonTitle).to.equal('Create new policy'); | ||
}); | ||
it('shows policy count total', async () => { | ||
const policyTotal = await testSubjects.getVisibleText('policyTotalCount'); | ||
expect(policyTotal).to.equal('0 Policies'); | ||
}); | ||
it('has correct table headers', async () => { | ||
const allHeaderCells = await pageObjects.endpointPageUtils.tableHeaderVisibleText( | ||
'policyTable' | ||
); | ||
expect(allHeaderCells).to.eql([ | ||
'Policy Name', | ||
'Created By', | ||
'Created Date', | ||
'Last Updated By', | ||
'Last Updated', | ||
'Version', | ||
'Actions', | ||
]); | ||
}); | ||
it('should show empty table results message', async () => { | ||
const [, [noItemsFoundMessage]] = await pageObjects.endpointPageUtils.tableData( | ||
'policyTable' | ||
); | ||
expect(noItemsFoundMessage).to.equal('No items found'); | ||
it('shows empty state', async () => { | ||
await testSubjects.existOrFail('emptyPolicyTable'); | ||
}); | ||
|
||
describe('and policies exists', () => { | ||
|
@@ -76,6 +56,21 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { | |
} | ||
}); | ||
|
||
it('has correct table headers', async () => { | ||
const allHeaderCells = await pageObjects.endpointPageUtils.tableHeaderVisibleText( | ||
'policyTable' | ||
); | ||
expect(allHeaderCells).to.eql([ | ||
'Policy Name', | ||
'Created By', | ||
'Created Date', | ||
'Last Updated By', | ||
'Last Updated', | ||
'Version', | ||
'Actions', | ||
]); | ||
}); | ||
|
||
it('should show policy on the list', async () => { | ||
const [, policyRow] = await pageObjects.endpointPageUtils.tableData('policyTable'); | ||
// Validate row data with the exception of the Date columns - since those are initially | ||
|
@@ -106,9 +101,19 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { | |
await pageObjects.policy.launchAndFindDeleteModal(); | ||
await testSubjects.existOrFail('policyListDeleteModal'); | ||
await pageObjects.common.clickConfirmOnModal(); | ||
await pageObjects.endpoint.waitForTableToNotHaveData('policyTable'); | ||
const policyTotal = await testSubjects.getVisibleText('policyTotalCount'); | ||
expect(policyTotal).to.equal('0 Policies'); | ||
let emptyPolicyTable; | ||
await retry.waitForWithTimeout( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why did you need to use const emptyPolicyTable = await testSubjects.find('emptyPolicyTable'); should work (no?) 😃 |
||
'table to not have data and empty state returns', | ||
2000, | ||
async () => { | ||
emptyPolicyTable = await testSubjects.find('emptyPolicyTable'); | ||
if (emptyPolicyTable) { | ||
return true; | ||
} | ||
return false; | ||
} | ||
); | ||
expect(emptyPolicyTable).not.to.be(null); | ||
}); | ||
}); | ||
|
||
|
@@ -148,5 +153,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { | |
await policyTestResources.deletePolicyByName(newPolicyName); | ||
}); | ||
}); | ||
|
||
describe('and user clicks on page header create button', () => { | ||
it('should direct users to the ingest management integrations add datasource', async () => { | ||
await pageObjects.policy.navigateToPolicyList(); | ||
await (await pageObjects.policy.findEmptyStateButton()).click(); | ||
await pageObjects.ingestManagerCreateDatasource.ensureOnCreatePageOrFail(); | ||
}); | ||
}); | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -101,5 +101,13 @@ export function EndpointPolicyPageProvider({ getService, getPageObjects }: FtrPr | |
async findDatasourceEndpointCustomConfiguration(onEditPage: boolean = false) { | ||
return await testSubjects.find(`endpointDatasourceConfig_${onEditPage ? 'edit' : 'create'}`); | ||
}, | ||
|
||
/** | ||
* Finds and returns the onboarding button displayed in empty List pages | ||
*/ | ||
async findEmptyStateButton() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. rename suggestion:
|
||
await testSubjects.waitForEnabled('onboardingStartButton'); | ||
return await testSubjects.find('onboardingStartButton'); | ||
}, | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: change
table
variable name (since its no longer a table)