[Uptime] Uptime to Infra integration (#35847) (#36720)

Adds integration links for Infrastructure UI.
This commit is contained in:
Andrew Cholakian 2019-05-20 17:27:42 -04:00 committed by GitHub
parent adb433530e
commit b43386173c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 329 additions and 16 deletions

View file

@ -1,9 +1,34 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`IntegrationLink component renders a disabled link when href is undefined 1`] = `
<EuiFlexGroup>
<EuiFlexItem
grow={false}
>
<EuiToolTip
content="Required data for this integration was not found."
delay="regular"
position="top"
>
<EuiIcon
type="apmApp"
/>
</EuiToolTip>
</EuiFlexItem>
<EuiFlexItem>
<EuiText
color="subdued"
>
click for bar
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
`;
exports[`IntegrationLink component renders without errors 1`] = `
<EuiLink
aria-label="foo"
color="subdued"
color="primary"
href="/app/foo?kuery=localhost"
type="button"
>

View file

@ -21,4 +21,16 @@ describe('IntegrationLink component', () => {
);
expect(component).toMatchSnapshot();
});
it('renders a disabled link when href is undefined', () => {
const component = shallowWithIntl(
<IntegrationLink
ariaLabel="foo"
href={undefined}
iconType="apmApp"
message="click for bar"
tooltipContent="info for bar"
/>
);
expect(component).toMatchSnapshot();
});
});

View file

@ -4,12 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLink, EuiToolTip } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLink, EuiText, EuiToolTip } from '@elastic/eui';
import React from 'react';
import { i18n } from '@kbn/i18n';
interface IntegrationLinkProps {
ariaLabel: string;
href: string;
href: string | undefined;
iconType: 'apmApp' | 'infraApp' | 'loggingApp';
message: string;
tooltipContent: string;
@ -21,15 +22,31 @@ export const IntegrationLink = ({
iconType,
message,
tooltipContent,
}: IntegrationLinkProps) => (
<EuiLink aria-label={ariaLabel} color="subdued" href={href}>
}: IntegrationLinkProps) =>
typeof href === 'undefined' ? (
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiToolTip content={tooltipContent} position="top">
<EuiToolTip
content={i18n.translate('xpack.uptime.integrationLink.missingDataMessage', {
defaultMessage: 'Required data for this integration was not found.',
})}
>
<EuiIcon type={iconType} />
</EuiToolTip>
</EuiFlexItem>
<EuiFlexItem>{message}</EuiFlexItem>
<EuiFlexItem>
<EuiText color="subdued">{message}</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiLink>
);
) : (
<EuiLink aria-label={ariaLabel} href={href}>
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiToolTip content={tooltipContent} position="top">
<EuiIcon type={iconType} />
</EuiToolTip>
</EuiFlexItem>
<EuiFlexItem>{message}</EuiFlexItem>
</EuiFlexGroup>
</EuiLink>
);

View file

@ -10,7 +10,12 @@ import { i18n } from '@kbn/i18n';
import { get } from 'lodash';
import { LatestMonitor } from '../../../common/graphql/types';
import { IntegrationLink } from './integration_link';
import { getApmHref } from '../../lib/helper';
import {
getApmHref,
getInfraContainerHref,
getInfraIpHref,
getInfraKubernetesHref,
} from '../../lib/helper';
interface MonitorListActionsPopoverProps {
basePath: string;
@ -28,7 +33,10 @@ export const MonitorListActionsPopover = ({
}: MonitorListActionsPopoverProps) => {
const popoverId = `${monitor.id.key}_popover`;
const [popoverIsVisible, setPopoverIsVisible] = useState<boolean>(false);
const domain = get(ping, 'url.domain', '');
const domain = get<string>(ping, 'url.domain', '');
const podUid = get<string | undefined>(ping, 'kubernetes.pod.uid', undefined);
const containerId = get<string | undefined>(ping, 'container.id', undefined);
const ip = get<string | undefined>(ping, 'monitor.ip');
return (
<EuiPopover
button={
@ -51,7 +59,7 @@ export const MonitorListActionsPopover = ({
id={popoverId}
isOpen={popoverIsVisible}
>
<EuiFlexGroup>
<EuiFlexGroup direction="column">
<EuiFlexItem>
<IntegrationLink
ariaLabel={i18n.translate('xpack.uptime.apmIntegrationAction.description', {
@ -79,6 +87,89 @@ export const MonitorListActionsPopover = ({
)}
/>
</EuiFlexItem>
<EuiFlexItem>
<IntegrationLink
ariaLabel={i18n.translate(
'xpack.uptime.monitorList.infraIntegrationAction.ip.ariaLabel',
{
defaultMessage: `Check Infrastructure UI for this montor's ip address`,
description: 'This value is shown as the aria label value for screen readers.',
}
)}
href={getInfraIpHref(monitor, basePath)}
iconType="infraApp"
message={i18n.translate('xpack.uptime.monitorList.infraIntegrationAction.ip.message', {
defaultMessage: 'Show host metrics',
description: `A message explaining that this link will take the user to the Infrastructure UI, filtered for this monitor's IP Address`,
})}
tooltipContent={i18n.translate(
'xpack.uptime.monitorList.infraIntegrationAction.ip.tooltip',
{
defaultMessage: 'Check Infrastructure UI for the IP "{ip}"',
values: {
ip,
},
}
)}
/>
</EuiFlexItem>
<EuiFlexItem>
<IntegrationLink
ariaLabel={i18n.translate(
'xpack.uptime.monitorList.infraIntegrationAction.kubernetes.description',
{
defaultMessage: `Check Infrastructure UI for this monitor's pod UID`,
description: 'This value is shown as the aria label value for screen readers.',
}
)}
href={getInfraKubernetesHref(monitor, basePath)}
iconType="infraApp"
message={i18n.translate(
'xpack.uptime.monitorList.infraIntegrationAction.kubernetes.message',
{
defaultMessage: 'Show pod metrics',
description:
'A message explaining that this link will take the user to the Infrastructure UI filtered for the monitor Pod UID.',
}
)}
tooltipContent={i18n.translate(
'xpack.uptime.monitorList.infraIntegrationAction.kubernetes.tooltip',
{
defaultMessage: 'Check Infrastructure UI for pod UID "{podUid}".',
values: {
podUid,
},
}
)}
/>
</EuiFlexItem>
<EuiFlexItem>
<IntegrationLink
ariaLabel={i18n.translate(
'xpack.uptime.monitorList.infraIntegrationAction.docker.description',
{
defaultMessage: `Check Infrastructure UI for this monitor's container ID`,
}
)}
href={getInfraContainerHref(monitor, basePath)}
iconType="infraApp"
message={i18n.translate(
'xpack.uptime.monitorList.infraIntegrationAction.container.message',
{
defaultMessage: 'Show container metrics',
}
)}
tooltipContent={i18n.translate(
'xpack.uptime.monitorList.infraIntegrationAction.docker.tooltip',
{
defaultMessage: 'Check Infrastructure UI for container ID "{containerId}"',
values: {
containerId,
},
}
)}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPopover>
);

