[APM] Fit service map to container (#56336)

* Set the height of the service map dynamically
* Move the loading overlay from the outside to the inside of the Cytoscape container
* Remove the EUI spacer from the Home and ServiceDetailTabs components and add it to the individual components that use them
This commit is contained in:
Nathan L Smith 2020-01-29 22:23:07 -06:00 committed by GitHub
parent 85000a57ab
commit 4084be7380
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 228 additions and 170 deletions

View file

@ -95,39 +95,42 @@ const ErrorGroupOverview: React.FC = () => {
}
return (
<EuiFlexGroup>
<EuiFlexItem grow={1}>
<LocalUIFilters {...localUIFiltersConfig} />
</EuiFlexItem>
<EuiFlexItem grow={7}>
<EuiFlexGroup>
<EuiFlexItem>
<EuiPanel>
<ErrorDistribution
distribution={errorDistributionData}
title={i18n.translate(
'xpack.apm.serviceDetails.metrics.errorOccurrencesChartTitle',
{
defaultMessage: 'Error occurrences'
}
)}
/>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
<>
<EuiSpacer />
<EuiFlexGroup>
<EuiFlexItem grow={1}>
<LocalUIFilters {...localUIFiltersConfig} />
</EuiFlexItem>
<EuiFlexItem grow={7}>
<EuiFlexGroup>
<EuiFlexItem>
<EuiPanel>
<ErrorDistribution
distribution={errorDistributionData}
title={i18n.translate(
'xpack.apm.serviceDetails.metrics.errorOccurrencesChartTitle',
{
defaultMessage: 'Error occurrences'
}
)}
/>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="s" />
<EuiPanel>
<EuiTitle size="xs">
<h3>Errors</h3>
</EuiTitle>
<EuiSpacer size="s" />
<ErrorGroupList items={errorGroupListData} />
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
<EuiPanel>
<EuiTitle size="xs">
<h3>Errors</h3>
</EuiTitle>
<EuiSpacer size="s" />
<ErrorGroupList items={errorGroupListData} />
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
</>
);
};

View file

@ -5,27 +5,26 @@
*/
import {
EuiButtonEmpty,
EuiFlexGroup,
EuiFlexItem,
EuiTitle,
EuiButtonEmpty,
EuiTabs,
EuiSpacer
EuiTitle
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { $ElementType } from 'utility-types';
import { useApmPluginContext } from '../../../hooks/useApmPluginContext';
import { ApmHeader } from '../../shared/ApmHeader';
import { EuiTabLink } from '../../shared/EuiTabLink';
import { ServiceMapLink } from '../../shared/Links/apm/ServiceMapLink';
import { ServiceOverviewLink } from '../../shared/Links/apm/ServiceOverviewLink';
import { SettingsLink } from '../../shared/Links/apm/SettingsLink';
import { TraceOverviewLink } from '../../shared/Links/apm/TraceOverviewLink';
import { SetupInstructionsLink } from '../../shared/Links/SetupInstructionsLink';
import { ServiceMap } from '../ServiceMap';
import { ServiceOverview } from '../ServiceOverview';
import { TraceOverview } from '../TraceOverview';
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';
import { useApmPluginContext } from '../../../hooks/useApmPluginContext';
function getHomeTabs({
serviceMapEnabled = false
@ -116,7 +115,6 @@ export function Home({ tab }: Props) {
</EuiTabLink>
))}
</EuiTabs>
<EuiSpacer />
{selectedTab.render()}
</div>
);

View file

