[Infrastructure UI] Hosts view: Add links to apm and uptime for a single host (#154269)

Closes #150985 

## Summary

This PR adds links to APM Traces and Uptime.
<img width="1909" alt="image"
src="https://user-images.githubusercontent.com/14139027/229580612-a3d0c03a-4f68-4b09-b333-ffa79c8454e6.png">

# Testing
1. Open Hosts view 
2. Click on the button to open the flyout for a single host
- Click on the APM traces link and verify the query parameters are
correct
- Click on the uptime link and verify the query parameters are correct
   


https://user-images.githubusercontent.com/14139027/229581672-3c50ea55-e834-4431-aac6-3ed3ff9f96cc.mov

---------

Co-authored-by: Carlos Crespo <crespocarlos@users.noreply.github.com>
This commit is contained in:
jennypavlova 2023-04-06 12:23:03 +02:00 committed by GitHub
parent b8409c9173
commit 8c2ff54c96
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 166 additions and 6 deletions

View file

@ -6,13 +6,26 @@
*/
import React from 'react';
import { EuiFlyout, EuiFlyoutHeader, EuiTitle, EuiFlyoutBody } from '@elastic/eui';
import { EuiSpacer, EuiTabs, EuiTab } from '@elastic/eui';
import {
EuiFlyout,
EuiFlyoutHeader,
EuiTitle,
EuiFlyoutBody,
EuiFlexGroup,
EuiFlexItem,
EuiTab,
EuiSpacer,
EuiTabs,
useEuiTheme,
} from '@elastic/eui';
import { css } from '@emotion/react';
import { useUnifiedSearchContext } from '../../hooks/use_unified_search';
import { LinkToUptime } from './links/link_to_uptime';
import { LinkToApmServices } from './links/link_to_apm_services';
import { useLazyRef } from '../../../../../hooks/use_lazy_ref';
import { metadataTab } from './metadata';
import type { InventoryItemType } from '../../../../../../common/inventory_models/types';
import type { HostNodeRow } from '../../hooks/use_hosts_table';
import { useUnifiedSearchContext } from '../../hooks/use_unified_search';
import { processesTab } from './processes';
import { Metadata } from './metadata/metadata';
import { Processes } from './processes/processes';
@ -28,6 +41,7 @@ const NODE_TYPE = 'host' as InventoryItemType;
export const Flyout = ({ node, closeFlyout }: Props) => {
const { getDateRangeAsTimestamp } = useUnifiedSearchContext();
const { euiTheme } = useEuiTheme();
const currentTimeRange = {
...getDateRangeAsTimestamp(),
@ -57,9 +71,24 @@ export const Flyout = ({ node, closeFlyout }: Props) => {
return (
<EuiFlyout onClose={closeFlyout} ownFocus={false}>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="xs">
<h2>{node.name}</h2>
</EuiTitle>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem>
<EuiTitle size="xs">
<h2>{node.name}</h2>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<LinkToApmServices hostName={node.name} apmField={'host.hostname'} />
</EuiFlexItem>
<EuiFlexItem
grow={false}
css={css`
margin-right: ${euiTheme.size.l};
`}
>
<LinkToUptime nodeType={NODE_TYPE} node={node} />
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="s" />
<EuiTabs style={{ marginBottom: '-25px' }} size="s">
{tabEntries}

View file

@ -0,0 +1,52 @@
/*
* 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 React from 'react';
import { stringify } from 'querystring';
import { encode } from '@kbn/rison';
import { css } from '@emotion/react';
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
import { EuiIcon, EuiLink, useEuiTheme } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { useKibanaContextForPlugin } from '../../../../../../hooks/use_kibana';
interface LinkToApmServicesProps {
hostName: string;
apmField: string;
}
export const LinkToApmServices = ({ hostName, apmField }: LinkToApmServicesProps) => {
const { services } = useKibanaContextForPlugin();
const { http } = services;
const { euiTheme } = useEuiTheme();
const queryString = new URLSearchParams(
encode(
stringify({
kuery: `${apmField}:"${hostName}"`,
})
)
);
const linkToApmServices = http.basePath.prepend(`/app/apm/services?${queryString}`);
return (
<RedirectAppLinks coreStart={services}>
<EuiLink href={linkToApmServices} data-test-subj="hostsView-flyout-apm-services-link">
<EuiIcon
type="popout"
css={css`
margin-right: ${euiTheme.size.xs};
`}
/>
<FormattedMessage
id="xpack.infra.hostsViewPage.flyout.apmServicesLinkLabel"
defaultMessage="APM Services"
/>
</EuiLink>
</RedirectAppLinks>
);
};

View file

@ -0,0 +1,47 @@
/*
* 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 React from 'react';
import { EuiLink, EuiIcon, useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/react';
import { FormattedMessage } from '@kbn/i18n-react';
import { uptimeOverviewLocatorID } from '@kbn/observability-plugin/public';
import { useKibanaContextForPlugin } from '../../../../../../hooks/use_kibana';
import type { InventoryItemType } from '../../../../../../../common/inventory_models/types';
import type { HostNodeRow } from '../../../hooks/use_hosts_table';
interface LinkTUptimeProps {
nodeType: InventoryItemType;
node: HostNodeRow;
}
export const LinkToUptime = ({ nodeType, node }: LinkTUptimeProps) => {
const { share } = useKibanaContextForPlugin().services;
const { euiTheme } = useEuiTheme();
return (
<EuiLink
data-test-subj="hostsView-flyout-uptime-link"
onClick={() =>
share.url.locators
.get(uptimeOverviewLocatorID)!
.navigate({ [nodeType]: node.name, ip: node.ip })
}
>
<EuiIcon
type="popout"
css={css`
margin-right: ${euiTheme.size.xs};
`}
/>
<FormattedMessage
id="xpack.infra.hostsViewPage.flyout.uptimeLinkLabel"
defaultMessage="Uptime"
/>
</EuiLink>
);
};

View file

@ -73,6 +73,7 @@ describe('useHostTable hook', () => {
{
name: 'host-0',
os: '-',
ip: '',
id: 'host-0-0',
title: {
cloudProvider: 'aws',
@ -103,6 +104,7 @@ describe('useHostTable hook', () => {
{
name: 'host-1',
os: 'macOS',
ip: '243.86.94.22',
id: 'host-1-1',
title: {
cloudProvider: null,

View file

@ -31,6 +31,7 @@ type HostMetrics = Record<HostMetric, SnapshotNodeMetric>;
export interface HostNodeRow extends HostMetrics {
os?: string | null;
ip?: string | null;
servicesOnHost?: number | null;
title: { name: string; cloudProvider?: CloudProvider | null };
name: string;
@ -53,6 +54,7 @@ const buildItemsList = (nodes: SnapshotNode[]) => {
id: `${name}-${index}`,
name,
os: path.at(-1)?.os ?? '-',
ip: path.at(-1)?.ip ?? '',
title: {
name,
cloudProvider: path.at(-1)?.cloudProvider ?? null,

View file

@ -244,6 +244,26 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
expect(metadataTab).to.contain('Metadata');
});
it('should navigate to Uptime after click', async () => {
await pageObjects.infraHostsView.clickFlyoutUptimeLink();
await pageObjects.infraHome.waitForLoading();
const url = await browser.getCurrentUrl();
expect(url).to.contain(
'app/uptime/?search=host.name%3A%20%22Jennys-MBP.fritz.box%22%20OR%20host.ip%3A%20%22192.168.1.79%22'
);
await browser.goBack();
await pageObjects.infraHome.waitForLoading();
});
it('should navigate to APM services after click', async () => {
await pageObjects.infraHostsView.clickFlyoutApmServicesLink();
await pageObjects.infraHome.waitForLoading();
const url = await browser.getCurrentUrl();
expect(url).to.contain('app/apm/services?kuery=host.hostname%3A%22Jennys-MBP.fritz.box%22');
await browser.goBack();
await pageObjects.infraHome.waitForLoading();
});
describe('should render processes tab', async () => {
const processTitles = [
'Total processes',

View file

@ -36,6 +36,14 @@ export function InfraHostsViewProvider({ getService }: FtrProviderContext) {
return testSubjects.click('infraProcessRowButton');
},
async clickFlyoutUptimeLink() {
return testSubjects.click('hostsView-flyout-uptime-link');
},
async clickFlyoutApmServicesLink() {
return testSubjects.click('hostsView-flyout-apm-services-link');
},
async getHostsLandingPageDisabled() {
const container = await testSubjects.find('hostView-no-enable-access');
const containerText = await container.getVisibleText();