Skip to content

Commit

Permalink
user services instead of manager classes
Browse files Browse the repository at this point in the history
  • Loading branch information
HighKo committed Jan 10, 2024
1 parent c3f7cf9 commit 72b96c6
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.IssueInputParameters
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.util.ErrorCollection
Expand All @@ -52,9 +52,7 @@ import kotlin.math.ceil
object SdkJiraIssueOperator : JiraIssueOperator<SdkJiraField> {
override var RESULTS_PER_PAGE: Int = 10

private val issueManager by lazy { ComponentAccessor.getIssueManager() }
private val issueService by lazy { ComponentAccessor.getIssueService() }
private val issueFactory by lazy { ComponentAccessor.getIssueFactory() }
private val customFieldManager by lazy { ComponentAccessor.getCustomFieldManager() }
private val searchService: SearchService by lazy { ComponentAccessor.getComponent(SearchService::class.java) }
private val jiraAuthenticationContext by lazy { ComponentAccessor.getJiraAuthenticationContext() }
Expand All @@ -69,45 +67,53 @@ object SdkJiraIssueOperator : JiraIssueOperator<SdkJiraField> {
projectId: Long,
issueTypeId: Int,
fields: List<SdkJiraField>
): Either<JiraClientError, JiraIssue?> = Either.catchJiraClientError {
val freshIssue: MutableIssue = issueFactory.issue
freshIssue.projectId = projectId
freshIssue.issueTypeId = issueTypeId.toString()
fields.forEach { field ->
field.render(freshIssue)
}
val createdIssue: Issue = issueManager.createIssueObject(user(), freshIssue)

val basePath = applicationProperties.jiraBaseUrl
val contextPath = webResourceUrlProvider.baseUrl
val fullPath = if (contextPath.isNotEmpty()) "$basePath/$contextPath" else basePath
val selfLink = fullPath + "/rest/api/2/issue/" + createdIssue.id
JiraIssue(createdIssue.id.toString(), createdIssue.key, selfLink)
): Either<JiraClientError, JiraIssue?> = either {
Either.catchJiraClientError {
val freshIssue: IssueInputParameters = issueService.newIssueInputParameters()
freshIssue.projectId = projectId
freshIssue.issueTypeId = issueTypeId.toString()
fields.forEach { field ->
field.render(freshIssue)
}
val createValidationResult = issueService.validateCreate(user(), freshIssue)
val validateCreate = createValidationResult.toEither().bind()
val createResult = issueService.create(user(), validateCreate).toEither().bind()
val createdIssue = createResult.issue

val basePath = applicationProperties.jiraBaseUrl
val contextPath = webResourceUrlProvider.baseUrl
val fullPath = if (contextPath.isNotEmpty()) "$basePath/$contextPath" else basePath
val selfLink = fullPath + "/rest/api/2/issue/" + createdIssue.id
JiraIssue(createdIssue.id.toString(), createdIssue.key, selfLink)
}.bind()
}

override suspend fun updateIssue(
projectId: Long,
issueTypeId: Int,
issueKey: String,
fields: List<SdkJiraField>
): Either<JiraClientError, Unit> = Either.catchJiraClientError {
val issue = issueManager.getIssueByCurrentKey(issueKey)
issue.projectId = projectId
issue.issueTypeId = issueTypeId.toString()
fields.forEach { field ->
field.render(issue)
}
issueManager.updateIssue(user(), issue, EventDispatchOption.ISSUE_UPDATED, false)
): Either<JiraClientError, Unit> = either {
Either.catchJiraClientError {
val issueId = issueService.getIssue(user(), issueKey).toEither().bind().issue.id
val issueInput: IssueInputParameters = issueService.newIssueInputParameters()
issueInput.projectId = projectId
issueInput.issueTypeId = issueTypeId.toString()
fields.forEach { field ->
field.render(issueInput)
}
val validationResult = issueService.validateUpdate(user(), issueId, issueInput).toEither().bind()
issueService.update(user(), validationResult, EventDispatchOption.ISSUE_UPDATED, false).toEither().bind()
}.bind()
}

override suspend fun deleteIssue(issueKey: String): Either<JiraClientError, Unit> =
either {
Either.catchJiraClientError {
val issueToDelete = issueService.getIssue(user(), issueKey).toEither().bind()
val validateDelete = issueService.validateDelete(user(), issueToDelete.issue.id).toEither().bind()
issueService.delete(user(), validateDelete, EventDispatchOption.ISSUE_DELETED, false).toEither().bind()
}.bind()
}
override suspend fun deleteIssue(issueKey: String): Either<JiraClientError, Unit> = either {
Either.catchJiraClientError {
val issueToDelete = issueService.getIssue(user(), issueKey).toEither().bind()
val validateDelete = issueService.validateDelete(user(), issueToDelete.issue.id).toEither().bind()
issueService.delete(user(), validateDelete, EventDispatchOption.ISSUE_DELETED, false).toEither().bind()
}.bind()
}

private fun <T : ServiceResult> T.toEither() : Either<JiraClientError, T> =
when {
Expand All @@ -117,8 +123,8 @@ object SdkJiraIssueOperator : JiraIssueOperator<SdkJiraField> {

private fun ErrorCollection.toEither() : Either<JiraClientError, Unit> =
when {
this.hasAnyErrors() -> Either.Left(jiraClientError(this))
else -> Either.Right(Unit)
this.hasAnyErrors() -> jiraClientError(this).left()
else -> Unit.right()
}

private fun jiraClientError(errorCollection: ErrorCollection): JiraClientError {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,76 +21,86 @@ package com.linkedplanet.kotlinjiraclient.sdk.field

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.config.IssueTypeManager
import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.IssueInputParameters
import com.atlassian.jira.issue.context.IssueContext
import com.atlassian.jira.issue.context.IssueContextImpl
import com.atlassian.jira.issue.fields.CustomField
import com.atlassian.jira.timezone.TimeZoneManager
import com.linkedplanet.kotlinjiraclient.api.field.*
import com.riadalabs.jira.plugins.insight.channel.external.api.facade.ObjectFacade
import java.sql.Timestamp
import com.linkedplanet.kotlinjiraclient.api.field.JiraAssigneeField
import com.linkedplanet.kotlinjiraclient.api.field.JiraCustomDateTimeField
import com.linkedplanet.kotlinjiraclient.api.field.JiraCustomField
import com.linkedplanet.kotlinjiraclient.api.field.JiraCustomInsightObjectField
import com.linkedplanet.kotlinjiraclient.api.field.JiraCustomInsightObjectsField
import com.linkedplanet.kotlinjiraclient.api.field.JiraCustomNumberField
import com.linkedplanet.kotlinjiraclient.api.field.JiraCustomRadioField
import com.linkedplanet.kotlinjiraclient.api.field.JiraCustomTextField
import com.linkedplanet.kotlinjiraclient.api.field.JiraDescriptionField
import com.linkedplanet.kotlinjiraclient.api.field.JiraEpicLinkField
import com.linkedplanet.kotlinjiraclient.api.field.JiraEpicNameField
import com.linkedplanet.kotlinjiraclient.api.field.JiraIssueTypeField
import com.linkedplanet.kotlinjiraclient.api.field.JiraIssueTypeNameField
import com.linkedplanet.kotlinjiraclient.api.field.JiraProjectField
import com.linkedplanet.kotlinjiraclient.api.field.JiraReporterField
import com.linkedplanet.kotlinjiraclient.api.field.JiraSummaryField
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter

private val customFieldManager by lazy { ComponentAccessor.getCustomFieldManager() }
private val userManager by lazy { ComponentAccessor.getUserManager() }
private val issueManager by lazy { ComponentAccessor.getComponent(IssueManager::class.java) }
private val issueTypeManager by lazy { ComponentAccessor.getComponent(IssueTypeManager::class.java) }
private val optionsManager by lazy { ComponentAccessor.getOptionsManager() }
private val timezoneManager by lazy { ComponentAccessor.getComponent(TimeZoneManager::class.java) }

// Service interface to access and manage Insight objects
private val objectFacade by lazy { ComponentAccessor.getOSGiComponentInstanceOfType(ObjectFacade::class.java) }

interface SdkJiraField {
fun render(issue: MutableIssue)
fun render(issue: IssueInputParameters)
}

class SdkJiraSummaryField(summary: String) : JiraSummaryField(summary), SdkJiraField {
override fun render(issue: MutableIssue) {
override fun render(issue: IssueInputParameters) {
issue.summary = summary
}
}

class SdkJiraDescriptionField(description: String) : JiraDescriptionField(description), SdkJiraField {
override fun render(issue: MutableIssue) {
override fun render(issue: IssueInputParameters) {
issue.description = description
}
}

class SdkJiraProjectField(projectId: Long) : JiraProjectField(projectId), SdkJiraField {
override fun render(issue: MutableIssue) {
override fun render(issue: IssueInputParameters) {
issue.projectId = projectId
}
}

class SdkJiraIssueTypeField(issueTypeId: Int) : JiraIssueTypeField(issueTypeId), SdkJiraField {
override fun render(issue: MutableIssue) {
override fun render(issue: IssueInputParameters) {
issue.issueTypeId = issueTypeId.toString()
}
}

class SdkJiraIssueTypeNameField(issueTypeName: String) : JiraIssueTypeNameField(issueTypeName), SdkJiraField {
override fun render(issue: MutableIssue) {
override fun render(issue: IssueInputParameters) {
issue.setIssueType(this)
}
}

class SdkJiraAssigneeField(username: String) : JiraAssigneeField(username), SdkJiraField {
override fun render(issue: MutableIssue) {
issue.assignee = userManager.getUserByName(username)
override fun render(issue: IssueInputParameters) {
issue.assigneeId = username
}
}

class SdkJiraReporterField(username: String) : JiraReporterField(username), SdkJiraField {
override fun render(issue: MutableIssue) {
issue.reporter = userManager.getUserByName(username)
override fun render(issue: IssueInputParameters) {
issue.reporterId = username
}
}

class SdkJiraEpicLinkField(epicIssueKey: String?) : JiraEpicLinkField(epicIssueKey), SdkJiraField {
override fun render(issue: MutableIssue) {
issue.setCustomFieldValue(
customField(),
epicIssueKey?.let { issueManager.getIssueByCurrentKey(it) }
override fun render(issue: IssueInputParameters) {
issue.addCustomFieldValue(
customField().id,
epicIssueKey
)
}
}
Expand All @@ -99,7 +109,7 @@ class SdkJiraCustomInsightObjectField(
customFieldName: String,
insightKey: String?
) : JiraCustomInsightObjectField(customFieldName, insightKey), SdkJiraField {
override fun render(issue: MutableIssue) {
override fun render(issue: IssueInputParameters) {
issue.setInsightObjects(this, insightKey?.let { listOf(it) })
}
}
Expand All @@ -108,46 +118,46 @@ class SdkJiraCustomInsightObjectsField(
customFieldName: String,
insightKeys: List<String>
) : JiraCustomInsightObjectsField(customFieldName, insightKeys), SdkJiraField {
override fun render(issue: MutableIssue) {
override fun render(issue: IssueInputParameters) {
issue.setInsightObjects(this, insightKeys)
}
}

class SdkJiraEpicNameField(
epicName: String
) : JiraEpicNameField(epicName), SdkJiraField {
override fun render(issue: MutableIssue) {
issue.setCustomFieldValue(customField(), epicName)
override fun render(issue: IssueInputParameters) {
issue.addCustomFieldValue(customField().id, epicName)
}
}

class SdkJiraCustomTextField(
customFieldName: String,
text: String
) : JiraCustomTextField(customFieldName, text), SdkJiraField {
override fun render(issue: MutableIssue) {
issue.setCustomFieldValue(customField(), text)
override fun render(issue: IssueInputParameters) {
issue.addCustomFieldValue(customField().id, text)
}
}

class SdkJiraCustomNumberField(
customFieldName: String,
value: Number
) : JiraCustomNumberField(customFieldName, value), SdkJiraField {
override fun render(issue: MutableIssue) {
issue.setCustomFieldValue(customField(), number.toDouble())
override fun render(issue: IssueInputParameters) {
issue.addCustomFieldValue(customField().id, number.toString())
}
}

class SdkJiraCustomDateTimeField(
customFieldName: String,
dateTime: ZonedDateTime
) : JiraCustomDateTimeField(customFieldName, dateTime), SdkJiraField {
override fun render(issue: MutableIssue) {
override fun render(issue: IssueInputParameters) {
val dateTimeInUserTimeZone = dateTime.withZoneSameInstant(timezoneManager.loggedInUserTimeZone.toZoneId())
issue.setCustomFieldValue(
customField(),
Timestamp.from(dateTimeInUserTimeZone.toInstant())
issue.addCustomFieldValue(
customField().id,
dateTimeInUserTimeZone.format(DateTimeFormatter.ofPattern("dd/MMM/yy h:mm a"))
)
}
}
Expand All @@ -156,15 +166,17 @@ class SdkJiraCustomRadioField(
customFieldName: String,
value: String
) : JiraCustomRadioField(customFieldName, value), SdkJiraField {
override fun render(issue: MutableIssue) {
override fun render(issue: IssueInputParameters) {
val customField = customFieldManager.getCustomFieldObjectsByName(customFieldName).single()
val option = optionsManager.getOptions(customField.getRelevantConfig(issue)).getOptionForValue(value, null)
issue.setCustomFieldValue(customField(), option)
val issueContext: IssueContext = IssueContextImpl(issue.projectId, issue.issueTypeId)
val relevantConfig = customField.getRelevantConfig(issueContext)
val option = optionsManager.getOptions(relevantConfig).getOptionForValue(value, null)
issue.addCustomFieldValue(customField.id, option.optionId.toString())
}
}

private fun MutableIssue.setIssueType(field: JiraIssueTypeNameField) {
issueType = issueTypeManager.issueTypes.firstOrNull { it.name == field.issueTypeName }
private fun IssueInputParameters.setIssueType(field: JiraIssueTypeNameField) {
this.issueTypeId = issueTypeManager.issueTypes.firstOrNull { it.name == field.issueTypeName }?.id
?: throw IllegalArgumentException("Unknown Issue Type ${field.issueTypeName}")
}

Expand All @@ -177,7 +189,6 @@ private fun JiraCustomField.customField(): CustomField {
}
}

private fun MutableIssue.setInsightObjects(field: JiraCustomField, objectKeys: List<String>?) {
val beans = objectKeys?.map { objectKey -> objectFacade.loadObjectBean(objectKey) }
setCustomFieldValue(field.customField(), beans)
private fun IssueInputParameters.setInsightObjects(field: JiraCustomField, objectKeys: List<String>?) {
addCustomFieldValue(field.customField().id, *(objectKeys?.toTypedArray() ?: emptyArray()))
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,10 @@ interface JiraIssueOperatorTest<JiraFieldType> : BaseTestConfigProvider<JiraFiel
}

(1..10).forEach { searchedKeyIndex ->
val fields = listOf<JiraFieldType>(
val fields = listOf(
fieldFactory.jiraProjectField(projectId),
fieldFactory.jiraIssueTypeField(issueTypeId),
fieldFactory.jiraReporterField("test2"),
fieldFactory.jiraSummaryField("Test-$searchedKeyIndex"),
fieldFactory.jiraCustomInsightObjectField("InsightObject", "IT-1")
)
Expand Down Expand Up @@ -316,7 +317,7 @@ interface JiraIssueOperatorTest<JiraFieldType> : BaseTestConfigProvider<JiraFiel
}

@Test
fun issues_07CreateIssueWithourPermission() {
fun issues_07CreateIssueWithoutPermission() {
loginAsUser("EveTheEvilHacker")
val error = runBlocking { issueOperator.createIssue(projectId, issueTypeId, listOf()) }.assertLeft()
assertThat(error.message, anyOf(containsString("401"), containsString("400")))
Expand All @@ -334,8 +335,8 @@ interface JiraIssueOperatorTest<JiraFieldType> : BaseTestConfigProvider<JiraFiel
fun issues_07xDeleteIssueWithoutPermission() { // throwable wtf
val issue = runBlocking { issueOperator.getIssueByJQL("summary ~ \"MyNewSummary\"", ::issueParser) }.orFail()
loginAsUser("EveTheEvilHacker")
val permissionError = runBlocking { issueOperator.deleteIssue(issue.key) }.assertLeft()
assertThat(permissionError.message, containsString("401"))
val error = runBlocking { issueOperator.deleteIssue(issue.key) }.assertLeft()
assertThat(error.message, containsString("401"))
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class JiraIssueTestHelper<JiraFieldType>(
val combinedFields = listOf(
fieldFactory.jiraProjectField(projectId),
fieldFactory.jiraIssueTypeField(jiraIssueTypeId),
fieldFactory.jiraReporterField("test2")
).plus(fields)
issueOperator.createIssue(projectId, jiraIssueTypeId, combinedFields)
}.orFail()
Expand Down

0 comments on commit 72b96c6

Please sign in to comment.