From 4736064e1891a1d1c3a6213f965b5b7897c3b1b4 Mon Sep 17 00:00:00 2001 From: dantb Date: Fri, 29 Sep 2023 12:02:52 +0200 Subject: [PATCH] Add tags to indices for resources, files and schemas --- .../resources/defaults/default-mapping.json | 4 ++ .../plugins/storage/files/model/File.scala | 42 ++++++++++++------- .../nexus/delta/rdf/Vocabulary.scala | 5 ++- .../src/main/resources/contexts/metadata.json | 4 ++ .../nexus/delta/sdk/ResourceShifts.scala | 2 +- .../nexus/delta/sdk/model/Tags.scala | 1 + .../delta/sdk/resources/model/Resource.scala | 25 ++++++++--- .../delta/sdk/schemas/model/Schema.scala | 21 ++++++++-- .../kg/listings/all/resource-by-type-4.json | 2 + 9 files changed, 80 insertions(+), 26 deletions(-) diff --git a/delta/plugins/elasticsearch/src/main/resources/defaults/default-mapping.json b/delta/plugins/elasticsearch/src/main/resources/defaults/default-mapping.json index 41efb5b71e..35065cd94a 100644 --- a/delta/plugins/elasticsearch/src/main/resources/defaults/default-mapping.json +++ b/delta/plugins/elasticsearch/src/main/resources/defaults/default-mapping.json @@ -52,6 +52,10 @@ "type": "long", "copy_to": "_all_fields" }, + "_tags": { + "type": "text", + "copy_to": "_all_fields" + }, "_deprecated": { "type": "boolean", "copy_to": "_all_fields" diff --git a/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/files/model/File.scala b/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/files/model/File.scala index aa6c845783..a9d21f2fc4 100644 --- a/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/files/model/File.scala +++ b/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/files/model/File.scala @@ -1,9 +1,12 @@ package ch.epfl.bluebrain.nexus.delta.plugins.storage.files.model import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.Files +import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.model.File.Metadata import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.StoragesConfig.StorageTypeConfig import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.model.StorageType import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri +import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary.contexts +import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.ContextValue import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.JsonLdContext.keywords import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.encoder.JsonLdEncoder import ch.epfl.bluebrain.nexus.delta.sdk.ResourceShift @@ -11,6 +14,7 @@ import ch.epfl.bluebrain.nexus.delta.sdk.jsonld.JsonLdContent import ch.epfl.bluebrain.nexus.delta.sdk.model.{BaseUri, IdSegmentRef, Tags} import ch.epfl.bluebrain.nexus.delta.sourcing.model.ResourceRef import ch.epfl.bluebrain.nexus.delta.sourcing.model.ProjectRef +import ch.epfl.bluebrain.nexus.delta.sourcing.model.Tag.UserTag import io.circe.syntax._ import io.circe.{Encoder, Json} @@ -37,32 +41,42 @@ final case class File( storageType: StorageType, attributes: FileAttributes, tags: Tags -) +) { + def metadata: Metadata = Metadata(tags.tags) +} object File { - implicit def fileEncoder(implicit config: StorageTypeConfig): Encoder.AsObject[File] = - Encoder.encodeJsonObject.contramapObject { file => - implicit val storageType: StorageType = file.storageType - val storageJson = Json.obj( - keywords.id -> file.storage.iri.asJson, - keywords.tpe -> storageType.iri.asJson, - "_rev" -> file.storage.rev.asJson - ) - file.attributes.asJsonObject.add("_storage", storageJson) - } + final case class Metadata(tags: List[UserTag]) + + implicit def fileEncoder(implicit config: StorageTypeConfig): Encoder[File] = { file => + implicit val storageType: StorageType = file.storageType + val storageJson = Json.obj( + keywords.id -> file.storage.iri.asJson, + keywords.tpe -> storageType.iri.asJson, + "_rev" -> file.storage.rev.asJson + ) + file.attributes.asJson.mapObject(_.add("_storage", storageJson)) + } implicit def fileJsonLdEncoder(implicit config: StorageTypeConfig): JsonLdEncoder[File] = JsonLdEncoder.computeFromCirce(_.id, Files.context) - type Shift = ResourceShift[FileState, File, Nothing] + implicit private val fileMetadataEncoder: Encoder[Metadata] = { m => + Json.obj("_tags" -> m.tags.asJson) + } + + implicit val fileMetadataJsonLdEncoder: JsonLdEncoder[Metadata] = + JsonLdEncoder.computeFromCirce(ContextValue(contexts.metadata)) + + type Shift = ResourceShift[FileState, File, Metadata] def shift(files: Files)(implicit baseUri: BaseUri, config: StorageTypeConfig): Shift = - ResourceShift.apply[FileState, File]( + ResourceShift.withMetadata[FileState, File, Metadata]( Files.entityType, (ref, project) => files.fetch(IdSegmentRef(ref), project), state => state.toResource, - value => JsonLdContent(value, value.value.asJson, None) + value => JsonLdContent(value, value.value.asJson, Some(value.value.metadata)) ) } diff --git a/delta/rdf/src/main/scala/ch/epfl/bluebrain/nexus/delta/rdf/Vocabulary.scala b/delta/rdf/src/main/scala/ch/epfl/bluebrain/nexus/delta/rdf/Vocabulary.scala index 97f4977909..327fff5517 100644 --- a/delta/rdf/src/main/scala/ch/epfl/bluebrain/nexus/delta/rdf/Vocabulary.scala +++ b/delta/rdf/src/main/scala/ch/epfl/bluebrain/nexus/delta/rdf/Vocabulary.scala @@ -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") @@ -216,7 +217,6 @@ object Vocabulary { val acls = contexts + "acls.json" val aclsMetadata = contexts + "acls-metadata.json" val error = contexts + "error.json" - val validation = contexts + "validation.json" val identities = contexts + "identities.json" val metadata = contexts + "metadata.json" val offset = contexts + "offset.json" @@ -236,10 +236,11 @@ object Vocabulary { val search = contexts + "search.json" val schemasMetadata = contexts + "schemas-metadata.json" val shacl = contexts + "shacl-20170720.json" + val statistics = contexts + "statistics.json" val supervision = contexts + "supervision.json" val tags = contexts + "tags.json" + val validation = contexts + "validation.json" val version = contexts + "version.json" - val statistics = contexts + "statistics.json" } diff --git a/delta/sdk/src/main/resources/contexts/metadata.json b/delta/sdk/src/main/resources/contexts/metadata.json index e3193c6aad..f76fa81274 100644 --- a/delta/sdk/src/main/resources/contexts/metadata.json +++ b/delta/sdk/src/main/resources/contexts/metadata.json @@ -1,6 +1,10 @@ { "@context": { "_rev": "https://bluebrain.github.io/nexus/vocabulary/rev", + "_tags": { + "@id": "https://bluebrain.github.io/nexus/vocabulary/tags", + "@container": "@set" + }, "_deprecated": "https://bluebrain.github.io/nexus/vocabulary/deprecated", "_createdAt": { "@id": "https://bluebrain.github.io/nexus/vocabulary/createdAt", diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/ResourceShifts.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/ResourceShifts.scala index d3f64dd5f2..95762a3a61 100644 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/ResourceShifts.scala +++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/ResourceShifts.scala @@ -12,7 +12,7 @@ import io.circe.Json import monix.bio.{IO, Task, UIO} /** - * Aggregates the different [[ResourceShift]] to perform operations on resources indepently of their types + * Aggregates the different [[ResourceShift]] to perform operations on resources independently of their types */ trait ResourceShifts { diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/model/Tags.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/model/Tags.scala index 2f979e73df..0b82303dec 100644 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/model/Tags.scala +++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/model/Tags.scala @@ -12,6 +12,7 @@ final case class Tags(value: Map[UserTag, Int]) extends AnyVal { def contains(tag: UserTag): Boolean = value.contains(tag) def +(tag: (UserTag, Int)): Tags = Tags(value + tag) def -(tag: UserTag): Tags = Tags(value - tag) + def tags: List[UserTag] = value.keys.toList } object Tags { diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/model/Resource.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/model/Resource.scala index 4c51650779..9a69509744 100644 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/model/Resource.scala +++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/model/Resource.scala @@ -2,6 +2,7 @@ package ch.epfl.bluebrain.nexus.delta.sdk.resources.model import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri import ch.epfl.bluebrain.nexus.delta.rdf.RdfError +import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary.contexts import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.api.{JsonLdApi, JsonLdOptions} import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.{ContextValue, RemoteContextResolution} import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.encoder.JsonLdEncoder @@ -10,9 +11,12 @@ import ch.epfl.bluebrain.nexus.delta.sdk.ResourceShift import ch.epfl.bluebrain.nexus.delta.sdk.jsonld.JsonLdContent import ch.epfl.bluebrain.nexus.delta.sdk.model.{BaseUri, IdSegmentRef, Tags} import ch.epfl.bluebrain.nexus.delta.sdk.resources.Resources +import ch.epfl.bluebrain.nexus.delta.sdk.resources.model.Resource.Metadata import ch.epfl.bluebrain.nexus.delta.sdk.syntax._ +import ch.epfl.bluebrain.nexus.delta.sourcing.model.Tag.UserTag import ch.epfl.bluebrain.nexus.delta.sourcing.model.{ProjectRef, ResourceRef} -import io.circe.Json +import io.circe.syntax.EncoderOps +import io.circe.{Encoder, Json} import monix.bio.IO /** @@ -41,10 +45,14 @@ final case class Resource( source: Json, compacted: CompactedJsonLd, expanded: ExpandedJsonLd -) +) { + def metadata: Metadata = Metadata(tags.tags) +} object Resource { + final case class Metadata(tags: List[UserTag]) + implicit val resourceJsonLdEncoder: JsonLdEncoder[Resource] = new JsonLdEncoder[Resource] { @@ -62,13 +70,20 @@ object Resource { value.source.topContextValueOrEmpty } - type Shift = ResourceShift[ResourceState, Resource, Nothing] + implicit private val fileMetadataEncoder: Encoder[Metadata] = { m => + Json.obj("_tags" -> m.tags.asJson) + } + + implicit val fileMetadataJsonLdEncoder: JsonLdEncoder[Metadata] = + JsonLdEncoder.computeFromCirce(ContextValue(contexts.metadata)) + + type Shift = ResourceShift[ResourceState, Resource, Metadata] def shift(resources: Resources)(implicit baseUri: BaseUri): Shift = - ResourceShift.apply[ResourceState, Resource]( + ResourceShift.withMetadata[ResourceState, Resource, Metadata]( Resources.entityType, (ref, project) => resources.fetch(IdSegmentRef(ref), project, None), state => state.toResource, - value => JsonLdContent(value, value.value.source, None) + value => JsonLdContent(value, value.value.source, Some(value.value.metadata)) ) } diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/schemas/model/Schema.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/schemas/model/Schema.scala index fd82ec9c8a..95b136f095 100644 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/schemas/model/Schema.scala +++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/schemas/model/Schema.scala @@ -17,9 +17,12 @@ import ch.epfl.bluebrain.nexus.delta.sdk.model.{BaseUri, IdSegmentRef, Tags} import ch.epfl.bluebrain.nexus.delta.sdk.schemas.Schemas import ch.epfl.bluebrain.nexus.delta.sdk.syntax._ import ch.epfl.bluebrain.nexus.delta.sourcing.model.ProjectRef -import io.circe.Json +import io.circe.{Encoder, Json} import monix.bio.IO import ch.epfl.bluebrain.nexus.delta.kernel.effect.migration._ +import ch.epfl.bluebrain.nexus.delta.sdk.schemas.model.Schema.Metadata +import ch.epfl.bluebrain.nexus.delta.sourcing.model.Tag.UserTag +import io.circe.syntax.EncoderOps /** * A schema representation @@ -67,10 +70,13 @@ final case class Schema( Graph.empty(id).add(triples) } + def metadata: Metadata = Metadata(tags.tags) } object Schema { + final case class Metadata(tags: List[UserTag]) + implicit val schemaJsonLdEncoder: JsonLdEncoder[Schema] = new JsonLdEncoder[Schema] { @@ -88,14 +94,21 @@ object Schema { value.source.topContextValueOrEmpty.merge(ContextValue(contexts.shacl)) } - type Shift = ResourceShift[SchemaState, Schema, Nothing] + implicit private val fileMetadataEncoder: Encoder[Metadata] = { m => + Json.obj("_tags" -> m.tags.asJson) + } + + implicit val fileMetadataJsonLdEncoder: JsonLdEncoder[Metadata] = + JsonLdEncoder.computeFromCirce(ContextValue(contexts.schemasMetadata)) + + type Shift = ResourceShift[SchemaState, Schema, Metadata] def shift(schemas: Schemas)(implicit baseUri: BaseUri): Shift = - ResourceShift.apply[SchemaState, Schema]( + ResourceShift.withMetadata[SchemaState, Schema, Metadata]( Schemas.entityType, (ref, project) => schemas.fetch(IdSegmentRef(ref), project).toBIO[SchemaRejection], state => state.toResource, - value => JsonLdContent(value, value.value.source, None) + value => JsonLdContent(value, value.value.source, Some(value.value.metadata)) ) } diff --git a/tests/src/test/resources/kg/listings/all/resource-by-type-4.json b/tests/src/test/resources/kg/listings/all/resource-by-type-4.json index ce161b996a..04c844a90e 100644 --- a/tests/src/test/resources/kg/listings/all/resource-by-type-4.json +++ b/tests/src/test/resources/kg/listings/all/resource-by-type-4.json @@ -25,6 +25,7 @@ "_deprecated": false, "_project": "{{deltaUri}}/projects/{{org2}}/{{proj3}}", "_rev": 2, + "_tags": ["v1.0.1"], "_schemaProject": "{{deltaUri}}/projects/{{org2}}/{{proj3}}", "_updatedBy": "{{deltaUri}}/realms/{{realm}}/users/{{user}}" }, @@ -47,6 +48,7 @@ "_deprecated": false, "_project": "{{deltaUri}}/projects/{{org1}}/{{proj1}}", "_rev": 2, + "_tags": ["v1.0.0"], "_schemaProject": "{{deltaUri}}/projects/{{org1}}/{{proj1}}", "_updatedBy": "{{deltaUri}}/realms/{{realm}}/users/{{user}}" }