Skip to content

Commit

Permalink
Use preferStableVersion to compute default artifact
Browse files Browse the repository at this point in the history
  • Loading branch information
adpi2 committed May 23, 2024
1 parent 9b3abbb commit e82d387
Show file tree
Hide file tree
Showing 13 changed files with 80 additions and 99 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ case class SemanticVersion(
def isPreRelease: Boolean =
preRelease.isDefined || metadata.isDefined

def isRelease: Boolean =
def isStable: Boolean =
isSemantic && !isPreRelease

override def toString: String = this match {
Expand Down Expand Up @@ -103,12 +103,9 @@ object SemanticVersion {
)
}

/**
* Often we will prefer the latest release, but if there is no full release, we will select the most recent
* pre-release.
*/
val PreferReleases: Ordering[SemanticVersion] =
Ordering.by[SemanticVersion, Boolean](_.isRelease).orElse(ordering)
// We prefer the latest stable artifact.
val PreferStable: Ordering[SemanticVersion] =
Ordering.by[SemanticVersion, Boolean](_.isStable).orElse(ordering)

private def MajorP[A: P]: P[Int] = Number

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ trait WebDatabase {
): Future[Seq[Artifact]]
def getArtifacts(ref: Project.Reference, artifactName: Artifact.Name, version: SemanticVersion): Future[Seq[Artifact]]
def getArtifactsByName(projectRef: Project.Reference, artifactName: Artifact.Name): Future[Seq[Artifact]]
def getLatestArtifacts(ref: Project.Reference): Future[Seq[Artifact]]
def getLatestArtifacts(ref: Project.Reference, preferStableVersions: Boolean): Future[Seq[Artifact]]
def getArtifactByMavenReference(mavenRef: Artifact.MavenReference): Future[Option[Artifact]]
def getAllArtifacts(language: Option[Language], platform: Option[Platform]): Future[Seq[Artifact]]
def countArtifacts(): Future[Long]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package scaladex.core.model
import org.scalatest.funspec.AsyncFunSpec
import org.scalatest.matchers.should.Matchers
import org.scalatest.prop.TableDrivenPropertyChecks
import scaladex.core.model.SemanticVersion.PreferReleases
import scaladex.core.model.SemanticVersion.PreferStable
import scaladex.core.test.Values._

class SemanticVersionTests extends AsyncFunSpec with Matchers with TableDrivenPropertyChecks {
Expand Down Expand Up @@ -44,7 +44,7 @@ class SemanticVersionTests extends AsyncFunSpec with Matchers with TableDrivenPr
it("should allow us to prefer releases over pre-releases") {
val versions = Seq(`7.0.0`, `7.1.0`, `7.2.0-PREVIEW.1`)
versions.max shouldBe `7.2.0-PREVIEW.1`
versions.max(PreferReleases) shouldBe `7.1.0`
versions.max(PreferStable) shouldBe `7.1.0`
}

it("should encode and decode any version") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ class InMemoryDatabase extends SchedulerDatabase {
Future.successful(())
}

override def getLatestArtifacts(ref: Project.Reference): Future[Seq[Artifact]] = {
override def getLatestArtifacts(ref: Project.Reference, preferStableVersion: Boolean): Future[Seq[Artifact]] = {
val res = allArtifacts(ref)
.groupBy(a => (a.groupId, a.artifactId))
.values
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ class ElasticsearchEngine(esClient: ElasticClient, index: String)(implicit ec: E
"githubInfo.openIssues",
existsQuery("githubInfo.openIssues")
),
existsQuery("githubInfo.contributingGuide"),
existsQuery("githubInfo.contributingGuide")
)
)

Expand Down
25 changes: 15 additions & 10 deletions modules/infra/src/main/scala/scaladex/infra/SqlDatabase.scala
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,21 @@ class SqlDatabase(datasource: HikariDataSource, xa: doobie.Transactor[IO]) exten
override def getArtifactsByName(ref: Project.Reference, artifactName: Artifact.Name): Future[Seq[Artifact]] =
run(ArtifactTable.selectArtifactByProjectAndName.to[Seq]((ref, artifactName)))

override def getLatestArtifacts(ref: Project.Reference): Future[Seq[Artifact]] =
for {
releases <- run(ArtifactTable.selectLatestArtifacts(mustBeRelease = true).to[Seq](ref))
nonReleases <- run(ArtifactTable.selectLatestArtifacts(mustBeRelease = false).to[Seq](ref))
} yield {
// override non-released artifacts with their latest released version
val merge = nonReleases.map(artifact => artifact.mavenReference -> artifact).toMap ++
releases.map(artifact => artifact.mavenReference -> artifact).toMap
merge.values.toSeq
}
override def getLatestArtifacts(ref: Project.Reference, preferStableVersions: Boolean): Future[Seq[Artifact]] = {
val latestArtifactsF = run(ArtifactTable.selectLatestArtifacts(stableOnly = false).to[Seq](ref))
if (preferStableVersions) {
for {
latestStableArtifacts <- run(ArtifactTable.selectLatestArtifacts(stableOnly = true).to[Seq](ref))
latestArtifacts <- latestArtifactsF
} yield
// override non-stable version with the latest stable version
(latestStableArtifacts ++ latestArtifacts)
.groupBy(a => (a.groupId, a.artifactId))
.valuesIterator
.map(_.head)
.toSeq
} else latestArtifactsF
}

override def getArtifactByMavenReference(mavenRef: Artifact.MavenReference): Future[Option[Artifact]] =
run(ArtifactTable.selectByMavenReference.option(mavenRef))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,24 +136,21 @@ object ArtifactTable {
groupBy = Seq("organization", "repository ", "platform ", "language_version", "version")
)

def selectLatestArtifacts(mustBeRelease: Boolean): Query[Project.Reference, Artifact] =
selectRequest1(
latestDateTable(mustBeRelease),
fields.map(c => s"a.$c")
)
def selectLatestArtifacts(stableOnly: Boolean): Query[Project.Reference, Artifact] =
selectRequest1(latestDateTable(stableOnly), fields.map(c => s"a.$c"))

// the latest release date of all artifact IDs
private def latestDateTable(mustBeRelease: Boolean): String =
private def latestDateTable(stableOnly: Boolean): String =
s"($table a " +
s"INNER JOIN (${selectLatestDate(mustBeRelease).sql}) d " +
s"INNER JOIN (${selectLatestDate(stableOnly).sql}) d " +
s"ON a.group_id=d.group_id " +
s"AND a.artifact_id=d.artifact_id " +
s"AND a.release_date=d.release_date)"

private def selectLatestDate(
mustBeRelease: Boolean
stableOnly: Boolean
): Query[Project.Reference, (Artifact.GroupId, String, Instant)] = {
val isReleaseFilters = if (mustBeRelease) Seq("is_semantic='true'", "is_prerelease='false'") else Seq.empty
val isReleaseFilters = if (stableOnly) Seq("is_semantic='true'", "is_prerelease='false'") else Seq.empty
selectRequest1(
table,
Seq("group_id", "artifact_id", "MAX(release_date) as release_date"),
Expand Down
10 changes: 5 additions & 5 deletions modules/server/src/main/scala/scaladex/server/route/Badges.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import scaladex.core.model.Scala
import scaladex.core.model.ScalaJs
import scaladex.core.model.ScalaNative
import scaladex.core.model.SemanticVersion
import scaladex.core.model.SemanticVersion.PreferReleases
import scaladex.core.model.SemanticVersion.PreferStable
import scaladex.core.service.WebDatabase

class Badges(database: WebDatabase)(implicit executionContext: ExecutionContext) {
Expand Down Expand Up @@ -156,10 +156,10 @@ class Badges(database: WebDatabase)(implicit executionContext: ExecutionContext)
): Future[Option[Artifact]] = {
val artifactSelection = ArtifactSelection(binaryVersion, artifact)
database.getArtifacts(project.reference).map { artifacts =>
val (releaseArtifacts, nonReleaseArtifacts) = artifacts.partition(_.version.isRelease)
val (stableArtifacts, nonStableArtifacts) = artifacts.partition(_.version.isStable)
artifactSelection
.defaultArtifact(releaseArtifacts, project)
.orElse(artifactSelection.defaultArtifact(nonReleaseArtifacts, project))
.defaultArtifact(stableArtifacts, project)
.orElse(artifactSelection.defaultArtifact(nonStableArtifacts, project))
}
}
}
Expand All @@ -174,7 +174,7 @@ object Badges {

private[route] def summaryOfLatestVersions(versionsByScalaVersions: Map[Scala, Seq[SemanticVersion]]): String =
versionsByScalaVersions.view
.mapValues(_.max(PreferReleases))
.mapValues(_.max(PreferStable))
.groupMap { case (_, latestVersion) => latestVersion } { case (scalaVersion, _) => scalaVersion }
.toSeq
.sortBy(_._1)(SemanticVersion.ordering.reverse)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,14 +243,19 @@ class ProjectPages(env: Env, database: WebDatabase, searchEngine: SearchEngine)(
private def getProjectHeader(project: Project): Future[Option[ProjectHeader]] = {
val ref = project.reference
for {
latestArtifacts <- database.getLatestArtifacts(ref)
latestArtifacts <- database.getLatestArtifacts(ref, project.settings.preferStableVersion)
versionCount <- database.countVersions(ref)
} yield ProjectHeader(project.reference, latestArtifacts, versionCount, project.settings.defaultArtifact)
} yield ProjectHeader(
project.reference,
latestArtifacts,
versionCount,
project.settings.defaultArtifact,
project.settings.preferStableVersion
)
}

private def getProjectPage(ref: Project.Reference, user: Option[UserState]): Route =
getProjectOrRedirect(ref, user) { project =>
logger.info(s"Accessing project page for: $ref")
for {
header <- getProjectHeader(project)
directDependencies <-
Expand All @@ -259,7 +264,6 @@ class ProjectPages(env: Env, database: WebDatabase, searchEngine: SearchEngine)(
.getOrElse(Future.successful(Seq.empty))
reverseDependencies <- database.getProjectDependents(ref)
} yield {
logger.info(s"Successfully retrieved project data for: $ref")
val groupedDirectDependencies = directDependencies
.groupBy(_.target)
.view
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,14 @@ class DependencyUpdater(database: SchedulerDatabase)(implicit ec: ExecutionConte
database.deleteProjectDependencies(project.reference).map(_ => ())
else
for {
latestArtifacts <- database.getLatestArtifacts(project.reference)
header = ProjectHeader(project.reference, latestArtifacts, 0, project.settings.defaultArtifact)
latestArtifacts <- database.getLatestArtifacts(project.reference, project.settings.preferStableVersion)
header = ProjectHeader(
project.reference,
latestArtifacts,
0,
project.settings.defaultArtifact,
project.settings.preferStableVersion
)
dependencies <- header
.map(h => database.computeProjectDependencies(project.reference, h.defaultVersion))
.getOrElse(Future.successful(Seq.empty))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ class SearchSynchronizer(database: WebDatabase, searchEngine: SearchEngine)(impl
projectsToDelete =
allProjectsAndStatus.collect { case (p, GithubStatus.NotFound(_)) => p.reference }
projectsToSync = allProjectsAndStatus
.collect {
case (p, GithubStatus.Ok(_) | GithubStatus.Unknown(_) | GithubStatus.Failed(_, _, _)) => p
}
.collect { case (p, GithubStatus.Ok(_) | GithubStatus.Unknown(_) | GithubStatus.Failed(_, _, _)) => p }

_ = logger.info(s"${movedProjects.size} projects were moved")
_ = logger.info(s"Deleting ${projectsToDelete.size} projects from search engine")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,88 +17,62 @@ object ProjectHeader {
ref: Project.Reference,
latestArtifacts: Seq[Artifact],
versionCount: Long,
defaultArtifactName: Option[Artifact.Name]
defaultArtifactName: Option[Artifact.Name],
preferStableVersion: Boolean
): Option[ProjectHeader] =
Option.when(latestArtifacts.nonEmpty)(new ProjectHeader(ref, latestArtifacts, versionCount, defaultArtifactName))
Option.when(latestArtifacts.nonEmpty) {
new ProjectHeader(ref, latestArtifacts, versionCount, defaultArtifactName, preferStableVersion)
}
}

final case class ProjectHeader(
ref: Project.Reference,
latestArtifacts: Seq[Artifact],
versionCount: Long,
defaultArtifactName: Option[Artifact.Name]
defaultArtifactName: Option[Artifact.Name],
preferStableVersion: Boolean
) {
def artifactNames: Seq[Artifact.Name] = latestArtifacts.map(_.artifactName).distinct.sorted
def languages: Seq[Language] = latestArtifacts.map(_.language).distinct.sorted
def platforms: Seq[Platform] = latestArtifacts.map(_.platform).distinct.sorted

def platforms(artifactName: Artifact.Name): Seq[Platform] =
latestArtifacts.filter(_.artifactName == artifactName).map(_.platform).distinct.sorted
latestArtifacts.filter(_.artifactName == artifactName).map(_.platform).distinct.sorted(Platform.ordering.reverse)

def defaultVersion: SemanticVersion = getDefaultArtifact(None, None).version

def artifactsUrl: String = {
val artifact = getDefaultArtifact(None, None)
val preReleaseFilter =
if (artifact.version.isPreRelease || !artifact.version.isSemantic) "?pre-releases=true" else ""
s"/$ref/artifacts/${artifact.artifactName}$preReleaseFilter"
}
def artifactsUrl: String = artifactsUrl(getDefaultArtifact(None, None))

def artifactsUrl(language: Language): String = {
val artifact = getDefaultArtifact(Some(language), None)
val preReleaseFilter =
if (artifact.version.isPreRelease || !artifact.version.isSemantic) "&pre-releases=true" else ""
s"/$ref/artifacts/${artifact.artifactName}?binary-versions=${artifact.binaryVersion.label}$preReleaseFilter"
}
def artifactsUrl(language: Language): String = artifactsUrl(getDefaultArtifact(Some(language), None))

def artifactsUrl(platform: Platform): String = artifactsUrl(getDefaultArtifact(None, Some(platform)))

def artifactsUrl(platform: Platform): String = {
val artifact = getDefaultArtifact(None, Some(platform))
val preReleaseFilter =
if (artifact.version.isPreRelease || !artifact.version.isSemantic) "&pre-releases=true" else ""
s"/$ref/artifacts/${artifact.artifactName}?binary-versions=${artifact.binaryVersion.label}$preReleaseFilter"
private def artifactsUrl(defaultArtifact: Artifact): String = {
val preReleaseFilter = if (!preferStableVersion || !defaultArtifact.version.isStable) "&pre-releases=true" else ""
s"/$ref/artifacts/${defaultArtifact.artifactName}?binary-versions=${defaultArtifact.binaryVersion.label}$preReleaseFilter"
}

def getDefaultArtifact(language: Option[Language], platform: Option[Platform]): Artifact = {
val candidates = latestArtifacts
val filteredArtifacts = latestArtifacts
.filter(artifact => language.forall(_ == artifact.language) && platform.forall(_ == artifact.platform))
val stableCandidates = candidates.filter(artifact => artifact.version.isSemantic && !artifact.version.isPreRelease)

def byNameStable = stableCandidates
.filter(a => defaultArtifactName.exists(_ == a.artifactName))
.sortBy(_.binaryVersion)(BinaryVersion.ordering.reverse)
.headOption
def byName: Option[Artifact] =
defaultArtifactName.toSeq
.flatMap(defaultName => filteredArtifacts.filter(a => defaultName == a.artifactName))
.maxByOption(_.binaryVersion)

def byNamePrerelease = candidates
.filter(a => defaultArtifactName.exists(_ == a.artifactName))
.sortBy(_.binaryVersion)(BinaryVersion.ordering.reverse)
.headOption

def byVersion(version: SemanticVersion) =
candidates
def ofVersion(version: SemanticVersion): Artifact =
filteredArtifacts
.filter(_.version == version)
.sortBy(a => (a.binaryVersion, a.artifactName))(
Ordering.Tuple2(Ordering[BinaryVersion].reverse, Ordering[Artifact.Name])
.maxBy(a => (a.binaryVersion, a.artifactName))(
Ordering.Tuple2(Ordering[BinaryVersion], Ordering[Artifact.Name].reverse)
)
.head

// find version of latest stable artifact then default artifact of that version
def byLatestVersionStable =
stableCandidates
.sortBy(_.releaseDate)
.lastOption
.map(a => byVersion(a.version))

def byLatestVersionPrerelease =
candidates
.sortBy(_.releaseDate)
.lastOption
.map(a => byVersion(a.version))
def byLatestVersion: Option[Artifact] =
filteredArtifacts.sortBy(_.releaseDate).lastOption.map(a => ofVersion(a.version))

byNameStable
.orElse(byNamePrerelease)
.orElse(byLatestVersionStable)
.orElse(byLatestVersionPrerelease)
.get
byName.orElse(byLatestVersion).get
}

def scalaVersions: Seq[Scala] = languages.collect { case v: Scala => v }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class ProjectHeaderTests extends AnyFunSpec with Matchers {
}

private def getDefaultArtifact(artifacts: Artifact*): Artifact = {
val header = ProjectHeader(reference, artifacts, 10, None).get
val header = ProjectHeader(reference, artifacts, 10, None, false).get
header.getDefaultArtifact(None, None)
}
}

0 comments on commit e82d387

Please sign in to comment.