import { ElementRef , Injectable } from '@angular/core' ; import { IdGenerator } from '../Utils/IdGenerator' ; import { ITreeNode } from '../ViewModels/TreeTypes' ; import { IClusterHealthChunkQueryDescription , IClusterHealthChunk , HealthStateFilterFlags } from '../Models/HealthChunkRawDataTypes' ; import { DeployedReplicaCollection , DeployedCodePackageCollection } from '../Models/DataModels/collections/Collections' ; import { NodeStatusConstants , Constants } from '../Common/Constants' ; import { ClusterHealth , ClusterManifest } from '../Models/DataModels/Cluster' ; import { TreeViewModel } from '../ViewModels/TreeViewModel' ; import { DataService } from './data.service' ; import { RoutesService } from './routes.service' ; import { of , Observable , forkJoin } from 'rxjs' ; import { mergeMap , map , finalize , catchError } from 'rxjs/operators' ; import { SettingsService } from './settings.service' ; import { RefreshService } from './refresh.service' ; @ Injectable ( { providedIn : 'root' } ) export class TreeService { public containerRef : ElementRef ; public tree : TreeViewModel ; private clusterHealth : ClusterHealth ; private cachedTreeSelection : { path : string [ ] ; skipSelectAction ? : boolean ; } ; constructor ( private data , private routes , private settings , private refreshService ) { } public init ( ) { this . clusterHealth = new ClusterHealth ( this . data , HealthStateFilterFlags . None , HealthStateFilterFlags . None , HealthStateFilterFlags . None ) ; this . tree = new TreeViewModel ( ( ) => this . getRootNode ( ) ) ; if ( this . cachedTreeSelection ) { this . tree . selectTreeNode ( this . cachedTreeSelection . path , this . cachedTreeSelection . skipSelectAction ) ; } this . refreshService . refreshSubject . subscribe ( ( ) => this . refresh ( ) . subscribe ( ) ) ; } public selectTreeNode ( path , skipSelectAction ? ) { if ( ! this . tree ) { this . cachedTreeSelection = { path , skipSelectAction } ; return of ( null ) ; } return this . tree . selectTreeNode ( path , skipSelectAction ) ; } public setFirstVisit ( ) { if ( ! this . tree ) { return true ; } if ( this . tree . firstTreeSelect ) { this . tree . firstTreeSelect = false ; return true ; } return false ; } public refresh ( ) { const clusterHealthQueryDescription = this . tree . addHealthChunkFiltersRecursively ( this . data . getInitialClusterHealthChunkQueryDescription ( ) ) ; return this . data . getClusterHealthChunk ( clusterHealthQueryDescription ) . pipe ( mergeMap ( healthChunk => { return forkJoin ( [ this . clusterHealth . mergeHealthStateChunk ( healthChunk ) , this . tree . mergeClusterHealthStateChunk ( healthChunk ) ] ) . pipe ( mergeMap ( ( ) => this . tree . refresh ( ) ) ) ; } ) ) ; } private getRootNode ( ) { return this . clusterHealth . ensureInitialized ( ) . pipe ( map ( clusterHealth => { return [ { nodeId : IdGenerator . cluster ( ) , displayName : ( ) => 'Cluster' , childrenQuery : ( ) => this . getGroupNodes ( ) , selectAction : ( ) => this . routes . navigate ( ( ) => RoutesService . getClusterViewPath ( ) ) , badge : ( ) => clusterHealth . healthState , alwaysVisible : true , startExpanded : true , mergeClusterHealthStateChunk : ( clusterHealthChunk ) => { return forkJoin ( [ this . data . getApps ( ) . pipe ( map ( apps => apps . mergeClusterHealthStateChunk ( clusterHealthChunk ) ) ) , this . data . getNodes ( ) . pipe ( map ( nodes => nodes . mergeClusterHealthStateChunk ( clusterHealthChunk ) ) ) , this . data . getSystemApp ( ) . pipe ( map ( systemApp => systemApp . mergeHealthStateChunk ( clusterHealthChunk . SystemApplicationHealthStateChunk ) ) ) ] ) ; } } ] ; } ) ) ; } private getGroupNodes ( ) { const getAppsPromise = this . data . getApps ( ) . pipe ( map ( apps => { return { nodeId : IdGenerator . appGroup ( ) , displayName : ( ) => 'Applications' , childrenQuery : ( ) => this . getApplicationTypes ( ) , badge : ( ) => apps . healthState , selectAction : ( ) => this . routes . navigate ( ( ) => apps . viewPath ) , listSettings : this . settings . getNewOrExistingTreeNodeListSettings ( apps . viewPath ) , alwaysVisible : true } ; } ) ) ; const getNodesPromise = this . data . getNodes ( ) . pipe ( map ( nodes => { return { nodeId : IdGenerator . nodeGroup ( ) , displayName : ( ) => 'Nodes' , selectAction : ( ) => this . routes . navigate ( ( ) => nodes . viewPath ) , childrenQuery : ( ) => this . getNodes ( ) , badge : ( ) => nodes . healthState , listSettings : this . settings . getNewOrExistingTreeNodeListSettings ( nodes . viewPath ) , alwaysVisible : true } ; } ) ) ; const systemNodePromise = this . data . getSystemApp ( ) . pipe ( catchError ( err => { return of ( null ) ; } ) , map ( systemApp => { if ( systemApp ) { return { nodeId : IdGenerator . systemAppGroup ( ) , displayName : ( ) => Constants . SystemAppTypeName , selectAction : ( ) => this . routes . navigate ( ( ) => systemApp . viewPath ) , childrenQuery : ( ) => this . getServices ( Constants . SystemAppId ) , badge : ( ) => systemApp . healthState , listSettings : this . settings . getNewOrExistingTreeNodeListSettings ( systemApp . viewPath ) , alwaysVisible : true , addHealthStateFiltersForChildren : ( clusterHealthChunkQueryDescription ) => { systemApp . addHealthStateFiltersForChildren ( clusterHealthChunkQueryDescription ) ; } , mergeClusterHealthStateChunk : ( clusterHealthChunk ) => { return systemApp . services . mergeClusterHealthStateChunk ( clusterHealthChunk ) ; } } ; } else { return null ; } } ) ) ; return forkJoin ( [ getAppsPromise , getNodesPromise , systemNodePromise ] ) . pipe ( map ( resp => { if ( resp [ 2 ] === null ) { resp . splice ( 2 ) ; return resp ; } return resp ; } ) ) ; } private getNodes ( ) { return this . data . getNodes ( true ) . pipe ( map ( nodes => { return nodes . collection . map ( node => { return { nodeId : IdGenerator . node ( node . name ) , displayName : ( ) => { let suffix = '' ; if ( node . raw . NodeStatus !== NodeStatusConstants . Up ) { if ( node . raw . IsStopped ) { suffix = 'Down (Stopped)' ; } else { suffix = node . raw . NodeStatus ; if ( node . raw . NodeDeactivationInfo . NodeDeactivationIntent !== NodeStatusConstants . Invalid ) { suffix += ' -> ' + node . raw . NodeDeactivationInfo . NodeDeactivationIntent ; } } } if ( node . raw . IsSeedNode ) { suffix = 'Seed Node' + ( suffix === '' ? '' : ' - ' + suffix ) ; } if ( suffix !== '' ) { return `${ node . name } (${ suffix })` ; } return node . name ; } , selectAction : ( ) => this . routes . navigate ( ( ) => node . viewPath ) , childrenQuery : ( ) => this . getDeployedApplications ( node . name ) , badge : ( ) => node . healthState , sortBy : ( ) => [ node . name ] , actions : node . actions , addHealthStateFiltersForChildren : ( clusterHealthChunkQueryDescription ) => { node . addHealthStateFiltersForChildren ( clusterHealthChunkQueryDescription ) ; } , mergeClusterHealthStateChunk : ( clusterHealthChunk ) => { return node . deployedApps . mergeClusterHealthStateChunk ( clusterHealthChunk ) ; } , listSettings : this . settings . getNewOrExistingTreeNodeListSettings ( node . viewPath ) , } ; } ) ; } ) ) ; } private getApplicationTypes ( ) { return this . data . getAppTypeGroups ( true ) . pipe ( map ( appGroups => { return appGroups . collection . map ( appTypeGroup => { return { nodeId : IdGenerator . appType ( appTypeGroup . name ) , displayName : ( ) => appTypeGroup . name , selectAction : ( ) => this . routes . navigate ( ( ) => appTypeGroup . viewPath ) , childrenQuery : ( ) => this . getApplicationsForType ( appTypeGroup . name ) , badge : ( ) => appTypeGroup . appsHealthState , sortBy : ( ) => [ appTypeGroup . name ] , actions : appTypeGroup . actions , listSettings : this . settings . getNewOrExistingTreeNodeListSettings ( appGroups . viewPath ) , } ; } ) ; } ) ) ; } private getDeployedApplications ( nodeName ) { return this . data . getDeployedApplications ( nodeName ) . pipe ( map ( deployedApps => { return deployedApps . collection . map ( deployedApp => { return { nodeId : IdGenerator . deployedApp ( deployedApp . id ) , displayName : ( ) => deployedApp . name , selectAction : ( ) => this . routes . navigate ( ( ) => deployedApp . viewPath ) , childrenQuery : ( ) => this . getDeployedServicePackages ( nodeName , deployedApp . id ) , badge : ( ) => deployedApp . health . healthState , sortBy : ( ) => [ deployedApp . name ] , listSettings : this . settings . getNewOrExistingTreeNodeListSettings ( deployedApp . viewPath ) , addHealthStateFiltersForChildren : ( clusterHealthChunkQueryDescription ) => { deployedApp . addHealthStateFiltersForChildren ( clusterHealthChunkQueryDescription ) ; } , mergeClusterHealthStateChunk : ( clusterHealthChunk ) => { return deployedApp . deployedServicePackages . mergeClusterHealthStateChunk ( clusterHealthChunk ) ; } , canExpandAll : true } ; } ) ; } ) ) ; } private getDeployedServicePackages ( nodeName , applicationId ) { return this . data . getDeployedServicePackages ( nodeName , applicationId ) . pipe ( map ( deployedServicePackages => { return deployedServicePackages . collection . map ( deployedServicePackage => { return { nodeId : IdGenerator . deployedServicePackage ( deployedServicePackage . name , deployedServicePackage . servicePackageActivationId ) , displayName : ( ) => deployedServicePackage . uniqueId , selectAction : ( ) => this . routes . navigate ( ( ) => deployedServicePackage . viewPath ) , childrenQuery : ( ) => this . getDeployedServiceChildrenGroupNodes ( nodeName , applicationId , deployedServicePackage . name , deployedServicePackage . servicePackageActivationId ) , badge : ( ) => deployedServicePackage . health . healthState , sortBy : ( ) => [ deployedServicePackage . uniqueId ] , canExpandAll : true } ; } ) ; } ) ) ; } private getDeployedServiceChildrenGroupNodes ( nodeName , applicationId , servicePackageName , servicePackageActivationId ) { let codePkgNode ; const getCodePkgsPromise = this . data . getDeployedCodePackages ( nodeName , applicationId , servicePackageName , servicePackageActivationId , true ) . pipe ( map ( codePkgs => { codePkgNode = { nodeId : IdGenerator . deployedCodePackageGroup ( ) , displayName : ( ) => 'Code Packages' , childrenQuery : ( ) => this . getDeployedCodePackages ( codePkgs , nodeName , applicationId , servicePackageName , servicePackageActivationId ) , selectAction : ( ) => this . routes . navigate ( ( ) => codePkgs . viewPath ) , } ; } ) ) ; let replicasNode ; const getReplicasPromise = this . data . getDeployedReplicas ( nodeName , applicationId , servicePackageName , servicePackageActivationId , true ) . pipe ( map ( replicas => { replicasNode = { nodeId : IdGenerator . deployedReplicaGroup ( ) , displayName : ( ) => replicas . isStatelessService ? 'Instances' : 'Replicas' , childrenQuery : ( ) => this . getDeployedReplicas ( replicas , nodeName , applicationId , servicePackageName , servicePackageActivationId ) , selectAction : ( ) => this . routes . navigate ( ( ) => replicas . viewPath ) , listSettings : this . settings . getNewOrExistingTreeNodeListSettings ( replicas . viewPath ) , } ; } ) ) ; return forkJoin ( [ getCodePkgsPromise , getReplicasPromise ] ) . pipe ( map ( ( ) => { return [ codePkgNode , replicasNode ] ; } ) ) ; } private getDeployedCodePackages ( deployedCodePackages , nodeName , applicationId , servicePackageName , servicePackageActivationId ) { return of ( deployedCodePackages . collection . map ( codePackage => { return { nodeId : IdGenerator . deployedCodePackage ( codePackage . name ) , displayName : ( ) => codePackage . uniqueId , selectAction : ( ) => this . routes . navigate ( ( ) => codePackage . viewPath ) , sortBy : ( ) => [ codePackage . uniqueId ] , actions : codePackage . actions } ; } ) ) ; } private getDeployedReplicas ( deployedReplicas , nodeName , applicationId , servicePackageName , servicePackageActivationId ) { return of ( deployedReplicas . collection . map ( replica => { return { nodeId : IdGenerator . deployedReplica ( replica . raw . PartitionId ) , displayName : ( ) => replica . isStatelessService ? replica . id : replica . id + ' (' + replica . role + ')' , selectAction : ( ) => this . routes . navigate ( ( ) => replica . viewPath ) , sortBy : ( ) => replica . isStatelessService ? [ replica . id ] : [ replica . replicaRoleSortPriority , replica . id ] , actions : replica . actions } ; } ) ) ; } private getApplicationsForType ( appTypeName ) { return this . data . getAppTypeGroup ( appTypeName ) . pipe ( map ( appTypeGroup => { return appTypeGroup . apps . map ( app => { return { nodeId : IdGenerator . app ( app . id ) , displayName : ( ) => app . name , selectAction : ( ) => this . routes . navigate ( ( ) => app . viewPath ) , childrenQuery : ( ) => this . getServices ( app . id ) , badge : ( ) => app . healthState , sortBy : ( ) => [ app . name ] , actions : app . actions , listSettings : this . settings . getNewOrExistingTreeNodeListSettings ( app . viewPath ) , addHealthStateFiltersForChildren : ( clusterHealthChunkQueryDescription ) => { app . addHealthStateFiltersForChildren ( clusterHealthChunkQueryDescription ) ; } , mergeClusterHealthStateChunk : ( clusterHealthChunk ) => { return app . services . mergeClusterHealthStateChunk ( clusterHealthChunk ) ; } , canExpandAll : true } ; } ) ; } ) ) ; } private getServices ( appId ) { return this . data . getServices ( appId ) . pipe ( map ( services => { return services . collection . map ( service => { return { nodeId : IdGenerator . service ( service . id ) , displayName : ( ) => service . name , selectAction : ( ) => this . routes . navigate ( ( ) => service . viewPath ) , childrenQuery : ( ) => this . getPartitions ( appId , service . id ) , badge : ( ) => service . healthState , sortBy : ( ) => [ service . name ] , actions : service . actions , listSettings : this . settings . getNewOrExistingTreeNodeListSettings ( service . viewPath ) , addHealthStateFiltersForChildren : ( clusterHealthChunkQueryDescription ) => { service . addHealthStateFiltersForChildren ( clusterHealthChunkQueryDescription ) ; } , mergeClusterHealthStateChunk : ( clusterHealthChunk ) => { return service . partitions . mergeClusterHealthStateChunk ( clusterHealthChunk ) ; } , canExpandAll : true } ; } ) ; } ) ) ; } private getPartitions ( appId , serviceId ) { return this . data . getPartitions ( appId , serviceId ) . pipe ( map ( partitions => { return partitions . collection . map ( partition => { return { nodeId : IdGenerator . partition ( partition . id ) , displayName : ( ) => partition . partitionInformation . isPartitionKindNamed ? `${ partition . partitionInformation . raw . Name } (${ partition . id })` : partition . id , selectAction : ( ) => this . routes . navigate ( ( ) => partition . viewPath ) , childrenQuery : ( ) => this . getReplicas ( appId , serviceId , partition . id ) , badge : ( ) => partition . healthState , sortBy : ( ) => [ partition . name ] , listSettings : this . settings . getNewOrExistingTreeNodeListSettings ( partition . viewPath ) } ; } ) ; } ) ) ; } private getReplicas ( appId , serviceId , partitionId ) { return this . data . getReplicasOnPartition ( appId , serviceId , partitionId , true ) . pipe ( map ( replicas => { return replicas . collection . map ( replica => { return { nodeId : IdGenerator . replica ( replica . id ) , displayName : ( ) => replica . isStatelessService ? replica . raw . NodeName : `${ replica . role } (${ replica . raw . NodeName })` , selectAction : ( ) => this . routes . navigate ( ( ) => replica . viewPath ) , badge : ( ) => replica . healthState , sortBy : ( ) => replica . isStatelessService ? [ replica . raw . NodeName ] : [ replica . replicaRoleSortPriority , replica . raw . NodeName ] , actions : replica . actions , canExpandAll : false } ; } ) ; } ) ) ; } }