Serverless computing is a software design pattern where applications are hosted by a third-party service, eliminating the need for server software and hardware management by a developer. Applications are broken up into individual functions that can be invoked and scaled individually.
The Freshworks app development platform includes a serverless environment to enable you to create apps that run in response to events that can originate from the Freshworks products, the app setup lifecycle, as well as events from any compatible service on the Internet.
Sample use cases
Serverless apps primarily respond to events and run custom business logic. The following event types are supported.
Product Events
Product events are triggered by the Freshworks products, such as ticket-create, ticket-update, and lead-delete, that an app can subscribe to.
App Set-up Events
App set-up events are triggered when an app is installed or uninstalled. The two corresponding events are,
External Events
Apps can be invoked in response to events that occur in an external product or service, by creating webhooks in that product or service and subscribing to the webhooks in the app. Every time the webhook is triggered, an external event invokes the app in response.
Scheduled Events
One-time or recurring scheduled events can be created to invoke serverless apps at an appropriate time.
In this tutorial, we are going to build a serverless app that creates a GitHub issue every time a Freshdesk ticket is created. The app will use GitHub webhooks to close the ticket when the corresponding issue is closed.
The following image describes a high-level flow diagram for the app's business logic.
As a prerequisite, you should have completed codelab #3 and have a working version of that app. You can switch to level3
directory from level2 using the following command
cd ../level3
Make sure you have the iparam_test_data.json
and oauth_config.json
setup based on the learnings from the previous tutorial.
Let us now extend the boilerplate server.js created in the previous section to create issues in GitHub.
onTicketCreateHandler
method with the code from the following function snippet. /**
* Handler for onTicketCreate event
*
* A GitHub issue is created based on the ticket details from the payload received in the function argument
*
* @param {object} args - payload
*/
onTicketCreateHandler: function (args) {
$request.post(`https://api.github.com/repos/${args.iparams.github_repo}/issues`, {
headers: {
Authorization: 'token <%= access_token %>',
'User-Agent': 'FreshHuddle Sample User Agent'
},
isOAuth: true,
json: {
title: args.data.ticket.subject,
body: args.data.ticket.description_text,
labels: [
"bug"
]
}
}).then(data => {
console.info('Successfully created the GitHub issue for the Freshdesk ticket');
saveMapping({ ticketID: args.data.ticket.id, issueID: data.id, issueNumber: data.number }).then(function () {
console.info('Successfully set the mapping in the db');
}, error => {
console.error('Error: Failed to set the mapping in the db');
console.error(error);
});
}, error => {
console.error('Error: Failed to create the GitHub issue for the Freshdesk ticket');
console.error(error);
});
}
function saveMapping(data) {
var dbKey = String(`fdTicket:${data.ticketID}`).substr(0, 30);
var dbKey2 = String(`gitIssue:${data.issueNumber}`).substr(0, 30);
return Promise.all([$db.set(dbKey, { issue_data: data }), $db.set(dbKey2, { issue_data: data })])
}
function lookupTicketId(issueNumber) {
var dbKey = String(`gitIssue:${issueNumber}`).substr(0, 30);
return $db.get(dbKey)
}
We will revisit this function later to understand why it stores two mappings.
fdk run
command to run the app.onTicketCreate
event from the drop-down list.onTicketCreate
event mapping object. This will add two new event handlers (app event handlers) to our serverless app.{ event: 'onAppInstall', callback: 'onInstallHandler' },
{ event: 'onAppUninstall', callback: 'onUnInstallHandler' }
onTicketCreateHandler
method. This method registers a webhook with Github by using a unique target URL generated by the app. It also stores the target URL in Data Storage. We will shortly see why!onInstallHandler: function (args) {
generateTargetUrl().then(function (targetUrl) {
$request.post(`https://api.github.com/repos/${args.iparams.github_username}/${args.iparams.github_repo_name}/hooks`, {
headers: {
Authorization: 'token <%= access_token %>',
'User-Agent': 'FreshHuddle Sample User Agent'
},
isOAuth: true,
json: {
name: 'web',
active: true,
events: [
'issues'
],
config: {
url: targetUrl,
content_type: 'json'
}
}
}).then(data => {
$db.set('githubWebhookId', { url: data.response.url }).then(function () {
console.info('Successfully stored the webhook in the db');
renderData();
}, error => {
console.error('Error: Failed to store the webhook URL in the db');
console.error(error);
renderData({ message: 'The webhook registration failed' });
});
}, error => {
console.error('Error: Failed to register the webhook for GitHub repo');
console.error(error);
renderData({ message: 'The webhook registration failed' });
})
})
.fail(function () {
console.error('Error: Failed to generate the webhook');
renderData({ message: 'The webhook registration failed' });
});
}
installHandler
method. The method will fetch the webhook URL from Data Storage when the app is uninstalled and request GitHub to delete the webhook. This ensures that GitHub stops sending notifications to the app after it is uninstalled.onUnInstallHandler: function (args) {
$db.get('githubWebhookId').then(function (data) {
$request.delete(data.url, {
headers: {
Authorization: 'token <%= access_token %>',
'User-Agent': 'freshdesk',
Accept: 'application/json'
},
isOAuth: true
}).then(() => {
console.info('Successfully deregistered the webhook for GitHub repo');
renderData();
}, () => renderData())
}, error => {
console.error('Error: Failed to get the stored webhook URL from the db');
console.error(error)
renderData({ message: 'The webhook deregistration failed' });
});
}
{ event: 'onExternalEvent', callback: 'onExternalEventHandler' }
onExternalEventHandler
method after the onTicketCreateHandler
method. This method fetches the ticket-id corresponding to the GitHub issue that was closed, from Data Storage. This is why we stored the second mapping from the Github issue to the Freshdesk ticket, when the issue was created . We can then close the ticket by a Request API invocation to Freshdesk.onExternalEventHandler: function (payload) {
const payloadData = typeof payload.data === 'string' ? JSON.parse(payload.data) : payload.data;
if (payloadData.action === 'closed') {
getData(payloadData.issue.number).then(data => {
$request.post(payload.domain + "/api/v2/tickets/" + data.issue_data.ticketID,
{
headers: {
Authorization: '<%= encode(iparam.freshdesk_api_key) %>'
},
json: {
status: 5
},
method: "PUT"
}).then(() => {
console.info('Successfully closed the ticket in Freshdesk');
}, error => {
console.error('Error: Failed to close the ticket in Freshdesk');
console.error(error)
})
}, error => {
console.error('Error: Failed to get issue data. Unable to create ticket');
console.error(error);
});
} else {
console.error('The action of the GitHub issue is not defined');
}
}
We are now ready to test the app thoroughly.
onExternalEvent
from the drop-down list.{
"account_id": 12345,
"event": "onExternalEvent",
"timestamp": 1500351361762,
"domain": "https://sample.freshdesk.com",
"options": "freshdesk.onExternalEvent",
"data": "{ \"action\": \"closed\", \"issue\": { \"id\": 1, \"number\": 2, \"body\": \"a new issue created for a bug\", \"title\": \"bug issue\" } }",
"headers": {
"Content-Type": "application/json"
}
}
data
property, in the issue object replace the id value with the id you created through an earlier simulation of the app. You should be able to locate the issue from the Issues page of the GitHub repository.Yay! 🎉 You have successfully completed and tested your first advanced serverless app that reacts to product events, app setup events, and external events, as well as leverages OAuth for secure API requests and Data Storage to maintain mappings. Give yourself a pat on the back.👏