Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix!: partOf and sequenceOf properties are not marked as isEditable #2268

Merged
merged 8 commits into from Oct 27, 2022
26 changes: 10 additions & 16 deletions docs/01-introduction/example-project.md
Expand Up @@ -176,22 +176,16 @@ relationship is expressed using the property `incunabula:partOf`:

The key things to notice here are:

* `rdfs:subPropertyOf knora-base:isPartOf`: The Knora base ontology
provides a generic `isPartOf` property to express part-whole
relationships. Like many properties defined in `knora-base`, a
project cannot use `knora-base:isPartOf` directly, but must make a
subproperty such as `incunabula:partOf`. It is important to note
that `knora-base:isPartOf` is a subproperty of
`knora-base:hasLinkTo`. Any property that points to a
`knora-base:Resource` must be a subproperty of
`knora-base:hasLinkTo`. In Knora terminology, such a property is
called a *link property*.
* `knora-base:objectClassConstraint :book`: The object of this
property must be a member of the class `incunabula:book`, which, as
we will see below, is a subclass of `knora-base:Resource`.
* `salsah-gui:guiElement salsah-gui:Searchbox`: When SALSAH prompts a
user to select the book that a page is part of, it should provide a
search box enabling the user to find the desired book.
* `rdfs:subPropertyOf knora-base:isPartOf`: The `knora-base` ontology provides a generic `isPartOf` property to express
part-whole relationships. A project may use `knora-base:isPartOf` directly, however creating a subproperty such as
`incunabula:partOf` will allow to customize the property further, e.g. by giving it a more descriptive label.
It is important to note that `knora-base:isPartOf` is a subproperty of `knora-base:hasLinkTo`. Any property that
points to a `knora-base:Resource` must be a subproperty of `knora-base:hasLinkTo`. Such a
property is called a *link property*.
* `knora-base:objectClassConstraint :book`: The object of this property must be a member of the class `incunabula:book`,
which, as we will see below, is a subclass of `knora-base:Resource`.
* `salsah-gui:guiElement salsah-gui:Searchbox`: When SALSAH prompts a user to select the book that a page is part of, it
should provide a search box enabling the user to find the desired book.

Because `incunabula:partOf` is a link property, it must always
accompanied by a *link value property*, which enables Knora to store
Expand Down
35 changes: 18 additions & 17 deletions docs/02-knora-ontologies/knora-base.md
Expand Up @@ -575,34 +575,35 @@ as the `kb:LinkValue`.

#### isPartOf

A special case of linked resources are _part-of related resources_, i.e. a resource consisting of several other resources.
In order to create a part-of relation between two resources, the resource that is part of another resource needs to have
a property that is a subproperty of `kb:isPartOf`. This property needs to point to the resource class it is part of via
its predicate `kb:objectType`.
A special case of linked resources are _part-of related resources_, i.e. a resource consisting of several other
resources. In order to create a part-of relation between two resources, the resource that is part of another resource
needs to have a property that is either `kb:isPartOf` or a subproperty thereof.
`kb:isPartOf` itself is a subproperty of `kb:hasLinkTo`. Same as described above for link properties, a corresponding
part-of value property is created automatically. This value property has the same name as the part-of property with
`Value` appended. For example, if in an ontology `data` a property `data:partOf` was defined, the corresponding value
property would be named `data:partOfValue`. This newly created property `data:partOfValue` is defined as a subproperty
of `kb:isPartOfValue`.

