diff --git a/common/src/main/scala/no/ndla/common/model/domain/draft/Draft.scala b/common/src/main/scala/no/ndla/common/model/domain/draft/Draft.scala index 9c56e4c64..16b4cbce8 100644 --- a/common/src/main/scala/no/ndla/common/model/domain/draft/Draft.scala +++ b/common/src/main/scala/no/ndla/common/model/domain/draft/Draft.scala @@ -44,7 +44,8 @@ case class Draft( slug: Option[String], comments: Seq[Comment], priority: Priority, - started: Boolean + started: Boolean, + qualityEvaluation: Option[QualityEvaluation] ) extends Content { def supportedLanguages: Seq[String] = diff --git a/common/src/main/scala/no/ndla/common/model/domain/draft/Grade.scala b/common/src/main/scala/no/ndla/common/model/domain/draft/Grade.scala new file mode 100644 index 000000000..7b1dfc463 --- /dev/null +++ b/common/src/main/scala/no/ndla/common/model/domain/draft/Grade.scala @@ -0,0 +1,33 @@ +/* + * Part of NDLA common + * Copyright (C) 2024 NDLA + * + * See LICENSE + */ + +package no.ndla.common.model.domain.draft + +import com.scalatsi.TypescriptType.{TSLiteralNumber, TSUnion} +import com.scalatsi.{TSNamedType, TSType} +import enumeratum.values.{IntCirceEnum, IntEnum, IntEnumEntry} +import sttp.tapir.Schema +import sttp.tapir.codec.enumeratum.* + +sealed abstract class Grade(val value: Int) extends IntEnumEntry + +object Grade extends IntEnum[Grade] with IntCirceEnum[Grade] { + + val values: IndexedSeq[Grade] = findValues + implicit val schema: Schema[Grade] = schemaForIntEnumEntry[Grade] + + case object One extends Grade(1) + case object Two extends Grade(2) + case object Three extends Grade(3) + case object Four extends Grade(4) + case object Five extends Grade(5) + + implicit val enumTsType: TSNamedType[Grade] = TSType.alias[Grade]( + "Grade", + TSUnion(values.map(e => TSLiteralNumber(e.value))) + ) +} diff --git a/common/src/main/scala/no/ndla/common/model/domain/draft/QualityEvaluation.scala b/common/src/main/scala/no/ndla/common/model/domain/draft/QualityEvaluation.scala new file mode 100644 index 000000000..236aa1404 --- /dev/null +++ b/common/src/main/scala/no/ndla/common/model/domain/draft/QualityEvaluation.scala @@ -0,0 +1,21 @@ +/* + * Part of NDLA common + * Copyright (C) 2024 NDLA + * + * See LICENSE + */ + +package no.ndla.common.model.domain.draft + +import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder} +import io.circe.{Decoder, Encoder} + +case class QualityEvaluation( + grade: Grade, + note: String +) + +object QualityEvaluation { + implicit def encoder: Encoder[QualityEvaluation] = deriveEncoder + implicit def decoder: Decoder[QualityEvaluation] = deriveDecoder +} diff --git a/draft-api/src/main/scala/no/ndla/draftapi/model/api/Article.scala b/draft-api/src/main/scala/no/ndla/draftapi/model/api/Article.scala index c3d1eb81e..628361b92 100644 --- a/draft-api/src/main/scala/no/ndla/draftapi/model/api/Article.scala +++ b/draft-api/src/main/scala/no/ndla/draftapi/model/api/Article.scala @@ -49,7 +49,8 @@ case class Article( @description("Information about comments attached to the article") comments: Seq[Comment], @description("If the article should be prioritized") prioritized: Boolean, @description("If the article should be prioritized. Possible values are prioritized, on-hold, unspecified") priority: String, - @description("If the article has been edited after last status or responsible change") started: Boolean + @description("If the article has been edited after last status or responsible change") started: Boolean, + @description("The quality evaluation of the article. Consist of a score from 1 to 5 and a comment.") qualityEvaluation : Option[QualityEvaluation], ) object Article { diff --git a/draft-api/src/main/scala/no/ndla/draftapi/model/api/NewArticle.scala b/draft-api/src/main/scala/no/ndla/draftapi/model/api/NewArticle.scala index 1e6e9d7c3..de386afd1 100644 --- a/draft-api/src/main/scala/no/ndla/draftapi/model/api/NewArticle.scala +++ b/draft-api/src/main/scala/no/ndla/draftapi/model/api/NewArticle.scala @@ -40,7 +40,8 @@ case class NewArticle( @description("The path to the frontpage article") slug: Option[String], @description("Information about a comment attached to an article") comments: Option[List[NewComment]], @description("If the article should be prioritized") prioritized: Option[Boolean], - @description("If the article should be prioritized. Possible values are prioritized, on-hold, unspecified") priority: Option[String] + @description("If the article should be prioritized. Possible values are prioritized, on-hold, unspecified") priority: Option[String], + @description("The quality evaluation of the article. Consist of a score from 1 to 5 and a comment.") qualityEvaluation : Option[QualityEvaluation], ) // format: on diff --git a/draft-api/src/main/scala/no/ndla/draftapi/model/api/QualityEvaluation.scala b/draft-api/src/main/scala/no/ndla/draftapi/model/api/QualityEvaluation.scala new file mode 100644 index 000000000..a15556b20 --- /dev/null +++ b/draft-api/src/main/scala/no/ndla/draftapi/model/api/QualityEvaluation.scala @@ -0,0 +1,24 @@ +/* + * Part of NDLA draft-api + * Copyright (C) 2024 NDLA + * + * See LICENSE + */ + +package no.ndla.draftapi.model.api + +import io.circe.{Decoder, Encoder} +import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder} +import no.ndla.common.model.domain.draft.Grade +import sttp.tapir.Schema.annotations.description + +@description("Quality evaluation of the article") +case class QualityEvaluation( + @description("The grade (1-5) of the article") grade: Grade, + @description("Note explaining the score") note: String +) + +object QualityEvaluation { + implicit def encoder: Encoder[QualityEvaluation] = deriveEncoder + implicit def decoder: Decoder[QualityEvaluation] = deriveDecoder +} diff --git a/draft-api/src/main/scala/no/ndla/draftapi/model/api/UpdatedArticle.scala b/draft-api/src/main/scala/no/ndla/draftapi/model/api/UpdatedArticle.scala index 0bebeda59..76fd58380 100644 --- a/draft-api/src/main/scala/no/ndla/draftapi/model/api/UpdatedArticle.scala +++ b/draft-api/src/main/scala/no/ndla/draftapi/model/api/UpdatedArticle.scala @@ -45,8 +45,10 @@ case class UpdatedArticle( @description("The path to the frontpage article") slug: Option[String], @description("Information about a comment attached to an article") comments: Option[List[UpdatedComment]], @description("If the article should be prioritized") prioritized: Option[Boolean], - @description("If the article should be prioritized. Possible values are prioritized, on-hold, unspecified") priority: Option[String] -) + @description("If the article should be prioritized. Possible values are prioritized, on-hold, unspecified") priority: Option[String], + @description("The quality evaluation of the article. Consist of a score from 1 to 5 and a comment.") qualityEvaluation : Option[QualityEvaluation], + + ) // format: on object UpdatedArticle { diff --git a/draft-api/src/main/scala/no/ndla/draftapi/service/ConverterService.scala b/draft-api/src/main/scala/no/ndla/draftapi/service/ConverterService.scala index 3537ce08c..e01028e89 100644 --- a/draft-api/src/main/scala/no/ndla/draftapi/service/ConverterService.scala +++ b/draft-api/src/main/scala/no/ndla/draftapi/service/ConverterService.scala @@ -122,7 +122,8 @@ trait ConverterService { slug = newArticle.slug, comments = newCommentToDomain(newArticle.comments.getOrElse(List.empty)), priority = priority, - started = false + started = false, + qualityEvaluation = qualityEvaluationToDomain(newArticle.qualityEvaluation) ) ) } @@ -168,6 +169,11 @@ trait ConverterService { ) } + private[service] def qualityEvaluationToDomain( + newQualityEvaluation: Option[api.QualityEvaluation] + ): Option[common.draft.QualityEvaluation] = + newQualityEvaluation.map(qe => common.draft.QualityEvaluation(grade = qe.grade, note = qe.note)) + private[service] def updatedCommentToDomainNullDocument( updatedComments: List[UpdatedComment] ): Try[Seq[Comment]] = { @@ -403,7 +409,8 @@ trait ConverterService { comments = article.comments.map(toApiComment), prioritized = article.priority == Priority.Prioritized, priority = article.priority.entryName, - started = article.started + started = article.started, + qualityEvaluation = toApiQualityEvaluation(article.qualityEvaluation) ) ) } else { @@ -481,6 +488,12 @@ trait ConverterService { solved = comment.solved ) + private def toApiQualityEvaluation( + qualityEvaluation: Option[common.draft.QualityEvaluation] + ): Option[api.QualityEvaluation] = { + qualityEvaluation.map(qe => api.QualityEvaluation(grade = qe.grade, note = qe.note)) + } + def toApiArticleTag(tag: common.Tag): api.ArticleTag = api.ArticleTag(tag.tags, tag.language) private def toApiRequiredLibrary(required: common.RequiredLibrary): api.RequiredLibrary = { @@ -760,7 +773,8 @@ trait ConverterService { responsible = responsible, slug = article.slug.orElse(toMergeInto.slug), comments = updatedComments, - priority = priority + priority = priority, + qualityEvaluation = qualityEvaluationToDomain(article.qualityEvaluation) ) val articleWithNewContent = article.copy(content = newContent) @@ -885,7 +899,8 @@ trait ConverterService { slug = article.slug, comments = comments, priority = priority, - started = false + started = false, + qualityEvaluation = qualityEvaluationToDomain(article.qualityEvaluation) ) } diff --git a/draft-api/src/test/scala/no/ndla/draftapi/TestData.scala b/draft-api/src/test/scala/no/ndla/draftapi/TestData.scala index 88d86fb9a..1a9858623 100644 --- a/draft-api/src/test/scala/no/ndla/draftapi/TestData.scala +++ b/draft-api/src/test/scala/no/ndla/draftapi/TestData.scala @@ -110,7 +110,8 @@ object TestData { Seq.empty, priority = Priority.Unspecified.entryName, started = false, - prioritized = false + prioritized = false, + qualityEvaluation = None ) val blankUpdatedArticle: UpdatedArticle = api.UpdatedArticle( @@ -140,7 +141,8 @@ object TestData { slug = None, comments = None, prioritized = None, - priority = None + priority = None, + qualityEvaluation = None ) val sampleApiUpdateArticle: UpdatedArticle = blankUpdatedArticle.copy( @@ -227,7 +229,8 @@ object TestData { Seq.empty, false, Priority.Unspecified.entryName, - false + false, + None ) val apiArticleUserTest: api.Article = api.Article( @@ -279,7 +282,8 @@ object TestData { Seq.empty, false, Priority.Unspecified.entryName, - false + false, + None ) val sampleTopicArticle: Draft = Draft( @@ -312,7 +316,8 @@ object TestData { None, Seq.empty, Priority.Unspecified, - false + false, + None ) val sampleArticleWithPublicDomain: Draft = Draft( @@ -345,7 +350,8 @@ object TestData { None, Seq.empty, Priority.Unspecified, - false + false, + None ) val sampleDomainArticle: Draft = Draft( @@ -380,7 +386,8 @@ object TestData { None, Seq.empty, Priority.Unspecified, - false + false, + None ) val newArticle: NewArticle = api.NewArticle( @@ -418,6 +425,7 @@ object TestData { None, None, None, + None, None ) @@ -467,7 +475,8 @@ object TestData { slug = None, comments = Seq.empty, priority = Priority.Unspecified, - started = false + started = false, + qualityEvaluation = None ) val apiArticleWithHtmlFaultV2: api.Article = api.Article( @@ -522,7 +531,8 @@ object TestData { comments = Seq.empty, prioritized = false, priority = Priority.Unspecified.entryName, - started = false + started = false, + qualityEvaluation = None ) val (nodeId, nodeId2) = ("1234", "4321") diff --git a/draft-api/src/test/scala/no/ndla/draftapi/service/ConverterServiceTest.scala b/draft-api/src/test/scala/no/ndla/draftapi/service/ConverterServiceTest.scala index b66f3896a..3b0da75f1 100644 --- a/draft-api/src/test/scala/no/ndla/draftapi/service/ConverterServiceTest.scala +++ b/draft-api/src/test/scala/no/ndla/draftapi/service/ConverterServiceTest.scala @@ -326,7 +326,8 @@ class ConverterServiceTest extends UnitSuite with TestEnvironment { slug = None, comments = Seq.empty, priority = Priority.Unspecified, - started = false + started = false, + qualityEvaluation = None ) val updatedNothing = TestData.blankUpdatedArticle.copy( @@ -369,7 +370,8 @@ class ConverterServiceTest extends UnitSuite with TestEnvironment { slug = None, comments = Seq.empty, priority = Priority.Unspecified, - started = false + started = false, + qualityEvaluation = None ) val expectedArticle = Draft( @@ -402,7 +404,8 @@ class ConverterServiceTest extends UnitSuite with TestEnvironment { slug = None, comments = Seq.empty, priority = Priority.Unspecified, - started = false + started = false, + qualityEvaluation = None ) val updatedEverything = TestData.blankUpdatedArticle.copy( @@ -463,7 +466,8 @@ class ConverterServiceTest extends UnitSuite with TestEnvironment { slug = None, comments = Seq.empty, priority = Priority.Unspecified, - started = false + started = false, + qualityEvaluation = None ) val expectedArticle = Draft( @@ -496,7 +500,8 @@ class ConverterServiceTest extends UnitSuite with TestEnvironment { slug = None, comments = Seq.empty, priority = Priority.Unspecified, - started = false + started = false, + qualityEvaluation = None ) val updatedEverything = TestData.blankUpdatedArticle.copy( @@ -1095,7 +1100,8 @@ class ConverterServiceTest extends UnitSuite with TestEnvironment { slug = Some("kjempe-slug"), comments = Seq.empty, priority = Priority.Unspecified, - started = false + started = false, + qualityEvaluation = None ) val article = common.model.domain.article.Article( id = Some(articleId), diff --git a/draft-api/src/test/scala/no/ndla/draftapi/service/StateTransitionRulesTest.scala b/draft-api/src/test/scala/no/ndla/draftapi/service/StateTransitionRulesTest.scala index 81d499879..a11d715ba 100644 --- a/draft-api/src/test/scala/no/ndla/draftapi/service/StateTransitionRulesTest.scala +++ b/draft-api/src/test/scala/no/ndla/draftapi/service/StateTransitionRulesTest.scala @@ -357,7 +357,8 @@ class StateTransitionRulesTest extends UnitSuite with TestEnvironment { slug = None, comments = Seq.empty, priority = Priority.Unspecified, - started = false + started = false, + qualityEvaluation = None ) val article = common.article.Article( id = Some(articleId), @@ -477,7 +478,8 @@ class StateTransitionRulesTest extends UnitSuite with TestEnvironment { slug = None, comments = Seq.empty, priority = Priority.Unspecified, - started = false + started = false, + qualityEvaluation = None ) val status = common.Status(PLANNED, Set.empty) val transitionsToTest = StateTransitionRules.StateTransitions.filter(_.to == PUBLISHED) @@ -532,7 +534,8 @@ class StateTransitionRulesTest extends UnitSuite with TestEnvironment { slug = None, comments = Seq.empty, priority = Priority.Unspecified, - started = false + started = false, + qualityEvaluation = None ) val status = common.Status(PLANNED, Set.empty) val transitionsToTest = StateTransitionRules.StateTransitions.filter(_.to == ARCHIVED) @@ -591,7 +594,8 @@ class StateTransitionRulesTest extends UnitSuite with TestEnvironment { slug = None, comments = Seq.empty, priority = Priority.Unspecified, - started = false + started = false, + qualityEvaluation = None ) val status = common.Status(PLANNED, Set.empty) val transitionsToTest = StateTransitionRules.StateTransitions.filter(_.to == UNPUBLISHED) @@ -652,7 +656,8 @@ class StateTransitionRulesTest extends UnitSuite with TestEnvironment { slug = None, comments = Seq.empty, priority = Priority.Unspecified, - started = false + started = false, + qualityEvaluation = None ) val status = common.Status(PUBLISHED, Set.empty) val transitionToTest: StateTransition = PUBLISHED -> IN_PROGRESS diff --git a/integration-tests/src/test/scala/no/ndla/integrationtests/draftapi/articleapi/ArticleApiClientTest.scala b/integration-tests/src/test/scala/no/ndla/integrationtests/draftapi/articleapi/ArticleApiClientTest.scala index cdfd19510..cfa72c9a5 100644 --- a/integration-tests/src/test/scala/no/ndla/integrationtests/draftapi/articleapi/ArticleApiClientTest.scala +++ b/integration-tests/src/test/scala/no/ndla/integrationtests/draftapi/articleapi/ArticleApiClientTest.scala @@ -124,7 +124,8 @@ class ArticleApiClientTest slug = None, comments = Seq.empty, priority = Priority.Unspecified, - started = false + started = false, + qualityEvaluation = None ) val exampleToken = diff --git a/search-api/src/test/scala/no/ndla/searchapi/TestData.scala b/search-api/src/test/scala/no/ndla/searchapi/TestData.scala index ff8730bd0..f9f5c1a12 100644 --- a/search-api/src/test/scala/no/ndla/searchapi/TestData.scala +++ b/search-api/src/test/scala/no/ndla/searchapi/TestData.scala @@ -550,7 +550,8 @@ object TestData { slug = None, comments = Seq.empty, priority = Priority.Unspecified, - started = false + started = false, + qualityEvaluation = None ) val draftStatus: Status = Status(DraftStatus.PLANNED, Set.empty) @@ -611,7 +612,8 @@ object TestData { slug = None, comments = Seq.empty, priority = Priority.Unspecified, - started = false + started = false, + qualityEvaluation = None ) val sampleDraftWithByNcSa: Draft = sampleDraftWithPublicDomain.copy(copyright = Some(draftByNcSaCopyright)) diff --git a/typescript/types-backend/draft-api.ts b/typescript/types-backend/draft-api.ts index c17ea98cc..cfed4517b 100644 --- a/typescript/types-backend/draft-api.ts +++ b/typescript/types-backend/draft-api.ts @@ -2,6 +2,8 @@ export type Availability = ("everyone" | "teacher") +export type Grade = (1 | 2 | 3 | 4 | 5) + export interface IArticle { id: number oldNdlaUrl?: string @@ -35,6 +37,7 @@ export interface IArticle { prioritized: boolean priority: string started: boolean + qualityEvaluation?: IQualityEvaluation } export interface IArticleContent { @@ -162,6 +165,7 @@ export interface INewArticle { comments?: INewComment[] prioritized?: boolean priority?: string + qualityEvaluation?: IQualityEvaluation } export interface INewArticleMetaImage { @@ -174,6 +178,11 @@ export interface INewComment { isOpen?: boolean } +export interface IQualityEvaluation { + grade: Grade + note: string +} + export interface IRelatedContentLink { title: string url: string @@ -241,6 +250,7 @@ export interface IUpdatedArticle { comments?: IUpdatedComment[] prioritized?: boolean priority?: string + qualityEvaluation?: IQualityEvaluation } export interface IUpdatedComment {