View file

@ -5,5 +5,5 @@
*/
export { convertMicrosecondsToMilliseconds } from './convert_measurements';
export { getApmHref } from './observability_integration';
export * from './observability_integration';
export { UptimeUrlParams, getSupportedUrlParams } from './url_params';

View file

@ -0,0 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`getInfraHref getInfraContainerHref creates a link for valid parameters 1`] = `"/foo/app/infra#/link-to/container-detail/test-container-id"`;
exports[`getInfraHref getInfraContainerHref does not specify a base path when none is available 1`] = `"/app/infra#/link-to/container-detail/test-container-id"`;
exports[`getInfraHref getInfraIpHref creates a link for valid parameters 1`] = `"/bar/app/infra#/infrastructure/inventory?waffleFilter=(expression:'host.ip%20%3A%20151.101.202.217',kind:kuery)"`;
exports[`getInfraHref getInfraIpHref does not specify a base path when none is available 1`] = `"/app/infra#/infrastructure/inventory?waffleFilter=(expression:'host.ip%20%3A%20151.101.202.217',kind:kuery)"`;
exports[`getInfraHref getInfraKubernetesHref creates a link for valid parameters 1`] = `"/foo/app/infra#/link-to/pod-detail/test-pod-uid"`;
exports[`getInfraHref getInfraKubernetesHref does not specify a base path when none is available 1`] = `"/app/infra#/link-to/pod-detail/test-pod-uid"`;

View file