Part-of relations are recommended for resources of type `kb:StillImageRepresentation`. In that case, the resource that is
part of another resource needs to have a property that is a subproperty of `kb:seqnum` with an integer as value.
A client can then use this information to leaf through the parts of the compound resource (p.ex. to leaf through the
pages of a book like in [this](https://docs.dasch.swiss/DSP-API/01-introduction/example-project/#resource-classes) example).
Part-of relations are recommended for resources of type `kb:StillImageRepresentation`. In that case, the resource that
is part of another resource needs to have a property `kb:seqnum` or a subproperty thereof, with an integer as value. A
client can then use this information to leaf through the parts of the compound resource (p.ex. to leaf through the pages
of a book like in [this](https://docs.dasch.swiss/DSP-API/01-introduction/example-project/#resource-classes) example).

#### isSequenceOf

Similar to `kb:isPartOf` for `kb:StillImageRepresentations`, part-whole-relations can be defined for resources that have a time
dimension by using `kb:isSequenceOf`. You can use it for video or audio resources that are subtypes of `kb:MovingImageRepresentation`
and `kb:AudioRepresentation`.
Similar to `kb:isPartOf` for `kb:StillImageRepresentations`, part-whole-relations can be defined for resources that have
a time dimension by using `kb:isSequenceOf`. You can use it for video or audio resources that are subtypes of
`kb:MovingImageRepresentation` and `kb:AudioRepresentation`.

`kb:isSequenceOf` is intended to be used in combination with the property `kb:hasSequenceBounds` which points to a `kb:IntervalValue`.
This defines the start and end point of the subseqence in relation to the entire audio/video resource as an [interval](#intervalvalue).
A dedicated frontend behaviour is planned, if these properties are used in combination.
`kb:isSequenceOf` is intended to be used in combination with the property `kb:hasSequenceBounds` which points to a
`kb:IntervalValue`. This defines the start and end point of the subseqence in relation to the entire audio/video
resource as an [interval](#intervalvalue). When the properties are used in this combination, a dedicated behavior in the
frontend allows to display the sequences alongside the main resource.

There is an important difference between `kb:isSequenceOf` and `kb:isPartOf`: For `kb:isPartOf`, each part *is a* `kb:StillImageRepresentation` and
the whole consists of multiple such parts. In `kb:isSequenceOf` on the other hand, the whole is one `kb:MovingImageRepresentation` or `kb:AudioRepresentation`.
The parts only define which sub-sequence of this representation they are.
There is an important difference between `kb:isSequenceOf` and `kb:isPartOf`: For `kb:isPartOf`, each part *is a*
`kb:StillImageRepresentation` and the whole consists of multiple such parts. In `kb:isSequenceOf` on the other hand, the
whole is one `kb:MovingImageRepresentation` or `kb:AudioRepresentation`. The parts only define which sub-sequence of
this representation they are.

### Text with Standoff Markup

Expand Down
6 changes: 6 additions & 0 deletions knora-ontologies/knora-base.ttl
Expand Up @@ -561,6 +561,7 @@
rdfs:comment "Indicates that this resource is part of another resource"@en ;
:subjectClassConstraint :Resource ;
:objectClassConstraint :Resource ;
:isEditable true ;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I think I don't understand why here everything is set to :isEditable true. Shouldn't knora-base things not be editable?

rdfs:subPropertyOf :hasLinkTo .


Expand All @@ -570,6 +571,7 @@
rdf:type owl:ObjectProperty ;
:objectClassConstraint :LinkValue ;
:subjectClassConstraint :Resource ;
:isEditable true ;
rdfs:subPropertyOf :hasLinkToValue .


Expand All @@ -584,6 +586,7 @@
rdfs:comment "Indicates that this resource is a sequence of a video or audio resource"@en ;
:subjectClassConstraint :Resource ;
:objectClassConstraint :Resource ;
:isEditable true ;
rdfs:subPropertyOf :hasLinkTo .


Expand All @@ -593,6 +596,7 @@
rdf:type owl:ObjectProperty ;
:objectClassConstraint :LinkValue ;
:subjectClassConstraint :Resource ;
:isEditable true ;
rdfs:subPropertyOf :hasLinkToValue .


Expand Down Expand Up @@ -641,6 +645,7 @@
:seqnum
rdf:type owl:ObjectProperty ;
:objectClassConstraint :IntValue ;
:isEditable true ;
rdfs:subPropertyOf :hasValue ;
rdfs:label "Sequenznummer"@de,
"Sequence number"@en,
Expand All @@ -654,6 +659,7 @@
:hasSequenceBounds
rdf:type owl:ObjectProperty ;
:objectClassConstraint :IntervalValue ;
:isEditable true ;
rdfs:subPropertyOf :hasValue ;
rdfs:label "Sequenz-Grenzen"@de,
"Sequence Bounds"@en;
Expand Down
Expand Up @@ -217,6 +217,7 @@ object OntologyConstants {
val HasLinkToValue: IRI = KnoraBasePrefixExpansion + "hasLinkToValue"
val IsPartOf: IRI = KnoraBasePrefixExpansion + "isPartOf"
val IsPartOfValue: IRI = KnoraBasePrefixExpansion + "isPartOfValue"
val Seqnum: IRI = KnoraBasePrefixExpansion + "seqnum"
val IsSequenceOf: IRI = KnoraBasePrefixExpansion + "isSequenceOf"
val IsSequenceOfValue: IRI = KnoraBasePrefixExpansion + "isSequenceOfValue"
val HasSequenceBounds: IRI = KnoraBasePrefixExpansion + "hasSequenceBounds"
Expand Down Expand Up @@ -830,6 +831,7 @@ object OntologyConstants {

val IsPartOf: IRI = KnoraApiV2PrefixExpansion + "isPartOf"
val IsPartOfValue: IRI = KnoraApiV2PrefixExpansion + "isPartOfValue"
val Seqnum: IRI = KnoraApiV2PrefixExpansion + "seqnum"
val IsSequenceOf: IRI = KnoraApiV2PrefixExpansion + "isSequenceOf"
val IsSequenceOfValue: IRI = KnoraApiV2PrefixExpansion + "isSequenceOfValue"
val HasSequenceBounds: IRI = KnoraApiV2PrefixExpansion + "hasSequenceBounds"
Expand Down Expand Up @@ -985,6 +987,7 @@ object OntologyConstants {

val IsPartOf: IRI = KnoraApiV2PrefixExpansion + "isPartOf"
val IsRegionOf: IRI = KnoraApiV2PrefixExpansion + "isRegionOf"
val Seqnum: IRI = KnoraApiV2PrefixExpansion + "seqnum"
val IsSequenceOf: IRI = KnoraApiV2PrefixExpansion + "isSequenceOf"
val IsSequenceOfValue: IRI = KnoraApiV2PrefixExpansion + "isSequenceOfValue"
val HasSequenceBounds: IRI = KnoraApiV2PrefixExpansion + "hasSequenceBounds"
Expand Down
Expand Up @@ -1899,6 +1899,8 @@ object OntologyHelpers {
val subPropertyOf: SmartIri = internalPropertyDef.subPropertyOf match {
case subProps if subProps.contains(OntologyConstants.KnoraBase.IsPartOf.toSmartIri) =>
OntologyConstants.KnoraBase.IsPartOfValue.toSmartIri
case subProps if subProps.contains(OntologyConstants.KnoraBase.IsSequenceOf.toSmartIri) =>
OntologyConstants.KnoraBase.IsSequenceOfValue.toSmartIri
case subProps if subProps.contains(OntologyConstants.KnoraBase.HasLinkTo.toSmartIri) =>
OntologyConstants.KnoraBase.HasLinkToValue.toSmartIri
case subProps
Expand Down
Expand Up @@ -3864,6 +3864,44 @@ class OntologyV2R2RSpec extends R2RSpec {
assert(cardinality == MustHaveOne)
}
}

"return isSequenceOf and isPartOf properties from knora-base marked as isEditable" in {
val requestUrl = s"/v2/ontologies/allentities/$knoraApiWithValueObjectsOntologySegment"
Get(requestUrl) ~> ontologiesPath ~> check {
val responseStr: String = responseAs[String]
assert(status == StatusCodes.OK, response.toString)
val responseJsonDoc = JsonLDUtil.parseJsonLD(responseStr)
val graph = responseJsonDoc.body.requireArray(JsonLDKeywords.GRAPH).value.map(_.asInstanceOf[JsonLDObject])

val isSequenceOfIsEditable = graph
.find(_.requireString(JsonLDKeywords.ID) == OntologyConstants.KnoraApiV2Complex.IsSequenceOf)
.fold(false)(_.requireBoolean(OntologyConstants.KnoraApiV2Complex.IsEditable))
val isSequenceOfValueIsEditable = graph
.find(_.requireString(JsonLDKeywords.ID) == OntologyConstants.KnoraApiV2Complex.IsSequenceOfValue)
.fold(false)(_.requireBoolean(OntologyConstants.KnoraApiV2Complex.IsEditable))
val hasSequenceBoundsIsEditable = graph
.find(_.requireString(JsonLDKeywords.ID) == OntologyConstants.KnoraApiV2Complex.HasSequenceBounds)
.fold(false)(_.requireBoolean(OntologyConstants.KnoraApiV2Complex.IsEditable))
val isPartOfIsEditable = graph
.find(_.requireString(JsonLDKeywords.ID) == OntologyConstants.KnoraApiV2Complex.IsPartOf)
.fold(false)(_.requireBoolean(OntologyConstants.KnoraApiV2Complex.IsEditable))
val isPartOfValueIsEditable = graph
.find(_.requireString(JsonLDKeywords.ID) == OntologyConstants.KnoraApiV2Complex.IsPartOfValue)
.fold(false)(_.requireBoolean(OntologyConstants.KnoraApiV2Complex.IsEditable))
val seqnumIsEditable = graph
.find(_.requireString(JsonLDKeywords.ID) == OntologyConstants.KnoraApiV2Complex.Seqnum)
.fold(false)(_.requireBoolean(OntologyConstants.KnoraApiV2Complex.IsEditable))

assert(isSequenceOfIsEditable)
assert(isSequenceOfValueIsEditable)
assert(hasSequenceBoundsIsEditable)
assert(isPartOfIsEditable)
assert(isPartOfValueIsEditable)
assert(seqnumIsEditable)

}
}

"not create a property with invalid gui attribute" in {
val params =
s"""{
Expand Down
Expand Up @@ -2310,7 +2310,7 @@ class ResourcesRouteV2E2ESpec extends E2ESpec {
val createSequenceResponseBody = responseToJsonLDDocument(createSequenceResponse).body
val sequenceResourceIri = URLEncoder.encode(createSequenceResponseBody.requireString(JsonLDKeywords.ID), "UTF-8")

// get the newly created sequence reource
// get the newly created sequence resource
val sequenceGetRequest = Get(s"$resUrl/$sequenceResourceIri") ~> addCredentials(cred)
val sequenceResponse = singleAwaitingRequest(sequenceGetRequest)
assert(sequenceResponse.status == StatusCodes.OK)
Expand Down