Skip to content
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

[Infra UI] Provide routes for accessing pre-filtered log views #23246

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions x-pack/plugins/infra/common/graphql/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,14 @@ export namespace SourceQuery {
__typename?: 'InfraSourceConfiguration';
metricAlias: string;
logAlias: string;
fields: Fields;
};

export type Fields = {
__typename?: 'InfraSourceFields';
container: string;
hostname: string;
pod: string;
};

export type Status = {
Expand Down
35 changes: 35 additions & 0 deletions x-pack/plugins/infra/public/components/loading_page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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 {
EuiFlexGroup,
EuiFlexItem,
EuiLoadingSpinner,
EuiPageBody,
EuiPageContent,
} from '@elastic/eui';
import React from 'react';

import { FlexPage } from './page';

interface LoadingPageProps {
message?: string;
}

export const LoadingPage = ({ message }: LoadingPageProps) => (
<FlexPage>
<EuiPageBody>
<EuiPageContent verticalPosition="center" horizontalPosition="center">
<EuiFlexGroup alignItems="center">
<EuiFlexItem grow={false}>
<EuiLoadingSpinner size="xl" />
</EuiFlexItem>
<EuiFlexItem>{message}</EuiFlexItem>
</EuiFlexGroup>
</EuiPageContent>
</EuiPageBody>
</FlexPage>
);
5 changes: 5 additions & 0 deletions x-pack/plugins/infra/public/components/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { EuiPage } from '@elastic/eui';
import styled from 'styled-components';

export const ColumnarPage = styled.div`
Expand All @@ -19,3 +20,7 @@ export const PageContent = styled.div`
flex-direction: row;
background-color: ${props => props.theme.eui.euiColorEmptyShade};
`;

export const FlexPage = styled(EuiPage)`
flex: 1 0 0;
`;
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import { EuiContextMenu, EuiContextMenuPanelDescriptor, EuiPopover } from '@elastic/eui';
import React from 'react';
import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../lib/lib';
import { getContainerLogsUrl, getHostLogsUrl, getPodLogsUrl } from '../../pages/link_to';

interface Props {
options: InfraWaffleMapOptions;
Expand All @@ -24,15 +25,21 @@ export const NodeContextMenu: React.SFC<Props> = ({
// TODO: This needs to be change to be dynamic based on the options passed in.
const nodeType = 'host';

const nodeLogsUrl = getNodeLogsUrl(nodeType, node);

const panels: EuiContextMenuPanelDescriptor[] = [
{
id: 0,
title: '',
items: [
{
name: `View logs for this ${nodeType}`,
href: `#/logs?filter=${node.name}`,
},
...(nodeLogsUrl
? [
{
name: `View logs for this ${nodeType}`,
href: nodeLogsUrl,
},
]
: []),
{
name: `View APM Traces for this ${nodeType}`,
href: `/app/apm`,
Expand All @@ -52,3 +59,25 @@ export const NodeContextMenu: React.SFC<Props> = ({
</EuiPopover>
);
};

const getNodeLogsUrl = (
nodeType: 'host' | 'container' | 'pod',
{ path }: InfraWaffleMapNode
): string | undefined => {
if (path.length <= 0) {
return undefined;
}

const lastPathSegment = path[path.length - 1];

switch (nodeType) {
case 'host':
return getHostLogsUrl({ hostname: lastPathSegment.value });
case 'container':
return getContainerLogsUrl({ containerId: lastPathSegment.value });
case 'host':
return getPodLogsUrl({ podId: lastPathSegment.value });
default:
return undefined;
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { connect } from 'react-redux';
import { logFilterActions, logFilterSelectors, State } from '../../store';
import { asChildFunctionRenderer } from '../../utils/typed_react';
import { bindPlainActionCreators } from '../../utils/typed_redux';
import { UrlStateContainer } from '../../utils/url_state';
import { replaceStateKeyInQueryString, UrlStateContainer } from '../../utils/url_state';

const withLogFilter = connect(
(state: State) => ({
Expand Down Expand Up @@ -71,3 +71,9 @@ const mapToFilterQuery = (value: any): LogFilterUrlState | undefined =>
expression: value.expression,
}
: undefined;

export const replaceLogFilterInQueryString = (expression: string) =>
replaceStateKeyInQueryString<LogFilterUrlState>('logFilter', {
kind: 'kuery',
expression,
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { pickTimeKey } from '../../../common/time';
import { logPositionActions, logPositionSelectors, State } from '../../store';
import { asChildFunctionRenderer } from '../../utils/typed_react';
import { bindPlainActionCreators } from '../../utils/typed_redux';
import { UrlStateContainer } from '../../utils/url_state';
import { replaceStateKeyInQueryString, UrlStateContainer } from '../../utils/url_state';

export const withLogPosition = connect(
(state: State) => ({
Expand Down Expand Up @@ -111,3 +111,13 @@ const mapToPositionUrlState = (value: any) =>
: undefined;

const mapToStreamLiveUrlState = (value: any) => (typeof value === 'boolean' ? value : undefined);

export const replaceLogPositionInQueryString = (time: number) =>
Number.isNaN(time)
? (value: string) => value
: replaceStateKeyInQueryString<LogPositionUrlState>('logPosition', {
position: {
time,
tiebreaker: 0,
},
});
16 changes: 16 additions & 0 deletions x-pack/plugins/infra/public/containers/with_source.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* 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 { connect } from 'react-redux';

import { sourceSelectors, State } from '../store';
import { asChildFunctionRenderer } from '../utils/typed_react';

export const withSource = connect((state: State) => ({
configuredFields: sourceSelectors.selectSourceFields(state),
}));

export const WithSource = asChildFunctionRenderer(withSource);
10 changes: 10 additions & 0 deletions x-pack/plugins/infra/public/pages/link_to/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* 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.
*/

export { LinkToPage } from './link_to';
export { getContainerLogsUrl, RedirectToContainerLogs } from './redirect_to_container_logs';
export { getHostLogsUrl, RedirectToHostLogs } from './redirect_to_host_logs';
export { getPodLogsUrl, RedirectToPodLogs } from './redirect_to_pod_logs';
34 changes: 34 additions & 0 deletions x-pack/plugins/infra/public/pages/link_to/link_to.tsx
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;
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';
import { match as RouteMatch, Redirect, Route, Switch } from 'react-router-dom';

import { RedirectToContainerLogs } from './redirect_to_container_logs';
import { RedirectToHostLogs } from './redirect_to_host_logs';
import { RedirectToPodLogs } from './redirect_to_pod_logs';

interface LinkToPageProps {
match: RouteMatch<{}>;
}

export class LinkToPage extends React.Component<LinkToPageProps> {
public render() {
const { match } = this.props;

return (
<Switch>
<Route
path={`${match.url}/container-logs/:containerId`}
component={RedirectToContainerLogs}
/>
<Route path={`${match.url}/host-logs/:hostname`} component={RedirectToHostLogs} />
<Route path={`${match.url}/pod-logs/:podId`} component={RedirectToPodLogs} />
<Redirect to="/home" />
</Switch>
);
}
}
14 changes: 14 additions & 0 deletions x-pack/plugins/infra/public/pages/link_to/query_params.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* 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 { Location } from 'history';

import { getParamFromQueryString, getQueryStringFromLocation } from '../../utils/url_state';

export const getTimeFromLocation = (location: Location) => {
const timeParam = getParamFromQueryString(getQueryStringFromLocation(location), 'time');
return timeParam ? parseFloat(timeParam) : NaN;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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 compose from 'lodash/fp/compose';
import React from 'react';
import { Redirect, RouteComponentProps } from 'react-router-dom';

import { LoadingPage } from '../../components/loading_page';
import { replaceLogFilterInQueryString } from '../../containers/logs/with_log_filter';
import { replaceLogPositionInQueryString } from '../../containers/logs/with_log_position';
import { WithSource } from '../../containers/with_source';
import { getTimeFromLocation } from './query_params';

export const RedirectToContainerLogs = ({
match,
location,
}: RouteComponentProps<{ containerId: string }>) => (
<WithSource>
{({ configuredFields }) => {
if (!configuredFields) {
return <LoadingPage message="Loading container logs" />;
}

const searchString = compose(
replaceLogFilterInQueryString(`${configuredFields.container}: ${match.params.containerId}`),
replaceLogPositionInQueryString(getTimeFromLocation(location))
)('');

return <Redirect to={`/logs?${searchString}`} />;
}}
</WithSource>
);

export const getContainerLogsUrl = ({
containerId,
time,
}: {
containerId: string;
time?: number;
}) => ['#/link-to/container-logs/', containerId, ...(time ? [`?time=${time}`] : [])].join('');
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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 compose from 'lodash/fp/compose';
import React from 'react';
import { Redirect, RouteComponentProps } from 'react-router-dom';

import { LoadingPage } from '../../components/loading_page';
import { replaceLogFilterInQueryString } from '../../containers/logs/with_log_filter';
import { replaceLogPositionInQueryString } from '../../containers/logs/with_log_position';
import { WithSource } from '../../containers/with_source';
import { getTimeFromLocation } from './query_params';

export const RedirectToHostLogs = ({
match,
location,
}: RouteComponentProps<{ hostname: string }>) => (
<WithSource>
{({ configuredFields }) => {
if (!configuredFields) {
return <LoadingPage message="Loading host logs" />;
}

const searchString = compose(
replaceLogFilterInQueryString(`${configuredFields.hostname}: ${match.params.hostname}`),
replaceLogPositionInQueryString(getTimeFromLocation(location))
)('');

return <Redirect to={`/logs?${searchString}`} />;
}}
</WithSource>
);

export const getHostLogsUrl = ({ hostname, time }: { hostname: string; time?: number }) =>
['#/link-to/host-logs/', hostname, ...(time ? [`?time=${time}`] : [])].join('');
35 changes: 35 additions & 0 deletions x-pack/plugins/infra/public/pages/link_to/redirect_to_pod_logs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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 compose from 'lodash/fp/compose';
import React from 'react';
import { Redirect, RouteComponentProps } from 'react-router-dom';

import { LoadingPage } from '../../components/loading_page';
import { replaceLogFilterInQueryString } from '../../containers/logs/with_log_filter';
import { replaceLogPositionInQueryString } from '../../containers/logs/with_log_position';
import { WithSource } from '../../containers/with_source';
import { getTimeFromLocation } from './query_params';

export const RedirectToPodLogs = ({ match, location }: RouteComponentProps<{ podId: string }>) => (
<WithSource>
{({ configuredFields }) => {
if (!configuredFields) {
return <LoadingPage message="Loading pod logs" />;
}

const searchString = compose(
replaceLogFilterInQueryString(`${configuredFields.pod}: ${match.params.podId}`),
replaceLogPositionInQueryString(getTimeFromLocation(location))
)('');

return <Redirect to={`/logs?${searchString}`} />;
}}
</WithSource>
);

export const getPodLogsUrl = ({ podId, time }: { podId: string; time?: number }) =>
['#/link-to/pod-logs/', podId, ...(time ? [`?time=${time}`] : [])].join('');
2 changes: 2 additions & 0 deletions x-pack/plugins/infra/public/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Redirect, Route, Router, Switch } from 'react-router-dom';

import { NotFoundPage } from './pages/404';
import { HomePage } from './pages/home';
import { LinkToPage } from './pages/link_to';
import { LogsPage } from './pages/logs';

interface RouterProps {
Expand All @@ -23,6 +24,7 @@ export const PageRouter: React.SFC<RouterProps> = ({ history }) => {
<Redirect from="/" exact={true} to="/home" />
<Route path="/logs" component={LogsPage} />
<Route path="/home" component={HomePage} />
<Route path="/link-to" component={LinkToPage} />
<Route component={NotFoundPage} />
</Switch>
</Router>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ export const sourceQuery = gql`
configuration {
metricAlias
logAlias
fields {
container
hostname
pod
}
}
status {
indexFields {
Expand Down
Loading