@ -4,24 +4,24 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiTabs } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { EuiTabs, EuiSpacer } from '@elastic/eui';
import { ErrorGroupOverview } from '../ErrorGroupOverview';
import { TransactionOverview } from '../TransactionOverview';
import { ServiceMetrics } from '../ServiceMetrics';
import { isRumAgentName, isJavaAgentName } from '../../../../common/agent_name';
import { EuiTabLink } from '../../shared/EuiTabLink';
import { isJavaAgentName, isRumAgentName } from '../../../../common/agent_name';
import { useAgentName } from '../../../hooks/useAgentName';
import { useApmPluginContext } from '../../../hooks/useApmPluginContext';
import { useUrlParams } from '../../../hooks/useUrlParams';
import { TransactionOverviewLink } from '../../shared/Links/apm/TransactionOverviewLink';
import { EuiTabLink } from '../../shared/EuiTabLink';
import { ErrorOverviewLink } from '../../shared/Links/apm/ErrorOverviewLink';
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';
import { useApmPluginContext } from '../../../hooks/useApmPluginContext';
import { ServiceNodeOverviewLink } from '../../shared/Links/apm/ServiceNodeOverviewLink';
import { TransactionOverviewLink } from '../../shared/Links/apm/TransactionOverviewLink';
import { ErrorGroupOverview } from '../ErrorGroupOverview';
import { ServiceMap } from '../ServiceMap';
import { ServiceMetrics } from '../ServiceMetrics';
import { ServiceNodeOverview } from '../ServiceNodeOverview';
import { TransactionOverview } from '../TransactionOverview';
interface Props {
tab: 'transactions' | 'errors' | 'metrics' | 'nodes' | 'service-map';
@ -124,7 +124,6 @@ export function ServiceDetailTabs({ tab }: Props) {
</EuiTabLink>
))}
</EuiTabs>
<EuiSpacer />
{selectedTab ? selectedTab.render() : null}
</>
);

View file

