Repository: scala-steward-org/scala-steward (scala)

Issue:
findPullRequest finds old PR
Here is an interesting case where Scala Steward thinks a PR is outdated because `PullRequestRepository.findPullRequest` didn't return the latest PR:
```
2020-12-22 06:52:22,845 INFO  ──────────── Steward zio/zio-telemetry ────────────
2020-12-22 06:52:22,845 INFO  Check cache of zio/zio-telemetry
2020-12-22 06:52:23,353 INFO  Find updates for zio/zio-telemetry
...
2020-12-22 06:52:23,533 INFO  Found 29 updates:
  ...
  io.opentelemetry:opentelemetry-api : 0.7.1 -> 0.13.1
  io.opentelemetry:opentelemetry-exporters-inmemory : 0.7.1 -> 0.9.1
  io.opentelemetry:opentelemetry-exporters-jaeger : 0.7.1 -> 0.9.1
  io.opentelemetry:opentelemetry-sdk : 0.7.1 -> 0.13.1
  ...
2020-12-22 06:52:23,533 INFO  zio/zio-telemetry is outdated:
  PR outdated: io.opentelemetry:opentelemetry-exporters-inmemory : 0.7.1 -> 0.9.1 (https://github.com/zio/zio-telemetry/pull/238)
  PR outdated: io.opentelemetry:opentelemetry-exporters-jaeger : 0.7.1 -> 0.9.1 (https://github.com/zio/zio-telemetry/pull/238)
2020-12-22 06:52:23,533 INFO  Nurture zio/zio-telemetry
2020-12-22 06:52:23,533 INFO  Clone and synchronize zio/zio-telemetry
2020-12-22 06:52:26,486 INFO  Found 1 update:
  io.opentelemetry:{opentelemetry-exporters-inmemory, opentelemetry-exporters-jaeger} : 0.7.1 -> 0.9.1
2020-12-22 06:52:26,490 INFO  Process update io.opentelemetry:{opentelemetry-exporters-inmemory, opentelemetry-exporters-jaeger} : 0.7.1 -> 0.9.1
2020-12-22 06:52:26,727 INFO  Found PR https://github.com/zio/zio-telemetry/pull/252
2020-12-22 06:52:26,764 INFO  PR has no conflict with master
2020-12-22 06:52:26,796 INFO  ──────────── Total time: Steward zio/zio-telemetry: 3s 950ms ────────────
```
So what happened here? Scala Steward found an existing but outdated PR (238) that updates opentelemetry-exporters-inmemory and opentelemetry-exporters-jaeger. It then clones and syncs the repository to update that PR in case there is a conflict. But when it tries to find the existing PR again for the update group `io.opentelemetry:{opentelemetry-exporters-inmemory, opentelemetry-exporters-jaeger} : 0.7.1 -> 0.9.1`, it finds a newer PR (252) which is already up-to-date.

The above problem can be fixed by changing `PullRequestRepository.findPullRequest` so that it returns the PR with the highest `PullRequestData.entryCreatedAt` if there are multiple matching PRs.

Pull request:
Closes https://github.com/scala-steward-org/scala-steward/issues/1831.

Patch:
diff --git a/modules/core/src/main/scala/org/scalasteward/core/nurture/PullRequestRepository.scala b/modules/core/src/main/scala/org/scalasteward/core/nurture/PullRequestRepository.scala
index 4042f428dc..80c9ca7cdf 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/nurture/PullRequestRepository.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/nurture/PullRequestRepository.scala
@@ -24,8 +24,8 @@ import org.scalasteward.core.git.Sha1
 import org.scalasteward.core.persistence.KeyValueStore
 import org.scalasteward.core.update.UpdateAlg
 import org.scalasteward.core.util.{DateTimeAlg, Timestamp}
-import org.scalasteward.core.vcs.data.{PullRequestNumber, PullRequestState, Repo}
 import org.scalasteward.core.vcs
+import org.scalasteward.core.vcs.data.{PullRequestNumber, PullRequestState, Repo}
 
 final class PullRequestRepository[F[_]](
     kvStore: KeyValueStore[F, Repo, Map[Uri, PullRequestData]]
@@ -82,18 +82,18 @@ final class PullRequestRepository[F[_]](
       }.flatten.toList
     }
 
-  def findPullRequest(
+  def findLatestPullRequest(
       repo: Repo,
       crossDependency: CrossDependency,
       newVersion: String
   ): F[Option[(Uri, Sha1, PullRequestState)]] =
     kvStore.getOrElse(repo, Map.empty).map {
-      _.collectFirst {
-        case (url, data)
-            if UpdateAlg.isUpdateFor(data.update, crossDependency) &&
-              data.update.nextVersion === newVersion =>
-          (url, data.baseSha1, data.state)
+      _.filter { case (_, data) =>
+        UpdateAlg.isUpdateFor(data.update, crossDependency) &&
+          data.update.nextVersion === newVersion
       }
+        .maxByOption { case (_, data) => data.entryCreatedAt.millis }
+        .map { case (url, data) => (url, data.baseSha1, data.state) }
     }
 
   def lastPullRequestCreatedAt(repo: Repo): F[Option[Timestamp]] =
diff --git a/modules/core/src/main/scala/org/scalasteward/core/update/PruningAlg.scala b/modules/core/src/main/scala/org/scalasteward/core/update/PruningAlg.scala
index ae17e25b4a..5a504957c8 100644
--- a/modules/core/src/main/scala/org/scalasteward/core/update/PruningAlg.scala
+++ b/modules/core/src/main/scala/org/scalasteward/core/update/PruningAlg.scala
@@ -115,7 +115,7 @@ final class PruningAlg[F[_]](implicit
     updates.find(UpdateAlg.isUpdateFor(_, crossDependency)) match {
       case None => F.pure(DependencyUpToDate(crossDependency))
       case Some(update) =>
-        pullRequestRepository.findPullRequest(repo, crossDependency, update.nextVersion).map {
+        pullRequestRepository.findLatestPullRequest(repo, crossDependency, update.nextVersion).map {
           case None =>
             DependencyOutdated(crossDependency, update)
           case Some((uri, _, Closed)) =>


Test patch:
diff --git a/modules/core/src/test/scala/org/scalasteward/core/nurture/PullRequestRepositoryTest.scala b/modules/core/src/test/scala/org/scalasteward/core/nurture/PullRequestRepositoryTest.scala
index a0cca0c4a6..348dba921d 100644
--- a/modules/core/src/test/scala/org/scalasteward/core/nurture/PullRequestRepositoryTest.scala
+++ b/modules/core/src/test/scala/org/scalasteward/core/nurture/PullRequestRepositoryTest.scala
@@ -29,7 +29,7 @@ class PullRequestRepositoryTest extends FunSuite {
           PullRequestState.Open,
           number
         )
-        result <- pullRequestRepository.findPullRequest(repo, update.crossDependency, "1.0.0")
+        result <- pullRequestRepository.findLatestPullRequest(repo, update.crossDependency, "1.0.0")
         createdAt <- pullRequestRepository.lastPullRequestCreatedAt(repo)
       } yield (result, createdAt)
       val (state, (result, createdAt)) = p.run(MockState.empty).unsafeRunSync()