[Logs UI] Remove apollo deps from log link-to routes (#74502)
This replaces the use of the old graphql-based `useSource` hook with the new plain JSON `useLogSource` hook. It also fixes two more problems: - A rendering problem with the source configuration loading screen and a `setState` race condition in the `useLogSource` hook. - A non-backwards-compatible change of the `/link-to/:sourceId/logs` route in #61162.
This commit is contained in:
parent
1ac87168b1
commit
0f1128281e
|
@ -20,6 +20,9 @@ export const logSourceConfigurationOriginRT = rt.keyof({
|
|||
export type LogSourceConfigurationOrigin = rt.TypeOf<typeof logSourceConfigurationOriginRT>;
|
||||
|
||||
const logSourceFieldsConfigurationRT = rt.strict({
|
||||
container: rt.string,
|
||||
host: rt.string,
|
||||
pod: rt.string,
|
||||
timestamp: rt.string,
|
||||
tiebreaker: rt.string,
|
||||
});
|
||||
|
|
|
@ -30,7 +30,6 @@ export const findInventoryModel = (type: InventoryItemType) => {
|
|||
};
|
||||
|
||||
interface InventoryFields {
|
||||
message: string[];
|
||||
host: string;
|
||||
pod: string;
|
||||
container: string;
|
||||
|
|
|
@ -17,10 +17,14 @@ import { FlexPage } from './page';
|
|||
|
||||
interface LoadingPageProps {
|
||||
message?: ReactNode;
|
||||
'data-test-subj'?: string;
|
||||
}
|
||||
|
||||
export const LoadingPage = ({ message }: LoadingPageProps) => (
|
||||
<FlexPage>
|
||||
export const LoadingPage = ({
|
||||
message,
|
||||
'data-test-subj': dataTestSubj = 'loadingPage',
|
||||
}: LoadingPageProps) => (
|
||||
<FlexPage data-test-subj={dataTestSubj}>
|
||||
<EuiPageBody>
|
||||
<EuiPageContent verticalPosition="center" horizontalPosition="center">
|
||||
<EuiFlexGroup alignItems="center">
|
||||
|
|
|
@ -23,5 +23,6 @@ export const PageContent = euiStyled.div`
|
|||
`;
|
||||
|
||||
export const FlexPage = euiStyled(EuiPage)`
|
||||
align-self: stretch;
|
||||
flex: 1 0 0%;
|
||||
`;
|
||||
|
|
|
@ -11,6 +11,7 @@ import { LoadingPage } from './loading_page';
|
|||
|
||||
export const SourceLoadingPage: React.FunctionComponent = () => (
|
||||
<LoadingPage
|
||||
data-test-subj="sourceLoadingPage"
|
||||
message={
|
||||
<FormattedMessage
|
||||
id="xpack.infra.sourceLoadingPage.loadingDataSourcesMessage"
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { LogSourceConfiguration, LogSourceStatus, useLogSource } from './log_source';
|
||||
|
||||
type CreateUseLogSource = (sourceConfiguration?: { sourceId?: string }) => typeof useLogSource;
|
||||
|
||||
const defaultSourceId = 'default';
|
||||
|
||||
export const createUninitializedUseLogSourceMock: CreateUseLogSource = ({
|
||||
sourceId = defaultSourceId,
|
||||
} = {}) => () => ({
|
||||
derivedIndexPattern: {
|
||||
fields: [],
|
||||
title: 'unknown',
|
||||
},
|
||||
hasFailedLoadingSource: false,
|
||||
hasFailedLoadingSourceStatus: false,
|
||||
initialize: jest.fn(),
|
||||
isLoading: false,
|
||||
isLoadingSourceConfiguration: false,
|
||||
isLoadingSourceStatus: false,
|
||||
isUninitialized: true,
|
||||
loadSource: jest.fn(),
|
||||
loadSourceConfiguration: jest.fn(),
|
||||
loadSourceFailureMessage: undefined,
|
||||
loadSourceStatus: jest.fn(),
|
||||
sourceConfiguration: undefined,
|
||||
sourceId,
|
||||
sourceStatus: undefined,
|
||||
updateSourceConfiguration: jest.fn(),
|
||||
});
|
||||
|
||||
export const createLoadingUseLogSourceMock: CreateUseLogSource = ({
|
||||
sourceId = defaultSourceId,
|
||||
} = {}) => (args) => ({
|
||||
...createUninitializedUseLogSourceMock({ sourceId })(args),
|
||||
isLoading: true,
|
||||
isLoadingSourceConfiguration: true,
|
||||
isLoadingSourceStatus: true,
|
||||
});
|
||||
|
||||
export const createLoadedUseLogSourceMock: CreateUseLogSource = ({
|
||||
sourceId = defaultSourceId,
|
||||
} = {}) => (args) => ({
|
||||
...createUninitializedUseLogSourceMock({ sourceId })(args),
|
||||
sourceConfiguration: createBasicSourceConfiguration(sourceId),
|
||||
sourceStatus: {
|
||||
logIndexFields: [],
|
||||
logIndexStatus: 'available',
|
||||
},
|
||||
});
|
||||
|
||||
export const createBasicSourceConfiguration = (sourceId: string): LogSourceConfiguration => ({
|
||||
id: sourceId,
|
||||
origin: 'stored',
|
||||
configuration: {
|
||||
description: `description for ${sourceId}`,
|
||||
logAlias: 'LOG_INDICES',
|
||||
logColumns: [],
|
||||
fields: {
|
||||
container: 'CONTAINER_FIELD',
|
||||
host: 'HOST_FIELD',
|
||||
pod: 'POD_FIELD',
|
||||
tiebreaker: 'TIEBREAKER_FIELD',
|
||||
timestamp: 'TIMESTAMP_FIELD',
|
||||
},
|
||||
name: sourceId,
|
||||
},
|
||||
});
|
||||
|
||||
export const createAvailableSourceStatus = (logIndexFields = []): LogSourceStatus => ({
|
||||
logIndexFields,
|
||||
logIndexStatus: 'available',
|
||||
});
|
|
@ -5,13 +5,14 @@
|
|||
*/
|
||||
|
||||
import createContainer from 'constate';
|
||||
import { useState, useMemo, useCallback } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useMountedState } from 'react-use';
|
||||
import { HttpSetup } from 'src/core/public';
|
||||
import {
|
||||
LogSourceConfiguration,
|
||||
LogSourceStatus,
|
||||
LogSourceConfigurationPropertiesPatch,
|
||||
LogSourceConfigurationProperties,
|
||||
LogSourceConfigurationPropertiesPatch,
|
||||
LogSourceStatus,
|
||||
} from '../../../../common/http_api/log_sources';
|
||||
import { useTrackedPromise } from '../../../utils/use_tracked_promise';
|
||||
import { callFetchLogSourceConfigurationAPI } from './api/fetch_log_source_configuration';
|
||||
|
@ -32,6 +33,7 @@ export const useLogSource = ({
|
|||
sourceId: string;
|
||||
fetch: HttpSetup['fetch'];
|
||||
}) => {
|
||||
const getIsMounted = useMountedState();
|
||||
const [sourceConfiguration, setSourceConfiguration] = useState<
|
||||
LogSourceConfiguration | undefined
|
||||
>(undefined);
|
||||
|
@ -45,6 +47,10 @@ export const useLogSource = ({
|
|||
return await callFetchLogSourceConfigurationAPI(sourceId, fetch);
|
||||
},
|
||||
onResolve: ({ data }) => {
|
||||
if (!getIsMounted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setSourceConfiguration(data);
|
||||
},
|
||||
},
|
||||
|
@ -58,6 +64,10 @@ export const useLogSource = ({
|
|||
return await callPatchLogSourceConfigurationAPI(sourceId, patchedProperties, fetch);
|
||||
},
|
||||
onResolve: ({ data }) => {
|
||||
if (!getIsMounted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setSourceConfiguration(data);
|
||||
loadSourceStatus();
|
||||
},
|
||||
|
@ -72,6 +82,10 @@ export const useLogSource = ({
|
|||
return await callFetchLogSourceStatusAPI(sourceId, fetch);
|
||||
},
|
||||
onResolve: ({ data }) => {
|
||||
if (!getIsMounted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setSourceStatus(data);
|
||||
},
|
||||
},
|
||||
|
|
326
x-pack/plugins/infra/public/pages/link_to/link_to_logs.test.tsx
Normal file
326
x-pack/plugins/infra/public/pages/link_to/link_to_logs.test.tsx
Normal file
|
@ -0,0 +1,326 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { render } from '@testing-library/react';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import React from 'react';
|
||||
import { Route, Router, Switch } from 'react-router-dom';
|
||||
import { httpServiceMock } from 'src/core/public/mocks';
|
||||
// import { HttpSetup } from 'src/core/public';
|
||||
import { KibanaContextProvider } from 'src/plugins/kibana_react/public';
|
||||
import { useLogSource } from '../../containers/logs/log_source';
|
||||
import {
|
||||
createLoadedUseLogSourceMock,
|
||||
createLoadingUseLogSourceMock,
|
||||
} from '../../containers/logs/log_source/log_source.mock';
|
||||
import { LinkToLogsPage } from './link_to_logs';
|
||||
|
||||
jest.mock('../../containers/logs/log_source');
|
||||
const useLogSourceMock = useLogSource as jest.MockedFunction<typeof useLogSource>;
|
||||
|
||||
const renderRoutes = (routes: React.ReactElement) => {
|
||||
const history = createMemoryHistory();
|
||||
const services = {
|
||||
http: httpServiceMock.createStartContract(),
|
||||
};
|
||||
const renderResult = render(
|
||||
<KibanaContextProvider services={services}>
|
||||
<Router history={history}>{routes}</Router>
|
||||
</KibanaContextProvider>
|
||||
);
|
||||
|
||||
return {
|
||||
...renderResult,
|
||||
history,
|
||||
services,
|
||||
};
|
||||
};
|
||||
|
||||
describe('LinkToLogsPage component', () => {
|
||||
beforeEach(() => {
|
||||
useLogSourceMock.mockImplementation(createLoadedUseLogSourceMock());
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
useLogSourceMock.mockRestore();
|
||||
});
|
||||
|
||||
describe('default route', () => {
|
||||
it('redirects to the stream at a given time filtered for a user-defined criterion', () => {
|
||||
const { history } = renderRoutes(
|
||||
<Switch>
|
||||
<Route path="/link-to" component={LinkToLogsPage} />
|
||||
</Switch>
|
||||
);
|
||||
|
||||
history.push('/link-to?time=1550671089404&filter=FILTER_FIELD:FILTER_VALUE');
|
||||
|
||||
expect(history.location.pathname).toEqual('/stream');
|
||||
|
||||
const searchParams = new URLSearchParams(history.location.search);
|
||||
expect(searchParams.get('sourceId')).toEqual('default');
|
||||
expect(searchParams.get('logFilter')).toMatchInlineSnapshot(
|
||||
`"(expression:'FILTER_FIELD:FILTER_VALUE',kind:kuery)"`
|
||||
);
|
||||
expect(searchParams.get('logPosition')).toMatchInlineSnapshot(
|
||||
`"(end:'2019-02-20T14:58:09.404Z',position:(tiebreaker:0,time:1550671089404),start:'2019-02-20T12:58:09.404Z',streamLive:!f)"`
|
||||
);
|
||||
});
|
||||
|
||||
it('redirects to the stream using a specific source id', () => {
|
||||
const { history } = renderRoutes(
|
||||
<Switch>
|
||||
<Route path="/link-to" component={LinkToLogsPage} />
|
||||
</Switch>
|
||||
);
|
||||
|
||||
history.push('/link-to/OTHER_SOURCE');
|
||||
|
||||
expect(history.location.pathname).toEqual('/stream');
|
||||
|
||||
const searchParams = new URLSearchParams(history.location.search);
|
||||
expect(searchParams.get('sourceId')).toEqual('OTHER_SOURCE');
|
||||
expect(searchParams.get('logFilter')).toMatchInlineSnapshot(`"(expression:'',kind:kuery)"`);
|
||||
expect(searchParams.get('logPosition')).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('logs route', () => {
|
||||
it('redirects to the stream at a given time filtered for a user-defined criterion', () => {
|
||||
const { history } = renderRoutes(
|
||||
<Switch>
|
||||
<Route path="/link-to" component={LinkToLogsPage} />
|
||||
</Switch>
|
||||
);
|
||||
|
||||
history.push('/link-to/logs?time=1550671089404&filter=FILTER_FIELD:FILTER_VALUE');
|
||||
|
||||
expect(history.location.pathname).toEqual('/stream');
|
||||
|
||||
const searchParams = new URLSearchParams(history.location.search);
|
||||
expect(searchParams.get('sourceId')).toEqual('default');
|
||||
expect(searchParams.get('logFilter')).toMatchInlineSnapshot(
|
||||
`"(expression:'FILTER_FIELD:FILTER_VALUE',kind:kuery)"`
|
||||
);
|
||||
expect(searchParams.get('logPosition')).toMatchInlineSnapshot(
|
||||
`"(end:'2019-02-20T14:58:09.404Z',position:(tiebreaker:0,time:1550671089404),start:'2019-02-20T12:58:09.404Z',streamLive:!f)"`
|
||||
);
|
||||
});
|
||||
|
||||
it('redirects to the stream using a specific source id', () => {
|
||||
const { history } = renderRoutes(
|
||||
<Switch>
|
||||
<Route path="/link-to" component={LinkToLogsPage} />
|
||||
</Switch>
|
||||
);
|
||||
|
||||
history.push('/link-to/OTHER_SOURCE/logs');
|
||||
|
||||
expect(history.location.pathname).toEqual('/stream');
|
||||
|
||||
const searchParams = new URLSearchParams(history.location.search);
|
||||
expect(searchParams.get('sourceId')).toEqual('OTHER_SOURCE');
|
||||
expect(searchParams.get('logFilter')).toMatchInlineSnapshot(`"(expression:'',kind:kuery)"`);
|
||||
expect(searchParams.get('logPosition')).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('host-logs route', () => {
|
||||
it('redirects to the stream filtered for a host', () => {
|
||||
const { history } = renderRoutes(
|
||||
<Switch>
|
||||
<Route path="/link-to" component={LinkToLogsPage} />
|
||||
</Switch>
|
||||
);
|
||||
|
||||
history.push('/link-to/host-logs/HOST_NAME');
|
||||
|
||||
expect(history.location.pathname).toEqual('/stream');
|
||||
|
||||
const searchParams = new URLSearchParams(history.location.search);
|
||||
expect(searchParams.get('sourceId')).toEqual('default');
|
||||
expect(searchParams.get('logFilter')).toMatchInlineSnapshot(
|
||||
`"(expression:'HOST_FIELD: HOST_NAME',kind:kuery)"`
|
||||
);
|
||||
expect(searchParams.get('logPosition')).toEqual(null);
|
||||
});
|
||||
|
||||
it('redirects to the stream at a given time filtered for a host and a user-defined criterion', () => {
|
||||
const { history } = renderRoutes(
|
||||
<Switch>
|
||||
<Route path="/link-to" component={LinkToLogsPage} />
|
||||
</Switch>
|
||||
);
|
||||
|
||||
history.push(
|
||||
'/link-to/host-logs/HOST_NAME?time=1550671089404&filter=FILTER_FIELD:FILTER_VALUE'
|
||||
);
|
||||
|
||||
expect(history.location.pathname).toEqual('/stream');
|
||||
|
||||
const searchParams = new URLSearchParams(history.location.search);
|
||||
expect(searchParams.get('sourceId')).toEqual('default');
|
||||
expect(searchParams.get('logFilter')).toMatchInlineSnapshot(
|
||||
`"(expression:'(HOST_FIELD: HOST_NAME) and (FILTER_FIELD:FILTER_VALUE)',kind:kuery)"`
|
||||
);
|
||||
expect(searchParams.get('logPosition')).toMatchInlineSnapshot(
|
||||
`"(end:'2019-02-20T14:58:09.404Z',position:(tiebreaker:0,time:1550671089404),start:'2019-02-20T12:58:09.404Z',streamLive:!f)"`
|
||||
);
|
||||
});
|
||||
|
||||
it('redirects to the stream filtered for a host using a specific source id', () => {
|
||||
const { history } = renderRoutes(
|
||||
<Switch>
|
||||
<Route path="/link-to" component={LinkToLogsPage} />
|
||||
</Switch>
|
||||
);
|
||||
|
||||
history.push('/link-to/OTHER_SOURCE/host-logs/HOST_NAME');
|
||||
|
||||
expect(history.location.pathname).toEqual('/stream');
|
||||
|
||||
const searchParams = new URLSearchParams(history.location.search);
|
||||
expect(searchParams.get('sourceId')).toEqual('OTHER_SOURCE');
|
||||
expect(searchParams.get('logFilter')).toMatchInlineSnapshot(
|
||||
`"(expression:'HOST_FIELD: HOST_NAME',kind:kuery)"`
|
||||
);
|
||||
expect(searchParams.get('logPosition')).toEqual(null);
|
||||
});
|
||||
|
||||
it('renders a loading page while loading the source configuration', () => {
|
||||
useLogSourceMock.mockImplementation(createLoadingUseLogSourceMock());
|
||||
|
||||
const { history, queryByTestId } = renderRoutes(
|
||||
<Switch>
|
||||
<Route path="/link-to" component={LinkToLogsPage} />
|
||||
</Switch>
|
||||
);
|
||||
|
||||
history.push('/link-to/host-logs/HOST_NAME');
|
||||
|
||||
expect(queryByTestId('nodeLoadingPage-host')).not.toBeEmpty();
|
||||
});
|
||||
});
|
||||
|
||||
describe('container-logs route', () => {
|
||||
it('redirects to the stream filtered for a container', () => {
|
||||
const { history } = renderRoutes(
|
||||
<Switch>
|
||||
<Route path="/link-to" component={LinkToLogsPage} />
|
||||
</Switch>
|
||||
);
|
||||
|
||||
history.push('/link-to/container-logs/CONTAINER_ID');
|
||||
|
||||
expect(history.location.pathname).toEqual('/stream');
|
||||
|
||||
const searchParams = new URLSearchParams(history.location.search);
|
||||
expect(searchParams.get('sourceId')).toEqual('default');
|
||||
expect(searchParams.get('logFilter')).toMatchInlineSnapshot(
|
||||
`"(expression:'CONTAINER_FIELD: CONTAINER_ID',kind:kuery)"`
|
||||
);
|
||||
expect(searchParams.get('logPosition')).toEqual(null);
|
||||
});
|
||||
|
||||
it('redirects to the stream at a given time filtered for a container and a user-defined criterion', () => {
|
||||
const { history } = renderRoutes(
|
||||
<Switch>
|
||||
<Route path="/link-to" component={LinkToLogsPage} />
|
||||
</Switch>
|
||||
);
|
||||
|
||||
history.push(
|
||||
'/link-to/container-logs/CONTAINER_ID?time=1550671089404&filter=FILTER_FIELD:FILTER_VALUE'
|
||||
);
|
||||
|
||||
expect(history.location.pathname).toEqual('/stream');
|
||||
|
||||
const searchParams = new URLSearchParams(history.location.search);
|
||||
expect(searchParams.get('sourceId')).toEqual('default');
|
||||
expect(searchParams.get('logFilter')).toMatchInlineSnapshot(
|
||||
`"(expression:'(CONTAINER_FIELD: CONTAINER_ID) and (FILTER_FIELD:FILTER_VALUE)',kind:kuery)"`
|
||||
);
|
||||
expect(searchParams.get('logPosition')).toMatchInlineSnapshot(
|
||||
`"(end:'2019-02-20T14:58:09.404Z',position:(tiebreaker:0,time:1550671089404),start:'2019-02-20T12:58:09.404Z',streamLive:!f)"`
|
||||
);
|
||||
});
|
||||
|
||||
it('renders a loading page while loading the source configuration', () => {
|
||||
useLogSourceMock.mockImplementation(createLoadingUseLogSourceMock());
|
||||
|
||||
const { history, queryByTestId } = renderRoutes(
|
||||
<Switch>
|
||||
<Route path="/link-to" component={LinkToLogsPage} />
|
||||
</Switch>
|
||||
);
|
||||
|
||||
history.push('/link-to/container-logs/CONTAINER_ID');
|
||||
|
||||
expect(queryByTestId('nodeLoadingPage-container')).not.toBeEmpty();
|
||||
});
|
||||
});
|
||||
|
||||
describe('pod-logs route', () => {
|
||||
it('redirects to the stream filtered for a pod', () => {
|
||||
const { history } = renderRoutes(
|
||||
<Switch>
|
||||
<Route path="/link-to" component={LinkToLogsPage} />
|
||||
</Switch>
|
||||
);
|
||||
|
||||
history.push('/link-to/pod-logs/POD_UID');
|
||||
|
||||
expect(history.location.pathname).toEqual('/stream');
|
||||
|
||||
const searchParams = new URLSearchParams(history.location.search);
|
||||
expect(searchParams.get('sourceId')).toEqual('default');
|
||||
expect(searchParams.get('logFilter')).toMatchInlineSnapshot(
|
||||
`"(expression:'POD_FIELD: POD_UID',kind:kuery)"`
|
||||
);
|
||||
expect(searchParams.get('logPosition')).toEqual(null);
|
||||
});
|
||||
|
||||
it('redirects to the stream at a given time filtered for a pod and a user-defined criterion', () => {
|
||||
const { history } = renderRoutes(
|
||||
<Switch>
|
||||
<Route path="/link-to" component={LinkToLogsPage} />
|
||||
</Switch>
|
||||
);
|
||||
|
||||
history.push('/link-to/pod-logs/POD_UID?time=1550671089404&filter=FILTER_FIELD:FILTER_VALUE');
|
||||
|
||||
expect(history.location.pathname).toEqual('/stream');
|
||||
|
||||
const searchParams = new URLSearchParams(history.location.search);
|
||||
expect(searchParams.get('sourceId')).toEqual('default');
|
||||
expect(searchParams.get('logFilter')).toMatchInlineSnapshot(
|
||||
`"(expression:'(POD_FIELD: POD_UID) and (FILTER_FIELD:FILTER_VALUE)',kind:kuery)"`
|
||||
);
|
||||
expect(searchParams.get('logPosition')).toMatchInlineSnapshot(
|
||||
`"(end:'2019-02-20T14:58:09.404Z',position:(tiebreaker:0,time:1550671089404),start:'2019-02-20T12:58:09.404Z',streamLive:!f)"`
|
||||
);
|
||||
});
|
||||
|
||||
it('renders a loading page while loading the source configuration', () => {
|
||||
useLogSourceMock.mockImplementation(createLoadingUseLogSourceMock());
|
||||
|
||||
const { history, queryByTestId } = renderRoutes(
|
||||
<Switch>
|
||||
<Route path="/link-to" component={LinkToLogsPage} />
|
||||
</Switch>
|
||||
);
|
||||
|
||||
history.push('/link-to/pod-logs/POD_UID');
|
||||
|
||||
expect(queryByTestId('nodeLoadingPage-pod')).not.toBeEmpty();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -27,6 +27,7 @@ export const LinkToLogsPage: React.FC<LinkToPageProps> = (props) => {
|
|||
path={`${props.match.url}/:sourceId?/:nodeType(${ITEM_TYPES})-logs/:nodeId`}
|
||||
component={RedirectToNodeLogs}
|
||||
/>
|
||||
<Route path={`${props.match.url}/:sourceId?/logs`} component={RedirectToLogs} />
|
||||
<Route path={`${props.match.url}/:sourceId?`} component={RedirectToLogs} />
|
||||
<Redirect to="/" />
|
||||
</Switch>
|
||||
|
|
|
@ -1,119 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { createLocation } from 'history';
|
||||
import React from 'react';
|
||||
import { matchPath } from 'react-router-dom';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { RedirectToNodeLogs } from './redirect_to_node_logs';
|
||||
|
||||
jest.mock('../../containers/source/source', () => ({
|
||||
useSource: ({ sourceId }: { sourceId: string }) => ({
|
||||
sourceId,
|
||||
source: {
|
||||
configuration: {
|
||||
fields: {
|
||||
container: 'CONTAINER_FIELD',
|
||||
host: 'HOST_FIELD',
|
||||
pod: 'POD_FIELD',
|
||||
},
|
||||
},
|
||||
},
|
||||
isLoading: sourceId === 'perpetuallyLoading',
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('RedirectToNodeLogs component', () => {
|
||||
it('renders a redirect with the correct host filter', () => {
|
||||
const component = shallow(
|
||||
<RedirectToNodeLogs {...createRouteComponentProps('/host-logs/HOST_NAME')} />
|
||||
);
|
||||
|
||||
expect(component).toMatchInlineSnapshot(`
|
||||
<Redirect
|
||||
to="/stream?sourceId=default&logFilter=(expression:'HOST_FIELD:%20HOST_NAME',kind:kuery)"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
|
||||
it('renders a redirect with the correct container filter', () => {
|
||||
const component = shallow(
|
||||
<RedirectToNodeLogs {...createRouteComponentProps('/container-logs/CONTAINER_ID')} />
|
||||
);
|
||||
|
||||
expect(component).toMatchInlineSnapshot(`
|
||||
<Redirect
|
||||
to="/stream?sourceId=default&logFilter=(expression:'CONTAINER_FIELD:%20CONTAINER_ID',kind:kuery)"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
|
||||
it('renders a redirect with the correct pod filter', () => {
|
||||
const component = shallow(
|
||||
<RedirectToNodeLogs {...createRouteComponentProps('/pod-logs/POD_ID')} />
|
||||
);
|
||||
|
||||
expect(component).toMatchInlineSnapshot(`
|
||||
<Redirect
|
||||
to="/stream?sourceId=default&logFilter=(expression:'POD_FIELD:%20POD_ID',kind:kuery)"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
|
||||
it('renders a redirect with the correct position', () => {
|
||||
const component = shallow(
|
||||
<RedirectToNodeLogs
|
||||
{...createRouteComponentProps('/host-logs/HOST_NAME?time=1550671089404')}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component).toMatchInlineSnapshot(`
|
||||
<Redirect
|
||||
to="/stream?logPosition=(end:'2019-02-20T14:58:09.404Z',position:(tiebreaker:0,time:1550671089404),start:'2019-02-20T12:58:09.404Z',streamLive:!f)&sourceId=default&logFilter=(expression:'HOST_FIELD:%20HOST_NAME',kind:kuery)"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
|
||||
it('renders a redirect with the correct user-defined filter', () => {
|
||||
const component = shallow(
|
||||
<RedirectToNodeLogs
|
||||
{...createRouteComponentProps(
|
||||
'/host-logs/HOST_NAME?time=1550671089404&filter=FILTER_FIELD:FILTER_VALUE'
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component).toMatchInlineSnapshot(`
|
||||
<Redirect
|
||||
to="/stream?logPosition=(end:'2019-02-20T14:58:09.404Z',position:(tiebreaker:0,time:1550671089404),start:'2019-02-20T12:58:09.404Z',streamLive:!f)&sourceId=default&logFilter=(expression:'(HOST_FIELD:%20HOST_NAME)%20and%20(FILTER_FIELD:FILTER_VALUE)',kind:kuery)"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
|
||||
it('renders a redirect with the correct custom source id', () => {
|
||||
const component = shallow(
|
||||
<RedirectToNodeLogs
|
||||
{...createRouteComponentProps('/SOME-OTHER-SOURCE/host-logs/HOST_NAME')}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component).toMatchInlineSnapshot(`
|
||||
<Redirect
|
||||
to="/stream?sourceId=SOME-OTHER-SOURCE&logFilter=(expression:'HOST_FIELD:%20HOST_NAME',kind:kuery)"
|
||||
/>
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
const createRouteComponentProps = (path: string) => {
|
||||
const location = createLocation(path);
|
||||
return {
|
||||
match: matchPath(location.pathname, { path: '/:sourceId?/:nodeType-logs/:nodeId' }) as any,
|
||||
history: null as any,
|
||||
location,
|
||||
};
|
||||
};
|
|
@ -5,21 +5,20 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { flowRight } from 'lodash';
|
||||
import flowRight from 'lodash/flowRight';
|
||||
import React from 'react';
|
||||
import { Redirect, RouteComponentProps } from 'react-router-dom';
|
||||
|
||||
import { useMount } from 'react-use';
|
||||
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
|
||||
import { findInventoryFields } from '../../../common/inventory_models';
|
||||
import { InventoryItemType } from '../../../common/inventory_models/types';
|
||||
import { LoadingPage } from '../../components/loading_page';
|
||||
import { replaceLogFilterInQueryString } from '../../containers/logs/log_filter';
|
||||
import { replaceLogPositionInQueryString } from '../../containers/logs/log_position';
|
||||
import { useLogSource } from '../../containers/logs/log_source';
|
||||
import { replaceSourceIdInQueryString } from '../../containers/source_id';
|
||||
import { SourceConfigurationFields } from '../../graphql/types';
|
||||
import { getFilterFromLocation, getTimeFromLocation } from './query_params';
|
||||
import { useSource } from '../../containers/source/source';
|
||||
import { findInventoryFields } from '../../../common/inventory_models';
|
||||
import { InventoryItemType } from '../../../common/inventory_models/types';
|
||||
import { LinkDescriptor } from '../../hooks/use_link_props';
|
||||
import { getFilterFromLocation, getTimeFromLocation } from './query_params';
|
||||
|
||||
type RedirectToNodeLogsType = RouteComponentProps<{
|
||||
nodeId: string;
|
||||
|
@ -27,26 +26,27 @@ type RedirectToNodeLogsType = RouteComponentProps<{
|
|||
sourceId?: string;
|
||||
}>;
|
||||
|
||||
const getFieldByNodeType = (
|
||||
nodeType: InventoryItemType,
|
||||
fields: SourceConfigurationFields.Fields
|
||||
) => {
|
||||
const inventoryFields = findInventoryFields(nodeType, fields);
|
||||
return inventoryFields.id;
|
||||
};
|
||||
|
||||
export const RedirectToNodeLogs = ({
|
||||
match: {
|
||||
params: { nodeId, nodeType, sourceId = 'default' },
|
||||
},
|
||||
location,
|
||||
}: RedirectToNodeLogsType) => {
|
||||
const { source, isLoading } = useSource({ sourceId });
|
||||
const configuration = source && source.configuration;
|
||||
const { services } = useKibana();
|
||||
const { isLoading, loadSourceConfiguration, sourceConfiguration } = useLogSource({
|
||||
fetch: services.http.fetch,
|
||||
sourceId,
|
||||
});
|
||||
const fields = sourceConfiguration?.configuration.fields;
|
||||
|
||||
useMount(() => {
|
||||
loadSourceConfiguration();
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<LoadingPage
|
||||
data-test-subj={`nodeLoadingPage-${nodeType}`}
|
||||
message={i18n.translate('xpack.infra.redirectToNodeLogs.loadingNodeLogsMessage', {
|
||||
defaultMessage: 'Loading {nodeType} logs',
|
||||
values: {
|
||||
|
@ -55,13 +55,11 @@ export const RedirectToNodeLogs = ({
|
|||
})}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!configuration) {
|
||||
} else if (fields == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const nodeFilter = `${getFieldByNodeType(nodeType, configuration.fields)}: ${nodeId}`;
|
||||
const nodeFilter = `${findInventoryFields(nodeType, fields).id}: ${nodeId}`;
|
||||
const userFilter = getFilterFromLocation(location);
|
||||
const filter = userFilter ? `(${nodeFilter}) and (${userFilter})` : nodeFilter;
|
||||
|
||||
|
|
Loading…
Reference in a new issue