The synchronization of the Customer Request Type is a complex one given the specific behavior of the JIRA Service Desk handling of this field.

The Customer Request Type depends on 

  • The service desk project
  • The issue type
  • The request type

 

The representation value of the customer request type is something which is distilled from the project key and the request type.

Source side

Data Filter

Add the following script to send the data of the Request Type issue field.

...
replica.customFields."Request Type"   = issue.customFields."Request Type"
...


Destination side

JIRA Server

Use a nodeHelper getCustomerRequestType method to extract the correct value.

Add the script to your Create/Change Processor

//define getRequestTypes function, which uses servide desk API to get all existing request types for the project, specified with the help of the project key
def requestType = ({ String serviceDeskProjectKey, String issueTypeName ->
    def _pluginAccessor = com.atlassian.jira.component.ComponentAccessor.pluginAccessor
    def _sdPlugin = _pluginAccessor.getEnabledPlugin("com.atlassian.servicedesk")
    def _sdCl = _sdPlugin.classLoader
    def _rtsClass = _sdCl.loadClass("com.atlassian.servicedesk.api.requesttype.RequestTypeService")
    def _psClass = _sdCl.loadClass("com.atlassian.servicedesk.api.portal.PortalService")
    def _sdsClass = _sdCl.loadClass("com.atlassian.servicedesk.api.ServiceDeskService")
    def _rtqIClass = _sdCl.loadClass("com.atlassian.servicedesk.internal.feature.customer.request.requesttype.RequestTypeQueryImpl")

    def _requestTypeService = com.atlassian.jira.component.ComponentAccessor.getOSGiComponentInstanceOfType(_rtsClass)
    def _portalService = com.atlassian.jira.component.ComponentAccessor.getOSGiComponentInstanceOfType(_psClass)
    def _sdService = com.atlassian.jira.component.ComponentAccessor.getOSGiComponentInstanceOfType(_sdsClass)


    def _user = com.atlassian.jira.component.ComponentAccessor.getOSGiComponentInstanceOfType(com.exalate.api.node.INodeService.class).getProxyUser()

    def _projectObject = com.atlassian.jira.component.ComponentAccessor.getProjectManager().getProjectObjByKey(serviceDeskProjectKey)
    def _serviceDeskEither = _sdService.getServiceDeskForProject(_user, _projectObject)

    if (_serviceDeskEither.isLeft()) {
        log.debug "Error ${_serviceDeskEither.left().get()}"
        throw new com.exalate.api.exception.IssueTrackerException("Failed to find service desk for project `${_projectObject.key}`: ${_serviceDeskEither.left().get()}".toString())
    }
    def _serviceDesk = _serviceDeskEither.right().get()

    def builder = _rtqIClass.builder()
    def query = builder.serviceDesk(_serviceDesk.getId()).build()

    def _requestTypes = _requestTypeService.getRequestTypes(_user, query)

    if (_requestTypes.isLeft()) {
        log.error "${_requestTypes.left().get()}"
        throw new com.exalate.api.exception.IssueTrackerException("Failed to find request types for project `${_projectObject.key}`, service desk `${_serviceDesk.id}`: ${_requestTypes.left().get()}".toString())
    }

    def portalRequest = _portalService.getPortalForProject(_user, _projectObject)


    if (portalRequest.isLeft()) {
        log.debug "Error ${portalRequest.left().get()}"
        throw new com.exalate.api.exception.IssueTrackerException("Failed to find _portal for project `${_projectObject.key}`, user `${_user.displayName}` (${_user.key}): ${_requestTypes.left().get()}".toString())
    }

    def _portal = portalRequest.right().get()

    def requestTypesByProject = _requestTypes?.right()?.get()?.findAll {it.portalId == _portal.id} ?: []

    //find all request types for the project and the issueType
    def requestTypesByProjectAndIssueType = requestTypesByProject.findAll { it.issueTypeId == (nodeHelper.getIssueType(issueTypeName)?.id as Long) }

    //get the first request type for the project and the issueType
    nodeHelper.getCustomerRequestType("Request Type", requestTypesByProjectAndIssueType.find()?.key)
})(issue.projectKey ?: issue.project?.key, issue.typeName ?: issue.type?.name)

//if there is a request type for the project TEST and issueType Task set it to the issue on the destination side
if (requestType != null) {
    issue.customFields."Request Type".value = requestType
}

JIRA Cloud

Add the script to your Create/Change Processor 

