Generate A JIRA Issue In Salesforce Apex Using JIRA Integration.

A Jira issue refers to a specific task, defect, or piece of work within a Jira project. Users or teams create Jira issues to document bug fixes, new features, enhancements, or tasks. These issues are typically generated within the Jira project itself. This guide will provide detailed instructions on integrating Jira into Salesforce Apex and how to create a Jira issue directly from Salesforce.

Jira is widely used as a project management tool.

Use Case for Jira Integration in Apex

Create a Jira issue from the Case record when the button ‘Create Jira Issue’ is clicked. Get the issue id after creation and put that in the Jira ID

Solution

To create a Jira issue in Salesforce, we will use the Jira REST API. We can integrate Jira REST API using Apex and Salesforce Flow. For this post, we will use Salesforce Apex to integrate Jira.

We will create a Custom Metadata type for storing Jira information like project, issue type and due date. This information will be used to integrate and create issues in specific projects. We will also add one field JiraId in the Case object to update the generated Jira Id. This Jira id can be used later for retrieval for reporting purposes.

Implementation

To implement this we have to follow these steps

  • Create Named and External Credential
  • Create custom metadata type
  • Create Apex Classes for Jira Integration
  • Create an Apex Invocable class
  • Create a flow to call the apex method from the Button
  • Create a button to call Flow
  • Test Functionality

Let us handle each step to complete integration with Jira.

1. Establish Named and External Credentials

To establish a secure connection between Salesforce and Jira, we will need a named credential. Follow steps 1 to 5 outlined in the blog post titled “Verify API Connection in Flow HTTP Callout” to create a named credential, external credential, named principal, and permission set. Ensure to assign the permission set to your user or the required user. Assigning a permission set is crucial for the user, as without it, the integration process cannot be completed.

For this tutorial, the API URL is https://tenetizer.atlassian.net/rest/api/3/issue/. However, please note that your API URL may differ, so be sure to verify it with your Jira project.

2. Create custom metadata type

Create a custom metadata type Jira Configuration (JiraConfig__mdt) to store Jira project information. Create below metadata fields

Field LabelField APIData Type
Due DateDueDate__cNumber(2, 0)
Issue Type IdIssueTypeId__cText(20)
Project KeyProjectKey__cText(20)

Metadata Types Fields

Add the below record in this metadata object.

LabelJira Config NameDue DateProject KeyIssue Type Id
PRJ1PRJ17SFDCTEAM10004

If you have multiple projects, you can add all of them and use them in Apex accordingly. This information will be different for you, so add information accordingly.

3. Create Apex Class for Jira Integration

Create below apex classes for integrating Jira.

  • BaseException – Common exception class.
  • Jira – It is a data transformation class.
  • JiraResponse – It is a callout response wrapper class.
  • JiraService – This class holds code for the Jira API Integration.
  • JiraTaskController – It is a controller class to call JiraService apex. It will get metadata information and current record information to use in Jira issue creation.