@ -25,6 +25,7 @@ export const CytoscapeContext = createContext<cytoscape.Core | undefined>(
interface CytoscapeProps {
children?: ReactNode;
elements: cytoscape.ElementDefinition[];
height: number;
serviceName?: string;
style?: CSSProperties;
}
@ -54,11 +55,16 @@ function useCytoscape(options: cytoscape.CytoscapeOptions) {
export function Cytoscape({
children,
elements,
height,
serviceName,
style
}: CytoscapeProps) {
const [ref, cy] = useCytoscape({ ...cytoscapeOptions, elements });
// Add the height to the div style. The height is a separate prop because it
// is required and can trigger rendering when changed.
const divStyle = { ...style, height };
// Trigger a custom "data" event when data changes
useEffect(() => {
if (cy) {
@ -108,7 +114,7 @@ export function Cytoscape({
return (
<CytoscapeContext.Provider value={cy}>
<div ref={ref} style={style}>
<div ref={ref} style={divStyle}>
{children}
</div>
</CytoscapeContext.Provider>

View file

@ -31,16 +31,11 @@ const ProgressBarContainer = styled.div`
`;
interface Props {
children: React.ReactNode;
isLoading: boolean;
percentageLoaded: number;
}
export const LoadingOverlay = ({
children,
isLoading,
percentageLoaded
}: Props) => (
export const LoadingOverlay = ({ isLoading, percentageLoaded }: Props) => (
<Container>
{isLoading && (
<Overlay>
@ -61,6 +56,5 @@ export const LoadingOverlay = ({
</EuiText>
</Overlay>
)}
{children}
</Container>
);

View file

@ -30,13 +30,13 @@ import { getCytoscapeElements } from './get_cytoscape_elements';
import { LoadingOverlay } from './LoadingOverlay';
import { PlatinumLicensePrompt } from './PlatinumLicensePrompt';
import { Popover } from './Popover';
import { useRefHeight } from './useRefHeight';
interface ServiceMapProps {
serviceName?: string;
}
const cytoscapeDivStyle = {
height: '85vh',
background: `linear-gradient(
90deg,
${theme.euiPageBackgroundColor}
@ -52,7 +52,8 @@ linear-gradient(
center,
${theme.euiColorLightShade}`,
backgroundSize: `${theme.euiSizeL} ${theme.euiSizeL}`,
margin: `-${theme.gutterTypes.gutterLarge}`
margin: `-${theme.gutterTypes.gutterLarge}`,
marginTop: 0
};
const MAX_REQUESTS = 5;
@ -198,17 +199,27 @@ export function ServiceMap({ serviceName }: ServiceMapProps) {
license?.isActive &&
(license?.type === 'platinum' || license?.type === 'trial');
const [wrapperRef, height] = useRefHeight();
return isValidPlatinumLicense ? (
<LoadingOverlay isLoading={isLoading} percentageLoaded={percentageLoaded}>
<div
style={{ height: height - parseInt(theme.gutterTypes.gutterLarge, 10) }}
ref={wrapperRef}
>
<Cytoscape
elements={renderedElements.current}
serviceName={serviceName}
height={height}
style={cytoscapeDivStyle}
>
<LoadingOverlay
isLoading={isLoading}
percentageLoaded={percentageLoaded}
/>
<Controls />
<Popover focusedServiceName={serviceName} />
</Cytoscape>
</LoadingOverlay>
</div>
) : (
<PlatinumLicensePrompt />
);

View file

@ -0,0 +1,20 @@
/*
* 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 { MutableRefObject, useRef } from 'react';
import { useWindowSize } from 'react-use';
export function useRefHeight(): [
MutableRefObject<HTMLDivElement | null>,
number
] {
const ref = useRef<HTMLDivElement>(null);
const windowHeight = useWindowSize().height;
const topOffset = ref.current?.getBoundingClientRect()?.top ?? 0;
const height = ref.current ? windowHeight - topOffset : 0;
return [ref, height];
}

View file

@ -43,24 +43,27 @@ export function ServiceMetrics({ agentName }: ServiceMetricsProps) {
);
return (
<EuiFlexGroup>
<EuiFlexItem grow={1}>
<LocalUIFilters {...localFiltersConfig} />
</EuiFlexItem>
<EuiFlexItem grow={7}>
<ChartsSyncContextProvider>
<EuiFlexGrid columns={2} gutterSize="s">
{data.charts.map(chart => (
<EuiFlexItem key={chart.key}>
<EuiPanel>
<MetricsChart start={start} end={end} chart={chart} />
</EuiPanel>
</EuiFlexItem>
))}
</EuiFlexGrid>
<EuiSpacer size="xxl" />
</ChartsSyncContextProvider>
</EuiFlexItem>
</EuiFlexGroup>
<>
<EuiSpacer />
<EuiFlexGroup>
<EuiFlexItem grow={1}>
<LocalUIFilters {...localFiltersConfig} />
</EuiFlexItem>
<EuiFlexItem grow={7}>
<ChartsSyncContextProvider>
<EuiFlexGrid columns={2} gutterSize="s">
{data.charts.map(chart => (
<EuiFlexItem key={chart.key}>
<EuiPanel>
<MetricsChart start={start} end={end} chart={chart} />
</EuiPanel>
</EuiFlexItem>
))}
</EuiFlexGrid>
<EuiSpacer size="xxl" />
</ChartsSyncContextProvider>
</EuiFlexItem>
</EuiFlexGroup>
</>
);
}

View file

@ -4,7 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { useMemo } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiToolTip } from '@elastic/eui';
import {
EuiFlexGroup,
EuiFlexItem,
EuiPanel,
EuiToolTip,
EuiSpacer
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import styled from 'styled-components';
import { UNIDENTIFIED_SERVICE_NODES_LABEL } from '../../../../common/i18n';
@ -150,25 +156,31 @@ const ServiceNodeOverview = () => {
];
return (
<EuiFlexGroup>
<EuiFlexItem grow={1}>
<LocalUIFilters {...localFiltersConfig} />
</EuiFlexItem>
<EuiFlexItem grow={7}>
<EuiPanel>
<ManagedTable
noItemsMessage={i18n.translate('xpack.apm.jvmsTable.noJvmsLabel', {
defaultMessage: 'No JVMs were found'
})}
items={items}
columns={columns}
initialPageSize={INITIAL_PAGE_SIZE}
initialSortField={INITIAL_SORT_FIELD}
initialSortDirection={INITIAL_SORT_DIRECTION}
/>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
<>
<EuiSpacer />
<EuiFlexGroup>
<EuiFlexItem grow={1}>
<LocalUIFilters {...localFiltersConfig} />
</EuiFlexItem>
<EuiFlexItem grow={7}>
<EuiPanel>
<ManagedTable
noItemsMessage={i18n.translate(
'xpack.apm.jvmsTable.noJvmsLabel',
{
defaultMessage: 'No JVMs were found'
}
)}
items={items}
columns={columns}
initialPageSize={INITIAL_PAGE_SIZE}
initialSortField={INITIAL_SORT_FIELD}
initialSortDirection={INITIAL_SORT_DIRECTION}
/>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
</>
);
};

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiPanel, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { EuiPanel, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
import { EuiLink } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useEffect, useMemo } from 'react';
@ -94,23 +94,26 @@ export function ServiceOverview() {
);
return (
<EuiFlexGroup>
<EuiFlexItem grow={1}>
<LocalUIFilters {...localFiltersConfig} />
</EuiFlexItem>
<EuiFlexItem grow={7}>
<EuiPanel>
<ServiceList
items={data.items}
noItemsMessage={
<NoServicesMessage
historicalDataFound={data.hasHistoricalData}
status={status}
/>
}
/>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
<>
<EuiSpacer />
<EuiFlexGroup>
<EuiFlexItem grow={1}>
<LocalUIFilters {...localFiltersConfig} />
</EuiFlexItem>
<EuiFlexItem grow={7}>
<EuiPanel>
<ServiceList
items={data.items}
noItemsMessage={
<NoServicesMessage
historicalDataFound={data.hasHistoricalData}
status={status}
/>
}
/>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
</>
);
}

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiPanel, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { EuiPanel, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
import React, { useMemo } from 'react';
import { FETCH_STATUS, useFetcher } from '../../../hooks/useFetcher';
import { TraceList } from './TraceList';
@ -47,15 +47,21 @@ export function TraceOverview() {
}, []);
return (
<EuiFlexGroup>
<EuiFlexItem grow={1}>
<LocalUIFilters {...localUIFiltersConfig} showCount={false} />
</EuiFlexItem>
<EuiFlexItem grow={7}>
<EuiPanel>
<TraceList items={data} isLoading={status === FETCH_STATUS.LOADING} />
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
<>
<EuiSpacer />
<EuiFlexGroup>
<EuiFlexItem grow={1}>
<LocalUIFilters {...localUIFiltersConfig} showCount={false} />
</EuiFlexItem>
<EuiFlexItem grow={7}>
<EuiPanel>
<TraceList
items={data}
isLoading={status === FETCH_STATUS.LOADING}
/>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
</>
);
}

View file

@ -119,41 +119,44 @@ export function TransactionOverview() {
}
return (
<EuiFlexGroup>
<EuiFlexItem grow={1}>
<LocalUIFilters {...localFiltersConfig}>
<TransactionTypeFilter transactionTypes={serviceTransactionTypes} />
<EuiSpacer size="xl" />
<EuiHorizontalRule margin="none" />
</LocalUIFilters>
</EuiFlexItem>
<EuiFlexItem grow={7}>
<ChartsSyncContextProvider>
<TransactionBreakdown initialIsOpen={true} />
<>
<EuiSpacer />
<EuiFlexGroup>
<EuiFlexItem grow={1}>
<LocalUIFilters {...localFiltersConfig}>
<TransactionTypeFilter transactionTypes={serviceTransactionTypes} />
<EuiSpacer size="xl" />
<EuiHorizontalRule margin="none" />
</LocalUIFilters>
</EuiFlexItem>
<EuiFlexItem grow={7}>
<ChartsSyncContextProvider>
<TransactionBreakdown initialIsOpen={true} />
<EuiSpacer size="s" />
<TransactionCharts
hasMLJob={hasMLJob}
charts={transactionCharts}
location={location}
urlParams={urlParams}
/>
</ChartsSyncContextProvider>
<EuiSpacer size="s" />
<TransactionCharts
hasMLJob={hasMLJob}
charts={transactionCharts}
location={location}
urlParams={urlParams}
/>
</ChartsSyncContextProvider>
<EuiSpacer size="s" />
<EuiPanel>
<EuiTitle size="xs">
<h3>Transactions</h3>
</EuiTitle>
<EuiSpacer size="s" />
<TransactionList
isLoading={transactionListStatus === 'loading'}
items={transactionListData}
/>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
<EuiPanel>
<EuiTitle size="xs">
<h3>Transactions</h3>
</EuiTitle>
<EuiSpacer size="s" />
<TransactionList
isLoading={transactionListStatus === 'loading'}
items={transactionListData}
/>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
</>
);
}

View file

@ -40,7 +40,7 @@ import { setReadonlyBadge } from './updateBadge';
export const REACT_APP_ROOT_ID = 'react-apm-root';
const MainContainer = styled.main`
const MainContainer = styled.div`
min-width: ${px(unit * 50)};
padding: ${px(units.plus)};
height: 100%;
@ -48,7 +48,7 @@ const MainContainer = styled.main`
const App = () => {
return (
<MainContainer data-test-subj="apmMainContainer">
<MainContainer data-test-subj="apmMainContainer" role="main">
<UpdateBreadcrumbs routes={routes} />
<Route component={ScrollToTopOnPathChange} />
<Switch>