Fix metrics to uptime redirection with locator (#125098)

* move locator registration to plugin setup

* make locator naming consistent

* use locator in inventory view

* update locator to handle supported host types

* try another import

* remove locator constant

* Revert "remove locator constant"

This reverts commit 84416b00ca.

* Revert "try another import"

This reverts commit b42ac97b40.

* add share plugin type

* reduce constant import scope

* fix tests

* use uptime locator in waffle context menu

* remove obsolete create_uptime_link files

* use host.ip instead of monitor.ip

* align locator to infra implementation

* navigate_to_uptime helper

* use navigate_to_uptime helper

* fix waffle link color

* lint

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Kevin Lacabane 2022-03-30 16:48:45 +02:00 committed by GitHub
parent 47c861bbad
commit 6ea7541ada
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 81 additions and 170 deletions

View file

@ -3,6 +3,7 @@
"version": "8.0.0",
"kibanaVersion": "kibana",
"requiredPlugins": [
"share",
"features",
"usageCollection",
"spaces",

View file

@ -25,7 +25,8 @@ import { OVERLAY_Y_START, OVERLAY_BOTTOM_MARGIN } from './tabs/shared';
import { useLinkProps } from '../../../../../../../observability/public';
import { getNodeDetailUrl } from '../../../../link_to';
import { findInventoryModel } from '../../../../../../common/inventory_models';
import { createUptimeLink } from '../../lib/create_uptime_link';
import { navigateToUptime } from '../../lib/navigate_to_uptime';
import { InfraClientCoreStart, InfraClientStartDeps } from '../../../../../types';
interface Props {
isOpen: boolean;
@ -49,7 +50,8 @@ export const NodeContextPopover = ({
const tabConfigs = [MetricsTab, LogsTab, ProcessesTab, PropertiesTab, AnomaliesTab, OsqueryTab];
const inventoryModel = findInventoryModel(nodeType);
const nodeDetailFrom = currentTime - inventoryModel.metrics.defaultTimeRangeInSeconds * 1000;
const uiCapabilities = useKibana().services.application?.capabilities;
const { application, share } = useKibana<InfraClientCoreStart & InfraClientStartDeps>().services;
const uiCapabilities = application?.capabilities;
const canCreateAlerts = useMemo(
() => Boolean(uiCapabilities?.infrastructure?.save),
[uiCapabilities]
@ -91,7 +93,6 @@ export const NodeContextPopover = ({
kuery: `${apmField}:"${node.id}"`,
},
});
const uptimeMenuItemLinkProps = useLinkProps(createUptimeLink(options, nodeType, node));
if (!isOpen) {
return null;
@ -164,7 +165,7 @@ export const NodeContextPopover = ({
defaultMessage="APM"
/>
</EuiTab>
<EuiTab {...uptimeMenuItemLinkProps}>
<EuiTab onClick={() => navigateToUptime(share.url.locators, nodeType, node)}>
<EuiIcon type="popout" />{' '}
<FormattedMessage
id="xpack.infra.infra.nodeDetails.updtimeTabLabel"

View file

@ -13,7 +13,6 @@ import React, { useMemo, useState } from 'react';
import { AlertFlyout } from '../../../../../alerting/inventory/components/alert_flyout';
import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../../../../lib/lib';
import { getNodeDetailUrl, getNodeLogsUrl } from '../../../../link_to';
import { createUptimeLink } from '../../lib/create_uptime_link';
import { findInventoryModel, findInventoryFields } from '../../../../../../common/inventory_models';
import { useKibana } from '../../../../../../../../../src/plugins/kibana_react/public';
import { InventoryItemType } from '../../../../../../common/inventory_models/types';
@ -28,6 +27,8 @@ import {
ActionMenuDivider,
} from '../../../../../../../observability/public';
import { useLinkProps } from '../../../../../../../observability/public';
import { navigateToUptime } from '../../lib/navigate_to_uptime';
import { InfraClientCoreStart, InfraClientStartDeps } from '../../../../../types';
interface Props {
options: InfraWaffleMapOptions;
@ -41,7 +42,9 @@ export const NodeContextMenu: React.FC<Props & { theme?: EuiTheme }> = withTheme
const [flyoutVisible, setFlyoutVisible] = useState(false);
const inventoryModel = findInventoryModel(nodeType);
const nodeDetailFrom = currentTime - inventoryModel.metrics.defaultTimeRangeInSeconds * 1000;
const uiCapabilities = useKibana().services.application?.capabilities;
const { application, share } = useKibana<InfraClientCoreStart & InfraClientStartDeps>()
.services;
const uiCapabilities = application?.capabilities;
// Due to the changing nature of the fields between APM and this UI,
// We need to have some exceptions until 7.0 & ECS is finalized. Reference
// #26620 for the details for these fields.
@ -95,7 +98,6 @@ export const NodeContextMenu: React.FC<Props & { theme?: EuiTheme }> = withTheme
kuery: `${apmField}:"${node.id}"`,
},
});
const uptimeMenuItemLinkProps = useLinkProps(createUptimeLink(options, nodeType, node));
const nodeLogsMenuItem: SectionLinkProps = {
label: i18n.translate('xpack.infra.nodeContextMenu.viewLogsName', {
@ -131,7 +133,7 @@ export const NodeContextMenu: React.FC<Props & { theme?: EuiTheme }> = withTheme
defaultMessage: '{inventoryName} in Uptime',
values: { inventoryName: inventoryModel.singularDisplayName },
}),
...uptimeMenuItemLinkProps,
onClick: () => navigateToUptime(share.url.locators, nodeType, node),
isDisabled: !showUptimeLink,
};
@ -171,7 +173,7 @@ export const NodeContextMenu: React.FC<Props & { theme?: EuiTheme }> = withTheme
<SectionLink data-test-subj="viewLogsContextMenuItem" {...nodeLogsMenuItem} />
<SectionLink {...nodeDetailMenuItem} />
<SectionLink data-test-subj="viewApmTracesContextMenuItem" {...apmTracesMenuItem} />
<SectionLink {...uptimeMenuItem} />
<SectionLink {...uptimeMenuItem} color={'primary'} />
</SectionLinks>
<ActionMenuDivider />
<SectionLinks>

View file

@ -1,113 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { createUptimeLink } from './create_uptime_link';
import { InfraWaffleMapOptions, InfraFormatterType } from '../../../../lib/lib';
import { SnapshotMetricType } from '../../../../../common/inventory_models/types';
const options: InfraWaffleMapOptions = {
formatter: InfraFormatterType.percent,
formatTemplate: '{{value}}',
metric: { type: 'cpu' },
groupBy: [],
sort: { by: 'name', direction: 'asc' },
legend: {
type: 'gradient',
rules: [],
},
};
describe('createUptimeLink()', () => {
it('should work for hosts with ip', () => {
const node = {
pathId: 'host-01',
id: 'host-01',
name: 'host-01',
ip: '10.0.1.2',
path: [],
metrics: [
{
name: 'cpu' as SnapshotMetricType,
value: 0.5,
max: 0.8,
avg: 0.6,
},
],
};
expect(createUptimeLink(options, 'host', node)).toStrictEqual({
app: 'uptime',
hash: '/',
search: { search: 'host.ip:"10.0.1.2"' },
});
});
it('should work for hosts without ip', () => {
const node = {
pathId: 'host-01',
id: 'host-01',
name: 'host-01',
path: [],
metrics: [
{
name: 'cpu' as SnapshotMetricType,
value: 0.5,
max: 0.8,
avg: 0.6,
},
],
};
expect(createUptimeLink(options, 'host', node)).toStrictEqual({
app: 'uptime',
hash: '/',
search: { search: 'host.name:"host-01"' },
});
});
it('should work for pods', () => {
const node = {
pathId: 'pod-01',
id: '29193-pod-02939',
name: 'pod-01',
path: [],
metrics: [
{
name: 'cpu' as SnapshotMetricType,
value: 0.5,
max: 0.8,
avg: 0.6,
},
],
};
expect(createUptimeLink(options, 'pod', node)).toStrictEqual({
app: 'uptime',
hash: '/',
search: { search: 'kubernetes.pod.uid:"29193-pod-02939"' },
});
});
it('should work for container', () => {
const node = {
pathId: 'docker-01',
id: 'docker-1234',
name: 'docker-01',
path: [],
metrics: [
{
name: 'cpu' as SnapshotMetricType,
value: 0.5,
max: 0.8,
avg: 0.6,
},
],
};
expect(createUptimeLink(options, 'container', node)).toStrictEqual({
app: 'uptime',
hash: '/',
search: { search: 'container.id:"docker-1234"' },
});
});
});

View file

@ -1,35 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../../../lib/lib';
import { InventoryItemType } from '../../../../../common/inventory_models/types';
import { getFieldByType } from '../../../../../common/inventory_models';
import { LinkDescriptor } from '../../../../../../observability/public';
export const createUptimeLink = (
options: InfraWaffleMapOptions,
nodeType: InventoryItemType,
node: InfraWaffleMapNode
): LinkDescriptor => {
if (nodeType === 'host' && node.ip) {
return {
app: 'uptime',
hash: '/',
search: {
search: `host.ip:"${node.ip}"`,
},
};
}
const field = getFieldByType(nodeType);
return {
app: 'uptime',
hash: '/',
search: {
search: `${field ? field + ':' : ''}"${node.id}"`,
},
};
};

View file

@ -0,0 +1,19 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { InfraWaffleMapNode } from '../../../../lib/lib';
import { InventoryItemType } from '../../../../../common/inventory_models/types';
import { uptimeOverviewLocatorID } from '../../../../../../observability/public';
import { LocatorClient } from '../../../../../../../../src/plugins/share/common/url_service/locators';
export const navigateToUptime = (
locators: LocatorClient,
nodeType: InventoryItemType,
node: InfraWaffleMapNode
) => {
return locators.get(uptimeOverviewLocatorID)!.navigate({ [nodeType]: node.id, ip: node.ip });
};

View file

@ -10,6 +10,7 @@ import { IHttpFetchError } from 'src/core/public';
import type { DataPublicPluginStart } from '../../../../src/plugins/data/public';
import type { EmbeddableSetup, EmbeddableStart } from '../../../../src/plugins/embeddable/public';
import type { HomePublicPluginSetup } from '../../../../src/plugins/home/public';
import type { SharePluginSetup, SharePluginStart } from '../../../../src/plugins/share/public';
import type {
UsageCollectionSetup,
UsageCollectionStart,
@ -54,6 +55,7 @@ export interface InfraClientSetupDeps {
usageCollection: UsageCollectionSetup;
ml: MlPluginSetup;
embeddable: EmbeddableSetup;
share: SharePluginSetup;
}
export interface InfraClientStartDeps {
@ -66,6 +68,7 @@ export interface InfraClientStartDeps {
ml: MlPluginStart;
embeddable?: EmbeddableStart;
osquery?: unknown; // OsqueryPluginStart;
share: SharePluginStart;
}
export type InfraClientCoreSetup = CoreSetup<InfraClientStartDeps, InfraClientStartExports>;

View file

@ -33,4 +33,4 @@ export const casesPath = '/cases';
// Name of a locator created by the uptime plugin. Intended for use
// by other plugins as well, so defined here to prevent cross-references.
export const uptimeOverviewLocatorID = 'uptime-overview-locator';
export const uptimeOverviewLocatorID = 'UPTIME_OVERVIEW_LOCATOR';

View file

@ -25,17 +25,34 @@ describe('uptimeOverviewNavigatorParams', () => {
});
it('creates a path with expected search when hostname is specified', async () => {
const location = await uptimeOverviewNavigatorParams.getLocation({ hostname: 'elastic.co' });
expect(location.path).toEqual(`${OVERVIEW_ROUTE}?search=url.domain: "elastic.co"`);
const location = await uptimeOverviewNavigatorParams.getLocation({ host: 'elastic.co' });
expect(location.path).toEqual(`${OVERVIEW_ROUTE}?search=host.name: "elastic.co"`);
});
it('creates a path with expected search when multiple keys are specified', async () => {
it('creates a path with expected search when multiple host keys are specified', async () => {
const location = await uptimeOverviewNavigatorParams.getLocation({
hostname: 'elastic.co',
host: 'elastic.co',
ip: '127.0.0.1',
});
expect(location.path).toEqual(
`${OVERVIEW_ROUTE}?search=monitor.ip: "127.0.0.1" OR url.domain: "elastic.co"`
`${OVERVIEW_ROUTE}?search=host.name: "elastic.co" OR host.ip: "127.0.0.1"`
);
});
it('creates a path with expected search when multiple kubernetes pod is specified', async () => {
const location = await uptimeOverviewNavigatorParams.getLocation({
pod: 'foo',
ip: '10.0.0.1',
});
expect(location.path).toEqual(
`${OVERVIEW_ROUTE}?search=kubernetes.pod.uid: "foo" OR monitor.ip: "10.0.0.1"`
);
});
it('creates a path with expected search when docker container is specified', async () => {
const location = await uptimeOverviewNavigatorParams.getLocation({
container: 'foo',
});
expect(location.path).toEqual(`${OVERVIEW_ROUTE}?search=container.id: "foo"`);
});
});

View file

@ -6,15 +6,31 @@
*/
import { uptimeOverviewLocatorID } from '../../../../observability/public';
import { OVERVIEW_ROUTE } from '../../../common/constants';
import { OVERVIEW_ROUTE } from '../../../common/constants/ui';
const formatSearchKey = (key: string, value: string) => `${key}: "${value}"`;
async function navigate({ ip, hostname }: { ip?: string; hostname?: string }) {
async function navigate({
ip,
host,
container,
pod,
}: {
ip?: string;
host?: string;
container?: string;
pod?: string;
}) {
const searchParams: string[] = [];
if (ip) searchParams.push(formatSearchKey('monitor.ip', ip));
if (hostname) searchParams.push(formatSearchKey('url.domain', hostname));
if (host) searchParams.push(formatSearchKey('host.name', host));
if (container) searchParams.push(formatSearchKey('container.id', container));
if (pod) searchParams.push(formatSearchKey('kubernetes.pod.uid', pod));
if (ip) {
const root = host ? 'host' : 'monitor';
searchParams.push(formatSearchKey(`${root}.ip`, ip));
}
const searchString = searchParams.join(' OR ');

View file

@ -49,6 +49,7 @@ import {
import { LazySyntheticsCustomAssetsExtension } from '../components/fleet_package/lazy_synthetics_custom_assets_extension';
import { Start as InspectorPluginStart } from '../../../../../src/plugins/inspector/public';
import { CasesUiStart } from '../../../cases/public';
import { uptimeOverviewNavigatorParams } from './locators/overview';
export interface ClientPluginsSetup {
home?: HomePublicPluginSetup;
@ -104,6 +105,8 @@ export class UptimePlugin
return UptimeDataHelper(coreStart);
};
plugins.share.url.locators.create(uptimeOverviewNavigatorParams);
plugins.observability.dashboard.register({
appName: 'synthetics',
hasData: async () => {

View file

@ -17,7 +17,6 @@ import {
} from '../../common/constants';
import { UptimeApp, UptimeAppProps } from './uptime_app';
import { ClientPluginsSetup, ClientPluginsStart } from './plugin';
import { uptimeOverviewNavigatorParams } from './locators/overview';
export function renderApp(
core: CoreStart,
@ -41,8 +40,6 @@ export function renderApp(
const canSave = (capabilities.uptime.save ?? false) as boolean;
plugins.share.url.locators.create(uptimeOverviewNavigatorParams);
const props: UptimeAppProps = {
isDev,
plugins,