Jira is a cloud-based issue tracking product which is developed by Atlassian. It provides bug tracking, issue tracking and project management functions. Jira is a powerful tool which is used by software developers to track, organize and prioritize bugs and improvements for certain software releases.
Jira software is popular among project managers across the industries. All the MNC used JIRA to manage the release and priorities of the work. It's easy to plan and track the project progress using the interactive dashboards.
Atlassian Jira and Salesforce Integration
Salesforce JIRA Integration provides ongoing availability to the client and manages to the respective work item from backend. This allows coordination between groups who involved in various task. Client issues will be settled faster and client communication ends up being seamless and consistent.
By integrating Salesforce and JIRA, we can reduce human error with client coordination. It helps to keep client needs clearer, makes consistent correspondences with the clients and diminishes the customer issues resolution time. There are some scenarios where clients will use integration, which will help to manage customer service or release management.
An organization that already uses Jira management operations and recently decided to migrate to the Salesforce service cloud.
Organization wants to use Salesforce features such as reporting, Dashboards and forecasting features.
Better customer support, such as user who is working on Salesforce if they face any issue, directly they can create in Salesforce, which will be sync in JIRA and support team can help them faster to resolve the issue.
Advantage
Complete transparency into each customer issue
Faster resolution of customer issues
Increased productivity
Higher customer satisfaction score
Cross Selling
Sales and marketing team can more likely to be able to offer a customer more relevant deals. Agents can see synchronized updates and comments in Salesforce and Jira. Which help to do cross selling of the product.
Reporting
With the help of Salesforce and Jira integration up to date customer information will be with your sales and marketing reports. You can display a list of Salesforce records in a table and the data set with restricted by specifying conditions, also advanced reporting and integrated reports across Jira from Salesforce data can be generated for product planning.
Time saving
Time is money and productivity and this is particularly important for sales and customer account managers. Get the right information in front of them in a user-friendly way. They did not to fill the details manually in both system or use both application to track the data as all data will be sync, Jira team can track all the details of the open issues.
End to End business Journey
Salesforce is used for account management and Jira is used for incident management all the issue which usually company have, those will be tracked from the JIRA which will have all info about process and sub issue tickets. A customer who has been waiting for a product or service to be delivered is unlikely to be responsive to a sales agent not aware of this delay. Staff on the sales side can create issues in Jira from Salesforce and multiple cases can be mapped to a single Jira issue.
Salesforce two way sync with Jira
When Jira apps integrated with Salesforce, information related to customer requests and case management flow bi-directionally between teams providing end-to-end traceability. This will help to make better customer relations in collaboration with ITSM, development and other DevOps teams.
Let’s take an Example – A company has Salesforce with Jira Service Desk. When a customer reports a defect, customer service agent will registers a case in CRM system. This case presents to Jira Service Desk which will be synced with Salesforce service cloud which can be easily access by the developer and get all the customer information and about the issue. This will save the time as well human error to manually check the system of the present open case. In the same manner when the issue will be fixed developer can update the status in Salesforce application itself which will be synced with Jira and close the ticket.
Jira Rest API
The Jira REST APIs are used to interact with the Jira Server applications remotely, for example- when configuring webhooks. The Jira Server platform provides the REST API for common features like issues and workflows.
The Jira Software and Jira Service Desk applications have REST APIs for their application specific features like sprints (Jira Software) or customer requests (Jira Service Desk).
To simplify API responses, the Jira REST API uses resource expansion. This means that the API will only return parts of the resource when explicitly requested. This helps you avoid problems that can occur when you request too little information or too much information.
https://jira.atlassian.com/rest/api/latest/issue/JRA-9?expand=names,renderedFields
Jira uses pagination to Apex Callouts which are two types
Web service callouts to SOAP web services that use XML as a medium of transportation and require a WSDL (Web Services Definition Language) for code generation.
HTTP callouts to a service that use JSON as a medium of transportation and relies on REST APIs provided by the external service.
Example:
{ "startAt" : 0, "maxResults" : 10, "total": 200, "values": [ { /* result 0 / }, { / result 1 / }, { / result 2 */ } ] }
JIRA API Implementation
REST stands for Representational State Transfer and the systems confirming to the constraints of REST are called RESTful systems. RESTful systems usually 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.
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.
Most operations in this API require permissions. The calling user must have the required permissions for an operation to use it. The app user must have the required permissions for the operation and the app must have scopes that permit the operation.
Administer the Cloud site
Administer Jira
Administer a project in Jira
Access a project in Jira
Access Jira Application
Jira REST API uses pagination to improve performance. Pagination is enforced for operations that could return a large collection of items.
Example:
{ "startAt" : 0, "maxResults" : 10, "total": 200, "isLast": false, "values": [ { /* result 0 / }, { / result 1 / }, { / result 2 */ } ] }
Status codes - The Jira Cloud platform REST API uses the HTTP status codes. Operations that return an error status code may also be able to return a response body which contain details of the error or errors. The schema for the response body is shown below:
{ "id": "https://docs.atlassian.com/jira/REST/schema/error-collection#", "title": "Error Collection", "type": "object", "properties": { "errorMessages": { "type": "array", "items": { "type": "string" } }, "errors": { "type": "object", "patternProperties": { ".+": { "type": "string" } }, "additionalProperties": false }, "status": { "type": "integer" } }, "additionalProperties": false }
Configure Salesforce Jira Integration
We can configure Salesforce wil Apex with Salesforce Apex Callout.
Apex Callouts are of two types:
SOAP based callout works on XML request will be communicate throw web service
HTTP callouts to services that use JSON (Javascript Object Notation) as a medium of transportation and rely on REST APIs provided by the external service.
Salesforce provide very secure way to communicate with 3rd party application such as Salesforce gave high priority to the security. Remote Site setting will help to know Salesforce, which 3rd party application is going to connect with application as without permission application cannot communicate to the other external application.
For the remote site name, enter any name of your choice.
For the remote site URL, enter https://organization-domain-name.atlassian.net.
Configure your domain url for the Jira application https://infoexemplars.atlassian.net
-Enter some relevant Description
-Click Save
Let’s create an account in JIRA to make the integration from Salesforce to JIRA.
Jira provide free version of the Jira cloud which can be access if you wants to implement in your local dev org.
Set of information you needs to provide based on that it will generate custom url of the jira application for your organisation.
Below is the rest api url of the generated account of Jira which will help to access the issue data from Jira application.
https://infoexemplars.atlassian.net/rest/api/3/issue/TEST100-1
JSON Format of the request.
{
"expand":"renderedFields,names,schema,operations,editmeta,changelog,versionedRepresentations",
"id":"10000",
"self":"https://infoexemplars.atlassian.net/rest/api/3/issue/10000",
"key":"TEST100-1",
"fields":{
"statuscategorychangedate":"2020-06-11T08:54:01.821+0000",
"issuetype":{
"self":"https://infoexemplars.atlassian.net/rest/api/3/issuetype/10001",
"id":"10001",
"description":"Tasks track small, distinct pieces of work.",
"iconUrl":"https://infoexemplars.atlassian.net/secure/viewavatar?size=medium&avatarId=10318&avatarType=issuetype",
"name":"Task",
"subtask":false,
"avatarId":10318,
"entityId":"86b59cb7-7ec2-4c28-b711-36d01cee5794"
},
"timespent":null,
"project":{
"self":"https://infoexemplars.atlassian.net/rest/api/3/project/10000",
"id":"10000",
"key":"TEST100",
"name":"InfoExemplars",
"projectTypeKey":"software",
"simplified":true,
"avatarUrls":{
"48x48":"https://infoexemplars.atlassian.net/secure/projectavatar?pid=10000&avatarId=10418",
"24x24":"https://infoexemplars.atlassian.net/secure/projectavatar?size=small&s=small&pid=10000&avatarId=10418",
"16x16":"https://infoexemplars.atlassian.net/secure/projectavatar?size=xsmall&s=xsmall&pid=10000&avatarId=10418",
"32x32":"https://infoexemplars.atlassian.net/secure/projectavatar?size=medium&s=medium&pid=10000&avatarId=10418"
}
},
"fixVersions":[
],
"aggregatetimespent":null,
"resolution":null,
"resolutiondate":null,
"workratio":-1,
"watches":{
"self":"https://infoexemplars.atlassian.net/rest/api/3/issue/TEST100-1/watchers",
"watchCount":1,
"isWatching":true
},
"lastViewed":"2020-06-11T09:19:38.011+0000",
"created":"2020-06-11T08:54:01.588+0000",
"customfield_10020":null,
"customfield_10021":null,
"customfield_10022":null,
"priority":{
"self":"https://infoexemplars.atlassian.net/rest/api/3/priority/3",
"iconUrl":"https://infoexemplars.atlassian.net/images/icons/priorities/medium.svg",
"name":"Medium",
"id":"3"
},
"customfield_10023":null,
"customfield_10024":null,
"customfield_10025":null,
"labels":[
],
"customfield_10016":null,
"customfield_10017":null,
"customfield_10018":{
"hasEpicLinkFieldDependency":false,
"showField":false,
"nonEditableReason":{
"reason":"PLUGIN_LICENSE_ERROR",
"message":"The Parent Link is only available to Jira Premium users."
}
},
"customfield_10019":"0|hzzzzz:",
"timeestimate":null,
"aggregatetimeoriginalestimate":null,
"versions":[
],
"issuelinks":[
],
"assignee":null,
"updated":"2020-06-11T08:54:01.588+0000",
"status":{
"self":"https://infoexemplars.atlassian.net/rest/api/3/status/10000",
"description":"",
"iconUrl":"https://infoexemplars.atlassian.net/",
"name":"To Do",
"id":"10000",
"statusCategory":{
"self":"https://infoexemplars.atlassian.net/rest/api/3/statuscategory/2",
"id":2,
"key":"new",
"colorName":"blue-gray",
"name":"To Do"
}
},
"components":[
],
"timeoriginalestimate":null,
"description":{
"version":1,
"type":"doc",
"content":[
{
"type":"paragraph",
"content":[
{
"type":"text",
"text":"Test Integration with Salesforce "
}
]
}
]
},
"customfield_10010":null,
"customfield_10014":null,
"customfield_10015":null,
"timetracking":{
},
"customfield_10005":null,
"customfield_10006":null,
"customfield_10007":null,
"security":null,
"customfield_10008":null,
"attachment":[
],
"aggregatetimeestimate":null,
"customfield_10009":null,
"summary":"Issue - 1",
"creator":{
"self":"https://infoexemplars.atlassian.net/rest/api/3/user?accountId=5ee1e8b9fff48a0ab53ef44b",
"accountId":"5ee1e8b9fff48a0ab53ef44b",
"emailAddress":"infoexemplars@gmail.com",
"avatarUrls":{
"48x48":"https://secure.gravatar.com/avatar/ad421e26ff484b6e84a583c05ed8f2ee?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FES-2.png",
"24x24":"https://secure.gravatar.com/avatar/ad421e26ff484b6e84a583c05ed8f2ee?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FES-2.png",
"16x16":"https://secure.gravatar.com/avatar/ad421e26ff484b6e84a583c05ed8f2ee?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FES-2.png",
"32x32":"https://secure.gravatar.com/avatar/ad421e26ff484b6e84a583c05ed8f2ee?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FES-2.png"
},
"displayName":"Exemplars IT Services",
"active":true,
"timeZone":"Etc/GMT",
"accountType":"atlassian"
},
"subtasks":[
],
"reporter":{
"self":"https://infoexemplars.atlassian.net/rest/api/3/user?accountId=5ee1e8b9fff48a0ab53ef44b",
"accountId":"5ee1e8b9fff48a0ab53ef44b",
"emailAddress":"infoexemplars@gmail.com",
"avatarUrls":{
"48x48":"https://secure.gravatar.com/avatar/ad421e26ff484b6e84a583c05ed8f2ee?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FES-2.png",
"24x24":"https://secure.gravatar.com/avatar/ad421e26ff484b6e84a583c05ed8f2ee?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FES-2.png",
"16x16":"https://secure.gravatar.com/avatar/ad421e26ff484b6e84a583c05ed8f2ee?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FES-2.png",
"32x32":"https://secure.gravatar.com/avatar/ad421e26ff484b6e84a583c05ed8f2ee?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FES-2.png"
},
"displayName":"Exemplars IT Services",
"active":true,
"timeZone":"Etc/GMT",
"accountType":"atlassian"
},
"customfield_10000":"{}",
"aggregateprogress":{
"progress":0,
"total":0
},
"customfield_10001":null,
"customfield_10002":null,
"customfield_10003":null,
"customfield_10004":null,
"environment":null,
"duedate":null,
"progress":{
"progress":0,
"total":0
},
"votes":{
"self":"https://infoexemplars.atlassian.net/rest/api/3/issue/TEST100-1/votes",
"votes":0,
"hasVoted":false
},
"comment":{
"comments":[
],
"maxResults":0,
"total":0,
"startAt":0
},
"worklog":{
"startAt":0,
"maxResults":20,
"total":0,
"worklogs":[
]
}
}
}
This JSON will return from the JIRA interface which can be converted in Apex class. You can understand what will happen if you try to create the code an inbound to do the same operation from external system. With the help of JSONTOAPEX you can generate apex call of same JSON file.
Apex class
//
// Generated by JSON2Apex http://json2apex.herokuapp.com/
//
// The supplied json has fields with names that are not valid in apex
// and so can only be parsed with explicitly generated code, this option
// was auto selected for you.
public class JSON2Apex {
public class Status {
public String self {get;set;}
public String description {get;set;}
public String iconUrl {get;set;}
public String name {get;set;}
public String id {get;set;}
public StatusCategory statusCategory {get;set;}
public Status(JSONParser parser) {
while (parser.nextToken() != System.JSONToken.END_OBJECT) {
if (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) {
String text = parser.getText();
if (parser.nextToken() != System.JSONToken.VALUE_NULL) {
if (text == 'self') {
self = parser.getText();
} else if (text == 'description') {
description = parser.getText();
} else if (text == 'iconUrl') {
iconUrl = parser.getText();
} else if (text == 'name') {
name = parser.getText();
} else if (text == 'id') {
id = parser.getText();
} else if (text == 'statusCategory') {
statusCategory = new StatusCategory(parser);
} else {
System.debug(LoggingLevel.WARN, 'Status consuming unrecognized property: '+text);
consumeObject(parser);
}
}
}
}
}
}
public class Comment {
public List<FixVersions> comments {get;set;}
public Integer maxResults {get;set;}
public Integer total {get;set;}
public Integer startAt {get;set;}
public Comment(JSONParser parser) {
while (parser.nextToken() != System.JSONToken.END_OBJECT) {
if (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) {
String text = parser.getText();
if (parser.nextToken() != System.JSONToken.VALUE_NULL) {
if (text == 'comments') {
comments = arrayOfFixVersions(parser);
} else if (text == 'maxResults') {
maxResults = parser.getIntegerValue();
} else if (text == 'total') {
total = parser.getIntegerValue();
} else if (text == 'startAt') {
startAt = parser.getIntegerValue();
} else {
System.debug(LoggingLevel.WARN, 'Comment consuming unrecognized property: '+text);
consumeObject(parser);
}
}
}
}
}
}
public class Description {
public Integer version {get;set;}
public String type_Z {get;set;} // in json: type
public List<Content_Z> content {get;set;}
public Description(JSONParser parser) {
while (parser.nextToken() != System.JSONToken.END_OBJECT) {
if (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) {
String text = parser.getText();
if (parser.nextToken() != System.JSONToken.VALUE_NULL) {
if (text == 'version') {
version = parser.getIntegerValue();
} else if (text == 'type') {
type_Z = parser.getText();
} else if (text == 'content') {
content = arrayOfContent_Z(parser);
} else {
System.debug(LoggingLevel.WARN, 'Description consuming unrecognized property: '+text);
consumeObject(parser);
}
}
}
}
}
}
public String expand {get;set;}
public String id {get;set;}
public String self {get;set;}
public String key {get;set;}
public Fields fields {get;set;}
public JSON2Apex(JSONParser parser) {
while (parser.nextToken() != System.JSONToken.END_OBJECT) {
if (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) {
String text = parser.getText();
if (parser.nextToken() != System.JSONToken.VALUE_NULL) {
if (text == 'expand') {
expand = parser.getText();
} else if (text == 'id') {
id = parser.getText();
} else if (text == 'self') {
self = parser.getText();
} else if (text == 'key') {
key = parser.getText();
} else if (text == 'fields') {
fields = new Fields(parser);
} else {
System.debug(LoggingLevel.WARN, 'JSON2Apex consuming unrecognized property: '+text);
consumeObject(parser);
}
}
}
}
}
public class Priority {
public String self {get;set;}
public String iconUrl {get;set;}
public String name {get;set;}
public String id {get;set;}
public Priority(JSONParser parser) {
while (parser.nextToken() != System.JSONToken.END_OBJECT) {
if (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) {
String text = parser.getText();
if (parser.nextToken() != System.JSONToken.VALUE_NULL) {
if (text == 'self') {
self = parser.getText();
} else if (text == 'iconUrl') {
iconUrl = parser.getText();
} else if (text == 'name') {
name = parser.getText();
} else if (text == 'id') {
id = parser.getText();
} else {
System.debug(LoggingLevel.WARN, 'Priority consuming unrecognized property: '+text);
consumeObject(parser);
}
}
}
}
}
}
public class Aggregateprogress {
public Integer progress {get;set;}
public Integer total {get;set;}
public Aggregateprogress(JSONParser parser) {
while (parser.nextToken() != System.JSONToken.END_OBJECT) {
if (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) {
String text = parser.getText();
if (parser.nextToken() != System.JSONToken.VALUE_NULL) {
if (text == 'progress') {
progress = parser.getIntegerValue();
} else if (text == 'total') {
total = parser.getIntegerValue();
} else {
System.debug(LoggingLevel.WARN, 'Aggregateprogress consuming unrecognized property: '+text);
consumeObject(parser);
}
}
}
}
}
}
public class Watches {
public String self {get;set;}
public Integer watchCount {get;set;}
public Boolean isWatching {get;set;}
public Watches(JSONParser parser) {
while (parser.nextToken() != System.JSONToken.END_OBJECT) {
if (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) {
String text = parser.getText();
if (parser.nextToken() != System.JSONToken.VALUE_NULL) {
if (text == 'self') {
self = parser.getText();
} else if (text == 'watchCount') {
watchCount = parser.getIntegerValue();
} else if (text == 'isWatching') {
isWatching = parser.getBooleanValue();
} else {
System.debug(LoggingLevel.WARN, 'Watches consuming unrecognized property: '+text);
consumeObject(parser);
}
}
}
}
}
}
public class Creator {
public String self {get;set;}
public String accountId {get;set;}
public String emailAddress {get;set;}
public AvatarUrls avatarUrls {get;set;}
public String displayName {get;set;}
public Boolean active {get;set;}
public String timeZone {get;set;}
public String accountType {get;set;}
public Creator(JSONParser parser) {
while (parser.nextToken() != System.JSONToken.END_OBJECT) {
if (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) {
String text = parser.getText();
if (parser.nextToken() != System.JSONToken.VALUE_NULL) {
if (text == 'self') {
self = parser.getText();
} else if (text == 'accountId') {
accountId = parser.getText();
} else if (text == 'emailAddress') {
emailAddress = parser.getText();
} else if (text == 'avatarUrls') {
avatarUrls = new AvatarUrls(parser);
} else if (text == 'displayName') {
displayName = parser.getText();
} else if (text == 'active') {
active = parser.getBooleanValue();
} else if (text == 'timeZone') {
timeZone = parser.getText();
} else if (text == 'accountType') {
accountType = parser.getText();
} else {
System.debug(LoggingLevel.WARN, 'Creator consuming unrecognized property: '+text);
consumeObject(parser);
}
}
}
}
}
}
public class Project {
public String self {get;set;}
public String id {get;set;}
public String key {get;set;}
public String name {get;set;}
public String projectTypeKey {get;set;}
public Boolean simplified {get;set;}
public AvatarUrls avatarUrls {get;set;}
public Project(JSONParser parser) {
while (parser.nextToken() != System.JSONToken.END_OBJECT) {
if (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) {
String text = parser.getText();
if (parser.nextToken() != System.JSONToken.VALUE_NULL) {
if (text == 'self') {
self = parser.getText();
} else if (text == 'id') {
id = parser.getText();
} else if (text == 'key') {
key = parser.getText();
} else if (text == 'name') {
name = parser.getText();
} else if (text == 'projectTypeKey') {
projectTypeKey = parser.getText();
} else if (text == 'simplified') {
simplified = parser.getBooleanValue();
} else if (text == 'avatarUrls') {
avatarUrls = new AvatarUrls(parser);
} else {
System.debug(LoggingLevel.WARN, 'Project consuming unrecognized property: '+text);
consumeObject(parser);
}
}
}
}
}
}
public class StatusCategory {
public String self {get;set;}
public Integer id {get;set;}
public String key {get;set;}
public String colorName {get;set;}
public String name {get;set;}
public StatusCategory(JSONParser parser) {
while (parser.nextToken() != System.JSONToken.END_OBJECT) {
if (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) {
String text = parser.getText();
if (parser.nextToken() != System.JSONToken.VALUE_NULL) {
if (text == 'self') {
self = parser.getText();
} else if (text == 'id') {
id = parser.getIntegerValue();
} else if (text == 'key') {
key = parser.getText();
} else if (text == 'colorName') {
colorName = parser.getText();
} else if (text == 'name') {
name = parser.getText();
}
else {
System.debug(LoggingLevel.WARN, 'StatusCategory consuming unrecognized property: '+text);
consumeObject(parser);
}
}
}
}
}
}
public class Worklog {
public Integer startAt {get;set;}
public Integer maxResults {get;set;}
public Integer total {get;set;}
public List<FixVersions> worklogs {get;set;}
public Worklog(JSONParser parser) {
while (parser.nextToken() != System.JSONToken.END_OBJECT) {
if (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) {
String text = parser.getText();
if (parser.nextToken() != System.JSONToken.VALUE_NULL) {
if (text == 'startAt') {
startAt = parser.getIntegerValue();
} else if (text == 'maxResults') {
maxResults = parser.getIntegerValue();
} else if (text == 'total') {
total = parser.getIntegerValue();
} else if (text == 'worklogs') {
worklogs = arrayOfFixVersions(parser);
} else {
System.debug(LoggingLevel.WARN, 'Worklog consuming unrecognized property: '+text);
consumeObject(parser);
}
}
}
}
}
}
public class Fields {
public String statuscategorychangedate {get;set;}
public Issuetype issuetype {get;set;}
public Object timespent {get;set;}
public Project project {get;set;}
public List<FixVersions> fixVersions {get;set;}
public Object aggregatetimespent {get;set;}
public Object resolution {get;set;}
public Object resolutiondate {get;set;}
public Integer workratio {get;set;}
public Watches watches {get;set;}
public String lastViewed {get;set;}
public String created {get;set;}
public Object customfield_10020 {get;set;}
public Object customfield_10021 {get;set;}
public Object customfield_10022 {get;set;}
public Priority priority {get;set;}
public Object customfield_10023 {get;set;}
public Object customfield_10024 {get;set;}
public Object customfield_10025 {get;set;}
public List<FixVersions> labels {get;set;}
public Object customfield_10016 {get;set;}
public Object customfield_10017 {get;set;}
public Customfield_10018 customfield_10018 {get;set;}
public String customfield_10019 {get;set;}
public Object aggregatetimeoriginalestimate {get;set;}
public Object timeestimate {get;set;}
public List<FixVersions> versions {get;set;}
public List<FixVersions> issuelinks {get;set;}
public Object assignee {get;set;}
public String updated {get;set;}
public Status status {get;set;}
public List<FixVersions> components {get;set;}
public Object timeoriginalestimate {get;set;}
public Description description {get;set;}
public Object customfield_10010 {get;set;}
public Object customfield_10014 {get;set;}
public Object customfield_10015 {get;set;}
public FixVersions timetracking {get;set;}
public Object customfield_10005 {get;set;}
public Object customfield_10006 {get;set;}
public Object customfield_10007 {get;set;}
public Object security {get;set;}
public Object customfield_10008 {get;set;}
public List<FixVersions> attachment {get;set;}
public Object aggregatetimeestimate {get;set;}
public Object customfield_10009 {get;set;}
public String summary {get;set;}
public Creator creator {get;set;}
public List<FixVersions> subtasks {get;set;}
public Creator reporter {get;set;}
public Aggregateprogress aggregateprogress {get;set;}
public String customfield_10000 {get;set;}
public Object customfield_10001 {get;set;}
public Object customfield_10002 {get;set;}
public Object customfield_10003 {get;set;}
public Object customfield_10004 {get;set;}
public Object environment {get;set;}
public Object duedate {get;set;}
public Aggregateprogress progress {get;set;}
public Comment comment {get;set;}
public Votes votes {get;set;}
public Worklog worklog {get;set;}
public Fields(JSONParser parser) {
while (parser.nextToken() != System.JSONToken.END_OBJECT) {
if (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) {
String text = parser.getText();
if (parser.nextToken() != System.JSONToken.VALUE_NULL) {
if (text == 'statuscategorychangedate') {
statuscategorychangedate = parser.getText();
} else if (text == 'issuetype') {
issuetype = new Issuetype(parser);
} else if (text == 'timespent') {
timespent = parser.readValueAs(Object.class);
} else if (text == 'project') {
project = new Project(parser);
} else if (text == 'fixVersions') {
fixVersions = arrayOfFixVersions(parser);
} else if (text == 'aggregatetimespent') {
aggregatetimespent = parser.readValueAs(Object.class);
} else if (text == 'resolution') {
resolution = parser.readValueAs(Object.class);
} else if (text == 'resolutiondate') {
resolutiondate = parser.readValueAs(Object.class);
} else if (text == 'workratio') {
workratio = parser.getIntegerValue();
} else if (text == 'watches') {
watches = new Watches(parser);
} else if (text == 'lastViewed') {
lastViewed = parser.getText();
} else if (text == 'created') {
created = parser.getText();
} else if (text == 'customfield_10020') {
customfield_10020 = parser.readValueAs(Object.class);
} else if (text == 'customfield_10021') {
customfield_10021 = parser.readValueAs(Object.class);
} else if (text == 'customfield_10022') {
customfield_10022 = parser.readValueAs(Object.class);
} else if (text == 'priority') {
priority = new Priority(parser);
} else if (text == 'customfield_10023') {
customfield_10023 = parser.readValueAs(Object.class);
} else if (text == 'customfield_10024') {
customfield_10024 = parser.readValueAs(Object.class);
} else if (text == 'customfield_10025') {
customfield_10025 = parser.readValueAs(Object.class);
} else if (text == 'labels') {
labels = arrayOfFixVersions(parser);
} else if (text == 'customfield_10016') {
customfield_10016 = parser.readValueAs(Object.class);
} else if (text == 'customfield_10017') {
customfield_10017 = parser.readValueAs(Object.class);
} else if (text == 'customfield_10018') {
customfield_10018 = new Customfield_10018(parser);
} else if (text == 'customfield_10019') {
customfield_10019 = parser.getText();
} else if (text == 'aggregatetimeoriginalestimate') {
aggregatetimeoriginalestimate = parser.readValueAs(Object.class);
} else if (text == 'timeestimate') {
timeestimate = parser.readValueAs(Object.class);
} else if (text == 'versions') {
versions = arrayOfFixVersions(parser);
} else if (text == 'issuelinks') {
issuelinks = arrayOfFixVersions(parser);
} else if (text == 'assignee') {
assignee = parser.readValueAs(Object.class);
} else if (text == 'updated') {
updated = parser.getText();
} else if (text == 'status') {
status = new Status(parser);
} else if (text == 'components') {
components = arrayOfFixVersions(parser);
} else if (text == 'timeoriginalestimate') {
timeoriginalestimate = parser.readValueAs(Object.class);
} else if (text == 'description') {
description = new Description(parser);
} else if (text == 'customfield_10010') {
customfield_10010 = parser.readValueAs(Object.class);
} else if (text == 'customfield_10014') {
customfield_10014 = parser.readValueAs(Object.class);
} else if (text == 'customfield_10015') {
customfield_10015 = parser.readValueAs(Object.class);
} else if (text == 'timetracking') {
timetracking = new FixVersions(parser);
} else if (text == 'customfield_10005') {
customfield_10005 = parser.readValueAs(Object.class);
} else if (text == 'customfield_10006') {
customfield_10006 = parser.readValueAs(Object.class);
} else if (text == 'customfield_10007') {
customfield_10007 = parser.readValueAs(Object.class);
} else if (text == 'security') {
security = parser.readValueAs(Object.class);
} else if (text == 'customfield_10008') {
customfield_10008 = parser.readValueAs(Object.class);
} else if (text == 'attachment') {
attachment = arrayOfFixVersions(parser);
} else if (text == 'aggregatetimeestimate') {
aggregatetimeestimate = parser.readValueAs(Object.class);
} else if (text == 'customfield_10009') {
customfield_10009 = parser.readValueAs(Object.class);
} else if (text == 'summary') {
summary = parser.getText();
} else if (text == 'creator') {
creator = new Creator(parser);
} else if (text == 'subtasks') {
subtasks = arrayOfFixVersions(parser);
} else if (text == 'reporter') {
reporter = new Creator(parser);
} else if (text == 'aggregateprogress') {
aggregateprogress = new Aggregateprogress(parser);
} else if (text == 'customfield_10000') {
customfield_10000 = parser.getText();
} else if (text == 'customfield_10001') {
customfield_10001 = parser.readValueAs(Object.class);
} else if (text == 'customfield_10002') {
customfield_10002 = parser.readValueAs(Object.class);
} else if (text == 'customfield_10003') {
customfield_10003 = parser.readValueAs(Object.class);
} else if (text == 'customfield_10004') {
customfield_10004 = parser.readValueAs(Object.class);
} else if (text == 'environment') {
environment = parser.readValueAs(Object.class);
} else if (text == 'duedate') {
duedate = parser.readValueAs(Object.class);
} else if (text == 'progress') {
progress = new Aggregateprogress(parser);
} else if (text == 'comment') {
comment
= new Comment(parser);
} else if (text == 'votes') {
votes = new Votes(parser);
} else if (text == 'worklog') {
worklog = new Worklog(parser);
} else {
System.debug(LoggingLevel.WARN, 'Fields consuming unrecognized property: '+text);
consumeObject(parser);
}
}
}
}
}
}
public class NonEditableReason {
public String reason {get;set;}
public String message {get;set;}
public NonEditableReason(JSONParser parser) {
while (parser.nextToken() != System.JSONToken.END_OBJECT) {
if (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) {
String text = parser.getText();
if (parser.nextToken() != System.JSONToken.VALUE_NULL) {
if (text == 'reason') {
reason = parser.getText();
} else if (text == 'message') {
message = parser.getText();
} else {
System.debug(LoggingLevel.WARN, 'NonEditableReason consuming unrecognized property: '+text);
consumeObject(parser);
}
}
}
}
}
}
public class FixVersions {
public FixVersions(JSONParser parser) {
while (parser.nextToken() != System.JSONToken.END_OBJECT) {
if (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) {
String text = parser.getText();
if (parser.nextToken() != System.JSONToken.VALUE_NULL) {
{
System.debug(LoggingLevel.WARN, 'FixVersions consuming unrecognized property: '+text);
consumeObject(parser);
}
}
}
}
}
}
public class Customfield_10018 {
public Boolean hasEpicLinkFieldDependency {get;set;}
public Boolean showField {get;set;}
public NonEditableReason nonEditableReason {get;set;}
public Customfield_10018(JSONParser parser) {
while (parser.nextToken() != System.JSONToken.END_OBJECT) {
if (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) {
String text = parser.getText();
if (parser.nextToken() != System.JSONToken.VALUE_NULL) {
if (text == 'hasEpicLinkFieldDependency') {
hasEpicLinkFieldDependency = parser.getBooleanValue();
} else if (text == 'showField') {
showField = parser.getBooleanValue();
} else if (text == 'nonEditableReason') {
nonEditableReason = new NonEditableReason(parser);
} else {
System.debug(LoggingLevel.WARN, 'Customfield_10018 consuming unrecognized property: '+text);
consumeObject(parser);
}
}
}
}
}
}
public class Content {
public String type_Z {get;set;} // in json: type
public String text {get;set;}
public Content(JSONParser parser) {
while (parser.nextToken() != System.JSONToken.END_OBJECT) {
if (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) {
String text = parser.getText();
if (parser.nextToken() != System.JSONToken.VALUE_NULL) {
if (text == 'type') {
type_Z = parser.getText();
} else if (text == 'text') {
text = parser.getText();
} else {
System.debug(LoggingLevel.WARN, 'Content consuming unrecognized property: '+text);
consumeObject(parser);
}
}
}
}
}
}
public class Issuetype {
public String self {get;set;}
public String id {get;set;}
public String description {get;set;}
public String iconUrl {get;set;}
public String name {get;set;}
public Boolean subtask {get;set;}
public Integer avatarId {get;set;}
public String entityId {get;set;}
public Issuetype(JSONParser parser) {
while (parser.nextToken() != System.JSONToken.END_OBJECT) {
if (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) {
String text = parser.getText();
if (parser.nextToken() != System.JSONToken.VALUE_NULL) {
if (text == 'self') {
self = parser.getText();
} else if (text == 'id') {
id = parser.getText();
} else if (text == 'description') {
description = parser.getText();
} else if (text == 'iconUrl') {
iconUrl = parser.getText();
} else if (text == 'name') {
name = parser.getText();
} else if (text == 'subtask') {
subtask = parser.getBooleanValue();
} else if (text == 'avatarId') {
avatarId = parser.getIntegerValue();
} else if (text == 'entityId') {
entityId = parser.getText();
} else {
System.debug(LoggingLevel.WARN, 'Issuetype consuming unrecognized property: '+text);
consumeObject(parser);
}
}
}
}
}
}
public class AvatarUrls {
public String 48x48 {get;set;}
public String 24x24 {get;set;}
public String 16x16 {get;set;}
public String 32x32 {get;set;}
public AvatarUrls(JSONParser parser) {
while (parser.nextToken() != System.JSONToken.END_OBJECT) {
if (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) {
String text = parser.getText();
if (parser.nextToken() != System.JSONToken.VALUE_NULL) {
if (text == '48x48') {
48x48 = parser.getText();
} else if (text == '24x24') {
24x24 = parser.getText();
} else if (text == '16x16') {
16x16 = parser.getText();
} else if (text == '32x32') {
32x32 = parser.getText();
} else {
System.debug(LoggingLevel.WARN, 'AvatarUrls consuming unrecognized property: '+text);
consumeObject(parser);
}
}
}
}
}
}
public class Votes {
public String self {get;set;}
public Integer votes {get;set;}
public Boolean hasVoted {get;set;}
public Votes(JSONParser parser) {
while (parser.nextToken() != System.JSONToken.END_OBJECT) {
if (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) {
String text = parser.getText();
if (parser.nextToken() != System.JSONToken.VALUE_NULL) {
if (text == 'self') {
self = parser.getText();
} else if (text == 'votes') {
votes = parser.getIntegerValue();
} else if (text == 'hasVoted') {
hasVoted = parser.getBooleanValue();
} else {
System.debug(LoggingLevel.WARN, 'Votes consuming unrecognized property: '+text);
consumeObject(parser);
}
}
}
}
}
}
public class Content_Z {
public String type_Z {get;set;} // in json: type
public List<Content> content {get;set;}
public Content_Z(JSONParser parser) {
while (parser.nextToken() != System.JSONToken.END_OBJECT) {
if (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) {
String text = parser.getText();
if (parser.nextToken() != System.JSONToken.VALUE_NULL) {
if (text == 'type') {
type_Z = parser.getText();
} else if (text == 'content') {
content = arrayOfContent(parser);
} else {
System.debug(LoggingLevel.WARN, 'Content_Z consuming unrecognized property: '+text);
consumeObject(parser);
}
}
}
}
}
}
public static JSON2Apex parse(String json) {
System.JSONParser parser = System.JSON.createParser(json);
return new JSON2Apex(parser);
}
public static void consumeObject(System.JSONParser parser) {
Integer depth = 0;
do {
System.JSONToken curr = parser.getCurrentToken();
if (curr == System.JSONToken.START_OBJECT ||
curr == System.JSONToken.START_ARRAY) {
depth++;
} else if (curr == System.JSONToken.END_OBJECT ||
curr == System.JSONToken.END_ARRAY) {
depth--;
}
} while (depth > 0 && parser.nextToken() != null);
}
private static List<Content_Z> arrayOfContent_Z(System.JSONParser p) {
List<Content_Z> res = new List<Content_Z>();
if (p.getCurrentToken() == null) p.nextToken();
while (p.nextToken() != System.JSONToken.END_ARRAY) {
res.add(new Content_Z(p));
}
return res;
}
private static List<Content> arrayOfContent(System.JSONParser p) {
List<Content> res = new List<Content>();
if (p.getCurrentToken() == null) p.nextToken();
while (p.nextToken() != System.JSONToken.END_ARRAY) {
res.add(new Content(p));
}
return res;
}
private static List<FixVersions> arrayOfFixVersions(System.JSONParser p) {
List<FixVersions> res = new List<FixVersions>();
if (p.getCurrentToken() == null) p.nextToken();
while (p.nextToken() != System.JSONToken.END_ARRAY) {
res.add(new FixVersions(p));
}
return res;
}
}
Salesforce Case Management
Let’s make the Ticketing tool inside Salesforce, which can be sync with Jira.
Let’s take an example- your organization already has Jira system which has been used from the support team to access. And you are using Salesforce Services cloud to help track the Marketing and Sales customer. If anyone of the user faced issue they could directly raise a ticket inside Salesforce Service Cloud, which will be sync to the.
Let’s make the configuration in Salesforce to achieve the expected result.
We will create a customer field which will store Key of the JIRA project, when you create a project you need to provide the key to identify the records.
Go to the Setup.
Click on Object and go to the Case.
Add a field which will store JIRA key. When you set up the JIRA profile and make project it will ask to fill the Key which will be unique for the projects.
Do the mapping with Jira Inbound fields.
This Key field will help to send the authentication details while sending the data from Salesforce to JIRA.
Apex Class Interface will do the external call to the Jira which will make the authentication and will push the request.
Apex Class
global class JIRACreateIssue {
@future (callout=true)
WebService static void UpdateFields(String Key) {
String User = <username>; //Needs to change
String Password = <password>; //Needs to change
String jiraURL = 'https://infoexemplars.atlassian.net'; // Change as per requirement
sObject record = [SELECT Subject, Assign, Priority_Id__C FROM Case WHERE JIRA_Key__c = :JIRA_key LIMIT 1];
String summary = (String) record.get('Subject');
String assignee = (String) record.get('Assign');
String priority = (String) record.get('Priority_Id__C');
HttpRequest request = new HttpRequest();
HttpResponse response = new HttpResponse();
Http http = new Http();
Blob headerValue = Blob.valueOf(User+':'+Password);
String authorizationHeader = 'Basic ' + EncodingUtil.base64Encode(headerValue);
request.setHeader('Authorization', authorizationHeader);
request.setHeader('Content-Type','application/json');
String endpoint = jiraURL+'/rest/api/2/issue/'+JIRA_Key;
request.setMethod('PUT');
request.setEndpoint(endpoint);
request.setBody('{ \"fields\":{\"summary\": \"'+summary+'\", \"assign\":{\"name\":\"'+assignee+'\"}, \"priority\":{\"id\":\"'+priority+'\"}}}');
try {
response = http.send(request);
}
catch(System.CalloutException e) {
System.debug('ERROR:' + e);
System.debug(response.toString());
}
}
}
Execution – You can write a trigger that will allow to execute this Apex class to sync the data in JIRA application while creating a new record in Salesforce Case.
This is the simple Trigger class that will execute when record will create in Salesforce Application.
trigger SyncCase on Case (after insert) {
for (Case c : Trigger.new) {
String JIRA_Key=c.JIRA_Key__c;
JIRACreateIssue.UpdateFields(JIRA_Key);
}
}
This Configuration will be help to create new record sync with JIRA.
Jira Salesforce Cloud Connectors
As we know, it’s not always easy to build the application integration between two systems and even for JIRA more complex configuration required as JIRA provide a different range of functionality. Moreover, to make the interface or API, Customer needs to setup the development team and designer of the application who will suggest and advise to make the solution as per requirement. This all required investment, Management and infrastructure setup and most important time.
Salesforce App Exchange is a plate for where different solution provider companies have listed the solution, which is very easy to use and guided installation is required.
Jira Atlassian verified vendor is Service Rocket, which provides three types of Jira integrations: Jira Server, Jira Cloud, and Jira Service Desk Connector. Salesforce customers can track Jira issues within Salesforce and see communications from both platforms.
The connector helps establish a secure and controlled bidirectional flow o between Salesforce products like Sales, Service, Community, Marketing Cloud and custom application and Jira products like Software, Core, Service Desk which have both on-cloud and on-premises version.
Salesforce Jira Integration Features
01.
Easily Configuration
If you want configuration or want some customizations for your business processes with short time period it will be helpful to connect with plugin directly.
02.
Bidirectional Sync
Jira connector enables a controlled bidirectional syncing of data between Salesforce and Jira. This will improve collaboration among teams.
03.
Reporting
You can gain significant insights into the cases linked with Jira issues like the total number of cases for which the Jira issue has been raised, consolidated view of cases linked with Jira by Jira status.
04.
Search Capability
It will increase the capability of cases/other details search as it allows searching or finding the records from the external system.
Below are the some of the list of the Salesforce cloud plug-in connectors.
Service Rocket
zAgile
Go2Group
Workato
Zapier
Peeklogic Jira Connector
Top comments (0)