async function processRepository()

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);
  }
}