Part III - The Code

RESTful Systems

REST stands for Representational State Transfer and the systems confirming to the constraints of REST are called RESTful systems. RESTful systems usually (not always) communicate over HTTP protocol and expose their services through REST URLs; the method (GET, POST, DELETE, PUT, PATCH, TRACE etc. ) of which depends on the direction of data transfer and action viz.,

  • GET for retrieving the data from remote system,
  • POST for posting data/creating a resource on the remote system,
  • DELETE for deleting a resource identified by the URL etc.

For the URLs to work with these methods, the external system must support and implement those.

Note: Firstly you need to claim a site (similar to inteygrate.atlassian.net) for yourself by signing up for a free 30 days trial here

Following is a sample REST URLs to fetch the details of an issue by passing the issue key.

REST URL: https://inteygrate.atlassian.net/rest/api/3/issue/SJI-3
JIRA REST URL and JSON Response

JSON Preview in Console
JSON Preview in Console


JSON2Apex

Now let us look at some sample code where we will get a list of issues from Jira using a Controller class and simply list them in tabular form using a corresponding Visualforce page. For this we will create two objects, first JiraIssue data model for storing the issues and second Jira Adapter to save the Jira credentials that will be passed in the REST URL to authenticate into Jira.

Since we will be retrieving the list of issues in JSON format, we first need to define the issue class; the structure of which will be the exact mirror image of the JSON response. For e.g. the class for JSON {issue:{field1:val1,field2:val2}} will be as below,

public class issue{
 public string field1;
 public string field2;
}

The JSON in the above example is fairly simple and easy to parse. For more complex JSON structures, we can use JSON2Apex. This tool will generate a strongly typed Apex code for parsing the JSON structure. We can simply paste in the JSON response that we saw above, and the tool will generate the necessary Apex code.

The below class has been generated from the same tool.

public class issue{
      public string expand;
      public fields fields;
      public string id;
      public string key;
      public string self;
}
public class Status {
    public String self;
    public String description;
    public String iconUrl;
    public String name;
    public String id;
    public StatusCategory statusCategory;
}

public class Assignee {
    public String self;
    public String name;
    public String key;
    public String emailAddress;
    public AvatarUrls avatarUrls;
    public String displayName;
    public Boolean active;
    public String timeZone;
}

public class Comment {
    public Integer startAt;
    public Integer maxResults;
    public Integer total;
    public List<Comments> comments;
}

public String expand;
public String id;
public String self;
public String key;
public Fields fields;

public class links {
    public String self;
}

public class Priority {
    public String self;
    public String iconUrl;
    public String name;
    public String id;
}

public class Comments {
    public String self;
    public String id;
    public Assignee author;
    public String body;
    public Assignee updateAuthor;
    public String created;
    public String updated;
}

public class Aggregateprogress {
    public Integer progress;
    public Integer total;
}

public class Watches {
    public String self;
    public Integer watchCount;
    public Boolean isWatching;
}

public class StartTime {
    public String iso8601;
    public String friendly;
    public Long epochMillis;
}

public class Customfield_10025 {
    public Integer id;
    public String name;
    public links links;
    public List<FixVersions> completedCycles;
    public OngoingCycle ongoingCycle;
}

public class Customfield_10026 {
    public String errorMessage;
}

public class Customfield_10015 {
    public String errorMessage;
    public I18nErrorMessage i18nErrorMessage;
}

public class OngoingCycle {
    public StartTime startTime;
    public StartTime breachTime;
    public Boolean breached;
    public Boolean paused;
    public Boolean withinCalendarHours;
    public GoalDuration goalDuration;
    public GoalDuration elapsedTime;
    public GoalDuration remainingTime;
}

public class Project {
    public String self;
    public String id;
    public String key;
    public String name;
    public AvatarUrls avatarUrls;
}

public class StatusCategory {
    public String self;
    public Integer id;
    public String key;
    public String colorName;
    public String name;
}

public class Worklog {
    public Integer startAt;
    public Integer maxResults;
    public Integer total;
    public List<FixVersions> worklogs;
}

