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.

  1. In the server.js file, replace the existing 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);
   });
 }
  1. Add the following helper code before the exports object, to save the mapping data of the ticket and the GitHub issue in Data Storage.
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.

  1. Use the fdk run command to run the app.
  2. Navigate to the simulation page on http://localhost:10001/web/test .
  3. Choose the onTicketCreate event from the drop-down list.
  4. Update the sample payload with your test data as necessary.
  5. Click the Simulate button, to simulate the event. An issue is created in GitHub for the ticket referenced in the simulation payload.
  1. Add the following JSON objects, in the server.js file, in the events array, just before the 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' }
  1. Add the following method after the 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' });
      });
  }
  1. Add the following method immediately after the 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' });
    });
  }
  1. Add the following object in the events array, this time to introduce an external event handler.
{ event: 'onExternalEvent', callback: 'onExternalEventHandler' }
  1. Add the following 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.

  1. Use the fdk run command to run the app.
  2. Navigate to the simulation page on http://localhost:10001/web/test .
  3. Choose onExternalEvent from the drop-down list.
  4. Replace the JSON object to simulate, with the following code snippet.
{
  "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"
  }
}
  1. In the 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.
  2. Click on the Simulate button to simulate the event.
  3. Navigate to your Freshdesk portal and verify if the ticket is closed.

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.👏