in jobs/reports/repositories.ts [249:458]
async function processRepository(context: IReportsContext, repository: Repository) {
console.log('repository: ' + context.processing.repos.remaining-- + ': ' + repository.full_name);
const organization = repository.organization;
// repo context
const repositoryContext: IReportsRepositoryContext = {
parent: context,
definitionsUsed: new Set(),
issues: {},
name: repository.full_name,
nameLowercase: repository.full_name.toLowerCase(),
repository,
countOfAdministratorCollaborators: 0,
countOfAdministratorTeams: 0,
};
const campaignSettings = context.settings.campaign;
function reposDirectLink(content, suffix?, alternateForRepoFullPath?): string {
const reposUrl = context.config.urls.repos;
const q = getCampaignData(content);
let fullPath = `${organization.name}/repos/${repository.name}`;
if (suffix) {
fullPath + '/' + suffix;
}
return reposUrl + (alternateForRepoFullPath || fullPath) + '?' + querystring.stringify(q as any);
}
function getCampaignData(content): ICampaignData {
return {
utm_source: campaignSettings.source,
utm_medium: campaignSettings.medium,
utm_campaign: campaignSettings.campaign,
utm_content: content,
};
}
function githubDirectLink(content, prefix?, suffix?, query?, alternateForRepoFullName?) {
const reposUrl = context.config.urls.repos;
const repoFullName = repositoryContext.name; // full_name
const q = getCampaignData(content);
q.go_github = null;
if (prefix) {
q.go_github_prefix = prefix;
}
if (suffix) {
q.go_github = suffix;
}
if (query) {
q.go_github_query = query;
}
return reposUrl + (alternateForRepoFullName || repoFullName) + '?' + querystring.stringify(q as any);
}
if (!context.repositoryData) {
context.repositoryData = [];
}
context.repositoryData.push(repositoryContext);
await getRepositoryDetails(repositoryContext);
const administrators = await getRepositoryAdministrators(repositoryContext);
repositoryContext.administrators = administrators;
await gatherLinkData(repositoryContext, administrators);
await identifyActionableAdmins(repositoryContext, repository, administrators);
await identityAdministratorsWithoutLinks(repositoryContext);
const privateEngineering = organization.privateEngineering;
const basicRepository: IBasicRepository = {
repoName: repository.name,
entityName: repository.full_name,
orgName: organization.name,
// Pre-populate; overwritten if and when an approval is found
approvalType: {
color: 'gray',
text: 'Created on GitHub or unknown',
},
countOfAdministratorCollaborators: repositoryContext.countOfAdministratorCollaborators || '-',
countOfAdministratorTeams: repositoryContext.countOfAdministratorTeams || '-',
};
await getNewRepoCreationInformation(context, repositoryContext, basicRepository);
const publicPrivateStatus = {
text: repository.private ? 'Private' : 'Public',
color: repository.private ? 'red' : 'green',
};
basicRepository.status = publicPrivateStatus;
// Recipients
repositoryContext.recipients = [];
const corporateAdministrators = [];
if (repositoryContext.actionableAdministrators) {
for (let y = 0; y < repositoryContext.actionableAdministrators.length; y++) {
const admin = repositoryContext.actionableAdministrators[y];
if (admin && admin.link && admin.link.aadupn) {
corporateAdministrators.push(admin.link.aadupn);
if (!privateEngineering) {
// Private engineering orgs do not send individuals nags on emails for now
repositoryContext.recipients.push({
type: 'upn',
value: admin.link.aadupn,
reasons: transformReasonsToArray(admin, repository.full_name),
});
}
}
}
}
// Send to org admins
const orgName = repository.organization.name;
const orgData = context.organizationData[orgName];
for (let i = 0; orgData && orgData.organizationContext && orgData.organizationContext.recipients && orgData.organizationContext.recipients.length && i < orgData.organizationContext.recipients.length; i++) {
repositoryContext.recipients.push(orgData.organizationContext.recipients[i]);
}
// Basic administrators info
basicRepository.administrators = 'None';
if (corporateAdministrators.length > 0) {
let caLink = 'mailto:' + corporateAdministrators.join(';') + '?subject=' + repository.full_name;
const peoplePlurality = corporateAdministrators.length > 1 ? 'people' : 'person';
basicRepository.administrators = {
link: caLink,
text: `${corporateAdministrators.length} ${peoplePlurality}`,
};
}
const actionEditCollaborators = {
link: githubDirectLink('editRepoPermissions', null, 'settings/collaboration'),
text: 'Permissions',
};
const actionDelete = {
link: githubDirectLink('repoDeleteOrTransfer', null, 'settings'),
text: 'Consider deleting or transferring',
};
const actionView = {
link: githubDirectLink('repoBrowse'),
text: 'Open',
};
const actionShip = {
link: githubDirectLink('repoShipIt', null, 'settings'),
text: 'Ship it',
};
const actionViewInPortal = context.config.urls ? {
link: reposDirectLink('repoDetails'),
text: 'Details',
} : null;
if (repositoryContext.administratorsByType.linked.length === 0 || repositoryContext.actionableAdministrators.length === 0) {
addEntityToIssueType(context, repositoryContext, 'noRepositoryAdministrators', basicRepository, actionEditCollaborators, actionViewInPortal);
}
let createdAt = repository.created_at ? moment(repository.created_at) : null;
if (createdAt) {
basicRepository.created = createdAt.format(simpleDateFormat);
}
let updatedAt = repository.updated_at ? moment(repository.updated_at) : null;
if (updatedAt) {
basicRepository.updated = updatedAt.format(simpleDateFormat);
}
let pushedAt = repository.pushed_at ? moment(repository.pushed_at) : null;
if (pushedAt) {
basicRepository.pushed = pushedAt.format(simpleDateFormat);
}
let mostRecentActivityMoment = createdAt;
let mostRecentActivity = 'Created';
if (updatedAt && updatedAt.isAfter(mostRecentActivityMoment)) {
mostRecentActivity = 'Updated';
mostRecentActivityMoment = updatedAt;
}
if (pushedAt && pushedAt.isAfter(mostRecentActivityMoment)) {
mostRecentActivity = 'Pushed';
mostRecentActivityMoment = pushedAt;
}
const twoYearsAgo = moment().subtract(2, 'years');
const oneYearAgo = moment().subtract(1, 'years');
const nineMonthsAgo = moment().subtract(9, 'months');
const thirtyDaysAgo = moment().subtract(30, 'days');
const thisWeek = moment().subtract(7, 'days');
const today = moment().subtract(1, 'days');
const ageInMonths = today.diff(createdAt, 'months');
if (ageInMonths > 0) {
basicRepository.ageInMonths = ageInMonths === 1 ? '1 month' : ageInMonths + ' months';
}
const monthsSinceUpdates = today.diff(mostRecentActivityMoment, 'months');
const timeAsString = monthsSinceUpdates + ' month' + (monthsSinceUpdates === 1 ? '' : 's');
basicRepository.recentActivity = monthsSinceUpdates < 1 ? 'Active' : `${timeAsString} (${mostRecentActivity})`;
if (mostRecentActivityMoment.isBefore(nineMonthsAgo)) {
basicRepository.abandoned = {
text: `${monthsSinceUpdates} months`,
color: 'red',
};
}
if (exemptRepositories && exemptRepositories[repository.id] && exemptRepositories[repository.id].approved && exemptRepositories[repository.id].days) {
const exemptionExpiresAt = moment(exemptRepositories[repository.id].approved)
.add(exemptRepositories[repository.id].days, 'days')
.subtract(2, 'weeks');
if (moment().isAfter(exemptionExpiresAt)) {
basicRepository.exemptionExpiresAt = exemptionExpiresAt.format(simpleDateFormat);
addEntityToIssueType(context, repositoryContext, 'expiringPrivateEngineeringExemptions', basicRepository, actionShip, actionDelete);
}
} else if (!repository.private && mostRecentActivityMoment.isBefore(twoYearsAgo)) {
addEntityToIssueType(context, repositoryContext, 'abandonedPublicRepositories', basicRepository, actionView, actionDelete);
} else if (repository.private && mostRecentActivityMoment.isBefore(twoYearsAgo)) {
addEntityToIssueType(context, repositoryContext, 'twoYearOldPrivateRepositories', basicRepository, actionView, actionDelete);
} else if (repository.private && createdAt.isBefore(oneYearAgo) && !privateEngineering) {
addEntityToIssueType(context, repositoryContext, 'oneYearOldPrivateRepositories', basicRepository, actionView, actionDelete);
} else if (repository.private && createdAt.isBefore(thirtyDaysAgo) && !privateEngineering) {
addEntityToIssueType(context, repositoryContext, 'privateRepositoriesLessThanOneYear', basicRepository, actionShip, actionDelete);
} else if (createdAt.isAfter(thisWeek) && !privateEngineering) {
// New public and private repos
const repositoryForManagerAndLawyer = shallowCloneWithAdditionalRecipients(basicRepository, repositoryContext.additionalRecipients);
if (createdAt.isAfter(today)) {
addEntityToIssueType(context, repositoryContext, 'NewReposToday', repositoryForManagerAndLawyer, actionView, actionViewInPortal);
}
// Always include in the weekly summary
addEntityToIssueType(context, repositoryContext, 'NewReposWeek', repositoryForManagerAndLawyer, actionView, actionViewInPortal);
}
// Alert on too many administrators, excluding private engineering organizations at this time
// NOTE: commenting out the "too many" notice for September 2017
//if (!privateEngineering && repositoryContext.actionableAdministrators.length > context.settings.tooManyRepoAdministrators) {
//addEntityToIssueType(context, repositoryContext, 'repositoryTooManyAdministrators', basicRepository, actionViewInPortal, actionEditCollaborators);
//}
if (context.settings.repoDelayAfter) {
await sleep(context.settings.repoDelayAfter);
}
}