public class Fields {
    public Issuetype issuetype;
    public Object timespent;
    public Project project;
    public List<FixVersions> fixVersions;
    public Object aggregatetimespent;
    public Object resolution;
    public Object resolutiondate;
    public Integer workratio;
    public String lastViewed;
    public Watches watches;
    public String created;
    public Object customfield_10020;
    public Object customfield_10021;
    public Priority priority;
    public Customfield_10025 customfield_10025;
    public List<FixVersions> labels;
    public Customfield_10026 customfield_10026;
    public List<FixVersions> customfield_10016;
    public Object customfield_10017;
    public Object customfield_10018;
    public String customfield_10019;
    public Object timeestimate;
    public Object aggregatetimeoriginalestimate;
    public List<FixVersions> versions;
    public List<FixVersions> issuelinks;
    public Assignee assignee;
    public String updated;
    public Status status;
    public List<FixVersions> components;
    public Object timeoriginalestimate;
    public String description;
    public Object customfield_10010;
    public Object customfield_10011;
    public Object customfield_10012;
    public String customfield_10013;
    public Object customfield_10014;
    public FixVersions timetracking;
    public Customfield_10015 customfield_10015;
    public Object customfield_10005;
    public Object customfield_10006;
    public Object customfield_10007;
    public Object customfield_10008;
    public List<FixVersions> attachment;
    public Object customfield_10009;
    public Object aggregatetimeestimate;
    public String summary;
    public Assignee creator;
    public List<FixVersions> subtasks;
    public Assignee reporter;
    public Object customfield_10000;
    public Aggregateprogress aggregateprogress;
    public Object customfield_10001;
    public Object customfield_10002;
    public Object customfield_10003;
    public Object customfield_10004;
    public Object environment;
    public String duedate;
    public Aggregateprogress progress;
    public Comment comment;
    public Votes votes;
    public Worklog worklog;
}

public class FixVersions {
}

public class I18nErrorMessage {
    public String i18nKey;
    public List<FixVersions> parameters;
}

public class Issuetype {
    public String self;
    public String id;
    public String description;
    public String iconUrl;
    public String name;
    public Boolean subtask;
}

public class AvatarUrls {
    public String fortyEight;
    public String twentyFour;
    public String sixteen;
    public String thirtyTwo;
}

public class GoalDuration {
    public Integer millis;
    public String friendly;
}

public class Votes {
    public String self;
    public Integer votes;
    public Boolean hasVoted;
}

Controller

Following next is the controller code with the above issue class included. The code is very simple and just to give a brief, starting with controller we first fetch the Jira credentials from the single record of the Jira Adapter custom object as shown below.
Jira Adapter Custom Object

This credentials along with other search parameters are passed in the REST URL to the req.setEndpoint() method which is similar to hitting a URL in the web browser. There is another method as well to authenticate with Jira called OAuth but we aren't discussing it here. Post this step we simply parse through the JSON response and deserialize it to the issue class structure. You can see fields parameter commented in the URL as I am fetching all the fields of an issue in Jira. If you want only certain fields then you can pass the name of them as below so that only those fields will be retrieved.

 string fields   =   'key,'+
                     'assignee,'+
                     'project,'+
                     'summary,'+
                     'created,'+
                     'updated,'+
                     'priority,'+
                     'status';

Note: You might encounter an error System.JSONException: Apex Type unsupported in JSON: Object while deserializing. Please refer this solution on Stackoverflow and it will be resolved.

 public class JiraIntegrationController {

  public List<JiraIssue__c> supportIssues {get;set;}
  public String issueKey{get;set;}
  string searchKey = '';
  public string username;
  public string password;
  
  public List<JiraIssue__c> getSupportIssues(){
      return supportIssues;
  }
  
  public integer startAt = 0;
  public integer maxResults= 10;
 

/******************Issue Class*****************/

//ISSUE CLASS GOES HERE. REMOVED FOR BREVITY

/**********************************************/  

public void sameAsJIC(){

        Http http = new Http();
        HttpRequest req = new HttpRequest();
        req.setEndpoint(''+
                        '&os_username=' + username + 
                        '&os_password=' + password +
                        //'&fields=' + fields + 
                        '&startAt='+ startAt +
                        '&maxResults='+maxResults);
        req.setMethod('GET');

        HttpResponse res = http.send(req);
    
        // Log the JSON content
        System.debug('JSON Response: ' + res.getBody());
        
        String JSONContent = res.getBody();
        supportIssues = new List<JiraIssue__c>();

        JSONParser parserJira = JSON.createParser(JSONContent);
        while (parserJira.nextToken() != null) 
        {
            
            // Start at the array of issues.
            if (parserJira.getCurrentToken() == JSONToken.START_ARRAY) {
                while (parserJira.nextToken() != null) {
                    if ((parserJira .getCurrentToken() == JSONToken.START_OBJECT)){ 
                        issue jiraIssue = (issue)parserJira.readValueAs(issue.class);
                        JiraIssue__c supportIssue = new JiraIssue__c();
                        supportIssue.Issue_Key__c = jiraIssue.key;
                        supportIssue.Summary__c = jiraIssue.fields.summary;
                        supportIssues.add(supportIssue);
                    }
                   
                }
            }
        }
      
  }
  
  public JiraIntegrationController() {
        Jira_Adapter__c[] jiraUsers = [SELECT Username__c, Password__c FROM Jira_Adapter__c];
        if(jiraUsers.size() > 0){
            username = jiraUsers[0].Username__c;
            password = jiraUsers[0].Password__c;
        }
        sameAsJIC();
  }
}

Visualforce Page

