Code to create a request in JSD from a customer does not work

Hello!

We had a question in Moscow Atlassian User Group that the following code does not work:

ServiceDeskCustomerRequestService customerRequestService = ComponentAccessor.getOSGiComponentInstanceOfType(ServiceDeskCustomerRequestService.class);
        ServiceDeskService serviceDeskService = ComponentAccessor.getOSGiComponentInstanceOfType(ServiceDeskService.class);
        RequestTypeService requestTypeService = ComponentAccessor.getOSGiComponentInstanceOfType(RequestTypeService.class);

        ApplicationUser curUser = ComponentAccessor.getUserUtil().getUserByName("admin");
        int portalID = 1;
        ServiceDesk serviced = serviceDeskService.getServiceDeskById(curUser, portalID);


        RequestTypeQuery requestTypeQuery = requestTypeService.newQueryBuilder()
                .serviceDesk(serviced.getId())
                .build();

        PagedResponse<RequestType> requestTypes = requestTypeService.getRequestTypes(curUser, requestTypeQuery);

        RequestType rqType = requestTypes.findFirst().get();
___

CustomerRequestCreateParameters reqBuilder = customerRequestService.newCreateBuilder()
                .serviceDesk(serviced)
                .requestType(rqType)
                .fieldValue(
                        FieldId.withId("summary"),
                        FieldInputValue.withValue(params.getSummary())
                )
                .customerRequestChannelSource(CustomerRequestChannelSource.PORTAL)
                .raiseOnBehalfOf(curUser.getName())  
//                .requestParticipants(user)
                .build();
//
        CustomerRequest customerRequest = customerRequestService.createCustomerRequest(curUser, reqBuilder);

This code must create a Jira Service Desk request but when a customer tries to create a request using this code there is an error in the logs:

ERROR      [feature.customer.request.CustomerRequestManager] Request creation caused a validation error that should have been handled by JSD code instead of JIRA: The issue type selected is invalid.

First of all, let’s have a look at lines like this:

ServiceDeskService serviceDeskService = ComponentAccessor.getOSGiComponentInstanceOfType(ServiceDeskService.class);

It is the wrong way to get services in Atlassian apps. This way of getting services goes from Groovy like apps. But you should not do like this in you own apps. Your code becomes very difficult for unit testing because you use static methods. The correct way is to inject services via the constructor or properties.

Also have a look at this line:

ApplicationUser curUser = ComponentAccessor.getUserUtil().getUserByName("admin");

Beside it uses a static method from the ComponentAcessor class, it also uses a deprecated method getUserByName and also hardcodes the admin user. We should not use deprecated methods or hardcodes.

There is also another line:

int portalID = 1;

Again we hardcoded our value. I am not sure how this code must work in production that is why I will not get rid of hardcodes. But in the production code there should be no hardcodes.

Having said it I rewrote this code like this:

@Named
public class MyServlet extends HttpServlet {
    private static final Logger log = LoggerFactory.getLogger(MyServlet.class);


    final private ServiceDeskCustomerRequestService serviceDeskCustomerRequestService;
    final private ServiceDeskService serviceDeskService;
    final private RequestTypeService requestTypeService;
    final private CustomerContextService customerContextService;
    final private JiraAuthenticationContext jiraAuthenticationContext;
    final private UserManager userManager;

    @Inject
    public MyServlet(@ComponentImport
                             ServiceDeskCustomerRequestService serviceDeskCustomerRequestService,
                     @ComponentImport
                             ServiceDeskService serviceDeskService,
                     @ComponentImport
                             RequestTypeService requestTypeService,
                     @ComponentImport
                             CustomerContextService customerContextService,
                     @ComponentImport
                             JiraAuthenticationContext jiraAuthenticationContext,
                     @ComponentImport
                             UserManager userManager

    ) {

        this.serviceDeskCustomerRequestService = serviceDeskCustomerRequestService;
        this.serviceDeskService = serviceDeskService;
        this.requestTypeService = requestTypeService;
        this.customerContextService = customerContextService;
        this.jiraAuthenticationContext = jiraAuthenticationContext;
        this.userManager = userManager;
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {


            ApplicationUser customerUser = userManager.getUserByName("agrant-sd-demo");

            int portalID = 1;
            ServiceDesk serviced = serviceDeskService.getServiceDeskById(customerUser, portalID);
            RequestTypeQuery requestTypeQuery = requestTypeService.newQueryBuilder()
                    .serviceDesk(serviced.getId())
                    .build();
            PagedResponse<RequestType> requestTypes = requestTypeService.getRequestTypes(customerUser, requestTypeQuery);
            RequestType rqType = requestTypes.findFirst().get();
            CustomerRequestCreateParameters reqBuilder = serviceDeskCustomerRequestService.newCreateBuilder()
                    .serviceDesk(serviced)
                    .requestType(rqType)
                    .fieldValue(
                            FieldId.withId("summary"),
                            FieldInputValue.withValue("my summary")
                    )
                    .customerRequestChannelSource(CustomerRequestChannelSource.PORTAL)
                    .build();
            CustomerRequest customerRequest = serviceDeskCustomerRequestService.createCustomerRequest(customerUser, reqBuilder);

        resp.setContentType("text/html");
        resp.getWriter().write("<html><body>Hello World</body></html>");
    }

}

I made a servlet out of it to make it easier to test. Also I injected all services in the constructor:

public MyServlet(@ComponentImport
                             ServiceDeskCustomerRequestService serviceDeskCustomerRequestService,
                     @ComponentImport
                             ServiceDeskService serviceDeskService,
                     @ComponentImport
                             RequestTypeService requestTypeService,
                     @ComponentImport
                             CustomerContextService customerContextService,
                     @ComponentImport
                             JiraAuthenticationContext jiraAuthenticationContext,
                     @ComponentImport
                             UserManager userManager

    )

And I got rid of the deprecated method:

ApplicationUser customerUser = userManager.getUserByName("agrant-sd-demo");

I changed my user to a customer user and ran this code.

I received an error on this line:

ServiceDesk serviced = serviceDeskService.getServiceDeskById(customerUser, portalID);

The problem was that my customer could not select a service desk object because my customer did not have enough permissions. That is why I rewrote my code like this:

