mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-01-09 00:52:20 +01:00
Custom fields for issues and pull requests (#3034)
This commit is contained in:
31
src/main/resources/update/gitbucket-core_4.38.xml
Normal file
31
src/main/resources/update/gitbucket-core_4.38.xml
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<changeSet>
|
||||
<!--================================================================================================-->
|
||||
<!-- CUSTOM_FIELD -->
|
||||
<!--================================================================================================-->
|
||||
<createTable tableName="CUSTOM_FIELD">
|
||||
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="FIELD_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||
<column name="FIELD_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="FIELD_TYPE" type="varchar(100)" nullable="false"/>
|
||||
<column name="ENABLE_FOR_ISSUES" type="boolean" nullable="false"/>
|
||||
<column name="ENABLE_FOR_PULL_REQUESTS" type="boolean" nullable="false"/>
|
||||
</createTable>
|
||||
<addPrimaryKey constraintName="IDX_CUSTOM_FIELD_PK" tableName="CUSTOM_FIELD" columnNames="USER_NAME, REPOSITORY_NAME, FIELD_ID"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_CUSTOM_FIELD_FK0" baseTableName="CUSTOM_FIELD" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||
|
||||
<!--================================================================================================-->
|
||||
<!-- ISSUE_CUSTOM_FIELD -->
|
||||
<!--================================================================================================-->
|
||||
<createTable tableName="ISSUE_CUSTOM_FIELD">
|
||||
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="ISSUE_ID" type="int" nullable="false"/>
|
||||
<column name="FIELD_ID" type="int" nullable="false"/>
|
||||
<column name="VALUE" type="varchar(200)" nullable="true"/>
|
||||
</createTable>
|
||||
<addPrimaryKey constraintName="IDX_ISSUE_CUSTOM_FIELD_PK" tableName="ISSUE_CUSTOM_FIELD" columnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID, FIELD_ID"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_ISSUE_CUSTOM_FIELD_FK0" baseTableName="ISSUE_CUSTOM_FIELD" baseColumnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID" referencedTableName="ISSUE" referencedColumnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_ISSUE_CUSTOM_FIELD_FK1" baseTableName="ISSUE_CUSTOM_FIELD" baseColumnNames="USER_NAME, REPOSITORY_NAME, FIELD_ID" referencedTableName="CUSTOM_FIELD" referencedColumnNames="USER_NAME, REPOSITORY_NAME, FIELD_ID"/>
|
||||
</changeSet>
|
||||
@@ -122,5 +122,6 @@ object GitBucketCoreModule
|
||||
new Version("4.36.2"),
|
||||
new Version("4.37.0", new LiquibaseMigration("update/gitbucket-core_4.37.xml")),
|
||||
new Version("4.37.1"),
|
||||
new Version("4.37.2")
|
||||
new Version("4.37.2"),
|
||||
new Version("4.38.0", new LiquibaseMigration("update/gitbucket-core_4.38.xml"))
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.issues.html
|
||||
import gitbucket.core.model.Account
|
||||
import gitbucket.core.model.{Account, CustomFieldBehavior}
|
||||
import gitbucket.core.service.IssuesService._
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.util.Implicits._
|
||||
@@ -21,6 +21,7 @@ class IssuesController
|
||||
with ActivityService
|
||||
with HandleCommentService
|
||||
with IssueCreationService
|
||||
with CustomFieldsService
|
||||
with ReadableUsersAuthenticator
|
||||
with ReferrerAuthenticator
|
||||
with WritableUsersAuthenticator
|
||||
@@ -41,6 +42,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
with ActivityService
|
||||
with HandleCommentService
|
||||
with IssueCreationService
|
||||
with CustomFieldsService
|
||||
with ReadableUsersAuthenticator
|
||||
with ReferrerAuthenticator
|
||||
with WritableUsersAuthenticator
|
||||
@@ -109,6 +111,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
getMilestonesWithIssueCount(repository.owner, repository.name),
|
||||
getPriorities(repository.owner, repository.name),
|
||||
getLabels(repository.owner, repository.name),
|
||||
getCustomFieldsWithValue(repository.owner, repository.name, issueId.toInt).filter(_._1.enableForIssues),
|
||||
isIssueEditable(repository),
|
||||
isIssueManageable(repository),
|
||||
isIssueCommentManageable(repository),
|
||||
@@ -126,6 +129,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
getPriorities(repository.owner, repository.name),
|
||||
getDefaultPriority(repository.owner, repository.name),
|
||||
getLabels(repository.owner, repository.name),
|
||||
getCustomFields(repository.owner, repository.name).filter(_.enableForIssues),
|
||||
isIssueManageable(repository),
|
||||
getContentTemplate(repository, "ISSUE_TEMPLATE"),
|
||||
repository
|
||||
@@ -147,6 +151,25 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
form.labelNames.toSeq.flatMap(_.split(",")),
|
||||
loginAccount
|
||||
)
|
||||
|
||||
// Insert custom field values
|
||||
params.toMap.foreach {
|
||||
case (key, value) =>
|
||||
if (key.startsWith("custom-field-")) {
|
||||
getCustomField(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
key.replaceFirst("^custom-field-", "").toInt
|
||||
).foreach { field =>
|
||||
CustomFieldBehavior.validate(field, value, messages) match {
|
||||
case None =>
|
||||
insertOrUpdateCustomFieldValue(field, repository.owner, repository.name, issue.issueId, value)
|
||||
case Some(_) => halt(400)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
redirect(s"/${issue.userName}/${issue.repositoryName}/issues/${issue.issueId}")
|
||||
} else Unauthorized()
|
||||
}
|
||||
@@ -362,6 +385,35 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
Ok("updated")
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/customfield_validation/:fieldId")(writableUsersOnly { repository =>
|
||||
val fieldId = params("fieldId").toInt
|
||||
val value = params("value")
|
||||
getCustomField(repository.owner, repository.name, fieldId)
|
||||
.flatMap { field =>
|
||||
CustomFieldBehavior.validate(field, value, messages).map { error =>
|
||||
Ok(error)
|
||||
}
|
||||
}
|
||||
.getOrElse(Ok())
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/:id/customfield/:fieldId")(writableUsersOnly { repository =>
|
||||
val issueId = params("id").toInt
|
||||
val fieldId = params("fieldId").toInt
|
||||
val value = params("value")
|
||||
|
||||
for {
|
||||
_ <- getIssue(repository.owner, repository.name, issueId.toString)
|
||||
field <- getCustomField(repository.owner, repository.name, fieldId)
|
||||
} {
|
||||
CustomFieldBehavior.validate(field, value, messages) match {
|
||||
case None => insertOrUpdateCustomFieldValue(field, repository.owner, repository.name, issueId, value)
|
||||
case Some(_) => halt(400)
|
||||
}
|
||||
}
|
||||
Ok(value)
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/batchedit/state")(writableUsersOnly { repository =>
|
||||
val action = params.get("value")
|
||||
action match {
|
||||
|
||||
@@ -25,6 +25,7 @@ class PullRequestsController
|
||||
with PullRequestService
|
||||
with MilestonesService
|
||||
with LabelsService
|
||||
with CustomFieldsService
|
||||
with CommitsService
|
||||
with ActivityService
|
||||
with WebHookPullRequestService
|
||||
@@ -44,6 +45,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
with IssuesService
|
||||
with MilestonesService
|
||||
with LabelsService
|
||||
with CustomFieldsService
|
||||
with CommitsService
|
||||
with ActivityService
|
||||
with PullRequestService
|
||||
@@ -133,6 +135,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
getMilestonesWithIssueCount(repository.owner, repository.name),
|
||||
getPriorities(repository.owner, repository.name),
|
||||
getLabels(repository.owner, repository.name),
|
||||
getCustomFieldsWithValue(repository.owner, repository.name, issueId).filter(_._1.enableForPullRequests),
|
||||
isEditable(repository),
|
||||
isManageable(repository),
|
||||
hasDeveloperRole(pullreq.requestUserName, pullreq.requestRepositoryName, context.loginAccount),
|
||||
@@ -505,7 +508,8 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
getMilestones(originRepository.owner, originRepository.name),
|
||||
getPriorities(originRepository.owner, originRepository.name),
|
||||
getDefaultPriority(originRepository.owner, originRepository.name),
|
||||
getLabels(originRepository.owner, originRepository.name)
|
||||
getLabels(originRepository.owner, originRepository.name),
|
||||
getCustomFields(originRepository.owner, originRepository.name).filter(_.enableForPullRequests)
|
||||
)
|
||||
}
|
||||
case (oldId, newId) =>
|
||||
|
||||
@@ -2,7 +2,6 @@ package gitbucket.core.controller
|
||||
|
||||
import java.time.{LocalDateTime, ZoneOffset}
|
||||
import java.util.Date
|
||||
|
||||
import gitbucket.core.settings.html
|
||||
import gitbucket.core.model.{RepositoryWebHook, WebHook}
|
||||
import gitbucket.core.service._
|
||||
@@ -21,7 +20,7 @@ import org.eclipse.jgit.lib.Constants
|
||||
import org.eclipse.jgit.lib.ObjectId
|
||||
|
||||
import scala.util.Using
|
||||
import org.scalatra.Forbidden
|
||||
import org.scalatra.{Forbidden, Ok}
|
||||
|
||||
class RepositorySettingsController
|
||||
extends RepositorySettingsControllerBase
|
||||
@@ -31,6 +30,7 @@ class RepositorySettingsController
|
||||
with ProtectedBranchService
|
||||
with CommitStatusService
|
||||
with DeployKeyService
|
||||
with CustomFieldsService
|
||||
with ActivityService
|
||||
with OwnerAuthenticator
|
||||
with UsersAuthenticator
|
||||
@@ -43,6 +43,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
with ProtectedBranchService
|
||||
with CommitStatusService
|
||||
with DeployKeyService
|
||||
with CustomFieldsService
|
||||
with ActivityService
|
||||
with OwnerAuthenticator
|
||||
with UsersAuthenticator =>
|
||||
@@ -121,6 +122,21 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
"newOwner" -> trim(label("New owner", text(required, transferUser)))
|
||||
)(TransferOwnerShipForm.apply)
|
||||
|
||||
// for custom field
|
||||
case class CustomFieldForm(
|
||||
fieldName: String,
|
||||
fieldType: String,
|
||||
enableForIssues: Boolean,
|
||||
enableForPullRequests: Boolean
|
||||
)
|
||||
|
||||
val customFieldForm = mapping(
|
||||
"fieldName" -> trim(label("Field name", text(required, maxlength(100)))),
|
||||
"fieldType" -> trim(label("Field type", text(required))),
|
||||
"enableForIssues" -> trim(label("Enable for issues", boolean(required))),
|
||||
"enableForPullRequests" -> trim(label("Enable for pull requests", boolean(required))),
|
||||
)(CustomFieldForm.apply)
|
||||
|
||||
/**
|
||||
* Redirect to the Options page.
|
||||
*/
|
||||
@@ -477,6 +493,58 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/deploykey")
|
||||
})
|
||||
|
||||
/** Custom fields for issues and pull requests */
|
||||
get("/:owner/:repository/settings/issues")(ownerOnly { repository =>
|
||||
val customFields = getCustomFields(repository.owner, repository.name)
|
||||
html.issues(customFields, repository)
|
||||
})
|
||||
|
||||
/** New custom field form */
|
||||
get("/:owner/:repository/settings/issues/fields/new")(ownerOnly { repository =>
|
||||
html.issuesfieldform(None, repository)
|
||||
})
|
||||
|
||||
/** Add custom field */
|
||||
ajaxPost("/:owner/:repository/settings/issues/fields/new", customFieldForm)(ownerOnly { (form, repository) =>
|
||||
val fieldId = createCustomField(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
form.fieldName,
|
||||
form.fieldType,
|
||||
form.enableForIssues,
|
||||
form.enableForPullRequests
|
||||
)
|
||||
html.issuesfield(getCustomField(repository.owner, repository.name, fieldId).get)
|
||||
})
|
||||
|
||||
/** Edit custom field form */
|
||||
ajaxGet("/:owner/:repository/settings/issues/fields/:fieldId/edit")(ownerOnly { repository =>
|
||||
getCustomField(repository.owner, repository.name, params("fieldId").toInt).map { customField =>
|
||||
html.issuesfieldform(Some(customField), repository)
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
/** Update custom field */
|
||||
ajaxPost("/:owner/:repository/settings/issues/fields/:fieldId/edit", customFieldForm)(ownerOnly {
|
||||
(form, repository) =>
|
||||
updateCustomField(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
params("fieldId").toInt,
|
||||
form.fieldName,
|
||||
form.fieldType,
|
||||
form.enableForIssues,
|
||||
form.enableForPullRequests
|
||||
)
|
||||
html.issuesfield(getCustomField(repository.owner, repository.name, params("fieldId").toInt).get)
|
||||
})
|
||||
|
||||
/** Delete custom field */
|
||||
ajaxPost("/:owner/:repository/settings/issues/fields/:fieldId/delete")(ownerOnly { repository =>
|
||||
deleteCustomField(repository.owner, repository.name, params("fieldId").toInt)
|
||||
Ok()
|
||||
})
|
||||
|
||||
/**
|
||||
* Provides duplication check for web hook url.
|
||||
*/
|
||||
|
||||
191
src/main/scala/gitbucket/core/model/CustomField.scala
Normal file
191
src/main/scala/gitbucket/core/model/CustomField.scala
Normal file
@@ -0,0 +1,191 @@
|
||||
package gitbucket.core.model
|
||||
|
||||
import gitbucket.core.controller.Context
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.util.StringUtil
|
||||
import gitbucket.core.view.helpers
|
||||
import org.scalatra.i18n.Messages
|
||||
|
||||
trait CustomFieldComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.api._
|
||||
|
||||
lazy val CustomFields = TableQuery[CustomFields]
|
||||
|
||||
class CustomFields(tag: Tag) extends Table[CustomField](tag, "CUSTOM_FIELD") with BasicTemplate {
|
||||
val fieldId = column[Int]("FIELD_ID", O AutoInc)
|
||||
val fieldName = column[String]("FIELD_NAME")
|
||||
val fieldType = column[String]("FIELD_TYPE")
|
||||
val enableForIssues = column[Boolean]("ENABLE_FOR_ISSUES")
|
||||
val enableForPullRequests = column[Boolean]("ENABLE_FOR_PULL_REQUESTS")
|
||||
def * =
|
||||
(userName, repositoryName, fieldId, fieldName, fieldType, enableForIssues, enableForPullRequests)
|
||||
.<>(CustomField.tupled, CustomField.unapply)
|
||||
|
||||
def byPrimaryKey(userName: String, repositoryName: String, fieldId: Int) =
|
||||
(this.userName === userName.bind) && (this.repositoryName === repositoryName.bind) && (this.fieldId === fieldId.bind)
|
||||
}
|
||||
}
|
||||
|
||||
case class CustomField(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
fieldId: Int = 0,
|
||||
fieldName: String,
|
||||
fieldType: String, // long, double, string, or date
|
||||
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)(
|
||||
implicit context: Context
|
||||
): String
|
||||
def validate(name: String, value: String, messages: Messages): Option[String]
|
||||
}
|
||||
|
||||
object CustomFieldBehavior {
|
||||
def validate(field: CustomField, value: String, messages: Messages): Option[String] = {
|
||||
if (value.isEmpty) None
|
||||
else {
|
||||
CustomFieldBehavior(field.fieldType).flatMap { behavior =>
|
||||
behavior.validate(field.fieldName, value, messages)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def apply(fieldType: String): Option[CustomFieldBehavior] = {
|
||||
fieldType match {
|
||||
case "long" => Some(LongFieldBehavior)
|
||||
case "double" => Some(DoubleFieldBehavior)
|
||||
case "string" => Some(StringFieldBehavior)
|
||||
case "date" => Some(DateFieldBehavior)
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
case object LongFieldBehavior extends TextFieldBehavior {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||
try {
|
||||
value.toLong
|
||||
None
|
||||
} catch {
|
||||
case _: NumberFormatException => Some(messages("error.number").format(name))
|
||||
}
|
||||
}
|
||||
}
|
||||
case object DoubleFieldBehavior extends TextFieldBehavior {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||
try {
|
||||
value.toDouble
|
||||
None
|
||||
} catch {
|
||||
case _: NumberFormatException => Some(messages("error.number").format(name))
|
||||
}
|
||||
}
|
||||
}
|
||||
case object StringFieldBehavior extends TextFieldBehavior
|
||||
case object DateFieldBehavior extends TextFieldBehavior {
|
||||
private val pattern = "yyyy-MM-dd"
|
||||
override protected val fieldType: String = "date"
|
||||
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||
try {
|
||||
new java.text.SimpleDateFormat(pattern).parse(value)
|
||||
None
|
||||
} catch {
|
||||
case _: java.text.ParseException =>
|
||||
Some(messages("error.datePattern").format(name, pattern))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait TextFieldBehavior extends CustomFieldBehavior {
|
||||
protected val fieldType = "text"
|
||||
|
||||
def createHtml(repository: RepositoryInfo, fieldId: Int)(implicit context: Context): String = {
|
||||
val sb = new StringBuilder
|
||||
sb.append(
|
||||
s"""<input type="$fieldType" class="form-control input-sm" id="custom-field-$fieldId" name="custom-field-$fieldId" data-field-id="$fieldId" style="width: 120px;"/>"""
|
||||
)
|
||||
sb.append(s"""<script>
|
||||
|$$('#custom-field-$fieldId').focusout(function(){
|
||||
| const $$this = $$(this);
|
||||
| const fieldId = $$this.data('field-id');
|
||||
| $$.post('${helpers.url(repository)}/issues/customfield_validation/' + fieldId,
|
||||
| { value: $$this.val() },
|
||||
| function(data){
|
||||
| if (data != '') {
|
||||
| $$('#custom-field-$fieldId-error').text(data);
|
||||
| } else {
|
||||
| $$('#custom-field-$fieldId-error').text('');
|
||||
| }
|
||||
| }
|
||||
| );
|
||||
|});
|
||||
|</script>
|
||||
|""".stripMargin)
|
||||
sb.toString()
|
||||
}
|
||||
|
||||
def fieldHtml(repository: RepositoryInfo, issueId: Int, fieldId: Int, value: String, editable: Boolean)(
|
||||
implicit context: Context
|
||||
): String = {
|
||||
val sb = new StringBuilder
|
||||
sb.append(
|
||||
s"""<span id="custom-field-$fieldId-label" class="custom-field-label">${StringUtil
|
||||
.escapeHtml(value)}</span>""".stripMargin
|
||||
)
|
||||
if (editable) {
|
||||
sb.append(
|
||||
s"""<input type="$fieldType" id="custom-field-$fieldId-editor" class="form-control input-sm custom-field-editor" data-field-id="$fieldId" style="width: 120px; display: none;"/>"""
|
||||
)
|
||||
sb.append(s"""<script>
|
||||
|$$('#custom-field-$fieldId-label').click(function(){
|
||||
| const $$this = $$(this);
|
||||
| $$this.hide();
|
||||
| $$this.next().val($$this.text()).show().focus();
|
||||
|});
|
||||
|
|
||||
|$$('#custom-field-$fieldId-editor').focusout(function(){
|
||||
| const $$this = $$(this);
|
||||
| const fieldId = $$this.data('field-id');
|
||||
| $$.post('${helpers.url(repository)}/issues/customfield_validation/' + fieldId,
|
||||
| { value: $$this.val() },
|
||||
| function(data){
|
||||
| if (data != '') {
|
||||
| $$('#custom-field-$fieldId-error').text(data);
|
||||
| } else {
|
||||
| $$('#custom-field-$fieldId-error').text('');
|
||||
| $$.post('${helpers.url(repository)}/issues/$issueId/customfield/' + fieldId,
|
||||
| { value: $$this.val() },
|
||||
| function(data){
|
||||
| $$this.hide();
|
||||
| $$this.prev().text(data).show();
|
||||
| }
|
||||
| );
|
||||
| }
|
||||
| }
|
||||
| );
|
||||
|});
|
||||
|
|
||||
|// ESC key handling in text field
|
||||
|$$('#custom-field-$fieldId-editor').keyup(function(e){
|
||||
| if (e.keyCode == 27) {
|
||||
| const $$this = $$(this);
|
||||
| $$this.hide();
|
||||
| $$this.prev().show();
|
||||
| }
|
||||
| if (e.keyCode == 13) {
|
||||
| $$('#custom-field-$fieldId-editor').blur();
|
||||
| }
|
||||
|});
|
||||
|</script>
|
||||
|""".stripMargin)
|
||||
}
|
||||
sb.toString()
|
||||
}
|
||||
|
||||
def validate(name: String, value: String, messages: Messages): Option[String] = None
|
||||
}
|
||||
}
|
||||
31
src/main/scala/gitbucket/core/model/IssueCustomFields.scala
Normal file
31
src/main/scala/gitbucket/core/model/IssueCustomFields.scala
Normal file
@@ -0,0 +1,31 @@
|
||||
package gitbucket.core.model
|
||||
|
||||
trait IssueCustomFieldComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.api._
|
||||
import self._
|
||||
|
||||
lazy val IssueCustomFields = TableQuery[IssueCustomFields]
|
||||
|
||||
class IssueCustomFields(tag: Tag) extends Table[IssueCustomField](tag, "ISSUE_CUSTOM_FIELD") {
|
||||
val userName = column[String]("USER_NAME", O.PrimaryKey)
|
||||
val repositoryName = column[String]("REPOSITORY_NAME", O.PrimaryKey)
|
||||
val issueId = column[Int]("ISSUE_ID", O.PrimaryKey)
|
||||
val fieldId = column[Int]("FIELD_ID", O.PrimaryKey)
|
||||
val value = column[String]("VALUE")
|
||||
def * =
|
||||
(userName, repositoryName, issueId, fieldId, value)
|
||||
.<>(IssueCustomField.tupled, IssueCustomField.unapply)
|
||||
|
||||
def byPrimaryKey(owner: String, repository: String, issueId: Int, fieldId: Int) = {
|
||||
this.userName === owner.bind && this.repositoryName === repository.bind && this.issueId === issueId.bind && this.fieldId === fieldId.bind
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case class IssueCustomField(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
issueId: Int,
|
||||
fieldId: Int,
|
||||
value: String
|
||||
)
|
||||
@@ -72,5 +72,7 @@ trait CoreProfile
|
||||
with ReleaseAssetComponent
|
||||
with AccountExtraMailAddressComponent
|
||||
with AccountPreferenceComponent
|
||||
with CustomFieldComponent
|
||||
with IssueCustomFieldComponent
|
||||
|
||||
object Profile extends CoreProfile
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.model.{CustomField, IssueCustomField}
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
|
||||
trait CustomFieldsService {
|
||||
|
||||
def getCustomFields(owner: String, repository: String)(implicit s: Session): List[CustomField] =
|
||||
CustomFields.filter(_.byRepository(owner, repository)).sortBy(_.fieldId asc).list
|
||||
|
||||
def getCustomFieldsWithValue(owner: String, repository: String, issueId: Int)(
|
||||
implicit s: Session
|
||||
): List[(CustomField, Option[IssueCustomField])] = {
|
||||
CustomFields
|
||||
.filter(_.byRepository(owner, repository))
|
||||
.joinLeft(IssueCustomFields)
|
||||
.on { case (t1, t2) => t1.fieldId === t2.fieldId && t2.issueId === issueId.bind }
|
||||
.sortBy { case (t1, t2) => t1.fieldId }
|
||||
.list
|
||||
}
|
||||
|
||||
def getCustomField(owner: String, repository: String, fieldId: Int)(implicit s: Session): Option[CustomField] =
|
||||
CustomFields.filter(_.byPrimaryKey(owner, repository, fieldId)).firstOption
|
||||
|
||||
def createCustomField(
|
||||
owner: String,
|
||||
repository: String,
|
||||
fieldName: String,
|
||||
fieldType: String,
|
||||
enableForIssues: Boolean,
|
||||
enableForPullRequests: Boolean
|
||||
)(implicit s: Session): Int = {
|
||||
CustomFields returning CustomFields.map(_.fieldId) insert CustomField(
|
||||
userName = owner,
|
||||
repositoryName = repository,
|
||||
fieldName = fieldName,
|
||||
fieldType = fieldType,
|
||||
enableForIssues = enableForIssues,
|
||||
enableForPullRequests = enableForPullRequests
|
||||
)
|
||||
}
|
||||
|
||||
def updateCustomField(
|
||||
owner: String,
|
||||
repository: String,
|
||||
fieldId: Int,
|
||||
fieldName: String,
|
||||
fieldType: String,
|
||||
enableForIssues: Boolean,
|
||||
enableForPullRequests: Boolean
|
||||
)(
|
||||
implicit s: Session
|
||||
): Unit =
|
||||
CustomFields
|
||||
.filter(_.byPrimaryKey(owner, repository, fieldId))
|
||||
.map(t => (t.fieldName, t.fieldType, t.enableForIssues, t.enableForPullRequests))
|
||||
.update((fieldName, fieldType, enableForIssues, enableForPullRequests))
|
||||
|
||||
def deleteCustomField(owner: String, repository: String, fieldId: Int)(implicit s: Session): Unit = {
|
||||
IssueCustomFields
|
||||
.filter(t => t.userName === owner.bind && t.repositoryName === repository.bind && t.fieldId === fieldId.bind)
|
||||
.delete
|
||||
CustomFields.filter(_.byPrimaryKey(owner, repository, fieldId)).delete
|
||||
}
|
||||
|
||||
def getCustomFieldValues(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
issueId: Int,
|
||||
)(implicit s: Session): List[IssueCustomField] = {
|
||||
IssueCustomFields
|
||||
.filter(t => t.userName === userName && t.repositoryName === repositoryName.bind && t.issueId === issueId.bind)
|
||||
.list
|
||||
}
|
||||
|
||||
def insertOrUpdateCustomFieldValue(
|
||||
field: CustomField,
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
issueId: Int,
|
||||
value: String
|
||||
)(implicit s: Session): Unit = {
|
||||
IssueCustomFields.insertOrUpdate(
|
||||
IssueCustomField(
|
||||
userName = userName,
|
||||
repositoryName = repositoryName,
|
||||
issueId = issueId,
|
||||
fieldId = field.fieldId,
|
||||
value = value
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
priorities: List[gitbucket.core.model.Priority],
|
||||
defaultPriority: Option[gitbucket.core.model.Priority],
|
||||
labels: List[gitbucket.core.model.Label],
|
||||
customFields: List[gitbucket.core.model.CustomField],
|
||||
isManageable: Boolean,
|
||||
content: String,
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
|
||||
@@ -27,13 +28,36 @@
|
||||
elastic = true
|
||||
)
|
||||
<div class="align-right">
|
||||
<input type="submit" class="btn btn-success" value="Submit new issue"/>
|
||||
<input type="submit" class="btn btn-success" value="Submit new issue" onclick="javascript:return checkCustomFieldErrors();"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
@gitbucket.core.issues.html.issueinfo(None, Nil, Nil, collaborators, milestones.map(x => (x, 0, 0)), priorities, defaultPriority, labels, isManageable, repository)
|
||||
@gitbucket.core.issues.html.issueinfo(
|
||||
issue = None,
|
||||
comments = Nil,
|
||||
issueLabels = Nil,
|
||||
collaborators = collaborators,
|
||||
milestones = milestones.map(x => (x, 0, 0)),
|
||||
priorities= priorities,
|
||||
defaultPriority = defaultPriority,
|
||||
labels = labels,
|
||||
customFields = customFields.map((_, None)),
|
||||
isManageable = isManageable,
|
||||
repository = repository
|
||||
)
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<script>
|
||||
function checkCustomFieldErrors() {
|
||||
let error = false;
|
||||
$('.custom-field-error').each(function(i, e) {
|
||||
if ($(e).text() != '') {
|
||||
error = true;
|
||||
}
|
||||
});
|
||||
return !error;
|
||||
}
|
||||
</script>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
milestones: List[(gitbucket.core.model.Milestone, Int, Int)],
|
||||
priorities: List[gitbucket.core.model.Priority],
|
||||
labels: List[gitbucket.core.model.Label],
|
||||
customFields: List[(gitbucket.core.model.CustomField, Option[gitbucket.core.model.IssueCustomField])],
|
||||
isEditable: Boolean,
|
||||
isManageable: Boolean,
|
||||
isCommentManageable: Boolean,
|
||||
@@ -56,7 +57,19 @@
|
||||
@gitbucket.core.issues.html.commentform(issue, true, isEditable, isManageable, repository)
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
@gitbucket.core.issues.html.issueinfo(Some(issue), comments, issueLabels, collaborators, milestones, priorities, None, labels, isManageable, repository)
|
||||
@gitbucket.core.issues.html.issueinfo(
|
||||
issue = Some(issue),
|
||||
comments = comments,
|
||||
issueLabels = issueLabels,
|
||||
collaborators = collaborators,
|
||||
milestones = milestones,
|
||||
priorities = priorities,
|
||||
defaultPriority = None,
|
||||
labels = labels,
|
||||
customFields = customFields,
|
||||
isManageable = isManageable,
|
||||
repository = repository
|
||||
)
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@import org.json4s.scalap.scalasig.ClassFileParser.field
|
||||
@(issue: Option[gitbucket.core.model.Issue],
|
||||
comments: List[gitbucket.core.model.Comment],
|
||||
issueLabels: List[gitbucket.core.model.Label],
|
||||
@@ -6,6 +7,7 @@
|
||||
priorities: List[gitbucket.core.model.Priority],
|
||||
defaultPriority: Option[gitbucket.core.model.Priority],
|
||||
labels: List[gitbucket.core.model.Label],
|
||||
customFields: List[(gitbucket.core.model.CustomField, Option[gitbucket.core.model.IssueCustomField])],
|
||||
isManageable: Boolean,
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@@ -147,6 +149,25 @@
|
||||
@if(issue.isEmpty){
|
||||
<input type="hidden" name="assignedUserName" value=""/>
|
||||
}
|
||||
|
||||
@customFields.map { case (field, value) =>
|
||||
<hr/>
|
||||
<div style="margin-bottom: 14px;">
|
||||
<span class="muted small strong">@field.fieldName</span>
|
||||
<div class="pull-right">
|
||||
@gitbucket.core.model.CustomFieldBehavior(field.fieldType).map { behavior =>
|
||||
@if(issue.nonEmpty) {
|
||||
@Html(behavior.fieldHtml(repository, issue.get.issueId, field.fieldId, value.map(_.value).getOrElse(""), isManageable))
|
||||
}
|
||||
@if(issue.isEmpty) {
|
||||
@Html(behavior.createHtml(repository, field.fieldId))
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<span id="custom-field-@field.fieldId-error" class="error custom-field-error"></span>
|
||||
}
|
||||
|
||||
@issue.map { issue =>
|
||||
@gitbucket.core.plugin.PluginRegistry().getIssueSidebars.map { sidebarComponent =>
|
||||
@sidebarComponent(issue, repository, context)
|
||||
@@ -167,7 +188,7 @@
|
||||
$(function(){
|
||||
@issue.map { issue =>
|
||||
$('a.toggle-label').click(function(){
|
||||
var path = switchLabel($(this));
|
||||
const path = switchLabel($(this));
|
||||
$.post('@helpers.url(repository)/issues/@issue.issueId/label/' + path,
|
||||
{ labelId : $(this).data('label-id') },
|
||||
function(data){
|
||||
@@ -178,8 +199,8 @@ $(function(){
|
||||
});
|
||||
|
||||
$('a.milestone').click(function(){
|
||||
var title = $(this).data('title');
|
||||
var milestoneId = $(this).data('id');
|
||||
const title = $(this).data('title');
|
||||
const milestoneId = $(this).data('id');
|
||||
$.post('@helpers.url(repository)/issues/@issue.issueId/milestone',
|
||||
{ milestoneId: milestoneId },
|
||||
function(data){
|
||||
@@ -189,11 +210,11 @@ $(function(){
|
||||
});
|
||||
|
||||
$('a.priority').click(function(){
|
||||
var priorityName = $(this).data('name');
|
||||
var priorityId = $(this).data('id');
|
||||
var description = $(this).attr('title');
|
||||
var color = $(this).data('color');
|
||||
var fontColor = $(this).data('font-color');
|
||||
const priorityName = $(this).data('name');
|
||||
const priorityId = $(this).data('id');
|
||||
const description = $(this).attr('title');
|
||||
const color = $(this).data('color');
|
||||
const fontColor = $(this).data('font-color');
|
||||
$.post('@helpers.url(repository)/issues/@issue.issueId/priority',
|
||||
{ priorityId: priorityId },
|
||||
function(data){
|
||||
@@ -203,8 +224,8 @@ $(function(){
|
||||
});
|
||||
|
||||
$('a.assign').click(function(){
|
||||
var $this = $(this);
|
||||
var userName = $this.data('name');
|
||||
const $this = $(this);
|
||||
const userName = $this.data('name');
|
||||
$.post('@helpers.url(repository)/issues/@issue.issueId/assign',
|
||||
{ assignedUserName: userName },
|
||||
function(){
|
||||
@@ -215,7 +236,7 @@ $(function(){
|
||||
}.getOrElse {
|
||||
$('a.toggle-label').click(function(){
|
||||
switchLabel($(this));
|
||||
var labelNames = Array();
|
||||
const labelNames = Array();
|
||||
$('a.toggle-label').each(function(i, e){
|
||||
if($(e).children('i').hasClass('octicon-check') == true){
|
||||
labelNames.push($(e).text().trim());
|
||||
@@ -232,32 +253,32 @@ $(function(){
|
||||
});
|
||||
|
||||
$('a.milestone').click(function(){
|
||||
var title = $(this).data('title');
|
||||
var milestoneId = $(this).data('id');
|
||||
const title = $(this).data('title');
|
||||
const milestoneId = $(this).data('id');
|
||||
displayMilestone(title, milestoneId);
|
||||
$('input[name=milestoneId]').val(milestoneId);
|
||||
});
|
||||
|
||||
$('a.priority').click(function(){
|
||||
var priorityName = $(this).data('name');
|
||||
var priorityId = $(this).data('id');
|
||||
var description = $(this).attr('title');
|
||||
var color = $(this).data('color');
|
||||
var fontColor = $(this).data('font-color');
|
||||
const priorityName = $(this).data('name');
|
||||
const priorityId = $(this).data('id');
|
||||
const description = $(this).attr('title');
|
||||
const color = $(this).data('color');
|
||||
const fontColor = $(this).data('font-color');
|
||||
displayPriority(priorityName, priorityId, description, color, fontColor);
|
||||
$('input[name=priorityId]').val(priorityId);
|
||||
});
|
||||
|
||||
$('a.assign').click(function(){
|
||||
var $this = $(this);
|
||||
var userName = $this.data('name');
|
||||
const $this = $(this);
|
||||
const userName = $this.data('name');
|
||||
displayAssignee($this, userName);
|
||||
$('input[name=assignedUserName]').val(userName);
|
||||
});
|
||||
}
|
||||
|
||||
function switchLabel($this){
|
||||
var i = $this.children('i');
|
||||
const i = $this.children('i');
|
||||
if(i.hasClass('octicon-check')){
|
||||
i.removeClass('octicon-check');
|
||||
return 'delete';
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
milestones: List[gitbucket.core.model.Milestone],
|
||||
priorities: List[gitbucket.core.model.Priority],
|
||||
defaultPriority: Option[gitbucket.core.model.Priority],
|
||||
labels: List[gitbucket.core.model.Label])(implicit context: gitbucket.core.controller.Context)
|
||||
labels: List[gitbucket.core.model.Label],
|
||||
customFields: List[gitbucket.core.model.CustomField])(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@gitbucket.core.html.main(s"Pull requests - ${repository.owner}/${repository.name}", Some(repository)){
|
||||
@gitbucket.core.html.menu("pulls", repository){
|
||||
@@ -101,7 +102,19 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
@gitbucket.core.issues.html.issueinfo(None, Nil, Nil, collaborators, milestones.map((_, 0, 0)), priorities, defaultPriority, labels, hasOriginWritePermission, repository)
|
||||
@gitbucket.core.issues.html.issueinfo(
|
||||
issue = None,
|
||||
comments = Nil,
|
||||
issueLabels = Nil,
|
||||
collaborators = collaborators,
|
||||
milestones = milestones.map((_, 0, 0)),
|
||||
priorities = priorities,
|
||||
defaultPriority = defaultPriority,
|
||||
labels = labels,
|
||||
customFields = customFields.map((_, None)),
|
||||
isManageable = hasOriginWritePermission,
|
||||
repository = repository
|
||||
)
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
milestones: List[(gitbucket.core.model.Milestone, Int, Int)],
|
||||
priorities: List[gitbucket.core.model.Priority],
|
||||
labels: List[gitbucket.core.model.Label],
|
||||
customFields: List[(gitbucket.core.model.CustomField, Option[gitbucket.core.model.IssueCustomField])],
|
||||
isEditable: Boolean,
|
||||
isManageable: Boolean,
|
||||
isManageableForkedRepository: Boolean,
|
||||
@@ -50,7 +51,19 @@
|
||||
}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
@gitbucket.core.issues.html.issueinfo(Some(issue), comments.toList, issueLabels, collaborators, milestones, priorities, None, labels, isManageable, repository)
|
||||
@gitbucket.core.issues.html.issueinfo(
|
||||
Some(issue),
|
||||
comments.toList,
|
||||
issueLabels,
|
||||
collaborators,
|
||||
milestones,
|
||||
priorities,
|
||||
None,
|
||||
labels,
|
||||
customFields,
|
||||
isManageable,
|
||||
repository
|
||||
)
|
||||
</div>
|
||||
<script>
|
||||
$(function(){
|
||||
|
||||
55
src/main/twirl/gitbucket/core/settings/issues.scala.html
Normal file
55
src/main/twirl/gitbucket/core/settings/issues.scala.html
Normal file
@@ -0,0 +1,55 @@
|
||||
@(customFields: List[gitbucket.core.model.CustomField],
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@gitbucket.core.html.main("Issues", Some(repository)) {
|
||||
@gitbucket.core.html.menu("settings", repository) {
|
||||
@gitbucket.core.settings.html.menu("issues", repository) {
|
||||
<div class="pull-right" style="margin-bottom: 10px;">
|
||||
<a class="btn btn-success" href="javascript:void(0);" id="new-field-button">New field</a>
|
||||
</div>
|
||||
<table class="table table-bordered table-hover table-issues" id="new-field-table" style="display: none;">
|
||||
<tr><td></td></tr>
|
||||
</table>
|
||||
<table class="table table-bordered table-hover">
|
||||
<tr id="field-row-header">
|
||||
<th>@customFields.size custom fields</th>
|
||||
</tr>
|
||||
@customFields.map { customField =>
|
||||
@gitbucket.core.settings.html.issuesfield(customField)
|
||||
}
|
||||
</table>
|
||||
}
|
||||
}
|
||||
}
|
||||
<script>
|
||||
$(function(){
|
||||
$('#new-field-button').click(function(e){
|
||||
if($('#edit-field-area-new').length != 0){
|
||||
$('div#edit-field-area-new').remove();
|
||||
$('#new-field-table').hide();
|
||||
} else {
|
||||
$.get('@helpers.url(repository)/settings/issues/fields/new',
|
||||
function(data){
|
||||
$('#new-field-table').show().find('tr td').append(data);
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function deleteField(fieldId){
|
||||
if(confirm('Once you delete this field, there is no going back.\nAre you sure?')){
|
||||
$.post('@helpers.url(repository)/settings/issues/fields/' + fieldId + '/delete', function(){
|
||||
$('tr#field-row-' + fieldId).remove();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function editField(fieldId){
|
||||
$.get('@helpers.url(repository)/settings/issues/fields/' + fieldId + '/edit',
|
||||
function(data){
|
||||
$('#field-' + fieldId).hide().parent().append(data);
|
||||
}
|
||||
);
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,28 @@
|
||||
@(customField: gitbucket.core.model.CustomField)
|
||||
<tr id="field-row-@customField.fieldId">
|
||||
<td>
|
||||
<div class="row" id="field-@customField.fieldId">
|
||||
<div class="col-md-4 strong">
|
||||
@customField.fieldName
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
@customField.fieldType
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
@if(customField.enableForIssues) {
|
||||
Issues
|
||||
}
|
||||
@if(customField.enableForPullRequests) {
|
||||
Pull requests
|
||||
}
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div class="pull-right">
|
||||
<a href="javascript:void(0);" onclick="editField(@customField.fieldId)">Edit</a>
|
||||
|
||||
<a href="javascript:void(0);" onclick="deleteField(@customField.fieldId)">Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -0,0 +1,66 @@
|
||||
@(field: Option[gitbucket.core.model.CustomField],
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@defining(field.map(_.fieldId).getOrElse("new")){ fieldId =>
|
||||
<div id="edit-field-area-@fieldId">
|
||||
<form class="form-inline" autocomplete="off">
|
||||
<input type="text" id="fieldName-@fieldId" style="width: 300px; float: left; margin-right: 4px;" class="form-control input-sm" value="@field.map(_.fieldName)"@if(fieldId == "new"){ placeholder="New field name"}/>
|
||||
<select id="fieldType-@fieldId" class="form-control input-sm">
|
||||
<option value="long" @if(field.map(_.fieldType == "long").getOrElse(false)){selected}>Long</option>
|
||||
<option value="double" @if(field.map(_.fieldType == "double").getOrElse(false)){selected}>Double</option>
|
||||
<option value="string" @if(field.map(_.fieldType == "string").getOrElse(false)){selected}>String</option>
|
||||
<option value="date" @if(field.map(_.fieldType == "date").getOrElse(false)){selected}>Date</option>
|
||||
</select>
|
||||
<label for="enableForIssues-@fieldId" class="normal" style="margin-left: 4px;">
|
||||
<input type="checkbox" id="enableForIssues-@fieldId" @if(field.map(_.enableForIssues).getOrElse(false)){checked}> Issues
|
||||
</label>
|
||||
<label for="enableForPullRequests-@fieldId" class="normal" style="margin-left: 4px;">
|
||||
<input type="checkbox" id="enableForPullRequests-@fieldId" @if(field.map(_.enableForPullRequests).getOrElse(false)){checked}> Pull requests
|
||||
</label>
|
||||
<span class="pull-right">
|
||||
<span id="field-error-@fieldId" class="error"></span>
|
||||
<input type="button" id="cancel-@fieldId" class="btn btn-sm btn-default field-edit-cancel" value="Cancel">
|
||||
<input type="button" id="submit-@fieldId" class="btn btn-sm btn-success" style="margin-bottom: 0px;" value="@(if(fieldId == "new") "Create field" else "Save changes")"/>
|
||||
</span>
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
$(function(){
|
||||
$('#submit-@fieldId').click(function(e){
|
||||
$.post('@helpers.url(repository)/settings/issues/fields/@{if(fieldId == "new") "new" else s"$fieldId/edit"}', {
|
||||
'fieldName' : $('#fieldName-@fieldId').val(),
|
||||
'fieldType': $('#fieldType-@fieldId option:selected').val(),
|
||||
'enableForIssues': $('#enableForIssues-@fieldId').prop('checked'),
|
||||
'enableForPullRequests': $('#enableForPullRequests-@fieldId').prop('checked')
|
||||
}, function(data, status){
|
||||
$('div#edit-field-area-@fieldId').remove();
|
||||
@if(fieldId == "new"){
|
||||
$('#new-field-table').hide();
|
||||
// Insert row into the top of table
|
||||
$('#field-row-header').after(data);
|
||||
} else {
|
||||
// Replace table row
|
||||
$('#field-row-@fieldId').after(data).remove();
|
||||
}
|
||||
}).fail(function(xhr, status, error){
|
||||
const errors = JSON.parse(xhr.responseText);
|
||||
if(errors.fieldName){
|
||||
$('span#field-error-@fieldId').text(errors.fieldName);
|
||||
} else {
|
||||
$('span#field-error-@fieldId').text('error');
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#cancel-@fieldId').click(function(e){
|
||||
$('div#edit-field-area-@fieldId').remove();
|
||||
@if(fieldId == "new"){
|
||||
$('#new-field-table').hide();
|
||||
} else {
|
||||
$('#field-@fieldId').show();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
}
|
||||
@@ -12,6 +12,9 @@
|
||||
<a href="@helpers.url(repository)/settings/branches">Branches</a>
|
||||
</li>
|
||||
}
|
||||
<li@if(active=="issues"){ class="active"}>
|
||||
<a href="@helpers.url(repository)/settings/issues">Issues</a>
|
||||
</li>
|
||||
<li@if(active=="hooks"){ class="active"}>
|
||||
<a href="@helpers.url(repository)/settings/hooks">Service Hooks</a>
|
||||
</li>
|
||||
|
||||
Reference in New Issue
Block a user