// CUSTOMER REQUEST TYPE SYNC
def requestType = ({
    final def injector = play.api.Play$.MODULE$.current().injector()
    def issueLevelError = { String msg ->
        new com.exalate.api.exception.IssueTrackerException(msg)
    }
    def issueLevelError2 = { String msg, Throwable e ->
        new com.exalate.api.exception.IssueTrackerException(msg, e)
    }
    def fn = { Closure<?> closure ->
        new scala.runtime.AbstractFunction1<Object, Object>() {
            @Override
            Object apply(Object p) {
                return closure.call(p)
            }
        }
    }
    def fn2 = { Closure<?> closure ->
        new scala.runtime.AbstractFunction2<Object, Object, Object>() {
            @Override
            Object apply(Object p1, Object p2) {
                return closure.call(p1, p2)
            }
        }
    }
    def await = { scala.concurrent.Future<?> f -> scala.concurrent.Await$.MODULE$.result(f, scala.concurrent.duration.Duration$.MODULE$.Inf()) }
    def orNull = { scala.Option<?> opt -> opt.isDefined() ? opt.get() : null }
    def none = { scala.Option$.MODULE$.<?>empty() }
    def pair = { l, r -> scala.Tuple2$.MODULE$.<?, ?>apply(l, r) }
    def seq =  { ... ts ->
        def list = Arrays.asList(ts)
        def scalaBuffer = scala.collection.JavaConversions.asScalaBuffer(list)
        scalaBuffer.toSeq()
    }
    def seqPlus = { scala.collection.Seq<?> tsLeft, ... tsRight ->
        def list = Arrays.asList(tsRight)
        def scalaBuffer = scala.collection.JavaConversions.asScalaBuffer(list)
        scala.collection.Seq$.MODULE$
                .newBuilder()
                .$plus$plus$eq(tsLeft)
                .$plus$plus$eq(scalaBuffer)
                .result()
    }
    def paginateInternal
    paginateInternal = { Integer offset, Integer limit, scala.collection.Seq<?> result, scala.runtime.AbstractFunction2<Integer, Integer, ?> nextPageFn, scala.runtime.AbstractFunction1<?, Integer> getTotalFn ->
        def page = nextPageFn.apply(offset, limit)
        def total = getTotalFn.apply(page)
        def last = total < limit
        def newResult = seqPlus(result, page)
        if (last) {
            newResult
        } else {
            paginateInternal(offset + limit, limit, newResult, nextPageFn, getTotalFn)
        }
    }
    def paginate = { Integer limit, scala.runtime.AbstractFunction2<Integer, Integer, ?> nextPageFn, scala.runtime.AbstractFunction1<?, Integer> getTotalFn ->
        scala.collection.Seq<?> resultSeq = paginateInternal(0, limit, seq(), nextPageFn, getTotalFn)
        scala.collection.JavaConversions.bufferAsJavaList(resultSeq.toBuffer())
    }
    def getGeneralSettings = {
        def gsp = injector.instanceOf(com.exalate.api.persistence.issuetracker.jcloud.IJCloudGeneralSettingsPersistence.class)
        def gsOpt = await(gsp.get())
        def gs = orNull(gsOpt)
        gs
    }
    final def gs = getGeneralSettings()
    def removeTailingSlash = { String str -> str.trim().replace("/+\$","") }
    final def jiraCloudUrl = removeTailingSlash(gs.issueTrackerUrl)
    def getServiceDesks = {
        //noinspection GroovyAssignabilityCheck
        def _serviceDeskPages = paginate(
                50,
                fn2 { Integer offset, Integer limit ->
                    def queryParams = seq(
                            pair("start", offset as String),
                            pair("limit", limit as String)
                    )
                    def qParamsStr = scala.collection.JavaConversions
                            .bufferAsJavaList(
                            queryParams
                                    .tail()
                                    .tail()
                                    .<scala.Tuple2<String, String>>toBuffer()
                    )
                            .inject ("") { resultStr, qp ->
                        resultStr + "&"+ qp._1()+"="+qp._2()
                    }
                    def response
                    try {
                        //noinspection GroovyAssignabilityCheck
                        response = await(await(httpClient.authenticate(
                                none(),
                                httpClient
                                        .ws()
                                        .url(jiraCloudUrl+"/rest/servicedeskapi/servicedesk")
                                        .withQueryString(queryParams)
                                        .withMethod("GET"),
                                gs
                        )).get())
                    } catch (Exception e) {
                        throw issueLevelError2(
                                "Unable to get the service desks, please contact Exalate Support: " +
                                        "\nRequest: GET /rest/servicedeskapi/servicedesk?start="+ offset +"&limit="+limit + qParamsStr +
                                        "\nError: " + e.message
                                , e
                        )
                    }
                    if (response.status() != 200) {
                        throw issueLevelError(
                                "Can not get the service desks (status "+ response.status() +"), please contact Exalate Support: " +
                                        "\nRequest: GET /rest/servicedeskapi/servicedesk?start="+ offset +"&limit="+limit + qParamsStr+
                                        "\nResponse: "+ response.body()
                        )
                    }
                    def resultStr = response.body()
                    groovy.json.JsonSlurper s = new groovy.json.JsonSlurper()
                    def resultJson
                    try {
                        resultJson = s.parseText(resultStr)
                    } catch (Exception e) {
                        throw issueLevelError2("Can not parse the service desk json, please contact Exalate Support: " + resultStr, e)
                    }
                    /*
{
  "_expands": [],
  "size": 3,
  "start": 3,
  "limit": 3,
  "isLastPage": false,
  "_links": {
    ...
  },
  "values": [
    {
      "id": "10001",
      "projectId": "11001",
      "projectName": "IT Help Desk",
      "projectKey": "ITH",
      "_links": {
        "self": "https://your-domain.atlassian.net/rest/servicedeskapi/servicedesk/10001"
      }
    },
    ...
  ]
}
                    */
                    if (!(resultJson instanceof Map)) {
                        throw issueLevelError("Boards json has unrecognized structure, please contact Exalate Support: " + resultStr)
                    }
                    resultJson as Map<String, Object>
                },
                fn { Map<String, Object> page -> (page.values as List<Map<String, Object>>).size() }
        )
        _serviceDeskPages
                .collect { it.get("values") as List<Map<String, Object>> }
                .flatten()
    }
    def getRequestTypesForServiceDesk = { String sdId ->
        //noinspection GroovyAssignabilityCheck
        def _serviceDeskPages = paginate(
                50,
                fn2 { Integer offset, Integer limit ->
                    def queryParams = seq(
                            pair("start", offset as String),
                            pair("limit", limit as String)
                    )
                    def qParamsStr = scala.collection.JavaConversions
                            .bufferAsJavaList(
                            queryParams
                                    .tail()
                                    .tail()
                                    .<scala.Tuple2<String, String>>toBuffer()
                    )
                            .inject ("") { resultStr, qp ->
                        resultStr + "&"+ qp._1()+"="+qp._2()
                    }
                    def response
                    try {
                        //noinspection GroovyAssignabilityCheck
                        response = await(await(httpClient.authenticate(
                                none(),
                                httpClient
                                        .ws()
                                        .url(jiraCloudUrl+"/rest/servicedeskapi/servicedesk/$sdId/requesttype".toString())
                                        .withQueryString(queryParams)
                                        .withMethod("GET"),
                                gs
                        )).get())
                    } catch (Exception e) {
                        throw issueLevelError2(
                                "Unable to get the request types for the service desk $sdId, please contact Exalate Support: ".toString() +
                                        "\nRequest: GET /rest/servicedeskapi/servicedesk/$sdId/requesttype?start="+ offset +"&limit="+limit + qParamsStr +
                                        "\nError: " + e.message
                                , e
                        )
                    }
                    if (response.status() != 200) {
                        throw issueLevelError(
                                "Can not get request types for the service desk $sdId (status ".toString()+ response.status() +"), please contact Exalate Support: " +
                                        "\nRequest: GET /rest/servicedeskapi/servicedesk/$sdId/requesttype?start="+ offset +"&limit="+limit + qParamsStr+
                                        "\nResponse: "+ response.body()
                        )
                    }
                    def resultStr = response.body()
                    groovy.json.JsonSlurper s = new groovy.json.JsonSlurper()
                    def resultJson
                    try {
                        resultJson = s.parseText(resultStr)
                    } catch (Exception e) {
                        throw issueLevelError2("Can not parse the service desk request types json, please contact Exalate Support: " + resultStr, e)
                    }
                    /*
{
  "_expands": [],
  "size": 3,
  "start": 3,
  "limit": 3,
  "isLastPage": false,
  "_links": {
    ...
  },
  "values": [
    {
      "_expands": [],
      "id": "11001",
      "_links": { ... },
      "name": "Get IT Help",
      "description": "Get IT Help",
      "helpText": "Please tell us clearly the problem you have within 100 words.",
      "issueTypeId": "12345",
      "serviceDeskId": "28",
      "groupIds": [
        "12"
      ],
      "icon": {
        ...
      }
    },
    ...
  ]
}
                    */
                    if (!(resultJson instanceof Map)) {
                        throw issueLevelError("Service desk request types json has unrecognized structure, please contact Exalate Support: " + resultStr)
                    }
                    resultJson as Map<String, Object>
                },
                fn { Map<String, Object> page -> (page.values as List<Map<String, Object>>).size() }
        )
        _serviceDeskPages
                .collect { it.get("values") as List<Map<String, Object>> }
                .flatten()
    }
    def _serviceDesks = getServiceDesks()
    // Finding the service desk for local project
    def _sd = _serviceDesks.find { it.projectKey == issue.projectKey }
    if (_sd == null) {
        return null
    }
    def _requestTypes = getRequestTypesForServiceDesk(_sd.id as String)
    def _requestType = _requestTypes.find { it.issueTypeId == nodeHelper.getIssueType(issue.typeName).id}
    if (_requestType == null) {
        return null
    }
    serviceDeskHelper.getRequestTypeById(_sd.id as String, _requestType.id as String)
})()
if (requestType != null) {
    issue.customFields."Request Type".value = requestType
}
// END: CUSTOMER REQUEST TYPE SYNC


See Also