Skip to content

Commit

Permalink
Custom fields for issues and pull requests
Browse files Browse the repository at this point in the history
  • Loading branch information
takezoe committed Apr 17, 2022
1 parent e521c21 commit 20246bc
Show file tree
Hide file tree
Showing 18 changed files with 742 additions and 32 deletions.
31 changes: 31 additions & 0 deletions src/main/resources/update/gitbucket-core_4.38.xml
Original file line number Diff line number Diff line change
@@ -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>
3 changes: 2 additions & 1 deletion src/main/scala/gitbucket/core/GitBucketCoreModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
)
54 changes: 53 additions & 1 deletion src/main/scala/gitbucket/core/controller/IssuesController.scala
Original file line number Diff line number Diff line change
@@ -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._
Expand All @@ -21,6 +21,7 @@ class IssuesController
with ActivityService
with HandleCommentService
with IssueCreationService
with CustomFieldsService
with ReadableUsersAuthenticator
with ReferrerAuthenticator
with WritableUsersAuthenticator
Expand All @@ -41,6 +42,7 @@ trait IssuesControllerBase extends ControllerBase {
with ActivityService
with HandleCommentService
with IssueCreationService
with CustomFieldsService
with ReadableUsersAuthenticator
with ReferrerAuthenticator
with WritableUsersAuthenticator
Expand Down Expand Up @@ -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),
Expand All @@ -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
Expand All @@ -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()
}
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class PullRequestsController
with PullRequestService
with MilestonesService
with LabelsService
with CustomFieldsService
with CommitsService
with ActivityService
with WebHookPullRequestService
Expand All @@ -44,6 +45,7 @@ trait PullRequestsControllerBase extends ControllerBase {
with IssuesService
with MilestonesService
with LabelsService
with CustomFieldsService
with CommitsService
with ActivityService
with PullRequestService
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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._
Expand All @@ -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
Expand All @@ -31,6 +30,7 @@ class RepositorySettingsController
with ProtectedBranchService
with CommitStatusService
with DeployKeyService
with CustomFieldsService
with ActivityService
with OwnerAuthenticator
with UsersAuthenticator
Expand All @@ -43,6 +43,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
with ProtectedBranchService
with CommitStatusService
with DeployKeyService
with CustomFieldsService
with ActivityService
with OwnerAuthenticator
with UsersAuthenticator =>
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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.
*/
Expand Down
Loading

0 comments on commit 20246bc

Please sign in to comment.