Skip to content

Commit

Permalink
feat(api-v2): Return events describing version history of resources a…
Browse files Browse the repository at this point in the history
…nd values of a project ordered by data (DSP-1528) (#1844)

* feature(api-v2): route to get history of resources within project + message tests

* feat(api-v2): add templates for getting all resources of a project

* refactor(api-v2): rename twirl templates for getting resources of a project by class

* test(api-v2): tests for returning resource IRIs of a project

* fix(application.conf): increase TCP connecting-timeout

* feat (api-v2): return version history of all resources in a project

* feat(api-v2): get full representation of each resource in each time stamp in its history

* feat(api-v2): model event for response

* feat (histEvent): return a createResource event for each resource within a project as it was initially created

* fix (historyEvents): fix test

* feat(historyEvents): extract full value histories

* feat (historyEvents): return create/update/delete events for values of a resource

* feat (historyEvents): update Permission event

* fix (historyEvent) use isAfter method of instant

* fix(testData): creation date of the resource a-thing-picture cannot be after creationdate of its values

* feat(historyEvents): return all resource and value histories of a project as events

* feat (historyEvents): sort events by date

* feat(historyEvent) serialize project history response as JSONLD

* feat(historyEvent): return deleted values as part of full representation if asked, make delete events for them

* test(historyEvent): e2e test

* test (historyEvent): test for serialization of event metadata

* test (historyEvents): e2e test

* feat(historyEvent): serialize value contents

* fix (historyEvent): fix the failing test

* refactor (historyEvent): some refactoring and clean up

* docs(historyEvent): add documentation

* fix (historyEvents): fix the failing test

* fix (historyEvent): fix the remaining wrong dates

* refactor(historyEvents): some restructuring

* feat(historyEvent): create an event for a deleted resource

* feat (historyEvent): return delete comment when getting the full representation of the resource

* fix (historyEvent): fix the build problem

* refactor (docs): clean up documentation

Co-authored-by: Benjamin Geer <benjaminlewis.geer@unibas.ch>
  • Loading branch information
SepidehAlassi and Benjamin Geer committed May 3, 2021
1 parent d8dbb4f commit 84f7c14
Show file tree
Hide file tree
Showing 32 changed files with 1,434 additions and 90 deletions.
2 changes: 1 addition & 1 deletion docs/03-apis/api-v2/editing-resources.md
Expand Up @@ -277,7 +277,7 @@ Here is an example:
"knora-api:lastModificationDate" : {
"@type" : "xsd:dateTimeStamp",
"@value" : "2017-11-20T15:55:17Z"
}
},
"knora-api:newModificationDate" : {
"@type" : "xsd:dateTimeStamp",
"@value" : "2018-12-21T16:56:18Z"
Expand Down
130 changes: 130 additions & 0 deletions docs/03-apis/api-v2/reading-and-searching-resources.md
Expand Up @@ -558,3 +558,133 @@ The `orderByProperty` parameter is optional; if it is not supplied, resources wi
be sorted alphabetically by resource IRI (an arbitrary but consistent order).
The value of `page` is a 0-based integer page number. Paging works as it does
in [Gravsearch](query-language.md)).

### Get the Version History of Resources and Values of a Project

To get a list of the changes that have been made to resources and values of a project since their creation ordered by date
use this route:

```
HTTP GET to http://host/v2/resources/projectHistory/projectIRI
```

The project IRI must be URL-encoded. The response is a list of events describing changes made to the resource and its values,
in chronological order. Each entry has the properties:
`knora-api:eventType` (the type of the operation performed on a specific date. The operation can be either
`createResource`, `deleteResource`, `createValue`, `updateValueContent`, `updateValuePermissions`, or `deleteValue`.),
`knora-api:versionDate` (the date when the change was made),
`knora-api:author` (the IRI of the user who made the change),
`knora-api:eventBody` (the information necessary to make the same request). For example:

