Configuring Automatic Pull from Salesforce

This guide will help you to configure Automatic Pull from Salesforce by enabling the Automatic Pull feature, as well as configuring the required Salesforce triggers. If you haven't done so, configure the available objects and fields before proceeding with the following steps. The Salesforce Package must be installed and configured as well.

Note that Automatic Pull will only work if:

  • Connection is configured to Allow Automatic Pull
  • Association is configured to Allow Automatic Pull
  • Salesforce Triggers for the respective objects are installed
  • Connection allows modification
  • Salesforce object for which you configure the Automatic Pull trigger is configured to be available

A word on naming: Automatic Pull in Jira is called Automatic Push in Salesforce. The naming is chosen based on the point of view of the user of the respective system.

Enabling Connection to allow Automatic Pull

  1. If you are using Jira Cloud, click the Jira icon in the top left (, or ) > Salesforce > Connections.
    If you are using Jira Server, click  > Add-ons, and then in the sidebar go to Salesforce > Connections.

  2. At the Salesforce Connections screen, go to the Connection you want to configure > Options Menu  > Configure.



  3. At the Connection Configuration screen, locate the Connection Settings and enable the switch for Allow Automatic Pull. At this point make also sure the switch for Allow Modification is enabled:

Create Automatic Pull Enabled Association

Automatic Pull from Salesforce has to be enabled/disabled per Association. You can do this either when creating an association or later by Configuring an association. Additionally, the preset configuration for the Automatic Pull switch can be configured on the Connection Configuration page.

Create and Deploy Apex triggers

The Connector relies on Apex triggers to be informed of changes on Salesforce objects. These triggers have to be created and deployed manually in Salesforce. For Salesforce Enterprise and Unlimited edition, the standard deployment of Apex triggers are required (see Salesforce documentation).

The following code samples shows how to write triggers for Cases. To create triggers for other objects, replace the object name in the code accordingly.

Push to Jira after Object updated 

CaseUpdatedTrigger
trigger CaseUpdatedTrigger on Case (after update) {
    JCFS.API.pushUpdatesToJira();
}
CaseUpdatedTrigger
trigger CaseUpdatedTrigger on Case (after update) {
    JSFS.API.pushUpdatesToJira();
}




Apex test class for the triggers

Once triggers are created, they need a certain level of test coverage to be allowed to deploy to the Production environment. The package contains a test helper which tests the whole scenario. You simply need to add a unit test for each trigger and call the test helper method as follows:

CaseUpdatedTriggerTest
@isTest public class CaseUpdatedTriggerTest {
    @isTest static void testTriggerAfterUpdate() {
        JCFS.JiraTriggerTestHelper.testAfterUpdate('Case');
    }
}
CaseUpdatedTriggerTest
@isTest public class CaseUpdatedTriggerTest {
    @isTest static void testTriggerAfterUpdate() {
        JSFS.JiraTriggerTestHelper.testAfterUpdate('Case');
    }
}


Your Jira and Salesforce system should now be ready to push changes on configured Salesforce objects automatically to the associated Jira issues.

Just as the trigger itself, you need a test for each object trigger. All you have to do is replace Case in the above code with the actual object name.

After creating your test class, click Run Test. This ensures the test gets associated with your newly created trigger and provides code coverage.

You will need to re-run the Apex class if you ever deactivate and reactivate the trigger.

Caveats

The above-mentioned method works only if you call the API directly from within a trigger. If you need to call the API indirectly, for instance from a helper method, see the advanced usage section below.

Also note that our API should not be called from within a Future. When called from a Future, the API simply logs it as a warning. Check your Apex Logs to see if that is the case if the automatic synchronization is not working.

Custom objects

The Apex API and test helper methods work as well with any custom object. You just need to find out the object name (e.g. "MyObject__c") and use it in the trigger code as documented above. If you are testing a Custom Object, provide the fully qualified name (include your namespace in the Salesforce Object Name, e.g. 'yournamespace__MyObject__c'). You can get the fully qualified name from the API Name which contains your namespace and the Salesforce Object Name.

To obtain the API Name, navigate to your custom object definition detail configuration screen and look for API name. For more information, view the Salesforce documentation on namespace prefixes.

Advanced usage

Our Apex API supports a selective pushing, so that you can exclude unwanted Salesforce objects from the push. For this purpose you can use the following API method:

