import * as dagre from 'dagre' ; import { color } from '../Css' ; import { Constants } from './Constants' ; import { parseTaskDisplayName } from './ParserUtils' ; import { graphlib } from 'dagre' ; export type nodeType = 'container' | 'resource' | 'dag' | 'unknown' ; export interface KeyValue < T > extends Array < any > { 0 ? : string | JSX . Element ; 1 ? : T ; } export class SelectedNodeInfo { public args : string [ ] ; public command : string [ ] ; public condition : string ; public image : string ; public inputs : Array < KeyValue < string > > ; public nodeType : nodeType ; public outputs : Array < KeyValue < string > > ; public volumeMounts : Array < KeyValue < string > > ; public resource : Array < KeyValue < string > > ; constructor ( ) { this . args = [ ] ; this . command = [ ] ; this . condition = '' ; this . image = '' ; this . inputs = [ [ ] ] ; this . nodeType = 'unknown' ; this . outputs = [ [ ] ] ; this . volumeMounts = [ [ ] ] ; this . resource = [ [ ] ] ; } } export function _populateInfoFromTask ( info , task ? ) { if ( ! task ) { return info ; } info . nodeType = 'container' ; if ( task [ 'taskSpec' ] && task [ 'taskSpec' ] [ 'steps' ] ) { const steps = task [ 'taskSpec' ] [ 'steps' ] ; info . args = steps [ 0 ] [ 'args' ] || [ ] ; info . command = steps [ 0 ] [ 'command' ] || [ ] ; info . image = steps [ 0 ] [ 'image' ] || [ ] ; info . volumeMounts = ( steps [ 0 ] [ 'volumeMounts' ] || [ ] ) . map ( ( volume ) => [ volume . mountPath , volume . name , ] ) ; } if ( task [ 'taskSpec' ] && task [ 'taskSpec' ] [ 'params' ] ) info . inputs = ( task [ 'taskSpec' ] [ 'params' ] || [ ] ) . map ( ( p ) => [ p [ 'name' ] , p [ 'value' ] || '' ] ) ; if ( task [ 'taskSpec' ] && task [ 'taskSpec' ] [ 'results' ] ) info . outputs = ( task [ 'taskSpec' ] [ 'results' ] || [ ] ) . map ( ( p ) => { return [ p [ 'name' ] , p [ 'description' ] || '' ] ; } ) ; if ( task [ 'params' ] ) { info . inputs = ( task [ 'params' ] || [ ] ) . map ( ( p ) => [ p [ 'name' ] , p [ 'value' ] || '' ] ) ; } return info ; } export function createGraph ( workflow ) { const graph = new dagre . graphlib . Graph ( ) ; graph . setGraph ( { } ) ; graph . setDefaultEdgeLabel ( ( ) => ( { } ) ) ; buildTektonDag ( graph , workflow ) ; return graph ; } function buildTektonDag ( graph , template ) { const pipeline = template ; if ( ! template || ! template . spec ) { throw new Error ( "Graph template or template spec doesn't exist." ) ; } const tasks = ( pipeline [ 'spec' ] [ 'pipelineSpec' ] [ 'tasks' ] || [ ] ) . concat ( pipeline [ 'spec' ] [ 'pipelineSpec' ] [ 'finally' ] || [ ] ) ; const exitHandlers = ( pipeline [ 'spec' ] [ 'pipelineSpec' ] [ 'finally' ] || [ ] ) . map ( ( element ) => { return element [ 'name' ] ; } ) || [ ] ; for ( const task of tasks ) { const taskName = task [ 'name' ] ; if ( task [ 'runAfter' ] ) task [ 'runAfter' ] . forEach ( ( depTask ) => { graph . setEdge ( depTask , taskName ) ; } ) ; for ( const condition of task [ 'when' ] || [ ] ) { const input = condition [ 'input' ] ; if ( input . substring ( 0 , 8 ) === '$(tasks.' && input . substring ( input . length - 1 ) === ')' ) { const paramSplit = input . split ( '.' ) ; const parentTask = paramSplit [ 1 ] ; graph . setEdge ( parentTask , taskName ) ; } } for ( const condition of task [ 'conditions' ] || [ ] ) { for ( const condParam of condition [ 'params' ] || [ ] ) { if ( condParam [ 'value' ] . substring ( 0 , 8 ) === '$(tasks.' && condParam [ 'value' ] . substring ( condParam [ 'value' ] . length - 1 ) === ')' ) { const paramSplit = condParam [ 'value' ] . split ( '.' ) ; const parentTask = paramSplit [ 1 ] ; graph . setEdge ( parentTask , taskName ) ; } } } if ( task [ 'taskSpec' ] && task [ 'taskSpec' ] [ 'steps' ] && task [ 'taskSpec' ] [ 'steps' ] [ 0 ] && task [ 'taskSpec' ] [ 'steps' ] [ 0 ] [ 'args' ] && task [ 'taskSpec' ] [ 'steps' ] [ 0 ] [ 'command' ] && task [ 'taskSpec' ] [ 'steps' ] [ 0 ] [ 'command' ] [ 0 ] && task [ 'taskSpec' ] [ 'steps' ] [ 0 ] [ 'command' ] [ 0 ] === 'any-task' ) { let isNextTaskList = false ; let isNextCondition = false ; task [ 'taskSpec' ] [ 'steps' ] [ 0 ] [ 'args' ] . forEach ( ( arg ) => { if ( arg === '--taskList' ) { isNextTaskList = true ; } else if ( arg === '-c' ) { isNextCondition = true ; } else if ( isNextTaskList ) { arg . split ( ',' ) . forEach ( ( parentTask ) => { graph . setEdge ( parentTask , taskName ) ; } ) ; isNextTaskList = false ; } else if ( isNextCondition ) { graph . setEdge ( arg . substring ( arg . indexOf ( 'results_' ) + 8 , arg . indexOf ( '_output' ) ) , taskName ) ; isNextCondition = false ; } } ) ; } for ( const param of task [ 'params' ] || [ ] ) { if ( param [ 'value' ] . indexOf ( '$(tasks.' ) !== - 1 && param [ 'value' ] . indexOf ( ')' ) !== - 1 ) { const paramSplit = param [ 'value' ] . split ( '.' ) ; const parentTask = paramSplit [ 1 ] ; graph . setEdge ( parentTask , taskName ) ; } } const info = new SelectedNodeInfo ( ) ; _populateInfoFromTask ( info , task ) ; const label = exitHandlers . includes ( task [ 'name' ] ) ? 'onExit - ' + taskName : taskName ; const bgColor = exitHandlers . includes ( task [ 'name' ] ) ? color . lightGrey : task . when ? 'cornsilk' : undefined ; graph . setNode ( taskName , { bgColor : bgColor , height : Constants . NODE_HEIGHT , info , label : parseTaskDisplayName ( task [ 'taskSpec' ] || task [ 'taskRef' ] ) || label , width : Constants . NODE_WIDTH , } ) ; } } param graph export function transitiveReduction ( graph ) { if ( ! graph || graph . edgeCount ( ) > 1000 || graph . nodeCount ( ) > 1000 ) { return undefined ; } const result = graphlib . json . read ( graphlib . json . write ( graph ) ) ; let visited = [ ] ; const dfs_with_removal = ( current , parent ) => { result . successors ( current ) ?. forEach ( ( node ) => { if ( visited . includes ( node ) ) return ; visited . push ( node ) ; if ( result . successors ( parent ) ?. includes ( node ) ) { result . removeEdge ( parent , node ) ; } dfs_with_removal ( node , parent ) ; } ) ; } ; result . nodes ( ) . forEach ( node => { visited = [ ] ; result . successors ( node ) ?. forEach ( ( successor ) => dfs_with_removal ( successor , node ) ) ; } ) ; return result ; } export function compareGraphEdges ( graph1 , graph2 ) { return ( graph1 . edges ( ) . map ( e => `${ e . name }${ e . v }${ e . w }` ) . sort ( ) . toString ( ) === graph2 . edges ( ) . map ( e => `${ e . name }${ e . v }${ e . w }` ) . sort ( ) . toString ( ) ) ; }