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

DSP-804: create a child node with a custom IRI #1741

Merged
merged 5 commits into from
Oct 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions docs/03-apis/api-admin/lists.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ License along with Knora. If not, see <http://www.gnu.org/licenses/>.
- `GET: /admin/lists/<listIri>` : return complete list with children
- `POST: /admin/lists` : create new list
- `PUT: /admin/lists/<listIri>` : update list information
- `POST: /admin/lists/<nodeIri>` : create new child node under the supplied parent node IRI
- `POST: /admin/lists/<parentNodeIri>` : create new child node under the supplied parent node IRI
- NOT IMPLEMENTED: `DELETE: /admin/lists/<listIri>` : delete list including children if not used
- `GET: /admin/lists/infos/<listIri>` : return list information (without children)

Expand Down Expand Up @@ -83,19 +83,32 @@ Additionally, each list can have an optional custom IRI (of [Knora IRI](../api-v
- Appends a new child node under the supplied nodeIri. If the supplied nodeIri
is the listIri, then a new child node is appended to the top level. Children
are currently only appended.
- POST: `/admin/lists/<nodeIri>`
- POST: `/admin/lists/<parentNodeIri>`
- BODY:

```json
{
"parentNodeIri": "nodeIri",
"parentNodeIri": "parentNodeIri",
"projectIri": "someprojectiri",
"name": "first",
"labels": [{ "value": "New First Child List Node Value", "language": "en"}],
"comments": [{ "value": "New First Child List Node Comment", "language": "en"}]
}
```

Additionally, each child node can have an optional custom IRI (of [Knora IRI](../api-v2/knora-iris.md#iris-for-data) form) specified by the `id` in the request body as below:

```json
{
"id": "http://rdfh.ch/lists/0001/a-child-node-with-IRI",
"parentNodeIri": "http://rdfh.ch/lists/0001/a-list-with-IRI",
"projectIri": "http://rdfh.ch/projects/0001",
"name": "child node with a custom IRI",
"labels": [{ "value": "New child node with IRI", "language": "en"}],
"comments": [{ "value": "New child node comment", "language": "en"}]
}
```

### Get list's information

- Required permission: none
Expand Down
2 changes: 2 additions & 0 deletions webapi/scripts/expected-client-test-data.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ test-data/admin/lists/add-second-child-to-root-request.json
test-data/admin/lists/add-second-child-to-root-response.json
test-data/admin/lists/create-child-node-request.json
test-data/admin/lists/create-child-node-response.json
test-data/admin/lists/create-child-node-with-custom-IRI-request.json
test-data/admin/lists/create-child-node-with-custom-IRI-response.json
test-data/admin/lists/create-list-request.json
test-data/admin/lists/create-list-response.json
test-data/admin/lists/create-list-with-custom-IRI-request.json
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,17 +79,22 @@ case class CreateListApiRequestADM(id: Option[IRI] = None,
* is added can be either a root list node or a child list node. At least one label needs to be supplied. If other
* child nodes exist, the newly created list node will be appended to the end.
*
* @param parentNodeIri
* @param labels
* @param comments
* @param id the optional custom IRI of the list node.
* @param parentNodeIri the IRI of the parent node.
* @param projectIri the IRI of the project.
* @param name the optional name of the list node.
* @param labels labels of the list node.
* @param comments comments of the list node.
*/
case class CreateChildNodeApiRequestADM(parentNodeIri: IRI,
case class CreateChildNodeApiRequestADM(id: Option[IRI] = None,
parentNodeIri: IRI,
projectIri: IRI,
name: Option[String],
labels: Seq[StringLiteralV2],
comments: Seq[StringLiteralV2]) extends ListADMJsonProtocol {

private val stringFormatter = StringFormatter.getInstanceForConstantOntologies
stringFormatter.validateOptionalListIri(id, throw BadRequestException(s"Invalid list node IRI"))

if (parentNodeIri.isEmpty) {
// println(this)
Expand Down Expand Up @@ -967,7 +972,7 @@ trait ListADMJsonProtocol extends SprayJsonSupport with DefaultJsonProtocol with


implicit val createListApiRequestADMFormat: RootJsonFormat[CreateListApiRequestADM] = jsonFormat(CreateListApiRequestADM, "id", "projectIri", "name", "labels", "comments")
implicit val createListNodeApiRequestADMFormat: RootJsonFormat[CreateChildNodeApiRequestADM] = jsonFormat(CreateChildNodeApiRequestADM, "parentNodeIri", "projectIri", "name", "labels", "comments")
implicit val createListNodeApiRequestADMFormat: RootJsonFormat[CreateChildNodeApiRequestADM] = jsonFormat(CreateChildNodeApiRequestADM, "id" , "parentNodeIri", "projectIri", "name", "labels", "comments")
implicit val changeListInfoApiRequestADMFormat: RootJsonFormat[ChangeListInfoApiRequestADM] = jsonFormat(ChangeListInfoApiRequestADM, "listIri", "projectIri", "name", "labels", "comments")
implicit val nodePathGetResponseADMFormat: RootJsonFormat[NodePathGetResponseADM] = jsonFormat(NodePathGetResponseADM, "elements")
implicit val listsGetResponseADMFormat: RootJsonFormat[ListsGetResponseADM] = jsonFormat(ListsGetResponseADM, "lists")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -617,13 +617,12 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde
throw BadRequestException(s"The node name ${createListRequest.name.get} is already used by a list inside the project ${createListRequest.projectIri}.")
}

maybeShortcode = project.shortcode
dataNamedGraph = stringFormatter.projectDataNamedGraphV2(project)

// check the custom IRI; if not given, create an unused IRI
customListIri: Option[SmartIri] = createListRequest.id.map(iri => iri.toSmartIri)
maybeShortcode = project.shortcode
listIri: IRI <- checkOrCreateEntityIri(customListIri, stringFormatter.makeRandomListIri(maybeShortcode))

dataNamedGraph = stringFormatter.projectDataNamedGraphV2(project)
// Create the new list
createNewListSparqlString = org.knora.webapi.messages.twirl.queries.sparql.admin.txt.createNewList(
dataNamedGraph = dataNamedGraph,
Expand Down Expand Up @@ -826,9 +825,10 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde
// calculate the data named graph
dataNamedGraph = stringFormatter.projectDataNamedGraphV2(project)

// calculate the new node's IRI
// check the custom IRI; if not given, create an unused IRI
customListIri: Option[SmartIri] = createChildNodeRequest.id.map(iri => iri.toSmartIri)
maybeShortcode = project.shortcode
newListNodeIri = stringFormatter.makeRandomListIri(maybeShortcode)
newListNodeIri: IRI <- checkOrCreateEntityIri(customListIri, stringFormatter.makeRandomListIri(maybeShortcode))

// Create the new list node
createNewListSparqlString = org.knora.webapi.messages.twirl.queries.sparql.admin.txt.createNewListChildNode(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,59 @@ class ListsADME2ESpec extends E2ESpec(ListsADME2ESpec.config) with SessionJsonPr
val invalidIri: Boolean = errorMessage.contains(s"IRI: '${SharedTestDataADM.customListIRI}' already exists, try another one.")
invalidIri should be(true)
}

"add a child with a custom IRI" in {

val customChildNodeIRI = "http://rdfh.ch/lists/0001/a-child-node-with-IRI"

val createChildNodeWithCustomIriRequest =
s"""
|{ "id": "$customChildNodeIRI",
| "parentNodeIri": "${SharedTestDataADM.customListIRI}",
| "projectIri": "${SharedTestDataADM.ANYTHING_PROJECT_IRI}",
| "name": "node with a custom IRI",
| "labels": [{ "value": "New List Node", "language": "en"}],
| "comments": []
|}""".stripMargin

clientTestDataCollector.addFile(
TestDataFileContent(
filePath = TestDataFilePath(
directoryPath = clientTestDataPath,
filename = "create-child-node-with-custom-IRI-request",
fileExtension = "json"
),
text = createChildNodeWithCustomIriRequest
)
)

val encodedParentNodeUrl = java.net.URLEncoder.encode(SharedTestDataADM.customListIRI, "utf-8")

val request = Post(baseApiUrl + s"/admin/lists/" + encodedParentNodeUrl, HttpEntity(ContentTypes.`application/json`, createChildNodeWithCustomIriRequest)) ~> addCredentials(anythingAdminUserCreds.basicHttpCredentials)
val response: HttpResponse = singleAwaitingRequest(request)
// println(s"response: ${response.toString}")
response.status should be(StatusCodes.OK)

val received: ListNodeInfoADM = AkkaHttpUtils.httpResponseToJson(response).fields("nodeinfo").convertTo[ListNodeInfoADM]

// check correct node info
val childNodeInfo = received match {
case info: ListChildNodeInfoADM => info
case something => fail(s"expecting ListChildNodeInfoADM but got ${something.getClass.toString} instead.")
}
childNodeInfo.id should be (customChildNodeIRI)

clientTestDataCollector.addFile(
TestDataFileContent(
filePath = TestDataFilePath(
directoryPath = clientTestDataPath,
filename = "create-child-node-with-custom-IRI-response",
fileExtension = "json"
),
text = responseToString(response)
)
)
}
}

"used to modify list information" should {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ class ListsMessagesADMSpec extends AnyWordSpecLike with Matchers with ListADMJso
thrown.getMessage should equal (UPDATE_REQUEST_EMPTY_LABEL_OR_COMMENT_ERROR)
}

"throw 'BadRequestException' for `CreateChildNodeApiRequestADM` when list node iri is empty" in {
"throw 'BadRequestException' for `CreateChildNodeApiRequestADM` when no parent node iri is given" in {

val payload =
s"""
Expand All @@ -280,7 +280,7 @@ class ListsMessagesADMSpec extends AnyWordSpecLike with Matchers with ListADMJso

}

"throw 'BadRequestException' for `CreateChildNodeApiRequestADM` when list node iri is invalid" in {
"throw 'BadRequestException' for `CreateChildNodeApiRequestADM` when parent node iri is invalid" in {

val payload =
s"""
Expand Down Expand Up @@ -352,5 +352,23 @@ class ListsMessagesADMSpec extends AnyWordSpecLike with Matchers with ListADMJso

}

"throw 'BadRequestException' for `CreateChildNodeApiRequestADM` when custom iri of the child node is invalid" in {

val payload =
s"""
|{ "id": "invalid-list-node-IRI",
| "parentNodeIri": "$exampleListIri",
| "projectIri": "${SharedTestDataADM.IMAGES_PROJECT_IRI}",
| "labels": [{ "value": "Neuer List Node", "language": "de"}],
| "comments": []
|}
""".stripMargin

val thrown = the [BadRequestException] thrownBy payload.parseJson.convertTo[CreateChildNodeApiRequestADM]

thrown.getMessage should equal ("Invalid list node IRI")

}

}
}