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("""
""")
+ 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"""
}