@ -0,0 +1,88 @@
/*
* 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 { getInfraContainerHref, getInfraKubernetesHref, getInfraIpHref } from '../get_infra_href';
import { LatestMonitor } from '../../../../../common/graphql/types';
describe('getInfraHref', () => {
let monitor: LatestMonitor;
beforeEach(() => {
monitor = {
id: {
key: 'foo',
url: 'http://bar.com/',
},
ping: {
timestamp: '1557405354000',
container: {
id: 'test-container-id',
},
kubernetes: {
pod: {
uid: 'test-pod-uid',
},
},
monitor: {
ip: '151.101.202.217',
},
},
};
});
it('getInfraContainerHref creates a link for valid parameters', () => {
expect.assertions(2);
const result = getInfraContainerHref(monitor, 'foo');
expect(result).not.toBeUndefined();
expect(result).toMatchSnapshot();
});
it('getInfraContainerHref does not specify a base path when none is available', () => {
expect.assertions(1);
expect(getInfraContainerHref(monitor, '')).toMatchSnapshot();
});
it('getInfraContainerHref returns undefined when no container id is present', () => {
expect.assertions(1);
delete monitor.ping;
expect(getInfraContainerHref(monitor, 'foo')).toBeUndefined();
});
it('getInfraKubernetesHref creates a link for valid parameters', () => {
expect.assertions(2);
const result = getInfraKubernetesHref(monitor, 'foo');
expect(result).not.toBeUndefined();
expect(result).toMatchSnapshot();
});
it('getInfraKubernetesHref does not specify a base path when none is available', () => {
expect.assertions(1);
expect(getInfraKubernetesHref(monitor, '')).toMatchSnapshot();
});
it('getInfraKubernetesHref returns undefined when no pod data is present', () => {
expect.assertions(1);
delete monitor.ping;
expect(getInfraKubernetesHref(monitor, 'foo')).toBeUndefined();
});
it('getInfraIpHref creates a link for valid parameters', () => {
expect.assertions(2);
const result = getInfraIpHref(monitor, 'bar');
expect(result).not.toBeUndefined();
expect(result).toMatchSnapshot();
});
it('getInfraIpHref does not specify a base path when none is available', () => {
expect.assertions(1);
expect(getInfraIpHref(monitor, '')).toMatchSnapshot();
});
it('getInfraIpHref returns undefined when ip is present', () => {
expect.assertions(1);
delete monitor.ping;
expect(getInfraIpHref(monitor, 'foo')).toBeUndefined();
});
});

View file

@ -0,0 +1,8 @@
/*
* 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 const addBasePath = (basePath: string, url: string) =>
`${basePath.length > 0 ? `/${basePath}` : ''}${url}`;

View file

@ -5,6 +5,7 @@
*/
import { get } from 'lodash';
import { addBasePath } from './add_base_path';
import { LatestMonitor } from '../../../../common/graphql/types';
export const getApmHref = (
@ -13,6 +14,9 @@ export const getApmHref = (
dateRangeStart: string,
dateRangeEnd: string
) =>
`${basePath && basePath.length ? `/${basePath}` : ''}/app/apm#/services?kuery=${encodeURI(
`url.domain: "${get(monitor, 'ping.url.domain')}"`
)}&rangeFrom=${dateRangeStart}&rangeTo=${dateRangeEnd}`;
addBasePath(
basePath,
`/app/apm#/services?kuery=${encodeURI(
`url.domain: "${get(monitor, 'ping.url.domain')}"`
)}&rangeFrom=${dateRangeStart}&rangeTo=${dateRangeEnd}`
);

View file

@ -0,0 +1,54 @@
/*
* 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 { get } from 'lodash';
import { LatestMonitor } from '../../../../common/graphql/types';
import { addBasePath } from './add_base_path';
/**
* Builds URLs to the designated features by extracting values from the provided
* monitor object on a given path. Then returns the result of a provided function
* to place the value in its rightful place on the URI string.
* @param monitor the data object
* @param path the location on the object of the desired data
* @param getHref a function that returns the full URL
*/
const buildHref = (
monitor: LatestMonitor,
path: string,
getHref: (value: string) => string
): string | undefined => {
const queryValue = get<string | undefined>(monitor, path);
if (queryValue === undefined) {
return undefined;
}
return getHref(queryValue);
};
export const getInfraContainerHref = (
monitor: LatestMonitor,
basePath: string
): string | undefined =>
buildHref(monitor, 'ping.container.id', containerId =>
addBasePath(basePath, `/app/infra#/link-to/container-detail/${encodeURIComponent(containerId)}`)
);
export const getInfraKubernetesHref = (
monitor: LatestMonitor,
basePath: string
): string | undefined =>
buildHref(monitor, 'ping.kubernetes.pod.uid', uid =>
addBasePath(basePath, `/app/infra#/link-to/pod-detail/${encodeURIComponent(uid)}`)
);
export const getInfraIpHref = (monitor: LatestMonitor, basePath: string) =>
buildHref(monitor, 'ping.monitor.ip', ip => {
const expression = encodeURIComponent(`host.ip : ${ip}`);
return addBasePath(
basePath,
`/app/infra#/infrastructure/inventory?waffleFilter=(expression:'${expression}',kind:kuery)`
);
});

View file

@ -5,3 +5,4 @@
*/
export { getApmHref } from './get_apm_href';
export { getInfraContainerHref, getInfraIpHref, getInfraKubernetesHref } from './get_infra_href';