Skip to content

Commit

Permalink
Support searching resources in ES by tag
Browse files Browse the repository at this point in the history
  • Loading branch information
dantb committed Oct 17, 2023
1 parent 5c3ec3d commit 82897c8
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ final case class QueryBuilder private[client] (private val query: JsonObject) {
params.createdBy.map(term(nxv.createdBy.prefix, _)) ++
range(nxv.createdAt.prefix, params.createdAt) ++
params.updatedBy.map(term(nxv.updatedBy.prefix, _)) ++
range(nxv.updatedAt.prefix, params.updatedAt),
range(nxv.updatedAt.prefix, params.updatedAt) ++
params.tag.map(term(nxv.tags.prefix, _)),
mustNotTerms = typesTerms(params.typeOperator.negate, excludeTypes),
withScore = params.q.isDefined
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import ch.epfl.bluebrain.nexus.delta.sdk.marshalling.QueryParamsUnmarshalling.{i
import ch.epfl.bluebrain.nexus.delta.sdk.projects.model.ProjectContext
import ch.epfl.bluebrain.nexus.delta.sourcing.model.Identity.Subject
import ch.epfl.bluebrain.nexus.delta.sourcing.model.ResourceRef
import ch.epfl.bluebrain.nexus.delta.sourcing.model.Tag.UserTag

/**
* Search parameters for any generic resource type.
Expand All @@ -34,6 +35,8 @@ import ch.epfl.bluebrain.nexus.delta.sourcing.model.ResourceRef
* schema to consider, where empty implies any schema
* @param q
* a full text search query parameter
* @param tag
* an optional tag to filter resources on, returning the latest revision of matching resources.
*/
final case class ResourcesSearchParams(
locate: Option[Iri] = None,
Expand All @@ -47,7 +50,8 @@ final case class ResourcesSearchParams(
types: List[Type] = List.empty,
typeOperator: TypeOperator = TypeOperator.Or,
schema: Option[ResourceRef] = None,
q: Option[String] = None
q: Option[String] = None,
tag: Option[UserTag] = None
) {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,24 +68,40 @@ trait ElasticSearchViewsDirectives extends UriDirectives {
baseUri: BaseUri,
pc: ProjectContext
): Directive1[ResourcesSearchParams] = {
(searchParams & createdAt & updatedAt & types & typeOperator & schema & id & locate & parameter("q".?)).tmap {
case (deprecated, rev, createdBy, updatedBy, createdAt, updatedAt, types, typeOperator, schema, id, locate, q) =>
val qq = q.filter(_.trim.nonEmpty).map(_.toLowerCase)
ResourcesSearchParams(
locate,
id,
deprecated,
rev,
createdBy,
createdAt,
updatedBy,
updatedAt,
types,
typeOperator,
schema,
qq
)
}
(searchParams & createdAt & updatedAt & types & typeOperator & schema & id & locate & parameter("q".?) & tagParam)
.tmap {
case (
deprecated,
rev,
createdBy,
updatedBy,
createdAt,
updatedAt,
types,
typeOperator,
schema,
id,
locate,
q,
tag
) =>
val qq = q.filter(_.trim.nonEmpty).map(_.toLowerCase)
ResourcesSearchParams(
locate,
id,
deprecated,
rev,
createdBy,
createdAt,
updatedBy,
updatedAt,
types,
typeOperator,
schema,
qq,
tag
)
}
}

private[routes] def searchParameters(implicit
Expand All @@ -95,24 +111,40 @@ trait ElasticSearchViewsDirectives extends UriDirectives {
implicit val baseIriUm: FromStringUnmarshaller[IriBase] =
DeltaSchemeDirectives.iriBaseFromStringUnmarshallerNoExpansion

(searchParams & createdAt & updatedAt & types & typeOperator & schema & id & locate & parameter("q".?)).tmap {
case (deprecated, rev, createdBy, updatedBy, createdAt, updatedAt, types, typeOperator, schema, id, locate, q) =>
val qq = q.filter(_.trim.nonEmpty).map(_.toLowerCase)
ResourcesSearchParams(
locate,
id,
deprecated,
rev,
createdBy,
createdAt,
updatedBy,
updatedAt,
types,
typeOperator,
schema,
qq
)
}
(searchParams & createdAt & updatedAt & types & typeOperator & schema & id & locate & parameter("q".?) & tagParam)
.tmap {
case (
deprecated,
rev,
createdBy,
updatedBy,
createdAt,
updatedAt,
types,
typeOperator,
schema,
id,
locate,
q,
tag
) =>
val qq = q.filter(_.trim.nonEmpty).map(_.toLowerCase)
ResourcesSearchParams(
locate,
id,
deprecated,
rev,
createdBy,
createdAt,
updatedBy,
updatedAt,
types,
typeOperator,
schema,
qq,
tag
)
}
}

private[routes] def searchParametersAndSortList(implicit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.RemoteContextResolution
import ch.epfl.bluebrain.nexus.delta.sdk.DataResource
import ch.epfl.bluebrain.nexus.delta.sdk.generators.ResourceGen
import ch.epfl.bluebrain.nexus.delta.sdk.implicits._
import ch.epfl.bluebrain.nexus.delta.sdk.model.BaseUri
import ch.epfl.bluebrain.nexus.delta.sdk.model.{BaseUri, Tags}
import ch.epfl.bluebrain.nexus.delta.sdk.model.search._
import ch.epfl.bluebrain.nexus.delta.sourcing.model.Identity.{Anonymous, Subject, User}
import ch.epfl.bluebrain.nexus.delta.sourcing.model.Tag.UserTag
import ch.epfl.bluebrain.nexus.delta.sourcing.model.{Label, ProjectRef, ResourceRef}
import ch.epfl.bluebrain.nexus.testkit.bio.BioSuite
import ch.epfl.bluebrain.nexus.testkit.{CirceLiteral, TestHelpers}
Expand All @@ -46,6 +47,7 @@ class DefaultViewSearchSuite
private def epochPlus(plus: Long) = Instant.EPOCH.plusSeconds(plus)
private val realm = Label.unsafe("myrealm")
private val alice = User("Alice", realm)
private val myTag = UserTag.unsafe("mytag")

private val orgType = nxv + "Organization"
private val orgSchema = ResourceRef.Latest(nxv + "org")
Expand All @@ -58,7 +60,8 @@ class DefaultViewSearchSuite
orgSchema,
createdAt = epochPlus(5L),
updatedAt = epochPlus(10L),
createdBy = alice
createdBy = alice,
tag = myTag.some
)
private val epfl =
Sample(
Expand Down Expand Up @@ -184,6 +187,7 @@ class DefaultViewSearchSuite
private val byId = ResourcesSearchParams(id = Some(bbpResource.id))
private val byLocatingId = ResourcesSearchParams(locate = Some(bbpResource.id))
private val byLocatingSelf = ResourcesSearchParams(locate = Some(bbpResource.self))
private val byTag = ResourcesSearchParams(tag = myTag.some)

// Action / params / matching resources

Expand All @@ -204,7 +208,8 @@ class DefaultViewSearchSuite
("resources updated before 12", byUpdated_Before_12, updatedBefore_12),
(s"resources with id ${bbpResource.id}", byId, List(bbp)),
(s"resources by locating id ${bbpResource.id}", byLocatingId, List(bbp)),
(s"resources by locating self ${bbpResource.self}", byLocatingSelf, List(bbp))
(s"resources by locating self ${bbpResource.self}", byLocatingSelf, List(bbp)),
(s"resources with tag ${myTag.value}", byTag, List(bbp))
).foreach { case (testName, params, expected) =>
test(s"Search: $testName") {
search(params).map(Ids.extractAll).assert(expected.map(_.id))
Expand Down Expand Up @@ -265,13 +270,15 @@ object DefaultViewSearchSuite {
createdAt: Instant,
updatedAt: Instant,
createdBy: Subject = Anonymous,
updatedBy: Subject = Anonymous
updatedBy: Subject = Anonymous,
tag: Option[UserTag] = None
) {

def id: Iri = nxv + suffix

def asResourceF(implicit rcr: RemoteContextResolution): DataResource = {
val resource = ResourceGen.resource(id, project, Json.obj())
val tags = tag.map(t => Tags(t -> rev)).getOrElse(Tags.empty)
val resource = ResourceGen.resource(id, project, Json.obj(), tags = tags)
ResourceGen
.resourceFor(resource, types = types, rev = rev, deprecated = deprecated)
.copy(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import ch.epfl.bluebrain.nexus.delta.sdk.model.search.{Sort, SortList}
import ch.epfl.bluebrain.nexus.delta.sdk.projects.model.{ApiMappings, ProjectContext}
import ch.epfl.bluebrain.nexus.delta.sdk.utils.RouteHelpers
import ch.epfl.bluebrain.nexus.delta.sourcing.model.Identity.User
import ch.epfl.bluebrain.nexus.delta.sourcing.model.Tag.UserTag
import ch.epfl.bluebrain.nexus.delta.sourcing.model.{Label, ResourceRef}
import ch.epfl.bluebrain.nexus.testkit.{CirceLiteral, IOValues, TestHelpers, TestMatchers}
import io.circe.generic.extras.Configuration
Expand Down Expand Up @@ -93,6 +94,7 @@ class ElasticSearchViewsDirectivesSpec
val createdAtEncoded = UrlUtils.encode(s"*..${createdAt.value}")
val updatedAt = TimeRange.Between(Instant.EPOCH, Instant.EPOCH.plusSeconds(5L))
val updatedAtEncoded = UrlUtils.encode(s"${updatedAt.start}..${updatedAt.end}")
val tag = UserTag.unsafe("mytag")

val query = List(
"locate" -> "self",
Expand All @@ -108,7 +110,8 @@ class ElasticSearchViewsDirectivesSpec
"type" -> "B",
"type" -> "-C",
"schema" -> "mySchema",
"q" -> "something"
"q" -> "something",
"tag" -> tag.value
).map { case (k, v) => s"$k=$v" }.mkString("&")

val expected = ResourcesSearchParams(
Expand All @@ -126,7 +129,8 @@ class ElasticSearchViewsDirectivesSpec
ExcludedType(iri"${vocab}C")
),
schema = Some(ResourceRef.Latest(iri"${base}mySchema")),
q = Some("something")
q = Some("something"),
tag = Some(tag)
)

Get(s"/search/org/project?$query") ~> Accept(`*/*`) ~> route ~> check {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ object Vocabulary {
val score = Metadata("score")
val self = Metadata("self")
val source = Metadata("source")
val tags = Metadata("tags")
val tokenEndpoint = Metadata("tokenEndpoint")
val total = Metadata("total")
val types = Metadata("types")
Expand Down

0 comments on commit 82897c8

Please sign in to comment.