[APM] Service Map Layout (#59020)
* Addresses #55544. - uses the core breadthfirst cytoscape layout - rotates elements by -90degrees - selects rum nodes as roots - implements hover styles to show connected nodes - fixes flash of unstyled cytoscape elements on initial load * PR review feedback * adds canned response for testing cytoscape layout in storybook * update dep snapshot for removing cytoscape-dagre
This commit is contained in:
parent
b12ef02cc4
commit
5539d6955f
|
@ -55,7 +55,7 @@ addParameters({
|
||||||
brandTitle: 'Kibana Storybook',
|
brandTitle: 'Kibana Storybook',
|
||||||
brandUrl: 'https://github.com/elastic/kibana/tree/master/packages/kbn-storybook',
|
brandUrl: 'https://github.com/elastic/kibana/tree/master/packages/kbn-storybook',
|
||||||
}),
|
}),
|
||||||
showPanel: true,
|
showPanel: false,
|
||||||
isFullscreen: false,
|
isFullscreen: false,
|
||||||
panelPosition: 'bottom',
|
panelPosition: 'bottom',
|
||||||
isToolshown: true,
|
isToolshown: true,
|
||||||
|
|
|
@ -9,8 +9,12 @@ import { storiesOf } from '@storybook/react';
|
||||||
import cytoscape from 'cytoscape';
|
import cytoscape from 'cytoscape';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Cytoscape } from './Cytoscape';
|
import { Cytoscape } from './Cytoscape';
|
||||||
|
import { getCytoscapeElements } from './get_cytoscape_elements';
|
||||||
|
import serviceMapResponse from './cytoscape-layout-test-response.json';
|
||||||
import { iconForNode } from './icons';
|
import { iconForNode } from './icons';
|
||||||
|
|
||||||
|
const elementsFromResponses = getCytoscapeElements([serviceMapResponse], '');
|
||||||
|
|
||||||
storiesOf('app/ServiceMap/Cytoscape', module).add(
|
storiesOf('app/ServiceMap/Cytoscape', module).add(
|
||||||
'example',
|
'example',
|
||||||
() => {
|
() => {
|
||||||
|
@ -49,11 +53,13 @@ storiesOf('app/ServiceMap/Cytoscape', module).add(
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
const height = 300;
|
const height = 300;
|
||||||
|
const width = 1340;
|
||||||
const serviceName = 'opbeans-python';
|
const serviceName = 'opbeans-python';
|
||||||
return (
|
return (
|
||||||
<Cytoscape
|
<Cytoscape
|
||||||
elements={elements}
|
elements={elements}
|
||||||
height={height}
|
height={height}
|
||||||
|
width={width}
|
||||||
serviceName={serviceName}
|
serviceName={serviceName}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -66,114 +72,137 @@ storiesOf('app/ServiceMap/Cytoscape', module).add(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
storiesOf('app/ServiceMap/Cytoscape', module).add(
|
storiesOf('app/ServiceMap/Cytoscape', module)
|
||||||
'node icons',
|
.add(
|
||||||
() => {
|
'node icons',
|
||||||
const cy = cytoscape();
|
() => {
|
||||||
const elements = [
|
const cy = cytoscape();
|
||||||
{ data: { id: 'default', label: 'default', type: undefined } },
|
const elements = [
|
||||||
{ data: { id: 'cache', label: 'cache', type: 'cache' } },
|
{ data: { id: 'default', label: 'default', type: undefined } },
|
||||||
{ data: { id: 'database', label: 'database', type: 'database' } },
|
{ data: { id: 'cache', label: 'cache', type: 'cache' } },
|
||||||
{ data: { id: 'external', label: 'external', type: 'external' } },
|
{ data: { id: 'database', label: 'database', type: 'database' } },
|
||||||
{ data: { id: 'messaging', label: 'messaging', type: 'messaging' } },
|
{ data: { id: 'external', label: 'external', type: 'external' } },
|
||||||
|
{ data: { id: 'messaging', label: 'messaging', type: 'messaging' } },
|
||||||
|
|
||||||
{
|
{
|
||||||
data: {
|
data: {
|
||||||
id: 'dotnet',
|
id: 'dotnet',
|
||||||
label: 'dotnet service',
|
label: 'dotnet service',
|
||||||
type: 'service',
|
type: 'service',
|
||||||
agentName: 'dotnet'
|
agentName: 'dotnet'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
data: {
|
data: {
|
||||||
id: 'go',
|
id: 'go',
|
||||||
label: 'go service',
|
label: 'go service',
|
||||||
type: 'service',
|
type: 'service',
|
||||||
agentName: 'go'
|
agentName: 'go'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
data: {
|
data: {
|
||||||
id: 'java',
|
id: 'java',
|
||||||
label: 'java service',
|
label: 'java service',
|
||||||
type: 'service',
|
type: 'service',
|
||||||
agentName: 'java'
|
agentName: 'java'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
data: {
|
data: {
|
||||||
id: 'js-base',
|
id: 'js-base',
|
||||||
label: 'js-base service',
|
label: 'js-base service',
|
||||||
type: 'service',
|
type: 'service',
|
||||||
agentName: 'js-base'
|
agentName: 'js-base'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
data: {
|
data: {
|
||||||
id: 'nodejs',
|
id: 'nodejs',
|
||||||
label: 'nodejs service',
|
label: 'nodejs service',
|
||||||
type: 'service',
|
type: 'service',
|
||||||
agentName: 'nodejs'
|
agentName: 'nodejs'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
data: {
|
data: {
|
||||||
id: 'php',
|
id: 'php',
|
||||||
label: 'php service',
|
label: 'php service',
|
||||||
type: 'service',
|
type: 'service',
|
||||||
agentName: 'php'
|
agentName: 'php'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
data: {
|
data: {
|
||||||
id: 'python',
|
id: 'python',
|
||||||
label: 'python service',
|
label: 'python service',
|
||||||
type: 'service',
|
type: 'service',
|
||||||
agentName: 'python'
|
agentName: 'python'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
data: {
|
data: {
|
||||||
id: 'ruby',
|
id: 'ruby',
|
||||||
label: 'ruby service',
|
label: 'ruby service',
|
||||||
type: 'service',
|
type: 'service',
|
||||||
agentName: 'ruby'
|
agentName: 'ruby'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
];
|
||||||
|
cy.add(elements);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EuiFlexGroup gutterSize="l" wrap={true}>
|
||||||
|
{cy.nodes().map(node => (
|
||||||
|
<EuiFlexItem key={node.data('id')}>
|
||||||
|
<EuiCard
|
||||||
|
description={
|
||||||
|
<pre>
|
||||||
|
agentName: {node.data('agentName') || 'undefined'}, type:{' '}
|
||||||
|
{node.data('type') || 'undefined'}
|
||||||
|
</pre>
|
||||||
|
}
|
||||||
|
icon={
|
||||||
|
<img
|
||||||
|
alt={node.data('label')}
|
||||||
|
src={iconForNode(node)}
|
||||||
|
height={80}
|
||||||
|
width={80}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
title={node.data('label')}
|
||||||
|
/>
|
||||||
|
</EuiFlexItem>
|
||||||
|
))}
|
||||||
|
</EuiFlexGroup>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
info: {
|
||||||
|
propTables: false,
|
||||||
|
source: false
|
||||||
}
|
}
|
||||||
];
|
|
||||||
cy.add(elements);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<EuiFlexGroup gutterSize="l" wrap={true}>
|
|
||||||
{cy.nodes().map(node => (
|
|
||||||
<EuiFlexItem key={node.data('id')}>
|
|
||||||
<EuiCard
|
|
||||||
description={
|
|
||||||
<pre>
|
|
||||||
agentName: {node.data('agentName') || 'undefined'}, type:{' '}
|
|
||||||
{node.data('type') || 'undefined'}
|
|
||||||
</pre>
|
|
||||||
}
|
|
||||||
icon={
|
|
||||||
<img
|
|
||||||
alt={node.data('label')}
|
|
||||||
src={iconForNode(node)}
|
|
||||||
height={80}
|
|
||||||
width={80}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
title={node.data('label')}
|
|
||||||
/>
|
|
||||||
</EuiFlexItem>
|
|
||||||
))}
|
|
||||||
</EuiFlexGroup>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
info: {
|
|
||||||
propTables: false,
|
|
||||||
source: false
|
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
);
|
.add(
|
||||||
|
'layout',
|
||||||
|
() => {
|
||||||
|
const height = 640;
|
||||||
|
const width = 1340;
|
||||||
|
const serviceName = undefined; // global service map
|
||||||
|
return (
|
||||||
|
<Cytoscape
|
||||||
|
elements={elementsFromResponses}
|
||||||
|
height={height}
|
||||||
|
width={width}
|
||||||
|
serviceName={serviceName}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
info: {
|
||||||
|
source: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.addParameters({ options: { showPanel: false } });
|
||||||
|
|
|
@ -10,13 +10,16 @@ import React, {
|
||||||
useRef,
|
useRef,
|
||||||
useEffect,
|
useEffect,
|
||||||
ReactNode,
|
ReactNode,
|
||||||
createContext
|
createContext,
|
||||||
|
useCallback
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import cytoscape from 'cytoscape';
|
import cytoscape from 'cytoscape';
|
||||||
import dagre from 'cytoscape-dagre';
|
import { isRumAgentName } from '../../../../../../../plugins/apm/common/agent_name';
|
||||||
import { cytoscapeOptions } from './cytoscapeOptions';
|
import {
|
||||||
|
cytoscapeOptions,
|
||||||
cytoscape.use(dagre);
|
nodeHeight,
|
||||||
|
animationOptions
|
||||||
|
} from './cytoscapeOptions';
|
||||||
|
|
||||||
export const CytoscapeContext = createContext<cytoscape.Core | undefined>(
|
export const CytoscapeContext = createContext<cytoscape.Core | undefined>(
|
||||||
undefined
|
undefined
|
||||||
|
@ -26,6 +29,7 @@ interface CytoscapeProps {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
elements: cytoscape.ElementDefinition[];
|
elements: cytoscape.ElementDefinition[];
|
||||||
height: number;
|
height: number;
|
||||||
|
width: number;
|
||||||
serviceName?: string;
|
serviceName?: string;
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
}
|
}
|
||||||
|
@ -52,19 +56,83 @@ function useCytoscape(options: cytoscape.CytoscapeOptions) {
|
||||||
return [ref, cy] as [React.MutableRefObject<any>, cytoscape.Core | undefined];
|
return [ref, cy] as [React.MutableRefObject<any>, cytoscape.Core | undefined];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getLayoutOptions(
|
||||||
|
selectedRoots: string[],
|
||||||
|
height: number,
|
||||||
|
width: number
|
||||||
|
): cytoscape.LayoutOptions {
|
||||||
|
return {
|
||||||
|
name: 'breadthfirst',
|
||||||
|
roots: selectedRoots,
|
||||||
|
fit: true,
|
||||||
|
padding: nodeHeight,
|
||||||
|
spacingFactor: 0.85,
|
||||||
|
animate: true,
|
||||||
|
animationEasing: animationOptions.easing,
|
||||||
|
animationDuration: animationOptions.duration,
|
||||||
|
// Rotate nodes from top -> bottom to display left -> right
|
||||||
|
// @ts-ignore
|
||||||
|
transform: (node: any, { x, y }: cytoscape.Position) => ({ x: y, y: -x }),
|
||||||
|
// swap width/height of boundingBox to compensation for the rotation
|
||||||
|
boundingBox: { x1: 0, y1: 0, w: height, h: width }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectRoots(elements: cytoscape.ElementDefinition[]): string[] {
|
||||||
|
const nodes = cytoscape({ elements }).nodes();
|
||||||
|
const unconnectedNodes = nodes.roots().intersection(nodes.leaves());
|
||||||
|
const rumNodes = nodes.filter(node => isRumAgentName(node.data('agentName')));
|
||||||
|
return rumNodes.union(unconnectedNodes).map(node => node.id());
|
||||||
|
}
|
||||||
|
|
||||||
export function Cytoscape({
|
export function Cytoscape({
|
||||||
children,
|
children,
|
||||||
elements,
|
elements,
|
||||||
height,
|
height,
|
||||||
|
width,
|
||||||
serviceName,
|
serviceName,
|
||||||
style
|
style
|
||||||
}: CytoscapeProps) {
|
}: CytoscapeProps) {
|
||||||
const [ref, cy] = useCytoscape({ ...cytoscapeOptions, elements });
|
const initialElements = elements.map(element => ({
|
||||||
|
...element,
|
||||||
|
// prevents flash of unstyled elements
|
||||||
|
classes: [element.classes, 'invisible'].join(' ').trim()
|
||||||
|
}));
|
||||||
|
|
||||||
|
const [ref, cy] = useCytoscape({
|
||||||
|
...cytoscapeOptions,
|
||||||
|
elements: initialElements
|
||||||
|
});
|
||||||
|
|
||||||
// Add the height to the div style. The height is a separate prop because it
|
// Add the height to the div style. The height is a separate prop because it
|
||||||
// is required and can trigger rendering when changed.
|
// is required and can trigger rendering when changed.
|
||||||
const divStyle = { ...style, height };
|
const divStyle = { ...style, height };
|
||||||
|
|
||||||
|
const dataHandler = useCallback<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');
|
||||||
|
cy.getElementById(serviceName).addClass('primary');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.cy.elements().length > 0) {
|
||||||
|
const selectedRoots = selectRoots(elements);
|
||||||
|
const layout = cy.layout(
|
||||||
|
getLayoutOptions(selectedRoots, height, width)
|
||||||
|
);
|
||||||
|
layout.one('layoutstop', () => {
|
||||||
|
// show elements after layout is applied
|
||||||
|
cy.elements().removeClass('invisible');
|
||||||
|
});
|
||||||
|
layout.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[cy, serviceName, elements, height, width]
|
||||||
|
);
|
||||||
|
|
||||||
// Trigger a custom "data" event when data changes
|
// Trigger a custom "data" event when data changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (cy) {
|
if (cy) {
|
||||||
|
@ -75,19 +143,6 @@ export function Cytoscape({
|
||||||
|
|
||||||
// Set up cytoscape event handlers
|
// Set up cytoscape event handlers
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
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');
|
|
||||||
cy.getElementById(serviceName).addClass('primary');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.cy.elements().length > 0) {
|
|
||||||
cy.layout(cytoscapeOptions.layout as cytoscape.LayoutOptions).run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const mouseoverHandler: cytoscape.EventHandler = event => {
|
const mouseoverHandler: cytoscape.EventHandler = event => {
|
||||||
event.target.addClass('hover');
|
event.target.addClass('hover');
|
||||||
event.target.connectedEdges().addClass('nodeHover');
|
event.target.connectedEdges().addClass('nodeHover');
|
||||||
|
@ -99,18 +154,23 @@ export function Cytoscape({
|
||||||
|
|
||||||
if (cy) {
|
if (cy) {
|
||||||
cy.on('data', dataHandler);
|
cy.on('data', dataHandler);
|
||||||
|
cy.ready(dataHandler);
|
||||||
cy.on('mouseover', 'edge, node', mouseoverHandler);
|
cy.on('mouseover', 'edge, node', mouseoverHandler);
|
||||||
cy.on('mouseout', 'edge, node', mouseoutHandler);
|
cy.on('mouseout', 'edge, node', mouseoutHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (cy) {
|
if (cy) {
|
||||||
cy.removeListener('data', undefined, dataHandler);
|
cy.removeListener(
|
||||||
|
'data',
|
||||||
|
undefined,
|
||||||
|
dataHandler as cytoscape.EventHandler
|
||||||
|
);
|
||||||
cy.removeListener('mouseover', 'edge, node', mouseoverHandler);
|
cy.removeListener('mouseover', 'edge, node', mouseoverHandler);
|
||||||
cy.removeListener('mouseout', 'edge, node', mouseoutHandler);
|
cy.removeListener('mouseout', 'edge, node', mouseoutHandler);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [cy, serviceName]);
|
}, [cy, dataHandler, serviceName]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CytoscapeContext.Provider value={cy}>
|
<CytoscapeContext.Provider value={cy}>
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -15,17 +15,6 @@ export const animationOptions: cytoscape.AnimationOptions = {
|
||||||
const lineColor = '#C5CCD7';
|
const lineColor = '#C5CCD7';
|
||||||
export const nodeHeight = parseInt(theme.avatarSizing.l.size, 10);
|
export const nodeHeight = parseInt(theme.avatarSizing.l.size, 10);
|
||||||
|
|
||||||
const layout = {
|
|
||||||
name: 'dagre',
|
|
||||||
nodeDimensionsIncludeLabels: true,
|
|
||||||
rankDir: 'LR',
|
|
||||||
animate: true,
|
|
||||||
animationEasing: animationOptions.easing,
|
|
||||||
animationDuration: animationOptions.duration,
|
|
||||||
fit: true,
|
|
||||||
padding: nodeHeight
|
|
||||||
};
|
|
||||||
|
|
||||||
function isService(el: cytoscape.NodeSingular) {
|
function isService(el: cytoscape.NodeSingular) {
|
||||||
return el.data('type') === 'service';
|
return el.data('type') === 'service';
|
||||||
}
|
}
|
||||||
|
@ -79,7 +68,9 @@ const style: cytoscape.Stylesheet[] = [
|
||||||
{
|
{
|
||||||
selector: 'edge',
|
selector: 'edge',
|
||||||
style: {
|
style: {
|
||||||
'curve-style': 'bezier',
|
'curve-style': 'taxi',
|
||||||
|
// @ts-ignore
|
||||||
|
'taxi-direction': 'rightward',
|
||||||
'line-color': lineColor,
|
'line-color': lineColor,
|
||||||
'overlay-opacity': 0,
|
'overlay-opacity': 0,
|
||||||
'target-arrow-color': lineColor,
|
'target-arrow-color': lineColor,
|
||||||
|
@ -103,13 +94,29 @@ const style: cytoscape.Stylesheet[] = [
|
||||||
'source-distance-from-node': theme.paddingSizes.xs,
|
'source-distance-from-node': theme.paddingSizes.xs,
|
||||||
'target-distance-from-node': theme.paddingSizes.xs
|
'target-distance-from-node': theme.paddingSizes.xs
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
// @ts-ignore
|
||||||
|
{
|
||||||
|
selector: '.invisible',
|
||||||
|
style: { visibility: 'hidden' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'edge.nodeHover',
|
||||||
|
style: {
|
||||||
|
width: 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'node.hover',
|
||||||
|
style: {
|
||||||
|
'border-width': 4
|
||||||
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
export const cytoscapeOptions: cytoscape.CytoscapeOptions = {
|
export const cytoscapeOptions: cytoscape.CytoscapeOptions = {
|
||||||
autoungrabify: true,
|
autoungrabify: true,
|
||||||
boxSelectionEnabled: false,
|
boxSelectionEnabled: false,
|
||||||
layout,
|
|
||||||
maxZoom: 3,
|
maxZoom: 3,
|
||||||
minZoom: 0.2,
|
minZoom: 0.2,
|
||||||
style
|
style
|
||||||
|
|
|
@ -32,7 +32,7 @@ import { Cytoscape } from './Cytoscape';
|
||||||
import { getCytoscapeElements } from './get_cytoscape_elements';
|
import { getCytoscapeElements } from './get_cytoscape_elements';
|
||||||
import { PlatinumLicensePrompt } from './PlatinumLicensePrompt';
|
import { PlatinumLicensePrompt } from './PlatinumLicensePrompt';
|
||||||
import { Popover } from './Popover';
|
import { Popover } from './Popover';
|
||||||
import { useRefHeight } from './useRefHeight';
|
import { useRefDimensions } from './useRefDimensions';
|
||||||
|
|
||||||
interface ServiceMapProps {
|
interface ServiceMapProps {
|
||||||
serviceName?: string;
|
serviceName?: string;
|
||||||
|
@ -196,7 +196,7 @@ export function ServiceMap({ serviceName }: ServiceMapProps) {
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [elements]);
|
}, [elements]);
|
||||||
|
|
||||||
const [wrapperRef, height] = useRefHeight();
|
const { ref: wrapperRef, width, height } = useRefDimensions();
|
||||||
|
|
||||||
if (!license) {
|
if (!license) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -211,6 +211,7 @@ export function ServiceMap({ serviceName }: ServiceMapProps) {
|
||||||
elements={renderedElements.current}
|
elements={renderedElements.current}
|
||||||
serviceName={serviceName}
|
serviceName={serviceName}
|
||||||
height={height}
|
height={height}
|
||||||
|
width={width}
|
||||||
style={cytoscapeDivStyle}
|
style={cytoscapeDivStyle}
|
||||||
>
|
>
|
||||||
<Controls />
|
<Controls />
|
||||||
|
|
|
@ -3,18 +3,19 @@
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
import { MutableRefObject, useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import { useWindowSize } from 'react-use';
|
import { useWindowSize } from 'react-use';
|
||||||
|
|
||||||
export function useRefHeight(): [
|
export function useRefDimensions() {
|
||||||
MutableRefObject<HTMLDivElement | null>,
|
|
||||||
number
|
|
||||||
] {
|
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const windowHeight = useWindowSize().height;
|
const windowHeight = useWindowSize().height;
|
||||||
const topOffset = ref.current?.getBoundingClientRect()?.top ?? 0;
|
|
||||||
|
|
||||||
const height = ref.current ? windowHeight - topOffset : 0;
|
if (!ref.current) {
|
||||||
|
return { ref, width: 0, height: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
return [ref, height];
|
const { top, width } = ref.current.getBoundingClientRect();
|
||||||
|
const height = windowHeight - top;
|
||||||
|
|
||||||
|
return { ref, width, height };
|
||||||
}
|
}
|
|
@ -222,7 +222,6 @@
|
||||||
"copy-to-clipboard": "^3.0.8",
|
"copy-to-clipboard": "^3.0.8",
|
||||||
"cronstrue": "^1.51.0",
|
"cronstrue": "^1.51.0",
|
||||||
"cytoscape": "^3.10.0",
|
"cytoscape": "^3.10.0",
|
||||||
"cytoscape-dagre": "^2.2.2",
|
|
||||||
"d3": "3.5.17",
|
"d3": "3.5.17",
|
||||||
"d3-scale": "1.0.7",
|
"d3-scale": "1.0.7",
|
||||||
"dedent": "^0.7.0",
|
"dedent": "^0.7.0",
|
||||||
|
|
|
@ -1,7 +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;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare module 'cytoscape-dagre';
|
|
22
yarn.lock
22
yarn.lock
|
@ -10695,13 +10695,6 @@ cypress@^4.0.2:
|
||||||
url "0.11.0"
|
url "0.11.0"
|
||||||
yauzl "2.10.0"
|
yauzl "2.10.0"
|
||||||
|
|
||||||
cytoscape-dagre@^2.2.2:
|
|
||||||
version "2.2.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/cytoscape-dagre/-/cytoscape-dagre-2.2.2.tgz#5f32a85c0ba835f167efee531df9e89ac58ff411"
|
|
||||||
integrity sha512-zsg36qNwua/L2stJSWkcbSDcvW3E6VZf6KRe6aLnQJxuXuz89tMqI5EVYVKEcNBgzTEzFMFv0PE3T0nD4m6VDw==
|
|
||||||
dependencies:
|
|
||||||
dagre "^0.8.2"
|
|
||||||
|
|
||||||
cytoscape@^3.10.0:
|
cytoscape@^3.10.0:
|
||||||
version "3.10.0"
|
version "3.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/cytoscape/-/cytoscape-3.10.0.tgz#3b462e0d35121ecd2d2702f470915fd6dae01777"
|
resolved "https://registry.yarnpkg.com/cytoscape/-/cytoscape-3.10.0.tgz#3b462e0d35121ecd2d2702f470915fd6dae01777"
|
||||||
|
@ -10967,14 +10960,6 @@ d@1:
|
||||||
dependencies:
|
dependencies:
|
||||||
es5-ext "^0.10.9"
|
es5-ext "^0.10.9"
|
||||||
|
|
||||||
dagre@^0.8.2:
|
|
||||||
version "0.8.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/dagre/-/dagre-0.8.4.tgz#26b9fb8f7bdc60c6110a0458c375261836786061"
|
|
||||||
integrity sha512-Dj0csFDrWYKdavwROb9FccHfTC4fJbyF/oJdL9LNZJ8WUvl968P6PAKEriGqfbdArVJEmmfA+UyumgWEwcHU6A==
|
|
||||||
dependencies:
|
|
||||||
graphlib "^2.1.7"
|
|
||||||
lodash "^4.17.4"
|
|
||||||
|
|
||||||
damerau-levenshtein@^1.0.4:
|
damerau-levenshtein@^1.0.4:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz#03191c432cb6eea168bb77f3a55ffdccb8978514"
|
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz#03191c432cb6eea168bb77f3a55ffdccb8978514"
|
||||||
|
@ -15304,13 +15289,6 @@ graceful-fs@~1.1:
|
||||||
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
|
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
|
||||||
integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=
|
integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=
|
||||||
|
|
||||||
graphlib@^2.1.7:
|
|
||||||
version "2.1.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.7.tgz#b6a69f9f44bd9de3963ce6804a2fc9e73d86aecc"
|
|
||||||
integrity sha512-TyI9jIy2J4j0qgPmOOrHTCtpPqJGN/aurBwc6ZT+bRii+di1I+Wv3obRhVrmBEXet+qkMaEX67dXrwsd3QQM6w==
|
|
||||||
dependencies:
|
|
||||||
lodash "^4.17.5"
|
|
||||||
|
|
||||||
graphql-anywhere@^4.1.0-alpha.0:
|
graphql-anywhere@^4.1.0-alpha.0:
|
||||||
version "4.1.16"
|
version "4.1.16"
|
||||||
resolved "https://registry.yarnpkg.com/graphql-anywhere/-/graphql-anywhere-4.1.16.tgz#82bb59643e30183cfb7b485ed4262a7b39d8a6c1"
|
resolved "https://registry.yarnpkg.com/graphql-anywhere/-/graphql-anywhere-4.1.16.tgz#82bb59643e30183cfb7b485ed4262a7b39d8a6c1"
|
||||||
|
|
Loading…
Reference in a new issue