import ErrorAction from "../../../classes/status/ErrorAction" ; import LoadAction from "../../../classes/status/LoadAction" ; import { TreeElement } from "../../../classes/TreeElement" ; import { lazyInject } from "../../../inversify.config" ; import AppStatusStore from "../../../stores/AppStatusStore" ; import SceneStore from "../../../stores/SceneStore" ; import TreeService from "../../TreeService" ; import { BackendService } from "../BackendService" ; import SonarQubeApiScm from "../SonarQubeApiScm" ; import ScmAuthorsCalculatorService from "./ScmAuthorsCalculatorService" ; import ScmCommitsCalculatorService from "./ScmCommitsCalculatorService" ; export default class SonarQubeScmService extends BackendService { public static readonly LOAD_SCM : LoadAction = new LoadAction ( "SONAR_LOAD_SCM" , "Request scm infos from SonarQube" ) ; public static readonly STATUS_SCM_NOT_AVAILABLE : LoadAction = new LoadAction ( "STATUS_SCM_NOT_AVAILABLE" , "SCM blame info is not available. Please check your scm plugin." ) ; private static readonly LOAD_SCM_ERROR_KEY = "LOAD_SCM_ERROR" ; @ lazyInject ( "SceneStore" ) private readonly sceneStore ! : SceneStore ; @ lazyInject ( "AppStatusStore" ) private readonly appStatusStore ! : AppStatusStore ; @ lazyInject ( "TreeService" ) private readonly treeService ! : TreeService ; @ lazyInject ( "ScmAuthorsCalculatorService" ) private readonly scmCalculator ! : ScmAuthorsCalculatorService ; @ lazyInject ( "ScmCommitsCalculatorService" ) private readonly scmCommitsCalculatorService ! : ScmCommitsCalculatorService ; public assertScmInfoAreLoaded ( ) { return new Promise < void > ( ( resolve ) => { if ( this . sceneStore . scmMetricLoaded ) { resolve ( ) ; return ; } this . loadScmInfosIfAvailable ( ) . then ( ( ) => { this . sceneStore . scmMetricLoaded = true ; resolve ( ) ; } ) ; } ) ; } public checkScmInfosAvailable ( ) { return new Promise < boolean > ( ( resolve , reject ) => { if ( this . sceneStore . projectData !== null ) { let allFiles = this . treeService . getAllFiles ( this . sceneStore . projectData ) ; allFiles = allFiles . slice ( 0 , 10 ) ; const requests = [ ] ; for ( const file of allFiles ) { requests . push ( this . loadScmInfosFor ( file ) ) ; } Promise . all ( requests ) . then ( ( ) => { const isScmMetricAvailable = this . checkScmMetricAvailable ( allFiles ) ; if ( ! isScmMetricAvailable ) { this . appStatusStore . status ( SonarQubeScmService . STATUS_SCM_NOT_AVAILABLE ) ; } resolve ( isScmMetricAvailable ) ; } ) . catch ( ( ) => { reject ( ) ; } ) ; } else { resolve ( false ) ; } } ) ; } public loadScmInfos ( ) { this . appStatusStore . load ( SonarQubeScmService . LOAD_SCM ) ; return new Promise < void > ( ( resolve , reject ) => { if ( this . sceneStore . projectData !== null ) { const allFiles = this . treeService . getAllFiles ( this . sceneStore . projectData ) ; this . loadScmInfosBatch ( allFiles ) . then ( ( ) => { this . appStatusStore . loadComplete ( SonarQubeScmService . LOAD_SCM ) ; resolve ( ) ; } ) . catch ( ( ) => { reject ( ) ; } ) ; } else { resolve ( ) ; } } ) ; } private loadScmInfosIfAvailable ( ) { return new Promise < void > ( ( resolve ) => { this . checkScmInfosAvailable ( ) . then ( ( isAvailable ) => { if ( isAvailable ) { this . loadScmInfos ( ) . then ( ( ) => resolve ( ) ) ; } else { resolve ( ) ; } } ) ; } ) ; } private checkScmMetricAvailable ( allFiles ) { for ( const file of allFiles ) { if ( file . measures . number_of_authors > 0 ) { return true ; } } return false ; } private loadScmInfosBatch ( allFiles , page = 0 ) { return new Promise < void > ( ( resolve , reject ) => { const pageSize = 75 ; const pagesMax = Math . floor ( allFiles . length / pageSize ) + 1 ; this . appStatusStore . loadStatusUpdate ( SonarQubeScmService . LOAD_SCM . key , pagesMax , page ) ; const requests = [ ] ; const start = page * pageSize ; let end = page * pageSize + pageSize ; if ( allFiles . length < end ) { end = allFiles . length ; } for ( let i = start ; i < end ; i ++ ) { requests . push ( this . loadScmInfosFor ( allFiles [ i ] ) ) ; } Promise . all ( requests ) . then ( ( ) => { const isNextBatchRequired = allFiles . length > page * pageSize + pageSize ; if ( isNextBatchRequired ) { this . loadScmInfosBatch ( allFiles , page + 1 ) . then ( ( ) => { resolve ( ) ; } ) ; } else { resolve ( ) ; } } ) . catch ( ( ) => { reject ( ) ; } ) ; } ) ; } private loadScmInfosFor ( element ) { return new Promise < void > ( ( resolve , reject ) => { const params = { key : element . key } ; this . callApi ( "/sources/scm" , { params } ) . then ( ( response ) => { const metrics = response . data . scm . map ( ( measure ) => { return new SonarQubeApiScm ( + measure [ 0 ] , measure [ 1 ] , measure [ 2 ] , measure [ 3 ] ) ; } ) ; element . measures = Object . assign ( element . measures , { number_of_authors : this . scmCalculator . calcNumberOfAuthors ( metrics ) , number_of_commits : this . scmCommitsCalculatorService . calcNumberOfCommits ( metrics ) , } ) ; resolve ( ) ; } ) . catch ( ( error ) => { this . appStatusStore . error ( new ErrorAction ( SonarQubeScmService . LOAD_SCM_ERROR_KEY , "SonarQube metric API is not available or responding: " + error . response , "Try again" , ( ) => { location . reload ( ) ; } ) ) ; this . appStatusStore . loadComplete ( SonarQubeScmService . LOAD_SCM ) ; reject ( ) ; } ) ; } ) ; } }