Skip to content

Commit

Permalink
Merge pull request #150 from SwissDataScienceCenter/creator-details
Browse files Browse the repository at this point in the history
Return user details for creator in project results
  • Loading branch information
eikek authored Jun 14, 2024
2 parents 80612a7 + 9afe55a commit 92b7f52
Show file tree
Hide file tree
Showing 24 changed files with 133 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ trait EntityConverter:
repositories = p.repositories,
visibility = p.visibility,
description = p.description,
createdBy = SearchEntity.UserId(p.createdBy),
createdBy = p.creatorDetails
.flatMap(_.docs.headOption)
.map(user)
.orElse(Some(SearchEntity.User(p.createdBy))),
creationDate = p.creationDate,
keywords = p.keywords,
score = p.score
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,18 @@ package io.renku.search.api.data

import io.bullet.borer.*
import io.bullet.borer.NullOptions.given
import io.bullet.borer.derivation.MapBasedCodecs.{deriveAllCodecs, deriveCodec}
import io.bullet.borer.derivation.MapBasedCodecs
import io.renku.json.EncoderSupport
import io.renku.search.model.*

sealed trait SearchEntity:
def id: Id
def score: Option[Double]

object SearchEntity:
private[api] val discriminatorField = "type"
given AdtEncodingStrategy = AdtEncodingStrategy.flat(discriminatorField)
given Codec[SearchEntity] = deriveAllCodecs[SearchEntity]
given Decoder[SearchEntity] = MapBasedCodecs.deriveDecoder[SearchEntity]
given Encoder[SearchEntity] = EncoderSupport.derive[SearchEntity]

final case class Project(
id: Id,
Expand All @@ -40,15 +41,17 @@ object SearchEntity:
repositories: Seq[Repository],
visibility: Visibility,
description: Option[Description] = None,
createdBy: UserId,
createdBy: Option[User],
creationDate: CreationDate,
keywords: List[Keyword] = Nil,
score: Option[Double] = None
) extends SearchEntity

final case class UserId(id: Id)
object UserId:
given Codec[UserId] = deriveCodec[UserId]
object Project:
given Encoder[Project] =
EncoderSupport.deriveWithDiscriminator[Project](discriminatorField)
given Decoder[Project] = MapBasedCodecs.deriveDecoder
end Project

final case class User(
id: Id,
Expand All @@ -58,10 +61,19 @@ object SearchEntity:
score: Option[Double] = None
) extends SearchEntity

object User:
given Encoder[User] = EncoderSupport.deriveWithDiscriminator(discriminatorField)
given Decoder[User] = MapBasedCodecs.deriveDecoder
end User

final case class Group(
id: Id,
name: Name,
namespace: Namespace,
description: Option[Description] = None,
score: Option[Double] = None
) extends SearchEntity
object Group:
given Encoder[Group] = EncoderSupport.deriveWithDiscriminator(discriminatorField)
given Decoder[Group] = MapBasedCodecs.deriveDecoder
end Group
Original file line number Diff line number Diff line change
Expand Up @@ -39,22 +39,34 @@ trait ApiSchema extends ApiSchema.Primitives:
.derived[Group]
.jsonExample(ApiSchema.exampleGroup)

given Schema[Project] = Schema
given (using userSchema: Schema[User]): Schema[Project] = Schema
.derived[Project]
.modify(_.createdBy) { schemaOptUser =>
// this is necessary to include the `type` property into the schema of the createdBy property
// It is not added automagically, because we use the concrete type `User` and not `SearchEntity`
// (the sealed trait).
// Using `SearchEntity` results in a deadlock when evaluating the magnolia macros from tapir. I
// tried to make all components lazy, but didn't manage to solve it
val userType = userSchema.schemaType.asInstanceOf[SProduct[User]]
val df = SProductField[User, String](
FieldName("type"),
Schema.string,
_ => Some("User")
)
val nextUserSchema: Schema[User] =
userSchema.copy(schemaType = userType.copy(fields = df :: userType.fields))
schemaOptUser.copy(schemaType = SOption(nextUserSchema)(identity))
}
.jsonExample(ApiSchema.exampleProject)

