diff --git a/src/main/resources/update/gitbucket-core_4.39.xml b/src/main/resources/update/gitbucket-core_4.39.xml new file mode 100644 index 0000000..9691ce1 --- /dev/null +++ b/src/main/resources/update/gitbucket-core_4.39.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala index 08bdbf6..74f7159 100644 --- a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala +++ b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala @@ -115,5 +115,6 @@ new Version("4.38.1"), new Version("4.38.2"), new Version("4.38.3"), - new Version("4.38.4") + new Version("4.38.4"), + new Version("4.39.0", new LiquibaseMigration("update/gitbucket-core_4.39.xml")), ) diff --git a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala index a949ba6..2a09fd1 100644 --- a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala @@ -126,6 +126,7 @@ case class CustomFieldForm( fieldName: String, fieldType: String, + constraints: Option[String], enableForIssues: Boolean, enableForPullRequests: Boolean ) @@ -133,6 +134,7 @@ val customFieldForm = mapping( "fieldName" -> trim(label("Field name", text(required, maxlength(100)))), "fieldType" -> trim(label("Field type", text(required))), + "constraints" -> trim(label("Constraints", optional(text()))), "enableForIssues" -> trim(label("Enable for issues", boolean(required))), "enableForPullRequests" -> trim(label("Enable for pull requests", boolean(required))), )(CustomFieldForm.apply) @@ -511,6 +513,7 @@ repository.name, form.fieldName, form.fieldType, + if (form.fieldType == "enum") form.constraints else None, form.enableForIssues, form.enableForPullRequests ) @@ -533,6 +536,7 @@ params("fieldId").toInt, form.fieldName, form.fieldType, + if (form.fieldType == "enum") form.constraints else None, form.enableForIssues, form.enableForPullRequests ) diff --git a/src/main/scala/gitbucket/core/model/CustomField.scala b/src/main/scala/gitbucket/core/model/CustomField.scala index 4d5a4f1..0eaac53 100644 --- a/src/main/scala/gitbucket/core/model/CustomField.scala +++ b/src/main/scala/gitbucket/core/model/CustomField.scala @@ -5,6 +5,7 @@ import gitbucket.core.util.StringUtil import gitbucket.core.view.helpers import org.scalatra.i18n.Messages +import play.twirl.api.Html trait CustomFieldComponent extends TemplateComponent { self: Profile => import profile.api._ @@ -15,10 +16,11 @@ val fieldId = column[Int]("FIELD_ID", O AutoInc) val fieldName = column[String]("FIELD_NAME") val fieldType = column[String]("FIELD_TYPE") + val constraints = column[Option[String]]("CONSTRAINTS") val enableForIssues = column[Boolean]("ENABLE_FOR_ISSUES") val enableForPullRequests = column[Boolean]("ENABLE_FOR_PULL_REQUESTS") def * = - (userName, repositoryName, fieldId, fieldName, fieldType, enableForIssues, enableForPullRequests) + (userName, repositoryName, fieldId, fieldName, fieldType, constraints, enableForIssues, enableForPullRequests) .<>(CustomField.tupled, CustomField.unapply) def byPrimaryKey(userName: String, repositoryName: String, fieldId: Int) = @@ -31,17 +33,28 @@ repositoryName: String, fieldId: Int = 0, fieldName: String, - fieldType: String, // long, double, string, or date + fieldType: String, // long, double, string, date, or enum + constraints: Option[String], enableForIssues: Boolean, enableForPullRequests: Boolean ) trait CustomFieldBehavior { - def createHtml(repository: RepositoryInfo, fieldId: Int)(implicit conext: Context): String - def fieldHtml(repository: RepositoryInfo, issueId: Int, fieldId: Int, value: String, editable: Boolean)( + def createHtml(repository: RepositoryInfo, fieldId: Int, fieldName: String, constraints: Option[String])( implicit context: Context ): String - def validate(name: String, value: String, messages: Messages): Option[String] + def fieldHtml( + repository: RepositoryInfo, + issueId: Int, + fieldId: Int, + fieldName: String, + constraints: Option[String], + value: String, + editable: Boolean + )( + implicit context: Context + ): String + def validate(name: String, constraints: Option[String], value: String, messages: Messages): Option[String] } object CustomFieldBehavior { @@ -49,7 +62,7 @@ if (value.isEmpty) None else { CustomFieldBehavior(field.fieldType).flatMap { behavior => - behavior.validate(field.fieldName, value, messages) + behavior.validate(field.fieldName, field.constraints, value, messages) } } } @@ -60,12 +73,18 @@ case "double" => Some(DoubleFieldBehavior) case "string" => Some(StringFieldBehavior) case "date" => Some(DateFieldBehavior) + case "enum" => Some(EnumFieldBehavior) case _ => None } } case object LongFieldBehavior extends TextFieldBehavior { - override def validate(name: String, value: String, messages: Messages): Option[String] = { + override def validate( + name: String, + constraints: Option[String], + value: String, + messages: Messages + ): Option[String] = { try { value.toLong None @@ -75,7 +94,12 @@ } } case object DoubleFieldBehavior extends TextFieldBehavior { - override def validate(name: String, value: String, messages: Messages): Option[String] = { + override def validate( + name: String, + constraints: Option[String], + value: String, + messages: Messages + ): Option[String] = { try { value.toDouble None @@ -89,7 +113,12 @@ private val pattern = "yyyy-MM-dd" override protected val fieldType: String = "date" - override def validate(name: String, value: String, messages: Messages): Option[String] = { + override def validate( + name: String, + constraints: Option[String], + value: String, + messages: Messages + ): Option[String] = { try { new java.text.SimpleDateFormat(pattern).parse(value) None @@ -100,10 +129,142 @@ } } + case object EnumFieldBehavior extends CustomFieldBehavior { + override def createHtml(repository: RepositoryInfo, fieldId: Int, fieldName: String, constraints: Option[String])( + implicit context: Context + ): String = { + createPulldownHtml(repository, fieldId, fieldName, constraints, None, None) + } + + override def fieldHtml( + repository: RepositoryInfo, + issueId: Int, + fieldId: Int, + fieldName: String, + constraints: Option[String], + value: String, + editable: Boolean + )(implicit context: Context): String = { + if (!editable) { + val sb = new StringBuilder + sb.append("""""") + sb.append("""
""") + if (value == "") { + sb.append(s"""No ${StringUtil.escapeHtml( + fieldName + )}""") + } else { + sb.append(s"""${StringUtil + .escapeHtml(value)}""") + } + sb.toString() + } else { + createPulldownHtml(repository, fieldId, fieldName, constraints, Some(issueId), Some(value)) + } + } + + private def createPulldownHtml( + repository: RepositoryInfo, + fieldId: Int, + fieldName: String, + constraints: Option[String], + issueId: Option[Int], + value: Option[String] + )(implicit context: Context): String = { + val sb = new StringBuilder + sb.append("""
""") + sb.append( + gitbucket.core.helper.html + .dropdown("Edit", right = true, filter = (fieldName, s"Filter $fieldName")) { + val options = new StringBuilder() + options.append( + s"""
  • Clear ${StringUtil + .escapeHtml(fieldName)}
  • """ + ) + constraints.foreach { + x => + x.split(",").map(_.trim).foreach { + item => + options.append(s"""
  • + | + | ${gitbucket.core.helper.html.checkicon(value.contains(item))} + | ${StringUtil.escapeHtml(item)} + | + |
  • + |""".stripMargin) + } + } + Html(options.toString()) + } + .toString() + ) + sb.append("""
    """) + sb.append("""
    """) + sb.append("""
    """) + value match { + case None => + sb.append(s"""No ${StringUtil.escapeHtml( + fieldName + )}""") + case Some(value) => + sb.append(s"""${StringUtil + .escapeHtml(value)}""") + } + if (value.isEmpty || issueId.isEmpty) { + sb.append(s"""""") + sb.append(s"""""".stripMargin) + } else { + sb.append(s""" + |""".stripMargin) + } + sb.toString() + } + + override def validate( + name: String, + constraints: Option[String], + value: String, + messages: Messages + ): Option[String] = None + } + trait TextFieldBehavior extends CustomFieldBehavior { protected val fieldType = "text" - def createHtml(repository: RepositoryInfo, fieldId: Int)(implicit context: Context): String = { + override def createHtml(repository: RepositoryInfo, fieldId: Int, fieldName: String, constraints: Option[String])( + implicit context: Context + ): String = { val sb = new StringBuilder sb.append( s"""""" @@ -111,8 +272,7 @@ sb.append(s""" }