sfdc-report-wide-upgrade2.pngIn this example we're going to walk through how to build a tile in Jive that displays data from a Salesforce report. We'll use a custom view tile to be able to fully control the user interface, and we'll configure the tile with external data provider option to use data sent from our add-on integration service. For background info on custom view tiles, check out Creating Custom View Tiles.


The diagram below shows main components of this integration and screenshot on right shows the tile with data from a sample report in Salesforce developer instance.


When using external data provider option, the add-on service can periodically query report data and send that to Jive to be available for tile rendering. This means each time a tile is displayed, it is rendered using latest data sent to Jive and no requests are made to the add-on service.



Getting started

To get us started off, you'll first need to have the following ready:


Download Sample on GitHub

Download the sample project at:


Installing Salesforce API Key

To get access to Salesforce API, go to your Salesforce developer account and configure the integration at Setup, Create, Apps, and use OAuth scope settings from screenshot below. The callback URL needs to point to the OAuth endpoint on your Node.JS/Jive-SDK add-on service.


Here's how to access the Setup menu

Screen Shot 2015-10-06 at 14.04.22.png


Here are the OAuth settings



The Consumer key and secret from Salesforce app definition are set in /jiveclientconfiguration.json

"oauth2-sfdc": {

  "originServerAuthorizationUrl": "",

  "originServerTokenRequestUrl": "",

  "clientOAuth2CallbackUrl": "http://localhost:8090/oauth/oauth2Callback",

  "oauth2ConsumerKey": "3MVG9y6x0357Hlee8TDL6y5tOH95AsjUFac6us6C.8e0KncuDmapBf6UaRVA._.xKjlMw_0HA80SXGIi6Frvq",

  "oauth2ConsumerSecret": ""




Tile setup

The sample project has two tile definitions, one for wide tile layout slot and one for narrow. We use two definitions only to detect layout slot type for display handling and actual tile implementation is all under sfdc-report-wide tile. Here is the definition for the wide tile, see tiles/sfdc-report-wide/definition.json:


    "config": "/sfdc-report-wide/config?type=wide",

    "displayName": "Salesforce Report Wide",

    "name": "sfdc-report-wide",

    "style": "CUSTOM_VIEW",

    "displayWidth": "WIDE",

    "view": "/sfdc-report-wide/view?type=wide",

    "action": "/sfdc-report-wide/action?type=wide",

    "dataProviderKey": "external",



The field "style":"CUSTOM_VIEW" defines that tile is rendered with HTML from "view" endpoint, and "dataProviderKey":"external" means that data push from external add-on service is provided to the tile javascript startup function.


Tile Configuration

When configuring the tile, user will login to Salesforce and grant API access for this app according to standard OAuth 2.0 web server authentication flow, resulting in a token used to make Salesforce API calls. To accomplish this our add-on needs to implement an OAuth Client, which is done by including "oauth2client.js" in your configuration page and calling OAuth2ServerFlow() on page load. Note that oauth2client.js is provided by the Jive-SDK and is not included as a file in the sample project.



<script src="/javascripts/oauth2client.js"></script>


<button type="button" id="grant-button">Grant Access</button>



$(document).ready( function() {

  var options = {

    grantDOMElementID: '#grant-button',

    authorizeUrl: '/oauth/authorizeUrl',



  OAuth2ServerFlow( options ).launch();




OAuth Configuration

OAuth2ServerFlow function defines a callback for the button defined with grantDOMElementID option, and when clicked, initiates the authentication flow with Salesforce.


The authorizeUrl option points to an endpoint on your add-on service that handles communication and token storage. This is implemented by extending OAuth util class from the Jive-SDK.



var jive = require('jive-sdk');

var oauthUtil = jive.util.oauth;

var tokenStore = jive.service.persistence();


// overrides jive-sdk/routes/oauth.js to store access token

var myOauth = Object.create(jive.service.routes.oauth);

module.exports = myOauth;


myOauth.fetchOAuth2Conf = function() {

    return jive.service.options['oauth2-sfdc'];



myOauth.oauth2SuccessCallback = function( state, originServerAccessTokenResponse, callback ) {

    var content = originServerAccessTokenResponse['entity'];'tokens', state['viewerID'], {

        ticket : state['viewerID'],

        accessToken: content['access_token'],

        refreshToken: content['refresh_token'],

        instanceUrl: content['instance_url']


    .then( function() {

        callback({'ticket': state['viewerID'] });




myOauth.getTokenStore = function() {

    return tokenStore;



When user has completed the OAuth flow, configuration page uses add-on service endpoints to get recently selected reports, and to search for reports. User interface controls were implemented with JQuery UI Dialog and Autocomplete.



Selecting a report

The config page uses JQuery autocomplete to display results for searched reports. Results are retrieved with a call to osapi.http.get() which proxies the call via Jive server, and targets an endpoint on add-on service.




  'href': SERVICE_BASE_URL + '/sfdc-report-wide/search?' +

          'ticketId=' + ticketId +

          '&term=' + encodeURIComponent(searchTerm),

  'format': 'json',

  'authz': 'signed'

}).execute(function( response ) {

  ... show search results



Implementing the search endpoint route on server


exports.search_route = function (req, res) {

  var url_parts = url.parse(req.url, true);

  var queryPart = url_parts.query;

  var term = queryPart["term"];

  var ticketId = queryPart["ticketId"];


  sfdc.getReportsBySearchTerm(term, ticketId)

  .then(function (entity) {

    // success response

  }).catch(function (err) {

    // error




See next section for details on how getReportsBySearchTerm function is implemented.


When user has selected a report, and clicks apply to save tile configuration, the page saves report ID, name and instance URL from Salesforce with a call to jive.tile.close(). This triggers Jive to register the tile instance with add-on service with the provided configuration.



$("#configure").click(function() {



    reportId: reportId,

    reportName: reportName,

    instanceUrl: sfdcInstanceUrl,

    ticketId: ticketId





Getting report data


Using Salesforce API

When querying for data, the ticket id from tile instance configuration is used to find OAuth token from data storage, and then Salesforce Reporting API  is used to get report data. OAuth token is sent in Authorization header of each request and error response codes are handled to refresh the token and detect if a report has been removed from Salesforce (see the full code sample for this error handling).



var API_PATH = '/services/data/v33.0';


exports.getReport = function(reportId, ticketId) {

    return doGet("/analytics/reports/" + reportId + "?includeDetails=true", ticketId)

    .then( function(response) {

        return response.entity;




function doGet(uri, ticketID) {

    var tokenData;

    return getAuthToken(ticketID)

    .then(function(found) {

        if (found) {

            tokenData = found;

            var headers = {'Authorization': 'Bearer ' + found.accessToken};

            return jive.util.buildRequest(found.instanceUrl + API_PATH + uri, 'GET', null, headers, null);





In a similar way, Salesforce Query API is used to search for reports based on name.

exports.getReportsBySearchTerm = function(term, ticketId) {

    var query = "select Id, Name, Format from Report where Name like '%" + term + "%' limit 50";

    return doQuery(query, ticketId)

    .then( function(entity) {

        return entity;




function doQuery(query, ticketId) {

    return doGet("/query?q=" + encodeURIComponent(query), ticketId)

    .then( function(response) {

        return response.entity;




Sending data to tile

The add-on service will periodically query for Salesforce report data, and send it to Jive for each registered tile. This is done by registering a task with which will be executed by jive-sdk at specified interval. Data is set to Jive with jive.tiles.pushData() function and will then be stored by Jive and will be available when rendering tile.



exports.task = new

    function() {

        jive.tiles.findByDefinitionName( 'sfdc-report-wide' ).then( function(instances) {

            instances.forEach( function( instance ) {





    120*60*1000 // 2hr interval



exports.processTileInstance = function(instance) {

    var reportData;

    var cfg = instance.config;


    sfdc.getReport(cfg.reportId, cfg.ticketId)

    .then(function(data) {

        reportData = data;

        return sfdc.getReportProperties(cfg.reportId, cfg.ticketId);


    .then(function(properties) {

        var tileData = {


            detailColumnInfo: [],

            groupingColumnInfo: [],

            groupingsDown: reportData.groupingsDown.groupings,

            aggregates: reportData.reportMetadata.aggregates,

            contents: reportData.factMap,



        .... report data processing

        return jive.tiles.pushData(instance, {data: tileData});




Salesforce report "fact map" data values are passed here as-is to the tile, but the data payload could be completely custom. Grouping info and aggregate data for summary reports are also sent for rendering groups in tile grid. Metadata about each column containing display label and data type is saved into detailColumnInfo field.


Rendering tile

When tile is rendered Jive gets the html page from "view" endpoint defined in tile's definition.json. The page javascript gets latest data push in jive.tile.onOpen() handler and renders it using jqGrid. Tabular report data rows are simply passed to jqGrid for display, but summary reports also require handling of groupings and aggregate values.



jive.tile.onOpen(function(config, options) {

    var app = new ReportView(config);




function ReportView(data) { = data;



ReportView.prototype.init = function() {

  var gridOptions = {

    datatype: 'local',



  if ( == "TABULAR") {

    $.each(['T!T'].rows, function (n, row) {

      // ...add data rows to grid



  else if ( == "SUMMARY") {

    gridOptions.grouping = true;

    // ...define summary groupings and aggregates for jqGrid







What's Next?