diff --git a/graph-commons/src/main/scala/io/renku/logging/ExecutionTimeRecorder.scala b/graph-commons/src/main/scala/io/renku/logging/ExecutionTimeRecorder.scala
index cc3fa95448..f81d86ed0f 100644
--- a/graph-commons/src/main/scala/io/renku/logging/ExecutionTimeRecorder.scala
+++ b/graph-commons/src/main/scala/io/renku/logging/ExecutionTimeRecorder.scala
@@ -39,15 +39,15 @@ abstract class ExecutionTimeRecorder[F[_]](threshold: ElapsedTime) {
maybeHistogramLabel: Option[String Refined NonEmpty] = None
): F[(ElapsedTime, A)]
- def measureAndLogTime[A](condition: PartialFunction[A, String])(
+ def measureAndLogTime[A](message: PartialFunction[A, String])(
block: F[A]
)(implicit F: Monad[F], L: Logger[F]): F[A] =
- measureExecutionTime(block).flatMap(logExecutionTimeWhen(condition))
+ measureExecutionTime(block).flatMap(logExecutionTimeWhen(message))
def logExecutionTimeWhen[A](
- condition: PartialFunction[A, String]
+ message: PartialFunction[A, String]
)(implicit F: Applicative[F], L: Logger[F]): ((ElapsedTime, A)) => F[A] = { resultAndTime =>
- logWarningIfAboveThreshold(resultAndTime, condition.lift).as(resultAndTime._2)
+ logWarningIfAboveThreshold(resultAndTime, message.lift).as(resultAndTime._2)
}
def logExecutionTime[A](
@@ -58,10 +58,10 @@ abstract class ExecutionTimeRecorder[F[_]](threshold: ElapsedTime) {
private def logWarningIfAboveThreshold[A](
resultAndTime: (ElapsedTime, A),
- condition: A => Option[String]
+ withMessage: A => Option[String]
)(implicit F: Applicative[F], L: Logger[F]): F[Unit] = {
val (elapsedTime, result) = resultAndTime
- condition(result)
+ withMessage(result)
.filter(_ => elapsedTime >= threshold)
.map(message => L.warn(s"$message in ${elapsedTime}ms"))
.getOrElse(F.unit)
diff --git a/graph-commons/src/main/scala/io/renku/triplesstore/ProjectSparqlClient.scala b/graph-commons/src/main/scala/io/renku/triplesstore/ProjectSparqlClient.scala
new file mode 100644
index 0000000000..6735a1b8b6
--- /dev/null
+++ b/graph-commons/src/main/scala/io/renku/triplesstore/ProjectSparqlClient.scala
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2023 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.triplesstore
+
+import cats.Monad
+import cats.effect._
+import cats.syntax.all._
+import eu.timepit.refined.api.Refined
+import eu.timepit.refined.collection.NonEmpty
+import eu.timepit.refined.auto._
+import fs2.io.net.Network
+import io.renku.jsonld.JsonLD
+import io.renku.triplesstore.client.http.{Retry, SparqlClient, SparqlQuery, SparqlUpdate}
+import org.typelevel.log4cats.Logger
+
+/** SparQL client fixed to the `projects` dataset. */
+trait ProjectSparqlClient[F[_]] extends SparqlClient[F]
+
+object ProjectSparqlClient {
+ def apply[F[_]: Monad: Logger: SparqlQueryTimeRecorder](c: SparqlClient[F]) =
+ new ProjectSparqlClient[F] {
+ private[this] val rec = SparqlQueryTimeRecorder[F].instance
+ override def update(request: SparqlUpdate) = {
+ val label = histogramLabel(request)
+ val work = c.update(request)
+ rec
+ .measureExecutionTime(work, label)
+ .flatMap(rec.logExecutionTime(s"Execute sparql update '$label'"))
+ }
+
+ override def upload(data: JsonLD) = {
+ val label: String Refined NonEmpty = "jsonld upload"
+ val work = c.upload(data)
+ rec
+ .measureExecutionTime(work, label.some)
+ .flatMap(rec.logExecutionTime("Execute JSONLD upload"))
+ }
+
+ override def query(request: SparqlQuery) = {
+ val label = histogramLabel(request)
+ val work = c.query(request)
+ rec
+ .measureExecutionTime(work, label)
+ .flatMap(rec.logExecutionTime(s"Execute sparql query '$label'"))
+ }
+ }
+
+ def apply[F[_]: Network: Async: Logger: SparqlQueryTimeRecorder](
+ cc: ProjectsConnectionConfig,
+ retryCfg: Retry.RetryConfig = Retry.RetryConfig.default
+ ): Resource[F, ProjectSparqlClient[F]] = {
+ val cfg = cc.toCC(Some(retryCfg))
+ SparqlClient[F](cfg).map(apply(_))
+ }
+
+ private def histogramLabel(r: Any): Option[String Refined NonEmpty] =
+ r match {
+ case q: io.renku.triplesstore.SparqlQuery => q.name.some
+ case _ => None
+ }
+}
diff --git a/graph-commons/src/main/scala/io/renku/triplesstore/SparqlQuery.scala b/graph-commons/src/main/scala/io/renku/triplesstore/SparqlQuery.scala
index a3d11027e5..3fbe115f17 100644
--- a/graph-commons/src/main/scala/io/renku/triplesstore/SparqlQuery.scala
+++ b/graph-commons/src/main/scala/io/renku/triplesstore/SparqlQuery.scala
@@ -27,17 +27,22 @@ import io.renku.jsonld.Schema
import io.renku.tinytypes.StringTinyType
import io.renku.triplesstore.SparqlQuery.Prefix
import io.renku.triplesstore.client.sparql.Fragment
+import io.renku.triplesstore.client.http.{SparqlQuery => ClientSparqlQuery, SparqlUpdate => ClientSparqlUpdate}
final case class SparqlQuery(name: String Refined NonEmpty,
prefixes: Set[Prefix],
body: String,
maybePagingRequest: Option[PagingRequest]
-) {
+) extends ClientSparqlQuery
+ with ClientSparqlUpdate {
+
override lazy val toString: String =
s"""|${prefixes.mkString("", "\n", "")}
|$body
|$pagingRequest""".stripMargin.trim
+ override lazy val render: String = toString
+
private lazy val pagingRequest =
maybePagingRequest
.map { pagingRequest =>
diff --git a/graph-commons/src/main/scala/io/renku/triplesstore/SparqlQueryTimeRecorder.scala b/graph-commons/src/main/scala/io/renku/triplesstore/SparqlQueryTimeRecorder.scala
index 67b9ef25c0..2963feec53 100644
--- a/graph-commons/src/main/scala/io/renku/triplesstore/SparqlQueryTimeRecorder.scala
+++ b/graph-commons/src/main/scala/io/renku/triplesstore/SparqlQueryTimeRecorder.scala
@@ -33,7 +33,7 @@ object SparqlQueryTimeRecorder {
import io.renku.metrics.MetricsRegistry
- def apply[F[_]: Sync: Logger: MetricsRegistry](): F[SparqlQueryTimeRecorder[F]] = MetricsRegistry[F]
+ def create[F[_]: Sync: Logger: MetricsRegistry](): F[SparqlQueryTimeRecorder[F]] = MetricsRegistry[F]
.register {
new LabeledHistogramImpl[F](
name = "sparql_execution_times",
diff --git a/graph-commons/src/test/scala/io/renku/logging/TestSparqlQueryTimeRecorder.scala b/graph-commons/src/test/scala/io/renku/logging/TestSparqlQueryTimeRecorder.scala
index b57b6836b5..b664441028 100644
--- a/graph-commons/src/test/scala/io/renku/logging/TestSparqlQueryTimeRecorder.scala
+++ b/graph-commons/src/test/scala/io/renku/logging/TestSparqlQueryTimeRecorder.scala
@@ -26,6 +26,6 @@ import org.typelevel.log4cats.Logger
object TestSparqlQueryTimeRecorder {
def apply[F[_]: Sync: Logger]: F[SparqlQueryTimeRecorder[F]] = {
implicit val metricsRegistry: MetricsRegistry[F] = TestMetricsRegistry[F]
- SparqlQueryTimeRecorder[F]()
+ SparqlQueryTimeRecorder.create[F]()
}
}
diff --git a/graph-commons/src/test/scala/io/renku/triplesstore/ProjectSparqlClientSpec.scala b/graph-commons/src/test/scala/io/renku/triplesstore/ProjectSparqlClientSpec.scala
new file mode 100644
index 0000000000..5ce8520a6d
--- /dev/null
+++ b/graph-commons/src/test/scala/io/renku/triplesstore/ProjectSparqlClientSpec.scala
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2023 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.triplesstore
+
+import cats.effect.IO
+import cats.effect.testing.scalatest.AsyncIOSpec
+import eu.timepit.refined.auto._
+import io.prometheus.client.Histogram
+import io.renku.cli.model.CliSoftwareAgent
+import io.renku.graph.model.agents
+import io.renku.interpreters.TestLogger
+import io.renku.jsonld.syntax._
+import io.renku.logging.TestExecutionTimeRecorder
+import io.renku.triplesstore.client.syntax._
+import io.renku.triplesstore.client.util.JenaContainerSupport
+import org.scalatest.flatspec.AsyncFlatSpec
+import org.scalatest.matchers.should
+import org.typelevel.log4cats.Logger
+
+class ProjectSparqlClientSpec extends AsyncFlatSpec with AsyncIOSpec with JenaContainerSupport with should.Matchers {
+ implicit val logger: Logger[IO] = TestLogger()
+
+ val makeHistogram = IO(
+ new Histogram.Builder().name("test").help("test").labelNames("update").buckets(0.5, 0.8).create()
+ )
+
+ def makeSparqlQueryTimeRecorder(h: Histogram): SparqlQueryTimeRecorder[IO] =
+ new SparqlQueryTimeRecorder[IO](TestExecutionTimeRecorder[IO](Some(h)))
+
+ def withProjectClient(implicit sqr: SparqlQueryTimeRecorder[IO]) =
+ withDataset("projects").map(ProjectSparqlClient.apply(_))
+
+ def assertSampled(histogram: Histogram) =
+ histogram.collect().get(0).samples.size should be > 0
+
+ def assertNotSampled(histogram: Histogram) =
+ histogram.collect().get(0).samples.size shouldBe 0
+
+ def resetHistogram(histogram: Histogram) = {
+ histogram.clear()
+ assertNotSampled(histogram)
+ }
+
+ it should "measure execution time for named queries" in {
+ val histogram = makeHistogram.unsafeRunSync()
+ implicit val sr: SparqlQueryTimeRecorder[IO] = makeSparqlQueryTimeRecorder(histogram)
+ withProjectClient.use { c =>
+ for {
+ _ <- IO(assertNotSampled(histogram))
+ up = SparqlQuery.apply(
+ name = "test-update",
+ prefixes = Set.empty,
+ body = sparql"""
+ |PREFIX p:
+ |INSERT DATA {
+ | p:fred p:hasSpouse p:wilma .
+ | p:fred p:hasChild p:pebbles .
+ | p:wilma p:hasChild p:pebbles .
+ | p:pebbles p:hasSpouse p:bamm-bamm ;
+ | p:hasChild p:roxy, p:chip.
+ |}""".stripMargin
+ )
+ _ <- c.update(up)
+ _ = assertSampled(histogram)
+
+ _ <- IO(resetHistogram(histogram))
+ q = SparqlQuery(
+ name = "test-query",
+ prefixes = Set.empty,
+ body = sparql"SELECT * WHERE { ?s ?p ?o } LIMIT 1"
+ )
+ _ <- c.query(q)
+ _ = assertSampled(histogram)
+
+ _ <- IO(resetHistogram(histogram))
+ data = CliSoftwareAgent(agents.ResourceId("http://u.rl"), agents.Name("test")).asJsonLD
+ _ <- c.upload(data)
+ _ = assertSampled(histogram)
+ } yield ()
+ }
+ }
+
+ it should "not measure execution time for un-named queries" in {
+ val histogram = makeHistogram.unsafeRunSync()
+ implicit val sr: SparqlQueryTimeRecorder[IO] = makeSparqlQueryTimeRecorder(histogram)
+
+ withProjectClient.use { c =>
+ for {
+ _ <- IO(assertNotSampled(histogram))
+
+ q = sparql"""
+ |PREFIX p:
+ |INSERT DATA {
+ | p:fred p:hasSpouse p:wilma .
+ | p:fred p:hasChild p:pebbles .
+ | p:wilma p:hasChild p:pebbles .
+ | p:pebbles p:hasSpouse p:bamm-bamm ;
+ | p:hasChild p:roxy, p:chip.
+ |}""".stripMargin
+
+ _ <- c.update(q)
+
+ _ = assertNotSampled(histogram)
+ } yield ()
+ }
+ }
+
+}
diff --git a/knowledge-graph/src/main/scala/io/renku/knowledgegraph/Microservice.scala b/knowledge-graph/src/main/scala/io/renku/knowledgegraph/Microservice.scala
index 0cf8fa6ba1..69fe42d23a 100644
--- a/knowledge-graph/src/main/scala/io/renku/knowledgegraph/Microservice.scala
+++ b/knowledge-graph/src/main/scala/io/renku/knowledgegraph/Microservice.scala
@@ -38,7 +38,7 @@ object Microservice extends IOMicroservice {
override def run(args: List[String]): IO[ExitCode] = for {
implicit0(mr: MetricsRegistry[IO]) <- MetricsRegistry[IO]()
- implicit0(sqtr: SparqlQueryTimeRecorder[IO]) <- SparqlQueryTimeRecorder[IO]()
+ implicit0(sqtr: SparqlQueryTimeRecorder[IO]) <- SparqlQueryTimeRecorder.create[IO]()
projectConnConfig <- ProjectsConnectionConfig[IO]()
certificateLoader <- CertificateLoader[IO]
sentryInitializer <- SentryInitializer[IO]
diff --git a/project-auth/src/test/scala/io/renku/projectauth/ProjectAuthServiceSpec.scala b/project-auth/src/test/scala/io/renku/projectauth/ProjectAuthServiceSpec.scala
index b4abc443b5..f402ff03f6 100644
--- a/project-auth/src/test/scala/io/renku/projectauth/ProjectAuthServiceSpec.scala
+++ b/project-auth/src/test/scala/io/renku/projectauth/ProjectAuthServiceSpec.scala
@@ -27,13 +27,13 @@ import io.renku.generators.Generators.Implicits._
import io.renku.graph.model.RenkuUrl
import io.renku.graph.model.persons.GitLabId
import io.renku.graph.model.projects.{Role, Visibility}
-import io.renku.triplesstore.client.util.JenaContainerSpec
+import io.renku.triplesstore.client.util.JenaContainerSupport
import org.scalatest.flatspec.AsyncFlatSpec
import org.scalatest.matchers.should
import org.typelevel.log4cats.Logger
import org.typelevel.log4cats.slf4j.Slf4jLogger
-class ProjectAuthServiceSpec extends AsyncFlatSpec with AsyncIOSpec with JenaContainerSpec with should.Matchers {
+class ProjectAuthServiceSpec extends AsyncFlatSpec with AsyncIOSpec with JenaContainerSupport with should.Matchers {
implicit val logger: Logger[IO] = Slf4jLogger.getLogger[IO]
implicit val renkuUrl: RenkuUrl = RenkuUrl("http://localhost/renku")
diff --git a/triples-generator/src/main/scala/io/renku/triplesgenerator/Microservice.scala b/triples-generator/src/main/scala/io/renku/triplesgenerator/Microservice.scala
index 83b9fd12fa..828bc4354b 100644
--- a/triples-generator/src/main/scala/io/renku/triplesgenerator/Microservice.scala
+++ b/triples-generator/src/main/scala/io/renku/triplesgenerator/Microservice.scala
@@ -42,7 +42,7 @@ import io.renku.triplesgenerator.events.consumers.tsmigrationrequest.migrations.
import io.renku.triplesgenerator.events.consumers.tsprovisioning.{minprojectinfo, triplesgenerated}
import io.renku.triplesgenerator.init.{CliVersionCompatibilityChecker, CliVersionCompatibilityVerifier}
import io.renku.triplesgenerator.metrics.MetricsService
-import io.renku.triplesstore.{ProjectsConnectionConfig, SparqlQueryTimeRecorder}
+import io.renku.triplesstore.{ProjectSparqlClient, ProjectsConnectionConfig, SparqlQueryTimeRecorder}
import natchez.Trace.Implicits.noop
import org.http4s.server.Server
import org.typelevel.log4cats.Logger
@@ -68,12 +68,15 @@ object Microservice extends IOMicroservice {
dbSessionPool <- Resource
.eval(new TgLockDbConfigProvider[IO].map(SessionPoolResource[IO, TgLockDB]))
.flatMap(identity)
+ implicit0(mr: MetricsRegistry[IO]) <- Resource.eval(MetricsRegistry[IO]())
+ implicit0(sqtr: SparqlQueryTimeRecorder[IO]) <- Resource.eval(SparqlQueryTimeRecorder.create[IO]())
+
projectConnConfig <- Resource.eval(ProjectsConnectionConfig[IO](config))
projectsSparql <- ProjectSparqlClient[IO](projectConnConfig)
- } yield (config, dbSessionPool, projectsSparql)
+ } yield (config, dbSessionPool, projectsSparql, mr, sqtr)
- resources.use { case (config, dbSessionPool, projectSparqlClient) =>
- doRun(config, dbSessionPool, projectSparqlClient)
+ resources.use { case (config, dbSessionPool, projectSparqlClient, mr, sqtr) =>
+ doRun(config, dbSessionPool, projectSparqlClient)(mr, sqtr)
}
}
@@ -81,12 +84,11 @@ object Microservice extends IOMicroservice {
config: Config,
dbSessionPool: SessionResource[IO, TgLockDB],
projectSparqlClient: ProjectSparqlClient[IO]
- ): IO[ExitCode] = for {
- implicit0(mr: MetricsRegistry[IO]) <- MetricsRegistry[IO]()
- implicit0(sqtr: SparqlQueryTimeRecorder[IO]) <- SparqlQueryTimeRecorder[IO]()
- implicit0(gc: GitLabClient[IO]) <- GitLabClient[IO]()
- implicit0(acf: AccessTokenFinder[IO]) <- AccessTokenFinder[IO]()
- implicit0(rp: ReProvisioningStatus[IO]) <- ReProvisioningStatus[IO]()
+ )(implicit mr: MetricsRegistry[IO], sqtr: SparqlQueryTimeRecorder[IO]): IO[ExitCode] = for {
+
+ implicit0(gc: GitLabClient[IO]) <- GitLabClient[IO]()
+ implicit0(acf: AccessTokenFinder[IO]) <- AccessTokenFinder[IO]()
+ implicit0(rp: ReProvisioningStatus[IO]) <- ReProvisioningStatus[IO]()
_ <- TgLockDB.migrate[IO](dbSessionPool, 20.seconds)
diff --git a/triples-generator/src/main/scala/io/renku/triplesgenerator/events/consumers/ProjectAuthSync.scala b/triples-generator/src/main/scala/io/renku/triplesgenerator/events/consumers/ProjectAuthSync.scala
index 48998d4cec..03033ddc93 100644
--- a/triples-generator/src/main/scala/io/renku/triplesgenerator/events/consumers/ProjectAuthSync.scala
+++ b/triples-generator/src/main/scala/io/renku/triplesgenerator/events/consumers/ProjectAuthSync.scala
@@ -25,7 +25,7 @@ import fs2.io.net.Network
import io.renku.graph.model.RenkuUrl
import io.renku.graph.model.projects.{Slug, Visibility}
import io.renku.projectauth.{ProjectAuthData, ProjectAuthService, ProjectMember}
-import io.renku.triplesstore.ProjectsConnectionConfig
+import io.renku.triplesstore.{ProjectSparqlClient, ProjectsConnectionConfig, SparqlQueryTimeRecorder}
import io.renku.triplesstore.client.http.{RowDecoder, SparqlClient}
import io.renku.triplesstore.client.syntax._
import org.typelevel.log4cats.Logger
@@ -37,7 +37,9 @@ trait ProjectAuthSync[F[_]] {
object ProjectAuthSync {
- def resource[F[_]: Async: Logger: Network](cc: ProjectsConnectionConfig)(implicit renkuUrl: RenkuUrl) =
+ def resource[F[_]: Async: Logger: Network: SparqlQueryTimeRecorder](cc: ProjectsConnectionConfig)(implicit
+ renkuUrl: RenkuUrl
+ ) =
ProjectSparqlClient[F](cc).map(apply[F])
def apply[F[_]: Sync](
diff --git a/triples-generator/src/main/scala/io/renku/triplesgenerator/events/consumers/ProjectSparqlClient.scala b/triples-generator/src/main/scala/io/renku/triplesgenerator/events/consumers/ProjectSparqlClient.scala
deleted file mode 100644
index 819ac0be51..0000000000
--- a/triples-generator/src/main/scala/io/renku/triplesgenerator/events/consumers/ProjectSparqlClient.scala
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2023 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.triplesgenerator.events.consumers
-
-import cats.effect._
-import fs2.io.net.Network
-import io.renku.jsonld.JsonLD
-import io.renku.triplesstore.ProjectsConnectionConfig
-import io.renku.triplesstore.client.http.{Retry, SparqlClient, SparqlQuery, SparqlUpdate}
-import org.typelevel.log4cats.Logger
-
-/** SparQL client fixed to the `projects` dataset. */
-trait ProjectSparqlClient[F[_]] extends SparqlClient[F]
-
-object ProjectSparqlClient {
- def apply[F[_]: Network: Async: Logger](
- cc: ProjectsConnectionConfig,
- retryCfg: Retry.RetryConfig = Retry.RetryConfig.default
- ): Resource[F, ProjectSparqlClient[F]] = {
- val cfg = cc.toCC(Some(retryCfg))
- SparqlClient[F](cfg).map(c =>
- new ProjectSparqlClient[F] {
- override def update(request: SparqlUpdate) = c.update(request)
- override def upload(data: JsonLD) = c.upload(data)
- override def query(request: SparqlQuery) = c.query(request)
- }
- )
- }
-}
diff --git a/triples-generator/src/main/scala/io/renku/triplesgenerator/events/consumers/membersync/EventHandler.scala b/triples-generator/src/main/scala/io/renku/triplesgenerator/events/consumers/membersync/EventHandler.scala
index 96611a3068..a3385f270b 100644
--- a/triples-generator/src/main/scala/io/renku/triplesgenerator/events/consumers/membersync/EventHandler.scala
+++ b/triples-generator/src/main/scala/io/renku/triplesgenerator/events/consumers/membersync/EventHandler.scala
@@ -31,7 +31,7 @@ import io.renku.lock.syntax._
import io.renku.http.client.GitLabClient
import io.renku.lock.Lock
import io.renku.triplesgenerator.TgLockDB.TsWriteLock
-import io.renku.triplesstore.SparqlQueryTimeRecorder
+import io.renku.triplesstore.{ProjectSparqlClient, SparqlQueryTimeRecorder}
import org.typelevel.log4cats.Logger
import tsmigrationrequest.migrations.reprovisioning.ReProvisioningStatus
diff --git a/triples-generator/src/main/scala/io/renku/triplesgenerator/events/consumers/membersync/MembersSynchronizer.scala b/triples-generator/src/main/scala/io/renku/triplesgenerator/events/consumers/membersync/MembersSynchronizer.scala
index f7f874d309..fb40fc0cb2 100644
--- a/triples-generator/src/main/scala/io/renku/triplesgenerator/events/consumers/membersync/MembersSynchronizer.scala
+++ b/triples-generator/src/main/scala/io/renku/triplesgenerator/events/consumers/membersync/MembersSynchronizer.scala
@@ -26,7 +26,6 @@ import io.renku.graph.tokenrepository.AccessTokenFinder
import io.renku.http.client.{AccessToken, GitLabClient}
import io.renku.logging.ExecutionTimeRecorder
import io.renku.logging.ExecutionTimeRecorder.ElapsedTime
-import io.renku.triplesgenerator.events.consumers.ProjectSparqlClient
import io.renku.triplesgenerator.gitlab.GitLabProjectMembersFinder
import io.renku.triplesstore._
import org.typelevel.log4cats.Logger
diff --git a/triples-generator/src/main/scala/io/renku/triplesgenerator/events/consumers/membersync/SubscriptionFactory.scala b/triples-generator/src/main/scala/io/renku/triplesgenerator/events/consumers/membersync/SubscriptionFactory.scala
index f2984585ed..fbe686b2b0 100644
--- a/triples-generator/src/main/scala/io/renku/triplesgenerator/events/consumers/membersync/SubscriptionFactory.scala
+++ b/triples-generator/src/main/scala/io/renku/triplesgenerator/events/consumers/membersync/SubscriptionFactory.scala
@@ -28,7 +28,7 @@ import io.renku.graph.tokenrepository.AccessTokenFinder
import io.renku.http.client.GitLabClient
import io.renku.triplesgenerator.Microservice
import io.renku.triplesgenerator.TgLockDB.TsWriteLock
-import io.renku.triplesgenerator.events.consumers.ProjectSparqlClient
+import io.renku.triplesstore.ProjectSparqlClient
import io.renku.triplesgenerator.events.consumers.tsmigrationrequest.migrations.reprovisioning.ReProvisioningStatus
import io.renku.triplesstore.SparqlQueryTimeRecorder
import org.typelevel.log4cats.Logger
diff --git a/triples-generator/src/main/scala/io/renku/triplesgenerator/events/consumers/membersync/namedgraphs/KGSynchronizer.scala b/triples-generator/src/main/scala/io/renku/triplesgenerator/events/consumers/membersync/namedgraphs/KGSynchronizer.scala
index 66c20ecc04..a777212104 100644
--- a/triples-generator/src/main/scala/io/renku/triplesgenerator/events/consumers/membersync/namedgraphs/KGSynchronizer.scala
+++ b/triples-generator/src/main/scala/io/renku/triplesgenerator/events/consumers/membersync/namedgraphs/KGSynchronizer.scala
@@ -24,7 +24,7 @@ import cats.effect._
import cats.syntax.all._
import io.renku.graph.config.RenkuUrlLoader
import io.renku.graph.model.{RenkuUrl, projects}
-import io.renku.triplesgenerator.events.consumers.{ProjectAuthSync, ProjectSparqlClient}
+import io.renku.triplesgenerator.events.consumers.ProjectAuthSync
import io.renku.triplesgenerator.gitlab.GitLabProjectMember
import io.renku.triplesstore._
import org.typelevel.log4cats.Logger
diff --git a/triples-generator/src/test/scala/io/renku/triplesgenerator/DatasetProvision.scala b/triples-generator/src/test/scala/io/renku/triplesgenerator/DatasetProvision.scala
index 71ca86691d..cda78a9101 100644
--- a/triples-generator/src/test/scala/io/renku/triplesgenerator/DatasetProvision.scala
+++ b/triples-generator/src/test/scala/io/renku/triplesgenerator/DatasetProvision.scala
@@ -23,9 +23,10 @@ import io.renku.entities.searchgraphs.SearchInfoDatasets
import io.renku.graph.model.entities.EntityFunctions
import io.renku.graph.model.projects.Role
import io.renku.graph.model.{RenkuUrl, entities}
+import io.renku.logging.{ExecutionTimeRecorder, TestExecutionTimeRecorder}
import io.renku.projectauth.ProjectMember
-import io.renku.triplesgenerator.events.consumers.{ProjectAuthSync, ProjectSparqlClient}
-import io.renku.triplesstore.{GraphsProducer, InMemoryJena, ProjectsDataset}
+import io.renku.triplesgenerator.events.consumers.ProjectAuthSync
+import io.renku.triplesstore._
trait DatasetProvision extends SearchInfoDatasets { self: ProjectsDataset with InMemoryJena =>
@@ -36,6 +37,9 @@ trait DatasetProvision extends SearchInfoDatasets { self: ProjectsDataset with I
graphsProducer: GraphsProducer[entities.Project],
renkuUrl: RenkuUrl
): IO[Unit] = {
+ val execTimeRecorder: ExecutionTimeRecorder[IO] = TestExecutionTimeRecorder[IO]()
+ implicit val sparqlQueryTimeRecorder: SparqlQueryTimeRecorder[IO] =
+ new SparqlQueryTimeRecorder[IO](execTimeRecorder)
val ps = ProjectSparqlClient[IO](projectsDSConnectionInfo).map(ProjectAuthSync[IO](_))
val members = project.members.flatMap(p => p.maybeGitLabId.map(id => ProjectMember(id, Role.Reader)))
super.provisionProject(project) *> ps.use(_.syncProject(project.slug, members))
diff --git a/triples-store-client/src/main/scala/io/renku/triplesstore/client/http/SparqlClient.scala b/triples-store-client/src/main/scala/io/renku/triplesstore/client/http/SparqlClient.scala
index cba2baa6ac..6bb0c20294 100644
--- a/triples-store-client/src/main/scala/io/renku/triplesstore/client/http/SparqlClient.scala
+++ b/triples-store-client/src/main/scala/io/renku/triplesstore/client/http/SparqlClient.scala
@@ -39,7 +39,7 @@ trait SparqlClient[F[_]] {
/** The sparql query operation, returning results as JSON. */
def query(request: SparqlQuery): F[Json]
- def queryDecode[A](request: SparqlQuery)(implicit d: RowDecoder[A], F: MonadThrow[F]): F[List[A]] = {
+ final def queryDecode[A](request: SparqlQuery)(implicit d: RowDecoder[A], F: MonadThrow[F]): F[List[A]] = {
val decoder = Decoder
.instance(c => c.downField("results").downField("bindings").as[List[A]])
.withErrorMessage(s"Decoding Sparql result failed for request: $request")
diff --git a/triples-store-client/src/test/scala/io/renku/triplesstore/client/http/SparqlClientSpec.scala b/triples-store-client/src/test/scala/io/renku/triplesstore/client/http/SparqlClientSpec.scala
index 0776d15e45..fef34d599a 100644
--- a/triples-store-client/src/test/scala/io/renku/triplesstore/client/http/SparqlClientSpec.scala
+++ b/triples-store-client/src/test/scala/io/renku/triplesstore/client/http/SparqlClientSpec.scala
@@ -27,11 +27,11 @@ import org.scalatest.matchers.should
import org.typelevel.log4cats.Logger
import org.typelevel.log4cats.slf4j.Slf4jLogger
import io.renku.triplesstore.client.syntax._
-import io.renku.triplesstore.client.util.JenaContainerSpec
+import io.renku.triplesstore.client.util.JenaContainerSupport
import java.time.Instant
-class SparqlClientSpec extends AsyncFlatSpec with AsyncIOSpec with JenaContainerSpec with should.Matchers {
+class SparqlClientSpec extends AsyncFlatSpec with AsyncIOSpec with JenaContainerSupport with should.Matchers {
implicit val logger: Logger[IO] = Slf4jLogger.getLogger[IO]
val dataset = "projects"
diff --git a/triples-store-client/src/test/scala/io/renku/triplesstore/client/util/JenaContainerDirectSpec.scala b/triples-store-client/src/test/scala/io/renku/triplesstore/client/util/JenaContainerDirectSupport.scala
similarity index 94%
rename from triples-store-client/src/test/scala/io/renku/triplesstore/client/util/JenaContainerDirectSpec.scala
rename to triples-store-client/src/test/scala/io/renku/triplesstore/client/util/JenaContainerDirectSupport.scala
index 5a7fb370c4..56bb5d7e01 100644
--- a/triples-store-client/src/test/scala/io/renku/triplesstore/client/util/JenaContainerDirectSpec.scala
+++ b/triples-store-client/src/test/scala/io/renku/triplesstore/client/util/JenaContainerDirectSupport.scala
@@ -25,7 +25,7 @@ import org.scalatest.{BeforeAndAfterAll, Suite}
import org.typelevel.log4cats.Logger
/** Trait for having a client directly accessible using "unsafe" effects. */
-trait JenaContainerDirectSpec extends JenaContainerSpec with BeforeAndAfterAll { self: Suite =>
+trait JenaContainerDirectSupport extends JenaContainerSupport with BeforeAndAfterAll { self: Suite =>
implicit def logger: Logger[IO]
implicit val ioRuntime: IORuntime
diff --git a/triples-store-client/src/test/scala/io/renku/triplesstore/client/util/JenaContainerSpec.scala b/triples-store-client/src/test/scala/io/renku/triplesstore/client/util/JenaContainerSupport.scala
similarity index 96%
rename from triples-store-client/src/test/scala/io/renku/triplesstore/client/util/JenaContainerSpec.scala
rename to triples-store-client/src/test/scala/io/renku/triplesstore/client/util/JenaContainerSupport.scala
index 2fd529915b..5ef2338399 100644
--- a/triples-store-client/src/test/scala/io/renku/triplesstore/client/util/JenaContainerSpec.scala
+++ b/triples-store-client/src/test/scala/io/renku/triplesstore/client/util/JenaContainerSupport.scala
@@ -29,7 +29,7 @@ import org.typelevel.log4cats.Logger
import scala.concurrent.duration._
-trait JenaContainerSpec extends ForAllTestContainer { self: Suite =>
+trait JenaContainerSupport extends ForAllTestContainer { self: Suite =>
protected val runMode: JenaRunMode = JenaRunMode.GenericContainer
protected val timeout: Duration = 2.minutes