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
94
95
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;
    const thumbUrl = "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";

    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": "${thumbUrl}",
      "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 atomist section of your Rug project’s package.json.

{
  "name": "@atomist-contrib/atomist-tutorials",
  "version": "0.3.0",
  ...
  "atomist": {
    "requires": "[1.0.0-m.4,2.0.0)",
    "extensions": {
      "com.atomist.rug:rug-function-http": "[0.7.3,1.0.0)"
    }
  }
}

Now commit those changes to your Rug project.

$ ( cd .atomist && npm install )
$ git add .atomist/handlers/command/SearchStackOverflow.ts .atomist/package*.json
$ 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>

Once the Rug archive is successfully published, the Atomist bot will be notified the new archive is available in your team and it should become available to use within a few minutes.

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