Jun 19, 2017

Using JavaScript In Zaps Part 2

You need a way to trigger your code

Last time in part 1, we looked at a dead-simple bit of JavaScript code this time I want to create some more complex code but also have a convenient way to trigger that code outside of the Zapier interface. There are lots of Apps you can use but since I'm in Slack all day I find it's a convenient trigger and testing tool. This works better in a public channel, so I have one I use for this and related tasks. You could use a private channel, but when posting to a private channel, Zapier only looks on a timed schedule. Posting from a public channel makes the trigger INSTANT, and you'll see that tag on your step once you finish the trigger.

img-alternative-text

Once you have a channel to use, create a new Zap. I select the Slack App as a trigger and select the radio button for New Message Posted to Channel. In the next step you can click the drop down, and Zapier will poll your Slack and provide you with a list of your team's channels. You can use the search item that shows up first to find your channel or just scroll through and find it.

img-alternative-text

While I could trigger on any message in my channel, I like to add a filter step next. I set it to continue only if the text contains "jstest." Then my third step is the Code by Zapier step. We will come back to this step in a minute. My final step is another Slack App step to Send Channel Message. Select the same channel you used for the trigger and for now put anything in the Message Text. Eventually, we will use data from our JavaScript code here. Here's an overview of the steps you'll have.

img-alternative-text

More elaborate JavaScript code

I think it can be instructive to write some more complicated JavaScript code. One problem you might run up against when using JavaScript code in Zapier is timezone related. While you can set your time zone in your prefs and some Zap Apps will use that, the Code By Zapier App isn't one of them. The code always appears to think it's in UTC 0. We can solve this problem with Google's Timezone API. It's a pretty simple API, and you might figure it all out just by reading this post. However, you do need to get free API key, and the documentation can walk you through that. It's very straightforward.

When writing more complicated JavaScript code for Zapier I would suggest using a good JavaScript editor like Brackets, VSCode, or even jsHint.com to help. We can get started with our code by setting up a bunch of constants that we need.

/*jshint esversion: 6 */
const googleUrl = "https://maps.googleapis.com/maps/api/timezone/json?",
    la = "location=34.0522,-118.2437",
    timestamp = "×tamp=",
    googleKey = "&key=",
    currentDate = new Date(),
    SECS_PER_HOUR = 3600,
    MSECS_PER_SEC = 1000,
    timeAsSecs = parseInt(currentDate.getTime() / 1000),
    googleCmd = googleUrl + la + timestamp + timeAsSecs + googleKey;
//console.log(googleCmd);

You may want to change la with a city of interest to you, but you HAVE to replace the part that says <Your API KEY HERE> with the API key you get from Google. Otherwise you won't get timezone information returned, just an error message. You'll notice I have a log statement disabled that shows the actual command. If you have problems you'll want to enable that line to help debug. We are just creating a URL that will look something like:
https://maps.googleapis.com/maps/api/timezone/json?location=34.0522,-118.2437&timestamp=1497848541&key=
And again, your API key should appear at the end after that final equals sign.

Now we can use Zapier's built in fetch command which returns a promise. If you've never used Javascript promises you can find a great explanation of them on Pluralsight in Jonathan Mill's JavaScript Best Practices - watch the first three clips (Introduction, Callbacks, Promises) of the module Async Patterns. Pluralsight (where I work) is a subscription service, but you can get a free ten-day trial. However, the example below is pretty simple and you should be able to follow along.

fetch(googleCmd)
    .then(function (res) {
        return res.json();
    })
    .then(function (json) {

That's the basic boilerplate for doing the API fetch to Google. Now inside this then clause, we can just use dot notation to get to any property of the returned JSON object. If you read the docs, you'll see all the names, but at the end, we're going to print it out, so you'll see it there. The key thing we want is the rawOffset field and the dstOffset field.

const offsetSecs = (json.rawOffset + json.dstOffset) * MSECS_PER_SEC,

The rawOffset doesn't include any daylight savings time adjustment so we need to add the two fields together to get our local time adjusted for DST, then I convert the value to milliseconds. The currentDate variable is in UTC 0, I can use getTime() to get the timestamp in milliseconds, add the two together and I have the timestamp for my local time.

localTimeStamp = currentDate.getTime() + offsetSecs,

Now we can just create a new date object with that timestamp, create the output object (which Zapier provides to us). I will put the localTimeStamp, the new localDateObj and the json timezone object into output, log that object to the console and issue the callback() command.

localDateObj = new Date(localTimeStamp),
output= {localTimeStamp:localTimeStamp,  localDateObject: localDateObj, originalTimezoneObj: json};
console.log (output); 
callback(null, output);

As a final step we want to add a .catch(callback section in case anything goes wrong. The entire example should look similar to this:

const googleUrl = "https://maps.googleapis.com/maps/api/timezone/json?",
    la = "location=34.0522,-118.2437",
    timestamp = "×tamp=",
    googleKey = "&key=",
    currentDate = new Date(),
    SECS_PER_HOUR = 3600,
    MSECS_PER_SEC = 1000,
    timeAsSecs = parseInt(currentDate.getTime() / 1000),
    googleCmd = googleUrl + la + timestamp + timeAsSecs + googleKey;
console.log(googleCmd);
fetch(googleCmd)
    .then(function (res) {
        return res.json();
    })
    .then(function (json) {

        const offsetSecs = (json.rawOffset + json.dstOffset) * MSECS_PER_SEC,
            localTimeStamp = currentDate.getTime() + offsetSecs,
            localDateObj = new Date(localTimeStamp),
            output = {
                localTimeStamp: localTimeStamp,
                localDateObject: localDateObj,
                originalTimezoneObj: json
            };
        console.log(output);
        callback(null, output);
    })
    .catch(callback);

When you test this in Zapier if you click on the view your run javascript link you should see an object that looks something like the following.

{ localTimeStamp: 1497823736838,
  localDateObject: Sun Jun 18 2017 22:08:56 GMT+0000 (UTC),
  originalTimezoneObj: 
   { dstOffset: 3600,
     rawOffset: -28800,
     status: 'OK',
     timeZoneId: 'America/Los_Angeles',
     timeZoneName: 'Pacific Daylight Time' } }

In our final Step where we post back to the original channel, we can use the selector in the upper right of the Message Text area to pick our Javascript by name and then select a field or fields that we write back to the channel as shown in the image below. Now from you selected Slack channel you can post a message with the text you filtered on it step 2, in about a second you should see your Zap post back the text from your JavaScript code that you selected.

img-alternative-text

The problem with this code is every time we want to use it we have to cut and paste it into a new Code By Zapier step. This wasn't an issue when we wrote a couple of lines of simple code. However, as we write more complex code not only is this a violation of "Don't Repeat Yourself", it has the potential to be a maintenance nightmare. In the next post we'll turn this code into our very own Zapier App so we can easily reuse it just like any other Action in Zapier.

About Me

My photo
Tod Gentille (@todgentille) is now a Curriculum Director for Pluralsight. He's been programming professionally since well before you were born and was a software consultant for most of his career. He's also a father, husband, drummer, and windsurfer. He wants to be a guitar player but he just hasn't got the chops for it.