From 7ff7d0449018dcdaacd949dc7c2de1651f0892d5 Mon Sep 17 00:00:00 2001 From: Paul Tavares Date: Tue, 7 Apr 2020 14:45:03 -0400 Subject: [PATCH 1/7] Add LinktoApp to host details for logs --- .../endpoint/components/link_to_app.tsx | 2 +- .../endpoint/view/hosts/details.tsx | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/components/link_to_app.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/components/link_to_app.tsx index b110d32442c2c4..858dac864b58aa 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/components/link_to_app.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/components/link_to_app.tsx @@ -12,7 +12,7 @@ import { useNavigateToAppEventHandler } from '../hooks/use_navigate_to_app_event export type LinkToAppProps = EuiLinkProps & { /** the app id - normally the value of the `id` in that plugin's `kibana.json` */ appId: string; - /** Any app specic path (route) */ + /** Any app specific path (route) */ appPath?: string; appState?: any; onClick?: MouseEventHandler; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details.tsx index 37080e8568350e..8c11f25c96abb1 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details.tsx @@ -28,6 +28,7 @@ import { useHostListSelector } from './hooks'; import { urlFromQueryParams } from './url_from_query_params'; import { FormattedDateAndTime } from '../formatted_date_time'; import { uiQueryParams, detailsData, detailsError } from './../../store/hosts/selectors'; +import { LinkToApp } from '../../components/link_to_app'; const HostIds = styled(EuiListGroupItem)` margin-top: 0; @@ -37,6 +38,7 @@ const HostIds = styled(EuiListGroupItem)` `; const HostDetails = memo(({ details }: { details: HostMetadata }) => { + const { appId, appPath, url } = useHostLogsUrl(details.host.id); const detailsResultsUpper = useMemo(() => { return [ { @@ -113,6 +115,15 @@ const HostDetails = memo(({ details }: { details: HostMetadata }) => { listItems={detailsResultsLower} data-test-subj="hostDetailsLowerList" /> + +

+ + + +

); }); @@ -170,3 +181,15 @@ export const HostDetailsFlyout = () => { ); }; + +const useHostLogsUrl = (hostId: string): { url: string; appId: string; appPath: string } => { + const { services } = useKibana(); + return useMemo(() => { + const appPath = `/stream?logFilter=(expression:host.id:${hostId},kind:kuery)`; + return { + url: `${services.application.getUrlForApp('logs')}${appPath}`, + appId: 'logs', + appPath, + }; + }, [hostId, services.application]); +}; From 7e25e07b0ba0f380d6f7271b2ea8f0e3ac4d1514 Mon Sep 17 00:00:00 2001 From: Paul Tavares Date: Tue, 7 Apr 2020 16:43:28 -0400 Subject: [PATCH 2/7] initial setup for testing link on details --- .../store/hosts/mock_host_result_list.ts | 10 +++++++++- .../endpoint/view/hosts/details.tsx | 9 +++++++-- .../endpoint/view/hosts/index.test.tsx | 18 ++++++++++++++++-- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/mock_host_result_list.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/mock_host_result_list.ts index db39ecf4483122..96a5fc9aaf936f 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/mock_host_result_list.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/mock_host_result_list.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { HostResultList } from '../../../../../common/types'; +import { HostMetadata, HostResultList } from '../../../../../common/types'; import { EndpointDocGenerator } from '../../../../../common/generate_data'; export const mockHostResultList: (options?: { @@ -37,3 +37,11 @@ export const mockHostResultList: (options?: { }; return mock; }; + +/** + * returns a mocked API response for retrieving a single host metadata + */ +export const mockHostDetailsApiResult = (): HostMetadata => { + const generator = new EndpointDocGenerator('seed'); + return generator.generateHostMetadata(); +}; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details.tsx index 8c11f25c96abb1..90829f7ad4cbe3 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details.tsx @@ -117,7 +117,12 @@ const HostDetails = memo(({ details }: { details: HostMetadata }) => { />

- + { const useHostLogsUrl = (hostId: string): { url: string; appId: string; appPath: string } => { const { services } = useKibana(); return useMemo(() => { - const appPath = `/stream?logFilter=(expression:host.id:${hostId},kind:kuery)`; + const appPath = `/stream?logFilter=(expression:'host.id:${hostId}',kind:kuery)`; return { url: `${services.application.getUrlForApp('logs')}${appPath}`, appId: 'logs', diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx index f6dfae99c1b11b..d85829896c94d3 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx @@ -15,7 +15,10 @@ import { createMemoryHistory, MemoryHistory } from 'history'; import { Router } from 'react-router-dom'; import { AppAction } from '../../types'; import { HostList } from './index'; -import { mockHostResultList } from '../../store/hosts/mock_host_result_list'; +import { + mockHostDetailsApiResult, + mockHostResultList, +} from '../../store/hosts/mock_host_result_list'; describe('when on the hosts page', () => { let render: () => reactTestingLibrary.RenderResult; @@ -56,7 +59,7 @@ describe('when on the hosts page', () => { expect(e).not.toBeNull(); }); }); - describe('when data loads', () => { + describe('when list data loads', () => { beforeEach(() => { reactTestingLibrary.act(() => { const action: AppAction = { @@ -100,6 +103,12 @@ describe('when on the hosts page', () => { search: '?selected_host=1', }); }); + reactTestingLibrary.act(() => { + store.dispatch({ + type: 'serverReturnedHostDetails', + payload: mockHostDetailsApiResult(), + }); + }); }); it('should show the flyout', () => { const renderResult = render(); @@ -107,5 +116,10 @@ describe('when on the hosts page', () => { expect(flyout).not.toBeNull(); }); }); + it('should include the link to logs', async () => { + const renderResult = render(); + const linkToLogs = await renderResult.findByTestId('hostDetailsLinkToLogs'); + expect(linkToLogs).not.toBeNull(); + }); }); }); From cbfe44edab8890403bec60b898632e47ad8209b1 Mon Sep 17 00:00:00 2001 From: Paul Tavares Date: Tue, 7 Apr 2020 17:45:39 -0400 Subject: [PATCH 3/7] Export interface AppContextTestRender for reference in tests --- .../public/applications/endpoint/mocks/app_context_render.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/mocks/app_context_render.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/mocks/app_context_render.tsx index af34205e2310f3..7cb1031ef9a09a 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/mocks/app_context_render.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/mocks/app_context_render.tsx @@ -18,7 +18,7 @@ type UiRender = (ui: React.ReactElement, options?: RenderOptions) => RenderResul /** * Mocked app root context renderer */ -interface AppContextTestRender { +export interface AppContextTestRender { store: ReturnType; history: ReturnType; coreStart: ReturnType; From d570c282be3aeb9e13ea6043bc278d70718963a3 Mon Sep 17 00:00:00 2001 From: Paul Tavares Date: Tue, 7 Apr 2020 17:46:47 -0400 Subject: [PATCH 4/7] Refactor hosts tests to use AppContextTestRender --- .../store/hosts/mock_host_result_list.ts | 2 +- .../endpoint/view/hosts/index.test.tsx | 37 +++++-------------- 2 files changed, 11 insertions(+), 28 deletions(-) diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/mock_host_result_list.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/mock_host_result_list.ts index 96a5fc9aaf936f..9b2d5bfc1dece7 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/mock_host_result_list.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/mock_host_result_list.ts @@ -41,7 +41,7 @@ export const mockHostResultList: (options?: { /** * returns a mocked API response for retrieving a single host metadata */ -export const mockHostDetailsApiResult = (): HostMetadata => { +export const mockHostDetailsApiResult = (): ReturnType => { const generator = new EndpointDocGenerator('seed'); return generator.generateHostMetadata(); }; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx index d85829896c94d3..cda12e61710dc8 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx @@ -6,43 +6,26 @@ import React from 'react'; import * as reactTestingLibrary from '@testing-library/react'; -import { Provider } from 'react-redux'; -import { I18nProvider } from '@kbn/i18n/react'; -import { EuiThemeProvider } from '../../../../../../../legacy/common/eui_styled_components'; -import { appStoreFactory } from '../../store'; -import { RouteCapture } from '../route_capture'; -import { createMemoryHistory, MemoryHistory } from 'history'; -import { Router } from 'react-router-dom'; +import { fireEvent } from '@testing-library/react'; import { AppAction } from '../../types'; import { HostList } from './index'; import { mockHostDetailsApiResult, mockHostResultList, } from '../../store/hosts/mock_host_result_list'; +import { AppContextTestRender, createAppRootMockRenderer } from '../../mocks'; +import { HostMetadata } from '../../../../../common/types'; describe('when on the hosts page', () => { - let render: () => reactTestingLibrary.RenderResult; - let history: MemoryHistory; - let store: ReturnType; + let render: () => ReturnType; + let history: AppContextTestRender['history']; + let store: AppContextTestRender['store']; + let coreStart: AppContextTestRender['coreStart']; beforeEach(async () => { - history = createMemoryHistory(); - store = appStoreFactory(); - render = () => { - return reactTestingLibrary.render( - - - - - - - - - - - - ); - }; + const mockedContext = createAppRootMockRenderer(); + ({ history, store, coreStart } = mockedContext); + render = () => mockedContext.render(); }); it('should show a table', async () => { From b7351afe6c839c7db22605ddedb2cfdae44a1a29 Mon Sep 17 00:00:00 2001 From: Paul Tavares Date: Tue, 7 Apr 2020 17:48:11 -0400 Subject: [PATCH 5/7] Render full details and validate link to logs --- .../endpoint/view/hosts/index.test.tsx | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx index cda12e61710dc8..dcc525a622a427 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx @@ -79,7 +79,19 @@ describe('when on the hosts page', () => { }); describe('when there is a selected host in the url', () => { + let hostDetails: HostMetadata; beforeEach(() => { + const { host, ...details } = mockHostDetailsApiResult(); + hostDetails = { + ...details, + host: { + ...host, + id: '1', + }, + }; + + coreStart.application.getUrlForApp.mockReturnValue('/app/logs'); + reactTestingLibrary.act(() => { history.push({ ...history.location, @@ -89,10 +101,14 @@ describe('when on the hosts page', () => { reactTestingLibrary.act(() => { store.dispatch({ type: 'serverReturnedHostDetails', - payload: mockHostDetailsApiResult(), + payload: hostDetails, }); }); }); + afterEach(() => { + jest.clearAllMocks(); + }); + it('should show the flyout', () => { const renderResult = render(); return renderResult.findByTestId('hostDetailsFlyout').then(flyout => { @@ -103,6 +119,17 @@ describe('when on the hosts page', () => { const renderResult = render(); const linkToLogs = await renderResult.findByTestId('hostDetailsLinkToLogs'); expect(linkToLogs).not.toBeNull(); + expect(linkToLogs.textContent).toEqual('Endpoint Logs'); + expect(linkToLogs.attributes.href.value).toEqual( + "/app/logs/stream?logFilter=(expression:'host.id:1',kind:kuery)" + ); + }); + it.skip('should navigate to logs without full page refresh', async () => { + const renderResult = render(); + const linkToLogs = await renderResult.findByTestId('hostDetailsLinkToLogs'); + expect(coreStart.application.navigateToApp.mock.calls).toHaveLength(0); + fireEvent.click(linkToLogs); + expect(coreStart.application.navigateToApp.mock.calls).toHaveLength(1); }); }); }); From 6f06fbb8a5a60cfe141c4b6cd8da9e5919df9533 Mon Sep 17 00:00:00 2001 From: Paul Tavares Date: Wed, 8 Apr 2020 15:05:33 -0400 Subject: [PATCH 6/7] one more test to ensure we navigate to app (not full page refresh) --- .../endpoint/view/hosts/index.test.tsx | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx index dcc525a622a427..eb99855ba90576 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx @@ -90,6 +90,7 @@ describe('when on the hosts page', () => { }, }; + coreStart.http.get.mockReturnValue(Promise.resolve(hostDetails)); coreStart.application.getUrlForApp.mockReturnValue('/app/logs'); reactTestingLibrary.act(() => { @@ -120,16 +121,21 @@ describe('when on the hosts page', () => { const linkToLogs = await renderResult.findByTestId('hostDetailsLinkToLogs'); expect(linkToLogs).not.toBeNull(); expect(linkToLogs.textContent).toEqual('Endpoint Logs'); - expect(linkToLogs.attributes.href.value).toEqual( + expect(linkToLogs.getAttribute('href')).toEqual( "/app/logs/stream?logFilter=(expression:'host.id:1',kind:kuery)" ); }); - it.skip('should navigate to logs without full page refresh', async () => { - const renderResult = render(); - const linkToLogs = await renderResult.findByTestId('hostDetailsLinkToLogs'); - expect(coreStart.application.navigateToApp.mock.calls).toHaveLength(0); - fireEvent.click(linkToLogs); - expect(coreStart.application.navigateToApp.mock.calls).toHaveLength(1); + describe('when link to logs is clicked', () => { + beforeEach(async () => { + const renderResult = render(); + const linkToLogs = await renderResult.findByTestId('hostDetailsLinkToLogs'); + reactTestingLibrary.act(() => fireEvent.click(linkToLogs)); + }); + + it('should navigate to logs without full page refresh', async () => { + // FIXME: this is not working :( + expect(coreStart.application.navigateToApp.mock.calls).toHaveLength(1); + }); }); }); }); From db546939cfc8802882015c3a91544978b21e3000 Mon Sep 17 00:00:00 2001 From: Paul Tavares Date: Wed, 8 Apr 2020 15:39:31 -0400 Subject: [PATCH 7/7] Fixes post master merge --- .../store/hosts/mock_host_result_list.ts | 9 +++-- .../endpoint/view/hosts/index.test.tsx | 34 ++++++++++++++----- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/mock_host_result_list.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/mock_host_result_list.ts index 056fea08378a5b..20aa973ffc93de 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/mock_host_result_list.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/mock_host_result_list.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { HostResultList, HostStatus } from '../../../../../common/types'; +import { HostInfo, HostResultList, HostStatus } from '../../../../../common/types'; import { EndpointDocGenerator } from '../../../../../common/generate_data'; export const mockHostResultList: (options?: { @@ -44,7 +44,10 @@ export const mockHostResultList: (options?: { /** * returns a mocked API response for retrieving a single host metadata */ -export const mockHostDetailsApiResult = (): ReturnType => { +export const mockHostDetailsApiResult = (): HostInfo => { const generator = new EndpointDocGenerator('seed'); - return generator.generateHostMetadata(); + return { + metadata: generator.generateHostMetadata(), + host_status: HostStatus.ERROR, + }; }; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx index eb99855ba90576..c3ff41268e3db1 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx @@ -14,7 +14,7 @@ import { mockHostResultList, } from '../../store/hosts/mock_host_result_list'; import { AppContextTestRender, createAppRootMockRenderer } from '../../mocks'; -import { HostMetadata } from '../../../../../common/types'; +import { HostInfo } from '../../../../../common/types'; describe('when on the hosts page', () => { let render: () => ReturnType; @@ -62,6 +62,16 @@ describe('when on the hosts page', () => { describe('when the user clicks the hostname in the table', () => { let renderResult: reactTestingLibrary.RenderResult; beforeEach(async () => { + const hostDetailsApiResponse = mockHostDetailsApiResult(); + + coreStart.http.get.mockReturnValue(Promise.resolve(hostDetailsApiResponse)); + reactTestingLibrary.act(() => { + store.dispatch({ + type: 'serverReturnedHostDetails', + payload: hostDetailsApiResponse, + }); + }); + renderResult = render(); const detailsLink = await renderResult.findByTestId('hostnameCellLink'); if (detailsLink) { @@ -79,14 +89,20 @@ describe('when on the hosts page', () => { }); describe('when there is a selected host in the url', () => { - let hostDetails: HostMetadata; + let hostDetails: HostInfo; beforeEach(() => { - const { host, ...details } = mockHostDetailsApiResult(); + const { + host_status, + metadata: { host, ...details }, + } = mockHostDetailsApiResult(); hostDetails = { - ...details, - host: { - ...host, - id: '1', + host_status, + metadata: { + ...details, + host: { + ...host, + id: '1', + }, }, }; @@ -129,7 +145,9 @@ describe('when on the hosts page', () => { beforeEach(async () => { const renderResult = render(); const linkToLogs = await renderResult.findByTestId('hostDetailsLinkToLogs'); - reactTestingLibrary.act(() => fireEvent.click(linkToLogs)); + reactTestingLibrary.act(() => { + fireEvent.click(linkToLogs); + }); }); it('should navigate to logs without full page refresh', async () => {