Skip to content

Commit

Permalink
Merge branch 'master' of github.com:elastic/kibana into edit-rule-act…
Browse files Browse the repository at this point in the history
…ions
  • Loading branch information
spong committed Jan 14, 2020
2 parents 23c1cc7 + daeddfd commit 760c8d5
Show file tree
Hide file tree
Showing 33 changed files with 1,501 additions and 131 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ interface CytoscapeProps {
children?: ReactNode;
elements: cytoscape.ElementDefinition[];
serviceName?: string;
style: CSSProperties;
style?: CSSProperties;
}

function useCytoscape(options: cytoscape.CytoscapeOptions) {
Expand Down Expand Up @@ -69,8 +69,8 @@ export function Cytoscape({

// Set up cytoscape event handlers
useEffect(() => {
if (cy) {
cy.on('data', event => {
const dataHandler: cytoscape.EventHandler = event => {
if (cy) {
// Add the "primary" class to the node if its id matches the serviceName.
if (cy.nodes().length > 0 && serviceName) {
cy.nodes().removeClass('primary');
Expand All @@ -80,8 +80,30 @@ export function Cytoscape({
if (event.cy.elements().length > 0) {
cy.layout(cytoscapeOptions.layout as cytoscape.LayoutOptions).run();
}
});
}
};
const mouseoverHandler: cytoscape.EventHandler = event => {
event.target.addClass('hover');
event.target.connectedEdges().addClass('nodeHover');
};
const mouseoutHandler: cytoscape.EventHandler = event => {
event.target.removeClass('hover');
event.target.connectedEdges().removeClass('nodeHover');
};

if (cy) {
cy.on('data', dataHandler);
cy.on('mouseover', 'edge, node', mouseoverHandler);
cy.on('mouseout', 'edge, node', mouseoutHandler);
}

return () => {
if (cy) {
cy.removeListener('data', undefined, dataHandler);
cy.removeListener('mouseover', 'edge, node', mouseoverHandler);
cy.removeListener('mouseout', 'edge, node', mouseoutHandler);
}
};
}, [cy, serviceName]);

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 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.
*/

/* eslint-disable @elastic/eui/href-or-on-click */

import { EuiButton, EuiFlexItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { MouseEvent } from 'react';
import { useUrlParams } from '../../../../hooks/useUrlParams';
import { getAPMHref } from '../../../shared/Links/apm/APMLink';

interface ButtonsProps {
focusedServiceName?: string;
onFocusClick?: (event: MouseEvent<HTMLAnchorElement>) => void;
selectedNodeServiceName: string;
}

export function Buttons({
focusedServiceName,
onFocusClick = () => {},
selectedNodeServiceName
}: ButtonsProps) {
const currentSearch = useUrlParams().urlParams.kuery ?? '';
const detailsUrl = getAPMHref(
`/services/${selectedNodeServiceName}/transactions`,
currentSearch
);
const focusUrl = getAPMHref(
`/services/${selectedNodeServiceName}/service-map`,
currentSearch
);

const isAlreadyFocused = focusedServiceName === selectedNodeServiceName;

return (
<>
<EuiFlexItem>
<EuiButton href={detailsUrl} fill={true}>
{i18n.translate('xpack.apm.serviceMap.serviceDetailsButtonText', {
defaultMessage: 'Service Details'
})}
</EuiButton>
</EuiFlexItem>
<EuiFlexItem>
<EuiButton
isDisabled={isAlreadyFocused}
color="secondary"
href={focusUrl}
onClick={onFocusClick}
title={
isAlreadyFocused
? i18n.translate('xpack.apm.serviceMap.alreadyFocusedTitleText', {
defaultMessage: 'Map is already focused'
})
: undefined
}
>
{i18n.translate('xpack.apm.serviceMap.focusMapButtonText', {
defaultMessage: 'Focus map'
})}
</EuiButton>
</EuiFlexItem>
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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 { i18n } from '@kbn/i18n';
import styled from 'styled-components';
import lightTheme from '@elastic/eui/dist/eui_theme_light.json';

const ItemRow = styled.div`
line-height: 2;
`;

const ItemTitle = styled.dt`
color: ${lightTheme.textColors.subdued};
`;

const ItemDescription = styled.dd``;

interface InfoProps {
type: string;
subtype?: string;
}

export function Info({ type, subtype }: InfoProps) {
const listItems = [
{
title: i18n.translate('xpack.apm.serviceMap.typePopoverMetric', {
defaultMessage: 'Type'
}),
description: type
},
{
title: i18n.translate('xpack.apm.serviceMap.subtypePopoverMetric', {
defaultMessage: 'Subtype'
}),
description: subtype
}
];

return (
<>
{listItems.map(
({ title, description }) =>
description && (
<ItemRow key={title}>
<ItemTitle>{title}</ItemTitle>
<ItemDescription>{description}</ItemDescription>
</ItemRow>
)
)}
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/*
* 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,
EuiLoadingSpinner,
EuiFlexItem,
EuiBadge
} from '@elastic/eui';
import lightTheme from '@elastic/eui/dist/eui_theme_light.json';
import { i18n } from '@kbn/i18n';
import { isNumber } from 'lodash';
import React from 'react';
import styled from 'styled-components';
import { ServiceNodeMetrics } from '../../../../../server/lib/service_map/get_service_map_service_node_info';
import {
asDuration,
asPercent,
toMicroseconds,
tpmUnit
} from '../../../../utils/formatters';
import { useUrlParams } from '../../../../hooks/useUrlParams';
import { useFetcher } from '../../../../hooks/useFetcher';

function LoadingSpinner() {
return (
<EuiFlexGroup
alignItems="center"
justifyContent="spaceAround"
style={{ height: 170 }}
>
<EuiLoadingSpinner size="xl" />
</EuiFlexGroup>
);
}

const ItemRow = styled('tr')`
line-height: 2;
`;

const ItemTitle = styled('td')`
color: ${lightTheme.textColors.subdued};
padding-right: 1rem;
`;

const ItemDescription = styled('td')`
text-align: right;
`;

const na = i18n.translate('xpack.apm.serviceMap.NotAvailableMetric', {
defaultMessage: 'N/A'
});

interface MetricListProps {
serviceName: string;
}

export function ServiceMetricList({ serviceName }: MetricListProps) {
const {
urlParams: { start, end, environment }
} = useUrlParams();

const { data = {} as ServiceNodeMetrics, status } = useFetcher(
callApmApi => {
if (serviceName && start && end) {
return callApmApi({
pathname: '/api/apm/service-map/service/{serviceName}',
params: {
path: {
serviceName
},
query: {
start,
end,
environment
}
}
});
}
},
[serviceName, start, end, environment],
{
preservePreviousData: false
}
);

const {
avgTransactionDuration,
avgRequestsPerMinute,
avgErrorsPerMinute,
avgCpuUsage,
avgMemoryUsage,
numInstances
} = data;
const isLoading = status === 'loading';

const listItems = [
{
title: i18n.translate(
'xpack.apm.serviceMap.avgTransDurationPopoverMetric',
{
defaultMessage: 'Trans. duration (avg.)'
}
),
description: isNumber(avgTransactionDuration)
? asDuration(toMicroseconds(avgTransactionDuration, 'milliseconds'))
: na
},
{
title: i18n.translate(
'xpack.apm.serviceMap.avgReqPerMinutePopoverMetric',
{
defaultMessage: 'Req. per minute (avg.)'
}
),
description: isNumber(avgRequestsPerMinute)
? `${avgRequestsPerMinute.toFixed(2)} ${tpmUnit('request')}`
: na
},
{
title: i18n.translate(
'xpack.apm.serviceMap.avgErrorsPerMinutePopoverMetric',
{
defaultMessage: 'Errors per minute (avg.)'
}
),
description: avgErrorsPerMinute?.toFixed(2) ?? na
},
{
title: i18n.translate('xpack.apm.serviceMap.avgCpuUsagePopoverMetric', {
defaultMessage: 'CPU usage (avg.)'
}),
description: isNumber(avgCpuUsage) ? asPercent(avgCpuUsage, 1) : na
},
{
title: i18n.translate(
'xpack.apm.serviceMap.avgMemoryUsagePopoverMetric',
{
defaultMessage: 'Memory usage (avg.)'
}
),
description: isNumber(avgMemoryUsage) ? asPercent(avgMemoryUsage, 1) : na
}
];
return isLoading ? (
<LoadingSpinner />
) : (
<>
{numInstances && numInstances > 1 && (
<EuiFlexItem>
<div>
<EuiBadge iconType="apps" color="hollow">
{i18n.translate('xpack.apm.serviceMap.numInstancesMetric', {
values: { numInstances },
defaultMessage: '{numInstances} instances'
})}
</EuiBadge>
</div>
</EuiFlexItem>
)}

<table>
<tbody>
{listItems.map(({ title, description }) => (
<ItemRow key={title}>
<ItemTitle>{title}</ItemTitle>
<ItemDescription>{description}</ItemDescription>
</ItemRow>
))}
</tbody>
</table>
</>
);
}
Loading

0 comments on commit 760c8d5

Please sign in to comment.