Extend Slack Workflows with Serverless Functions

This is the second article in a 4-part series that details how to extend Slack to better support enterprise use cases. To recap, article one describes the capabilities recently added to Slack that enable it to be used for running complex workflows.

The scale of change in Slack over time is large enough that it’s possible to now use Slack as the centerpiece of an enterprise workflow strategy. It now exposes a range of event hooks, extensions and input fields, allowing for a fully orchestrated experience. It’s reminiscent of HTML where early versions only described how to render a static Web page while HTML5 now describes a complex system for creating apps.

The focus of this document will be to to show how Slack can be further augmented with serverless technology. Driving human workflow in Slack is powerful, and adding serverless to the mix makes it even more so by allowing the Slack app to take action when reacting to user input.

The Power of Serverless

For those unfamiliar with serverless, it’s best understood as a way to create “worker” functions without spending time on server setup and configuration. These workers perform single tasks very well such as writing to a database or generating a thumbnail image. And the great thing is, they only run when they’re actively performing a task. Stitch a few together, and you can quickly create powerful, cost-effective solutions.

As of 2017 roughly 93% of serverless functions were deployed using Node.js. And an important quality of Node is that it is backed by Node Package Manager (NPM). From their Website:

“[NPM] is the world’s largest software registry, with approximately 3 billion downloads per week. The registry contains over 600,000 packages (building blocks of code).”

Once you’re accustomed to taking advantage of NPM, it’s difficult to approach a serverless solution without Node.js and NPM hand-in-hand. The breadth and quality of the packages make NPM a perfect fit for prototyping as well as production.

By way of example, I worked on a software product several years ago (prior to NPM) that conducted online surveys. One of the modules we needed to engineer was a text parser that could convert comma separated values (CSV) into structured data. It wasn’t a difficult engineering task, but unless you deal with this specific task day-to-day, it’s easy to overlook things. Everything from international character sets to smart-quotes caused issues with the parser. And it’s exactly the type of thing you no longer waste time on now that NPM is available. Just find a CSV-parsing library on NPM and deploy it as a serverless function. No server to manage; no custom code to author; no delays.

Serverless by Example

URL unfurling is a helpful feature that Slack already provides. Whenever a user posts a message containing a URL, Slack adds a summary and thumbnail, providing a preview for anyone reading the message without requiring them to visit the site.

I’ll build upon this concept and create a Slack app offering similar functionality. Each time a user uploads an Excel or CSV file (step 1 below), a Slack app will call a serverless function to interpret the file content and provide a “best-guess” set of charts to help users preview and visualize it (2). The user can then use this preview as a starting point when choosing and configuring a preferred visualization.

Serverless profiler preview

Note that the spreadsheet that was uploaded for this example contains multiple columns of information ranging from strings to numbers to dates. It’s a good data set to test with, because it shows how heterogeneous even small data sets can be.

Ultimately, given the variability of the data users might upload, there is no way to know for sure which chart visualization type is best. To be effective, our solution will need to engage the user in a conversational workflow, prompting them to choose and configure the visualization they prefer. This begins with step 3 below, where the user is prompted to choose the chart type.

Prompt the user to take action

If the user needs more information they can click the Help button (contained in section 3 above). This returns a brief set of instructions (step 1 below), along with a re-prompt message to choose the chart type (2).

Help Content and re-prompt message

Once the user chooses their desired chart type (a Pie chart in this case), a Slack dialog is shown, prompting them for the final configuration (step 1 below). The Name and Age columns are preselected, since these were the “best-guess” options chosen by the serverless function. The user can change these values as necessary (2).

Pie chart configuration dialog showing default values

Once the user submits their configuration choices, a final chart is rendered in Slack. Here we see the “Faculty Publications” series chosen as the preferred visualization. The spreadsheet now has a visualization that all users can see.

The final visualization: “Faculty Publications”

I’ll conclude this document with a review of the technologies involved, namely Intwixt (the process orchestrator) and serverless (which runs on AWS Lambda).

Intwixt in review

Three Intwixt process flows drive this example (see the image below). The process begins when the user uploads a file (1), and triggers the On File Upload flow.

A serverless function is called to profile the uploaded file (1.1). When complete, a URL is generated for accessing the visualization preview. This URL is sent to Slack, which displays the image (1.2).

The Choose Type flow is called next (2). This flow prompts the user to choose the chart type and also includes a button for Help. If the user clicks the Help button, a brief set of instructions is returned along with a re-prompt message to choose the chart type (2.1).

When the user chooses the chart type (3), the Config Pie Chart flow is run. This flow loads a Slack dialog (3.1), prompting the user to confirm the final chart configuration. The flow concludes by returning a message with the URL for the final visualization (3.2).

Chart preview and configuration workflow

A fourth and final flow is called when slack loads the URL for the chart image. This fourth flow (shown below) returns the chart as a PNG image. The serverless function that generates the chart PNG is shown in step 4.1.

Serverless chart PNG generator

Serverless in review

Serverless is the real workhorse in this solution, providing the ability to analyze a spreadsheet and generate a corresponding visualization. The two serverless functions that provide this capability are shown here. Note how each uses packages provided by Node Package Manager for core capabilities such as reading an Excel file or generating an image.

  1. Profiler function: analyzes the file to provide a “best-guess” profile (in JSON) describing possible visualizations. This function leverages the following NPM packages: axios (read a file over HTTP), csv-parse (convert CSV to JSON Array), exceljs (convert Excel to JSON Array), underscore (manipulate a JSON Array), moment (parse dates and times), and regression (analyze series data)
  2. Generator function: uses the JSON profile provided by the profiler to create the chart as a PNG image. It uses the following NPM packages: plotly (create a chart from JSON) and phantom (generate a PNG image).

Serverless is powerful at breaking things up and creating focused worker functions. But this also comes at a cost as it can be difficult to incorporate serverless functions back into business workflows in a predictable and reusable manner.

One insight we had early on when incorporating serverless functions was the importance of following standard documentation. In order for our process engine to orchestrate serverless functions with the same agility as APIs and third-party services, the serverless functions needed to be properly documented. (Process engines work best when inputs and outputs are well defined.)

Fortunately, JSDoc is already the accepted standard. Here, for example, is a serverless function that returns a factorial. It includes inline documentation describing one input and one output (both numbers).

/**
* @param {object} event The event
* @param {number} event.input The number to seed the factorial
* @param {object} context The call context
* @param {myCallback} callback The callback
*/
exports.handler = function(event, context, callback) {
function factorial(num) {
if (num > 1) {
return num * factorial(num - 1);
} else {
return 1;
}
}
if (isNaN(event.input) || event.input < 0) {
callback("Input must be a non-negative integer");
} else {
callback(null, { output: factorial(Number(event.input)) });
}
};
/**
* @callback myCallback
* @param {string} error The response error
* @param {object} success The callback message
* @param {number} success.output The final answer for the factorial
*/

The JSDoc markup serves as the contract for the function, allowing it to be predictably modeled. Here is the serverless function selected in the Intwixt process designer. Note how the field ID (output), its data type (number), and the description (The final answer for the factorial) are all imported directly from the JSDocs. By formalizing how the serverless function is authored, it’s possible to treat serverless functions as reusable building blocks with the same predictable surface as an API.

Modeling serverless inputs and outputs at design time

Summary

Serverless extends the usefulness of Slack workflows by taking action. And given the large repository of building blocks available via Node Package Manager, it’s possible to build even complex solutions with relative ease.

The next article in this series will describe how to extend Slack workflows using third-party APIs. I’ll focus on the Open API specification format and ways to incorporate third-party APIs into multi-step Slack workflows spanning multiple users.