Add Bot Command

You can write custom commands that the Atomist Bot can run in your team. We call these skills, and you can teach the bot new skills to automate common tasks your team routinely performs. Those skills are implemented inside Rug command handlers and this tutorial will show you how to create a new one.

Prerequisites

Completing the Getting Started Guide and the Rug CLI setup, Rug project creation, TypeScript setup, and Rug archive publishing tutorials are prerequisites for this tutorial.

Search StackOverflow from Slack

In this example, you will teach the Atomist Bot to search StackOverflow and show you the first few results right in chat.

Here is the TypeScript code for a command that does this.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import {
    CommandHandler, Intent, Parameter, ParseJson,
    ResponseHandler, Tags,
} from "@atomist/rug/operations/Decorators";
import {
    CommandPlan, HandleCommand, HandlerContext, HandleResponse,
    MessageMimeTypes, Response, ResponseMessage,
} from "@atomist/rug/operations/Handlers";
import * as mustache from "mustache";

const apiSearchUrl =
    `http://api.stackexchange.com/2.2/search/advanced?pagesize=3&order=desc&sort=relevance&site=stackoverflow&q=`;
const webSearchUrl = `http://stackoverflow.com/search?order=desc&sort=relevance&q=`;

@CommandHandler("SearchStackOverflow", "Query Stack Overflow")
@Tags("stack-overflow")
@Intent("search SO")
class SearchStackOverflow implements HandleCommand {

    @Parameter({ description: "your search query", pattern: "^.*$" })
    public query: string;

    public handle(ctx: HandlerContext): CommandPlan {
        const plan = new CommandPlan();

        plan.add({
            instruction: {
                kind: "execute",
                name: "http",
                parameters: {
                    method: "get",
                    url: encodeURI(apiSearchUrl + this.query),
                },
            },
            onSuccess: {
                kind: "respond",
                name: "SendStackOverflowResults",
                parameters: this,
            },
        });
        return plan;
    }
}
export const searchStackOverflow = new SearchStackOverflow();

@ResponseHandler("SendStackOverflowResults",
    "Shows answers to a query on Stack Overflow")
class StackOverflowResponder implements HandleResponse<any> {

    @Parameter({ description: "your search query", pattern: "^.*$" })
    public query: string;

    public handle( @ParseJson response: Response<any>): CommandPlan {
        return CommandPlan.ofMessage(
            renderResults(response.body, encodeURI(this.query)),
        );
    }
}
export let responder = new StackOverflowResponder();

function renderResults(result: any, query: string): ResponseMessage {

    if (result.items.length === 0) {
        return new ResponseMessage("No results found.",
            MessageMimeTypes.PLAIN_TEXT);
    }

    // mark the last item for rendering purpose by mustache
    result.items[result.items.length - 1].last = true;

    return new ResponseMessage(mustache.render(`{
  "attachments": [
{{#answers.items}}
    {
      "fallback": "{{{title}}}",
      "author_name": "{{{owner.display_name}}}",
      "author_link": "{{{owner.link}}}",
      "author_icon": "{{{owner.profile_image}}}",
      "title": "{{{title}}}",
      "title_link": "{{{link}}}",
      "thumb_url": "https://slack-imgs.com/?c=1&o1=wi75.he75&url=https%3A%2F%2Fcdn.sstatic.net%2FSites%2Fstackoverflow%2Fimg%2Fapple-touch-icon%402.png%3Fv%3D73d79a89bded",
      "footer": "{{#tags}}{{.}}  {{/tags}}",
      "ts": {{{last_activity_date}}}
    }{{^last}},{{/last}}
{{/answers.items}},
        {
            "title": "See more >",
            "title_link": "${webSearchUrl + query}"
        }
  ]
}`,
        { answers: result }), MessageMimeTypes.SLACK_JSON);
}

The @Intent specified in line 17, search SO, is the command that the Atomist Bot will respond to by executing this command handler.

This command takes a single parameter called query (line 21), which is the search term that will be passed to the StackOverflow API. Unless you provide a query parameter when calling this command, the Atomist Bot will always prompt for it.

Lines 26–40 tell Atomist to call the StackOverflow REST API URL and, on success, use the SendStackOverflowResults response handler, defined starting on line 46. It simply formats the search results from the StackOverflow API call.

Rug Command Handlers

See the Rug command handler user-guide for more information.

Get the Code Into Your Rug Project

Add the code example above into a new file called SearchStackOverflow.ts in the .atomist/handlers/command directory of the local repo for your Rug project like so:

$ cd atomist-tutorials
$ curl -o .atomist/handlers/command/SearchStackOverflow.ts \
    https://raw.githubusercontent.com/atomist/end-user-documentation/master/.atomist/handlers/command/SearchStackOverflow.ts

Your command handler uses the http function, so you need to add that dependency as an extension into the manifest.yml of your Rug project.

group: "atomist-contrib"
artifact: "atomist-tutorials"
version: "0.3.0"
requires: "[0.25.3,0.26.0)"
extensions:
  - "com.atomist.rug:rug-function-http:0.6.2"

Now commit those changes to your Rug project.

$ git add .atomist/handlers/command/SearchStackOverflow.ts .atomist/manifest.yml
$ git commit -m 'Added StackOverflow command handler'

Publish

Now, publish your Rug project, which add your new Atomist Bot skill, using the same team ID you determined in the Publish a Rug Project tutorial.

$ rug publish -i <YOUR_TEAM_ID>

Make it Active in Slack

In #general or any channel in your Slack team that Atomist Bot has been invited to, type this message, replacing <group> and <artifact> with the values for group and artifact found in your Rug project’s .atomist/manifest.yml.

@atomist add skills <group>:<artifact>
Add Bot Skills

Tell the Bot to load the new skills with this message:

@atomist refresh skills
Refresh Bot Skills

Running the command in slack

Once available in your team, you can use the new command:

@atomist search SO

Upon which the Atomist Bot will open a thread and ask you to enter a value for the query parameter. Enter “python3 runtime error” and submit. The Atomist Bot will trigger the execution of your command handler and return the results in the Slack channel.

Search StackOverflow results