given Schema[UserId] = Schema
.derived[UserId]
.jsonExample(UserId(Id("01HRA7AZ2Q234CDQWGA052F8MK")))

given (using
projectSchema: Schema[Project],
userSchema: Schema[User],
groupSchema: Schema[Group]
): Schema[SearchEntity] = {
val derived = Schema.derived[SearchEntity]
derived.schemaType match {
case s: SCoproduct[_] =>
case s: SCoproduct[?] =>
derived.copy(schemaType =
s.addDiscriminatorField(
FieldName(SearchEntity.discriminatorField),
Expand Down Expand Up @@ -107,16 +119,16 @@ object ApiSchema:
)

val exampleProject: SearchEntity = Project(
Id("01HRA7AZ2Q234CDQWGA052F8MK"),
Name("renku"),
Slug("renku"),
Some(Namespace("renku/renku")),
Seq(Repository("https://github.com/renku")),
Visibility.Public,
Some(Description("Renku project")),
UserId(Id("bla")),
CreationDate(Instant.now),
List(Keyword("data"), Keyword("science")),
Some(1.0)
id = Id("01HRA7AZ2Q234CDQWGA052F8MK"),
name = Name("renku"),
slug = Slug("renku"),
namespace = Some(Namespace("renku/renku")),
repositories = Seq(Repository("https://github.com/renku")),
visibility = Visibility.Public,
description = Some(Description("Renku project")),
createdBy = Some(exampleUser.asInstanceOf[User]),
creationDate = CreationDate(Instant.now),
keywords = List(Keyword("data"), Keyword("science")),
score = Some(1.0)
)
end ApiSchema
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import io.renku.search.solr.client.SearchSolrSuite
import io.renku.search.solr.client.SolrDocumentGenerators.*
import io.renku.search.solr.documents.{EntityDocument, User as SolrUser}
import io.renku.solr.client.DocVersion
import io.renku.solr.client.ResponseBody
import munit.CatsEffectSuite
import org.scalacheck.Gen
import scribe.Scribe
Expand All @@ -43,11 +44,13 @@ class SearchApiSpec extends CatsEffectSuite with SearchSolrSuite:
val project1 = projectDocumentGen(
"matching",
"matching description",
Gen.const(None),
Gen.const(Visibility.Public)
).generateOne
val project2 = projectDocumentGen(
"disparate",
"disparate description",
Gen.const(None),
Gen.const(Visibility.Public)
).generateOne
for {
Expand All @@ -66,13 +69,14 @@ class SearchApiSpec extends CatsEffectSuite with SearchSolrSuite:
)

test("return Project and User entities"):
val userId = ModelGenerators.idGen.generateOne
val user = SolrUser(userId, DocVersion.NotExists, FirstName("exclusive").some)
val project = projectDocumentGen(
"exclusive",
"exclusive description",
Gen.const(None),
Gen.const(Visibility.Public)
).generateOne
val user =
SolrUser(project.createdBy, DocVersion.NotExists, FirstName("exclusive").some)
).generateOne.copy(createdBy = userId)
for {
client <- IO(searchSolrClient())
searchApi = new SearchApiImpl[IO](client)
Expand All @@ -81,7 +85,10 @@ class SearchApiSpec extends CatsEffectSuite with SearchSolrSuite:
.query(AuthContext.anonymous)(mkQuery("exclusive"))
.map(_.fold(err => fail(s"Calling Search API failed with $err"), identity))

expected = toApiEntities(project, user).toSet
expected = toApiEntities(
project.copy(creatorDetails = ResponseBody.single(user).some),
user
).toSet
obtained = results.items.map(scoreToNone).toSet
} yield assert(
expected.diff(obtained).isEmpty,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import io.renku.search.http.borer.BorerEntityJsonCodec.given
import io.renku.search.model.*
import io.renku.search.solr.documents.{Project, User}
import io.renku.solr.client.DocVersion
import io.renku.solr.client.ResponseBody
import org.http4s.*
import org.http4s.MediaType.application
import org.http4s.Method.{GET, POST}
Expand Down Expand Up @@ -101,7 +102,8 @@ private class RandommerIoDocsCreator[F[_]: Async: ModelTypesGenerators](
description = Some(desc),
keywords = keywords,
createdBy = user.id,
creationDate = creationDate
creationDate = creationDate,
creatorDetails = Some(ResponseBody.single(user))
) -> List(user)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ trait Projects:
description = pc.description.map(_.toDescription),
keywords = pc.keywords.map(_.toKeyword).toList,
createdBy = pc.createdBy.toId,
creatorDetails = None,
creationDate = pc.creationDate.toCreationDate
)

Expand All @@ -68,7 +69,8 @@ trait Projects:
description = pc.description.map(_.toDescription),
keywords = pc.keywords.map(_.toKeyword).toList,
createdBy = pc.createdBy.toId,
creationDate = pc.creationDate.toCreationDate
creationDate = pc.creationDate.toCreationDate,
creatorDetails = None
)

def fromProjectUpdated(pu: v1.ProjectUpdated, orig: ProjectDocument): ProjectDocument =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ object GroupMemberAddedSpec:
.map(_.setMembers(groupMembers))
projects <- Gen
.choose(1, 6)
.flatMap(n => Gen.listOfN(n, SolrDocumentGenerators.projectDocumentGen))
.flatMap(n =>
Gen.listOfN(n, SolrDocumentGenerators.projectDocumentGenForInsert)
)
.map(
_.map(
_.copy(namespace = Some(group.namespace))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@ object GroupMemberRemovedSpec:
.map(_.setMembers(groupMembers))
projects <- Gen
.choose(1, 6)
.flatMap(n => Gen.listOfN(n, SolrDocumentGenerators.projectDocumentGen))
.flatMap(n =>
Gen.listOfN(n, SolrDocumentGenerators.projectDocumentGenForInsert)
)
.map(
_.map(
_.copy(namespace = Some(group.namespace))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ object GroupMemberUpdatedSpec:
.map(_.setMembers(groupMembers))
projects <- Gen
.choose(1, 6)
.flatMap(n => Gen.listOfN(n, SolrDocumentGenerators.projectDocumentGen))
.flatMap(n =>
Gen.listOfN(n, SolrDocumentGenerators.projectDocumentGenForInsert)
)
.map(
_.map(
_.copy(namespace = Some(group.namespace))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,5 +113,5 @@ object GroupRemovedProcessSpec:
def create: Gen[DbState] =
for
group <- SolrDocumentGenerators.groupDocumentGen
projects <- SolrDocumentGenerators.projectDocumentGen.asListOfN()
projects <- SolrDocumentGenerators.projectDocumentGenForInsert.asListOfN()
yield DbState(group, projects.toSet.map(_.copy(namespace = group.namespace.some)))
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ object GroupUpdatedProvisioningSpec:
val pgroup = SolrDocumentGenerators.partialGroupGen.generateOne
val upd = EventsGenerators.groupUpdatedGen("group-update").generateOne
val group2 = SolrDocumentGenerators.groupDocumentGen.generateOne
val projects = SolrDocumentGenerators.projectDocumentGen
val projects = SolrDocumentGenerators.projectDocumentGenForInsert
.asListOfN(1, 6)
.map(
_.map(_.copy(namespace = Some(group2.namespace), version = DocVersion.NotExists))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ object AuthorizationAddedProvisioningSpec:
val testCases =
for {
role <- MemberRole.valuesV1.toList
proj = SolrDocumentGenerators.projectDocumentGen.generateOne
proj = SolrDocumentGenerators.projectDocumentGenForInsert.generateOne
pproj = SolrDocumentGenerators.partialProjectGen.generateOne
dbState <- List(DbState.Empty, DbState.Project(proj), DbState.PartialProject(pproj))
userId = ModelGenerators.idGen.generateOne
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ object AuthorizationRemovedProvisioningSpec:
val userId = ModelGenerators.idGen.generateOne
val userRole = ModelGenerators.memberRoleGen.generateOne
val proj =
SolrDocumentGenerators.projectDocumentGen.generateOne.modifyEntityMembers(
SolrDocumentGenerators.projectDocumentGenForInsert.generateOne.modifyEntityMembers(
_.addMember(userId, userRole)
)
val pproj =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ object AuthorizationUpdatedProvisioningSpec:
val testCases =
for {
role <- MemberRole.valuesV1.toList
proj = SolrDocumentGenerators.projectDocumentGen.generateOne
proj = SolrDocumentGenerators.projectDocumentGenForInsert.generateOne
pproj = SolrDocumentGenerators.partialProjectGen.generateOne
dbState <- List(DbState.Empty, DbState.Project(proj), DbState.PartialProject(pproj))
userId = ModelGenerators.idGen.generateOne
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ object ProjectCreatedProvisioningSpec:
override def toString = s"ProjectCreated: ${projectId.value.take(6)}… db=$dbState"

val testCases =
val proj = SolrDocumentGenerators.projectDocumentGen.generateOne
val proj = SolrDocumentGenerators.projectDocumentGenForInsert.generateOne
val pproj = SolrDocumentGenerators.partialProjectGen.generateOne
val group = SolrDocumentGenerators.entityMembersGen
.flatMap(em => SolrDocumentGenerators.groupDocumentGen.map(g => g.setMembers(em)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,9 @@ object ProjectUpdatedProvisioningSpec:
val testCases =
val em = SolrDocumentGenerators.entityMembersGen
val proj = em
.flatMap(gm => SolrDocumentGenerators.projectDocumentGen.map(_.setGroupMembers(gm)))
.flatMap(gm =>
SolrDocumentGenerators.projectDocumentGenForInsert.map(_.setGroupMembers(gm))
)
.generateOne
val pproj = em
.flatMap(gm => SolrDocumentGenerators.partialProjectGen.map(_.setGroupMembers(gm)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ object UserRemovedProcessSpec:
val testCases: List[TestCase] =
val role = ModelGenerators.memberRoleGen.generateOne
val user = SolrDocumentGenerators.userDocumentGen.generateOne
val proj = SolrDocumentGenerators.projectDocumentGen
val proj = SolrDocumentGenerators.projectDocumentGenForInsert
.asListOfN(1, 3)
.map(_.map(_.modifyEntityMembers(_.addMember(user.id, role))))
.generateOne
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ private class SearchSolrClientImpl[F[_]: Async](solrClient: SolrClient[F])
private val logger = scribe.cats.effect[F]
private val interpreter = LuceneQueryInterpreter.forSync[F]

private val creatorDetails: FieldName = FieldName("creatorDetails")

private val typeTerms = Facet.Terms(
EntityDocumentSchema.Fields.entityType,
EntityDocumentSchema.Fields.entityType
Expand Down Expand Up @@ -69,6 +71,14 @@ private class SearchSolrClientImpl[F[_]: Async](solrClient: SolrClient[F])
.withSort(solrQuery.sort)
.withFacet(Facets(typeTerms))
.withFields(FieldName.all, FieldName.score)
.addSubQuery(
creatorDetails,
SubQuery(
"{!terms f=id v=$row.createdBy}",
"{!terms f=_kind v=fullentity}",
1
).withFields(FieldName.all)
)
)
} yield res

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import io.renku.search.model.*
import io.renku.search.model.MemberRole.*
import io.renku.search.solr.schema.EntityDocumentSchema.Fields
import io.renku.solr.client.DocVersion
import io.renku.solr.client.ResponseBody

sealed trait EntityDocument extends SolrDocument:
val score: Option[Double]
Expand All @@ -49,6 +50,7 @@ final case class Project(
visibility: Visibility,
description: Option[Description] = None,
createdBy: Id,
creatorDetails: Option[ResponseBody[User]] = None,
creationDate: CreationDate,
owners: Set[Id] = Set.empty,
editors: Set[Id] = Set.empty,
Expand Down
Loading

0 comments on commit 92b7f52

Please sign in to comment.