mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Endpoint] Add link to Logs UI to the Host Details view (#62852)
* Add LinktoApp to host details for logs * initial setup for testing link on details * Export interface AppContextTestRender for reference in tests * Refactor hosts tests to use AppContextTestRender * Render full details and validate link to logs * one more test to ensure we navigate to app (not full page refresh) * Fixes post master merge
This commit is contained in:
parent
13fe738b2a
commit
5e1c0be501
5 changed files with 119 additions and 32 deletions
|
@ -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<HTMLButtonElement | HTMLAnchorElement>;
|
||||
|
|
|
@ -18,7 +18,7 @@ type UiRender = (ui: React.ReactElement, options?: RenderOptions) => RenderResul
|
|||
/**
|
||||
* Mocked app root context renderer
|
||||
*/
|
||||
interface AppContextTestRender {
|
||||
export interface AppContextTestRender {
|
||||
store: ReturnType<typeof appStoreFactory>;
|
||||
history: ReturnType<typeof createMemoryHistory>;
|
||||
coreStart: ReturnType<typeof coreMock.createStart>;
|
||||
|
|
|
@ -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?: {
|
||||
|
@ -40,3 +40,14 @@ export const mockHostResultList: (options?: {
|
|||
};
|
||||
return mock;
|
||||
};
|
||||
|
||||
/**
|
||||
* returns a mocked API response for retrieving a single host metadata
|
||||
*/
|
||||
export const mockHostDetailsApiResult = (): HostInfo => {
|
||||
const generator = new EndpointDocGenerator('seed');
|
||||
return {
|
||||
metadata: generator.generateHostMetadata(),
|
||||
host_status: HostStatus.ERROR,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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,20 @@ const HostDetails = memo(({ details }: { details: HostMetadata }) => {
|
|||
listItems={detailsResultsLower}
|
||||
data-test-subj="hostDetailsLowerList"
|
||||
/>
|
||||
<EuiHorizontalRule margin="s" />
|
||||
<p>
|
||||
<LinkToApp
|
||||
appId={appId}
|
||||
appPath={appPath}
|
||||
href={url}
|
||||
data-test-subj="hostDetailsLinkToLogs"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.endpoint.host.details.linkToLogsTitle"
|
||||
defaultMessage="Endpoint Logs"
|
||||
/>
|
||||
</LinkToApp>
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
@ -170,3 +186,15 @@ export const HostDetailsFlyout = () => {
|
|||
</EuiFlyout>
|
||||
);
|
||||
};
|
||||
|
||||
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]);
|
||||
};
|
||||
|
|
|
@ -6,40 +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 { mockHostResultList } from '../../store/hosts/mock_host_result_list';
|
||||
import {
|
||||
mockHostDetailsApiResult,
|
||||
mockHostResultList,
|
||||
} from '../../store/hosts/mock_host_result_list';
|
||||
import { AppContextTestRender, createAppRootMockRenderer } from '../../mocks';
|
||||
import { HostInfo } from '../../../../../common/types';
|
||||
|
||||
describe('when on the hosts page', () => {
|
||||
let render: () => reactTestingLibrary.RenderResult;
|
||||
let history: MemoryHistory<never>;
|
||||
let store: ReturnType<typeof appStoreFactory>;
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let history: AppContextTestRender['history'];
|
||||
let store: AppContextTestRender['store'];
|
||||
let coreStart: AppContextTestRender['coreStart'];
|
||||
|
||||
beforeEach(async () => {
|
||||
history = createMemoryHistory<never>();
|
||||
store = appStoreFactory();
|
||||
render = () => {
|
||||
return reactTestingLibrary.render(
|
||||
<Provider store={store}>
|
||||
<I18nProvider>
|
||||
<EuiThemeProvider>
|
||||
<Router history={history}>
|
||||
<RouteCapture>
|
||||
<HostList />
|
||||
</RouteCapture>
|
||||
</Router>
|
||||
</EuiThemeProvider>
|
||||
</I18nProvider>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
const mockedContext = createAppRootMockRenderer();
|
||||
({ history, store, coreStart } = mockedContext);
|
||||
render = () => mockedContext.render(<HostList />);
|
||||
});
|
||||
|
||||
it('should show a table', async () => {
|
||||
|
@ -56,7 +42,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 = {
|
||||
|
@ -76,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) {
|
||||
|
@ -93,19 +89,71 @@ describe('when on the hosts page', () => {
|
|||
});
|
||||
|
||||
describe('when there is a selected host in the url', () => {
|
||||
let hostDetails: HostInfo;
|
||||
beforeEach(() => {
|
||||
const {
|
||||
host_status,
|
||||
metadata: { host, ...details },
|
||||
} = mockHostDetailsApiResult();
|
||||
hostDetails = {
|
||||
host_status,
|
||||
metadata: {
|
||||
...details,
|
||||
host: {
|
||||
...host,
|
||||
id: '1',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
coreStart.http.get.mockReturnValue(Promise.resolve(hostDetails));
|
||||
coreStart.application.getUrlForApp.mockReturnValue('/app/logs');
|
||||
|
||||
reactTestingLibrary.act(() => {
|
||||
history.push({
|
||||
...history.location,
|
||||
search: '?selected_host=1',
|
||||
});
|
||||
});
|
||||
reactTestingLibrary.act(() => {
|
||||
store.dispatch({
|
||||
type: 'serverReturnedHostDetails',
|
||||
payload: hostDetails,
|
||||
});
|
||||
});
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should show the flyout', () => {
|
||||
const renderResult = render();
|
||||
return renderResult.findByTestId('hostDetailsFlyout').then(flyout => {
|
||||
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();
|
||||
expect(linkToLogs.textContent).toEqual('Endpoint Logs');
|
||||
expect(linkToLogs.getAttribute('href')).toEqual(
|
||||
"/app/logs/stream?logFilter=(expression:'host.id:1',kind:kuery)"
|
||||
);
|
||||
});
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue