Phoebe
Posted on September 27, 2020
Continuing on my HelloWorld version, I completed my HelloGalaxy version. This journal article is to share the development process of heybot’s HelloGalaxy version, and one of my favourite challenges which there was no official documentation and I figured out my own solution (using private_metadata).
My full code is available here.
The Roadmap
>>Project Overview
>> Part 1: Hello World version
>> Part 3: Final version (coming up next)
Recalling from my original roadmap, this is where we stand.
Demo
Objectives
In this version, my goals were:
- To connect to BambooHR API by creating a trial account and Token key.
- When I say “Hello”, heybot replies with action options (buttons).
- When I click on a button to choose the service, heybot askes for employee ID via a
Modal
. Heybot returns answers to me.
Resources
BambooHR API
The Documentation is clear and well organized; APIs are grouped by activities (Report, Login, Employees).
I wanted to build heybot for one single customer, so all I needed was (1) an account (NOTE: a trial account is valid in 7 days only), (2) subdomain is “mycompany” which I created when I opened my trial account (My company is monsterinc :D), (3) an API key which I made from my trial account “Settings”.
How I create an API key:
I recommend copying the API key and saving it in a note because we have to use it frequently.
One more important thing that you should note is the AUTHORIZATION_TOKEN ("Basic NDJim******** *) in *headers.
Why do we need this?
We can't use the API key to send an HTTP Request to BambooHR directly. An API key will have to be converted into an authorization token.
Before running the Flask app, we have to "export AUTHORIZATION_TOKEN= "NDJim**********
" to be an environment variable (similar to what I did to SLACK_TOKEN
and SLACK_SIGNING_SECRET
which I explained in Step 3.1 here).
How to use BambooHR API Key to find
AUTHORIZATION TOKEN
?
Go to the API Reference page and choose an API and "Try It".
Challenges and Solutions
Find the right documentations on Slack API
Slack has been upgrading its API, and because of that, some features or methods were either discontinued or were still in use, but their support resources were deprecated.
In HelloGalaxy version, I needed to build:
- rich-text messages containing buttons/ menu/ date picker
- a modal.
For #1, Slack introduced
Blocks
(supported by Block Kit Builder to replaceattachments
according to this post. The purpose of this upgrade is to support more interactive features (vote, buttons, menu) or rich contents (video, link, image).
This is my action message built with Blocks
and sent to the user by chat_postMessage()
method.
You can see my Block payload here under def understood_greeting(self, user)
.
For #2, Modal
replaced Dialogs
according to this post. This change led to the retirement of dialog.open()
method and the introduction of views.open()
method to open Modal (we can't use chat_postMessage()
or other regular methods to open a Modal
).
Get content from request body with Content-type = application/x-www-form-urlencoded
Normally, working with Content-type = application/JSON
is very easy and straightforward. We can use request.data
, request.get_data
, request.json
, request.get_json
.
application/x-www-form-urlencoded
is a bytestring type file. So, if we use request.data
(the common solution), we will get an empty string.
This StackOverflow post is a comprehensive answer. My full answer with details was in here too. Below is my solution for your quick look.
@ app.route('/slack/request_handler', methods=['POST'])
def request_handler():
# return an ImmutableMultiDict with 1 pair
payload = request.form
# get value of key 'payload'
a1 = payload['payload']
# Note that if you have single quotes as a part
# of your keys or values, this will fail due to
# improper character replacement.
# Convert a string (representation of a Dict))
# to a Dict
a2 = json.loads(a1)
# Get value of key "channel"
channel = a2["channel"]
print(channel)
# {'id': 'D01ACC2E8S3', 'name': 'directmessage'}
Using private_metadata to solve view_submission payload's limitations.
NOTE: When a button clicked, it sends back a block_actions
payload8 containing channel_id
, action_id
. When a modal is closed (made submission), a view_submission
payload is sent.
I save samples of block_actions
payload and view_submission
payload here.
Context:
- A
Modal
appears to get Employee ID input from a user. Once the user provided the ID and clicked "Submit", aview_submission
payload was sent to my server.
I used that ID to get the answer from BambooHR.
I wanted to reply to the user.
Problem:
To call the correct function to send a GET
request to BambooHR and build a right template message, need an action_id
which I set when I created the Block
message. view_submission
payload doesn't have action_id
.
In order to reply, a channel_id
is a must. view_submission
payload doesn't have channel_id
.
Solution:
There was no official documentation on this from Slack. However, I utilized private_metadata
to store the action_id
received from the block_actions
payload.
This Slack doc suggested storing channel_id
in private_metadata
.
Pseudocode
Code
# build view payload
def get_employee_id_modal(self, channel_id, action_id):
private_metadata = {
"channel_id": channel_id,
"action_id": action_id
}
# jsonify a dict into a string
# (required type for private_metadata)
json_private_metadata =
json.dumps(private_metadata)
# return a block
return ({
"type": "modal",
"private_metadata": json_private_metadata,
"callback_id": "employee_id_modal",
[....]
})
Thanks for reading!
Phoebe
Posted on September 27, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.