diff --git a/src/main/resources/schema/schema_v37.sql b/src/main/resources/schema/schema_v37.sql new file mode 100644 index 00000000..8fe4cd45 --- /dev/null +++ b/src/main/resources/schema/schema_v37.sql @@ -0,0 +1,2 @@ +ALTER TABLE system_columns + ADD COLUMN decimal_digits INT; diff --git a/src/main/resources/swagger.json b/src/main/resources/swagger.json index 754385a8..465e4291 100644 --- a/src/main/resources/swagger.json +++ b/src/main/resources/swagger.json @@ -3887,6 +3887,24 @@ "type": "boolean", "description": "Flag if the member columns should be displayed in the frontend table view (They are always displayed in entity view).", "example": true + }, + "minLength": { + "type": "integer", + "description": "Minimum length of the value, only for text columns", + "format": "int32", + "example": 1 + }, + "maxLength": { + "type": "integer", + "description": "Maximum length of the value, only for text columns", + "format": "int32", + "example": 255 + }, + "decimalDigits": { + "type": "integer", + "description": "Number of decimal digits, only for numeric columns. Values between 0 and 10 are allowed. If not set, the default value is 3.", + "format": "int32", + "example": 2 } }, "description": "A column object, which is used to create a new column.", @@ -3925,7 +3943,8 @@ 2 ], "formatPattern": "city {{1}} has {{2}} inhabitants", - "showMemberColumns": true + "showMemberColumns": true, + "decimalDigits": 6 } ] }, diff --git a/src/main/scala/com/campudus/tableaux/arguments.scala b/src/main/scala/com/campudus/tableaux/arguments.scala index 2d0646fe..e3dc4a38 100644 --- a/src/main/scala/com/campudus/tableaux/arguments.scala +++ b/src/main/scala/com/campudus/tableaux/arguments.scala @@ -109,27 +109,6 @@ object ArgumentChecker { } } - def isDefined(options: Seq[Option[_]], trys: Seq[Try[Option[_]]], name: String): ArgumentCheck[Unit] = { - val empty = !options.exists(_.isDefined) - val noneHaveBeenDefined = trys.forall(_.isFailure) - - if (empty && noneHaveBeenDefined) { - FailArg(InvalidRequestException(s"None of these options has a (valid) value. ($name)")) - } else { - OkArg(()) - } - - } - - def nullableValuesAreDefined(trys: Seq[Try[Option[_]]], name: String = ""): ArgumentCheck[Unit] = { - val allHaveBeenSupplied = trys.forall(_.isSuccess) - if (allHaveBeenSupplied) { - FailArg(InvalidRequestException(s"None of these options has a (valid) value. ($name)")) - } else { - OkArg(()) - } - } - def oneOf[A](x: => A, list: List[A], name: String): ArgumentCheck[A] = { if (list.contains(x)) { OkArg(x) diff --git a/src/main/scala/com/campudus/tableaux/controller/StructureController.scala b/src/main/scala/com/campudus/tableaux/controller/StructureController.scala index 615082bd..cac2502f 100644 --- a/src/main/scala/com/campudus/tableaux/controller/StructureController.scala +++ b/src/main/scala/com/campudus/tableaux/controller/StructureController.scala @@ -445,18 +445,19 @@ class StructureController( tableId: TableId, columnId: ColumnId, columnName: Option[String], - ordering: Option[Ordering], - kind: Option[TableauxDbType], - identifier: Option[Boolean], - displayInfos: Option[Seq[DisplayInfo]], - countryCodes: Option[Seq[String]], - separator: Option[Boolean], - attributes: Option[JsonObject], - rules: Option[JsonArray], - hidden: Option[Boolean], - maxLengthTry: Try[Option[Int]], - minLengthTry: Try[Option[Int]], - showMemberColumns: Option[Boolean] + ordering: Option[Ordering] = None, + kind: Option[TableauxDbType] = None, + identifier: Option[Boolean] = None, + displayInfos: Option[Seq[DisplayInfo]] = None, + countryCodes: Option[Seq[String]] = None, + separator: Option[Boolean] = None, + attributes: Option[JsonObject] = None, + rules: Option[JsonArray] = None, + hidden: Option[Boolean] = None, + maxLength: Option[Int] = None, + minLength: Option[Int] = None, + showMemberColumns: Option[Boolean] = None, + decimalDigits: Option[Int] = None )(implicit user: TableauxUser): Future[ColumnType[_]] = { checkArguments( greaterZero(tableId), @@ -473,20 +474,16 @@ class StructureController( attributes, rules, hidden, - showMemberColumns + maxLength, + minLength, + showMemberColumns, + decimalDigits ), - Seq(maxLengthTry, minLengthTry), - "name, ordering, kind, identifier, displayInfos, countryCodes, separator, attributes, rules, hidden, maxLength, minLength, showMemberColumns" + "name, ordering, kind, identifier, displayInfos, countryCodes, separator, attributes, " + + "rules, hidden, maxLength, minLength, showMemberColumns, decimalDigits" ) ) - val maxLength = maxLengthTry match { - case Failure(exception) => None - case Success(opt) => opt - } - val minLength = minLengthTry match { - case Failure(exception) => None - case Success(opt) => opt - } + val structureProperties: Seq[Option[Any]] = Seq(columnName, ordering, kind, identifier, countryCodes) val isAtLeastOneStructureProperty: Boolean = structureProperties.exists(_.isDefined) @@ -513,7 +510,8 @@ class StructureController( hidden, maxLength, minLength, - showMemberColumns + showMemberColumns, + decimalDigits ) for { diff --git a/src/main/scala/com/campudus/tableaux/database/domain/column.scala b/src/main/scala/com/campudus/tableaux/database/domain/column.scala index 2e0129bc..c3f68912 100644 --- a/src/main/scala/com/campudus/tableaux/database/domain/column.scala +++ b/src/main/scala/com/campudus/tableaux/database/domain/column.scala @@ -37,6 +37,7 @@ sealed trait ColumnInformation { val hidden: Boolean val maxLength: Option[Int] val minLength: Option[Int] + val decimalDigits: Option[Int] } object BasicColumnInformation { @@ -64,7 +65,8 @@ object BasicColumnInformation { attributes, createColumn.hidden, createColumn.maxLength, - createColumn.minLength + createColumn.minLength, + createColumn.decimalDigits ) } } @@ -110,7 +112,8 @@ case class BasicColumnInformation( override val attributes: JsonObject, override val hidden: Boolean, override val maxLength: Option[Int], - override val minLength: Option[Int] + override val minLength: Option[Int], + override val decimalDigits: Option[Int] ) extends ColumnInformation case class StatusColumnInformation( @@ -126,7 +129,8 @@ case class StatusColumnInformation( val rules: JsonArray, override val hidden: Boolean, override val maxLength: Option[Int] = None, - override val minLength: Option[Int] = None + override val minLength: Option[Int] = None, + override val decimalDigits: Option[Int] = None ) extends ColumnInformation case class ConcatColumnInformation(override val table: Table) extends ColumnInformation { @@ -147,6 +151,7 @@ case class ConcatColumnInformation(override val table: Table) extends ColumnInfo override val attributes: JsonObject = Json.obj() override val maxLength: Option[Int] = None override val minLength: Option[Int] = None + override val decimalDigits: Option[Int] = None } object ColumnType { @@ -292,24 +297,27 @@ sealed trait ColumnType[+A] extends DomainObject { columnInformation.displayInfos.foreach(displayInfo => { displayInfo.optionalName.map(name => { json - .mergeIn( - Json - .obj("displayName" -> json.getJsonObject("displayName").mergeIn(Json.obj(displayInfo.langtag -> name))) - ) + .mergeIn(Json.obj("displayName" -> json.getJsonObject("displayName") + .mergeIn(Json.obj(displayInfo.langtag -> name)))) }) displayInfo.optionalDescription.map(desc => { json - .mergeIn( - Json - .obj("description" -> json.getJsonObject("description").mergeIn(Json.obj(displayInfo.langtag -> desc))) - ) + .mergeIn(Json.obj("description" -> json.getJsonObject("description") + .mergeIn(Json.obj(displayInfo.langtag -> desc)))) }) }) json } def checkValidValue[B](value: B): Try[Option[A]] + + protected def getJsonPart(columnInformationFiled: Option[Any], name: String): JsonObject = { + columnInformationFiled match { + case Some(v) => Json.obj(name -> v) + case None => Json.emptyObj() + } + } } /** @@ -410,15 +418,12 @@ case class TextColumn(override val languageType: LanguageType)(override val colu override def checkValidSingleValue[B](value: B): Try[String] = Try(value.asInstanceOf[String]) override def getJson: JsonObject = { - val maxLength = columnInformation.maxLength match { - case None => null - case Some(num) => num - } - val minLength = columnInformation.minLength match { - case None => null - case Some(num) => num - } - super.getJson.mergeIn(Json.obj("maxLength" -> maxLength, "minLength" -> minLength)) + val maxLengthJson = getJsonPart(columnInformation.maxLength, "maxLength") + val minLengthJson = getJsonPart(columnInformation.minLength, "minLength") + + super.getJson + .mergeIn(maxLengthJson) + .mergeIn(minLengthJson) } } @@ -430,15 +435,12 @@ case class ShortTextColumn(override val languageType: LanguageType)(override val override def checkValidSingleValue[B](value: B): Try[String] = Try(value.asInstanceOf[String]) override def getJson: JsonObject = { - val maxLength = columnInformation.maxLength match { - case None => null - case Some(num) => num - } - val minLength = columnInformation.minLength match { - case None => null - case Some(num) => num - } - super.getJson.mergeIn(Json.obj("maxLength" -> maxLength, "minLength" -> minLength)) + val maxLengthJson = getJsonPart(columnInformation.maxLength, "maxLength") + val minLengthJson = getJsonPart(columnInformation.minLength, "minLength") + + super.getJson + .mergeIn(maxLengthJson) + .mergeIn(minLengthJson) } } @@ -450,15 +452,12 @@ case class RichTextColumn(override val languageType: LanguageType)(override val override def checkValidSingleValue[B](value: B): Try[String] = Try(value.asInstanceOf[String]) override def getJson: JsonObject = { - val maxLength = columnInformation.maxLength match { - case None => null - case Some(num) => num - } - val minLength = columnInformation.minLength match { - case None => null - case Some(num) => num - } - super.getJson.mergeIn(Json.obj("maxLength" -> maxLength, "minLength" -> minLength)) + val maxLengthJson = getJsonPart(columnInformation.maxLength, "maxLength") + val minLengthJson = getJsonPart(columnInformation.minLength, "minLength") + + super.getJson + .mergeIn(maxLengthJson) + .mergeIn(minLengthJson) } } @@ -467,6 +466,12 @@ case class NumberColumn(override val languageType: LanguageType)(override val co val user: TableauxUser ) extends SimpleValueColumn[Number](NumericType)(languageType) { + override def getJson: JsonObject = { + val decimalDigitsJson = getJsonPart(columnInformation.decimalDigits, "decimalDigits") + + super.getJson + .mergeIn(decimalDigitsJson) + } override def checkValidSingleValue[B](value: B): Try[Number] = Try(value.asInstanceOf[Number]) } @@ -528,16 +533,19 @@ case class LinkColumn( override val languageType: LanguageType = to.languageType override def getJson: JsonObject = { - val constraintJson = linkDirection.constraint.getJson + val baseJson = Json.obj( + "toTable" -> to.table.id, + "toColumn" -> to.getJson + ) + + val constraintJson = linkDirection.constraint.getJson match { + case json if json.isEmpty => Json.emptyObj() + case json => Json.obj("constraint" -> json) + } super.getJson - .mergeIn( - Json.obj( - "toTable" -> to.table.id, - "toColumn" -> to.getJson - ) - ) - .mergeIn(if (constraintJson.isEmpty) Json.emptyObj() else Json.obj("constraint" -> constraintJson)) + .mergeIn(baseJson) + .mergeIn(constraintJson) } override def checkValidValue[B](value: B): Try[Option[Seq[RowId]]] = { @@ -721,7 +729,7 @@ case class GroupColumn( override val kind: GroupType.type = GroupType override def getJson: JsonObject = { - val json = super.getJson.mergeIn(Json.obj("groups" -> columns.map(_.getJson))) + val groupsJson = Json.obj("groups" -> columns.map(_.getJson)) val formatPatternJson = formatPattern match { case Some(pattern) => Json.obj("formatPattern" -> pattern) @@ -733,7 +741,10 @@ case class GroupColumn( case false => Json.emptyObj() } - json.mergeIn(formatPatternJson).mergeIn(showMemberColumnsJson) + super.getJson + .mergeIn(groupsJson) + .mergeIn(formatPatternJson) + .mergeIn(showMemberColumnsJson) } } diff --git a/src/main/scala/com/campudus/tableaux/database/domain/createcolumn.scala b/src/main/scala/com/campudus/tableaux/database/domain/createcolumn.scala index b9517cd3..6dea3e2b 100644 --- a/src/main/scala/com/campudus/tableaux/database/domain/createcolumn.scala +++ b/src/main/scala/com/campudus/tableaux/database/domain/createcolumn.scala @@ -17,6 +17,7 @@ sealed trait CreateColumn { val hidden: Boolean val maxLength: Option[Int] = None val minLength: Option[Int] = None + val decimalDigits: Option[Int] = None } case class CreateSimpleColumn( @@ -30,7 +31,8 @@ case class CreateSimpleColumn( override val attributes: Option[JsonObject], override val hidden: Boolean = false, override val maxLength: Option[Int] = None, - override val minLength: Option[Int] = None + override val minLength: Option[Int] = None, + override val decimalDigits: Option[Int] = None ) extends CreateColumn case class CreateBackLinkColumn( diff --git a/src/main/scala/com/campudus/tableaux/database/model/SystemModel.scala b/src/main/scala/com/campudus/tableaux/database/model/SystemModel.scala index 7e735b91..d4f2726e 100644 --- a/src/main/scala/com/campudus/tableaux/database/model/SystemModel.scala +++ b/src/main/scala/com/campudus/tableaux/database/model/SystemModel.scala @@ -185,7 +185,8 @@ class SystemModel(override protected[this] val connection: DatabaseConnection) e setupVersion(readSchemaFile("schema_v33"), 33), setupVersion(readSchemaFile("schema_v34"), 34), setupVersion(readSchemaFile("schema_v35"), 35), - setupVersion(readSchemaFile("schema_v36"), 36) + setupVersion(readSchemaFile("schema_v36"), 36), + setupVersion(readSchemaFile("schema_v37"), 37) ) private val setupShortCutFunction: Seq[DbTransaction => Future[DbTransaction]] = Seq( diff --git a/src/main/scala/com/campudus/tableaux/database/model/structure/ColumnModel.scala b/src/main/scala/com/campudus/tableaux/database/model/structure/ColumnModel.scala index c1138369..adb0312b 100644 --- a/src/main/scala/com/campudus/tableaux/database/model/structure/ColumnModel.scala +++ b/src/main/scala/com/campudus/tableaux/database/model/structure/ColumnModel.scala @@ -185,7 +185,8 @@ class CachedColumnModel( hidden: Option[Boolean], maxLength: Option[Int], minLength: Option[Int], - showMemberColumns: Option[Boolean] + showMemberColumns: Option[Boolean], + decimalDigits: Option[Int] )(implicit user: TableauxUser): Future[ColumnType[_]] = { for { _ <- removeCache(table.id, Some(columnId)) @@ -205,7 +206,8 @@ class CachedColumnModel( hidden, maxLength, minLength, - showMemberColumns + showMemberColumns, + decimalDigits ) } yield r } @@ -573,9 +575,10 @@ class ColumnModel(val connection: DatabaseConnection)( | hidden, | max_length, | min_length, - | show_member_columns + | show_member_columns, + | decimal_digits | ) - | VALUES (?, nextval('system_columns_column_id_table_$tableId'), ?, ?, $ordering, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + | VALUES (?, nextval('system_columns_column_id_table_$tableId'), ?, ?, $ordering, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) | RETURNING column_id, ordering |""".stripMargin } @@ -637,7 +640,8 @@ class ColumnModel(val connection: DatabaseConnection)( createColumn.hidden, maxLength, minLength, - showMemberColumns + showMemberColumns, + createColumn.decimalDigits.orNull ) ) case Some(ord) => @@ -659,7 +663,8 @@ class ColumnModel(val connection: DatabaseConnection)( createColumn.hidden, maxLength, minLength, - showMemberColumns + showMemberColumns, + createColumn.decimalDigits.orNull ) ) } @@ -879,7 +884,9 @@ class ColumnModel(val connection: DatabaseConnection)( | hidden, | max_length, | min_length, - | show_member_columns""".stripMargin + | show_member_columns, + | decimal_digits + |""".stripMargin private def retrieveOne(table: Table, columnId: ColumnId, depth: Int)( implicit user: TableauxUser @@ -1172,6 +1179,7 @@ class ColumnModel(val connection: DatabaseConnection)( val maxLength = Option(row.get[Int](13)) val minLength = Option(row.get[Int](14)) val showMemberColumns = row.get[Boolean](15) + val decimalDigits = Option(row.get[Int](16)) for { displayInfoSeq <- retrieveDisplayInfo(table, columnId) @@ -1188,7 +1196,8 @@ class ColumnModel(val connection: DatabaseConnection)( attributes, hidden, maxLength, - minLength + minLength, + decimalDigits ) column <- mapColumn(depth, kind, languageType, columnInformation, formatPattern, rules, showMemberColumns) @@ -1486,7 +1495,8 @@ class ColumnModel(val connection: DatabaseConnection)( hidden: Option[Boolean], maxLength: Option[Int], minLength: Option[Int], - showMemberColumns: Option[Boolean] + showMemberColumns: Option[Boolean], + decimalDigits: Option[Int] )(implicit user: TableauxUser): Future[ColumnType[_]] = { val tableId = table.id @@ -1507,7 +1517,7 @@ class ColumnModel(val connection: DatabaseConnection)( t <- connection.begin() // change column settings - (t, resultName) <- maybeUpdateColumn(t, "user_column_name", columnName) + (t, resultColumnName) <- maybeUpdateColumn(t, "user_column_name", columnName) (t, resultOrdering) <- maybeUpdateColumn(t, "ordering", ordering) (t, resultKind) <- maybeUpdateColumn(t, "column_type", kind, (k: TableauxDbType) => k.name) (t, resultIdentifier) <- maybeUpdateColumn(t, "identifier", identifier) @@ -1518,6 +1528,7 @@ class ColumnModel(val connection: DatabaseConnection)( maybeUpdateColumn(t, "country_codes", countryCodes, (c: Seq[String]) => Json.arr(c: _*)) (t, resultHidden) <- maybeUpdateColumn(t, "hidden", hidden) (t, resultShowMemberColumns) <- maybeUpdateColumn(t, "show_member_columns", showMemberColumns) + (t, resultDecimalDigits) <- maybeUpdateColumn(t, "decimal_digits", decimalDigits) // cannot use optionToValidFuture here, we need to be able to set these settings to null (t, resultMaxLength) <- maxLength match { @@ -1546,7 +1557,7 @@ class ColumnModel(val connection: DatabaseConnection)( _ <- Future( checkUpdateResults( - resultName, + resultColumnName, resultOrdering, resultKind, resultIdentifier, @@ -1556,7 +1567,8 @@ class ColumnModel(val connection: DatabaseConnection)( resultRules, resultMaxLength, resultMinLength, - resultShowMemberColumns + resultShowMemberColumns, + resultDecimalDigits ) ) .recoverWith(t.rollbackAndFail()) diff --git a/src/main/scala/com/campudus/tableaux/helper/JsonUtils.scala b/src/main/scala/com/campudus/tableaux/helper/JsonUtils.scala index 38589d3f..aac036ec 100644 --- a/src/main/scala/com/campudus/tableaux/helper/JsonUtils.scala +++ b/src/main/scala/com/campudus/tableaux/helper/JsonUtils.scala @@ -103,8 +103,9 @@ object JsonUtils extends LazyLogging { case Failure(s) => throw WrongJsonTypeException("Field attributes is not a valid json object.") } - val maxLength = Option(json.getInteger("maxLength")).map(_.toInt) - val minLength = Option(json.getInteger("minLength")).map(_.toInt) + val maxLength = Try(json.getInteger("maxLength").intValue()).toOption + val minLength = Try(json.getInteger("minLength").intValue()).toOption + val decimalDigits = parseDecimalDigits(json) // languageType or deprecated multilanguage // if languageType == 'country' countryCodes must be specified @@ -230,7 +231,8 @@ object JsonUtils extends LazyLogging { attributes, hidden, maxLength, - minLength + minLength, + decimalDigits ) } } @@ -281,6 +283,16 @@ object JsonUtils extends LazyLogging { } } + private def parseDecimalDigits(json: JsonObject): Option[Int] = { + val decimalDigits = Try(json.getInteger("decimalDigits").intValue()).toOption + + decimalDigits.map({ + case value if value > 10 || value < 0 => + throw InvalidJsonException(s"Decimal digits must be between 0 and 10, but was $value.", "decimalDigits") + case value => value + }) + } + def toRowValueSeq(json: JsonObject): Seq[Seq[_]] = { (for { checkedRowList <- toJsonObjectSeq("rows", json) @@ -323,11 +335,11 @@ object JsonUtils extends LazyLogging { } yield valueList } - private def getNullableJsonIntegerValue(key: String, json: JsonObject): Try[Option[Int]] = { + private def getNullableJsonIntegerValue(key: String, json: JsonObject): Try[Int] = { Try({ json.containsKey(key) match { case false => throw new KeyNotFoundInJsonException(key) - case true => Option(json.getInteger(key)).map(_.toInt) + case true => json.getInteger(key) } }) } @@ -343,9 +355,10 @@ object JsonUtils extends LazyLogging { Option[JsonObject], Option[JsonArray], Option[Boolean], - Try[Option[Int]], - Try[Option[Int]], - Option[Boolean] + Option[Int], + Option[Int], + Option[Boolean], + Option[Int] ) = { val name = Try(notNull(json.getString("name"), "name").get).toOption @@ -378,8 +391,9 @@ object JsonUtils extends LazyLogging { } ).map(_.asScala.toSeq.map({ case code: String => code })) - val maxLength = getNullableJsonIntegerValue("maxLength", json) - val minLength = getNullableJsonIntegerValue("minLength", json) + val maxLength = getNullableJsonIntegerValue("maxLength", json).toOption + val minLength = getNullableJsonIntegerValue("minLength", json).toOption + val decimalDigits = parseDecimalDigits(json) ( name, @@ -394,7 +408,8 @@ object JsonUtils extends LazyLogging { hidden, maxLength, minLength, - showMemberColumns + showMemberColumns, + decimalDigits ) } diff --git a/src/main/scala/com/campudus/tableaux/router/StructureRouter.scala b/src/main/scala/com/campudus/tableaux/router/StructureRouter.scala index a93d5095..3eb34319 100644 --- a/src/main/scala/com/campudus/tableaux/router/StructureRouter.scala +++ b/src/main/scala/com/campudus/tableaux/router/StructureRouter.scala @@ -274,7 +274,8 @@ class StructureRouter(override val config: TableauxConfig, val controller: Struc optHidden, maxLength, minLength, - showMemberColumns + showMemberColumns, + decimalDigits ) = toColumnChanges(json) @@ -293,7 +294,8 @@ class StructureRouter(override val config: TableauxConfig, val controller: Struc optHidden, maxLength, minLength, - showMemberColumns + showMemberColumns, + decimalDigits ) } ) diff --git a/src/test/scala/com/campudus/tableaux/api/auth/StructureControllerAuthTest.scala b/src/test/scala/com/campudus/tableaux/api/auth/StructureControllerAuthTest.scala index 9a844ae2..d721f9df 100644 --- a/src/test/scala/com/campudus/tableaux/api/auth/StructureControllerAuthTest.scala +++ b/src/test/scala/com/campudus/tableaux/api/auth/StructureControllerAuthTest.scala @@ -547,18 +547,7 @@ class StructureControllerColumnAuthTest_checkAuthorization extends StructureCont tableId, 1, None, - None, - None, - None, - Some(displayInfos), - None, - None, - None, - None, - None, - Try(Option(0)), - Try(Option(0)), - None + displayInfos = Some(displayInfos) ) } yield () } @@ -576,18 +565,7 @@ class StructureControllerColumnAuthTest_checkAuthorization extends StructureCont tableId, 1, None, - None, - None, - None, - Some(displayInfos), - None, - None, - None, - None, - None, - Try(Option(0)), - Try(Option(0)), - None + displayInfos = Some(displayInfos) ) } yield () } @@ -618,18 +596,7 @@ class StructureControllerColumnAuthTest_checkAuthorization extends StructureCont tableId, 1, Some("newName"), - None, - None, - None, - Some(displayInfos), - None, - None, - None, - None, - None, - Try(Option(0)), - Try(Option(0)), - None + displayInfos = Some(displayInfos) ) } yield () } @@ -647,18 +614,7 @@ class StructureControllerColumnAuthTest_checkAuthorization extends StructureCont tableId, 1, Some("newName"), - None, - None, - None, - Some(displayInfos), - None, - None, - None, - None, - None, - Try(Option(0)), - Try(Option(0)), - None + displayInfos = Some(displayInfos) ) } yield () } @@ -698,38 +654,13 @@ class StructureControllerColumnAuthTest_checkAuthorization extends StructureCont modelTableId, 1, Some("newName"), - None, - None, - None, - None, - None, - None, - None, - None, - None, - Try(Option(0)), - Try(Option(0)), None ) - ex <- controller - .changeColumn( - variantTableId, - 1, - Some("newName"), - None, - None, - None, - None, - None, - None, - None, - None, - None, - Try(Option(0)), - Try(Option(0)), - None - ) - .recover({ case ex => ex }) + ex <- controller.changeColumn( + variantTableId, + 1, + Some("newName") + ).recover({ case ex => ex }) } yield { assertEquals(UnauthorizedException(EditColumnStructureProperty, Seq("edit-columns-in-model-tables")), ex) } diff --git a/src/test/scala/com/campudus/tableaux/api/structure/ChangeStructureTest.scala b/src/test/scala/com/campudus/tableaux/api/structure/ChangeStructureTest.scala index 7c90b3ef..a99ef9f4 100644 --- a/src/test/scala/com/campudus/tableaux/api/structure/ChangeStructureTest.scala +++ b/src/test/scala/com/campudus/tableaux/api/structure/ChangeStructureTest.scala @@ -92,6 +92,30 @@ class ChangeStructureTest extends TableauxTestBase { } } + @Test + def changeColumnDecimalDigits(implicit c: TestContext): Unit = okTest { + val postJson = Json.obj("decimalDigits" -> 10) + + for { + _ <- createDefaultTable() + resultPost <- sendRequest("POST", "/tables/1/columns/2", postJson) + resultGet <- sendRequest("GET", "/tables/1/columns/2") + } yield { + assertEquals(10, resultGet.getInteger("decimalDigits")) + assertEquals(resultPost, resultGet) + } + } + + @Test + def changeColumnDecimalDigitsTooHigh(implicit c: TestContext): Unit = exceptionTest("error.json.decimalDigits") { + val postJson = Json.obj("decimalDigits" -> 11) + + for { + _ <- createDefaultTable() + resultPost <- sendRequest("POST", "/tables/1/columns/2", postJson) + } yield () + } + @Test def changeColumnKindWhichShouldFail(implicit c: TestContext): Unit = { okTest { diff --git a/src/test/scala/com/campudus/tableaux/api/structure/CreateColumnTest.scala b/src/test/scala/com/campudus/tableaux/api/structure/CreateColumnTest.scala index e20d8464..56919308 100644 --- a/src/test/scala/com/campudus/tableaux/api/structure/CreateColumnTest.scala +++ b/src/test/scala/com/campudus/tableaux/api/structure/CreateColumnTest.scala @@ -24,7 +24,8 @@ class CreateColumnTest extends TableauxTestBase { def createRichTextColumnJson(name: String) = RequestCreation.Columns().add(RequestCreation.RichTextCol(name)).getJson - def createNumberColumnJson(name: String) = RequestCreation.Columns().add(RequestCreation.NumericCol(name)).getJson + def createNumberColumnJson(name: String, decimalDigits: Option[Int] = None) = + RequestCreation.Columns().add(RequestCreation.NumericCol(name, decimalDigits)).getJson def createIntegerColumnJson(name: String) = RequestCreation.Columns().add(RequestCreation.IntegerCol(name)).getJson @@ -223,6 +224,66 @@ class CreateColumnTest extends TableauxTestBase { } } + @Test + def createNumberColumnWithCustomDecimalDigits(implicit c: TestContext): Unit = { + okTest { + val createColumn1 = createNumberColumnJson("column1", None) + val createColumn2 = createNumberColumnJson("column2", Some(10)) + val expectedJson = Json.obj( + "status" -> "ok", + "columns" -> Json.arr( + Json + .obj( + "id" -> 1, + "ordering" -> 1, + "multilanguage" -> false, + "identifier" -> false, + "displayName" -> Json.obj(), + "description" -> Json.obj() + ) + .mergeIn(createColumn1.getJsonArray("columns").getJsonObject(0)) + ) + ) + val expectedJson2 = Json.obj( + "status" -> "ok", + "columns" -> Json.arr( + Json + .obj( + "id" -> 2, + "ordering" -> 2, + "multilanguage" -> false, + "identifier" -> false, + "displayName" -> Json.obj(), + "description" -> Json.obj() + ) + .mergeIn(createColumn2.getJsonArray("columns").getJsonObject(0)) + ) + ) + + for { + _ <- sendRequest("POST", "/tables", createTableJson) + test1 <- sendRequest("POST", "/tables/1/columns", createColumn1) + test2 <- sendRequest("POST", "/tables/1/columns", createColumn2) + } yield { + assertJSONEquals(expectedJson, test1) + assertNull(test1.getJsonArray("columns").getJsonObject(0).getInteger("decimalDigits")) + assertJSONEquals(expectedJson2, test2) + } + } + } + + @Test + def createNumberColumnWithCustomDecimalDigitsTooHigh(implicit c: TestContext): Unit = { + exceptionTest("error.json.decimalDigits") { + val createColumn = createNumberColumnJson("column2", Some(11)) + + for { + _ <- sendRequest("POST", "/tables", createTableJson) + test <- sendRequest("POST", "/tables/1/columns", createColumn) + } yield () + } + } + @Test def createIntegerColumn(implicit c: TestContext): Unit = { okTest { diff --git a/src/test/scala/com/campudus/tableaux/controller/SystemControllerTest.scala b/src/test/scala/com/campudus/tableaux/controller/SystemControllerTest.scala index 7dc32c62..252a7b7d 100644 --- a/src/test/scala/com/campudus/tableaux/controller/SystemControllerTest.scala +++ b/src/test/scala/com/campudus/tableaux/controller/SystemControllerTest.scala @@ -72,8 +72,8 @@ class SystemControllerTest extends TableauxTestBase { okTest { val expectedJson = Json.obj( "database" -> Json.obj( - "current" -> 36, - "specification" -> 36 + "current" -> 37, + "specification" -> 37 ) ) diff --git a/src/test/scala/com/campudus/tableaux/testtools/RequestCreation.scala b/src/test/scala/com/campudus/tableaux/testtools/RequestCreation.scala index 24bb7536..f11b4240 100644 --- a/src/test/scala/com/campudus/tableaux/testtools/RequestCreation.scala +++ b/src/test/scala/com/campudus/tableaux/testtools/RequestCreation.scala @@ -62,7 +62,18 @@ object RequestCreation { case class RichTextCol(name: String) extends ColumnType("richtext") - case class NumericCol(name: String) extends ColumnType("numeric") + case class NumericCol(override val name: String, decimalDigits: Option[Int] = None) extends ColumnType("numeric") { + + override def getJson: JsonObject = { + super.getJson + .mergeIn( + decimalDigits match { + case Some(digits) => Json.obj("decimalDigits" -> digits) + case None => Json.emptyObj() + } + ) + } + } case class IntegerCol(name: String) extends ColumnType("integer")