Building a Slack App with Native SFDC Integration
Tags: tutorial, webdev
Published: 2022-04-13 14:36:35

### Synchronizing Data

At last, we’ve arrived at the final part of our series on using Salesforce’s [Slack Starter Kit](https://github.com/developerforce/salesforce-slack-starter-kit)  to quickly scaffold a deployable Slack App that interacts with Salesforce data. The Slack Starter Kit makes it tremendously easy to authenticate with Salesforce, organize your code into reusable chunks, and deploy the project to Heroku for live access. We’ve largely based this series on [these video tutorials showcasing how to build Slack Apps](https://www.youtube.com/playlist?list=PLgIMQe2PKPSKcl26YQoKCR7F3CwIc8kxi). 

[In our first post](https://dev.to/salesforcedevs/building-a-slack-app-with-native-sfdc-integration-o94), we got acquainted with the Slack Starter Kit and set up our development environment. [In our second post](https://dev.to/salesforcedevs/building-a-slack-app-with-native-sfdc-integration-4m4m), our Slack app issued a query to fetch data from a Salesforce org and then presented the result with UI components from Block Kit. Now, we’re going to extend that pattern to showcase how you can edit Salesforce data entirely within Slack. Let’s get started!


![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/smjc2xf1gr5pxyszvis3.jpg)

Photo by [Edgar Moran](https://unsplash.com/@ymoran?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on[ Unsplash](https://unsplash.com/s/photos/salesforce?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)


### Creating a shortcut

Outside of the Block Kit UI, Slack has support for two other interactivity systems: [Slash Commands](https://api.slack.com/interactivity/slash-commands) and [shortcuts](https://api.slack.com/interactivity/shortcuts/). Slash Commands are entered by a user in Slack’s text input (available in every channel), while shortcuts are graphical in nature. Since they’re easier to visualize, we’ll demonstrate shortcuts by creating a shortcut that will fetch our list of contacts and give us the ability to edit them.

Adding a shortcut (or a Slash Command, for that matter) first requires that you tell Slack the name of the command. Go to your app’s Overview page on Slack, and then click **Interactivity & Shortcuts**:


![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mjjmuisto15dcu3h9iku.png)

Click **Create New Shortcut**, and select **Global** as your shortcut type, then click **Next**. On the next page, enter these values:



* **Name**: Edit contacts
* **Short Description**: Edits contacts on SFDC
* **Callback ID**: `edit-contact-shortcut`

Click on **Create**, then click **Save Changes**. Switch over to your Slack workspace, and click on the plus sign in the text area. You’ll be able to browse all of your workspace shortcuts. Here, you’ll also see the brand new shortcut you just created:


![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jgrmqs4728p4gbfxk40d.png)

This shortcut doesn’t do anything yet, but this process is necessary for Slack to know about your shortcut. Next, let’s add the code to handle the event that fires whenever someone clicks on this shortcut.


### Wiring up the shortcut

In your code editor, navigate to `apps/slack-salesforce-starter-app/listeners/shortcuts/index.js`. This is the spot where we connect shortcut events to the code that’s executed. There’s already one shortcut given to us by the Starter Kit: `whoami`. The given line suggests to us what we need to do: We call a function called `shortcut` and pass in a string and a function name. In this case, our string is the callback ID we previously defined, and our function name is the code we’ve yet to write. Change the file contents to look like this:


```
const { whoamiCallback } = require('./whoami');
const { editContactCallback } = require('./edit-contact');

module.exports.register = (app) => {
   app.shortcut('who_am_i', whoamiCallback);
   app.shortcut('edit-contact-shortcut', editContactCallback);
};
```


We’re laying the groundwork here by saying: “_Slack App, if you get a shortcut with a callback ID of `edit-contact-shortcut` run `editContactCallback`_.”

In this same folder, create a file called `edit-contact.js`, and paste these lines into it:


```
'use strict';

const {
  editContactResponse,
  authorize_sf_prompt
} = require('../../user-interface/modals');

const editContactCallback = async ({ shortcut, ack, client, context }) => {
  try {
    await ack();
    if (context.hasAuthorized) {
      const conn = context.sfconnection;
      await client.views.open({
        trigger_id: shortcut.trigger_id,
        view: await editContactResponse(conn)
      });
    } else {
      // Get BotInfo
      const botInfo = await client.bots.info({ bot: context.botId });
      // Open a Modal with message to navigate to App Home for authorization
      await client.views.open({
        trigger_id: shortcut.trigger_id,
        view: authorize_sf_prompt(context.teamId, botInfo.bot.app_id)
      });
    }
  } catch (error) {
    // eslint-disable-next-line no-console
    console.error(error);
  }
};

module.exports = { editContactCallback };
```



Now, this might look intimidating, but most of it simply concerns the authentication boilerplate, ensuring that a user has an active SFDC connection. In the first logical path (if `context.hasAuthorized` is `true`), we execute a function called `editContactResponse`, which accepts our open Salesforce `conn`ection. In the negative case, we ask the user to go to the Home tab to reauthenticate, just as we did in Part 1 of this tutorial.

Navigate to the `apps/slack-salesforce-starter-app/user-interface/modals` folder, and create a file called `edit-contact-response.js`. Here, we’ll pop open a modal with contact information similar to the rows we saw in the Home tab in Part 2 of this tutorial:


```
'use strict';
const { Elements, Modal, Blocks } = require('slack-block-builder');

const editContactResponse = async (conn) => {
  const result = await conn.query(
    `Select Id, Name, Description FROM Contact`
  );
  let records = result.records;

  let blockCollection = records.map((record) => {
    return Blocks.Section({
      text: `*${record.Name}*\n${record.Description}`
    }).accessory(
      Elements.Button()
        .text(`Edit`)
        .actionId(`edit_contact`)
        .value(record.Id)
    );
  });

  return Modal({ title: 'Salesforce Slack App', close: 'Close' })
    .blocks(blockCollection)
    .buildToJSON();
};

module.exports = { editContactResponse };
```


The main difference between the code in Part 2 and this block is that we’re using an array called `blockCollection`, which lets us construct an array of blocks (in this case, Section blocks). `blocks` knows how to take this array and transform it into a format that Slack understands, which makes it super simple to create data through a looped array, as we’ve done here. In Part 2 of our series, we constructed a giant string of data. By using `BlockCollection`, however, we can attach other Slack elements—such as buttons—which we’ve done here.

Lastly, in `apps/slack-salesforce-starter-app/user-interface/modals/index.js`, we’ll need to export this function, so that it can be imported by our `edit-contact.js` function:


```
'use strict';
const { whoamiresponse } = require('./whoami-response');
const { editContactResponse } = require('./edit-contact-response');
const { authorize_sf_prompt } = require('./authorize-sf-prompt');

module.exports = { whoamiresponse, editContactResponse, authorize_sf_prompt };
```


After you’ve deployed this new code to Heroku via git push, switch to your Slack workspace and try executing the shortcut; you’ll be greeted with a dialog box similar to this one:


![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1l4q9hu9r2pwht523y6s.png)

### Updating Salesforce data

We’re able to fetch and display Salesforce data. Now, it’s time to connect the Edit button to change Salesforce data!

Many of Slack’s interactive components have an [`action_id`](https://api.slack.com/interactivity/handling#payloads), which, like the `callback_id`, serves to identify the element which a user acted upon. Just like everything else in the Starter Kit, there’s a special directory where you can define listeners for these action IDs: `apps/slack-salesforce-starter-app/listeners/actions`. In the `index.js` file there, let’s add a new line that ties together the action ID with our yet-to-be-written functionality:


```
'use strict';
const { appHomeAuthorizeButtonCallback } = require('./app-home-authorize-btn');
const { editContactButtonCallback } = require('./edit-contact-btn');

module.exports.register = (app) => {
  app.action('authorize-with-salesforce', appHomeAuthorizeButtonCallback);
  app.action('edit_contact', editContactButtonCallback);
};
```


In this same folder, create a new file called `edit-contact-btn.js`, and paste these lines into it:


```
'use strict';
const {
  editIndividualContact,
  authorize_sf_prompt
} = require('../../user-interface/modals');

const editContactButtonCallback = async ({ body, ack, client, context }) => {
  const contactId = body.actions[0].value;
  try {
    await ack();
  } catch (error) {
    // eslint-disable-next-line no-console
    console.error(error);
  }

  if (context.hasAuthorized) {
    const conn = context.sfconnection;
    const result = await conn.query(
      `SELECT Id, Name, Description FROM Contact WHERE Id='${contactId}'`
    );
    let record = result.records[0];
    await client.views.push({
      trigger_id: body.trigger_id,
      view: editIndividualContact(record)
    });
  } else {
    // Get BotInfo
    const botInfo = await client.bots.info({ bot: context.botId });
    // Open a Modal with message to navigate to App Home for authorization
    await client.views.push({
      trigger_id: body.trigger_id,
      view: authorize_sf_prompt(context.teamId, botInfo.bot.app_id)
    });
  }
};

module.exports = { editContactButtonCallback };
```


The beginning and ending of this file should look familiar: We’re sending an `ack` response back to Slack to let it know that our app received the event payload (in this case, from clicking on the Edit button). We’re also checking whether or not we’re still authenticated. Here, we’re doing a single DB lookup using the ID of the contact, which we attached as a value to the Edit button when constructing our UI.

This chunk of code creates another modal design which we need to define. Back in `apps/slack-salesforce-starter-app/user-interface/modals`, create a file called `edit-individual-contact.js` and paste these lines into it:


```
'use strict';
const { Elements, Modal, Blocks } = require('slack-block-builder');

const editIndividualContact = (record) => {
  return Modal({ title: 'Edit Contact', close: 'Close' })
    .blocks(
      Blocks.Input({ blockId: 'description-block', label: record.Name }).element(
        Elements.TextInput({
          placeholder: record.Description,
          actionId: record.Id
        })
     )
   )
   .submit('Save')
   .callbackId('edit-individual')
   .buildToJSON();
};

module.exports = { editIndividualContact };
```

Here, we’ve created a modal with a single block: an input element. The element will be pre-populated with the contact’s description. We can edit this block and change the description to whatever we want. 

There are two important notes to point out in this code snippet:


1. Notice that we’re attaching an `actionId` to the input element. This is analogous to the ID we attached to the Edit button earlier, except this time, it’s dynamically generated based on the ID of the record we’re editing.
2. You’ll also notice that we have another ID, the `callbackID`, which is attached to the modal itself. Keep the existence of these IDs in the back of your mind: we’ll address both of these in a moment. For now, open up the `index.js` file in this same directory, and `require/export` this new modal-creating function:

```
const { editIndividualContact } = require('./edit-individual-contact');
// ...
module.exports = {
  whoamiresponse,
  editContactResponse,
  editIndividualContact,
  authorize_sf_prompt
};
```

Now when you click the Edit button, you’ll be prompted to change the description:


![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ckktl7bqmuwr7fn7xteh.png)

We now need to send this updated text to Salesforce. Click on the **Save** button and…nothing happens. Why? Well, Slack has a different set of events for interactions like these, called [view submissions](https://api.slack.com/reference/interaction-payloads/views). The Starter Kit provides a good jumping off point when building an app, but it doesn’t handle every Slack use case, including this one. But that’s not a problem—we’ll add the functionality ourselves!

Within the `apps/slack-salesforce-starter-app/user-interface folder`, create a new folder called `views`. Just like before, our **Save** button here has an action ID to identify it: `edit-individual-contact`. We’ll head back into `apps/slack-salesforce-starter-app/listeners/actions/index.js` to configure this to a function:


```
const { editIndividualButtonCallback } = require('./edit-individual-btn);

// ... 

app.action('edit-individual-contact', editIndividualButtonCallback);
```


Create a new file called `edit-individual-contact.js`, and paste these lines into it:

```
'use strict';

const { submitEditCallback } = require('./submit-edit');

module.exports.register = (app) => {
  app.view('edit-individual', submitEditCallback);
};
```


This format is identical to the other listeners provided by the Slack Starter Kit. The only difference is that we are calling the `view` method. We also need to register this listener alongside the others. Open up `apps/slack-salesforce-starter-app/listeners/index.js`, and `require/register` the new view listener:


```
const viewListener = require('./views');

module.exports.registerListeners = (app) => {
  // ...
  viewListener.register(app);
};
```


Next, in `apps/slack-salesforce-starter-app/listeners/views`, create another file called `submit-edit.js` and paste these lines into it:


```
'use strict';
const { editContactResponse, authorize_sf_prompt } = require('../../user-interface/modals');

const submitEditCallback = async ({ view, ack, client, context }) => {
  try {
    await ack();
  } catch (error) {
    // eslint-disable-next-line no-console
    console.error(error);
  }

  if (context.hasAuthorized) {
    const contactId = view.blocks[0].element.action_id;
    const newDescription =
      view.state.values['description-block'][contactId].value;
    const conn = context.sfconnection;
    await conn.sobject('Contact').update({
      Id: contactId,
      Description: newDescription
    });

    await client.views.open({
      trigger_id: view.trigger_id,
      view: await editContactResponse(conn)
    });
  } else {
    // Get BotInfo
    const botInfo = await client.bots.info({ bot: context.botId });
    // Open a Modal with message to navigate to App Home for authorization
    await client.views.push({
      trigger_id: view.trigger_id,
       view: authorize_sf_prompt(context.teamId, botInfo.bot.app_id)
    });
  }
};

module.exports = { submitEditCallback };
```


Let’s discuss those IDs that we set before. When Slack sends event payloads over to our app, it automatically generates an ID for every input element by default. That’s because Slack doesn’t know what the underlying data _is_. It’s your responsibility to name the elements via action IDs. Slack uses these IDs as keys to populate the payload. When you receive Slack’s payload, you can use the keys you provided to parse the data the user entered.

Now, if you go through the flow to edit your contact’s description, you’ll notice that the modal will correctly save. To verify that the data on the Salesforce side was updated, run `sfdx force:org:open` in your terminal and navigate to the Contacts tab.


### Conclusion

The Slack Starter Kit has made it an absolute breeze to build a Slack App that listens to user events. Beyond that, though, it also makes interacting with Salesforce and Heroku an absolute pleasure. We’ve covered just about everything that the Starter Kit can do. If you’d like to learn more about how Slack and Salesforce can work together, check out our blog post on [building better together](https://developer.salesforce.com/blogs/2021/11/slack-and-salesforce-building-better-together). Also, the backend framework that interacts with Salesforce is the wonderful [JSforce](https://jsforce.github.io/) project. Be sure to check out both its documentation and [the Salesforce API Reference](https://developer.salesforce.com/docs/apis) to learn more about what you can build!