```jsonld
{
"@graph" : [
{
"knora-api:eventType": "createResource",
"knora-api:author": {
"@id": "http://rdfh.ch/users/9XBCrDV3SRa7kS1WwynB4Q"
},
"knora-api:eventBody": {
"rdfs:label": "A thing with version history",
"knora-api:resourceIri": "http://rdfh.ch/0001/thing-with-history",
"knora-api:resourceClassIri": "http://www.knora.org/ontology/0001/anything#Thing",
"knora-api:hasPermissions": "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:UnknownUser",
"knora-api:creationDate": {
"@value": "2019-02-08T15:05:10Z",
"@type": "xsd:dateTimeStamp"
},
"knora-api:attachedToProject": {
"@id": "http://rdfh.ch/projects/0001"
}
},
"knora-api:versionDate": {
"@value": "2019-02-08T15:05:10Z",
"@type": "xsd:dateTimeStamp"
}
},
{
"knora-api:eventType": "createValue",
"knora-api:author": {
"@id": "http://rdfh.ch/users/9XBCrDV3SRa7kS1WwynB4Q"
},
"knora-api:eventBody": {
"knora-api:resourceIri": "http://rdfh.ch/0001/thing-with-history",
"knora-api:resourceClassIri": "http://www.knora.org/ontology/0001/anything#Thing",
"knora-api:valueCreationDate": {
"@value": "2019-02-10T10:30:10Z",
"@type": "xsd:dateTimeStamp"
},
"knora-api:valueHasUUID": "IZGOjVqxTfSNO4ieKyp0SA",
"knora-api:hasPermissions": "V knora-admin:UnknownUser|M knora-admin:ProjectMember",
"@type": "knora-base:LinkValue",
"http://www.knora.org/ontology/0001/anything#hasOtherThingValue": {
"knora-api:linkValueHasTargetIri": {
"@id": "http://rdfh.ch/0001/2qMtTWvVRXWMBcRNlduvCQ"
}
},
"rdf:Property": "http://www.knora.org/ontology/0001/anything#hasOtherThingValue",
"@id": "http://rdfh.ch/0001/thing-with-history/values/3a"
},
"knora-api:versionDate": {
"@value": "2019-02-10T10:30:10Z",
"@type": "xsd:dateTimeStamp"
}
},
{
"knora-api:eventType": "updateValueContent",
"knora-api:author": {
"@id": "http://rdfh.ch/users/BhkfBc3hTeS_IDo-JgXRbQ"
},
"knora-api:eventBody": {
"knora-api:resourceIri": "http://rdfh.ch/0001/thing-with-history",
"knora-api:resourceClassIri": "http://www.knora.org/ontology/0001/anything#Thing"
"http://www.knora.org/ontology/0001/anything#hasText": {
"knora-api:valueAsString": "two"
},
"knora-api:valueCreationDate": {
"@value": "2019-02-11T10:05:10Z",
"@type": "xsd:dateTimeStamp"
},
"knora-base:previousValue": "http://rdfh.ch/0001/thing-with-history/values/2a",
"knora-api:valueHasUUID": "W5fm67e0QDWxRZumcXcs6g",
"@type": "knora-base:TextValue",
"rdf:Property": "http://www.knora.org/ontology/0001/anything#hasText",
"@id": "http://rdfh.ch/0001/thing-with-history/values/2b"
},
"knora-api:versionDate": {
"@value": "2019-02-11T10:05:10Z",
"@type": "xsd:dateTimeStamp"
}
},
{
"knora-api:eventType": "deleteValue",
"knora-api:author": {
"@id": "http://rdfh.ch/users/9XBCrDV3SRa7kS1WwynB4Q"
},
"knora-api:eventBody": {
"knora-api:resourceIri": "http://rdfh.ch/0001/thing-with-history",
"knora-api:resourceClassIri": "http://www.knora.org/ontology/0001/anything#Thing",
"knora-base:previousValue": "http://rdfh.ch/0001/thing-with-history/values/3a",
"knora-api:deleteDate": {
"@type": "xsd:dateTimeStamp",
"@value": "2019-02-13T09:00:10Z"
},
"knora-api:isDeleted": true,
"@type": "knora-base:LinkValue",
"rdf:Property": "http://www.knora.org/ontology/0001/anything#hasOtherThingValue",
"@id": "http://rdfh.ch/0001/thing-with-history/values/3b"
},
"knora-api:versionDate": {
"@value": "2019-02-13T09:00:10Z",
"@type": "xsd:dateTimeStamp"
}
}
],
"@context" : {
"rdf" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
"rdfs" : "http://www.w3.org/2000/01/rdf-schema#",
"xsd" : "http://www.w3.org/2001/XMLSchema#",
"knora-api" : "http://api.knora.org/ontology/knora-api/v2#"
}
}
```
5 changes: 3 additions & 2 deletions test_data/all_data/anything-data.ttl
Expand Up @@ -711,7 +711,7 @@
knora-base:attachedToProject <http://rdfh.ch/projects/0001>;
rdfs:label "A thing with a picture";
knora-base:hasPermissions "CR knora-admin:Creator|M knora-admin:ProjectMember|RV knora-admin:UnknownUser";
knora-base:creationDate "2016-03-02T15:05:10Z"^^xsd:dateTime;
knora-base:creationDate "2011-03-02T15:05:10Z"^^xsd:dateTime;
knora-base:hasStillImageFileValue <http://rdfh.ch/0001/a-thing-picture/values/goZ7JFRNSeqF-dNxsqAS7Q> .

<http://rdfh.ch/0001/a-thing-picture/values/goZ7JFRNSeqF-dNxsqAS7Q> a knora-base:StillImageFileValue;
Expand Down Expand Up @@ -1716,7 +1716,8 @@
anything:hasInteger <http://rdfh.ch/0001/XTxSMt0ySraVmwXD-bD2wQ/values/PVPAa37xR--K_wxQwlvSsg>;
rdfs:label "deleted thing";
knora-base:isDeleted true;
knora-base:deleteDate "2020-04-07T14:59:28.960124Z"^^xsd:dateTime .
knora-base:deleteDate "2020-04-07T14:59:28.960124Z"^^xsd:dateTime ;
knora-base:deleteComment "a comment for the deleted thing."^^xsd:string .

