import type { WebSiteManagementModels } from '@azure/arm-appservice' ; import * as os from 'os' ; import { ProgressLocation , ThemeIcon , window } from 'vscode' ; import { AzExtTreeItem , IActionContext , openReadOnlyContent , TreeItemIconPath } from 'vscode-azureextensionui' ; import { KuduModels } from 'vscode-azurekudu' ; import { createKuduClient } from '../createKuduClient' ; import { waitForDeploymentToComplete } from '../deploy/waitForDeploymentToComplete' ; import { ext } from '../extensionVariables' ; import { localize } from '../localize' ; import { ignore404Error , retryKuduCall } from '../utils/kuduUtils' ; import { nonNullProp } from '../utils/nonNull' ; import { openUrl } from '../utils/openUrl' ; import { DeploymentsTreeItem } from './DeploymentsTreeItem' ; enum DeployStatus { Building = 0 , Deploying = 1 , Pending = 2 , Failed = 3 , Success = 4 } /**
 * NOTE: This leverages two commands prefixed with `ext.prefix` that should be registered by each extension: "showOutputChannel" and "viewDeploymentLogs"
 */ export class DeploymentTreeItem extends AzExtTreeItem { public static contextValue : RegExp = new RegExp ( 'deployment\/.*' ) ; public readonly contextValue : string ; public label : string ; public receivedTime : Date ; public parent : DeploymentsTreeItem ; private _deployResult : KuduModels . DeployResult ; constructor ( parent , deployResult , scmType ) { super ( parent ) ; this . contextValue = `deployment/${ scmType }` . toLocaleLowerCase ( ) ; this . _deployResult = deployResult ; this . receivedTime = nonNullProp ( deployResult , 'receivedTime' ) ; const message = this . getDeploymentMessage ( deployResult ) ; this . label = `${ this . id . substring ( 0 , 7 ) } - ${ message }` ; } public get iconPath ( ) : TreeItemIconPath { return new ThemeIcon ( 'git-commit' ) ; } public get id ( ) : string { this . _deployResult . id = nonNullProp ( this . _deployResult , 'id' ) ; return this . _deployResult . id ; } public get commandId ( ) : string { return `${ ext . prefix }.viewDeploymentLogs` ; } public get description ( ) : string | undefined { if ( this . _deployResult . active ) { return localize ( 'active' , 'Active' ) ; } switch ( this . _deployResult . status ) { case DeployStatus . Building : return localize ( 'building' , 'Building...' ) ; case DeployStatus . Deploying : return localize ( 'deploying' , 'Deploying...' ) ; case DeployStatus . Pending : return localize ( 'pending' , 'Pending...' ) ; case DeployStatus . Failed : return localize ( 'failed' , 'Failed' ) ; case DeployStatus . Success : default : return ; } } public isAncestorOfImpl ( contextValue ) { return this . contextValue === contextValue ; } public async redeployDeployment ( context ) { if ( this . _deployResult . isReadonly ) { throw new Error ( localize ( 'redeployNotSupported' , 'Redeploy is not supported for non-git deployments.' ) ) ; } const redeploying = localize ( 'redeploying' , 'Redeploying commit "{0}" to "{1}". Check [output window](command:{2}) for status.' , this . id , this . parent . site . fullName , ext . prefix + '.showOutputChannel' ) ; const redeployed = localize ( 'redeployed' , 'Commit "{0}" has been redeployed to "{1}".' , this . id , this . parent . site . fullName ) ; await window . withProgress ( { location : ProgressLocation . Notification , title : redeploying } , async ( ) : Promise < void > => { ext . outputChannel . appendLog ( localize ( 'reployingOutput' , 'Redeploying commit "{0}" to "{1}"...' , this . id , this . parent . site . fullName ) , { resourceName : this . parent . site . fullName } ) ; const kuduClient = await createKuduClient ( context , this . parent . site ) ; void kuduClient . deployment . deploy ( this . id ) ; const refreshingInteveral = setInterval ( async ( ) => { await this . refresh ( context ) ; } , 1000 ) ; try { await waitForDeploymentToComplete ( context , this . parent . site , this . id ) ; await this . parent . refresh ( context ) ; void window . showInformationMessage ( redeployed ) ; ext . outputChannel . appendLog ( redeployed ) ; } finally { clearInterval ( refreshingInteveral ) ; } } ) ; } public async getDeploymentLogs ( context ) { const kuduClient = await createKuduClient ( context , this . parent . site ) ; let logEntries = [ ] ; await retryKuduCall ( context , 'getLogEntry' , async ( ) => { await ignore404Error ( context , async ( ) => { logEntries = await kuduClient . deployment . getLogEntry ( this . id ) ; } ) ; } ) ; let data = '' ; for ( const logEntry of logEntries ) { data += this . formatLogEntry ( logEntry ) ; let detailedLogEntries = [ ] ; await retryKuduCall ( context , 'getLogEntryDetails' , async ( ) => { await ignore404Error ( context , async ( ) => { if ( logEntry . detailsUrl && logEntry . id ) { detailedLogEntries = await kuduClient . deployment . getLogEntryDetails ( this . id , logEntry . id ) ; } } ) ; } ) ; for ( const detailedEntry of detailedLogEntries ) { data += this . formatLogEntry ( detailedEntry ) ; } } return data ; } public async viewDeploymentLogs ( context ) { await this . runWithTemporaryDescription ( context , localize ( 'retrievingLogs' , 'Retrieving logs...' ) , async ( ) => { const logData = await this . getDeploymentLogs ( context ) ; await openReadOnlyContent ( this , logData , '.log' ) ; } ) ; } public async viewCommitInGitHub ( context ) { const client = await this . parent . site . createClient ( context ) ; const sourceControl = await client . getSourceControl ( ) ; if ( sourceControl . repoUrl ) { const gitHubCommitUrl = `${ sourceControl . repoUrl }/commit/${ this . _deployResult . id }` ; await openUrl ( gitHubCommitUrl ) ; return ; } else { throw new Error ( localize ( 'noRepoUrl' , 'There is no GitHub repo url associated with deployment "{0}".' , this . _deployResult . id ) ) ; } } public async refreshImpl ( context ) { const kuduClient = await createKuduClient ( context , this . parent . site ) ; this . _deployResult = await kuduClient . deployment . getResult ( this . id ) ; } private formatLogEntry ( logEntry ) { if ( logEntry . logTime && logEntry . message ) { return `${ logEntry . logTime . toISOString ( ) } - ${ logEntry . message }${ os . EOL }` ; } else { return '' ; } } private getDeploymentMessage ( deployResult ) { let message = nonNullProp ( deployResult , 'message' ) ; try { const messageJSON = < { message ? : string ; } > JSON . parse ( message ) ; if ( messageJSON . message ) { message = messageJSON . message ; } } catch { } const firstLine = this . getFirstLine ( message ) ; return firstLine . length > 50 ? `${ firstLine . substring ( 0 , 50 ) }...` : firstLine ; } private getFirstLine ( message ) { const allLineBreaks = /\r?\n|\r/ ; const index = message . search ( allLineBreaks ) ; if ( index >= 0 ) { message = message . substring ( 0 , index ) ; } return message ; } }