Skip to content
/ kibana Public
forked from elastic/kibana

Commit

Permalink
[APM] Experimental Service Map front end
Browse files Browse the repository at this point in the history
Add service map tabs on the main APM screen and for individual services.

This is not yet hooked up to work with back-end data, so it always shows the same hard-coded graph.

This is experimental, so you must have x-pack.apm.serviceMapEnabled: true in your Kibana config for it to show up.

Also add "PSF" to the list of allowed licenses since a new dependency added uses this license (it's on the [green list](https://github.com/elastic/open-source/blob/master/elastic-product-policy.md#green-list).)

Fixes elastic#44890
Fixes elastic#44853
  • Loading branch information
smith committed Oct 7, 2019
1 parent dba0946 commit d75733f
Show file tree
Hide file tree
Showing 25 changed files with 1,251 additions and 427 deletions.
862 changes: 462 additions & 400 deletions packages/kbn-pm/dist/index.js

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions renovate.json5
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,14 @@
packageNames: [
'fancy-log',
'@types/fancy-log',
]
},
{
groupSlug: 'cytoscape',
groupName: 'cytoscape related packages',
packageNames: [
'cytoscape',
'@types/cytoscape',
],
},
{
Expand Down
6 changes: 4 additions & 2 deletions src/core/public/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@
*/
import { applicationServiceMock } from './application/application_service.mock';
import { chromeServiceMock } from './chrome/chrome_service.mock';
import { CoreSetup, CoreStart, PluginInitializerContext } from '.';
import { CoreSetup, LegacyCoreStart, PluginInitializerContext } from '.';
import { docLinksServiceMock } from './doc_links/doc_links_service.mock';
import { fatalErrorsServiceMock } from './fatal_errors/fatal_errors_service.mock';
import { httpServiceMock } from './http/http_service.mock';
import { i18nServiceMock } from './i18n/i18n_service.mock';
import { injectedMetadataServiceMock } from './injected_metadata/injected_metadata_service.mock';
import { notificationServiceMock } from './notifications/notifications_service.mock';
import { overlayServiceMock } from './overlays/overlay_service.mock';
import { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock';
Expand Down Expand Up @@ -58,12 +59,13 @@ function createCoreSetupMock() {
}

function createCoreStartMock() {
const mock: MockedKeys<CoreStart> = {
const mock: MockedKeys<LegacyCoreStart> = {
application: applicationServiceMock.createStartContract(),
chrome: chromeServiceMock.createStartContract(),
docLinks: docLinksServiceMock.createStartContract(),
http: httpServiceMock.createStartContract(),
i18n: i18nServiceMock.createStartContract(),
injectedMetadata: injectedMetadataServiceMock.createStartContract(),
notifications: notificationServiceMock.createStartContract(),
overlays: overlayServiceMock.createStartContract(),
uiSettings: uiSettingsServiceMock.createStartContract(),
Expand Down
1 change: 1 addition & 0 deletions src/dev/license_checker/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export const LICENSE_WHITELIST = [
'MIT/X11',
'new BSD, and MIT',
'(OFL-1.1 AND MIT)',
'PSF',
'Public Domain',
'Unlicense',
'WTFPL OR ISC',
Expand Down
6 changes: 5 additions & 1 deletion x-pack/legacy/plugins/apm/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const apm: LegacyPluginInitializer = kibana => {
apmUiEnabled: config.get('xpack.apm.ui.enabled'),
// TODO: rename to apm_oss.indexPatternTitle in 7.0 (breaking change)
apmIndexPatternTitle: config.get('apm_oss.indexPattern'),
apmServiceMapEnabled: config.get('xpack.apm.serviceMapEnabled'),
apmTransactionIndices: config.get('apm_oss.transactionIndices')
};
},
Expand Down Expand Up @@ -70,7 +71,10 @@ export const apm: LegacyPluginInitializer = kibana => {

// buckets
minimumBucketSize: Joi.number().default(15),
bucketTargetCount: Joi.number().default(15)
bucketTargetCount: Joi.number().default(15),

// service map
serviceMapEnabled: Joi.boolean().default(false)
}).default();
},

Expand Down
19 changes: 18 additions & 1 deletion x-pack/legacy/plugins/apm/public/components/app/Home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
EuiSpacer
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { npStart } from 'ui/new_platform';
import React from 'react';
import { $ElementType } from 'utility-types';
import { ApmHeader } from '../../shared/ApmHeader';
Expand All @@ -23,6 +24,8 @@ import { ServiceOverviewLink } from '../../shared/Links/apm/ServiceOverviewLink'
import { TraceOverviewLink } from '../../shared/Links/apm/TraceOverviewLink';
import { EuiTabLink } from '../../shared/EuiTabLink';
import { SettingsLink } from '../../shared/Links/apm/SettingsLink';
import { ServiceMapLink } from '../../shared/Links/apm/ServiceMapLink';
import { ServiceMap } from '../ServiceMap';

const homeTabs = [
{
Expand All @@ -49,12 +52,26 @@ const homeTabs = [
}
];

if (npStart.core.injectedMetadata.getInjectedVar('apmServiceMapEnabled')) {
homeTabs.push({
link: (
<ServiceMapLink>
{i18n.translate('xpack.apm.home.serviceMapTabLabel', {
defaultMessage: 'Service Map'
})}
</ServiceMapLink>
),
render: () => <ServiceMap />,
name: 'service-map'
});
}

const SETTINGS_LINK_LABEL = i18n.translate('xpack.apm.settingsLinkLabel', {
defaultMessage: 'Settings'
});

interface Props {
tab: 'traces' | 'services';
tab: 'traces' | 'services' | 'service-map';
}

export function Home({ tab }: Props) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import { i18n } from '@kbn/i18n';
import React from 'react';
import { Redirect, RouteComponentProps } from 'react-router-dom';
import { npStart } from 'ui/new_platform';
import { ErrorGroupDetails } from '../../ErrorGroupDetails';
import { ServiceDetails } from '../../ServiceDetails';
import { TransactionDetails } from '../../TransactionDetails';
Expand Down Expand Up @@ -138,6 +139,16 @@ export const routes: BreadcrumbRoute[] = [
},
name: RouteName.SERVICE_NODE_METRICS
},
// service map
{
exact: true,
path: '/services/:serviceName/service-map',
component: () => <ServiceDetails tab="service-map" />,
breadcrumb: i18n.translate('xpack.apm.breadcrumb.serviceMapTitle', {
defaultMessage: 'Service Map'
}),
name: RouteName.SINGLE_SERVICE_MAP
},
{
exact: true,
path: '/services/:serviceName/transactions/view',
Expand All @@ -149,3 +160,26 @@ export const routes: BreadcrumbRoute[] = [
name: RouteName.TRANSACTION_NAME
}
];

if (npStart.core.injectedMetadata.getInjectedVar('apmServiceMapEnabled')) {
routes.push(
{
exact: true,
path: '/service-map',
component: () => <Home tab="service-map" />,
breadcrumb: i18n.translate('xpack.apm.breadcrumb.serviceMapTitle', {
defaultMessage: 'Service Map'
}),
name: RouteName.SERVICE_MAP
},
{
exact: true,
path: '/services/:serviceName/service-map',
component: () => <ServiceDetails tab="service-map" />,
breadcrumb: i18n.translate('xpack.apm.breadcrumb.serviceMapTitle', {
defaultMessage: 'Service Map'
}),
name: RouteName.SINGLE_SERVICE_MAP
}
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
export enum RouteName {
HOME = 'home',
SERVICES = 'services',
SERVICE_MAP = 'service-map',
SINGLE_SERVICE_MAP = 'single-service-map',
TRACES = 'traces',
SERVICE = 'service',
TRANSACTIONS = 'transactions',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import { i18n } from '@kbn/i18n';
import React from 'react';
import { EuiTabs, EuiSpacer } from '@elastic/eui';
import { npStart } from 'ui/new_platform';
import { ErrorGroupOverview } from '../ErrorGroupOverview';
import { TransactionOverview } from '../TransactionOverview';
import { ServiceMetrics } from '../ServiceMetrics';
Expand All @@ -19,9 +20,11 @@ import { MetricOverviewLink } from '../../shared/Links/apm/MetricOverviewLink';
import { ServiceNodeOverviewLink } from '../../shared/Links/apm/ServiceNodeOverviewLink';
import { ServiceNodeOverview } from '../ServiceNodeOverview';
import { useAgentName } from '../../../hooks/useAgentName';
import { ServiceMap } from '../ServiceMap';
import { ServiceMapLink } from '../../shared/Links/apm/ServiceMapLink';

interface Props {
tab: 'transactions' | 'errors' | 'metrics' | 'nodes';
tab: 'transactions' | 'errors' | 'metrics' | 'nodes' | 'service-map';
}

export function ServiceDetailTabs({ tab }: Props) {
Expand Down Expand Up @@ -90,6 +93,22 @@ export function ServiceDetailTabs({ tab }: Props) {
tabs.push(metricsTab);
}

const serviceMapTab = {
link: (
<ServiceMapLink serviceName={serviceName}>
{i18n.translate('xpack.apm.home.serviceMapTabLabel', {
defaultMessage: 'Service Map'
})}
</ServiceMapLink>
),
render: () => <ServiceMap serviceName={serviceName} />,
name: 'service-map'
};

if (npStart.core.injectedMetadata.getInjectedVar('apmServiceMapEnabled')) {
tabs.push(serviceMapTab);
}

const selectedTab = tabs.find(serviceTab => serviceTab.name === tab);

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* 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, { useContext, useState, useEffect } from 'react';
import { EuiButtonIcon, EuiPanel } from '@elastic/eui';
import theme from '@elastic/eui/dist/eui_theme_light.json';
import styled from 'styled-components';
import { i18n } from '@kbn/i18n';
import { CytoscapeContext } from './Cytoscape';
import { FullscreenPanel } from './FullscreenPanel';

const Container = styled('div')`
left: ${theme.gutterTypes.gutterMedium};
position: absolute;
top: ${theme.gutterTypes.gutterSmall};
`;

const Button = styled(EuiButtonIcon)`
display: block;
margin: ${theme.paddingSizes.xs};
`;

const ZoomInButton = styled(Button)`
margin-bottom: ${theme.paddingSizes.s};
`;

const ZoomPanel = styled(EuiPanel)`
margin-bottom: ${theme.paddingSizes.s};
`;

const duration = parseInt(theme.euiAnimSpeedFast, 10);
const steps = 5;

function doZoom(cy: cytoscape.Core | undefined, increment: number) {
if (cy) {
const level = cy.zoom() + increment;
cy.animate({
duration,
zoom: { level, position: cy.$('.primary').position() }
});
}
}

export function Controls() {
const cy = useContext(CytoscapeContext);

const [zoom, setZoom] = useState((cy && cy.zoom()) || 1);

useEffect(() => {
if (cy) {
cy.on('zoom', event => {
setZoom(event.cy.zoom());
});
}
}, [cy]);

function zoomIn() {
doZoom(cy, increment);
}

function zoomOut() {
doZoom(cy, -increment);
}

if (!cy) {
return null;
}

const maxZoom = cy.maxZoom();
const isMaxZoom = zoom === maxZoom;
const minZoom = cy.minZoom();
const isMinZoom = zoom === minZoom;
const increment = (maxZoom - minZoom) / steps;
const mapDomElement = cy.container();
const zoomInLabel = i18n.translate('xpack.apm.serviceMap.zoomIn', {
defaultMessage: 'Zoom in'
});
const zoomOutLabel = i18n.translate('xpack.apm.serviceMap.zoomOut', {
defaultMessage: 'Zoom out'
});

return (
<Container>
<ZoomPanel hasShadow={true} paddingSize="none">
<ZoomInButton
aria-label={zoomInLabel}
color="text"
disabled={isMaxZoom}
iconType="plusInCircleFilled"
onClick={zoomIn}
title={zoomInLabel}
/>
<Button
aria-label={zoomOutLabel}
color="text"
disabled={isMinZoom}
iconType="minusInCircleFilled"
onClick={zoomOut}
title={zoomOutLabel}
/>
</ZoomPanel>
<FullscreenPanel element={mapDomElement} />
</Container>
);
}
Loading

0 comments on commit d75733f

Please sign in to comment.