Skip to content

Commit

Permalink
Merge pull request #3938 from jeff-phillips-18/collapse-groups
Browse files Browse the repository at this point in the history
Expand/collapse Topology Application Groups
  • Loading branch information
openshift-merge-robot committed Jan 25, 2020
2 parents 9db5868 + 864b7db commit 26a0fc1
Show file tree
Hide file tree
Showing 59 changed files with 2,548 additions and 1,405 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ const Popper: React.FC<PopperProps> = ({
returnFocus,
}) => {
const controlled = typeof open === 'boolean';
const openProp = controlled ? open : true;
const openProp = controlled ? open || false : true;
const nodeRef = React.useRef<Element>();
const popperRef = React.useRef<PopperJS>(null);
const popperRefs = useCombineRefs<PopperJS>(popperRef, popperRefIn);
Expand All @@ -102,7 +102,9 @@ const Popper: React.FC<PopperProps> = ({
(newOpen: boolean) => {
if (returnFocus && newOpen !== isOpen) {
if (newOpen) {
focusRef.current = document.activeElement;
if (document.activeElement) {
focusRef.current = document.activeElement;
}
} else if (focusRef.current instanceof HTMLElement && focusRef.current.ownerDocument) {
focusRef.current.focus();
}
Expand Down
64 changes: 25 additions & 39 deletions frontend/packages/dev-console/src/components/svg/SvgBoxedText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import {
useCombineRefs,
createSvgIdUrl,
} from '@console/topology';
import { getImageForIconClass } from '@console/internal/components/catalog/catalog-item-icon';
import SvgResourceIcon from '../topology/components/nodes/ResourceIcon';
import SvgCircledIcon from './SvgCircledIcon';
import SvgDropShadowFilter from './SvgDropShadowFilter';

export interface SvgBoxedTextProps {
Expand All @@ -19,18 +19,16 @@ export interface SvgBoxedTextProps {
y?: number;
cornerRadius?: number;
kind?: string;
typeIconClass?: string;
typeIconPadding?: number;
truncate?: number;
dragRef?: WithDndDragProps['dndDragRef'];
icon?: string;
// TODO remove with 2.0
onMouseEnter?: React.MouseEventHandler<SVGGElement>;
onMouseLeave?: React.MouseEventHandler<SVGGElement>;
}

const FILTER_ID = 'SvgBoxedTextDropShadowFilterId';
const iconFilterID = 'SVGBoxedTextRectIconFilter';
const iconSize = 36;
const iconPadding = 3;

const truncateEnd = (text: string = '', length: number): string => {
if (text.length <= length) {
Expand All @@ -51,29 +49,30 @@ const SvgBoxedText: React.FC<SvgBoxedTextProps> = ({
x = 0,
y = 0,
kind,
typeIconClass,
typeIconPadding = 4,
onMouseEnter,
onMouseLeave,
truncate,
dragRef,
icon,
...other
}) => {
const [labelHover, labelHoverRef] = useHover();
const [textSize, textRef] = useSize([children, className, labelHover]);
const [badgeSize, badgeRef] = useSize([kind]);
const [labelSize, labelRef] = useSize([children, textSize, badgeSize]);
const iconSpace = kind && badgeSize ? badgeSize.width + paddingX : 0;
const labelSizeWidth = icon ? paddingX * 2 + iconSpace + iconSize / 2 : paddingX * 2 + iconSpace;
const [iconSize, iconRef] = useSize([kind]);
const iconSpace = kind && iconSize ? iconSize.width + paddingX : 0;
const refs = useCombineRefs(dragRef, typeof truncate === 'number' ? labelHoverRef : undefined);
const typedIconWidth = typeIconClass && iconSize ? iconSize.height + typeIconPadding * 2 : 0;
const midX = typedIconWidth ? x + typedIconWidth / 2 : x;

return (
<g className={className} ref={refs}>
<SvgDropShadowFilter id={FILTER_ID} />
{textSize && (
<rect
ref={labelRef}
filter={createSvgIdUrl(FILTER_ID)}
x={x - paddingX - textSize.width / 2 - iconSpace / 2}
width={textSize.width + labelSizeWidth}
x={midX - paddingX - textSize.width / 2 - iconSpace / 2 - (typeIconClass ? 10 : 0)}
width={textSize.width + paddingX * 2 + iconSpace + (typeIconClass ? 10 : 0)}
y={y - paddingY - textSize.height / 2}
height={textSize.height + paddingY * 2}
rx={cornerRadius}
Expand All @@ -82,17 +81,26 @@ const SvgBoxedText: React.FC<SvgBoxedTextProps> = ({
)}
{textSize && kind && (
<SvgResourceIcon
ref={badgeRef}
x={x - textSize.width / 2 - paddingX / 2}
ref={iconRef}
x={midX - textSize.width / 2 - paddingX / 2}
y={y}
kind={kind}
/>
)}

{textSize && iconSize && typeIconClass && (
<SvgCircledIcon
x={midX - (textSize.width + iconSpace) / 2 - paddingX}
y={y - iconSize.height + paddingY * 1.5}
width={iconSize.height + paddingY}
height={iconSize.height + paddingY}
iconClass={typeIconClass}
padding={typeIconPadding}
/>
)}
<text
{...other}
ref={textRef}
x={x + iconSpace / 2}
x={midX + iconSpace / 2}
y={y}
textAnchor="middle"
dy="0.35em"
Expand All @@ -105,28 +113,6 @@ const SvgBoxedText: React.FC<SvgBoxedTextProps> = ({
: truncateEnd(children, truncate)
: children}
</text>
{icon && textSize && badgeSize && labelSize && (
<>
<SvgDropShadowFilter id={iconFilterID} />
<rect
x={x + labelSize.width / 2 + paddingX - iconSize / 2}
y={y}
width={iconSize}
height={iconSize}
fill="#fff"
rx={cornerRadius}
ry={cornerRadius}
filter={createSvgIdUrl(iconFilterID)}
/>
<image
x={x + labelSize.width / 2 + paddingX - iconSize / 2 + iconPadding}
y={y + iconPadding}
width={30}
height={30}
xlinkHref={getImageForIconClass(`icon-${icon}`)}
/>
</>
)}
</g>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import * as React from 'react';
import { useSize, createSvgIdUrl } from '@console/topology';
import { getImageForIconClass } from '@console/internal/components/catalog/catalog-item-icon';
import SvgDropShadowFilter from './SvgDropShadowFilter';

interface SvgTypedIconProps {
className?: string;
x: number;
y: number;
width: number;
height: number;
padding?: number;
iconClass: string;
}

const FILTER_ID = 'SvgTypedIconDropShadowFilterId';

export const CircledIcon: React.FC<SvgTypedIconProps> = (
{ className, x, y, width, height, iconClass, padding = 4 },
circleRef,
) => {
const [typedIconSize, typedIconRef] = useSize([]);

let iconWidth = 0;
let iconHeight = 0;

if (typedIconSize) {
({ width: iconWidth, height: iconHeight } = typedIconSize);
}

return (
<g className={className}>
<SvgDropShadowFilter id={FILTER_ID} />
<circle
ref={circleRef}
filter={createSvgIdUrl(FILTER_ID)}
cx={x - iconWidth / 2}
cy={y + iconHeight / 2}
r={iconWidth / 2 + padding}
/>
<g ref={typedIconRef}>
<image
x={x - iconWidth}
y={y}
width={width}
height={height}
xlinkHref={getImageForIconClass(iconClass)}
filter={createSvgIdUrl(FILTER_ID)}
/>
</g>
</g>
);
};

export default React.forwardRef(CircledIcon);
25 changes: 16 additions & 9 deletions frontend/packages/dev-console/src/components/topology/Topology.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from 'react';
import * as classNames from 'classnames';
import * as _ from 'lodash';
import { action } from 'mobx';
import { connect } from 'react-redux';
import { Button, ToolbarItem, Tooltip } from '@patternfly/react-core';
import {
TopologyView,
Expand All @@ -12,12 +12,12 @@ import {
import {
Visualization,
VisualizationSurface,
GraphElement,
isNode,
Model,
SELECTION_EVENT,
SelectionEventListener,
} from '@console/topology';
import { RootState } from '@console/internal/redux';
import { TopologyIcon } from '@patternfly/react-icons';
import TopologySideBar from './TopologySideBar';
import { TopologyDataModel, TopologyDataObject } from './topology-types';
Expand All @@ -28,8 +28,12 @@ import { layoutFactory, COLA_LAYOUT, COLA_FORCE_LAYOUT } from './layouts/layoutF
import ComponentFactory from './componentFactory';
import { TYPE_APPLICATION_GROUP } from './const';
import TopologyFilterBar from './filters/TopologyFilterBar';
import { getTopologyFilters, TopologyFilters } from './filters/filter-utils';

export interface TopologyProps {
interface StateProps {
filters: TopologyFilters;
}
export interface TopologyProps extends StateProps {
data: TopologyDataModel;
serviceBinding: boolean;
}
Expand All @@ -42,7 +46,7 @@ const graphModel: Model = {
},
};

const Topology: React.FC<TopologyProps> = ({ data, serviceBinding }) => {
const Topology: React.FC<TopologyProps> = ({ data, serviceBinding, filters }) => {
const visRef = React.useRef<Visualization | null>(null);
const componentFactoryRef = React.useRef<ComponentFactory | null>(null);
const [layout, setLayout] = React.useState<string>(graphModel.graph.layout);
Expand Down Expand Up @@ -73,7 +77,7 @@ const Topology: React.FC<TopologyProps> = ({ data, serviceBinding }) => {
}, [serviceBinding]);

React.useEffect(() => {
const newModel = topologyModelFromDataModel(data);
const newModel = topologyModelFromDataModel(data, filters);
visRef.current.fromModel(newModel);
setModel(newModel);
if (selectedIds.length && !visRef.current.getElementById(selectedIds[0])) {
Expand Down Expand Up @@ -181,9 +185,7 @@ const Topology: React.FC<TopologyProps> = ({ data, serviceBinding }) => {
application={{
id: selectedEntity.getId(),
name: selectedEntity.getLabel(),
resources: _.map(selectedEntity.getChildren(), (node: GraphElement) =>
node.getData(),
),
resources: selectedEntity.getData().groupResources,
}}
/>
);
Expand Down Expand Up @@ -220,4 +222,9 @@ const Topology: React.FC<TopologyProps> = ({ data, serviceBinding }) => {
);
};

export default Topology;
const TopologyStateToProps = (state: RootState): StateProps => {
const filters = getTopologyFilters(state);
return { filters };
};

export default connect(TopologyStateToProps)(Topology);
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ describe('TopologyDataController', () => {
knativeServices: true,
appGrouping: true,
operatorGrouping: true,
helmGrouping: true,
},
searchQuery: null,
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
withRemoveConnector,
withContextMenu,
} from '@console/topology';
import ApplicationGroup from './components/nodes/ApplicationGroup';
import Application from './components/nodes/Application';
import ConnectsTo from './components/edges/ConnectsTo';
import EventSource from './components/nodes/EventSource';
import EventSourceLink from './components/edges/EventSourceLink';
Expand Down Expand Up @@ -44,6 +44,7 @@ import {
TYPE_CONNECTS_TO,
TYPE_APPLICATION_GROUP,
TYPE_EVENT_SOURCE_LINK,
TYPE_AGGREGATE_EDGE,
TYPE_KNATIVE_SERVICE,
TYPE_REVISION_TRAFFIC,
TYPE_SERVICE_BINDING,
Expand All @@ -58,6 +59,7 @@ import RevisionNode from './components/nodes/RevisionNode';
import { createConnection, createSinkConnection } from './components/createConnection';
import { withEditReviewAccess } from './withEditReviewAccess';
import HelmRelease from './components/groups/HelmRelease';
import AggregateEdge from './components/edges/AggregateEdge';

type NodeProps = {
element: Node;
Expand Down Expand Up @@ -113,7 +115,7 @@ class ComponentFactory {
groupContextMenu,
document.getElementById('modal-container'),
'odc-topology-context-menu',
)(ApplicationGroup),
)(Application),
),
);
case TYPE_KNATIVE_SERVICE:
Expand Down Expand Up @@ -206,6 +208,8 @@ class ComponentFactory {
)(withRemoveConnector(removeConnectorCallback)(ConnectsTo));
case TYPE_SERVICE_BINDING:
return withRemoveConnector(removeConnectorCallback)(ServiceBinding);
case TYPE_AGGREGATE_EDGE:
return AggregateEdge;
default:
switch (kind) {
case ModelKind.graph:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@ import { AbstractAnchor, getEllipseAnchorPoint, Node, Point } from '@console/top
export default class RevisionTrafficTargetAnchor extends AbstractAnchor {
private radius: number;

private offset: number;
private radiusOffset: number;

constructor(node: Node, radius: number) {
super(node);
this.radius = radius;
// TODO align sizing with WorkloadNode
this.offset = radius * 0.7;
this.radiusOffset = radius * 0.7;
}

getLocation(reference: Point): Point {
const bounds = this.getOwner().getBounds();
if (this.radius) {
// location is edge of decorator
const center = new Point(bounds.right() - this.offset, bounds.y + this.offset);
const center = new Point(bounds.right() - this.radiusOffset, bounds.y + this.radiusOffset);
const size = this.radius * 2;
return getEllipseAnchorPoint(center, size, size, reference);
}
Expand All @@ -29,7 +29,7 @@ export default class RevisionTrafficTargetAnchor extends AbstractAnchor {
const bounds = this.getOwner().getBounds();
if (this.radius) {
// reference point is center of decorator
return new Point(bounds.right() - this.offset, bounds.y + this.offset);
return new Point(bounds.right() - this.radiusOffset, bounds.y + this.radiusOffset);
}
// reference point is center of node
return bounds.getCenter();
Expand Down

0 comments on commit 26a0fc1

Please sign in to comment.