app/lib/PRUpdater.scala (113 lines of code) (raw):

package lib import org.apache.pekko.stream.Materializer import com.madgag.scalagithub.GitHub import com.madgag.scalagithub.commands.CreateComment import com.madgag.scalagithub.model.{PullRequest, Repo} import com.madgag.time.Implicits._ import com.typesafe.scalalogging.LazyLogging import lib.Config.CheckpointMessages import lib.RepoSnapshot.WorthyOfCommentWindow import lib.Responsibility.responsibilityAndRecencyFor import lib.gitgithub.LabelMapping import lib.labels.{Overdue, PullRequestCheckpointStatus, Seen} import lib.sentry.{PRSentryRelease, SentryApiClient} import java.time.Instant import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future class PRUpdater(delayer: Delayer) extends LazyLogging { def process(prSnapshot: PRSnapshot, repoSnapshot: RepoSnapshot)(implicit g: GitHub, m: Materializer, sentryApiClientOpt: Option[SentryApiClient] ): Future[Option[PullRequestCheckpointsStateChangeSummary]] = { logger.trace(s"handling ${prSnapshot.pr.prId.slug}") for { snapshot <- getSummaryOfCheckpointChangesGiven(prSnapshot, repoSnapshot) } yield snapshot } private def getSummaryOfCheckpointChangesGiven(prSnapshot: PRSnapshot, repoSnapshot: RepoSnapshot)(implicit gitHub: GitHub, sentryApiClientOpt: Option[SentryApiClient] ): Future[Option[PullRequestCheckpointsStateChangeSummary]] = { val pr = prSnapshot.pr val (oldStateLabelsSeq, userLabels) = prSnapshot.labels.map(_.name).partition(repoSnapshot.allPossibleCheckpointPRLabels) val oldLabels = oldStateLabelsSeq.toSet val existingPersistedState: PRCheckpointState = repoSnapshot.labelToStateMapping.stateFrom(oldLabels) if (!ignoreItemsWithExistingState(existingPersistedState)) { for (currentSnapshot <- findCheckpointStateChange(existingPersistedState, pr, repoSnapshot)) yield { val newPersistableState = currentSnapshot.newPersistableState val stateChanged = newPersistableState != existingPersistedState logger.debug(s"handling ${pr.prId.slug} : state: existing=$existingPersistedState new=$newPersistableState stateChanged=$stateChanged") if (stateChanged) { logger.info(s"#${pr.prId.slug} state-change: $existingPersistedState -> $newPersistableState") val newLabels: Set[String] = repoSnapshot.labelToStateMapping.labelsFor(newPersistableState) assert(oldLabels != newLabels, s"Labels should differ for differing states. labels=$oldLabels oldState=$existingPersistedState newState=$newPersistableState") pr.labels.replace(userLabels ++ newLabels) delayer.doAfterSmallDelay { actionTaker(currentSnapshot, repoSnapshot) } } Some(currentSnapshot) } } else Future.successful(None) } def ignoreItemsWithExistingState(existingState: PRCheckpointState): Boolean = existingState.hasStateForCheckpointsWhichHaveAllBeenSeen def findCheckpointStateChange(oldState: PRCheckpointState, pr: PullRequest, repoSnapshot: RepoSnapshot): Future[PullRequestCheckpointsStateChangeSummary] = for (cs <- repoSnapshot.checkpointSnapshotsFor(pr, oldState)) yield { val details = PRCheckpointDetails(pr, cs, repoSnapshot.repoLevelDetails.gitRepo) PullRequestCheckpointsStateChangeSummary(details, oldState) } def actionTaker( checkpointsChangeSummary: PullRequestCheckpointsStateChangeSummary, repoSnapshot: RepoSnapshot )(implicit g: GitHub, sentryApiClientOpt: Option[SentryApiClient] ): Unit = { val pr = checkpointsChangeSummary.prCheckpointDetails.pr val now = Instant.now() def sentryReleaseOpt(): Option[PRSentryRelease] = { val sentryProjects = for { configs <- repoSnapshot.activeConfByPullRequest.get(pr).toSeq config <- configs sentryConf <- config.sentry.toSeq sentryProject <- sentryConf.projects } yield sentryProject for { mergeCommit <- pr.merge_commit_sha if sentryProjects.nonEmpty } yield PRSentryRelease(mergeCommit, sentryProjects) } val newlySeenSnapshots = checkpointsChangeSummary.changedByState.get(Seen).toSeq.flatten logger.info(s"action taking: ${pr.prId} newlySeenSnapshots = $newlySeenSnapshots") val mergeToNow = java.time.Duration.between(pr.merged_at.get.toInstant, now) val previouslyTouchedByProut = checkpointsChangeSummary.oldState.statusByCheckpoint.nonEmpty if (previouslyTouchedByProut || mergeToNow < WorthyOfCommentWindow) { logger.trace(s"changedSnapshotsByState : ${checkpointsChangeSummary.changedByState}") def commentOn(status: PullRequestCheckpointStatus, additionalAdvice: Option[String] = None) = { lazy val fileFinder = repoSnapshot.repoLevelDetails.createFileFinder() for (changedSnapshots <- checkpointsChangeSummary.changedByState.get(status)) { val checkpoints = changedSnapshots.map(_.snapshot.checkpoint.nameMarkdown).mkString(", ") val customAdvices = for { s <- changedSnapshots messages <- s.snapshot.checkpoint.details.messages path <- messages.filePathforStatus(status) message <- fileFinder.read(path) } yield message val advices = if(customAdvices.nonEmpty) customAdvices else CheckpointMessages.defaults.get(status).toSet val advice = (advices ++ additionalAdvice).mkString("\n\n") pr.comments2.create(CreateComment(s"${status.name} on $checkpoints (${responsibilityAndRecencyFor(pr)}) $advice")) } } for (updateReporter <- repoSnapshot.updateReporters) { updateReporter.report(repoSnapshot, pr, checkpointsChangeSummary) } val sentryDetails: Option[String] = for { sentry <- sentryApiClientOpt sentryRelease <- sentryReleaseOpt() } yield sentryRelease.detailsMarkdown(sentry.org) commentOn(Seen, sentryDetails) commentOn(Overdue) } } }