import { ApplicationInsightsQueryResponse , Column } from 'azure-services' ; import { AvailabilityTestConfig , getSerializableResponse , ResponseWithBodyType , ServiceConfiguration } from 'common' ; import { inject , injectable } from 'inversify' ; import { ContextAwareLogger } from 'logger' ; import { ApiController , HealthReport , HttpResponse , TestEnvironment , TestRun , TestRunResult , WebApiErrorCodes } from 'service-library' ; import { createHealthCheckQueryForRelease } from '../health-check-query' ; import { ApplicationInsightsClientProvider , webApiTypeNames } from '../web-api-types' ; export declare type HealthTarget = 'release' ; @ injectable ( ) export class HealthCheckController extends ApiController { public readonly apiVersion = '1.0' ; public readonly apiName = 'web-api-health-check' ; public constructor ( @ inject ( ServiceConfiguration ) protected readonly serviceConfig , @ inject ( ContextAwareLogger ) logger , @ inject ( webApiTypeNames . ApplicationInsightsClientProvider ) protected readonly appInsightsClientProvider , protected readonly createQueryForRelease = createHealthCheckQueryForRelease ) { super ( logger ) ; } public async handleRequest ( ) { this . logger . trackEvent ( 'HealthCheck' ) ; this . logger . setCommonProperties ( { source : 'getHealthCheckReportRESTApi' } ) ; const target = this . context . bindingData . target as HealthTarget ; if ( target === undefined ) { this . processEchoHealthRequest ( ) ; } else if ( target === 'release' ) { await this . processReleaseHealthRequest ( ) ; } else { this . context . res = HttpResponse . getErrorResponse ( WebApiErrorCodes . resourceNotFound ) ; } } protected validateApiVersion ( ) { return true ; } private async processReleaseHealthRequest ( ) { const releaseId = this . getReleaseId ( ) ; if ( releaseId === undefined ) { this . context . res = HttpResponse . getErrorResponse ( WebApiErrorCodes . missingReleaseVersion ) ; return ; } const queryResponse = await this . executeAppInsightsQuery ( releaseId ) ; if ( queryResponse . statusCode !== 200 ) { this . context . res = HttpResponse . getErrorResponse ( WebApiErrorCodes . internalError ) ; return ; } const healthReport = this . getHealthReport ( queryResponse . body , releaseId ) ; this . context . res = { status : 200 , body : healthReport , } ; } private async executeAppInsightsQuery ( releaseId ) { const appInsightsClient = await this . appInsightsClientProvider ( ) ; const logQueryTimeRange = ( await this . getAvailabilityTestConfig ( ) ) . logQueryTimeRange ; const queryString = this . createQueryForRelease ( releaseId ) ; const queryResponse = await appInsightsClient . executeQuery ( queryString , logQueryTimeRange ) ; if ( queryResponse . statusCode === 200 ) { this . logger . logInfo ( 'App Insights query succeeded.' , { query : queryString , statusCode : queryResponse . statusCode . toString ( ) , response : JSON . stringify ( getSerializableResponse ( queryResponse ) ) , } ) ; } else { this . logger . logError ( 'App Insights query failed.' , { query : queryString , statusCode : queryResponse . statusCode . toString ( ) , response : JSON . stringify ( getSerializableResponse ( queryResponse ) ) , } ) ; } return queryResponse ; } private getHealthReport ( queryResponse , releaseId ) { const table = queryResponse . tables [ 0 ] ; const columns = table . columns ; let testsPassed = 0 ; let testsFailed = 0 ; const testRuns = [ ] ; table . rows . forEach ( ( row ) => { const result = this . getColumnValue ( columns , row , 'result' ) as TestRunResult ; if ( result === 'pass' ) { testsPassed += 1 ; } else { testsFailed += 1 ; } const testRun = { testContainer : this . getColumnValue ( columns , row , 'testContainer' ) , testName : this . getColumnValue ( columns , row , 'testName' ) , scenarioName : this . getColumnValue ( columns , row , 'scenarioName' ) , scanId : this . getColumnValue ( columns , row , 'scanId' ) , result : result , timestamp : new Date ( this . getColumnValue ( columns , row , 'timestamp' ) ) , } ; if ( result === 'fail' ) { testRun . error = this . getColumnValue ( columns , row , 'error' ) ; } testRuns . push ( testRun ) ; } ) ; const environment = this . getColumnValue ( columns , table . rows [ 0 ] , 'environment' ) as TestEnvironment ; const runId = this . getColumnValue ( columns , table . rows [ 0 ] , 'runId' ) ; const healthStatus = testRuns . length > 0 ? ( testsFailed === 0 ? 'pass' : 'fail' ) : 'warn' ; return { healthStatus , environment : environment , releaseId : releaseId , runId : runId , testRuns : testRuns , testsPassed : testsPassed , testsFailed : testsFailed , } ; } private getReleaseId ( ) { const releaseId = < string > this . context . bindingData . targetId ; return releaseId !== undefined ? releaseId : process . env . RELEASE_VERSION ; } private processEchoHealthRequest ( ) { this . context . res = { status : 200 , } ; } private getColumnValue ( columns , row , columnName ) { const index = columns . findIndex ( ( c ) => c . name === columnName ) ; return index > - 1 && row !== undefined && row . length > index ? row [ columns . findIndex ( ( c ) => c . name === columnName ) ] : undefined ; } private async getAvailabilityTestConfig ( ) { return this . serviceConfig . getConfigValue ( 'availabilityTestConfig' ) ; } }