Skip to content

Commit

Permalink
artifacts logic
Browse files Browse the repository at this point in the history
remove unused params

translate artifact node

add Jeff fixes

expose monitoring icon for metric artifacts

Artifact node fixes

add groups logic to render collapsed group node styling

remove groups logic, fix icon color

remove PipelineTaskGroup

remove unused TaskGroupTargetAnchor

fix artifact node dimensions
  • Loading branch information
jenny-s51 committed Apr 4, 2024
1 parent 57c0e63 commit c99808e
Show file tree
Hide file tree
Showing 14 changed files with 296 additions and 40 deletions.
9 changes: 4 additions & 5 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
"@patternfly/react-styles": "^5.2.1",
"@patternfly/react-table": "^5.2.1",
"@patternfly/react-tokens": "^5.2.1",
"@patternfly/react-topology": "^5.1.0",
"@patternfly/react-topology": "^5.3.0-prerelease.5",
"@patternfly/react-virtualized-extension": "^5.0.0",
"@types/classnames": "^2.3.1",
"axios": "^1.6.4",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PipelineRunKFv2, PipelineSpecVariable } from '~/concepts/pipelines/kfTypes';
import { PipelineRunKFv2, PipelineSpecVariable, TaskKF } from '~/concepts/pipelines/kfTypes';

Check failure on line 1 in frontend/src/concepts/pipelines/topology/usePipelineTaskTopology.ts

View workflow job for this annotation

GitHub Actions / Tests (18.x)

