Skip to content

Commit

Permalink
[Infra UI] Provide routes for accessing pre-filtered log views (#23246)
Browse files Browse the repository at this point in the history
This PR introduces a set of routes that can be used as stable entry points into the infra ui with partly pre-populated stated (e.g. filters and time):

* `app/infra/#/link-to/container-logs/:containerId[?time=${TIMESTAMP}]`
* `app/infra/#/link-to/host-logs/:hostname[?time=${TIMESTAMP}]`
* `app/infra/#/link-to/pod-logs/:podId[?time=${TIMESTAMP}]`

It also fixes the links from the waffle map to the logging ui to result in an appropriately filtered view.
  • Loading branch information
weltenwort authored Sep 19, 2018
1 parent 1608d19 commit cc9c30b
Show file tree
Hide file tree
Showing 17 changed files with 333 additions and 26 deletions.
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 @@ -113,3 +113,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

0 comments on commit cc9c30b

Please sign in to comment.