import { Connection } from '@salesforce/core' ; import { AbstractGraph } from './abstractGraph' ; import { FindAllDependencies } from './DFSLib' ; import { Package , PackageNode , PackageDependency , SubscriberPackageVersion } from './PackageDefs' ; import { Edge , Node , ScalarNode } from './NodeDefs' ; import { isNull , isUndefined } from 'util' ; export class PackageGraph extends AbstractGraph { public edges : Set < Edge > = new Set < Edge > ( ) ; private connection : Connection ; private subscriberPackages : Package [ ] ; private packageDependencies : PackageDependency [ ] ; constructor ( conn , subscriberPacks ) { super ( ) ; this . connection = conn ; this . subscriberPackages = subscriberPacks ; this . connection . bulk . pollTimeout = 25000 ; } public async init ( ) { this . packageDependencies = await this . retrieveDependencies ( this . subscriberPackages , new Array < SubscriberPackageVersion > ( ) ) ; } public buildGraph ( ) { this . nodesMap = new Map < string , Node > ( ) ; this . edges = new Set < Edge > ( ) ; if ( ! isNull ( this . packageDependencies ) ) { for ( const record of this . packageDependencies ) { var srcNode ; if ( ! isUndefined ( record . Source ) ) { record . Source . Id ; const srcId = record . Source . Id ; ; const srcPath = record . Source . Path ; const srcName = record . Source . Name ; const srcNamespace = record . Source . Namespace ; const srcVersionId = record . Source . VersionId ; const srcVersionName = record . Source . VersionName ; const srcVersionNumber = record . Source . VersionNumber ; const srcDetails = new Map < string , object > ( ) ; srcDetails . set ( 'id' , ( srcId as String ) ) ; srcDetails . set ( 'name' , ( srcName as String ) ) ; srcDetails . set ( 'path' , ( srcPath as String ) ) ; srcDetails . set ( 'namespace' , ( srcNamespace as String ) ) ; srcDetails . set ( 'versionId' , ( srcVersionId as String ) ) ; srcDetails . set ( 'versionName' , ( srcVersionName as String ) ) ; srcDetails . set ( 'versionNumber' , ( srcVersionNumber as String ) ) ; srcNode = this . getOrAddNode ( srcId , srcDetails ) ; } var dstNode ; if ( ! isUndefined ( record . Target ) ) { const dstId = record . Target . Id ; const dstPath = record . Target . Path ; const dstName = record . Target . Name ; const dstNamespace = record . Target . Namespace ; const dstVersionId = record . Target . VersionId ; const dstVersionName = record . Target . VersionName ; const dstVersionNumber = record . Target . VersionNumber ; const dstDetails = new Map < string , object > ( ) ; dstDetails . set ( 'id' , ( dstId as String ) ) ; dstDetails . set ( 'name' , ( dstName as String ) ) ; dstDetails . set ( 'path' , ( dstPath as String ) ) ; dstDetails . set ( 'namespace' , ( dstNamespace as String ) ) ; dstDetails . set ( 'versionId' , ( dstVersionId as String ) ) ; dstDetails . set ( 'versionName' , ( dstVersionName as String ) ) ; dstDetails . set ( 'versionNumber' , ( dstVersionNumber as String ) ) ; dstNode = this . getOrAddNode ( dstId , dstDetails ) ; } if ( ! isUndefined ( record . Source ) && ! isUndefined ( record . Target ) ) { this . edges . add ( { from : record . Source . VersionId , to : record . Target . VersionId } ) ; this . addEdge ( srcNode , dstNode ) ; } } this . addPackageRelationships ( ) ; } } public runDFS ( initialNodes ) { const dfs = new FindAllDependencies ( this ) ; initialNodes . forEach ( node => { const graphNode = this . getOrAddNode ( node . name , node . details ) ; dfs . runNode ( graphNode ) ; } ) ; this . nodesMap = dfs . visited ; this . edges = dfs . visitedEdges ; } public getEdges ( src ) { return ( src as ScalarNode ) . getEdges ( ) ; } public addPackageRelationships ( ) { this . packageDependencies . forEach ( packagedef => { if ( ! isUndefined ( packagedef . Source ) && ! isUndefined ( packagedef . Target ) ) { const n1 = this . getNodeVersionId ( packagedef . Source . VersionId ) ; const n2 = this . getNodeVersionId ( packagedef . Target . VersionId ) ; if ( n1 != null && n2 != null ) { this . addEdge ( n1 , n2 ) ; } } } ) ; } protected getNodeVersionId ( versionId ) { if ( isNull ( versionId ) ) return null ; let found ; Array . from ( this . nodes ) . forEach ( node => { if ( ( node . details . get ( 'versionId' ) as String ) == versionId ) { found = node ; } } ) ; return found ; } /**
    * Render as DOT format
    */ public toDotFormat ( ) { let dot = 'digraph graphname {\n' ; dot += '  rankdir=RL;\n' ; dot += '  node[shape=Mrecord, bgcolor=black, fillcolor=lightblue, style=filled];\n' ; dot += '  // Nodes\n' ; for ( const node of this . nodes ) { dot += `  X${ node . details . get ( 'versionId' ) } [label=<${ node . details . get ( 'name' ) } ${ node . details . get ( 'versionNumber' ) }` ; dot += `<BR/><FONT POINT-SIZE="8">${ node . details . get ( 'id' ) }</FONT>` ; dot += `<BR/><FONT POINT-SIZE="8">${ node . details . get ( 'versionId' ) }</FONT>>]\n` ; } dot += '  // Paths\n' ; for ( const edge of this . edges ) { dot += `  X${ edge . from }->X${ edge . to }\n` ; } dot += '}' ; return dot ; } public toJson ( ) { const jsonRepresentation = new Array < PackageNode > ( ) ; for ( const node of this . nodes ) { const jsonNode = { id : node . name , name : ( node . details . get ( 'name' ) as String ) . valueOf ( ) , versionId : ( node . details . get ( 'versionId' ) as String ) . valueOf ( ) , version : ( node . details . get ( 'versionNumber' ) as String ) . valueOf ( ) } ; jsonRepresentation . push ( jsonNode ) ; } return { nodes : jsonRepresentation , edges : Array . from ( this . edges ) } ; } public async retrieveRecords < T > ( query ) { return ( await this . connection . tooling . query < T > ( query ) ) . records ; } public async retrieveBulkRecords < T > ( query ) { return ( await this . connection . bulk . query ( query ) . on ( 'record' , function ( rec ) { console . log ( rec ) ; return rec ; } ) ) . on ( 'error' , function ( err ) { console . error ( err ) ; } ) ; } private async retrieveDependencies ( packages , resultset ) { var queryString = 'SELECT Dependencies FROM SubscriberPackageVersion WHERE Id=' ; let dependencies = [ ] ; for ( const _package of packages ) { let resultset = ( await this . retrieveRecords < SubscriberPackageVersion > ( queryString . concat ( `\'${ _package . VersionId }\'` ) ) ) ; for ( const _elem of resultset ) { if ( ! isNull ( _elem . Dependencies ) ) { for ( const _id of _elem . Dependencies [ 'ids' ] ) { const _dependency = { Source : _package , Target : packages . find ( Target => Target . VersionId == _id . subscriberPackageVersionId ) } ; dependencies . push ( _dependency ) ; } } } } ; return dependencies ; } ; }