Skip to content

Commit

Permalink
convert pipeline visualization node to SVG
Browse files Browse the repository at this point in the history
  • Loading branch information
rottencandy committed Jan 27, 2021
1 parent a10903c commit fde7f3b
Show file tree
Hide file tree
Showing 9 changed files with 221 additions and 139 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,8 @@
"Add a sequential task before this task": "Add a sequential task before this task",
"Add a parallel task": "Add a parallel task",
"Task does not exist": "Task does not exist",
"No Tasks": "No Tasks",
"Select Task": "Select Task",
"No Tasks": "No Tasks",
"Delete Task": "Delete Task",
"Metrics": "Metrics",
"Select a Project to view the list of Pipelines": "Select a Project to view the list of Pipelines",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,66 +2,28 @@

$border-color: var(--pf-global--BorderColor--light-100);
.odc-pipeline-vis-task {
width: 10em;
fill: white;
stroke: $border-color;
stroke-width: 1;
cursor: default;

&__content {
width: inherit;
height: 2.5em;
white-space: normal;
border: 1px solid $border-color;
border-radius: 20px;
color: black;
background-color: white;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
display: flex;
flex-direction: row;
align-items: center;
&:hover {
box-shadow: 0 0 16px rgba(0, 0, 0, 0.12);
}

&.is-selected {
border: 2px solid $interactive-stroke-color;
}
}
&:not(:first-child) {
margin-top: 1.5em;
&.is-selected {
stroke: $interactive-stroke-color;
stroke-width: 2;
}

&__stepcount {
margin: 0 4px;
white-space: nowrap;
&.is-linked {
cursor: pointer;
}
}

&__status {
margin: 0 3px 0 7px;
width: 1.5em;
height: 1.5em;
flex-grow: 0;
flex-shrink: 0;
order: -1;
}
.odc-pipeline-vis-task-text {
cursor: default;
dominant-baseline: middle;

&__title-wrapper {
flex: 0 0 75px;
display: flex;
flex-direction: column;
min-width: 0;
&.is-text-center {
text-align: center;
margin: 0 auto;
flex: 0 0 100px;
}
&.is-text-center {
text-anchor: middle;
}
&.is-linked {
cursor: pointer;
}

&__title {
flex: 0 0 auto;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
import * as React from 'react';
import * as _ from 'lodash';
import * as cx from 'classnames';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { Tooltip } from '@patternfly/react-core';
import { createSvgIdUrl, useHover } from '@patternfly/react-topology';
import { K8sResourceKind, referenceForModel } from '@console/internal/module/k8s';
import { Firehose, resourcePathFromModel } from '@console/internal/components/utils';
import { runStatus, PipelineTaskSpec, PipelineTaskRef } from '../../../../utils/pipeline-augment';
import {
Firehose,
resourcePathFromModel,
truncateMiddle,
} from '@console/internal/components/utils';
import { SvgDropShadowFilter } from '@console/topology/src/components/svg';
import {
runStatus,
PipelineTaskSpec,
PipelineTaskRef,
getRunStatusColor,
} from '../../../../utils/pipeline-augment';
import { PipelineRunModel, TaskModel, ClusterTaskModel } from '../../../../models';
import { ColoredStatusIcon } from './StatusIcon';
import { StatusIcon } from './StatusIcon';
import { PipelineVisualizationStepList } from './PipelineVisualizationStepList';
import TaskComponentTaskStatus from './TaskComponentTaskStatus';
import { createStepStatus, StepStatus, TaskStatus } from './pipeline-step-utils';

import './PipelineVisualizationTask.scss';
Expand All @@ -26,6 +37,8 @@ interface TaskProps {
isPipelineRun: boolean;
disableTooltip?: boolean;
selected?: boolean;
width: number;
height: number;
}

interface PipelineVisualizationTaskProp {
Expand All @@ -42,8 +55,12 @@ interface PipelineVisualizationTaskProp {
disableTooltip?: boolean;
selected?: boolean;
isSkipped?: boolean;
width: number;
height: number;
}

const FILTER_ID = 'SvgTaskDropShadowFilterId';

export const PipelineVisualizationTask: React.FC<PipelineVisualizationTaskProp> = ({
pipelineRunName,
task,
Expand All @@ -52,6 +69,8 @@ export const PipelineVisualizationTask: React.FC<PipelineVisualizationTaskProp>
disableTooltip,
selected,
isSkipped,
width,
height,
}) => {
const taskStatus = task.status || {
duration: '',
Expand All @@ -75,6 +94,8 @@ export const PipelineVisualizationTask: React.FC<PipelineVisualizationTaskProp>
isPipelineRun={!!pipelineRunStatus}
disableTooltip={disableTooltip}
selected={selected}
width={width}
height={height}
/>
);

Expand Down Expand Up @@ -112,7 +133,10 @@ const TaskComponent: React.FC<TaskProps> = ({
isPipelineRun,
disableTooltip,
selected,
width,
height,
}) => {
const { t } = useTranslation();
const stepList = _.get(task, ['data', 'spec', 'steps'], []);
const stepStatusList: StepStatus[] = stepList.map((step) => createStepStatus(step, status));
const showStatusState: boolean = isPipelineRun && !!status && !!status.reason;
Expand All @@ -126,22 +150,53 @@ const TaskComponent: React.FC<TaskProps> = ({
status?.reason !== runStatus.Cancelled &&
!!path;

const [hover, hoverRef] = useHover();
const truncatedVisualName = React.useMemo(
() => truncateMiddle(visualName, { length: showStatusState ? 11 : 14, truncateEnd: true }),
[visualName, showStatusState],
);

let taskPill = (
<div className={cx('odc-pipeline-vis-task__content', { 'is-selected': selected })}>
<div
className={cx('odc-pipeline-vis-task__title-wrapper', {
<g ref={hoverRef}>
<SvgDropShadowFilter dy={1} id={FILTER_ID} />
<rect
filter={hover ? createSvgIdUrl(FILTER_ID) : ''}
width={width}
height={height}
rx={15}
className={cx('odc-pipeline-vis-task', {
'is-selected': selected,
'is-linked': enableLogLink,
})}
/>
<text
x={showStatusState ? 30 : width / 2}
y={height / 2 + 1}
className={cx('odc-pipeline-vis-task-text', {
'is-text-center': !isPipelineRun,
'is-linked': enableLogLink,
})}
>
<div className="odc-pipeline-vis-task__title">{visualName}</div>
{showStatusState && <TaskComponentTaskStatus steps={stepStatusList} />}
</div>
{isPipelineRun && (
<div className="odc-pipeline-vis-task__status">
{showStatusState && <ColoredStatusIcon status={status.reason} height={18} width={18} />}
</div>
{truncatedVisualName}
</text>
{isPipelineRun && showStatusState && (
<svg
width={30}
height={30}
viewBox="-5 -5 20 20"
style={{
color: status
? getRunStatusColor(status.reason, t).pftoken.value
: getRunStatusColor(runStatus.Cancelled, t).pftoken.value,
}}
>
<StatusIcon status={status.reason} shiftOrigin />
</svg>
)}
{showStatusState && (
<SvgTaskStatus steps={stepStatusList} x={30} y={23} width={width / 2 + 15} />
)}
</div>
</g>
);
if (!disableTooltip) {
taskPill = (
Expand All @@ -161,15 +216,37 @@ const TaskComponent: React.FC<TaskProps> = ({
);
}

const visTask = (
<>
<div className="odc-pipeline-vis-task__connector" />
{taskPill}
</>
);
return enableLogLink ? <Link to={path}>{taskPill}</Link> : taskPill;
};

interface SvgTaskStatusProps {
steps: StepStatus[];
x: number;
y: number;
width: number;
}

const SvgTaskStatus: React.FC<SvgTaskStatusProps> = ({ steps, x, y, width }) => {
const { t } = useTranslation();
if (steps.length === 0) {
return null;
}
const stepWidth = width / steps.length;
const gap = 2;
return (
<div className={cx('odc-pipeline-vis-task', { 'is-linked': enableLogLink })}>
{enableLogLink ? <Link to={path}>{visTask}</Link> : visTask}
</div>
<g>
{steps.map((step, index) => {
return (
<rect
key={step.name}
x={x + stepWidth * index}
y={y}
width={stepWidth - gap}
height={2}
fill={getRunStatusColor(step.runStatus, t).pftoken.value}
/>
);
})}
</g>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.pipeline-status-icon {
&--spin {
transform-origin: 30% 30%;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react';
import * as cx from 'classnames';
import { useTranslation } from 'react-i18next';
import {
AngleDoubleRightIcon,
Expand All @@ -11,17 +12,25 @@ import {
} from '@patternfly/react-icons';
import { getRunStatusColor, runStatus } from '../../../../utils/pipeline-augment';

import './StatusIcon.scss';

interface StatusIconProps {
status: string;
height?: number;
width?: number;
shiftOrigin?: boolean;
}

export const StatusIcon: React.FC<StatusIconProps> = ({ status, ...props }) => {
export const StatusIcon: React.FC<StatusIconProps> = ({ status, shiftOrigin, ...props }) => {
switch (status) {
case runStatus['In Progress']:
case runStatus.Running:
return <SyncAltIcon {...props} className="fa-spin" />;
return (
<SyncAltIcon
{...props}
className={cx('fa-spin', { 'pipeline-status-icon--spin': shiftOrigin })}
/>
);

case runStatus.Succeeded:
return <CheckCircleIcon {...props} />;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { Node, NodeModel, observer } from '@patternfly/react-topology';
import { Node, NodeModel, observer, useHover } from '@patternfly/react-topology';
import {
AddNodeDirection,
BUILDER_NODE_ADD_RADIUS,
Expand All @@ -19,19 +19,13 @@ type BuilderNodeProps = {

const BuilderNode: React.FC<BuilderNodeProps> = ({ element }) => {
const { t } = useTranslation();
const [showAdd, setShowAdd] = React.useState(false);
const [showAdd, hoverRef] = useHover();
const { width, height } = element.getBounds();
const data = element.getData();
const { error, onAddNode, onNodeSelection } = data;

return (
<g
className="odc-builder-node"
onFocus={() => setShowAdd(true)}
onBlur={() => setShowAdd(false)}
onMouseOver={() => setShowAdd(true)}
onMouseOut={() => setShowAdd(false)}
>
<g className="odc-builder-node" ref={hoverRef}>
<rect
x={-BUILDER_NODE_ADD_RADIUS * 2}
y={0}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,26 @@
overflow: hidden;
box-shadow: var(--pf-c-dropdown__menu--BoxShadow);
}
&__label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&__trigger-background {
background: var(--pf-global--BackgroundColor--light-100);
fill: var(--pf-global--BackgroundColor--light-100);
&.is-disabled {
fill: var(--pf-global--disabled-color--300);
}
}
&__trigger-underline {
fill: var(--pf-global--Color--100);
&__hover {
fill: var(--pf-global--link--Color);
}
}
&__trigger {
position: fixed;
padding: var(--pf-global--spacer--xs) var(--pf-global--spacer--md);
width: 100%;
font-size: var(--pf-global--FontSize--md);
text-anchor: middle;
dominant-baseline: middle;
cursor: pointer;
}
&__trigger-disabled {
fill: var(--pf-global--disabled-color--100);
}
&__list-items {
max-height: 350px;
Expand All @@ -27,4 +35,3 @@
margin: 0;
}
}

0 comments on commit fde7f3b

Please sign in to comment.