Below is the final output page where we have simply fetched the Key and Summary of the issues and displayed in a table. If you are wondering how come the UI looks so different from usual Salesforce UI, then yes, it is because the page is designed using Salesforce Lightning Design System.

Visualforce page for sfdc-jira integration

And the code for the same is given below. The code is pretty straightforward but if you still have any query/issues please let me know in the comment section below.

<apex:page controller="JiraIntegrationController" showHeader="false" standardStylesheets="false" sidebar="false" applyHtmlTag="false" applyBodyTag="false" docType="html-5.0">    

<html xmlns="" xmlns:xlink="">    

<head>
  <title>Salesforce Lightning Design System Trailhead Module</title>
  <apex:stylesheet value="{!URLFOR($Resource.SLDS090, 'assets/styles/salesforce-lightning-design-system-vf.css')}" />
</head>    

<body>
  <!-- REQUIRED SLDS WRAPPER -->
 <div class="slds">    
  <!-- MASTHEAD -->
  <p class="slds-text-heading--label slds-m-bottom--small">Salesforce-Jira Integration Demo. UI desinged using Salesforce Lightning Design System</p>
  <!-- / MASTHEAD -->    

 <!-- PAGE HEADER -->
 <div class="slds-page-header" role="banner">
  <!-- LAYOUT GRID -->
  <div class="slds-grid">
  <!-- GRID COL -->
    <div class="slds-col">
    <!-- HEADING AREA -->
    <!-- MEDIA OBJECT = FIGURE + BODY -->
    <div class="slds-media">
      <div class="slds-media__figure">
       <span class="slds-avatar slds-avatar--large">
        <img src="{!URLFOR($Resource.SLDS090, 'assets/images/avatar1.jpg')}" alt="portrait" />
       </span>
      </div>
      <div class="slds-media__body">
       <p class="slds-text-heading--label">Issues</p>
       <h1 class="slds-text-heading--medium">My Issues</h1>
      </div>
   </div>
   <!-- / MEDIA OBJECT -->
   <!-- /HEADING AREA -->
   </div>

  </div>
  <!-- / LAYOUT GRID -->
  <p class="slds-text-body--small slds-m-top--x-small">COUNT items</p>
</div>
<!-- / PAGE HEADER -->

<!-- PRIMARY CONTENT WRAPPER -->
<div class="myapp">  
  <!-- ACCOUNT LIST TABLE -->
  <div id="accountList" class="slds-p-vertical--medium"></div>
  <!-- / ACCOUNT LIST TABLE -->    
</div>
<!-- / PRIMARY CONTENT WRAPPER -->

<!-- FOOTER -->
<footer role="contentinfo" class="slds-p-around--large">
  <!-- LAYOUT GRID -->
  <div class="slds-grid slds-grid--align-spread">
    <p class="slds-col">Salesforce-Jira Integration Example.</p>
    <p class="slds-col">&copy; inteygrate.com</p>
  </div>
  <!-- / LAYOUT GRID -->
</footer>
<!-- / FOOTER -->
</div>
<!-- / REQUIRED SLDS WRAPPER -->    
</body>    

<!-- JAVASCRIPT -->
<script>

  var outputDiv = document.getElementById("accountList");    
  var issues={};

  <apex:repeat value="{!supportIssues}" var="thing">
    issues['{!thing.Issue_Key__c}']='{!thing.Summary__c}';
  </apex:repeat>

function updateOutputDiv() {
	 var accountIcon = '<span class="slds-icon__container slds-icon-standard-account">';
     accountIcon += '<svg aria-hidden="true" class="slds-icon">';
     accountIcon += '<use xlink:href="{!URLFOR($Resource.SLDS090, 'assets/icons/standard-sprite/svg/symbols.svg#account')}"></use>';
     accountIcon += '</svg><span class="slds-assistive-text">Account</span></span>';  

     var html = '<div class="slds-scrollable--x"><table class="slds-table slds-table--bordered">';  

     html += '<thead><tr><th scope="col">Type</th>';
     html += '<th scope="col">Issue Key</th>';
     html += '<th scope="col">Summary</th></tr></thead><tbody>';  

    for (var key in issues) {
         html += '<tr><td>' + accountIcon + '</td>';
         html += '<td>' + key+ '</td>';
         html += '<td>' + issues[key] + '</td></tr>';
     };
     html = html + '</tbody></table></div>';
     outputDiv.innerHTML = html;
}

  updateOutputDiv();
  return false;
}

</script>
<!-- / JAVASCRIPT -->
</html>
</apex:page>

Next, I will be writing a separate post for the Batch class that can be scheduled to run such that the issues in Salesforce and Jira can be put in Sync without any manual intervention.

Test Class

Click here to check the test class for apex methods making callout as is in our case.

You’ve successfully subscribed to inteygrate
Welcome back! You’ve successfully signed in.
Great! You’ve successfully signed up.
Your link has expired
Success! Check your email for magic link to sign-in.