import { logger } from 'gcf-utils' ; export const CONFIG_FILE_NAME = 'auto-label.yaml' ; export const LABEL_PRODUCT_BY_DEFAULT = true ; export const MAX_DAYS = 365 ; export const DEFAULT_DAYS_TO_STALE = 60 ; export const STALE_PREFIX = 'stale:' ; export const SIZE_PREFIX = 'size:' ; export const OLD_LABEL = 'old' ; export const EXTRAOLD_LABEL = 'extraold' ; export const DEFAULT_CONFIGS = { product : LABEL_PRODUCT_BY_DEFAULT , language : { pullrequest : false , } , path : { pullrequest : false , } , staleness : { pullrequest : false , } , requestsize : { enabled : false , } , } ; /**
 * The list of all labels used to mark pull request size.
 * Given a fact that by default pull request size labeling feature is off,
 * we will update those labels in repo only when configuration is enabled
 * Currently there are following labels available:
 *   "size: xs" for pull request with less than 50 changes.
 *   "size: s" for pull request with less than 250 changes.
 *   "size: m" for pull request with less than 1000 changes.
 *   "size: l" for pull request with less than 1250 changes.
 *   "size: xl" for pull request with less than 1500 changes.
 *   "size: xxl" for pull request with more than 1500 changes.
 */ export const PULL_REQUEST_SIZE_LABELS = [ { name : 'size: xs' , description : 'Pull request size is extra small.' , color : '2deb01' , } , { name : 'size: s' , description : 'Pull request size is small.' , color : '2cc785' , } , { name : 'size: m' , description : 'Pull request size is medium.' , color : '5d743d' , } , { name : 'size: l' , description : 'Pull request size is large.' , color : 'd65692' , } , { name : 'size: xl' , description : 'Pull request size is extra large.' , color : '912925' , } , { name : 'size: xxl' , description : 'Pull request size is extra extra large.' , color : 'd22b5f' , } , ] ; /**
 * Checks whether the intended label already exists
 */ export function labelExists ( labels , new_label ) { for ( const label of labels ) { if ( label . name === new_label ) { logger . info ( `Exiting: label ${ new_label } already exists` ) ; return label ; } } return null ; } /**
 * Checks whether the intended label already exists by given prefix
 */ export function fetchLabelByPrefix ( labels , label_prefix ) { if ( ! labels ) { return null ; } for ( const label of labels ) { if ( label . name . startsWith ( label_prefix ) ) { logger . info ( `Exiting: label ${ label . name } found by prefix ${ label_prefix }` ) ; return label ; } } return null ; } /**
 * Checks whether the given time is already expired (e.g. over given days limit)
 */ export function isExpiredByDays ( time , limit ) { const created_at = Date . parse ( time ) ; const diffInMs = Math . abs ( Date . now ( ) - created_at ) ; const diffInDays = diffInMs / ( 1000 * 60 * 60 * 24 ) ; if ( isNaN ( diffInDays ) || typeof diffInDays === 'undefined' ) return false ; return diffInDays > limit ; } /**
 * Checks whether the intended label already exists by given prefix
 */ export function getPullRequestSize ( changes ) { if ( changes >= 1500 ) { return 'xxl' ; } else if ( changes >= 1250 ) { return 'xl' ; } else if ( changes >= 1000 ) { return 'l' ; } else if ( changes >= 250 ) { return 'm' ; } else if ( changes >= 50 ) { return 's' ; } return 'xs' ; } export interface PathConfig { [ index ] : string | PathConfig ; } export interface LanguageConfig { pullrequest ? : boolean ; labelprefix ? : string ; extensions ? : { [ index ] : string [ ] ; } ; paths ? : PathConfig ; } export interface StaleConfig { pullrequest ? : boolean ; old ? : number ; extraold ? : number ; } export interface PullRequestSizeConfig { enabled ? : boolean ; } export interface Config { enabled ? : boolean ; product ? : boolean ; path ? : { pullrequest ? : boolean ; labelprefix ? : string ; paths ? : PathConfig ; } ; language ? : LanguageConfig ; staleness ? : StaleConfig ; requestsize ? : PullRequestSizeConfig ; } export interface Label { name : string ; } export interface DriftApi { github_label : string ; } export interface DriftRepo { github_label : string ; repo : string ; } param apis param title export function autoDetectLabel ( apis , title ) { if ( ! apis || ! title ) { return undefined ; } const conv = /[^(]+\(([^)]+)\):/ ; const match = title . match ( conv ) ; let firstPart = match ? match [ 1 ] : title ; const trimPrefixes = [ 'com.example.' , 'com.google.' , 'snippets.' ] ; for ( const prefix of trimPrefixes ) { if ( firstPart . startsWith ( prefix ) ) { firstPart = firstPart . slice ( prefix . length ) ; } } if ( firstPart . startsWith ( '/' ) ) firstPart = firstPart . substr ( 1 ) ; firstPart = firstPart . split ( ':' ) [ 0 ] ; firstPart = firstPart . split ( '/' ) [ 0 ] ; firstPart = firstPart . split ( '.' ) [ 0 ] ; firstPart = firstPart . split ( '_' ) [ 0 ] ; firstPart = firstPart . toLowerCase ( ) ; firstPart = firstPart . replace ( /\s/ , '' ) ; firstPart = firstPart . replace ( '-' , '' ) ; if ( firstPart === 'docs' || firstPart === 'build' ) { return undefined ; } const commonConversions = new Map ( ) ; commonConversions . set ( 'video' , 'videointelligence' ) ; commonConversions . set ( 'spannertest' , 'spanner' ) ; commonConversions . set ( 'spansql' , 'spanner' ) ; commonConversions . set ( 'media' , 'mediatranslation' ) ; firstPart = commonConversions . get ( firstPart ) || firstPart ; const possibleLabels = [ `api: ${ firstPart }` , `api: cloud${ firstPart }` ] ; return apis . find ( api => possibleLabels . indexOf ( api . github_label ) > - 1 ) ?. github_label ; } import defaultExtensions from './extensions.json' ; function getLabelFromPathConfig ( filename , config ) { let label = '' ; const dirs = filename . split ( '/' ) ; let paths = config . paths ! ; if ( '.' in paths ) { label = paths [ '.' ] ; } for ( const dir of dirs ) { if ( dir in paths ) { if ( '.' in paths ) label = paths [ '.' ] ; if ( typeof paths [ dir ] === 'string' ) { label = paths [ dir ] ; break ; } else { paths = paths [ dir ] as PathConfig ; } } else { break ; } } return label as string ; } param filename function getFileLabel ( filename , config , type ) { if ( config . paths ) { const lang = getLabelFromPathConfig ( filename , config ) ; if ( lang ) { if ( config . labelprefix ) { return config . labelprefix + lang ; } return lang ; } } if ( type === 'language' ) { const extensionMap = config . extensions ? { ... config . extensions , ... defaultExtensions } : defaultExtensions ; const ext = filename . substring ( filename . lastIndexOf ( '.' ) + 1 ) ; const lang = Object . keys ( extensionMap ) . find ( key => { return extensionMap [ key ] . includes ( ext ) ; } ) ; if ( ! lang ) { return '' ; } if ( config . labelprefix ) { return config . labelprefix + lang ; } return lang ; } return '' ; } /**
 * FileData
 * Extracting relevant data from each file changed from github.pulls.listFiles
 */ interface FileData { filename : string ; changes : number ; } /**
 * getLabel
 * Output: "[prefix]label"
 * Map reduces changes in files and its corresponding label
 * Returns the highest frequency language_label OR path_label
 */ export function getLabel ( data , config , type ) { const counts = data . reduce ( ( counted , file ) => { const l = getFileLabel ( file . filename , config , type ) ; if ( l ) { if ( ! counted [ l ] ) { counted [ l ] = file . changes ; } else { counted [ l ] += file . changes ; } } return counted ; } , { } ) ; const label = Object . keys ( counts ) . sort ( ( a , b ) => { return counts [ b ] - counts [ a ] ; } ) ; logger . info ( 'Detected labels based on files extensions are: ' + label ) ; return label [ 0 ] ; }