Azure DevOps integration with Google Hangouts Chat
Tags: azure, devops, chat, integration
Published: 2020-03-03 19:36:29

In my previous post: {% link https://dev.to/ib1/user-experience-how-to-design-a-train-station-not-4bb9 %} we veered away to the topic of UX Design. Let's return to the Azure Devops and discuss various ways of its integration with  third-party services and, in particular, with Google Hangouts Chat.

##Azure DevOps Service Hooks
Let's imagine the situation when you want to receive a notification about new User Stories, Pull Requests, or Build completions on your favorite means of team communication, such as Slack or Microsoft teams. Or you want to trigger some workflow based on Azure DevOps event. Well, you can! Service hooks let you run tasks on other services when events happen in your Azure DevOps Services projects. Service hooks concept is based on a pub/sub model where a producer publishes events to the topic, and a consumer subscribes and handles those events.
If you open Azure DevOps/Project Settings/Service Hooks menu, you will see that there are a lot of predefined integration points, including Jenkins, Trello, and others. There is also a hook to the excellent workflow automation tool [Zapier](https://zapier.com). It allows to easily integrate various services and APIs and I highly recommend it, although it is a subject for another post. Surprisingly, there are no predefined integration with Microsoft's own [Microsoft Power Automate](https://flow.microsoft.com) (formerly Microsoft Flow). 
![Azure DevOps Service Hooks](https://dev-to-uploads.s3.amazonaws.com/i/9rhvfmzgnlq9l7gos0xi.png)

## Azure DevOps Web Hooks
So what to do if your favorite service is not on the list? What if we want to send a message to Google Chat? Fear not, Web Hooks are to the rescue!
Web Hooks provide a way to send a JSON representation of an event to any public endpoint (HTTP or HTTPS). And that opens multitude of possibilities. 
Let's create one. When we select a web hook option, we are also able to select an event trigger. Here we have all kind of triggers related to Pull Requests, Builds, Work Items etc. For the purpose of this post let's choose a "Work Item commented on" option, which triggers an event for every new comment posted on a User Story or a Task.
![azure devops web hook](https://dev-to-uploads.s3.amazonaws.com/i/oo70ss6iuhp0eh745p6n.png)
Next, we need to provide a URL of our public endpoint. We also have some options for authentication. And we can customize to some extent a level of details passed in the event. I will show you later in this post how to setup an endpoint. Let's pretend that we have it already at this point and enter into the form.
![azure devops web hook](https://dev-to-uploads.s3.amazonaws.com/i/1ojewkurwofu521bi7n4.png)
There is a convenient "Test" button that sends a mock event to the configured URL and allows you to see a JSON message format that we will use later to transform it to the format that Google Chat can understand.
![azure devops web hook](https://dev-to-uploads.s3.amazonaws.com/i/2s6toszvvttilfmrc57v.png)
Great! We finished Azure DevOps configuration. Now to the Google Hangouts Chat.

## Google Chat Webhooks
Incoming webhooks let you send asynchronous messages into Hangouts Chat from applications, and they are fairly simple to configure. Let's create a new "DevOps" Room and in the members' menu select "Configure Webhooks"
![google chat webhook](https://dev-to-uploads.s3.amazonaws.com/i/4sbt3uevcjz2osxm8uva.png)
Add a new webhook. We can also configure a custom bot image.
![google chat webhook](https://dev-to-uploads.s3.amazonaws.com/i/spoptlomyvfm5dbi6b9g.png)
After we've finished with addition, a dedicated URL is assigned to the webhook. Please make a note of it as we'll need it in a moment.
![google chat webhook](https://dev-to-uploads.s3.amazonaws.com/i/wmvcga2mgqod9n8jlh97.png)
Let's move to the final step and tie all of this together.

## Azure Function
So now we have configured both Azure DevOps service hook and Google Chat webhook but they still do not talk to each other. DevOps sends a JSON message in a format that Chat is not able to understand. We need a way to transform the message an tie those two services together. There are a lot of options for that. We could use Zapier, mentioned above. Azure Logic apps has an integration with Google Chat out of the box. But why use ready-made solution when we can develop our own, right?
One of the option is Azure Function. It is simple, cheap, can be secured on a function or host level, and support Javascript language, among others.
I will not tell you here how to create a function, as there are many tutorials that go to excruciating level of details, [for example here](https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-function-app-portal). On a screenshot below I've created a "WorkItemUpdate" function with HTTP trigger.
![azure function](https://dev-to-uploads.s3.amazonaws.com/i/e7ird5oj56vybbo315on.png)
A code for the function is below:
```javascript
const https = require('https');

module.exports = function (context, req) {
    context.log('JavaScript HTTP trigger function processed a request.');

    if (req.body && req.body.message && req.body.message.text) {

        context.res = {
            status: 200,
            body: 'OK'
        };

        const data = JSON.stringify({
            text: req.body.message.text
        })

        const options = {
            hostname: 'chat.googleapis.com',
            port: 443,
            path: '/v1/spaces/<space>/messages?key=<key>&token=<token>',
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Content-Length': data.length
            }
        }

        const botReq = https.request(options, (res) => {
            context.log(`statusCode: ${res.statusCode}`)

            res.on('data', (d) => {
                context.log(d)
            })



            res.on('end', () => {

                context.done()
            });

            context.done()
        })

        botReq.on('error', (error) => {
            context.log(error)
            context.res = {
                status: 500,
                body: 'error'
            };
            context.done()
        })

        botReq.write(data)
        botReq.end()
        //context.done()

    }
    else {
        context.res = {
            status: 400,
            body: "Please pass a JSON body.message.text"
        };
        context.done();
    }
};
```

Please notice that "path" parameter in the code is the URL that we got from Google Chat webhook. The purpose of the code is to transform JSON received from DevOps event to the { 'text': 'custom message' } format accepted by Chat, and submit it to the Chat's webhook.

In the Azure portal we again see a convenient "Test" section where we can test our function in isolation. We can also see "Get Function URL" link.
![function url](https://dev-to-uploads.s3.amazonaws.com/i/kb25ymxozx6hvxb83042.png)
Copy it, we need it now! Remember that we glossed over URL configuration for Azure DevOps service hook above? That the URL that we need to enter there.

Finally, all configuration is done. Let's enter a new comment in a User Story, and in a second it will appear as a new message in our Google Chat Room:
![google chat message](https://dev-to-uploads.s3.amazonaws.com/i/2z1tyq710d2e5jeuq25d.png)

##Conclusion
We  have seen that Azure DevOps can be integrated with a number of services and were able to create a custom integration with Google Hangouts Chat using Azure function. 
I hope it was useful, here as a cat (and a dog) for you as a proof that different species and services can live peacefully with each other.
![cat and dog together](https://dev-to-uploads.s3.amazonaws.com/i/nla1bk4dtc87pyj65cei.png)