Increasingly, companies have been utilizing social media platforms to provide support services for their customers. Facebook and Twitter are highly popular in this regard due to their ubiquity and familiarity. By leveraging the power of Service Desk's new REST API and Atlassian Connect support, it is possible to extend Service Desk to support entirely new methods of customer interaction.
This guide will show you how to integrate Jira Service Desk with Twitter to create requests from a Twitter stream. We'll take you on a guided tour of an example app, jira-servicedesk-twitter-example
, that implements this functionality and explain the key concepts. You won't be building an app yourself in this guide, but you can browse, download, and even run the source code for the example app:
This guide is aimed at developers who are experienced with Jira Service Desk development and the Connect framework. If you are new to either of these, we recommend that you read our Getting started guide first.
The Service Desk REST API is the cornerstone for Atlassian Connect app development, serving as the primary means by which apps communicate with Service Desk. Using the REST API, your app can interact with Jira Service Desk in a number of ways, like retrieving a list of service desks, creating customer requests, and more! Check out the Jira Service Desk Cloud REST API documentation, if you'd like to learn more.
The Atlassian Connect Express framework provides a bundled version of the well-known request HTTP client, accessed via the addon object that is passed to your routes. To use it, you'd typically add code like this to the routes/index.js
file of your app:
1 2module.exports = (app, addon) => { ... let httpClient; app.get('/my-route', addon.authenticate(), (req, res) => { // Creating when in request context httpClient = addon.httpClient(req); }); // Creating when not in request context httpClient = addon.httpClient({ clientKey: clientKey, // The unique client key of the tenant to make a request to appKey: appKey // The key specified in your atlassian-connect.json descriptor }); ... }
This HTTP client could then be used to make secure REST calls to Service Desk from the backend of your app, like this: (Note, secure requests can also be made from your app's front-end by using the Request JavaScript module)
1 2httpClient.get('/some-endpoint', (err, res, body) => { console.log(body); });
While it's helpful to understand how the HTTP client works with Atlassian Connect Express, in practice it's better to not use the HTTP client directly. A better approach is to wrap the usage of the HTTP client in a simple REST client, so that it's decoupled from our app code. The example below shows how you might implement this in the routes/index.js
of your app (or in a file that is included by index.js
):
1 2class ServiceDeskClient { constructor(httpClient) { this.httpClient = httpClient; } getServiceDesks() { return promiseOf( this.httpClient.get, { url: '/rest/servicedeskapi/servicedesk', json: true } ); } getRequestTypes(serviceDeskId) { return promiseOf( this.httpClient.get, { url: `/rest/servicedeskapi/servicedesk/${serviceDeskId}/requesttype`, json: true } ); } getRequestTypeFields(serviceDeskId, requestTypeId) { return promiseOf( this.httpClient.get, { url: `/rest/servicedeskapi/servicedesk/${serviceDeskId}/requesttype/${requestTypeId}/field`, json: true } ); } createRequest(serviceDeskId, requestTypeId, requestFieldValues) { return promiseOf( this.httpClient.post, { url: `/rest/servicedeskapi/request`, body: { serviceDeskId, requestTypeId, requestFieldValues }, json: true } ); } }
You'll notice that the methods in the code above use a promiseOf
function. This simply wraps all of the HTTP client methods in a small utility function, promiseOf
, allowing us to use promises rather than callbacks:
1 2function promiseOf(fn, arg) { return new Promise((resolve, reject) => { function callback(err, res) { if (err) { reject(err); } else if (res.statusCode < 200 || res.statusCode > 299) { reject({error: `${res.statusCode} ${res.statusMessage}`}); } else { resolve(res.body); } } fn(arg, callback); }); }
Once you have implemented a REST client, it's easy to make REST calls to your Jira Service Desk instance. Have a look at the following code, which shows how you can use the REST client to make a GET call to a servicedesk
resource:
1 2const httpClient = addon.httpClient(req); const serviceDeskClient = new ServiceDeskClient(httpClient); serviceDeskClient.getServiceDesks() .then(serviceDesksResponse => { console.log(serviceDeskResponse); }, err => { console.error(err); });
The Twitter Streaming APIs offer near real-time access to Twitter's global tweet stream, allowing consumers to receive and process tweets in a reactive manner rather than polling REST endpoints. Several different streaming endpoints are available for use, each offering different capabilities and requiring different levels of access. For full details regarding the Streaming APIs and their capabilities, see the Twitter developer documentation.
If you are developing with Twitter's APIs, you must have Twitter API credentials. To obtain these credentials, you can either:
In this section, we'll have a look at the Twitter Streaming APIs and how to create Twitter Streaming API client for Jira Service Desk.
For our Twitter integration, we'll use the Twit library. Twit is fairly straightforward to use. Once you've added the dependency to your project via
1 2npm install twit --save-exact
a client can be instantiated with your Twitter API credentials, as shown in the example below:
1 2const Twit = require('twit'); const twitterClient = new Twit({ consumer_key: 'Twitter consumer key here', consumer_secret: 'Twitter consumer secret here', access_token: 'Twitter access token here', access_token_secret: 'Twitter access token secret here' });
You can use this Twit instance to do different things, like get the list of a user's followers or search for tweets matching certain criteria. The example code below uses the .stream()
method to create a tweet-stream object for a given endpoint and begin listening for tweets. All tweets that contain the text specified in the track
property will be streamed to the app, allowing us to react to and process them.
1 2const tweetStream = twitterClient.stream('statuses/filter', {track: '#AtlasCamp'}); tweetStream.on('tweet', tweet => { console.log(tweet.text); });
Just like we wrapped the HTTP client, it's good practice to write a simple wrapper client for the Twit library, so that the usage of external libraries is decoupled from your app code. See the example code below. The code below also shows how you can get your Twitter API credentials from environment variables, rather than storing them in the code.
1 2const DEFAULT_TWITTER_CONFIG = { consumer_key: process.env.TWITTER_CONSUMER_KEY, consumer_secret: process.env.TWITTER_CONSUMER_SECRET, access_token: process.env.TWITTER_ACCESS_TOKEN, access_token_secret: process.env.TWITTER_ACCESS_TOKEN_SECRET }; class TwitterClient { constructor(config) { this.twit = new Twit(config || DEFAULT_TWITTER_CONFIG); } streamTweets(options) { return new TweetStream(this.twit.stream('statuses/filter', options || {})); } } class TweetStream { constructor(twitStream) { this.twitStream = twitStream; } start() { this.twitStream.start(); return this; } stop() { this.twitStream.stop(); return this; } onTweetReceived(handler) { this.twitStream.on('tweet', handler); return this; } }
This gives us a Twitter Streaming API client that is easy to use, but also allows us to change the underlying library if we want to. Have a look at the following code, which shows how you can use the client to track tweets with the #AtlasCamp hashtag:
1 2const twitterClient = new TwitterClient(); const tweetStream = twitterClient.streamTweets({track: '#AtlasCamp'}) .onTweetReceived(tweet => { console.log(tweet.text); });
You have a REST client. You have a Twitter Streaming client. All that's left is adding the glue to join the two together!
One of the pieces that your app is still missing is the ability to create a customer request, so that it can create customer requests from incoming tweets. The following function in the routes/index.js
file takes a tweet, creates a customer request payload, then creates a customer request using the Service Desk REST API.
1 2function createRequestFromTweet(serviceDeskClient, serviceDeskId, requestTypeId, tweet) { // Normally, you'd use the serviceDeskId and requestTypeId to get the // list of fields for the request-type, then populate them. // For now, let's just create a payload directly and use that. const requestPayload = { summary: tweet.text, description: 'Created via Twitter' }; return serviceDeskClient.createRequest(serviceDeskId, requestTypeId, requestPayload); }
A few things to be aware of:
summary
and description
.WRITE
scope to the scopes list in your atlassian-connect.json
descriptor.We now have everything we need to seamlessly pipe tweets into Service Desk as customer requests. We just need a way of kicking the whole process off! Luckily for us, Atlassian Connect gives us a very easy method of doing this.
The atlassian-connect.json
descriptor contains a lifecycle
property, allowing us to specify endpoints within our app that will be hit at various stages of its lifecycle (see Lifecycle). The following code shows how we've added entries for the enabled
and disabled
events, which get fired when our app is enabled and disabled respectively by Service Desk:
1 2... "lifecycle": { "installed": "/installed", "enabled": "/enabled", "disabled": "/disabled" } ...
Finally, we just need to implement the associated route. You can see this in the index.js
file:
1 2... // You will need to set these values correctly for your instance const serviceDeskSettings = { serviceDeskId: 9, requestTypeId: 36 }; let tweetStream; module.exports = (app, addon) => { ... app.post('/enabled', (req, res) => { const appKey = req.body.key; const clientKey = req.body.clientKey; const httpClient = addon.httpClient({ appKey, clientKey }); const serviceDeskClient = new ServiceDeskClient(httpClient); const twitterClient = new TwitterClient(); tweetStream = twitterClient.streamTweets({track: '#AtlasCamp'}) .onTweetReceived(tweet => { createRequestFromTweet(serviceDeskClient, serviceDeskSettings.serviceDeskId, serviceDeskSettings.requestTypeId, tweet); }); }); app.post('/disabled', (req, res) => { tweetStream.stop(); }); ... }
If you'd like to see this integration in action, grab the code for our example app and run it: https://bitbucket.org/atlassianlabs/jira-servicedesk-twitter-example (instructions in the repository). Once it's running and installed in your development instance, try posting a tweet with the hashtag #AtlasCamp and you should see something like this:
If you've finished this tutorial, check out more Jira Service Desk tutorials.
Rate this page: