From 7e8c759e8d1f132832cb8acf140be8017e48fd27 Mon Sep 17 00:00:00 2001 From: irinaschubert Date: Mon, 10 Jan 2022 11:49:15 +0100 Subject: [PATCH] fix(search): Return matching sub-nodes when searching for list label (DEV-158) (#1973) * Return matching sub-nodes when searching for list label * add test * remove code smells * rename value names --- test_data/all_data/books-data.ttl | 82 +++++++++++++- .../v2/searchFulltextStandard.scala.txt | 10 +- .../responders/v2/SearchResponderV2Spec.scala | 25 +++- .../v2/SearchResponderV2SpecFullData.scala | 107 +++++++++++++----- 4 files changed, 190 insertions(+), 34 deletions(-) diff --git a/test_data/all_data/books-data.ttl b/test_data/all_data/books-data.ttl index 0e643fab05..230766b873 100644 --- a/test_data/all_data/books-data.ttl +++ b/test_data/all_data/books-data.ttl @@ -30,6 +30,26 @@ knora-base:hasPermissions "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser" ; knora-base:attachedToUser . + + a knora-base:TextValue ; + knora-base:valueHasUUID "IN4R19yYR0ygi3K2VEHpUQ"^^xsd:string ; + knora-base:isDeleted false ; + knora-base:valueCreationDate "2018-05-29T16:42:04.381Z"^^xsd:dateTime ; + knora-base:valueHasOrder 0 ; + knora-base:valueHasString "Lord of the Rings" ; + knora-base:hasPermissions "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser" ; + knora-base:attachedToUser . + + + a knora-base:TextValue ; + knora-base:valueHasUUID "IN4R19yYR0ygi3K2VEHpUQ"^^xsd:string ; + knora-base:isDeleted false ; + knora-base:valueCreationDate "2018-05-29T16:42:04.381Z"^^xsd:dateTime ; + knora-base:valueHasOrder 0 ; + knora-base:valueHasString "Treasure Island" ; + knora-base:hasPermissions "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser" ; + knora-base:attachedToUser . + a knora-base:IntValue ; knora-base:valueHasUUID "SZyeLLmOTcCCuS3B0VksHQ"^^xsd:string ; @@ -51,6 +71,26 @@ knora-base:valueCreationDate "2018-05-29T16:42:04.381Z"^^xsd:dateTime; knora-base:hasPermissions "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser" . + a knora-base:ListValue; + knora-base:valueHasUUID "list_value_fantasy"^^xsd:string; + knora-base:isDeleted false; + knora-base:attachedToUser ; + knora-base:valueHasString "http://rdfh.ch/lists/0001/ynm02-04"; + knora-base:valueHasOrder 0; + knora-base:valueHasListNode ; + knora-base:valueCreationDate "2018-05-29T16:42:04.381Z"^^xsd:dateTime; + knora-base:hasPermissions "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser" . + + a knora-base:ListValue; + knora-base:valueHasUUID "list_value_adventure"^^xsd:string; + knora-base:isDeleted false; + knora-base:attachedToUser ; + knora-base:valueHasString "http://rdfh.ch/lists/0001/ynm02-05"; + knora-base:valueHasOrder 0; + knora-base:valueHasListNode ; + knora-base:valueCreationDate "2018-05-29T16:42:04.381Z"^^xsd:dateTime; + knora-base:hasPermissions "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser" . + a books:Book ; knora-base:attachedToUser ; @@ -72,6 +112,28 @@ rdfs:label "instance of a book with a list value" ; knora-base:isDeleted false . + + a books:Book ; + knora-base:attachedToUser ; + knora-base:attachedToProject ; + knora-base:hasPermissions "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser" ; + knora-base:creationDate "2019-11-29T10:00:00.673298Z"^^xsd:dateTime ; + books:hasTitle ; + books:hasTextType ; + rdfs:label "Lord of the Rings" ; + knora-base:isDeleted false . + + + a books:Book ; + knora-base:attachedToUser ; + knora-base:attachedToProject ; + knora-base:hasPermissions "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser" ; + knora-base:creationDate "2019-11-29T10:00:00.673298Z"^^xsd:dateTime ; + books:hasTitle ; + books:hasTextType ; + rdfs:label "Treasure Island" ; + knora-base:isDeleted false . + a books:Page ; knora-base:attachedToUser ; @@ -96,7 +158,22 @@ knora-base:hasRootNode ; knora-base:listNodePosition 0 ; rdfs:label "Novel"@en ; - rdfs:label "Roman"@de . + rdfs:label "Roman"@de ; + knora-base:hasSubListNode , . + + a knora-base:ListNode ; + knora-base:listNodeName "fantasy" ; + knora-base:hasRootNode ; + knora-base:listNodePosition 0 ; + rdfs:label "Fantasy novel"@en ; + rdfs:label "Fantasyroman"@de . + + a knora-base:ListNode ; + knora-base:listNodeName "adventure" ; + knora-base:hasRootNode ; + knora-base:listNodePosition 1 ; + rdfs:label "Adventure novel"@en ; + rdfs:label "abenteuerroman"@de . a knora-base:ListNode ; knora-base:listNodeName "short-story" ; @@ -111,6 +188,3 @@ knora-base:listNodePosition 2 ; rdfs:label "Non fiction"@en ; rdfs:label "Sachbuch"@de . - - - diff --git a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/searchFulltextStandard.scala.txt b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/searchFulltextStandard.scala.txt index 94ec21ad11..3671a3d734 100644 --- a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/searchFulltextStandard.scala.txt +++ b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/v2/searchFulltextStandard.scala.txt @@ -103,12 +103,16 @@ WHERE { } OPTIONAL { - ?matchingSubject a ?valueObjectType . + # get all list nodes that match the search term + ?matchingSubject a knora-base:ListNode . - ?valueObjectType rdfs:subClassOf *knora-base:ListNode . + # get sub-node(s) of that node(s) (recursively) + ?matchingSubject knora-base:hasSubListNode* ?subListNode . - ?listValue knora-base:valueHasListNode ?matchingSubject . + # get all values that point to the node(s) and sub-node(s) + ?listValue knora-base:valueHasListNode ?subListNode . + # get all resources that have that values ?subjectWithListValue ?predicate ?listValue . FILTER NOT EXISTS { diff --git a/webapi/src/test/scala/org/knora/webapi/responders/v2/SearchResponderV2Spec.scala b/webapi/src/test/scala/org/knora/webapi/responders/v2/SearchResponderV2Spec.scala index c975a0170c..d9c19e2239 100644 --- a/webapi/src/test/scala/org/knora/webapi/responders/v2/SearchResponderV2Spec.scala +++ b/webapi/src/test/scala/org/knora/webapi/responders/v2/SearchResponderV2Spec.scala @@ -260,7 +260,30 @@ class SearchResponderV2Spec extends CoreSpec() with ImplicitSender { expectMsgPF(timeout) { case response: ReadResourcesSequenceV2 => compareReadResourcesSequenceV2Response( - expected = searchResponderV2SpecFullData.fulltextSearchForListNodeLabel, + expected = searchResponderV2SpecFullData.expectedResultFulltextSearchForListNodeLabel, + received = response + ) + } + } + + "search for list label and find sub-nodes" in { + + responderManager ! FulltextSearchRequestV2( + searchValue = "novel", + offset = 0, + limitToProject = None, + limitToResourceClass = None, + limitToStandoffClass = None, + returnFiles = false, + targetSchema = ApiV2Complex, + schemaOptions = SchemaOptions.ForStandoffWithTextValues, + featureFactoryConfig = defaultFeatureFactoryConfig, + requestingUser = SharedTestDataADM.anythingUser1 + ) + + expectMsgPF(timeout) { case response: ReadResourcesSequenceV2 => + compareReadResourcesSequenceV2Response( + expected = searchResponderV2SpecFullData.expectedResultFulltextSearchForListNodeLabelWithSubnodes, received = response ) } diff --git a/webapi/src/test/scala/org/knora/webapi/responders/v2/SearchResponderV2SpecFullData.scala b/webapi/src/test/scala/org/knora/webapi/responders/v2/SearchResponderV2SpecFullData.scala index 290eb04597..45318a32c7 100644 --- a/webapi/src/test/scala/org/knora/webapi/responders/v2/SearchResponderV2SpecFullData.scala +++ b/webapi/src/test/scala/org/knora/webapi/responders/v2/SearchResponderV2SpecFullData.scala @@ -18,6 +18,41 @@ class SearchResponderV2SpecFullData(implicit stringFormatter: StringFormatter) { implicit lazy val system: ActorSystem = ActorSystem("webapi") + val booksBookIri: String = "http://www.knora.org/ontology/0001/books#Book" + val booksHasTextType: String = "http://www.knora.org/ontology/0001/books#hasTextType" + val testUser1: String = "http://rdfh.ch/users/9XBCrDV3SRa7kS1WwynB4Q" + val testUser2: String = "http://rdfh.ch/users/BhkfBc3hTeS_IDo-JgXRbQ" + val bookTemplateReadResource: ReadResourceV2 = ReadResourceV2( + label = "", + resourceIri = "", + permissions = + "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser", + userPermission = ChangeRightsPermission, + attachedToUser = testUser1, + resourceClassIri = booksBookIri.toSmartIri, + projectADM = SharedTestDataADM.anythingProject, + creationDate = Instant.parse("2019-11-29T10:00:00.673298Z"), + values = Map(), + lastModificationDate = None, + versionDate = None, + deletionInfo = None + ) + val listValueTemplateReadOtherValue: ReadOtherValueV2 = ReadOtherValueV2( + valueContent = HierarchicalListValueContentV2( + ontologySchema = InternalSchema, + valueHasListNode = "" + ), + valueIri = "", + valueHasUUID = stringFormatter.decodeUuid("d34d34d3-4d34-d34d-3496-2b2dfef6a5b9"), + permissions = + "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser", + userPermission = ModifyPermission, + previousValueIri = None, + valueCreationDate = Instant.parse("2018-05-29T16:42:04.381Z"), + attachedToUser = testUser2, + deletionInfo = None + ) + val fulltextSearchForNarr: ReadResourcesSequenceV2 = ReadResourcesSequenceV2( resources = Vector( ReadResourceV2( @@ -980,7 +1015,7 @@ class SearchResponderV2SpecFullData(implicit stringFormatter: StringFormatter) { resourceIri = "http://rdfh.ch/0001/a-thing-with-text-values", permissions = "CR knora-admin:Creator|V knora-admin:ProjectMember", userPermission = ChangeRightsPermission, - attachedToUser = "http://rdfh.ch/users/9XBCrDV3SRa7kS1WwynB4Q", + attachedToUser = testUser1, resourceClassIri = "http://www.knora.org/ontology/0001/anything#Thing".toSmartIri, projectADM = SharedTestDataADM.anythingProject, creationDate = Instant.parse("2016-03-02T15:05:10Z"), @@ -1390,7 +1425,7 @@ class SearchResponderV2SpecFullData(implicit stringFormatter: StringFormatter) { userPermission = ChangeRightsPermission, previousValueIri = None, valueCreationDate = Instant.parse("2016-03-02T15:05:54Z"), - attachedToUser = "http://rdfh.ch/users/9XBCrDV3SRa7kS1WwynB4Q", + attachedToUser = testUser1, deletionInfo = None ), ReadTextValueV2( @@ -1797,7 +1832,7 @@ class SearchResponderV2SpecFullData(implicit stringFormatter: StringFormatter) { userPermission = ChangeRightsPermission, previousValueIri = None, valueCreationDate = Instant.parse("2016-03-02T15:05:54Z"), - attachedToUser = "http://rdfh.ch/users/9XBCrDV3SRa7kS1WwynB4Q", + attachedToUser = testUser1, deletionInfo = None ) ) @@ -2040,40 +2075,60 @@ class SearchResponderV2SpecFullData(implicit stringFormatter: StringFormatter) { querySchema = Some(ApiV2Simple) ) - val fulltextSearchForListNodeLabel: ReadResourcesSequenceV2 = ReadResourcesSequenceV2( + val expectedResultFulltextSearchForListNodeLabel: ReadResourcesSequenceV2 = ReadResourcesSequenceV2( resources = Vector( - ReadResourceV2( + bookTemplateReadResource.copy( label = "instance of a book with a list value", resourceIri = "http://rdfh.ch/0001/book-instance-02", - permissions = - "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser", - userPermission = ChangeRightsPermission, - attachedToUser = "http://rdfh.ch/users/9XBCrDV3SRa7kS1WwynB4Q", - resourceClassIri = "http://www.knora.org/ontology/0001/books#Book".toSmartIri, - projectADM = SharedTestDataADM.anythingProject, - creationDate = Instant.parse("2019-11-29T10:00:00.673298Z"), values = Map( - "http://www.knora.org/ontology/0001/books#hasTextType".toSmartIri -> Vector( - ReadOtherValueV2( + booksHasTextType.toSmartIri -> Vector( + listValueTemplateReadOtherValue.copy( valueContent = HierarchicalListValueContentV2( ontologySchema = InternalSchema, valueHasListNode = "http://rdfh.ch/lists/0001/ynm02-03" ), valueIri = "http://rdfh.ch/0001/book-instance-02/values/has-list-value-01", - valueHasUUID = stringFormatter.decodeUuid("d34d34d3-4d34-d34d-3496-2b2dfef6a5b9"), - permissions = - "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser", - userPermission = ModifyPermission, - previousValueIri = None, - valueCreationDate = Instant.parse("2018-05-29T16:42:04.381Z"), - attachedToUser = "http://rdfh.ch/users/BhkfBc3hTeS_IDo-JgXRbQ", - deletionInfo = None + valueHasUUID = stringFormatter.decodeUuid("d34d34d3-4d34-d34d-3496-2b2dfef6a5b9") ) ) - ), - lastModificationDate = None, - versionDate = None, - deletionInfo = None + ) + ) + ) + ) + + val expectedResultFulltextSearchForListNodeLabelWithSubnodes: ReadResourcesSequenceV2 = ReadResourcesSequenceV2( + resources = Vector( + bookTemplateReadResource.copy( + label = "Lord of the Rings", + resourceIri = "http://rdfh.ch/0001/book-instance-03", + values = Map( + booksHasTextType.toSmartIri -> Vector( + listValueTemplateReadOtherValue.copy( + valueContent = HierarchicalListValueContentV2( + ontologySchema = InternalSchema, + valueHasListNode = "http://rdfh.ch/lists/0001/ynm02-04" + ), + valueIri = "http://rdfh.ch/0001/book-instance-03/values/has-list-value-02", + valueHasUUID = stringFormatter.decodeUuid("d34d3496-2b2d-fef6-a5b9-efdf6a7b5ab3") + ) + ) + ) + ), + bookTemplateReadResource.copy( + label = "Treasure Island", + resourceIri = "http://rdfh.ch/0001/book-instance-04", + values = Map( + booksHasTextType.toSmartIri -> Vector( + listValueTemplateReadOtherValue.copy( + valueContent = HierarchicalListValueContentV2( + ontologySchema = InternalSchema, + valueHasListNode = "http://rdfh.ch/lists/0001/ynm02-05" + ), + valueIri = "http://rdfh.ch/0001/book-instance-04/values/has-list-value-03", + valueHasUUID = stringFormatter.decodeUuid("d34962b2-dfef-6a5b-9efd-a76f7a7b6ead") + ) + ) + ) ) ) )