Signature of JCFS.API.pushUpdatesToJira
JCFS.API.pushUpdatesToJira(List<SObject> newObjects, List<SObject> oldObjects)
Signature of JCFS.API.pushUpdatesToJira
JSFS.API.pushUpdatesToJira(List<SObject> newObjects, List<SObject> oldObjects)


Pass all the objects for which you want a push to Jira to happen as newObjects. Note that all the objects in this list must be of the same runtime type. So we recommend to use a concrete type, e.g. List<Case> or List<Account> for the variable you pass as this parameter. The oldObjects parameter is not used at the moment so you can pass Trigger.old or an empty list for it.

For instance if you want to push updates only for Cases whose summaries start with "Post" you can use the following trigger code:

Push updates to JIRA selectively
trigger CaseUpdatedTrigger on Case (after insert) {
    List<Case> toBePushedToJira = new List<Case>(); // proper runtime type, List<SObject> won't work
    for(Case c : Trigger.new) {
        if (c.Subject.startsWithIgnoreCase('Post')) {
            toBePushedToJira.add(c);
        }
    }
    JCFS.API.pushUpdatesToJira(toBePushedToJira, Trigger.old);
}
Push updates to JIRA selectively
trigger CaseUpdatedTrigger on Case (after insert) {
    List<Case> toBePushedToJira = new List<Case>(); // proper runtime type, List<SObject> won't work
    for(Case c : Trigger.new) {
        if (c.Subject.startsWithIgnoreCase('Post')) {
            toBePushedToJira.add(c);
        }
    }
    JSFS.API.pushUpdatesToJira(toBePushedToJira, Trigger.old);
}


Attachments

If you would like automatic pull of attachments, a separate trigger is required:

Attachment Trigger
trigger AttachmentTrigger on Attachment (after insert) {  
    Map<String, Set<Id>> sObjectsByType = new Map<String, Set<Id>>();
    for(Attachment a : Trigger.new) {
        String sObjectType = String.valueOf(a.ParentId.getSObjectType());
        if (sObjectsByType.containsKey(sObjectType)) {
            sObjectsByType.get(sObjectType).add(a.ParentId);
        } else {
            sObjectsByType.put(sObjectType, new Set<Id>{a.ParentId});
        }
    }
    
    for(String sObjectType : sObjectsByType.keySet()) {
        Set<Id> ids = sObjectsByType.get(sObjectType);
        String sObjectsById = 'SELECT Id FROM ' + sObjectType + ' where Id IN :ids'; 
        List<SObject> toBePushed = Database.query(sObjectsById);
        JCFS.API.pushUpdatesToJira(toBePushed, Trigger.old);
    }
}
Attachment Trigger
trigger AttachmentTrigger on Attachment (after insert) {  
    Map<String, Set<Id>> sObjectsByType = new Map<String, Set<Id>>();
    for(Attachment a : Trigger.new) {
        String sObjectType = String.valueOf(a.ParentId.getSObjectType());
        if (sObjectsByType.containsKey(sObjectType)) {
            sObjectsByType.get(sObjectType).add(a.ParentId);
        } else {
            sObjectsByType.put(sObjectType, new Set<Id>{a.ParentId});
        }
    }
    
    for(String sObjectType : sObjectsByType.keySet()) {
        Set<Id> ids = sObjectsByType.get(sObjectType);
        String sObjectsById = 'SELECT Id FROM ' + sObjectType + ' where Id IN :ids'; 
        List<SObject> toBePushed = Database.query(sObjectsById);
        JSFS.API.pushUpdatesToJira(toBePushed, Trigger.old);
    }
}


To get test coverage for the Attachment trigger, use the provided test helper below:

Apex test class for Attachment trigger
@isTest public class AttachmentTriggerTest {
    @isTest static void caseAfterInsertTest() {
        Case randomCase = new Case(Subject = 'AttachmentTriggerTest');
        insert randomCase;
        Attachment randomAttachment = new Attachment(ParentId = randomCase.Id, Name = 'test.txt', Body = Blob.valueOf('Test'));
        JCFS.JiraTriggerTestHelper.testAfterInsert(randomAttachment);
    }
}
Apex test class for Attachment trigger
@isTest public class AttachmentTriggerTest {
    @isTest static void caseAfterInsertTest() {
        Case randomCase = new Case(Subject = 'AttachmentTriggerTest');
        insert randomCase;
        Attachment randomAttachment = new Attachment(ParentId = randomCase.Id, Name = 'test.txt', Body = Blob.valueOf('Test'));
        JSFS.JiraTriggerTestHelper.testAfterInsert(randomAttachment);
    }
}


