-
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
[CTI] adds Risky Host Overview Card #109553
Changes from 12 commits
fa8d99e
7da420c
d6fdd56
620ad4c
2bb7903
09d657b
a363b5b
1f0b225
5df51bf
6e6a51b
6f636eb
75668fe
588a287
1ad0305
ee4ee86
7da8199
71c05be
5441214
465e273
d750217
3e843a3
7bf93d3
8899db4
d29e7d6
96fdf5e
97e67d7
2572427
a0e0b96
553ae3e
5c68f1f
a9dca41
e9ddd2a
29284b9
2967919
af99857
ab14a4d
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 |
---|---|---|
@@ -0,0 +1,15 @@ | ||
/* | ||
* 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 { Inspect, Maybe, RequestBasicOptions } from '../../..'; | ||
import { IEsSearchResponse } from '../../../../../../../../src/plugins/data/common'; | ||
|
||
export type HostsRiskyHostsRequestOptions = RequestBasicOptions; | ||
|
||
export interface HostsRiskyHostsStrategyResponse extends IEsSearchResponse { | ||
inspect?: Maybe<Inspect>; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
/* | ||
* 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 { | ||
OVERVIEW_RISKY_HOSTS_ENABLE_MODULE_BUTTON, | ||
OVERVIEW_RISKY_HOSTS_LINKS, | ||
OVERVIEW_RISKY_HOSTS_LINKS_ERROR_INNER_PANEL, | ||
OVERVIEW_RISKY_HOSTS_LINKS_WARNING_INNER_PANEL, | ||
OVERVIEW_RISKY_HOSTS_TOTAL_EVENT_COUNT, | ||
OVERVIEW_RISKY_HOSTS_VIEW_DASHBOARD_BUTTON, | ||
} from '../../screens/overview'; | ||
|
||
import { loginAndWaitForPage } from '../../tasks/login'; | ||
import { OVERVIEW_URL } from '../../urls/navigation'; | ||
import { cleanKibana } from '../../tasks/common'; | ||
import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver'; | ||
|
||
describe('Risky Hosts Link Panel', () => { | ||
before(() => { | ||
cleanKibana(); | ||
}); | ||
|
||
it('renders disabled panel view as expected', () => { | ||
loginAndWaitForPage(OVERVIEW_URL); | ||
cy.get(`${OVERVIEW_RISKY_HOSTS_LINKS} ${OVERVIEW_RISKY_HOSTS_LINKS_ERROR_INNER_PANEL}`).should( | ||
'exist' | ||
); | ||
cy.get(`${OVERVIEW_RISKY_HOSTS_VIEW_DASHBOARD_BUTTON}`).should('be.disabled'); | ||
cy.get(`${OVERVIEW_RISKY_HOSTS_TOTAL_EVENT_COUNT}`).should('have.text', 'Showing: 0 hosts'); | ||
cy.get(`${OVERVIEW_RISKY_HOSTS_ENABLE_MODULE_BUTTON}`).should('exist'); | ||
cy.get(`${OVERVIEW_RISKY_HOSTS_ENABLE_MODULE_BUTTON}`) | ||
.should('have.attr', 'href') | ||
.and('match', /host-risk-score.md/); | ||
}); | ||
|
||
describe('enabled module', () => { | ||
before(() => { | ||
esArchiverLoad('risky_hosts'); | ||
}); | ||
|
||
after(() => { | ||
esArchiverUnload('risky_hosts'); | ||
}); | ||
|
||
it('renders disabled dashboard module as expected when there are no hosts in the selected time period', () => { | ||
loginAndWaitForPage( | ||
`${OVERVIEW_URL}?sourcerer=(timerange:(from:%272021-07-08T04:00:00.000Z%27,kind:absolute,to:%272021-07-09T03:59:59.999Z%27))` | ||
); | ||
cy.get( | ||
`${OVERVIEW_RISKY_HOSTS_LINKS} ${OVERVIEW_RISKY_HOSTS_LINKS_WARNING_INNER_PANEL}` | ||
).should('exist'); | ||
cy.get(`${OVERVIEW_RISKY_HOSTS_VIEW_DASHBOARD_BUTTON}`).should('be.disabled'); | ||
cy.get(`${OVERVIEW_RISKY_HOSTS_TOTAL_EVENT_COUNT}`).should('have.text', 'Showing: 0 hosts'); | ||
}); | ||
|
||
it('renders dashboard module as expected when there are hosts in the selected time period', () => { | ||
loginAndWaitForPage(OVERVIEW_URL); | ||
cy.get( | ||
`${OVERVIEW_RISKY_HOSTS_LINKS} ${OVERVIEW_RISKY_HOSTS_LINKS_WARNING_INNER_PANEL}` | ||
).should('not.exist'); | ||
cy.get(`${OVERVIEW_RISKY_HOSTS_VIEW_DASHBOARD_BUTTON}`).should('be.disabled'); | ||
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. Should we add tests that cover cases when the dashboard button is enabled? 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. there were some complexities around uploading dashboard saved objects from last time around, there is an issue to track that work here at elastic/security-team#1438. I will add that issue to our 7.16checklist as a backlog item |
||
cy.get(`${OVERVIEW_RISKY_HOSTS_TOTAL_EVENT_COUNT}`).should('have.text', 'Showing: 1 host'); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
/* | ||
* 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 from 'react'; | ||
import { mount } from 'enzyme'; | ||
import { TestProviders } from '../../../common/mock'; | ||
import { DisabledLinkPanel } from './disabled_link_panel'; | ||
import { ThreatIntelPanelView as TestView } from '../overview_cti_links/threat_intel_panel_view'; | ||
|
||
jest.mock('../../../common/lib/kibana'); | ||
|
||
describe('DisabledLinkPanel', () => { | ||
const defaultProps = { | ||
docLink: '/doclink', | ||
listItems: [], | ||
titleCopy: 'title', | ||
bodyCopy: 'body', | ||
buttonCopy: 'button', | ||
dataTestSubjPrefix: 'test-prefix', | ||
LinkPanelViewComponent: TestView, | ||
}; | ||
|
||
it('renders expected children', () => { | ||
const wrapper = mount( | ||
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. Would you be able to rewrite this test using React Testing Library? We're trying to phase out enzyme in favor of it. |
||
<TestProviders> | ||
<DisabledLinkPanel {...defaultProps} /> | ||
</TestProviders> | ||
); | ||
|
||
expect( | ||
wrapper.exists( | ||
'[data-test-subj="test-prefix-inner-panel-danger"] [data-test-subj="test-prefix-enable-module-button"]' | ||
) | ||
).toEqual(true); | ||
expect( | ||
wrapper.find('[data-test-subj="test-prefix-enable-module-button"]').hostNodes().text() | ||
).toEqual(defaultProps.buttonCopy); | ||
expect(wrapper.find('a').hostNodes().props().href).toEqual(defaultProps.docLink); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/* | ||
* 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 { EuiButton } from '@elastic/eui'; | ||
|
||
import { InnerLinkPanel } from './inner_link_panel'; | ||
import { LinkPanelListItem, LinkPanelViewProps } from './types'; | ||
|
||
interface DisabledLinkPanelProps { | ||
docLink: string; | ||
listItems: LinkPanelListItem[]; | ||
titleCopy: string; | ||
bodyCopy: string; | ||
buttonCopy: string; | ||
dataTestSubjPrefix: string; | ||
LinkPanelViewComponent: React.ComponentType<LinkPanelViewProps>; | ||
} | ||
|
||
const DisabledLinkPanelComponent: React.FC<DisabledLinkPanelProps> = ({ | ||
docLink, | ||
listItems, | ||
titleCopy, | ||
bodyCopy, | ||
buttonCopy, | ||
dataTestSubjPrefix, | ||
LinkPanelViewComponent, | ||
}) => { | ||
return ( | ||
<LinkPanelViewComponent | ||
splitPanel={ | ||
<InnerLinkPanel | ||
color="warning" | ||
title={titleCopy} | ||
body={bodyCopy} | ||
button={ | ||
<EuiButton | ||
href={docLink} | ||
color="warning" | ||
target="_blank" | ||
data-test-subj={`${dataTestSubjPrefix}-enable-module-button`} | ||
> | ||
{buttonCopy} | ||
</EuiButton> | ||
} | ||
dataTestSubj={`${dataTestSubjPrefix}-inner-panel-danger`} | ||
/> | ||
} | ||
listItems={listItems} | ||
/> | ||
); | ||
}; | ||
|
||
export const DisabledLinkPanel = memo(DisabledLinkPanelComponent); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/* | ||
* 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 { LinkPanelListItem } from '.'; | ||
|
||
export const isLinkPanelListItem = ( | ||
item: LinkPanelListItem | Partial<LinkPanelListItem> | ||
): item is LinkPanelListItem => | ||
typeof item.title === 'string' && typeof item.path === 'string' && typeof item.count === 'number'; | ||
|
||
export interface EventCounts { | ||
[key: string]: number; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
/* | ||
* 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. | ||
*/ | ||
|
||
export { LinkPanel } from './link_panel'; | ||
export { InnerLinkPanel } from './inner_link_panel'; | ||
export { LinkPanelListItem } from './types'; | ||
export { isLinkPanelListItem } from './helpers'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
/* | ||
* 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 from 'react'; | ||
import { mount } from 'enzyme'; | ||
import { TestProviders } from '../../../common/mock'; | ||
import { InnerLinkPanel } from './inner_link_panel'; | ||
|
||
describe('InnerLinkPanel', () => { | ||
const defaultProps = { | ||
title: 'test_title', | ||
body: 'test_body', | ||
button: <div className={'test-button'} />, | ||
dataTestSubj: 'test-subj', | ||
}; | ||
|
||
it('renders expected children', () => { | ||
const wrapper = mount( | ||
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. Ditto on using 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. updated |
||
<TestProviders> | ||
<InnerLinkPanel color="warning" {...defaultProps} /> | ||
</TestProviders> | ||
); | ||
|
||
expect(wrapper.exists('[data-test-subj="test-subj"]')).toEqual(true); | ||
expect(wrapper.exists('.test-button')).toEqual(true); | ||
expect(wrapper.find('[data-test-subj="inner-link-panel-title"]').hostNodes().text()).toEqual( | ||
defaultProps.title | ||
); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/* | ||
* 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 from 'react'; | ||
import { mount } from 'enzyme'; | ||
import { Link } from './link'; | ||
|
||
describe('Link', () => { | ||
it('renders <a> tag when there is a path', () => { | ||
const wrapper = mount(<Link copy={'test'} path={'/path'} />); | ||
|
||
expect(wrapper.exists('a')).toEqual(true); | ||
expect(wrapper.find('a').hostNodes().props().href).toEqual('/path'); | ||
expect(wrapper.find('a').hostNodes().text()).toEqual('test(opens in a new tab or window)'); | ||
}); | ||
|
||
it('does not render <a> tag when there is no path', () => { | ||
const wrapper = mount(<Link copy={'test'} />); | ||
|
||
expect(wrapper.exists('a')).toEqual(false); | ||
expect(wrapper.find('div').hostNodes().at(0).text()).toEqual('test'); | ||
}); | ||
}); |
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.
Is this a typo? It looks like the response type is associated to the wrong
T
here.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.
fixed, thank you!