diff --git a/packages/demo-app-ts/src/utils/useDemoPipelineNodes.tsx b/packages/demo-app-ts/src/utils/useDemoPipelineNodes.tsx index 667c7c69..507247e2 100644 --- a/packages/demo-app-ts/src/utils/useDemoPipelineNodes.tsx +++ b/packages/demo-app-ts/src/utils/useDemoPipelineNodes.tsx @@ -8,7 +8,8 @@ import { RunStatus, WhenStatus, } from '@patternfly/react-topology'; - +import { CubeIcon } from '@patternfly/react-icons/dist/esm/icons/cube-icon'; +import { ExternalLinkAltIcon } from '@patternfly/react-icons/dist/esm/icons/external-link-alt-icon'; import { logos } from './logos'; export const NODE_PADDING_VERTICAL = 45; @@ -209,5 +210,69 @@ export const useDemoPipelineNodes = ( tasks.push(...taskGroups); } + const iconTask1: PipelineNodeModel = { + id: `task-icon-1`, + type: DEFAULT_TASK_NODE_TYPE, + label: `Lead icon task`, + width: DEFAULT_TASK_WIDTH + (showContextMenu ? 10 : 0) + (showBadges ? 40 : 0), + height: DEFAULT_TASK_HEIGHT, + style: { + padding: [NODE_PADDING_VERTICAL, NODE_PADDING_HORIZONTAL + (showIcons ? 25 : 0)] + }, + runAfterTasks: [] + }; + + // put options in data, our DEMO task node will pass them along to the TaskNode + iconTask1.data = { + status: RunStatus.Failed, + badge: showBadges ? '3/4' : undefined, + badgeTooltips, + taskIconClass: showIcons ? logos.get('icon-java') : undefined, + taskIconTooltip: showIcons ? 'Environment' : undefined, + showContextMenu, + columnGroup: TASK_STATUSES.length % STATUS_PER_ROW + 1, + leadIcon: , + }; + + if (!layout) { + const row = Math.ceil((TASK_STATUSES.length + 1) / STATUS_PER_ROW) - 1; + const columnWidth = COLUMN_WIDTH + (showIcons ? 15 : 0) + (showBadges ? 32 : 0) + (showContextMenu ? 20 : 0); + iconTask1.x = (showIcons ? 28 : 0) + columnWidth; + iconTask1.y = GRAPH_MARGIN_TOP + row * ROW_HEIGHT; + } + tasks.push(iconTask1); + + const iconTask2: PipelineNodeModel = { + id: `task-icon-2`, + type: DEFAULT_TASK_NODE_TYPE, + label: `Lead icon task`, + width: DEFAULT_TASK_WIDTH + (showContextMenu ? 10 : 0) + (showBadges ? 40 : 0), + height: DEFAULT_TASK_HEIGHT, + style: { + padding: [NODE_PADDING_VERTICAL, NODE_PADDING_HORIZONTAL + (showIcons ? 25 : 0)] + }, + runAfterTasks: [iconTask1.id] + }; + + // put options in data, our DEMO task node will pass them along to the TaskNode + iconTask2.data = { + badge: showBadges ? '3/4' : undefined, + badgeTooltips, + taskIconClass: showIcons ? logos.get('icon-java') : undefined, + taskIconTooltip: showIcons ? 'Environment' : undefined, + showContextMenu, + columnGroup: TASK_STATUSES.length % STATUS_PER_ROW + 1, + showStatusState: false, + leadIcon: , + }; + + if (!layout) { + const row = Math.ceil((TASK_STATUSES.length + 1) / STATUS_PER_ROW) - 1; + const columnWidth = COLUMN_WIDTH + (showIcons ? 15 : 0) + (showBadges ? 32 : 0) + (showContextMenu ? 20 : 0); + iconTask2.x = (showIcons ? 28 : 0) + 2 * columnWidth; + iconTask2.y = GRAPH_MARGIN_TOP + row * ROW_HEIGHT; + } + tasks.push(iconTask2); + return [...tasks, ...finallyNodes, finallyGroup]; }, [badgeTooltips, layout, showBadges, showContextMenu, showGroups, showIcons]); diff --git a/packages/module/src/pipelines/components/nodes/TaskNode.tsx b/packages/module/src/pipelines/components/nodes/TaskNode.tsx index 4d37125b..27e1ebfe 100644 --- a/packages/module/src/pipelines/components/nodes/TaskNode.tsx +++ b/packages/module/src/pipelines/components/nodes/TaskNode.tsx @@ -52,6 +52,8 @@ export interface TaskNodeProps { hideDetailsAtMedium?: boolean; /** Statuses to show at when details are hidden */ hiddenDetailsShownStatuses?: RunStatus[]; + /** Additional icon to be shown before the task label*/ + leadIcon?: React.ReactNode; /** Text for the label's badge */ badge?: string; /** Color to use for the label's badge background */ @@ -121,6 +123,7 @@ const TaskNodeInner: React.FC = observer(({ scaleNode, hideDetailsAtMedium, hiddenDetailsShownStatuses = [RunStatus.Failed, RunStatus.FailedToStart, RunStatus.Cancelled], + leadIcon, badge, badgeColor, badgeTextColor, @@ -160,6 +163,7 @@ const TaskNodeInner: React.FC = observer(({ const nameLabelTriggerRef = React.useRef(); const nameLabelRef = useCombineRefs(textRef, nameLabelTriggerRef) const [statusSize, statusRef] = useSize([status, showStatusState, statusIconSize]); + const [leadSize, leadIconRef] = useSize([leadIcon]); const [badgeSize, badgeRef] = useSize([badge]); const badgeLabelTriggerRef = React.useRef(); const [actionSize, actionRef] = useSize([actionIcon, paddingX]); @@ -201,7 +205,8 @@ const TaskNodeInner: React.FC = observer(({ pillWidth, badgeStartX, iconWidth, - iconStartX + iconStartX, + leadIconStartX } = React.useMemo(() => { if (!textSize) { return { @@ -213,7 +218,8 @@ const TaskNodeInner: React.FC = observer(({ pillWidth: 0, badgeStartX: 0, iconWidth: 0, - iconStartX: 0 + iconStartX: 0, + leadIconStartX: 0 }; } const height: number = textHeight + 2 * paddingY; @@ -225,7 +231,10 @@ const TaskNodeInner: React.FC = observer(({ const statusStartX = startX - statusIconSize / 4; // Adjust for icon padding const statusSpace = status && showStatusState && statusSize ? statusSize.width + paddingX : 0; - const textStartX = startX + statusSpace; + const leadIconStartX = startX + statusSpace; + const leadIconSpace = leadIcon ? leadSize.width + paddingX : 0; + + const textStartX = leadIconStartX + leadIconSpace; const textSpace = textWidth + paddingX; const badgeStartX = textStartX + textSpace; @@ -248,6 +257,7 @@ const TaskNodeInner: React.FC = observer(({ badgeStartX, iconWidth, iconStartX, + leadIconStartX, pillWidth }; }, [ @@ -262,6 +272,8 @@ const TaskNodeInner: React.FC = observer(({ statusIconSize, status, showStatusState, + leadSize, + leadIcon, statusSize, badgeSize, badge, @@ -423,6 +435,11 @@ const TaskNodeInner: React.FC = observer(({ )} + {leadIcon && ( + + {leadIcon} + + )} {taskIconComponent && (taskIconTooltip ? {taskIconComponent} : taskIconComponent)} {badgeComponent}