Handling Complicated Tasks with a Chatbot
This is a text summary of slides for a talk.
Who am I?
- Web Platform Engineer and Manager @ucsf_library (PHP, Javascript)
- Technical lead for the Ilios Project open source curriculum management system for health science education.
- UCTech Slack Ambassador
Ilios Story
- Ilios is a 20 year old project which started as an MS Access Database
- Built by UCSF, but available for free (and supported)
- Installed at medical, dental, pharmacy, nursing, around the world including UCI and maybe soon UCSD
- Been through several revisions since then, but in 2014 we decided to rewrite it
@greeterbot
Your First UCTech Friend
https://github.com/stopfstedt/uctech-greeter
- If you want to get your feet wet with bots we could always use help with greeterbot
- If you don't remember what it does just say
greet me
to that app in Slack
@Zorgbort
Or whatever stupid name you want to give your bot
https://github.com/ucsf-ckm/zorgbort
- The Ilios project bot
- Mainly what I'll be talking about today
Automate Everything
If you&##39;re scared to deploy on Fridays, you should be scared to deploy ever.
— Jessica Mauerhan (@JessicaMauerhan) July 27, 2018
Don&##39;t you have tests? Backups? Roll back? Come on.
Automation Brings Speed
- Jason is always 'Dialing it in'
- This is a task which may never be completely done, but like CI setup it's always worth getting right
- Good, reliable, automation truly pays off over the long term
and sometimes money!
- UCSF was able to offer paid hosting support to Stanford (and soon OHSU and USF)
- We were able to do this because we made the administrative costs very low for each instance
Our Automation Goal
Tag Code
- Tag Code as v65.4.3
- Go To Lunch
- v65.4.3 is now in Production
- Repeat
Zorgbort is just the fancy UI
The hard part is building automated processes
But knowing a UI can be built can make the automation process simpler
- Our first iteration was entirely conversational
- You would say
release the frontend
and Zorg would answer back with questions
- It wasn't as pretty, but it mostly got the job done.
Git tags
- We use git tags to drive our process
- Once something is tagged in github it kicks off automation
- Tagging things is easy
- Discovering tags is easy
Slack
- I'm going to be focusing on Slackbots
- There are all kinds of other chatbot options
- Whatever your team is most familiar with should work
- The bot is just a UI on top of a process
- We're using the slack conversation API, there is a newer Blocks API that you should check out.
Node.js
Easy to deploy lots of places
Botkit (and many other) frameworks
Cuz javascript is the BEST!
Home is where the bot lives
- Your bot will probably be portable so you can go with whatever makes you happy
- Lots of options for free bot hosts, or at the very least super cheap
- Some of the biggest names in hosting will host your app
- But probably anything you trust will do
- So what are the minimum requirements
Slackbot anatomy
… first of all they're called apps now
Don't call us, we'll call you Subscribe to the event types you need Governed by OAuth permission scopes Bot users served here Everything is eventual
https://api.slack.com/
Don't call us, we'll call you
POST
{
"token": "XXYYZZ",
"team_id": "TXXXXXXXX",
"api_app_id": "AXXXXXXXXX",
"event": {
"type": "name_of_event",
"event_ts": "1234567890.123456",
"user": "UXXXXXXX1",
...
},
"type": "event_callback",
"authed_users": [
"UXXXXXXX1",
"UXXXXXXX2"
],
"event_id": "Ev08MFMKH6",
"event_time": 1234567890
}
Everything is eventual
POST /api/chat.postMessage
{
"channel":"C061EG9SL",
"text":"I hope the tour went well, Mr. Wonka.",
"attachments":[
{
"text":"Who wins the lifetime supply of chocolate?",
"fallback":"You could be telling the computer exactly what it can do with a lifetime supply of chocolate.",
"color":"##3AA3E3",
"attachment_type":"default",
"callback_id":"select_simple_1234",
"actions":[
{
"name":"winners_list",
"text":"Who should win?",
"type":"select",
"data_source":"users"
}
]
}
]
}
Botkit
Our Hero
const Botkit = require('botkit');
const hal = new Botkit();
hal.hears('Open the pod bay doors', async (bot, message) => {
await bot.reply(message, "I'm Sorry Dave, I'm afraid I can't do that.");
});
Intelligent Conversation
const Botkit = require('botkit');
const Skynet = require('skynet');
const controller = new Botkit();
controller.hears(async (bot, message) => {
const reply = Skynet.process(message.text);
await bot.reply(message, reply);
});
- Services like AWS Lex can be used to make your bot easier to talk to
- If the bot is just a UI you could attach other UIs to the same structure
Don't trust the Developers at SlackHQ
Slack should be a thin layer on top of functionality
Because the slack API will change
Not without warning
- They're paid to invent new things
- they do this a lot
- so your bot will probably be old fast, that's ok.
@Zorgbort
module.exports = bot => {
bot.hears(['start release', 'release'], ['direct_message', 'direct_mention', 'mention'], startRelease);
bot.on('interactive_message_callback', chooseReleaseType);
bot.on('interactive_message_callback', confirmRelease);
bot.on('interactive_message_callback', doRelease);
};
https://github.com/ucsf-ckm/zorgbort
- Bot listens for different types of events or keywords and then passes execution off to another function.
@Zorgbort
chooseReleaseType
const startRelease = async (bot, message) => {
bot.reply(message, createActionReply(':cool: I just need to know:', releaseProject, [
{
name: 'project',
text: 'Which Project?',
type: 'select',
options: [
{
text: 'Ilios Frontend',
value: 'frontend',
},
{
text: 'Ember Simple Charts',
value: 'simple-charts',
},
]
},
]));
};
https://github.com/ucsf-ckm/zorgbort
- Some of those functions send messages back for clarification / authentication
@Zorgbort
releaseAndTag
const releaseAndTag = async (owner, repo, releaseType, namer) => {
const { nextVersion, currentVersion } = await incrementPackageVersion(dir, releaseType);
await commitAndTag(dir, version, releaseName);
const release = await Github.repos.createRelease({
owner,
repo,
tag_name: version,
name: releaseName,
body: releaseNotes,
draft: false,
prerelease: false,
});
return {
version,
releaseName,
releaseUrl: release.data.html_url
};
};
https://github.com/ucsf-ckm/zorgbort
- But the real meat is done without the bot at all, this makes it easier to switch out the conversational parts and to test the non-bot stuff.
Start the Conversation
Send Alerts
Start a Virtual Standup
Remind Yourself to Drink More Water
await bot.startConversationInChannel(SLACK_CHANNEL_ID);
await bot.say('The Server is On Fire!!! :fire:');
await bot.say('Everybody Blame Jon');
await bot.say("PS: don't tell Jon!");
Security
Depends on the specific threat
Slack requires token auth and SSL
You may need to validate users yourself
Authorize Users
if (validUsers.includes(user)) {
try {
return await releaseAndTag(owner, repo, releaseType, namer);
} catch (e) {
bot.reply(message, `Error: ${e.message} (stack trace in logs)`);
console.error(e);
}
- Slack UserIds are persistent
- Only as good as slack's account security
Testing locally
$ npm start
> zorgbort@1.0.0 start
> node zorgbort.js
Initializing Botkit v0.7.4
info: ** Using simple storage. Saving data to .data/db/
info: ** Starting webserver on port 3000
info: ** Serving webhook endpoints for Slash commands and outgoing webhooks at: http://0.0.0.0:3000/slack/receive
info: ** Serving login URL: http://0.0.0.0:8899/login
info: ** Serving oauth return endpoint: http://0.0.0.0:3000/oauth
So how to we hookup slack to port 3000?
$ npm install -g localtunnel
$ lt --port 3000 -s zorgbort-stage
your url is: https://zorgbort-stage.localtunnel.me
- The localtunnel NPM module is mad sweet
- Slack let's you pick any URL to send things to, create a test-bot app and a real one.