From eb8c5588e95fabf118b636b886176e513072dd81 Mon Sep 17 00:00:00 2001 From: Gunnar Velle Date: Thu, 29 Jan 2026 14:53:48 +0100 Subject: [PATCH 1/6] Get all responsibles for articles --- .../draftapi/controller/DraftController.scala | 16 ++++++++++++++++ .../model/search/SearchableArticle.scala | 2 ++ .../service/search/ArticleIndexService.scala | 1 + .../service/search/ArticleSearchService.scala | 19 +++++++++++++++++++ .../search/SearchConverterService.scala | 1 + .../SearchableArticleSerializerTest.scala | 2 ++ 6 files changed, 41 insertions(+) diff --git a/draft-api/src/main/scala/no/ndla/draftapi/controller/DraftController.scala b/draft-api/src/main/scala/no/ndla/draftapi/controller/DraftController.scala index 95a78ee78b..3152cae33e 100644 --- a/draft-api/src/main/scala/no/ndla/draftapi/controller/DraftController.scala +++ b/draft-api/src/main/scala/no/ndla/draftapi/controller/DraftController.scala @@ -123,6 +123,7 @@ class DraftController(using migrateOutdatedGreps, addNotes, deleteCurrentRevision, + getResponsibles, ) /** Does a scroll with [[ArticleSearchService]] If no scrollId is specified execute the function @orFunction in the @@ -672,4 +673,19 @@ class DraftController(using .serverLogicPure { _ => articleId => writeService.deleteCurrentRevision(articleId).handleErrorsOrOk } + + def getResponsibles: ServerEndpoint[Any, Eff] = endpoint + .get + .in("responsibles" / "list") + .summary("Get list of responsibles for drafts") + .description("Get list of responsibles for drafts") + .out(jsonBody[Seq[String]]) + .errorOut(errorOutputsFor(401, 403)) + .requirePermission(DRAFT_API_WRITE) + .serverLogicPure { _ => _ => + articleSearchService.getResponsibles() match { + case Success(resp) => Right(resp) + case Failure(ex) => errorHandling.returnLeftError(ex) + } + } } diff --git a/draft-api/src/main/scala/no/ndla/draftapi/model/search/SearchableArticle.scala b/draft-api/src/main/scala/no/ndla/draftapi/model/search/SearchableArticle.scala index 964cf91332..951bd08878 100644 --- a/draft-api/src/main/scala/no/ndla/draftapi/model/search/SearchableArticle.scala +++ b/draft-api/src/main/scala/no/ndla/draftapi/model/search/SearchableArticle.scala @@ -12,6 +12,7 @@ import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder} import io.circe.{Decoder, Encoder} import no.ndla.common.model.NDLADate import no.ndla.common.model.api.search.{ArticleTrait, SearchableLanguageList, SearchableLanguageValues} +import no.ndla.common.model.domain.Responsible case class SearchableArticle( id: Long, @@ -31,6 +32,7 @@ case class SearchableArticle( grepCodes: Seq[String], status: SearchableStatus, traits: List[ArticleTrait], + responsible: Option[Responsible], ) object SearchableArticle { diff --git a/draft-api/src/main/scala/no/ndla/draftapi/service/search/ArticleIndexService.scala b/draft-api/src/main/scala/no/ndla/draftapi/service/search/ArticleIndexService.scala index cb4f3bb435..d432e7f596 100644 --- a/draft-api/src/main/scala/no/ndla/draftapi/service/search/ArticleIndexService.scala +++ b/draft-api/src/main/scala/no/ndla/draftapi/service/search/ArticleIndexService.scala @@ -53,6 +53,7 @@ class ArticleIndexService(using keywordField("users"), keywordField("grepCodes"), keywordField("traits"), + ObjectField("responsible", properties = Seq(keywordField("responsibleId"), dateField("lastUpdated"))), ObjectField("status", properties = Seq(keywordField("current"), keywordField("other"))), ) val dynamics = generateLanguageSupportedFieldList("title", keepRaw = true) ++ diff --git a/draft-api/src/main/scala/no/ndla/draftapi/service/search/ArticleSearchService.scala b/draft-api/src/main/scala/no/ndla/draftapi/service/search/ArticleSearchService.scala index c0590c5550..c71bcf152c 100644 --- a/draft-api/src/main/scala/no/ndla/draftapi/service/search/ArticleSearchService.scala +++ b/draft-api/src/main/scala/no/ndla/draftapi/service/search/ArticleSearchService.scala @@ -9,6 +9,7 @@ package no.ndla.draftapi.service.search import com.sksamuel.elastic4s.ElasticDsl.* +import com.sksamuel.elastic4s.requests.searches.aggs.responses.bucket.Terms import com.sksamuel.elastic4s.requests.searches.queries.compound.BoolQuery import com.typesafe.scalalogging.StrictLogging import no.ndla.draftapi.DraftApiProperties @@ -130,6 +131,24 @@ class ArticleSearchService(using } } + def getResponsibles(): Try[Seq[String]] = { + val searchQuery = search(searchIndex) + .size(1000) + .aggs(termsAgg("responsiblesAgg", "responsible.responsibleId").size(1000)) + + e4sClient.execute(searchQuery) match { + case Success(response) => + val agg = response.result.aggs.result[Terms]("responsiblesAgg") + val responsibles = agg + .buckets + .map { bucket => + bucket.key + } + Success(responsibles) + case Failure(ex) => errorHandler(ex) + } + } + override def scheduleIndexDocuments(): Unit = { implicit val ec: ExecutionContextExecutorService = ExecutionContext.fromExecutorService(Executors.newSingleThreadExecutor) diff --git a/draft-api/src/main/scala/no/ndla/draftapi/service/search/SearchConverterService.scala b/draft-api/src/main/scala/no/ndla/draftapi/service/search/SearchConverterService.scala index f77a489f4c..5714bf6688 100644 --- a/draft-api/src/main/scala/no/ndla/draftapi/service/search/SearchConverterService.scala +++ b/draft-api/src/main/scala/no/ndla/draftapi/service/search/SearchConverterService.scala @@ -60,6 +60,7 @@ class SearchConverterService(using converterService: ConverterService) extends S grepCodes = ai.grepCodes, status = SearchableStatus(ai.status.current, ai.status.other), traits = ai.traits, + responsible = ai.responsible, ) } diff --git a/draft-api/src/test/scala/no/ndla/draftapi/model/search/SearchableArticleSerializerTest.scala b/draft-api/src/test/scala/no/ndla/draftapi/model/search/SearchableArticleSerializerTest.scala index b9a0c111a3..081ecfc829 100644 --- a/draft-api/src/test/scala/no/ndla/draftapi/model/search/SearchableArticleSerializerTest.scala +++ b/draft-api/src/test/scala/no/ndla/draftapi/model/search/SearchableArticleSerializerTest.scala @@ -12,6 +12,7 @@ import no.ndla.common.CirceUtil import no.ndla.common.model.NDLADate import no.ndla.common.model.api.search.ArticleTrait.Video import no.ndla.common.model.api.search.{LanguageValue, SearchableLanguageList, SearchableLanguageValues} +import no.ndla.common.model.domain.Responsible import no.ndla.common.model.domain.draft.DraftStatus import no.ndla.draftapi.{TestEnvironment, UnitSuite} import no.ndla.mapping.License @@ -38,6 +39,7 @@ class SearchableArticleSerializerTest extends UnitSuite with TestEnvironment { grepCodes = Seq("KM1337", "KM5432"), status = SearchableStatus(DraftStatus.PUBLISHED, Set.empty), traits = List(Video), + responsible = Some(Responsible("respId12345", NDLADate.of(2018, 2, 22, 13, 0, 51))), ) test("That deserialization and serialization of SearchableArticle works as expected") { From 0daa0e2ef2bd32f6cfbace91acff2da4d206859d Mon Sep 17 00:00:00 2001 From: Gunnar Velle Date: Tue, 3 Feb 2026 13:48:11 +0100 Subject: [PATCH 2/6] Replace search with db query --- .../draftapi/controller/DraftController.scala | 2 +- .../model/search/SearchableArticle.scala | 2 -- .../draftapi/repository/DraftRepository.scala | 10 ++++++++++ .../ndla/draftapi/service/ReadService.scala | 10 ++++++++++ .../service/search/ArticleIndexService.scala | 1 - .../service/search/ArticleSearchService.scala | 19 ------------------- .../search/SearchConverterService.scala | 1 - .../SearchableArticleSerializerTest.scala | 1 - 8 files changed, 21 insertions(+), 25 deletions(-) diff --git a/draft-api/src/main/scala/no/ndla/draftapi/controller/DraftController.scala b/draft-api/src/main/scala/no/ndla/draftapi/controller/DraftController.scala index 3152cae33e..c28fef162c 100644 --- a/draft-api/src/main/scala/no/ndla/draftapi/controller/DraftController.scala +++ b/draft-api/src/main/scala/no/ndla/draftapi/controller/DraftController.scala @@ -683,7 +683,7 @@ class DraftController(using .errorOut(errorOutputsFor(401, 403)) .requirePermission(DRAFT_API_WRITE) .serverLogicPure { _ => _ => - articleSearchService.getResponsibles() match { + readService.getAllResponsibles match { case Success(resp) => Right(resp) case Failure(ex) => errorHandling.returnLeftError(ex) } diff --git a/draft-api/src/main/scala/no/ndla/draftapi/model/search/SearchableArticle.scala b/draft-api/src/main/scala/no/ndla/draftapi/model/search/SearchableArticle.scala index 951bd08878..964cf91332 100644 --- a/draft-api/src/main/scala/no/ndla/draftapi/model/search/SearchableArticle.scala +++ b/draft-api/src/main/scala/no/ndla/draftapi/model/search/SearchableArticle.scala @@ -12,7 +12,6 @@ import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder} import io.circe.{Decoder, Encoder} import no.ndla.common.model.NDLADate import no.ndla.common.model.api.search.{ArticleTrait, SearchableLanguageList, SearchableLanguageValues} -import no.ndla.common.model.domain.Responsible case class SearchableArticle( id: Long, @@ -32,7 +31,6 @@ case class SearchableArticle( grepCodes: Seq[String], status: SearchableStatus, traits: List[ArticleTrait], - responsible: Option[Responsible], ) object SearchableArticle { diff --git a/draft-api/src/main/scala/no/ndla/draftapi/repository/DraftRepository.scala b/draft-api/src/main/scala/no/ndla/draftapi/repository/DraftRepository.scala index 3df558ab10..b4a50c0ccf 100644 --- a/draft-api/src/main/scala/no/ndla/draftapi/repository/DraftRepository.scala +++ b/draft-api/src/main/scala/no/ndla/draftapi/repository/DraftRepository.scala @@ -438,4 +438,14 @@ class DraftRepository(using draftErrorHelpers: DraftErrorHelpers, clock: Clock) sq.map(rs => rs.long("count")).runSingle().map(_.exists(_ > 0)) } + def getAllResponsibles(using session: DBSession): Try[Seq[String]] = { + val ar = DBArticle.syntax("ar") + tsql""" + select distinct (ar.document->'responsible'->>'responsibleId) as responsibleId + from ${DBArticle.as(ar)} + where ar.document is not NULL + and (ar.document->'responsible') is not null + """.map(rs => rs.string("responsibleId")).runList() + } + } diff --git a/draft-api/src/main/scala/no/ndla/draftapi/service/ReadService.scala b/draft-api/src/main/scala/no/ndla/draftapi/service/ReadService.scala index 1ae1134c48..cccd9418e8 100644 --- a/draft-api/src/main/scala/no/ndla/draftapi/service/ReadService.scala +++ b/draft-api/src/main/scala/no/ndla/draftapi/service/ReadService.scala @@ -226,4 +226,14 @@ class ReadService(using Success(ArticleRevisionHistoryDTO(articles, canDeleteCurrentRevision)) } + + def getAllResponsibles: Try[Seq[String]] = boundary { + dbUtility.readOnly { implicit session => + draftRepository.getAllResponsibles match { + case Failure(exception) => boundary.break(Failure(exception)) + case Success(value) => Success(value) + } + } + } + } diff --git a/draft-api/src/main/scala/no/ndla/draftapi/service/search/ArticleIndexService.scala b/draft-api/src/main/scala/no/ndla/draftapi/service/search/ArticleIndexService.scala index d432e7f596..cb4f3bb435 100644 --- a/draft-api/src/main/scala/no/ndla/draftapi/service/search/ArticleIndexService.scala +++ b/draft-api/src/main/scala/no/ndla/draftapi/service/search/ArticleIndexService.scala @@ -53,7 +53,6 @@ class ArticleIndexService(using keywordField("users"), keywordField("grepCodes"), keywordField("traits"), - ObjectField("responsible", properties = Seq(keywordField("responsibleId"), dateField("lastUpdated"))), ObjectField("status", properties = Seq(keywordField("current"), keywordField("other"))), ) val dynamics = generateLanguageSupportedFieldList("title", keepRaw = true) ++ diff --git a/draft-api/src/main/scala/no/ndla/draftapi/service/search/ArticleSearchService.scala b/draft-api/src/main/scala/no/ndla/draftapi/service/search/ArticleSearchService.scala index c71bcf152c..c0590c5550 100644 --- a/draft-api/src/main/scala/no/ndla/draftapi/service/search/ArticleSearchService.scala +++ b/draft-api/src/main/scala/no/ndla/draftapi/service/search/ArticleSearchService.scala @@ -9,7 +9,6 @@ package no.ndla.draftapi.service.search import com.sksamuel.elastic4s.ElasticDsl.* -import com.sksamuel.elastic4s.requests.searches.aggs.responses.bucket.Terms import com.sksamuel.elastic4s.requests.searches.queries.compound.BoolQuery import com.typesafe.scalalogging.StrictLogging import no.ndla.draftapi.DraftApiProperties @@ -131,24 +130,6 @@ class ArticleSearchService(using } } - def getResponsibles(): Try[Seq[String]] = { - val searchQuery = search(searchIndex) - .size(1000) - .aggs(termsAgg("responsiblesAgg", "responsible.responsibleId").size(1000)) - - e4sClient.execute(searchQuery) match { - case Success(response) => - val agg = response.result.aggs.result[Terms]("responsiblesAgg") - val responsibles = agg - .buckets - .map { bucket => - bucket.key - } - Success(responsibles) - case Failure(ex) => errorHandler(ex) - } - } - override def scheduleIndexDocuments(): Unit = { implicit val ec: ExecutionContextExecutorService = ExecutionContext.fromExecutorService(Executors.newSingleThreadExecutor) diff --git a/draft-api/src/main/scala/no/ndla/draftapi/service/search/SearchConverterService.scala b/draft-api/src/main/scala/no/ndla/draftapi/service/search/SearchConverterService.scala index 5714bf6688..f77a489f4c 100644 --- a/draft-api/src/main/scala/no/ndla/draftapi/service/search/SearchConverterService.scala +++ b/draft-api/src/main/scala/no/ndla/draftapi/service/search/SearchConverterService.scala @@ -60,7 +60,6 @@ class SearchConverterService(using converterService: ConverterService) extends S grepCodes = ai.grepCodes, status = SearchableStatus(ai.status.current, ai.status.other), traits = ai.traits, - responsible = ai.responsible, ) } diff --git a/draft-api/src/test/scala/no/ndla/draftapi/model/search/SearchableArticleSerializerTest.scala b/draft-api/src/test/scala/no/ndla/draftapi/model/search/SearchableArticleSerializerTest.scala index 081ecfc829..2a46f38def 100644 --- a/draft-api/src/test/scala/no/ndla/draftapi/model/search/SearchableArticleSerializerTest.scala +++ b/draft-api/src/test/scala/no/ndla/draftapi/model/search/SearchableArticleSerializerTest.scala @@ -39,7 +39,6 @@ class SearchableArticleSerializerTest extends UnitSuite with TestEnvironment { grepCodes = Seq("KM1337", "KM5432"), status = SearchableStatus(DraftStatus.PUBLISHED, Set.empty), traits = List(Video), - responsible = Some(Responsible("respId12345", NDLADate.of(2018, 2, 22, 13, 0, 51))), ) test("That deserialization and serialization of SearchableArticle works as expected") { From 1f0fb18085b4e46ff575d090d111f011507360f6 Mon Sep 17 00:00:00 2001 From: Gunnar Velle Date: Tue, 3 Feb 2026 14:07:05 +0100 Subject: [PATCH 3/6] Filter out nulls --- .../scala/no/ndla/draftapi/repository/DraftRepository.scala | 5 +++-- .../model/search/SearchableArticleSerializerTest.scala | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/draft-api/src/main/scala/no/ndla/draftapi/repository/DraftRepository.scala b/draft-api/src/main/scala/no/ndla/draftapi/repository/DraftRepository.scala index b4a50c0ccf..7e075541ee 100644 --- a/draft-api/src/main/scala/no/ndla/draftapi/repository/DraftRepository.scala +++ b/draft-api/src/main/scala/no/ndla/draftapi/repository/DraftRepository.scala @@ -441,10 +441,11 @@ class DraftRepository(using draftErrorHelpers: DraftErrorHelpers, clock: Clock) def getAllResponsibles(using session: DBSession): Try[Seq[String]] = { val ar = DBArticle.syntax("ar") tsql""" - select distinct (ar.document->'responsible'->>'responsibleId) as responsibleId + select distinct (ar.document -> 'responsible' ->> 'responsibleId') as responsibleId from ${DBArticle.as(ar)} where ar.document is not NULL - and (ar.document->'responsible') is not null + and (ar.document -> 'responsible') is not null + and (ar.document -> 'responsible' ->> 'responsibleId') is not null """.map(rs => rs.string("responsibleId")).runList() } diff --git a/draft-api/src/test/scala/no/ndla/draftapi/model/search/SearchableArticleSerializerTest.scala b/draft-api/src/test/scala/no/ndla/draftapi/model/search/SearchableArticleSerializerTest.scala index 2a46f38def..b9a0c111a3 100644 --- a/draft-api/src/test/scala/no/ndla/draftapi/model/search/SearchableArticleSerializerTest.scala +++ b/draft-api/src/test/scala/no/ndla/draftapi/model/search/SearchableArticleSerializerTest.scala @@ -12,7 +12,6 @@ import no.ndla.common.CirceUtil import no.ndla.common.model.NDLADate import no.ndla.common.model.api.search.ArticleTrait.Video import no.ndla.common.model.api.search.{LanguageValue, SearchableLanguageList, SearchableLanguageValues} -import no.ndla.common.model.domain.Responsible import no.ndla.common.model.domain.draft.DraftStatus import no.ndla.draftapi.{TestEnvironment, UnitSuite} import no.ndla.mapping.License From 5a78876346f7f2ce28ea0ad3efeb8b211d46c9cd Mon Sep 17 00:00:00 2001 From: Gunnar Velle Date: Tue, 3 Feb 2026 15:39:11 +0100 Subject: [PATCH 4/6] No boundrys --- .../scala/no/ndla/draftapi/service/ReadService.scala | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/draft-api/src/main/scala/no/ndla/draftapi/service/ReadService.scala b/draft-api/src/main/scala/no/ndla/draftapi/service/ReadService.scala index cccd9418e8..c445f330b2 100644 --- a/draft-api/src/main/scala/no/ndla/draftapi/service/ReadService.scala +++ b/draft-api/src/main/scala/no/ndla/draftapi/service/ReadService.scala @@ -227,12 +227,10 @@ class ReadService(using Success(ArticleRevisionHistoryDTO(articles, canDeleteCurrentRevision)) } - def getAllResponsibles: Try[Seq[String]] = boundary { - dbUtility.readOnly { implicit session => - draftRepository.getAllResponsibles match { - case Failure(exception) => boundary.break(Failure(exception)) - case Success(value) => Success(value) - } + def getAllResponsibles: Try[Seq[String]] = dbUtility.readOnly { implicit session => + draftRepository.getAllResponsibles match { + case Failure(exception) => Failure(exception) + case Success(value) => Success(value) } } From bf31c6f00c2da33aa469b511003f0bad0fe7dc1d Mon Sep 17 00:00:00 2001 From: Gunnar Velle Date: Wed, 4 Feb 2026 14:05:40 +0100 Subject: [PATCH 5/6] Add materialized view to calculate responsibleid --- .../V83__ResponsibleIdsMaterializedView.sql | 2 ++ .../ndla/draftapi/repository/DraftRepository.scala | 13 +++++-------- .../no/ndla/draftapi/service/WriteService.scala | 6 ++++-- 3 files changed, 11 insertions(+), 10 deletions(-) create mode 100644 draft-api/src/main/resources/no/ndla/draftapi/db/migration/V83__ResponsibleIdsMaterializedView.sql diff --git a/draft-api/src/main/resources/no/ndla/draftapi/db/migration/V83__ResponsibleIdsMaterializedView.sql b/draft-api/src/main/resources/no/ndla/draftapi/db/migration/V83__ResponsibleIdsMaterializedView.sql new file mode 100644 index 0000000000..5096aaee79 --- /dev/null +++ b/draft-api/src/main/resources/no/ndla/draftapi/db/migration/V83__ResponsibleIdsMaterializedView.sql @@ -0,0 +1,2 @@ +CREATE MATERIALIZED VIEW responsible_view AS SELECT distinct ("document" -> 'responsible' ->> 'responsibleId') as responsibleId FROM articledata where ("document" -> 'responsible' ->> 'responsibleId') IS NOT NULL; +CREATE UNIQUE INDEX responsibleId_idx ON responsible_view(responsibleId); \ No newline at end of file diff --git a/draft-api/src/main/scala/no/ndla/draftapi/repository/DraftRepository.scala b/draft-api/src/main/scala/no/ndla/draftapi/repository/DraftRepository.scala index 7e075541ee..07e3eecf2d 100644 --- a/draft-api/src/main/scala/no/ndla/draftapi/repository/DraftRepository.scala +++ b/draft-api/src/main/scala/no/ndla/draftapi/repository/DraftRepository.scala @@ -438,15 +438,12 @@ class DraftRepository(using draftErrorHelpers: DraftErrorHelpers, clock: Clock) sq.map(rs => rs.long("count")).runSingle().map(_.exists(_ > 0)) } + def refreshResponsibleView(using session: DBSession): Try[Unit] = { + tsql"refresh materialized view responsible_view".update().map(_ => ()) + } + def getAllResponsibles(using session: DBSession): Try[Seq[String]] = { - val ar = DBArticle.syntax("ar") - tsql""" - select distinct (ar.document -> 'responsible' ->> 'responsibleId') as responsibleId - from ${DBArticle.as(ar)} - where ar.document is not NULL - and (ar.document -> 'responsible') is not null - and (ar.document -> 'responsible' ->> 'responsibleId') is not null - """.map(rs => rs.string("responsibleId")).runList() + tsql"""select responsibleId from responsible_view""".map(rs => rs.string("responsibleId")).runList() } } diff --git a/draft-api/src/main/scala/no/ndla/draftapi/service/WriteService.scala b/draft-api/src/main/scala/no/ndla/draftapi/service/WriteService.scala index 62be524893..9bd43a4595 100644 --- a/draft-api/src/main/scala/no/ndla/draftapi/service/WriteService.scala +++ b/draft-api/src/main/scala/no/ndla/draftapi/service/WriteService.scala @@ -336,7 +336,7 @@ class WriteService(using statusWasUpdated: Boolean, updatedApiArticle: api.UpdatedArticleDTO, shouldNotAutoUpdateStatus: Boolean, - ): Draft = { + )(using DBSession): Draft = { val isAutomaticResponsibleChange = updatedApiArticle.responsibleId match { case UpdateWith(_) => false case _ => true @@ -353,7 +353,9 @@ class WriteService(using draft.copy(started = true) } else { val responsibleIdWasUpdated = hasResponsibleBeenUpdated(draft, oldDraft) - + if (responsibleIdWasUpdated) { + draftRepository.refreshResponsibleView: Unit + } val shouldReset = statusWasUpdated && !isAutomaticStatusChange || responsibleIdWasUpdated draft.copy(started = !shouldReset) } From 3957ee1a9cae9c0d3c0c793d7facbd23ac7e5ade Mon Sep 17 00:00:00 2001 From: Gunnar Velle Date: Fri, 20 Feb 2026 09:45:29 +0100 Subject: [PATCH 6/6] Add log statement if failure to update view --- .../scala/no/ndla/draftapi/service/WriteService.scala | 9 ++++++++- .../no/ndla/draftapi/service/WriteServiceTest.scala | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/draft-api/src/main/scala/no/ndla/draftapi/service/WriteService.scala b/draft-api/src/main/scala/no/ndla/draftapi/service/WriteService.scala index 9bd43a4595..c866f8fdb9 100644 --- a/draft-api/src/main/scala/no/ndla/draftapi/service/WriteService.scala +++ b/draft-api/src/main/scala/no/ndla/draftapi/service/WriteService.scala @@ -354,7 +354,14 @@ class WriteService(using } else { val responsibleIdWasUpdated = hasResponsibleBeenUpdated(draft, oldDraft) if (responsibleIdWasUpdated) { - draftRepository.refreshResponsibleView: Unit + draftRepository + .refreshResponsibleView + .recover { case ex => + logger.error( + s"Failed to refresh responsible view after responsible change for article ${draft.id.getOrElse("unknown")}", + ex, + ) + }: Unit } val shouldReset = statusWasUpdated && !isAutomaticStatusChange || responsibleIdWasUpdated draft.copy(started = !shouldReset) diff --git a/draft-api/src/test/scala/no/ndla/draftapi/service/WriteServiceTest.scala b/draft-api/src/test/scala/no/ndla/draftapi/service/WriteServiceTest.scala index 7cb738d3c5..b9d095c91c 100644 --- a/draft-api/src/test/scala/no/ndla/draftapi/service/WriteServiceTest.scala +++ b/draft-api/src/test/scala/no/ndla/draftapi/service/WriteServiceTest.scala @@ -1278,6 +1278,7 @@ class WriteServiceTest extends UnitSuite with TestEnvironment { .copy(revision = 1, title = Some("updated title"), language = Some("nb"), responsibleId = UpdateWith("heiho")) when(draftRepository.slugExists(any, any)(using any)).thenReturn(Success(false)) when(draftRepository.withId(eqTo(existing.id.get))(using any)).thenReturn(Success(Some(existing))) + when(draftRepository.refreshResponsibleView(using any)).thenReturn(Success(())) val result = service.updateArticle(existing.id.get, updatedArticle, TestData.userWithWriteAccess).get result.started should be(false)