            ApplicationUser adminUser = userManager.getUserByName("admin");            
            ApplicationUser customerUser = userManager.getUserByName("agrant-sd-demo");

            int portalID = 1;
            ServiceDesk serviced = serviceDeskService.getServiceDeskById(adminUser, portalID);
            RequestTypeQuery requestTypeQuery = requestTypeService.newQueryBuilder()
                    .serviceDesk(serviced.getId())
                    .build();
            PagedResponse<RequestType> requestTypes = requestTypeService.getRequestTypes(adminUser, requestTypeQuery);
            RequestType rqType = requestTypes.findFirst().get();
            CustomerRequestCreateParameters reqBuilder = serviceDeskCustomerRequestService.newCreateBuilder()
                    .serviceDesk(serviced)
                    .requestType(rqType)
                    .fieldValue(
                            FieldId.withId("summary"),
                            FieldInputValue.withValue("my summary")
                    )
                    .customerRequestChannelSource(CustomerRequestChannelSource.PORTAL)
                    .raiseOnBehalfOf(customerUser.getName())
                    .build();
            CustomerRequest customerRequest = serviceDeskCustomerRequestService.createCustomerRequest(adminUser, reqBuilder);

I added this line:

ApplicationUser adminUser = userManager.getUserByName("admin");    

Changed everywhere customerUser to adminUser and added this line:

.raiseOnBehalfOf(customerUser.getName())

Which means that I select the ServiceDesk and RequestType objects by the admin user and my request will be created also by the admin user but on behalf of the customer user.

And miracle happened the code was executed and a request was created. But…

But if we have a look at the created request we will discover that the reporter is our customer user but the creator is our admin user:

No, it is not what we want! The creator user must be also the customer user.

Now let’s think over.

Who are customers? Customers are users who do not have a Jira license but they still can create issues in Jira. Before Jira Service desk was builtin in Jira, a user who did not have a license could not create an issue. So, how could Atlassian make a user who does not have a license be able to create issues? Good question.

Let’s have a look at the Jira Service Desk Rest Api. I typed in google:

jira servicedesk api java

I got this link:

https://docs.atlassian.com/jira-servicedesk/3.9.1/

I suggested that there must be some kind of customer service which would do something special with permissions and it must be related to customers. I found the following package:

com.atlassian.servicedesk.api.customer

I opened the link:

Bingo! Exactly what we need. We can open this CustomerContextService and read intro:

A Service Desk customer context allows any "named user" access to JIRA Service Desk and JIRA Platform services.
This service allows you to setup customer context, execute some code and have the special JIRA Service Desk permissions apply to that called code.

Normally on the JIRA platform, only licensed users can login and get permission-ed access to services. JIRA Service Desk, however, allows exceptions to this. In JIRA Service Desk EVERY named user is considered a "customer" when they are asking for help. This service allows you to enter a "customer context" to run code in the name of that user (free or otherwise).

Everything is nicely explained to us. Let’s use this service:

customerContextService.runInCustomerContext(() -> {
            ApplicationUser customerUser = userManager.getUserByName("agrant-sd-demo");
            jiraAuthenticationContext.setLoggedInUser(customerUser);
            int portalID = 1;
            ServiceDesk serviced = serviceDeskService.getServiceDeskById(customerUser, portalID);
            RequestTypeQuery requestTypeQuery = requestTypeService.newQueryBuilder()
                    .serviceDesk(serviced.getId())
                    .build();
            PagedResponse<RequestType> requestTypes = requestTypeService.getRequestTypes(customerUser, requestTypeQuery);
            RequestType rqType = requestTypes.findFirst().get();
            CustomerRequestCreateParameters reqBuilder = serviceDeskCustomerRequestService.newCreateBuilder()
                    .serviceDesk(serviced)
                    .requestType(rqType)
                    .fieldValue(
                            FieldId.withId("summary"),
                            FieldInputValue.withValue("my summary")
                    )
                    .customerRequestChannelSource(CustomerRequestChannelSource.PORTAL)
                    .build();
            CustomerRequest customerRequest = serviceDeskCustomerRequestService.createCustomerRequest(customerUser, reqBuilder);
        });

I ran my method in the CustomerContext:

customerContextService.runInCustomerContext(() -> {
....
});

Also I switched my authentication context to a customer user with this code:

jiraAuthenticationContext.setLoggedInUser(customerUser);

And miracle happened!

Here is the final code:

package ru.matveev.alexey.wrongcode.servlet;

import com.atlassian.jira.security.JiraAuthenticationContext;
import com.atlassian.jira.user.ApplicationUser;
import com.atlassian.jira.user.UserUtils;
import com.atlassian.jira.user.util.UserManager;
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
import com.atlassian.servicedesk.api.ServiceDesk;

import com.atlassian.servicedesk.api.ServiceDeskService;
import com.atlassian.servicedesk.api.customer.CustomerContextService;
import com.atlassian.servicedesk.api.field.FieldId;
import com.atlassian.servicedesk.api.field.FieldInputValue;
import com.atlassian.servicedesk.api.request.CustomerRequest;
import com.atlassian.servicedesk.api.request.CustomerRequestChannelSource;
import com.atlassian.servicedesk.api.request.CustomerRequestCreateParameters;
import com.atlassian.servicedesk.api.request.ServiceDeskCustomerRequestService;
import com.atlassian.servicedesk.api.requesttype.RequestType;
import com.atlassian.servicedesk.api.requesttype.RequestTypeQuery;
import com.atlassian.servicedesk.api.requesttype.RequestTypeService;
import com.atlassian.servicedesk.api.util.paging.PagedResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Named
public class MyServlet extends HttpServlet {
    private static final Logger log = LoggerFactory.getLogger(MyServlet.class);