Salesforce Files

If you would like automatic pull of Salesforce Files, another trigger is required:

File Trigger
trigger ContentDocumentLinkTrigger on ContentDocumentLink (after insert) {
    Map<String, Set<Id>> sObjectsByType = new Map<String, Set<Id>>();
    for (ContentDocumentLink contentDocumentLink : Trigger.new) {
    	String sObjectType = String.valueOf(contentDocumentLink.LinkedEntityId.getSObjectType());
        if (sObjectsByType.containsKey(sObjectType)) {
            sObjectsByType.get(sObjectType).add(contentDocumentLink.LinkedEntityId);
        } else {
            sObjectsByType.put(sObjectType, new Set<Id>{contentDocumentLink.LinkedEntityId});
        }
    }
    for(String sObjectType : sObjectsByType.keySet()) {
        Set<Id> ids = sObjectsByType.get(sObjectType);
        String sObjectsById = 'SELECT Id FROM ' + sObjectType + ' where Id IN :ids';
        List<SObject> toBePushed = Database.query(sObjectsById);
        JCFS.API.pushUpdatesToJira(toBePushed, Trigger.old);
    }
}
File Trigger
trigger ContentDocumentLinkTrigger on ContentDocumentLink (after insert) {
    Map<String, Set<Id>> sObjectsByType = new Map<String, Set<Id>>();
    for (ContentDocumentLink contentDocumentLink : Trigger.new) {
    	String sObjectType = String.valueOf(contentDocumentLink.LinkedEntityId.getSObjectType());
        if (sObjectsByType.containsKey(sObjectType)) {
            sObjectsByType.get(sObjectType).add(contentDocumentLink.LinkedEntityId);
        } else {
            sObjectsByType.put(sObjectType, new Set<Id>{contentDocumentLink.LinkedEntityId});
        }
    }
    for(String sObjectType : sObjectsByType.keySet()) {
        Set<Id> ids = sObjectsByType.get(sObjectType);
        String sObjectsById = 'SELECT Id FROM ' + sObjectType + ' where Id IN :ids';
        List<SObject> toBePushed = Database.query(sObjectsById);
        JSFS.API.pushUpdatesToJira(toBePushed, Trigger.old);
    }
}


(warning) Known issue: Uploading multiple files in Salesforce at the same time may result in duplicate attachments in Jira.

To get test coverage for the ContentDocumentLink trigger, use the provided test helper below:

Apex test class for ContentDocumentLink trigger
@isTest public class ContentDocumentTriggerTest {
    @isTest static void caseAfterInsertTest() {
        Case randomCase = new Case(Subject = 'ContentDocumentTriggerTest');
        insert randomCase;
		ContentVersion contentVersion = new ContentVersion(PathOnClient = 'PathOnClient', VersionData = Blob.valueOf('Test'));
		insert contentVersion;
        ContentVersion createdContentVersion = [SELECT ContentDocumentId FROM ContentVersion WHERE Id = :contentVersion.Id];
        ContentDocumentLink randomContentDocumentLink = 
            new ContentDocumentLink(
                ContentDocumentId = createdContentVersion.ContentDocumentId, 
                LinkedEntityId = randomCase.Id,
                ShareType = 'V'
            );
        JCFS.JiraTriggerTestHelper.testAfterInsert(randomContentDocumentLink);
    }
}
Apex test class for ContentDocumentLink trigger
@isTest public class ContentDocumentTriggerTest {
    @isTest static void caseAfterInsertTest() {
        Case randomCase = new Case(Subject = 'ContentDocumentTriggerTest');
        insert randomCase;
		ContentVersion contentVersion = new ContentVersion(PathOnClient = 'PathOnClient', VersionData = Blob.valueOf('Test'));
		insert contentVersion;
        ContentVersion createdContentVersion = [SELECT ContentDocumentId FROM ContentVersion WHERE Id = :contentVersion.Id];
        ContentDocumentLink randomContentDocumentLink = 
            new ContentDocumentLink(
                ContentDocumentId = createdContentVersion.ContentDocumentId, 
                LinkedEntityId = randomCase.Id,
                ShareType = 'V'
            );
        JSFS.JiraTriggerTestHelper.testAfterInsert(randomContentDocumentLink);
    }
}



To make this work, Synchronize Attachments must be enabled in the Connection settings.