import { workspace , extensions } from 'vscode' ; import TelemetryReporter from 'vscode-extension-telemetry' ; import { basename as pathBasename , sep as pathSep } from 'path' ; import { isTestEnvironment } from './constants' ; import { extensionId , appinsightsKey , extensionRoot } from '../constants' ; import * as stackTrace from 'stack-trace' ; import { StopWatch } from './stopWatch' ; /**
 * Checks if the telemetry is disabled in user settings
 */ export function isTelemetryDisabled ( ) { if ( isTestEnvironment ( ) ) { return true ; } const settings = workspace . getConfiguration ( 'telemetry' ) . inspect < boolean > ( 'enableTelemetry' ) ! ; return settings . globalValue === false ? true : false ; } let telemetryReporter ; export function getTelemetryReporter ( ) { if ( isTestEnvironment ( ) || telemetryReporter ) { return telemetryReporter ; } const extension = extensions . getExtension ( extensionId ) ! ; const extensionVersion = extension . packageJSON . version ; return ( telemetryReporter = new TelemetryReporter ( extensionId , extensionVersion , appinsightsKey ) ) ; } export function clearTelemetryReporter ( ) { telemetryReporter = undefined ; } export function sendTelemetryEvent < P extends IEventNamePropertyMapping , E extends keyof P > ( eventName , durationMs ? , properties ? , ex ? ) { const reporter = getTelemetryReporter ( ) ; if ( isTelemetryDisabled ( ) || ! reporter ) { return ; } const measures = typeof durationMs === 'number' ? { duration : durationMs } : durationMs ? durationMs : undefined ; if ( ex && ( eventName as any ) !== 'ERROR' ) { const props = { } ; props . stackTrace = getStackTrace ( ex ) ; props . originalEventName = ( eventName as any ) as string ; reporter . sendTelemetryEvent ( 'ERROR' , props , measures ) ; } const customProperties = { } ; if ( properties ) { const data = properties as any ; Object . getOwnPropertyNames ( data ) . forEach ( prop => { if ( data [ prop ] === undefined || data [ prop ] === null ) { return ; } try { ( customProperties as any ) [ prop ] = typeof data [ prop ] === 'string' ? data [ prop ] : typeof data [ prop ] === 'object' ? 'object' : data [ prop ] . toString ( ) ; } catch ( ex ) { console . error ( `Failed to serialize ${ prop } for ${ eventName }` , ex ) ; } } ) ; } reporter . sendTelemetryEvent ( ( eventName as any ) as string , customProperties , measures ) ; } export function captureTelemetry < P extends IEventNamePropertyMapping , E extends keyof P > ( eventName ? , properties ? , captureDuration = true ) { return function ( target , propertyKey , descriptor ) { const className = target && ( target as Record < string , any > ) . constructor && ( target as Record < string , any > ) . constructor ? ( target as Record < string , any > ) . constructor . name : '' ; const telememtryEvent = eventName || ( `${ className || '' }.${ propertyKey }` as any ) ; const originalMethod = descriptor . value ; descriptor . value = function ( ... args ) { if ( ! captureDuration ) { sendTelemetryEvent ( telememtryEvent , undefined , properties ) ; return originalMethod . apply ( this , args ) ; } const stopWatch = new StopWatch ( ) ; const result = originalMethod . apply ( this , args ) ; if ( result && typeof result . then === 'function' && typeof result . catch === 'function' ) { ( result as Promise < void > ) . then ( ( ) => sendTelemetryEvent ( telememtryEvent , stopWatch . elapsedTime , properties ) ) . catch ( ex => { properties = properties || ( { } as any ) ; ( properties as any ) . failed = true ; sendTelemetryEvent ( telememtryEvent , stopWatch . elapsedTime , properties , ex ) ; } ) ; } else { sendTelemetryEvent ( telememtryEvent , stopWatch . elapsedTime , properties ) ; } return result ; } ; return descriptor ; } ; } export function sendTelemetryWhenDone < P extends IEventNamePropertyMapping , E extends keyof P > ( eventName , promise , stopWatch ? , properties ? ) { stopWatch = stopWatch ? stopWatch : new StopWatch ( ) ; if ( typeof promise . then === 'function' ) { ( promise as Promise < any > ) . then ( ( ) => sendTelemetryEvent ( eventName , stopWatch ! . elapsedTime , properties ) , ex => sendTelemetryEvent ( eventName , stopWatch ! . elapsedTime , properties , ex ) ) ; } else { console . error ( new Error ( 'Method is neither a Promise nor a Theneable' ) ) ; } } function sanitizeFilename ( filename ) { const extensionPath = extensionRoot . path ; if ( ! extensionPath ) { return '<hidden_no_extension_root>' ; } if ( filename . startsWith ( extensionPath ) ) { filename = `<githistory>${ filename . substring ( extensionPath . length ) }` ; } else { filename = `<hidden>${ pathSep }${ pathBasename ( filename ) }` ; } return filename ; } function sanitizeName ( name ) { if ( name . indexOf ( '/' ) === - 1 && name . indexOf ( '\\' ) === - 1 ) { return name ; } else { return '<hidden>' ; } } function getStackTrace ( ex ) { let trace = '' ; for ( const frame of stackTrace . parse ( ex ) ) { let filename = frame . getFileName ( ) ; if ( filename ) { filename = sanitizeFilename ( filename ) ; const lineno = frame . getLineNumber ( ) ; const colno = frame . getColumnNumber ( ) ; trace += `\n\tat ${ getCallsite ( frame ) } ${ filename }:${ lineno }:${ colno }` ; } else { trace += '\n\tat <anonymous>' ; } } return trace . trim ( ) . replace ( /\\/g , '/' ) ; } function getCallsite ( frame ) { const parts = [ ] ; if ( typeof frame . getTypeName ( ) === 'string' && frame . getTypeName ( ) . length > 0 ) { parts . push ( frame . getTypeName ( ) ) ; } if ( typeof frame . getMethodName ( ) === 'string' && frame . getMethodName ( ) . length > 0 ) { parts . push ( frame . getMethodName ( ) ) ; } if ( typeof frame . getFunctionName ( ) === 'string' && frame . getFunctionName ( ) . length > 0 ) { if ( parts . length !== 2 || parts . join ( '.' ) !== frame . getFunctionName ( ) ) { parts . push ( frame . getFunctionName ( ) ) ; } } return parts . map ( sanitizeName ) . join ( '.' ) ; } export interface IEventNamePropertyMapping { /**
     * Total time taken to activate extension.
     */ ACTIVATED : never | undefined ; /**
     * Tracks branch count per repo (hash of repo).
     * Useful in determining how we get information for branches.
     * More branches we have more careful we need to be in getting info (could end up using a lot of resources).
     */ BRANCH_COUNT : { count : number ; repo : string ; } ; FAILED_TO_GET_BRANCHES_WITH_REMOTES : undefined | never ; GET_BRANCHES_FALLBACK : undefined | never ; }