Skip to content

Commit

Permalink
Add entity type facet to search response
Browse files Browse the repository at this point in the history
  • Loading branch information
eikek committed Mar 7, 2024
1 parent 5cf1d18 commit 366318d
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@ import io.renku.search.api.data.*
import io.renku.search.model.users
import io.renku.search.solr.client.SearchSolrClient
import io.renku.search.solr.documents.Entity as SolrEntity
import io.renku.search.solr.schema.EntityDocumentSchema.Fields
import io.renku.solr.client.QueryResponse
import org.http4s.dsl.Http4sDsl
import scribe.Scribe
import io.renku.search.model.EntityType
import io.renku.solr.client.facet.FacetResponse

private class SearchApiImpl[F[_]: Async](solrClient: SearchSolrClient[F])
extends Http4sDsl[F]
Expand Down Expand Up @@ -58,9 +61,19 @@ private class SearchApiImpl[F[_]: Async](solrClient: SearchSolrClient[F])
): SearchResult =
val hasMore = solrResult.responseBody.docs.size > currentPage.limit
val pageInfo = PageWithTotals(currentPage, solrResult.responseBody.numFound, hasMore)
val facets = solrResult.facetResponse
.flatMap(_.buckets.get(Fields.entityType))
.map { counts =>
val all =
counts.buckets.flatMap { case FacetResponse.Bucket(key, count) =>
EntityType.fromString(key).toOption.map(et => et -> count)
}.toMap
FacetData(all)
}
.getOrElse(FacetData.empty)
val items = solrResult.responseBody.docs.map(toApiEntity)
if (hasMore) SearchResult(items.init, pageInfo)
else SearchResult(items, pageInfo)
if (hasMore) SearchResult(items.init, facets, pageInfo)
else SearchResult(items, facets, pageInfo)

private lazy val toApiEntity: SolrEntity => SearchEntity =
given Transformer[users.Id, UserId] = (id: users.Id) => UserId(id)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2024 Swiss Data Science Center (SDSC)
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
* Eidgenössische Technische Hochschule Zürich (ETHZ).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.renku.search.api.data

import io.renku.search.model.EntityType
import io.bullet.borer.Decoder
import io.bullet.borer.derivation.MapBasedCodecs
import sttp.tapir.Schema
import io.bullet.borer.Encoder
import io.renku.search.api.tapir.SchemaSyntax.*

final case class FacetData(
entityType: Map[EntityType, Int]
)

object FacetData:
val empty: FacetData = FacetData(Map.empty)

given Decoder[FacetData] = MapBasedCodecs.deriveDecoder
given Encoder[FacetData] = MapBasedCodecs.deriveEncoder
given Schema[FacetData] = {
given Schema[Map[EntityType, Int]] = Schema.schemaForMap(_.name)
Schema
.derived[FacetData]
.jsonExample(
FacetData(
Map(
EntityType.Project -> 15,
EntityType.User -> 3
)
)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import sttp.tapir.Schema

final case class SearchResult(
items: Seq[SearchEntity],
facets: FacetData,
pagingInfo: PageWithTotals
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2024 Swiss Data Science Center (SDSC)
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
* Eidgenössische Technische Hochschule Zürich (ETHZ).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.renku.search.api.tapir

import io.bullet.borer.Encoder
import io.bullet.borer.Json
import sttp.tapir.Schema

trait SchemaSyntax:

extension [T](self: Schema[T])
def jsonExample(value: T)(using Encoder[T]): Schema[T] =
self.encodedExample(Json.encode(value).toUtf8String)

object SchemaSyntax extends SchemaSyntax
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,18 @@ package io.renku.search.api.tapir
import sttp.tapir.*
import io.renku.search.api.data.*
import io.renku.search.query.Query
import io.renku.search.model.EntityType

trait TapirCodecs:
given Codec[String, Query, CodecFormat.TextPlain] =
Codec.string.mapEither(Query.parse(_))(_.render)

given Schema[Query] = Schema.anyObject[Query]
given Schema[QueryInput] = Schema.derived

given Codec[String, EntityType, CodecFormat.TextPlain] =
Codec.string.mapEither(EntityType.fromString(_))(_.name)

given Schema[EntityType] = Schema.derivedEnumeration.defaultStringBased

object TapirCodecs extends TapirCodecs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,20 @@ import io.renku.search.solr.documents.Entity
import io.renku.search.solr.query.LuceneQueryInterpreter
import io.renku.solr.client.{QueryData, QueryResponse, QueryString, SolrClient}
import io.renku.solr.client.schema.FieldName
import io.renku.solr.client.facet.{Facet, Facets}
import io.renku.search.solr.schema.EntityDocumentSchema

private class SearchSolrClientImpl[F[_]: Async](solrClient: SolrClient[F])
extends SearchSolrClient[F]:

private[this] val logger = scribe.cats.effect[F]
private[this] val interpreter = LuceneQueryInterpreter.forSync[F]

private val typeTerms = Facet.Terms(
EntityDocumentSchema.Fields.entityType,
EntityDocumentSchema.Fields.entityType
)

override def insert[D: Encoder](documents: Seq[D]): F[Unit] =
solrClient.insert(documents).void

Expand All @@ -48,6 +55,7 @@ private class SearchSolrClientImpl[F[_]: Async](solrClient: SolrClient[F])
.query[Entity](
QueryData(QueryString(solrQuery.query.value, limit, offset))
.withSort(solrQuery.sort)
.withFacet(Facets(typeTerms))
.withFields(FieldName.all, FieldName.score)
)
} yield res
2 changes: 1 addition & 1 deletion nix/openapi-doc.nix
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<script>
window.onload = () => {
window.ui = SwaggerUIBundle({
url: 'http://localhost:8080/apiv2/search/spec.json',
url: 'http://localhost:8080/search/spec.json',
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
Expand Down

0 comments on commit 366318d

Please sign in to comment.