'TaskKF' is defined but never used
import { createNode } from '~/concepts/topology';
import { PipelineNodeModelExpanded } from '~/concepts/topology/types';
import {
Expand All @@ -11,6 +11,7 @@ import {
translateStatusForNode,
} from './parseUtils';
import { KubeFlowTaskTopology } from './pipelineTaskTypes';
import { createArtifactNode } from '~/concepts/topology/utils';

Check failure on line 14 in frontend/src/concepts/pipelines/topology/usePipelineTaskTopology.ts

View workflow job for this annotation

GitHub Actions / Tests (18.x)

`~/concepts/topology/utils` import should occur before import of `./parseUtils`

const EMPTY_STATE: KubeFlowTaskTopology = { taskMap: {}, nodes: [] };

Expand Down Expand Up @@ -42,6 +43,7 @@ export const usePipelineTaskTopology = (
const component = components[componentRef];
const artifactsInComponent = componentArtifactMap[componentRef];
const isGroupNode = !!component?.dag;
const groupTasks = component?.dag?.tasks;

Check failure on line 46 in frontend/src/concepts/pipelines/topology/usePipelineTaskTopology.ts

View workflow job for this annotation

GitHub Actions / Tests (18.x)

'groupTasks' is assigned a value but never used

const executorLabel = component?.executorLabel;
const executor = executorLabel ? executors[executorLabel] : undefined;
Expand All @@ -64,10 +66,12 @@ export const usePipelineTaskTopology = (
const id = artifactId ?? artifactKey;

nodes.push(
createNode({
createArtifactNode({
id,
label,
artifactType: data.schemaTitle,
runAfter: [taskId],
status: translateStatusForNode(status?.state),
}),
);

Expand Down
30 changes: 30 additions & 0 deletions frontend/src/concepts/topology/PipelineTaskEdge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import * as React from 'react';
import {
DEFAULT_SPACER_NODE_TYPE,
GraphElement,
Edge,
EdgeTerminalType,
observer,
TaskEdge,
} from '@patternfly/react-topology';

interface PipelineTaskEdgeProps {
element: GraphElement;
}

const PipelineTaskEdge: React.FC<PipelineTaskEdgeProps> = ({ element, ...props }) => {
const edge = element as Edge;
return (
<TaskEdge
element={edge}
endTerminalType={
edge.getTarget().getType() !== DEFAULT_SPACER_NODE_TYPE
? EdgeTerminalType.directional
: undefined
}
{...props}
/>
);
};

export default observer(PipelineTaskEdge);
11 changes: 7 additions & 4 deletions frontend/src/concepts/topology/PipelineVisualizationSurface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,13 @@ const PipelineVisualizationSurface: React.FC<PipelineVisualizationSurfaceProps>
const controller = useVisualizationController();
const [error, setError] = React.useState<Error | null>();
React.useEffect(() => {
// PF Bug
// TODO: Pipeline Topology weirdly doesn't set a width and height on spacer nodes -- but they do when using finally spacer nodes
const spacerNodes = getSpacerNodes(nodes).map((s) => ({ ...s, width: 1, height: 1 }));
const renderNodes = [...spacerNodes, ...nodes];
const spacerNodes = getSpacerNodes(nodes);

// Dagre likes the root nodes to be first in the order
const renderNodes = [...spacerNodes, ...nodes].sort(
(a, b) => (a.runAfterTasks?.length ?? 0) - (b.runAfterTasks?.length ?? 0),
);

// TODO: We can have a weird edge issue if the node is off by a few pixels vertically from the center
const edges = getEdgesFromNodes(renderNodes);
try {
Expand Down
7 changes: 5 additions & 2 deletions frontend/src/concepts/topology/TaskEdge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
integralShapePath,
DEFAULT_SPACER_NODE_TYPE,
ConnectorArrow,
DagreLayoutOptions,
} from '@patternfly/react-topology';

interface TaskEdgeProps {
Expand All @@ -24,19 +25,21 @@ const TaskEdge: React.FunctionComponent<TaskEdgeProps> = ({
const endPoint = element.getEndPoint();
const groupClassName = css(styles.topologyEdge, className);
const startIndent: number = element.getData()?.indent || 0;
const verticalLayout =
(element.getGraph().getLayoutOptions?.() as DagreLayoutOptions).rankdir === 'TB';

return (
<g data-test-id="task-handler" className={groupClassName}>
<path
fillOpacity={0}
d={integralShapePath(startPoint, endPoint, startIndent, nodeSeparation)}
d={integralShapePath(startPoint, endPoint, startIndent, nodeSeparation, verticalLayout)}
shapeRendering="geometricPrecision"
/>

{element.getTarget().getType() !== DEFAULT_SPACER_NODE_TYPE ? (
<ConnectorArrow
className={styles.topologyEdge}
startPoint={endPoint.clone().translate(-1, 0)}
startPoint={endPoint.clone().translate(0, -1)}
endPoint={endPoint}
/>
) : null}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/concepts/topology/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ export const PIPELINE_LAYOUT = 'PipelineLayout';
export const PIPELINE_NODE_SEPARATION_VERTICAL = 100;

export const NODE_WIDTH = 100;
export const NODE_HEIGHT = 30;
export const NODE_HEIGHT = 35;
158 changes: 158 additions & 0 deletions frontend/src/concepts/topology/customNodes/ArtifactTaskNode.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import React, { LegacyRef } from 'react';
import {
TaskNode,
DEFAULT_WHEN_OFFSET,
Node,
WhenDecorator,
NodeModel,
WithSelectionProps,
observer,
useAnchor,
AnchorEnd,
getRunStatusModifier,
ScaleDetailsLevel,
useHover,
TaskNodeSourceAnchor,
TaskNodeTargetAnchor,
} from '@patternfly/react-topology';
import { ListIcon, MonitoringIcon } from '@patternfly/react-icons';
import { TaskNodeProps } from '@patternfly/react-topology/dist/esm/pipelines/components/nodes/TaskNode';
import { css } from '@patternfly/react-styles';
import { StandardTaskNodeData } from '~/concepts/topology/types';

const ICON_PADDING = 8;

type IconTaskNodeProps = {
element: Node<NodeModel, StandardTaskNodeData>;
} & WithSelectionProps;

const IconTaskNode: React.FC<IconTaskNodeProps> = observer(({ element, selected, onSelect }) => {
const data = element.getData();
const status = data?.status;
const bounds = element.getBounds();
const iconSize = bounds.height - ICON_PADDING * 2;

const runStatusModifier = status && getRunStatusModifier(status);

useAnchor(
React.useCallback(
(node: Node) => new TaskNodeSourceAnchor(node, ScaleDetailsLevel.high, 0, true),
[],
),
AnchorEnd.source,
);
useAnchor(
React.useCallback(
(node: Node) => new TaskNodeTargetAnchor(node, 0, ScaleDetailsLevel.high, 0, true),
[],
),
AnchorEnd.target,
);

return (
<g
className={css(
'pf-topology-pipelines__pill',
runStatusModifier,
selected && 'pf-m-selected',
onSelect && 'pf-m-selectable',
)}
onClick={onSelect}
>
<rect
className="pf-topology-pipelines__pill-background"
x={0}
y={0}
width={bounds.width}
height={bounds.height}
rx={bounds.height / 2}
/>
<g
transform={`translate(${(bounds.width - iconSize) / 2}, ${ICON_PADDING})`}
color={
selected
? 'var(--pf-v5-global--icon--Color--dark--light)'
: 'var(--pf-v5-global--icon--Color--light)'
}
>
{data?.artifactType === 'system.Metrics' ? (
<MonitoringIcon width={iconSize} height={iconSize} />
) : (
<ListIcon width={iconSize} height={iconSize} />
)}
</g>
</g>
);
});

type ArtifactTaskNodeInnerProps = WithSelectionProps & {
element: Node<NodeModel, StandardTaskNodeData>;
} & Omit<TaskNodeProps, 'element'> & { element: Node };

const ArtifactTaskNodeInner: React.FC<ArtifactTaskNodeInnerProps> = observer(
({ element, selected, onSelect, ...rest }) => {
const bounds = element.getBounds();
const [isHover, hoverRef] = useHover();
const detailsLevel = element.getGraph().getDetailsLevel();
const data = element.getData();
const scale = element.getGraph().getScale();
const iconSize = 24;
const whenDecorator = data?.whenStatus ? (
<WhenDecorator element={element} status={data.whenStatus} leftOffset={DEFAULT_WHEN_OFFSET} />
) : null;
const upScale = 1 / scale;

return (
<g
className={css('pf-topology__pipelines__task-node')}
ref={hoverRef as LegacyRef<SVGGElement>}
>
{isHover || detailsLevel !== ScaleDetailsLevel.high ? (
<g>
<TaskNode
nameLabelClass="artifact-node-label"
hideDetailsAtMedium
truncateLength={30}
element={element}
hover
selected={selected}
onSelect={onSelect}
status={data?.status}
scaleNode={isHover}
{...rest}
>
{whenDecorator}
</TaskNode>
{!isHover && detailsLevel !== ScaleDetailsLevel.high ? (
<g
transform={`translate(0, ${
(bounds.height - iconSize * upScale) / 2
}) scale(${upScale})`}
>
<g transform="translate(4, 4)">
<g
color={
selected
? 'var(--pf-v5-global--icon--Color--dark--light)'
: 'var(--pf-v5-global--icon--Color--light)'
}
>
{data?.artifactType === 'system.Metrics' ? <MonitoringIcon /> : <ListIcon />}
</g>
</g>
</g>
) : null}
</g>
) : (
<IconTaskNode selected={selected} onSelect={onSelect} element={element} />
)}
</g>
);
},
);

const ArtifactTaskNode: React.FC<TaskNodeProps> = ({ element, ...rest }) => (
<ArtifactTaskNodeInner element={element as Node} {...rest} />
);

export default ArtifactTaskNode;
58 changes: 46 additions & 12 deletions frontend/src/concepts/topology/customNodes/StandardTaskNode.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,64 @@
import * as React from 'react';
import {
TaskNode,
DEFAULT_LAYER,
DEFAULT_WHEN_OFFSET,
Node,
DEFAULT_WHEN_SIZE,
GraphElement,
Layer,
observer,
RunStatus,
ScaleDetailsLevel,
TaskNode,
TOP_LAYER,
useHover,
WhenDecorator,
NodeModel,
WithContextMenuProps,
WithSelectionProps,
observer,
} from '@patternfly/react-topology';
import { StandardTaskNodeData } from '~/concepts/topology/types';

type DemoTaskNodeProps = WithSelectionProps & {
element: Node<NodeModel, StandardTaskNodeData>;
};
type StandardTaskNodeProps = {
element: GraphElement;
} & WithContextMenuProps &
WithSelectionProps;

const StandardTaskNode: React.FC<DemoTaskNodeProps> = ({ element, onSelect, selected }) => {
const StandardTaskNode: React.FunctionComponent<StandardTaskNodeProps> = ({
element,
onSelect,
selected,
...rest
}) => {
const data = element.getData();
const [hover, hoverRef] = useHover();
const detailsLevel = element.getGraph().getDetailsLevel();

const whenDecorator = data?.whenStatus ? (
<WhenDecorator element={element} status={data.whenStatus} leftOffset={DEFAULT_WHEN_OFFSET} />
) : null;

return (
<TaskNode onSelect={onSelect} selected={selected} element={element} status={data?.status}>
{whenDecorator}
</TaskNode>
<Layer id={detailsLevel !== ScaleDetailsLevel.high && hover ? TOP_LAYER : DEFAULT_LAYER}>
<g ref={hoverRef as React.LegacyRef<SVGGElement>}>
<TaskNode
element={element}
onSelect={onSelect}
selected={selected}
scaleNode={hover && detailsLevel !== ScaleDetailsLevel.high}
status={data?.status}
hideDetailsAtMedium
hiddenDetailsShownStatuses={[
RunStatus.Succeeded,
RunStatus.Cancelled,
RunStatus.Failed,
RunStatus.Running,
]}
whenOffset={data?.whenStatus ? DEFAULT_WHEN_OFFSET : 0}
whenSize={data?.whenStatus ? DEFAULT_WHEN_SIZE : 0}
{...rest}
>
{whenDecorator}
</TaskNode>
</g>
</Layer>
);
};

Expand Down
Loading

0 comments on commit c99808e

Please sign in to comment.