[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>;
|
export type LogSourceConfigurationOrigin = rt.TypeOf<typeof logSourceConfigurationOriginRT>;
|
||||||
|
|
||||||
const logSourceFieldsConfigurationRT = rt.strict({
|
const logSourceFieldsConfigurationRT = rt.strict({
|
||||||
|
container: rt.string,
|
||||||
|
host: rt.string,
|
||||||
|
pod: rt.string,
|
||||||
timestamp: rt.string,
|
timestamp: rt.string,
|
||||||
tiebreaker: rt.string,
|
tiebreaker: rt.string,
|
||||||
});
|
});
|
||||||
|
|
|
@ -30,7 +30,6 @@ export const findInventoryModel = (type: InventoryItemType) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
interface InventoryFields {
|
interface InventoryFields {
|
||||||
message: string[];
|
|
||||||
host: string;
|
host: string;
|
||||||
pod: string;
|
pod: string;
|
||||||
container: string;
|
container: string;
|
||||||
|
|
|
@ -17,10 +17,14 @@ import { FlexPage } from './page';
|
||||||
|
|
||||||
interface LoadingPageProps {
|
interface LoadingPageProps {
|
||||||
message?: ReactNode;
|
message?: ReactNode;
|
||||||
|
'data-test-subj'?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LoadingPage = ({ message }: LoadingPageProps) => (
|
export const LoadingPage = ({
|
||||||
<FlexPage>
|
message,
|
||||||
|
'data-test-subj': dataTestSubj = 'loadingPage',
|
||||||
|
}: LoadingPageProps) => (
|
||||||
|
<FlexPage data-test-subj={dataTestSubj}>
|
||||||
<EuiPageBody>
|
<EuiPageBody>
|
||||||
<EuiPageContent verticalPosition="center" horizontalPosition="center">
|
<EuiPageContent verticalPosition="center" horizontalPosition="center">
|
||||||
<EuiFlexGroup alignItems="center">
|
<EuiFlexGroup alignItems="center">
|
||||||
|
|
|
@ -23,5 +23,6 @@ export const PageContent = euiStyled.div`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const FlexPage = euiStyled(EuiPage)`
|
export const FlexPage = euiStyled(EuiPage)`
|
||||||
|
align-self: stretch;
|
||||||
flex: 1 0 0%;
|
flex: 1 0 0%;
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { LoadingPage } from './loading_page';
|
||||||
|
|
||||||
export const SourceLoadingPage: React.FunctionComponent = () => (
|
export const SourceLoadingPage: React.FunctionComponent = () => (
|
||||||
<LoadingPage
|
<LoadingPage
|
||||||
|
data-test-subj="sourceLoadingPage"
|
||||||
message={
|
message={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="xpack.infra.sourceLoadingPage.loadingDataSourcesMessage"
|
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 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 { HttpSetup } from 'src/core/public';
|
||||||
import {
|
import {
|
||||||
LogSourceConfiguration,
|
LogSourceConfiguration,
|
||||||
LogSourceStatus,
|
|
||||||
LogSourceConfigurationPropertiesPatch,
|
|
||||||
LogSourceConfigurationProperties,
|
LogSourceConfigurationProperties,
|
||||||
|
LogSourceConfigurationPropertiesPatch,
|
||||||
|
LogSourceStatus,
|
||||||
} from '../../../../common/http_api/log_sources';
|
} from '../../../../common/http_api/log_sources';
|
||||||
import { useTrackedPromise } from '../../../utils/use_tracked_promise';
|
import { useTrackedPromise } from '../../../utils/use_tracked_promise';
|
||||||
import { callFetchLogSourceConfigurationAPI } from './api/fetch_log_source_configuration';
|
import { callFetchLogSourceConfigurationAPI } from './api/fetch_log_source_configuration';
|
||||||
|
@ -32,6 +33,7 @@ export const useLogSource = ({
|
||||||
sourceId: string;
|
sourceId: string;
|
||||||
fetch: HttpSetup['fetch'];
|
fetch: HttpSetup['fetch'];
|
||||||
}) => {
|
}) => {
|
||||||
|
const getIsMounted = useMountedState();
|
||||||
const [sourceConfiguration, setSourceConfiguration] = useState<
|
const [sourceConfiguration, setSourceConfiguration] = useState<
|
||||||
LogSourceConfiguration | undefined
|
LogSourceConfiguration | undefined
|
||||||
>(undefined);
|
>(undefined);
|
||||||
|
@ -45,6 +47,10 @@ export const useLogSource = ({
|
||||||
return await callFetchLogSourceConfigurationAPI(sourceId, fetch);
|
return await callFetchLogSourceConfigurationAPI(sourceId, fetch);
|
||||||
},
|
},
|
||||||
onResolve: ({ data }) => {
|
onResolve: ({ data }) => {
|
||||||
|
if (!getIsMounted()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setSourceConfiguration(data);
|
setSourceConfiguration(data);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -58,6 +64,10 @@ export const useLogSource = ({
|
||||||
return await callPatchLogSourceConfigurationAPI(sourceId, patchedProperties, fetch);
|
return await callPatchLogSourceConfigurationAPI(sourceId, patchedProperties, fetch);
|
||||||
},
|
},
|
||||||
onResolve: ({ data }) => {
|
onResolve: ({ data }) => {
|
||||||
|
if (!getIsMounted()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setSourceConfiguration(data);
|
setSourceConfiguration(data);
|
||||||
loadSourceStatus();
|
loadSourceStatus();
|
||||||
},
|
},
|
||||||
|
@ -72,6 +82,10 @@ export const useLogSource = ({
|
||||||
return await callFetchLogSourceStatusAPI(sourceId, fetch);
|
return await callFetchLogSourceStatusAPI(sourceId, fetch);
|
||||||
},
|
},
|
||||||
onResolve: ({ data }) => {
|
onResolve: ({ data }) => {
|
||||||
|
if (!getIsMounted()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setSourceStatus(data);
|
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`}
|
path={`${props.match.url}/:sourceId?/:nodeType(${ITEM_TYPES})-logs/:nodeId`}
|
||||||
component={RedirectToNodeLogs}
|
component={RedirectToNodeLogs}
|
||||||
/>
|
/>
|
||||||
|
<Route path={`${props.match.url}/:sourceId?/logs`} component={RedirectToLogs} />
|
||||||
<Route path={`${props.match.url}/:sourceId?`} component={RedirectToLogs} />
|
<Route path={`${props.match.url}/:sourceId?`} component={RedirectToLogs} />
|
||||||
<Redirect to="/" />
|
<Redirect to="/" />
|
||||||
</Switch>
|
</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 { i18n } from '@kbn/i18n';
|
||||||
|
import flowRight from 'lodash/flowRight';
|
||||||
import { flowRight } from 'lodash';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Redirect, RouteComponentProps } from 'react-router-dom';
|
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 { LoadingPage } from '../../components/loading_page';
|
||||||
import { replaceLogFilterInQueryString } from '../../containers/logs/log_filter';
|
import { replaceLogFilterInQueryString } from '../../containers/logs/log_filter';
|
||||||
import { replaceLogPositionInQueryString } from '../../containers/logs/log_position';
|
import { replaceLogPositionInQueryString } from '../../containers/logs/log_position';
|
||||||
|
import { useLogSource } from '../../containers/logs/log_source';
|
||||||
import { replaceSourceIdInQueryString } from '../../containers/source_id';
|
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 { LinkDescriptor } from '../../hooks/use_link_props';
|
||||||
|
import { getFilterFromLocation, getTimeFromLocation } from './query_params';
|
||||||
|
|
||||||
type RedirectToNodeLogsType = RouteComponentProps<{
|
type RedirectToNodeLogsType = RouteComponentProps<{
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
|
@ -27,26 +26,27 @@ type RedirectToNodeLogsType = RouteComponentProps<{
|
||||||
sourceId?: string;
|
sourceId?: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
const getFieldByNodeType = (
|
|
||||||
nodeType: InventoryItemType,
|
|
||||||
fields: SourceConfigurationFields.Fields
|
|
||||||
) => {
|
|
||||||
const inventoryFields = findInventoryFields(nodeType, fields);
|
|
||||||
return inventoryFields.id;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const RedirectToNodeLogs = ({
|
export const RedirectToNodeLogs = ({
|
||||||
match: {
|
match: {
|
||||||
params: { nodeId, nodeType, sourceId = 'default' },
|
params: { nodeId, nodeType, sourceId = 'default' },
|
||||||
},
|
},
|
||||||
location,
|
location,
|
||||||
}: RedirectToNodeLogsType) => {
|
}: RedirectToNodeLogsType) => {
|
||||||
const { source, isLoading } = useSource({ sourceId });
|
const { services } = useKibana();
|
||||||
const configuration = source && source.configuration;
|
const { isLoading, loadSourceConfiguration, sourceConfiguration } = useLogSource({
|
||||||
|
fetch: services.http.fetch,
|
||||||
|
sourceId,
|
||||||
|
});
|
||||||
|
const fields = sourceConfiguration?.configuration.fields;
|
||||||
|
|
||||||
|
useMount(() => {
|
||||||
|
loadSourceConfiguration();
|
||||||
|
});
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<LoadingPage
|
<LoadingPage
|
||||||
|
data-test-subj={`nodeLoadingPage-${nodeType}`}
|
||||||
message={i18n.translate('xpack.infra.redirectToNodeLogs.loadingNodeLogsMessage', {
|
message={i18n.translate('xpack.infra.redirectToNodeLogs.loadingNodeLogsMessage', {
|
||||||
defaultMessage: 'Loading {nodeType} logs',
|
defaultMessage: 'Loading {nodeType} logs',
|
||||||
values: {
|
values: {
|
||||||
|
@ -55,13 +55,11 @@ export const RedirectToNodeLogs = ({
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
} else if (fields == null) {
|
||||||
|
|
||||||
if (!configuration) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodeFilter = `${getFieldByNodeType(nodeType, configuration.fields)}: ${nodeId}`;
|
const nodeFilter = `${findInventoryFields(nodeType, fields).id}: ${nodeId}`;
|
||||||
const userFilter = getFilterFromLocation(location);
|
const userFilter = getFilterFromLocation(location);
|
||||||
const filter = userFilter ? `(${nodeFilter}) and (${userFilter})` : nodeFilter;
|
const filter = userFilter ? `(${nodeFilter}) and (${userFilter})` : nodeFilter;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue