diff --git a/src/components/Graph/components/Node/index.tsx b/src/components/Graph/components/Node/index.tsx index 969efb04a..8e22bfb95 100644 --- a/src/components/Graph/components/Node/index.tsx +++ b/src/components/Graph/components/Node/index.tsx @@ -1,22 +1,10 @@ -import CardNode, { CardNodeColumn, CardNodeTitle } from '@carbon/charts-react/diagrams/CardNode'; -import { Link, Paper, Typography } from '@material-ui/core'; +import CardNode from '@carbon/charts-react/diagrams/CardNode'; +import { Paper } from '@material-ui/core'; import React from 'react'; -import { StatusIcon } from '../../../StatusIcon'; import { useStyles } from './styles'; import { NodeProps } from './types'; -export const Node = ({ - x, - y, - height, - width, - title, - url, - icon, - color, - isRotating, - status, -}: NodeProps) => { +export const Node: React.FC = ({ x, y, height, width, color, children }) => { const classes = useStyles(color); return ( @@ -27,30 +15,7 @@ export const Node = ({ style={{ overflow: 'visible' }} > - - - - - - - {url ? ( - - {title} - - ) : ( - - {title} - - )} - - - + {children as React.ReactElement} ); diff --git a/src/components/Graph/components/types.ts b/src/components/Graph/components/types.ts index 313807b1a..873ff3ef5 100644 --- a/src/components/Graph/components/types.ts +++ b/src/components/Graph/components/types.ts @@ -1,25 +1,16 @@ import { ElkExtendedEdge, ElkNode } from 'elkjs'; -export interface MyNode extends ElkNode { - id: string; - color: string; - icon: string; - status: string; - isRotating: boolean; - title: string; - height: number; - width: number; - url?: string; -} - -export interface MyEdge extends ElkExtendedEdge { +export interface MyNode extends ElkNode { + data: T; color: string; } export interface GraphProps { id: string; - nodes: MyNode[]; - edges: MyEdge[]; + nodes: ElkNode[]; + edges: ElkExtendedEdge[]; type?: string; direction?: string; + renderEdge: (edge: ElkExtendedEdge) => JSX.Element; + renderNode: (node: ElkNode) => JSX.Element; } diff --git a/src/components/Graph/index.tsx b/src/components/Graph/index.tsx index f3b91dc54..867d8616f 100644 --- a/src/components/Graph/index.tsx +++ b/src/components/Graph/index.tsx @@ -1,14 +1,22 @@ import '@carbon/charts/styles-g90.css'; import { ArrowRightMarker } from '@carbon/charts-react/diagrams/Marker'; +import { Icon } from '@iconify/react'; +import { IconButton } from '@material-ui/core'; import { ElkNode } from 'elkjs'; import ELK from 'elkjs/lib/elk.bundled'; import React, { useEffect, useState } from 'react'; import { ReactZoomPanPinchRef, TransformComponent, TransformWrapper } from 'react-zoom-pan-pinch'; -import { Edge } from './components/Edge'; -import { Node } from './components/Node'; import { GraphProps } from './components/types'; -export const Graph = ({ direction = 'RIGHT', id, nodes, edges, type = 'detailed' }: GraphProps) => { +export const Graph = ({ + direction = 'RIGHT', + id, + nodes, + edges, + renderEdge, + renderNode, + type = 'detailed', +}: GraphProps) => { const elk = React.useMemo( () => new ELK({ @@ -66,24 +74,48 @@ export const Graph = ({ direction = 'RIGHT', id, nodes, edges, type = 'detailed' initialPositionY={0} ref={transformComponentRef} > - - - - - - {graphEdges.map((edge, i) => { - return ; - })} - {graphNodes.map((node, i) => { - //@ts-ignore - return ; - })} - - + {({ zoomIn, zoomOut, resetTransform }) => { + return ( + + + + + + + {graphEdges.map((edge, i) => { + return ( + + {renderEdge(edge)} + + ); + })} + {graphNodes.map((node, i) => { + return ( + + {renderNode(node)} + + ); + })} + + +
+ zoomIn()}> + + + zoomOut()}> + + + resetTransform()}> + + +
+
+ ); + }} ); }; diff --git a/src/k8s/TaskRun/constants.ts b/src/k8s/TaskRun/constants.ts index 8a01a51d3..783e4d99a 100644 --- a/src/k8s/TaskRun/constants.ts +++ b/src/k8s/TaskRun/constants.ts @@ -14,3 +14,13 @@ export const TASK_RUN_STATUS = { FALSE: 'false', UNKNOWN: 'unknown', } as const; + +export const TASK_RUN_STEP_STATUS = { + RUNNING: 'running', + TERMINATED: 'terminated', + WAITING: 'waiting', +} as const; + +export const TASK_RUN_STEP_REASON = { + COMPLETED: 'completed', +} as const; diff --git a/src/k8s/TaskRun/index.ts b/src/k8s/TaskRun/index.ts index 1d73ea3b9..5af6fdfd7 100644 --- a/src/k8s/TaskRun/index.ts +++ b/src/k8s/TaskRun/index.ts @@ -4,7 +4,12 @@ import { ICONS } from '../../icons/iconify-icons-mapping'; import { ValueOf } from '../../types/global'; import { streamResults } from '../common/streamResults'; import { TaskRunKubeObjectConfig } from './config'; -import { TASK_RUN_REASON, TASK_RUN_STATUS } from './constants'; +import { + TASK_RUN_REASON, + TASK_RUN_STATUS, + TASK_RUN_STEP_REASON, + TASK_RUN_STEP_STATUS, +} from './constants'; import { TASK_RUN_LABEL_SELECTOR_CD_PIPELINE_NAME, TASK_RUN_LABEL_SELECTOR_PARENT_PIPELINE_RUN, @@ -46,7 +51,10 @@ export class TaskRunKubeObject extends K8s.cluster.makeKubeObject, + reason: ValueOf + ): [string, string, boolean?] { if (status === undefined || reason === undefined) { return [ICONS.UNKNOWN, STATUS_COLOR.UNKNOWN]; } @@ -77,6 +85,32 @@ export class TaskRunKubeObject extends K8s.cluster.makeKubeObject, + reason: ValueOf + ): [string, string, boolean?] { + if (status === undefined) { + return [ICONS.UNKNOWN, STATUS_COLOR.UNKNOWN]; + } + const _status = status.toLowerCase(); + const _reason = reason.toLowerCase(); + + switch (_status) { + case TASK_RUN_STEP_STATUS.RUNNING: + return [ICONS.LOADER_CIRCLE, STATUS_COLOR.IN_PROGRESS, true]; + case TASK_RUN_STEP_STATUS.WAITING: + return [ICONS.LOADER_CIRCLE, STATUS_COLOR.IN_PROGRESS, true]; + case TASK_RUN_STEP_STATUS.TERMINATED: + if (_reason === TASK_RUN_STEP_REASON.COMPLETED) { + return [ICONS.CHECK_CIRCLE, STATUS_COLOR.SUCCESS]; + } + + return [ICONS.CROSS_CIRCLE, STATUS_COLOR.ERROR]; + default: + return [ICONS.UNKNOWN, STATUS_COLOR.UNKNOWN]; + } + } + static streamListByPipelineNameAndPipelineType({ namespace, CDPipelineName, diff --git a/src/override.css b/src/override.css index 85a34da09..906865d6e 100644 --- a/src/override.css +++ b/src/override.css @@ -24,8 +24,8 @@ div[class*="SnackbarItem-variantWarning"] { color: #000; } -.react-transform-wrapper { - width: auto !important; +.react-transform-wrapper, .react-transform-component { + width: 100% !important; height: auto !important; } diff --git a/src/pages/edp-stage-details/components/QualityGates/hooks/useQualityGatesGraphData.ts b/src/pages/edp-stage-details/components/QualityGates/hooks/useQualityGatesGraphData.ts index 3469ea65f..20acb9fd6 100644 --- a/src/pages/edp-stage-details/components/QualityGates/hooks/useQualityGatesGraphData.ts +++ b/src/pages/edp-stage-details/components/QualityGates/hooks/useQualityGatesGraphData.ts @@ -1,21 +1,13 @@ import React from 'react'; -import { useParams } from 'react-router-dom'; -import { useEDPComponentsURLsQuery } from '../../../../../k8s/EDPComponent/hooks/useEDPComponentsURLsQuery'; import { PipelineRunKubeObject } from '../../../../../k8s/PipelineRun'; import { TaskRunKubeObject } from '../../../../../k8s/TaskRun'; import { TaskRunKubeObjectInterface } from '../../../../../k8s/TaskRun/types'; -import { GENERATE_URL_SERVICE } from '../../../../../services/url'; -import { - EDPStageDetailsRouteParams, - EnrichedQualityGateWithAutotestPipelineRun, -} from '../../../types'; +import { EnrichedQualityGateWithAutotestPipelineRun } from '../../../types'; export const useQualityGatesGraphData = ( taskRunList: TaskRunKubeObjectInterface[], enrichedQualityGatesWithPipelineRuns: EnrichedQualityGateWithAutotestPipelineRun[] ) => { - const { namespace } = useParams(); - const { data: EDPComponentsURLS } = useEDPComponentsURLsQuery(namespace); const _enrichedQualityGatesWithPipelineRuns = React.useMemo( () => enrichedQualityGatesWithPipelineRuns || [], [enrichedQualityGatesWithPipelineRuns] @@ -24,35 +16,24 @@ export const useQualityGatesGraphData = ( const extraNodes = React.useMemo( () => _enrichedQualityGatesWithPipelineRuns.map((el, idx) => { - const tektonLink = - el?.autotestPipelineRun && - GENERATE_URL_SERVICE.createTektonPipelineRunLink( - EDPComponentsURLS?.tekton, - el?.autotestPipelineRun?.metadata?.namespace, - el?.autotestPipelineRun?.metadata?.name - ); - const status = PipelineRunKubeObject.parseStatus(el?.autotestPipelineRun); const reason = PipelineRunKubeObject.parseStatusReason(el?.autotestPipelineRun); - const [icon, color, isRotating] = PipelineRunKubeObject.getStatusIcon( - status, - reason - ); + const [color] = PipelineRunKubeObject.getStatusIcon(status, reason); return { id: `node::${idx}`, - status: `Status: ${status}. Reason: ${reason}`, - icon, color, - isRotating, - url: tektonLink, - title: el.qualityGate.stepName, height: 35, width: 150, + data: { + resourceType: 'pipelinerun', + resource: el.autotestPipelineRun, + title: el.qualityGate.stepName, + }, }; }), - [EDPComponentsURLS, _enrichedQualityGatesWithPipelineRuns] + [_enrichedQualityGatesWithPipelineRuns] ); const nodes = React.useMemo(() => { @@ -66,11 +47,7 @@ export const useQualityGatesGraphData = ( const initAutotestTaskRunStatus = TaskRunKubeObject.parseStatus(initAutotestTaskRun); const initAutotestTaskRunReason = TaskRunKubeObject.parseStatusReason(initAutotestTaskRun); - const [ - initAutotestTaskRunStatusIcon, - initAutotestTaskRunStatusColor, - initAutotestTaskRunStatusIsRotating, - ] = PipelineRunKubeObject.getStatusIcon( + const [, initAutotestTaskRunStatusColor] = PipelineRunKubeObject.getStatusIcon( initAutotestTaskRunStatus, initAutotestTaskRunReason ); @@ -85,11 +62,7 @@ export const useQualityGatesGraphData = ( const promoteAutotestTaskRunStatus = TaskRunKubeObject.parseStatus(promoteTaskRun); const promoteAutotestTaskRunReason = TaskRunKubeObject.parseStatusReason(promoteTaskRun); - const [ - promoteAutotestTaskRunStatusIcon, - promoteAutotestTaskRunStatusColor, - promoteAutotestTaskRunStatusIsRotating, - ] = PipelineRunKubeObject.getStatusIcon( + const [, promoteAutotestTaskRunStatusColor] = PipelineRunKubeObject.getStatusIcon( promoteAutotestTaskRunStatus, promoteAutotestTaskRunReason ); @@ -97,43 +70,29 @@ export const useQualityGatesGraphData = ( return [ { id: 'node::prepare', - status: `Status: ${initAutotestTaskRunStatus}. Reason: ${initAutotestTaskRunReason}`, - icon: initAutotestTaskRunStatusIcon, color: initAutotestTaskRunStatusColor, - isRotating: initAutotestTaskRunStatusIsRotating, - url: - initAutotestTaskRun && - GENERATE_URL_SERVICE.createTektonTaskRunLink( - EDPComponentsURLS?.tekton, - initAutotestTaskRun?.metadata?.namespace, - initAutotestTaskRun?.metadata?.name - ), - title: 'prepare', height: 35, width: 150, - y: 0, + data: { + resourceType: 'taskrun', + resource: initAutotestTaskRun, + title: 'prepare', + }, }, { id: 'node::promote', - status: `Status: ${promoteAutotestTaskRunStatus}. Reason: ${promoteAutotestTaskRunReason}`, - icon: promoteAutotestTaskRunStatusIcon, color: promoteAutotestTaskRunStatusColor, - isRotating: promoteAutotestTaskRunStatusIsRotating, - url: - promoteTaskRun && - GENERATE_URL_SERVICE.createTektonTaskRunLink( - EDPComponentsURLS?.tekton, - promoteTaskRun?.metadata?.namespace, - promoteTaskRun?.metadata?.name - ), - title: 'promote', height: 35, width: 150, - y: 0, + data: { + resourceType: 'taskrun', + resource: promoteTaskRun, + title: 'promote', + }, }, ...extraNodes, ]; - }, [EDPComponentsURLS?.tekton, extraNodes, taskRunList]); + }, [extraNodes, taskRunList]); const edges = React.useMemo(() => { const initAutotestTaskRun = diff --git a/src/pages/edp-stage-details/components/QualityGates/index.tsx b/src/pages/edp-stage-details/components/QualityGates/index.tsx index fefb78d82..8ad70ccf5 100644 --- a/src/pages/edp-stage-details/components/QualityGates/index.tsx +++ b/src/pages/edp-stage-details/components/QualityGates/index.tsx @@ -1,14 +1,27 @@ -import { Button, CircularProgress, Grid } from '@material-ui/core'; +import { CardNodeColumn, CardNodeTitle } from '@carbon/charts-react/diagrams/CardNode'; +import { Button, CircularProgress, Grid, Link, Tooltip, Typography } from '@material-ui/core'; import React from 'react'; import { useParams } from 'react-router-dom'; +import { ConditionalWrapper } from '../../../../components/ConditionalWrapper'; import { Graph } from '../../../../components/Graph'; +import { Edge } from '../../../../components/Graph/components/Edge'; +import { Node } from '../../../../components/Graph/components/Node'; +import { MyNode } from '../../../../components/Graph/components/types'; +import { StatusIcon } from '../../../../components/StatusIcon'; import { Table } from '../../../../components/Table'; import { PIPELINE_TYPES } from '../../../../constants/pipelineTypes'; +import { useEDPComponentsURLsQuery } from '../../../../k8s/EDPComponent/hooks/useEDPComponentsURLsQuery'; import { PipelineRunKubeObject } from '../../../../k8s/PipelineRun'; import { PIPELINE_RUN_REASON } from '../../../../k8s/PipelineRun/constants'; import { useCreateAutotestRunnerPipelineRun } from '../../../../k8s/PipelineRun/hooks/useCreateAutotestRunnerPipelineRun'; +import { PipelineRunKubeObjectInterface } from '../../../../k8s/PipelineRun/types'; +import { TaskRunKubeObject } from '../../../../k8s/TaskRun'; +import { TASK_RUN_STEP_REASON, TASK_RUN_STEP_STATUS } from '../../../../k8s/TaskRun/constants'; import { useStreamTaskRunListByPipelineNameAndPipelineType } from '../../../../k8s/TaskRun/hooks/useStreamTaskRunListByPipelineNameAndPipelineType'; +import { TaskRunKubeObjectInterface } from '../../../../k8s/TaskRun/types'; import { useStorageSizeQuery } from '../../../../k8s/TriggerTemplate/hooks/useStorageSizeQuery'; +import { GENERATE_URL_SERVICE } from '../../../../services/url'; +import { ValueOf } from '../../../../types/global'; import { sortKubeObjectByCreationTimestamp } from '../../../../utils/sort/sortKubeObjectsByCreationTimestamp'; import { rem } from '../../../../utils/styling/rem'; import { useDataContext } from '../../providers/Data/hooks'; @@ -18,6 +31,98 @@ import { useColumns } from './hooks/useColumns'; import { useQualityGatesGraphData } from './hooks/useQualityGatesGraphData'; import { QualityGatesProps } from './types'; +interface TaskRunStep { + // @ts-ignore + name: string; + [key: string]: { + reason?: ValueOf; + }; +} + +const parseTaskRunStepStatus = (step: TaskRunStep) => { + return step?.[TASK_RUN_STEP_STATUS.RUNNING] + ? TASK_RUN_STEP_STATUS.RUNNING + : step?.[TASK_RUN_STEP_STATUS.WAITING] + ? TASK_RUN_STEP_STATUS.WAITING + : step?.[TASK_RUN_STEP_STATUS.TERMINATED] + ? TASK_RUN_STEP_STATUS.TERMINATED + : undefined; +}; + +const parseTaskRunStepStatusObject = (step: TaskRunStep) => { + return ( + step?.[TASK_RUN_STEP_STATUS.RUNNING] || + step?.[TASK_RUN_STEP_STATUS.WAITING] || + step?.[TASK_RUN_STEP_STATUS.TERMINATED] + ); +}; + +const parseTaskRunStepReason = (step: TaskRunStep): ValueOf => { + const statusObject = parseTaskRunStepStatusObject(step); + return statusObject?.reason; +}; + +const getStatusByResourceType = ( + resourceType: 'pipelinerun' | 'taskrun', + resource: PipelineRunKubeObjectInterface | TaskRunKubeObjectInterface +): [string, string] => { + if (resourceType === 'taskrun') { + const status = TaskRunKubeObject.parseStatus(resource); + const reason = TaskRunKubeObject.parseStatusReason(resource); + return [status, reason]; + } else if (resourceType === 'pipelinerun') { + const status = PipelineRunKubeObject.parseStatus(resource); + const reason = PipelineRunKubeObject.parseStatusReason(resource); + return [status, reason]; + } + + return [undefined, undefined]; +}; + +const getStatusIconByResourceType = ( + resourceType: 'pipelinerun' | 'taskrun', + status: string, + reason: string +): [string, string, boolean] => { + if (resourceType === 'taskrun') { + // @ts-ignore + const [icon, color, isRotating] = TaskRunKubeObject.getStatusIcon(status, reason); + return [icon, color, isRotating]; + } else if (resourceType === 'pipelinerun') { + // @ts-ignore + const [icon, color, isRotating] = PipelineRunKubeObject.getStatusIcon(status, reason); + return [icon, color, isRotating]; + } + + return [undefined, undefined, undefined]; +}; + +const getResourceURLByResourceType = ( + resourceType: 'pipelinerun' | 'taskrun', + resource: PipelineRunKubeObjectInterface | TaskRunKubeObjectInterface, + tektonBaseURL +) => { + if (resourceType === 'taskrun') { + return ( + resource && + GENERATE_URL_SERVICE.createTektonTaskRunLink( + tektonBaseURL, + resource?.metadata?.namespace, + resource?.metadata?.name + ) + ); + } else if (resourceType === 'pipelinerun') { + return ( + resource && + GENERATE_URL_SERVICE.createTektonPipelineRunLink( + tektonBaseURL, + resource?.metadata?.namespace, + resource?.metadata?.name + ) + ); + } +}; + export const QualityGates = ({ enrichedQualityGatesWithPipelineRuns, argoApplications, @@ -27,6 +132,7 @@ export const QualityGates = ({ }: QualityGatesProps) => { const { namespace, CDPipelineName } = useParams(); const columns = useColumns(); + const { data: EDPComponentsURLS } = useEDPComponentsURLsQuery(namespace); const { enrichedApplications } = useDataContext(); const { stage } = useDynamicDataContext(); @@ -110,6 +216,112 @@ export const QualityGates = ({ enrichedQualityGatesWithPipelineRuns ); + const renderSteps = React.useCallback((steps: TaskRunStep[]) => { + if (!steps) { + return null; + } + + return steps.map(step => { + const stepName = step?.name; + const status = parseTaskRunStepStatus(step); + const reason = parseTaskRunStepReason(step); + const [icon, color, isRotating] = TaskRunKubeObject.getStepStatusIcon(status, reason); + return ( + + + + + + + + {stepName} + + + + + ); + }); + }, []); + + const renderNode = React.useCallback( + ( + node: MyNode<{ + title?: string; + resourceType?: 'pipelinerun' | 'taskrun'; + resource?: PipelineRunKubeObjectInterface | TaskRunKubeObjectInterface; + }> + ) => { + const { + data: { resourceType, resource, title }, + } = node; + + const [status, reason] = getStatusByResourceType(resourceType, resource); + + const [icon, color, isRotating] = getStatusIconByResourceType( + resourceType, + status, + reason + ); + const steps = resource?.status?.steps; + const Steps = renderSteps(steps); + const url = getResourceURLByResourceType( + resourceType, + resource, + EDPComponentsURLS?.tekton + ); + return ( + // @ts-ignore + +
+ + + + + ( + {Steps}} + interactive + arrow + placement={'bottom'} + > + {children} + + )} + > +
+ + {url ? ( + + {title} + + ) : ( + {title} + )} + +
+
+
+
+
+ ); + }, + [EDPComponentsURLS?.tekton, renderSteps] + ); + return ( @@ -121,21 +333,14 @@ export const QualityGates = ({ {!!nodes && !!nodes.length && !!edges && !!edges.length ? ( -
- -
+ } + renderNode={renderNode} + /> ) : ( )} diff --git a/src/widgets/PipelineRunGraph/index.tsx b/src/widgets/PipelineRunGraph/index.tsx index 6f795acd4..387003989 100644 --- a/src/widgets/PipelineRunGraph/index.tsx +++ b/src/widgets/PipelineRunGraph/index.tsx @@ -1,3 +1,4 @@ +import { CardNodeColumn, CardNodeTitle } from '@carbon/charts-react/diagrams/CardNode'; import { Icon } from '@iconify/react'; import { Dialog, @@ -5,22 +6,60 @@ import { DialogTitle, Grid, IconButton, + Tooltip, Typography, } from '@material-ui/core'; import React from 'react'; import { Graph } from '../../components/Graph'; +import { Edge } from '../../components/Graph/components/Edge'; +import { Node } from '../../components/Graph/components/Node'; +import { MyNode } from '../../components/Graph/components/types'; import { LoadingWrapper } from '../../components/LoadingWrapper'; import { Render } from '../../components/Render'; +import { StatusIcon } from '../../components/StatusIcon'; import { ICONS } from '../../icons/iconify-icons-mapping'; import { PipelineRunKubeObject } from '../../k8s/PipelineRun'; import { TaskRunKubeObject } from '../../k8s/TaskRun'; +import { TASK_RUN_STEP_REASON, TASK_RUN_STEP_STATUS } from '../../k8s/TaskRun/constants'; import { TASK_RUN_LABEL_SELECTOR_PARENT_PIPELINE_RUN } from '../../k8s/TaskRun/labels'; import { TaskRunKubeObjectInterface } from '../../k8s/TaskRun/types'; import { useSpecificDialogContext } from '../../providers/Dialog/hooks'; +import { ValueOf } from '../../types/global'; import { PIPELINE_RUN_GRAPH_DIALOG_NAME } from './constants'; import { useStyles } from './styles'; import { PipelineRunGraphDialogForwardedProps } from './types'; +interface TaskRunStep { + // @ts-ignore + name: string; + [key: string]: { + reason?: ValueOf; + }; +} + +const parseTaskRunStepStatus = (step: TaskRunStep) => { + return step?.[TASK_RUN_STEP_STATUS.RUNNING] + ? TASK_RUN_STEP_STATUS.RUNNING + : step?.[TASK_RUN_STEP_STATUS.WAITING] + ? TASK_RUN_STEP_STATUS.WAITING + : step?.[TASK_RUN_STEP_STATUS.TERMINATED] + ? TASK_RUN_STEP_STATUS.TERMINATED + : undefined; +}; + +const parseTaskRunStepStatusObject = (step: TaskRunStep) => { + return ( + step?.[TASK_RUN_STEP_STATUS.RUNNING] || + step?.[TASK_RUN_STEP_STATUS.WAITING] || + step?.[TASK_RUN_STEP_STATUS.TERMINATED] + ); +}; + +const parseTaskRunStepReason = (step: TaskRunStep): ValueOf => { + const statusObject = parseTaskRunStepStatusObject(step); + return statusObject?.reason; +}; + export const PipelineRunGraph = () => { const classes = useStyles(); @@ -73,21 +112,17 @@ export const PipelineRunGraph = () => { const TaskRunByName = TaskRunListByNameMap.get(name); const status = TaskRunKubeObject.parseStatus(TaskRunByName); const reason = TaskRunKubeObject.parseStatusReason(TaskRunByName); - const [icon, color, isRotating] = PipelineRunKubeObject.getStatusIcon(status, reason); + const [, color] = PipelineRunKubeObject.getStatusIcon(status, reason); _nodes = [ - ..._nodes, { id: `task::${name}`, - status: `Status: ${status}. Reason: ${reason}`, - icon, - color, - isRotating, - url: null, - title: name, height: 35, width: 150, + color, + data: { name, TaskRunByName }, }, + ..._nodes, ]; } @@ -130,6 +165,81 @@ export const PipelineRunGraph = () => { const diagramIsReady = nodes !== null && edges !== null; + const renderSteps = React.useCallback((steps: TaskRunStep[]) => { + if (!steps) { + return null; + } + + return steps.map(step => { + const stepName = step?.name; + const status = parseTaskRunStepStatus(step); + const reason = parseTaskRunStepReason(step); + const [icon, color, isRotating] = TaskRunKubeObject.getStepStatusIcon(status, reason); + return ( + + + + + + + + {stepName} + + + + + ); + }); + }, []); + + const renderNode = React.useCallback( + (node: MyNode<{ name: string; TaskRunByName: TaskRunKubeObjectInterface }>) => { + const { + data: { name, TaskRunByName }, + } = node; + + const steps = TaskRunByName?.status?.steps; + + const status = TaskRunKubeObject.parseStatus(TaskRunByName); + const reason = TaskRunKubeObject.parseStatusReason(TaskRunByName); + const [icon, color, isRotating] = PipelineRunKubeObject.getStatusIcon(status, reason); + const Steps = renderSteps(steps); + + return ( + // @ts-ignore + +
+ + + + + {Steps}} interactive arrow placement={'bottom'}> +
+ + {name} + +
+
+
+
+
+ ); + }, + [renderSteps] + ); + return ( { nodes={nodes} edges={edges} id={'pipeline-run-steps'} + renderEdge={edge => } + renderNode={renderNode} />