    final private ServiceDeskCustomerRequestService serviceDeskCustomerRequestService;
    final private ServiceDeskService serviceDeskService;
    final private RequestTypeService requestTypeService;
    final private CustomerContextService customerContextService;
    final private JiraAuthenticationContext jiraAuthenticationContext;
    final private UserManager userManager;

    @Inject
    public MyServlet(@ComponentImport
                             ServiceDeskCustomerRequestService serviceDeskCustomerRequestService,
                     @ComponentImport
                             ServiceDeskService serviceDeskService,
                     @ComponentImport
                             RequestTypeService requestTypeService,
                     @ComponentImport
                             CustomerContextService customerContextService,
                     @ComponentImport
                             JiraAuthenticationContext jiraAuthenticationContext,
                     @ComponentImport
                             UserManager userManager

    ) {

        this.serviceDeskCustomerRequestService = serviceDeskCustomerRequestService;
        this.serviceDeskService = serviceDeskService;
        this.requestTypeService = requestTypeService;
        this.customerContextService = customerContextService;
        this.jiraAuthenticationContext = jiraAuthenticationContext;
        this.userManager = userManager;
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {


        customerContextService.runInCustomerContext(() -> {
            ApplicationUser customerUser = userManager.getUserByName("agrant-sd-demo");
            jiraAuthenticationContext.setLoggedInUser(customerUser);
            int portalID = 1;
            ServiceDesk serviced = serviceDeskService.getServiceDeskById(customerUser, portalID);
            RequestTypeQuery requestTypeQuery = requestTypeService.newQueryBuilder()
                    .serviceDesk(serviced.getId())
                    .build();
            PagedResponse<RequestType> requestTypes = requestTypeService.getRequestTypes(customerUser, requestTypeQuery);
            RequestType rqType = requestTypes.findFirst().get();
            CustomerRequestCreateParameters reqBuilder = serviceDeskCustomerRequestService.newCreateBuilder()
                    .serviceDesk(serviced)
                    .requestType(rqType)
                    .fieldValue(
                            FieldId.withId("summary"),
                            FieldInputValue.withValue("my summary")
                    )
                    .customerRequestChannelSource(CustomerRequestChannelSource.PORTAL)
                    .build();
            CustomerRequest customerRequest = serviceDeskCustomerRequestService.createCustomerRequest(customerUser, reqBuilder);
        });
        resp.setContentType("text/html");
        resp.getWriter().write("<html><body>Hello World</body></html>");
    }

}

Leave a Reply

%d bloggers like this: