DEV Community

Nabheet Madan
Nabheet Madan

Posted on

My first Custom Slack App - Part 2

Blogs in the Series

Part Description
1 Setting the backend
2 Setting the API server to talk to Backend as well as frontend
3 Setting the Frontend to talk to API server
4 Deploying the app to something permanent
5 Research on deploying to slack app store

What we have done so far?

In our previous Blog we have set up our database google spreadsheet and enabled the API's to interact with it. So lets now build our NodeJS application which will talk to frontend and backend. Although this part utilize some set up from the next blog which is slack set up lets hold on to that till part 3 is out.

Setting up modules needed for our NodeJS App

Our NodeJS application will need some package to talk to Slack as well as spreadsheet.So luckily slack provides us with its npm package ** @slack/bolt ** and google spread sheet provides us with ** google-spreadsheet **. Lets first initialze a node project and install the two packages

npm init
npm i @slack/bolt
npm i google-spreadsheet

Lets do some coding

Since now we have everything set up lets create an index.js file and use the modules.

const App               = require('@slack/bolt');
const GoogleSpreadsheet = require('google-spreadsheet');

Once we have the modules lets add our excel spread sheet id as well as credential file and slack app token/secrets(hold on to this, we will be covering in next part). The tokens can be handled in a better way for sure, we were looking for a solution which is quick:)

const creds = require('./cred.json');
const spreadsheet = new GoogleSpreadsheet(<Spreadsheet id got in previous blog>);
const app = new App({
  signingSecret: "<secret1>",
  token: "<token>",
});

So now we have created two objects app for interacting with slack and spreadsheet for interacting with backend. Going forward we will be using the methods of these objects to get our work done.

Slack uses a concept called as command using which you can create dynamic things for example our poll as shown below.

Whenever this command is fired from Slack we shall be able to listen it in our API, so adding the code to handle command /ourpoll. This will return a response which will be shown as poll like above using say method as shown below.
The say method has a proper structure in which we need to pass the values as in response and buttons which is nothing but actions. We can use slack bot kit builder where can build our response and see the corresponding structure format.

app.command('/ourpoll', async ({
  command,
  ack,
  say
}) => {
  // Acknowledge command request
  ack();
  say({
    blocks: [{
        "type": "section",
        "text": {
          "type": "mrkdwn",
          "text": command.text
        }
      },
      {
        "type": "actions",
        "elements": [{
            "type": "button",
            "text": {
              "type": "plain_text",
              "text": "Yes",
              "emoji": true
            },
            "action_id": "Yes"
          },
          {
            "type": "button",
            "text": {
              "type": "plain_text",
              "text": "No",
              "emoji": true
            },
            "action_id": "No"
          }
        ]
      }
    ]
  });

});

Once we have the response given back to slack we are also calling spreadsheet.useServiceAccountAuth to save the data in our google spreadsheet.

  spreadsheet.useServiceAccountAuth(creds, function (err) {
   spreadsheet.addRow(1, {
      Question: command.text,
      Yes: ' ',
      No: ''
    }, function (err) {
      if (err) {
        console.log(err);
      }
    });
    if (err) {
      console.log(err)
    }
  });

So now we know how to handle the commands from slack but what about the two button or actions yes/no which we have added in our response. When user provide his/her decision we should be able to handle it. That is app object has provided method action for the same.
Lets first handle action Yes. The code can be enhanced in much much better way which i intend to do soon:). If you notice we are reading the rows of excel and then updating the relevant row with the decision.

app.action('Yes', ({
    body,
    ack,
    say
}) => {
    ack();
    spreadsheet.useServiceAccountAuth(creds, function(err) {
        spreadsheet.getRows(1, function(err, rows) {
            var filteredRows = rows.filter(function(el) {
                return el.question == body.message.blocks[0].text.text;
            });
            var sayConcatenated, yescount;
            filteredRows.forEach(row => {
                console.log('Row count is ' + row.yescount);
                if (row.yescount == "") {
                    row.yescount = 1;
                } else {
                    row.yescount = Number(row.yescount) + 1
                }
                yescount = row.yescount;
                if (row.yes == "" || row.yes == " ") {
                    row.yes = body.user.id;
                } else {
                    row.yes = row.yes + ',' + body.user.id;
                }
                if (row.yesusername == "") {
                    row.yesusername = body.user.name;
                } else {
                    row.yesusername = row.yesusername + ',' + body.user.name;
                }
                row.save();
                let users = row.yes.split(",");
                const say = users.map(user => '<@' + user + '>');
                sayConcatenated = say.reduce((acc, sayone) => acc + sayone);
            });
            say(`${sayConcatenated} ${yescount} geeks said Yes for ${body.message.blocks[0].text.text}`);
        });
        if (err) {
            console.log(err)
        }
    });
});

Now we handle action No

app.action('No', ({
    body,
    ack,
    say
}) => {
    ack();
    spreadsheet.useServiceAccountAuth(creds, function(err) {
        spreadsheet.getRows(1, function(err, rows) {
            console.log(body.message.blocks);
            var filteredRows = rows.filter(function(el) {
                return el.question == body.message.blocks[0].text.text;
            });
            var sayConcatenated, nocount;
            filteredRows.forEach(row => {
                if (row.nocount == "") {
                    row.nocount = 1;
                } else {
                    row.nocount = Number(row.nocount) + 1
                }
                nocount = row.nocount;
                if (row.no == "" || row.no == " ") {
                    row.no = body.user.id;
                } else {
                    row.no = row.no + ',' + body.user.id;
                }
                if (row.nousername == "") {
                    row.nousername = body.user.name;
                } else {
                    row.nousername = row.nousername + ',' + body.user.name;
                }
                row.save();
                let users = row.no.split(",");
                const say = users.map(user => '<@' + user + '>');
                sayConcatenated = say.reduce((acc, sayone) => acc + sayone);
            });
            say(`${sayConcatenated} ${nocount} geeks said No for ${body.message.blocks[0].text.text}`);
        });
        if (err) {
            console.log(err)
        }
    });

What is next?

So now we have our API which is talking to backend as well frontend ready. In the next blog we will set up the slack part so that Slack can talk to our NodeJS app which in turn can talk to the backend. Off course we will have a demo video also for the same:)

Top comments (0)