public class BaseException extends Exception {

}
public class Jira {
    public string ProjectKey{get;set;}
    public string IssueTypeId{get;set;}
    public string TaskDescription{get;set;}
    public string TaskSummary{get;set;}
    public string DueDate{get;set;}
}
public class JiraResponse{
	public String id;	//10004
	public String key;	//SFDCTEAM-5
	public String self;	//https://salesforcecodex.atlassian.net/rest/api/3/issue/10004
	public static JiraResponse parse(String json){
		return (JiraResponse) System.JSON.deserialize(json, JiraResponse.class);
	}
}
public class JiraService {    
	public static JiraResponse createJiraTicket(Jira jira)
    {
       	//Request Body Sample
        /*
        {
            "fields":
            {
                "project":
                {
                    "key":"SFDCTEAM"
                },
                "summary":"Issue1 from PostMan",
                "issuetype":{
                    "id":"10004"
                },
                "duedate":"2024-05-15",
                "description": {
                    "content": [
                        {
                        "content": [
                            {
                            "text": "Order entry fails when selecting supplier.",
                            "type": "text"
                            }
                        ],
                        "type": "paragraph"
                        }
                    ],
                    "type": "doc",
                    "version": 1
                    }
            }
        }*/
                
        string REQUEST_BODY='{"fields":{"project":{"key":"PKEY"},"summary":"SUMRY","issuetype":{"id":"ISTYPE"},"duedate":"DDATE","description":{"content":[{"content":[{"text":"TDESC","type":"text"}],"type":"paragraph"}],"type":"doc","version":1}}}';
      	      
        HttpRequest request=new HttpRequest();
        HttpResponse response=new HttpResponse();
        Http httpRootObj = new Http();
        //Authentication token is added in Named Credential
        request.setEndpoint('callout:JiraNC/rest/api/3/issue/');
        request.setHeader('Content-Type','application/json');
        request.setHeader('Accept','application/json');
        request.setMethod('POST');
       	REQUEST_BODY=REQUEST_BODY.replace('PKEY',jira.ProjectKey)
            .replace('SUMRY',jira.TaskSummary)
            .replace('ISTYPE',jira.IssueTypeId)
        	.replace('DDATE',jira.DueDate)
            .replace('TDESC',jira.TaskDescription);
        system.debug(REQUEST_BODY);
            
        request.setBody(REQUEST_BODY);
        request.setTimeout(60000);
        try 
        {
            response= httpRootObj.send(request);
            system.debug(response.getBody());
            return JiraResponse.parse(response.getBody());
        } 
        catch(Exception expObj) 
        {
            throw new BaseException(expObj);
        }   
    }
}
public class JiraTaskController {
    
    public static void createJiraTask(string recordId){
        
        List<JiraConfig__mdt> configs=[Select DueDate__c,IssueTypeId__c,ProjectKey__c from JiraConfig__mdt where DeveloperName='PRJ1'];
        if(configs.size()>0)
        {
            List<Case> cases=[Select Subject,Description,JiraId__c from Case where Id=:recordId];
            if(cases.size()>0){
                Jira jira=new Jira();
                jira.TaskSummary=cases[0].Description;
                jira.TaskDescription=cases[0].Subject;
                jira.DueDate=System.now().addDays(Integer.valueOf(configs[0].DueDate__c)).format('yyyy-MM-dd');
                jira.IssueTypeId=configs[0].IssueTypeId__c;
                jira.ProjectKey=configs[0].ProjectKey__c;
                system.debug('jira:'+JSON.serializepretty(jira));
                JiraResponse respo=JiraService.createJiraTicket(jira);
                cases[0].JiraId__c=respo.id;
                //Update current Case
                update cases[0];
            }
        }
    }
}

Based on the above classes it will return the below JSON response which will be deserialzed using JiraResponse class.

{
    "id": "10005",
    "key": "SFDCTEAM-6",
    "self": "https://salesforcecodex.atlassian.net/rest/api/3/issue/10005"
}

4. Create an Apex Invocable class

As we need to create the Jira Issue on the button click, we have to call the above classes from the button. We can not call the apex class directly from the button, we need to call this class from the Lightning component or Screen flow. We will use screen flow for this purpose.

To use the apex class in flow, we need to create an invocable apex class. Create the below apex that will be called from the screen flow. Method createIssue will call JiraTaskController to create an issue based on the passed parameter from the flow.

public class JiraServiceInvocable {    
    @InvocableMethod(Label='Create Jira Issue' Description='Create Jira Issue')
    public static List<boolean> createIssue(List<string> recordIds) {
        Boolean isSuccess=JiraTaskController.createJiraTask(recordIds[0]);
        return new List<boolean>{isSuccess};
    }
}

5. Create a Screen Flow to Invocable Method

Create a screen flow Jira Issue Creation and add action on it. On action select above created Invocable Method Create Jira Issue. This action requires a current record id for method execution so create a text variable with the API name recordId. The variable should be marked checked for Available for input.

6. Create an action button on the object

Create an action button to create Jira issue on the case object. This button will call the above-created screen flow.

LabelCreate Jira Issue
Action TypeFlow
FlowJira Issue Creation
Standard Label Type–None–
NameCreate_Jira_Issue

Jira Integration | Create Action Button | SalesforceCodex

Create an Action Button on the Salesforce Object

7. Add a Button to the Page Layout

Add the above-created action button to the page layout. This button will call flow to execute API Integration.

Jira Integration | Add Button to Page Layout | SalesforceCodex

Add Button to Salesforce Page Layout