Skip to content

Commit

Permalink
chore: prepare 2.10.0 (#1023)
Browse files Browse the repository at this point in the history
* feat: cross-entity search case-insensitive on creator (#1021)

* fix: single location path segment on the project file lineage (#1022)
  • Loading branch information
jachro authored Jun 15, 2022
1 parent cc31a47 commit 386a8ab
Show file tree
Hide file tree
Showing 8 changed files with 79 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import io.renku.graph.model.testentities.LineageExemplarData.ExemplarData
import io.renku.graph.model.testentities.generators.EntitiesGenerators.{personEntities, renkuProjectEntities, visibilityPublic}
import io.renku.graph.model.testentities.{LineageExemplarData, NodeDef}
import io.renku.http.client.AccessToken
import io.renku.http.client.UrlEncoder.urlEncode
import io.renku.jsonld.syntax._
import org.http4s.Status.{NotFound, Ok}
import org.scalatest.GivenWhenThen
Expand Down Expand Up @@ -85,7 +86,7 @@ class LineageResourcesSpec

When("user calls the lineage endpoint")
val response =
knowledgeGraphClient GET s"knowledge-graph/projects/${project.path}/files/${exemplarData.`grid_plot entity`.location}/lineage"
knowledgeGraphClient GET s"knowledge-graph/projects/${project.path}/files/${urlEncode(exemplarData.`grid_plot entity`.location)}/lineage"

Then("they should get Ok response with project lineage in Json")
response.status shouldBe Ok
Expand Down Expand Up @@ -121,7 +122,7 @@ class LineageResourcesSpec
When("user fetches the lineage of the project he is a member of")

val response =
knowledgeGraphClient GET (s"knowledge-graph/projects/${project.path}/files/${accessibleExemplarData.`grid_plot entity`.location}/lineage", user.accessToken)
knowledgeGraphClient GET (s"knowledge-graph/projects/${project.path}/files/${urlEncode(accessibleExemplarData.`grid_plot entity`.location)}/lineage", user.accessToken)

Then("he should get OK response with project lineage in Json")
response.status shouldBe Ok
Expand Down Expand Up @@ -150,7 +151,7 @@ class LineageResourcesSpec
When("user posts a graphql query to fetch lineage of the project he is not a member of")
val response =
knowledgeGraphClient.GET(
s"knowledge-graph/projects/${project.path}/files/${privateExemplarData.`grid_plot entity`.location}/lineage"
s"knowledge-graph/projects/${project.path}/files/${urlEncode(privateExemplarData.`grid_plot entity`.location)}/lineage"
)

Then("he should get a NotFound response without lineage")
Expand Down
11 changes: 11 additions & 0 deletions generators/src/main/scala/io/renku/generators/Generators.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@ object Generators {

def noDashUuid: Gen[String] = uuid.map(_.toString.replace("-", ""))

def randomiseCases(value: String): Gen[String] = {
for {
itemsNo <- positiveInts(value.length).map(_.value)
itemsIndices <- Gen.pick(itemsNo, value.zipWithIndex.map(_._2))
} yield value.zipWithIndex.foldLeft(List.empty[Char]) {
case (newChars, char -> idx) if itemsIndices contains idx =>
(if (char.isLower) char.toUpper else char.toLower) :: newChars
case (newChars, char -> _) => char :: newChars
}
}.map(_.reverse.mkString)

def nonEmptyStrings(maxLength: Int = 10, charsGenerator: Gen[Char] = alphaChar): Gen[String] = {
require(maxLength > 0)
nonBlankStrings(maxLength = Refined.unsafeApply(maxLength), charsGenerator = charsGenerator) map (_.value)
Expand Down
2 changes: 1 addition & 1 deletion knowledge-graph/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,7 @@ Response body example:

#### GET /knowledge-graph/projects/:namespace/:name/files/:location/lineage

Fetches lineage for a given project `namespace`/`name` and file `location` (relative path). This endpoint is intended to replace the graphql endpoint.
Fetches lineage for a given project `namespace`/`name` and file `location` (URL-encoded relative path to the file). This endpoint is intended to replace the graphql endpoint.

| Status | Description |
|----------------------------|-------------------------------------------------------------------|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import io.renku.control.{RateLimit, Throttler}
import io.renku.graph.http.server.security._
import io.renku.graph.model
import io.renku.graph.model.persons
import io.renku.http.InfoMessage
import io.renku.http.InfoMessage._
import io.renku.http.client.GitLabClient
import io.renku.http.rest.SortBy.Direction
import io.renku.http.rest.paging.PagingRequest
Expand All @@ -41,13 +43,12 @@ import io.renku.http.server.version
import io.renku.knowledgegraph.datasets.rest.DatasetsSearchEndpoint.Query.Phrase
import io.renku.knowledgegraph.datasets.rest._
import io.renku.knowledgegraph.graphql.QueryEndpoint
import io.renku.knowledgegraph.lineage.model.Node.Location
import io.renku.knowledgegraph.projects.rest.ProjectEndpoint
import io.renku.metrics.{MetricsRegistry, RoutesMetrics}
import io.renku.rdfstore.SparqlQueryTimeRecorder
import org.http4s.dsl.Http4sDsl
import org.http4s.server.AuthMiddleware
import org.http4s.{AuthedRoutes, ParseFailure, Request, Response}
import org.http4s.{AuthedRoutes, ParseFailure, Request, Response, Status, Uri}
import org.typelevel.log4cats.Logger

import scala.concurrent.ExecutionContext
Expand Down Expand Up @@ -209,49 +210,37 @@ private class MicroserviceRoutes[F[_]: MonadThrow](
.flatTap(authorizePath(_, maybeAuthUser).leftMap(_.toHttpResponse))
.semiflatMap(getProjectDatasets)
.merge
case LineageEndpoint(projectPathParts, locationParts) =>
(projectPathParts.toProjectPath -> locationParts.toLocation)
.mapN(_ -> _)
.flatTap { case (projectPath, _) => authorizePath(projectPath, maybeAuthUser).leftMap(_.toHttpResponse) }
.semiflatMap { case (projectPath, location) => `GET /lineage`(projectPath, location, maybeAuthUser) }
.merge
case projectPathParts :+ "files" :+ location :+ "lineage" =>
getLineage(projectPathParts, location, maybeAuthUser)
case projectPathParts =>
projectPathParts.toProjectPath
.flatTap(authorizePath(_, maybeAuthUser).leftMap(_.toHttpResponse))
.semiflatMap(getProject(_, maybeAuthUser))
.merge
}

object LineageEndpoint {
def unapply(segments: List[String]): Option[(List[String], List[String])] =
if (segments.contains("lineage") && segments.contains("files")) {
private def getLineage(projectPathParts: List[String], location: String, maybeAuthUser: Option[AuthUser]) = {
import io.renku.knowledgegraph.lineage.model.Node.Location

val indexOfFiles = segments.indexOf("files")
val projectPath = segments.take(indexOfFiles)
val location = segments.drop(indexOfFiles + 1).takeWhile(!_.contains("lineage"))
if (projectPath.nonEmpty && location.nonEmpty)
Some((projectPath, location))
else None
} else None
def toLocation(location: String): EitherT[F, Response[F], Location] = EitherT.fromEither[F] {
Location
.from(Uri.decode(location))
.leftMap(_ => Response[F](Status.NotFound).withEntity(InfoMessage("Resource not found")))
}

(projectPathParts.toProjectPath -> toLocation(location))
.mapN(_ -> _)
.flatTap { case (projectPath, _) => authorizePath(projectPath, maybeAuthUser).leftMap(_.toHttpResponse) }
.semiflatMap { case (projectPath, location) => `GET /lineage`(projectPath, location, maybeAuthUser) }
.merge
}

private implicit class PathPartsOps(parts: List[String]) {

import io.renku.http.InfoMessage
import io.renku.http.InfoMessage._
import org.http4s.{Response, Status}

lazy val toProjectPath: EitherT[F, Response[F], model.projects.Path] = EitherT.fromEither[F] {
model.projects.Path
.from(parts mkString "/")
.leftMap(_ => Response[F](Status.NotFound).withEntity(InfoMessage("Resource not found")))
}

lazy val toLocation: EitherT[F, Response[F], Location] = EitherT.fromEither[F] {
Location
.from(parts mkString "/")
.leftMap(_ => Response[F](Status.NotFound).withEntity(InfoMessage("Resource not found")))
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import cats.syntax.all._
import io.renku.graph.model.projects
import io.renku.knowledgegraph.entities.Endpoint.Criteria
import io.renku.knowledgegraph.entities.Endpoint.Criteria.Filters
import io.renku.tinytypes.{LocalDateTinyType, TinyType}
import io.renku.tinytypes.{LocalDateTinyType, StringTinyType, TinyType, TinyTypeFactory}

import java.time.{Instant, ZoneOffset}

Expand Down Expand Up @@ -70,15 +70,15 @@ package object finder {
filters.creators match {
case creators if creators.isEmpty => ""
case creators =>
s"FILTER (IF (BOUND($variableName), $variableName IN ${creators.map(_.asSparqlEncodedLiteral).mkString("(", ", ", ")")}, false))"
s"FILTER (IF (BOUND($variableName), LCASE($variableName) IN ${creators.map(_.toLowerCase.asSparqlEncodedLiteral).mkString("(", ", ", ")")}, false))"
}

def maybeOnCreatorsNames(variableName: String): String =
filters.creators match {
case creators if creators.isEmpty => ""
case creators =>
s"""FILTER (IF (BOUND($variableName), ${creators
.map(c => s"CONTAINS($variableName, ${c.asSparqlEncodedLiteral})")
.map(c => s"CONTAINS(LCASE($variableName), ${c.toLowerCase.asSparqlEncodedLiteral})")
.mkString(" || ")} , false))"""
}

Expand Down Expand Up @@ -155,6 +155,10 @@ package object finder {
lazy val asSparqlEncodedLiteral: String = s"'${sparqlEncode(v.show)}'"
lazy val asLiteral: String = show"'$v'"
}

private implicit class StringValueOps[TT <: StringTinyType](v: TT)(implicit s: Show[TT]) {
def toLowerCase(implicit factory: TinyTypeFactory[TT]): TT = factory(v.show.toLowerCase())
}
}

private[finder] object DecodingTools {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import cats.data.{Kleisli, OptionT}
import cats.effect.{IO, Resource}
import cats.syntax.all._
import eu.timepit.refined.auto._
import io.renku.http.client.UrlEncoder.urlEncode
import io.circe.Json
import io.renku.generators.CommonGraphGenerators._
import io.renku.generators.Generators.Implicits._
Expand Down Expand Up @@ -244,9 +245,10 @@ class MicroserviceRoutesSpec
response.body[ErrorMessage] shouldBe InfoMessage(AuthorizationFailure.getMessage)
}
}

"GET /knowledge-graph/projects/:projectId/files/:location/lineage" should {
def lineageUri(projectPath: ProjectPath, location: Location) =
Uri.unsafeFromString(s"knowledge-graph/projects/${projectPath.value}/files/${location.value}/lineage")
Uri.unsafeFromString(s"knowledge-graph/projects/${projectPath.show}/files/${urlEncode(location.show)}/lineage")

s"return $Ok when the lineage is found" in new TestCase {
val projectPath = projectPaths.generateOne
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,35 @@ class EntitiesFinderSpec extends AnyWordSpec with FinderSpecOps with should.Matc
).sortBy(_.name.value)
}

"return entities creator matches in a case-insensitive way" in new TestCase {
val creator = personEntities.generateOne

val soleProject = renkuProjectEntities(visibilityPublic)
.modify(creatorLens.modify(_ => creator.some))
.generateOne

val dsAndProject @ _ ::~ dsProject = renkuProjectEntities(visibilityPublic)
.addDataset(
datasetEntities(provenanceNonModified).modify(
provenanceLens.modify(
creatorsLens.modify(_ => NonEmptyList.of(personEntities.generateOne, creator))
)
)
)
.generateOne

loadToStore(soleProject, dsProject)

finder
.findEntities(Criteria(Filters(creators = Set(randomiseCases(creator.name.show).generateAs(persons.Name)))))
.unsafeRunSync()
.results shouldBe List(
soleProject.to[model.Entity.Project],
dsAndProject.to[model.Entity.Dataset],
creator.to[model.Entity.Person]
).sortBy(_.name.value)
}

"return entities that matches at least one of the given creators" in new TestCase {

val projectCreator = personEntities.generateOne
Expand Down Expand Up @@ -381,7 +410,7 @@ class EntitiesFinderSpec extends AnyWordSpec with FinderSpecOps with should.Matc
).sortBy(_.name.value)
}

"return no entities when no match on creator" in new TestCase {
"return no entities when there's no match on creator" in new TestCase {
val _ ::~ project = renkuProjectEntities(visibilityPublic)
.addDataset(datasetEntities(provenanceNonModified))
.generateOne
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,12 @@ class CliVersionSpec extends AnyWordSpec with ScalaCheckPropertyChecks with shou

"consider the major only if different" in {
forAll(cliVersions, cliVersions) { (version1, version2) =>
val list = List(version1, version2)
whenever(version1.major != version2.major) {
val list = List(version1, version2)

if (version1.major < version2.major) list.sorted shouldBe list
else list.sorted shouldBe list.reverse
if (version1.major < version2.major) list.sorted shouldBe list
else list.sorted shouldBe list.reverse
}
}
}

Expand Down

0 comments on commit 386a8ab

Please sign in to comment.