<http://rdfh.ch/0001/PHbbrEsVR32q5D_ioKt6pA/values/7BIm9QAiQqKixcgXDWf12Q> a knora-base:IntValue;
knora-base:attachedToUser <http://rdfh.ch/users/9XBCrDV3SRa7kS1WwynB4Q>;
Expand Down
4 changes: 2 additions & 2 deletions test_data/resourcesR2RV2/ThingWithPicture.jsonld
Expand Up @@ -25,15 +25,15 @@
},
"rdfs:label": "A thing with a picture",
"knora-api:versionArkUrl": {
"@value": "http://0.0.0.0:3336/ark:/72163/1/0001/a=thing=picture0.20160302T150510Z",
"@value": "http://0.0.0.0:3336/ark:/72163/1/0001/a=thing=picture0.20110302T150510Z",
"@type": "xsd:anyURI"
},
"knora-api:attachedToProject": {
"@id": "http://rdfh.ch/projects/0001"
},
"knora-api:userHasPermission": "RV",
"knora-api:creationDate": {
"@value": "2016-03-02T15:05:10Z",
"@value": "2011-03-02T15:05:10Z",
"@type": "xsd:dateTimeStamp"
},
"knora-api:attachedToUser": {
Expand Down
Expand Up @@ -39,15 +39,15 @@
"@id": "http://rdfh.ch/0001/a-thing-picture/values/goZ7JFRNSeqF-dNxsqAS7Q"
},
"knora-api:versionArkUrl": {
"@value": "http://0.0.0.0:3336/ark:/72163/1/0001/a=thing=picture0.20160302T150510Z",
"@value": "http://0.0.0.0:3336/ark:/72163/1/0001/a=thing=picture0.20110302T150510Z",
"@type": "xsd:anyURI"
},
"knora-api:attachedToProject": {
"@id": "http://rdfh.ch/projects/0001"
},
"knora-api:userHasPermission": "CR",
"knora-api:creationDate": {
"@value": "2016-03-02T15:05:10Z",
"@value": "2011-03-02T15:05:10Z",
"@type": "xsd:dateTimeStamp"
},
"knora-api:attachedToUser": {
Expand Down
Expand Up @@ -735,6 +735,10 @@ object OntologyConstants {
val Result: IRI = KnoraApiV2PrefixExpansion + "result"
val Error: IRI = KnoraApiV2PrefixExpansion + "error"
val MayHaveMoreResults: IRI = KnoraApiV2PrefixExpansion + "mayHaveMoreResults"
val EventType: IRI = KnoraApiV2PrefixExpansion + "eventType"
val EventBody: IRI = KnoraApiV2PrefixExpansion + "eventBody"
val ResourceClassIri: IRI = KnoraApiV2PrefixExpansion + "resourceClassIri"
val ResourceIri: IRI = KnoraApiV2PrefixExpansion + "resourceIri"

val IsShared: IRI = KnoraApiV2PrefixExpansion + "isShared"
val IsBuiltIn: IRI = KnoraApiV2PrefixExpansion + "isBuiltIn"
Expand Down
Expand Up @@ -1419,20 +1419,24 @@ object ConstructResponseUtilV2 {
timeout: Timeout,
executionContext: ExecutionContext): Future[ReadResourceV2] = {
def getDeletionInfo(rdfData: RdfData): Option[DeletionInfo] = {
val isDeleted: Boolean = rdfData.requireBooleanObject(OntologyConstants.KnoraBase.IsDeleted.toSmartIri)

if (isDeleted) {
val deleteDate = rdfData.requireDateTimeObject(OntologyConstants.KnoraBase.DeleteDate.toSmartIri)
val maybeDeleteComment = rdfData.maybeStringObject(OntologyConstants.KnoraBase.DeleteComment.toSmartIri)

Some(
DeletionInfo(
deleteDate = deleteDate,
maybeDeleteComment = maybeDeleteComment
)
)
} else {
None
val mayHaveDeletedStatements: Option[Boolean] =
rdfData.maybeBooleanObject(OntologyConstants.KnoraBase.IsDeleted.toSmartIri)
mayHaveDeletedStatements match {
case Some(isDeleted: Boolean) =>
if (isDeleted) {
val deleteDate = rdfData.requireDateTimeObject(OntologyConstants.KnoraBase.DeleteDate.toSmartIri)
val maybeDeleteComment = rdfData.maybeStringObject(OntologyConstants.KnoraBase.DeleteComment.toSmartIri)

Some(
DeletionInfo(
deleteDate = deleteDate,
maybeDeleteComment = maybeDeleteComment
)
)
} else {
None
}
case _ => None
}
}

Expand Down

0 comments on commit 84f7c14

Please sign in to comment.