Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { Handle, Position } from '@xyflow/react';
import type { EntityNodeViewModel, NodeProps } from '../types';
import {
NodeShapeContainer,
NodeLabel,
NodeShapeOnHoverSvg,
NodeShapeSvg,
NodeIcon,
Expand All @@ -20,6 +19,7 @@ import {
} from './styles';
import { DiamondHoverShape, DiamondShape } from './shapes/diamond_shape';
import { NodeExpandButton } from './node_expand_button';
import { Label } from './label';

const NODE_WIDTH = 99;
const NODE_HEIGHT = 98;
Expand Down Expand Up @@ -81,7 +81,7 @@ export const DiamondNode: React.FC<NodeProps> = memo((props: NodeProps) => {
style={HandleStyleOverride}
/>
</NodeShapeContainer>
<NodeLabel>{label ? label : id}</NodeLabel>
<Label text={label ? label : id} />
</>
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,18 @@ import { useEuiBackgroundColor, useEuiTheme } from '@elastic/eui';
import { Handle, Position } from '@xyflow/react';
import {
NodeShapeContainer,
NodeLabel,
NodeShapeOnHoverSvg,
NodeShapeSvg,
NodeIcon,
NodeButton,
HandleStyleOverride,
NODE_WIDTH,
NODE_HEIGHT,
} from './styles';
import type { EntityNodeViewModel, NodeProps } from '../types';
import { EllipseHoverShape, EllipseShape } from './shapes/ellipse_shape';
import { NodeExpandButton } from './node_expand_button';

const NODE_WIDTH = 90;
const NODE_HEIGHT = 90;
import { Label } from './label';

// eslint-disable-next-line react/display-name
export const EllipseNode: React.FC<NodeProps> = memo((props: NodeProps) => {
Expand Down Expand Up @@ -81,7 +80,7 @@ export const EllipseNode: React.FC<NodeProps> = memo((props: NodeProps) => {
style={HandleStyleOverride}
/>
</NodeShapeContainer>
<NodeLabel>{label ? label : id}</NodeLabel>
<Label text={label ? label : id} />
</>
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { useEuiBackgroundColor, useEuiTheme } from '@elastic/eui';
import { Handle, Position } from '@xyflow/react';
import {
NodeShapeContainer,
NodeLabel,
NodeShapeOnHoverSvg,
NodeShapeSvg,
NodeIcon,
Expand All @@ -20,6 +19,7 @@ import {
import type { EntityNodeViewModel, NodeProps } from '../types';
import { HexagonHoverShape, HexagonShape } from './shapes/hexagon_shape';
import { NodeExpandButton } from './node_expand_button';
import { Label } from './label';

const NODE_WIDTH = 87;
const NODE_HEIGHT = 96;
Expand Down Expand Up @@ -81,7 +81,7 @@ export const HexagonNode: React.FC<NodeProps> = memo((props: NodeProps) => {
style={HandleStyleOverride}
/>
</NodeShapeContainer>
<NodeLabel>{label ? label : id}</NodeLabel>
<Label text={label ? label : id} />
</>
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* 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 { ThemeProvider } from '@emotion/react';
import { pick } from 'lodash';
import { ReactFlow, Controls, Background } from '@xyflow/react';
import { Story } from '@storybook/react';
import { NodeViewModel } from '../types';
import { HexagonNode, PentagonNode, EllipseNode, RectangleNode, DiamondNode, LabelNode } from '.';

import '@xyflow/react/dist/style.css';

export default {
title: 'Components/Graph Components/Labels',
description: 'CDR - Graph visualization',
argTypes: {
color: {
options: ['primary', 'danger', 'warning'],
control: { type: 'radio' },
},
shape: {
options: ['ellipse', 'hexagon', 'pentagon', 'rectangle', 'diamond', 'label'],
control: { type: 'radio' },
},
expandButtonClick: { action: 'expandButtonClick' },
},
};

const nodeTypes = {
hexagon: HexagonNode,
pentagon: PentagonNode,
ellipse: EllipseNode,
rectangle: RectangleNode,
diamond: DiamondNode,
label: LabelNode,
};

const Template: Story<NodeViewModel> = (args: NodeViewModel) => (
<ThemeProvider theme={{ darkMode: false }}>
<ReactFlow
fitView
attributionPosition={undefined}
nodeTypes={nodeTypes}
nodes={[
{
id: args.id,
type: args.shape,
data: pick(args, ['id', 'label', 'color', 'icon', 'interactive', 'expandButtonClick']),
position: { x: 0, y: 0 },
},
]}
>
<Controls />
<Background />
</ReactFlow>
</ThemeProvider>
);

export const ShortLabel = Template.bind({});

ShortLabel.args = {
id: 'siem-windows',
label: '',
color: 'primary',
shape: 'hexagon',
icon: 'okta',
interactive: true,
};

export const ArnLabel = Template.bind({});

ArnLabel.args = {
id: 'siem-windows',
label: 'arn:aws:iam::1234567890:user/lorem-ipsumdol-sitamet-user-1234',
color: 'primary',
shape: 'hexagon',
icon: 'okta',
interactive: true,
};

export const DashedLabel = Template.bind({});

DashedLabel.args = {
id: 'siem-windows',
label: 'lore-ipsumdol-sitameta-consectetu-adipis342',
color: 'primary',
shape: 'hexagon',
icon: 'okta',
interactive: true,
};

export const NoSpacesLabel = Template.bind({});

NoSpacesLabel.args = {
id: 'siem-windows',
label: 'LoremIpsumDolorSitAmetConsectetur123',
color: 'primary',
shape: 'hexagon',
icon: 'okta',
interactive: true,
};

export const NoSpacesAllLoweredLabel = Template.bind({});

NoSpacesAllLoweredLabel.args = {
id: 'siem-windows',
label: 'loremipsumdolorsitametconsectetur123',
color: 'primary',
shape: 'hexagon',
icon: 'okta',
interactive: true,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* 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, { memo, type PropsWithChildren } from 'react';
import { EuiText, EuiTextTruncate, EuiToolTip } from '@elastic/eui';
import { css } from '@emotion/react';
import styled from '@emotion/styled';
import { NODE_LABEL_WIDTH, NODE_WIDTH } from './styles';

const WORD_BOUNDARIES_REGEX = /\b/;
const FORCE_BREAK_REGEX = /(.{10})/;

/**
* A component that renders an element with breaking opportunities (`<wbr>`s)
* spliced into text children at word boundaries.
* Copied from x-pack/plugins/security_solution/public/resolver/view/generated_text.tsx
*/
const GeneratedText = memo<PropsWithChildren<{}>>(function ({ children }) {
return <>{processedValue()}</>;

function processedValue() {
return React.Children.map(children, (child) => {
if (typeof child === 'string') {
let valueSplitByWordBoundaries = child.split(WORD_BOUNDARIES_REGEX);

if (valueSplitByWordBoundaries.length < 2) {
valueSplitByWordBoundaries = child.split(FORCE_BREAK_REGEX);

if (valueSplitByWordBoundaries.length < 2) {
return valueSplitByWordBoundaries[0];
}
}

return [
valueSplitByWordBoundaries[0],
...valueSplitByWordBoundaries
.splice(1)
.reduce((generatedTextMemo: Array<string | JSX.Element>, value) => {
if (
generatedTextMemo.length > 0 &&
typeof generatedTextMemo[generatedTextMemo.length - 1] === 'object'
) {
return [...generatedTextMemo, value];
}
return [...generatedTextMemo, value, <wbr />];
}, []),
];
} else {
return child;
}
});
}
});

GeneratedText.displayName = 'GeneratedText';

export interface LabelProps {
text?: string;
}

const LabelComponent: React.FC<LabelProps> = ({ text = '' }: LabelProps) => {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering if I should name it Label or NodeLabel.
There's already a node which represents a label and is named LabelNode.

Therefore I wasn't sure.
I'm open for suggestions for better names either for LabelNode or for the Label component

const [isTruncated, setIsTruncated] = React.useState(false);

return (
<EuiText
size="xs"
textAlign="center"
css={css`
width: ${NODE_LABEL_WIDTH}px;
margin-left: ${-(NODE_LABEL_WIDTH - NODE_WIDTH) / 2}px;
overflow: hidden;
text-overflow: ellipsis;
max-height: 32px;
`}
>
<EuiToolTip content={isTruncated ? text : ''} position="bottom">
<EuiTextTruncate
truncation="end"
truncationOffset={20}
text={text}
width={NODE_LABEL_WIDTH * 1.5}
>
{(truncatedText) => (
<>
{setIsTruncated(truncatedText.length !== text.length)}
{<GeneratedText>{truncatedText}</GeneratedText>}
</>
)}
</EuiTextTruncate>
</EuiToolTip>
</EuiText>
);
};

export const Label = styled(LabelComponent)`
width: ${NODE_LABEL_WIDTH}px;
margin-left: ${-(NODE_LABEL_WIDTH - NODE_WIDTH) / 2}px;
text-overflow: ellipsis;
overflow: hidden;
`;
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import styled from '@emotion/styled';
import { Handle, Position } from '@xyflow/react';
import {
NodeShapeContainer,
NodeLabel,
NodeShapeOnHoverSvg,
NodeShapeSvg,
NodeIcon,
Expand All @@ -21,6 +20,7 @@ import {
import type { EntityNodeViewModel, NodeProps } from '../types';
import { PentagonHoverShape, PentagonShape } from './shapes/pentagon_shape';
import { NodeExpandButton } from './node_expand_button';
import { Label } from './label';

const PentagonShapeOnHover = styled(NodeShapeOnHoverSvg)`
transform: translate(-50%, -51.5%);
Expand Down Expand Up @@ -86,7 +86,7 @@ export const PentagonNode: React.FC<NodeProps> = memo((props: NodeProps) => {
style={HandleStyleOverride}
/>
</NodeShapeContainer>
<NodeLabel>{label ? label : id}</NodeLabel>
<Label text={label ? label : id} />
</>
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { useEuiBackgroundColor, useEuiTheme } from '@elastic/eui';
import { Handle, Position } from '@xyflow/react';
import {
NodeShapeContainer,
NodeLabel,
NodeShapeOnHoverSvg,
NodeShapeSvg,
NodeIcon,
Expand All @@ -20,6 +19,7 @@ import {
import type { EntityNodeViewModel, NodeProps } from '../types';
import { RectangleHoverShape, RectangleShape } from './shapes/rectangle_shape';
import { NodeExpandButton } from './node_expand_button';
import { Label } from './label';

const NODE_WIDTH = 81;
const NODE_HEIGHT = 80;
Expand Down Expand Up @@ -81,7 +81,7 @@ export const RectangleNode: React.FC<NodeProps> = memo((props: NodeProps) => {
style={HandleStyleOverride}
/>
</NodeShapeContainer>
<NodeLabel>{label ? label : id}</NodeLabel>
<Label text={label ? label : id} />
</>
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const LABEL_PADDING_X = 15;
export const LABEL_BORDER_WIDTH = 1;
export const NODE_WIDTH = 90;
export const NODE_HEIGHT = 90;
const NODE_LABEL_WIDTH = 120;
export const NODE_LABEL_WIDTH = 160;

export const LabelNodeContainer = styled.div`
text-wrap: nowrap;
Expand Down Expand Up @@ -185,19 +185,6 @@ export const NodeIcon = ({ icon, color, x, y }: NodeIconProps) => {
);
};

export const NodeLabel = styled(EuiText)`
width: ${NODE_LABEL_WIDTH}px;
margin-left: ${-(NODE_LABEL_WIDTH - NODE_WIDTH) / 2}px;
text-overflow: ellipsis;
// white-space: nowrap;
overflow: hidden;
`;

NodeLabel.defaultProps = {
size: 'xs',
textAlign: 'center',
};

export const ExpandButtonSize = 18;

export const RoundEuiButtonIcon = styled(EuiButtonIcon)`
Expand Down