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

feat: new project dataset tags API #1071

Merged
merged 9 commits into from
Aug 24, 2022
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright 2022 Swiss Data Science Center (SDSC)
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
* Eidgenössische Technische Hochschule Zürich (ETHZ).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.renku.graph.acceptancetests.knowledgegraph

import cats.syntax.all._
import io.circe.Json
import io.renku.generators.CommonGraphGenerators.authUsers
import io.renku.generators.Generators.Implicits._
import io.renku.graph.acceptancetests.data.dataProjects
import io.renku.graph.acceptancetests.flows.TSProvisioning
import io.renku.graph.acceptancetests.tooling.GraphServices
import io.renku.graph.model.EventsGenerators.commitIds
import io.renku.graph.model.publicationEvents
import io.renku.graph.model.testentities._
import io.renku.http.client.AccessToken
import io.renku.jsonld.syntax._
import io.renku.tinytypes.json.TinyTypeDecoders._
import org.http4s.Status.Ok
import org.scalatest.GivenWhenThen
import org.scalatest.featurespec.AnyFeatureSpec

class ProjectDatasetTagsResourceSpec extends AnyFeatureSpec with GivenWhenThen with GraphServices with TSProvisioning {

Feature("GET knowledge-graph/projects/<namespace>/<name>/datasets/:dsName/tags to find project dataset's tags") {

Scenario("As a user I would like to find project dataset's tags by calling a REST endpoint") {
val user = authUsers.generateOne
implicit val accessToken: AccessToken = user.accessToken

Given("the user is authenticated")
`GET <gitlabApi>/user returning OK`(user)

And("there's a project with datasets and tags")
val (dataset, project) = {
val creator = personEntities(withGitLabId, withEmail).generateOne
val (ds, project) = renkuProjectEntities(visibilityPublic)
.modify(replaceProjectCreator(creator.some))
.modify(replaceMembers(Set(creator)))
.addDataset(
datasetEntities(provenanceInternal).modify(_.replacePublicationEvents(List(publicationEventFactory)))
)
.generateOne

(ds, dataProjects(project).generateOne)
}

val commitId = commitIds.generateOne
mockDataOnGitLabAPIs(project, project.entitiesProject.asJsonLD, commitId)
`data in the Triples Store`(project, commitId)

When("the user fetches the tags with GET knowledge-graph/projects/:namespace/:name/datasets/:dsName/tags")
val response = knowledgeGraphClient.GET(
s"knowledge-graph/projects/${project.path}/datasets/${dataset.identification.name}/tags",
accessToken
)

Then("he should get OK response with the relevant tags")
response.status shouldBe Ok
val Right(tags) = response.jsonBody.as[List[Json]]
tags
.map(_.hcursor.downField("name").as[publicationEvents.Name])
.sequence
.fold(fail(_), identity) shouldBe dataset.publicationEvents.sortBy(_.startDate).reverse.map(_.name)

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,23 @@
package io.renku.graph.acceptancetests.knowledgegraph

import cats.syntax.all._
import io.circe.literal._
import io.circe.Json
import io.renku.generators.CommonGraphGenerators._
import io.renku.generators.Generators.Implicits._
import io.renku.graph.acceptancetests.data._
import io.renku.graph.acceptancetests.flows.TSProvisioning
import io.renku.graph.acceptancetests.tooling.GraphServices
import io.renku.graph.model.projects.Visibility
import io.renku.graph.model.EventsGenerators.commitIds
import io.renku.graph.model.projects.Visibility
import io.renku.graph.model.testentities.RenkuProject._
import io.renku.graph.model.testentities._
import io.renku.graph.acceptancetests.data._
import io.renku.http.server.EndpointTester.{JsonOps, jsonEntityDecoder}
import io.renku.http.client.AccessToken
import io.renku.http.rest.Links
import io.renku.http.server.EndpointTester.{JsonOps, jsonEntityDecoder}
import io.renku.jsonld.syntax._
import org.http4s.Status._
import org.scalatest.GivenWhenThen
import org.scalatest.featurespec.AnyFeatureSpec
import eu.timepit.refined.auto._
import io.circe.Json
import io.renku.http.rest.Links

class ProjectsResourcesSpec
extends AnyFeatureSpec
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package io.renku.http.rest.paging

import cats.syntax.all._
import io.renku.http.rest.paging.model._
import org.http4s.Query

final case class PagingRequest(page: Page, perPage: PerPage)

Expand Down Expand Up @@ -50,6 +51,7 @@ object PagingRequest {
object page extends OptionalValidatingQueryParamDecoderMatcher[Page]("page") {
val parameterName: String = "page"
def errorMessage(value: String): String = s"'$value' not a valid '$parameterName' value"
def find(query: Query): Option[ValidatedNel[ParseFailure, Page]] = page.unapply(query.multiParams).flatten
}

private implicit val perPageParameterDecoder: QueryParamDecoder[PerPage] =
Expand All @@ -66,6 +68,7 @@ object PagingRequest {
object perPage extends OptionalValidatingQueryParamDecoderMatcher[PerPage]("per_page") {
val parameterName: String = "per_page"
def errorMessage(value: String): String = s"'$value' not a valid '$parameterName' value"
def find(query: Query): Option[ValidatedNel[ParseFailure, PerPage]] = perPage.unapply(query.multiParams).flatten
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,8 @@ class PagingResponseSpec extends AnyWordSpec with IOSpec with ScalaCheckProperty
}

"fail if requested page and perPage is beyond the number of results" in {
val paging = pagingRequests.generateOne.copy(page = Gen.oneOf(2, 3, 4, 5).generateAs(Page))
val paging =
PagingRequest(page = Gen.oneOf(2, 3, 4, 5).generateAs(Page), perPage = ints(2, 100).generateAs[PerPage])
val results = nonBlankStrings()
.generateNonEmptyList(maxElements = Refined.unsafeApply(paging.perPage.value - 1))
.toList
Expand Down
64 changes: 48 additions & 16 deletions knowledge-graph/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,23 @@ This is a microservice which provides API for the Graph DB.

The following routes may be slightly different when accessed via the main Renku API, which uses the gateway service (e.g. /api/kg/datasets)

| Method | Path | Description |
|--------|--------------------------------------------------------------------------|----------------------------------------------------------------------|
| GET | ```/knowledge-graph/datasets``` | Returns datasets filtered by the given predicates. |
| GET | ```/knowledge-graph/datasets/:id``` | Returns details of the dataset with the given `id` |
| GET | ```/knowledge-graph/entities``` | Returns entities filtered by the given predicates` |
| GET | ```/knowledge-graph/graphql``` | Returns GraphQL endpoint schema |
| POST | ```/knowledge-graph/graphql``` | GraphQL query endpoint |
| GET | ```/knowledge-graph/ontology``` | Returns ontology used in the Knowledge Graph |
| GET | ```/knowledge-graph/projects/:namespace/:name``` | Returns details of the project with the given `namespace/name` |
| GET | ```/knowledge-graph/projects/:namespace/:name/datasets``` | Returns datasets of the project with the given `path` |
| GET | ```/knowledge-graph/projects/:namespace/:name/files/:location/lineage``` | Returns the lineage for a the path (location) of a file on a project |
| GET | ```/knowledge-graph/spec.json``` | Returns OpenAPI specification of the service's resources |
| GET | ```/knowledge-graph/users/:id/projects``` | Returns all user's projects |
| GET | ```/metrics``` | Serves Prometheus metrics |
| GET | ```/ping``` | To check if service is healthy |
| GET | ```/version``` | Returns info about service version |
| Method | Path | Description |
|--------|--------------------------------------------------------------------------|--------------------------------------------------------------------------------------|
| GET | ```/knowledge-graph/datasets``` | Returns datasets filtered by the given predicates. |
| GET | ```/knowledge-graph/datasets/:id``` | Returns details of the dataset with the given `id` |
| GET | ```/knowledge-graph/entities``` | Returns entities filtered by the given predicates` |
| GET | ```/knowledge-graph/graphql``` | Returns GraphQL endpoint schema |
| POST | ```/knowledge-graph/graphql``` | GraphQL query endpoint |
| GET | ```/knowledge-graph/ontology``` | Returns ontology used in the Knowledge Graph |
| GET | ```/knowledge-graph/projects/:namespace/:name``` | Returns details of the project with the given `namespace/name` |
| GET | ```/knowledge-graph/projects/:namespace/:name/datasets``` | Returns datasets of the project with the given `path` |
| GET | ```/knowledge-graph/projects/:namespace/:name/datasets/:dsName/tags``` | Returns tags of the dataset with the given `dsName` on project with the given `path` |
| GET | ```/knowledge-graph/projects/:namespace/:name/files/:location/lineage``` | Returns the lineage for a the path (location) of a file on a project |
| GET | ```/knowledge-graph/spec.json``` | Returns OpenAPI specification of the service's resources |
| GET | ```/knowledge-graph/users/:id/projects``` | Returns all user's projects |
| GET | ```/metrics``` | Serves Prometheus metrics |
| GET | ```/ping``` | To check if service is healthy |
| GET | ```/version``` | Returns info about service version |

#### GET /knowledge-graph/datasets

Expand Down Expand Up @@ -784,6 +785,37 @@ Response body example:
]
```

#### GET /knowledge-graph/projects/:namespace/:name/datasets/:dsName/tags

Finds list of tags existing on the Dataset with the given `dsName` on the project with the given `namespace/name`.

**Response**

| Status | Description |
|----------------------------|-----------------------------------------------------------------------------------------------|
| OK (200) | If tags are found or `[]` if nothing is found |
| UNAUTHORIZED (401) | If given auth header cannot be authenticated |
| NOT_FOUND (404) | If there is no project with the given `namespace/name` or user is not authorised to access it |
| INTERNAL SERVER ERROR (500)| Otherwise |

Response body example:

```json
[
{
"name": "name",
"date": "2012-11-15T10:00:00.000Z",
"description": "desc",
"_links": [
{
"rel": "dataset-details",
"href": "http://t:5511/knowledge-graph/datasets/1232444"
}
]
}
]
```

#### GET /knowledge-graph/projects/:namespace/:name/files/:location/lineage

Fetches lineage for a given project `namespace`/`name` and file `location` (URL-encoded relative path to the file). This endpoint is intended to replace the graphql endpoint.
Expand Down
Loading