The Ops Community ⚙️

Cover image for PagerDuty Alerts for Important(ish) Stuff in GitHub
Mandi Walls for PagerDuty Community

Posted on

PagerDuty Alerts for Important(ish) Stuff in GitHub

(Or wherever webhooks are found)

Our team at PagerDuty has a number of open source repositories for our Ops Guides. These are a bunch of online docs that we created and manage about topics we think will help folks who use our products. The projects are stable; they don’t get much in the way of additions, outside pull requests, or issues, which means we’re not watching them too closely. So, when something does come in, we’d like to know about it by getting a PagerDuty notification, and not risk it getting lost in all of the GitHub info we get about other, busier repos. Plus, folks have their own preferences for how they handle GitHub notifications, so a generic solution for the whole team will be helpful.

PagerDuty has GitHub integrations that make use of change events. Change events are helpful in that they can provide context if a service has an incident - was that incident related to a recent change? - but change events don’t create alerts and notifications. These integrations are awesome, just not what we want for this use case!

What We Want

Our problem is this:

We want PagerDuty notifications generated from activities - issues and pull requests - on a number of low-traffic GitHub repositories, without folks needing to change their GitHub preferences.

How We Get There

We’re going to use a construct in PagerDuty called an App Event Transformer. Don’t worry if the docs seem complex! We’re going to use some existing code to bootstrap our project!

App event transformers allow us to use JavaScript to transform (get it?) incoming information into PagerDuty events. That JavaScript is edited in the PagerDuty web UI and lives on our servers. Even some of our third party integrations are transformers under the hood. In this article we’ll be catching events from GitHub and transforming them into PagerDuty events!

Start Here!

A great starting point is the PagerDuty App Event Transformer Sample project on GitHub. This project uses GitHub as the information source that will trigger PagerDuty alerts for new issues opened in a repo.
Follow the set up instructions in the README and use the code in transformer.js to create your own app event transformer. The instructions for setting up a transformer in that project are quite detailed, so we won’t repeat them here. Follow those, and when you have that working, come back here and we’ll add the rest!

How Do You Debug This??

Fortunately for us, the debug setting in the app event transformer set up is super helpful. It will tell us if there are missing values or if the alert couldn’t be parsed for some reason. In this example, the code is trying to find a value for an href that is missing in a link object:

PagerDuty web UI showing a resolved incident with an error message included, "link href is missing or blank"

GitHub has great tools for managing and configuring webhooks. One of those is a feature in the webhook support for redelivering a payload, so we won’t have to create a bunch of extra issues or pull requests to test out our code. We can create one to start and use redeliver. You can see this feature from the “edit” screen of your webhook. Click the Recent Deliveries tab at the top of the page:

GitHub web UI for the Manage Webhooks page. The "Recent Deliveries" tab is open, including a webhook for pull_request.opened. The "Redeliver" button is shown.

You’ll see the rest of the webhook, including headers, below the redeliver button. This is super useful for figuring out which parts of the payload you’d like to use in your alert. You can scroll through the attributes of the payload and choose the pieces that make the most sense for how your team works and manages incidents.

Changing the Alert Format a Little Bit

Before we add support for pull requests, let’s update the format of the output a little bit to make it more extensible. We’ll walk through those changes together, but you can also find the full source code for the updated transformer at the bottom of this post.

Inside the payload for the event the custom_details in a PagerDuty event can be almost anything you need it to be. In the current code, it’s just a string capturing the body of the GitHub issue and the username of the GitHub user who posted the issue:

   let normalized_event = {
        event_action: incidentAction,
        dedup_key: dedupKey,
        payload: {
            summary: incidentSummary,
            source: 'GitHub',
            severity: PD.Critical,
            custom_details: `${body.issue.body} (${body.issue.user.login})`
        },

        links: [{
            "href": githubLinkURL,
            "text": "View In GitHub"
        }],
    }
Enter fullscreen mode Exit fullscreen mode

The issue body can have a lot of info, and different teams might find other parts of the issue object more helpful to include in the alert. So let’s change the code around a little bit to make some space for other information.

First, I’m going to add a new variable on line 21, called customDetails, so that when we add support for pull requests, we can pick different parts of the incoming payload object based on the event type:

    let customDetails = ‘’;
Enter fullscreen mode Exit fullscreen mode

Now assign a value to customDetails in the section that reads issues event types. I’ve added this at line 33, inside our if statement checking to see if an action is opened or reopened:

            customDetails = {
                body: ${body.issue.body},
                user: ${body.issue.user.login}
            };
Enter fullscreen mode Exit fullscreen mode

Now we want to add this customDetails variable to our payload as the value of the custom_details field,:

…
payload: {
            summary: incidentSummary,
            source: 'GitHub',
            severity: PD.Critical,
            custom_details: customDetails
        },
…
Enter fullscreen mode Exit fullscreen mode

When my event is delivered to PagerDuty, I’ll have a nice table of information, and if I want to add more pieces later, it’s easy to add them to customDetails:

PagerDuty web UI showing a resolved incident. In the Custom Details section are two entries, one for the body and one for the user.

Adding Support for Pull Requests

With the introduction of the customDetails variable the code is now a little more flexible, and we can add support for pull requests to this same app event transformer. I’m choosing to do this with one transformer to keep things simple for the team who will be receiving these alerts. Everything we care about from GitHub will be dealt with by the same folks, so the alerts can go to the same destination. You might have other use cases where different actions should route to different teams. In those situations you can add multiple app event transformers to handle all of your use cases. Especially for Github, where the webhook settings offer a lot of granularity for what actions are sent to which webhooks, it’s handy to be able to split things up.

To start off, edit the webhook you set up in GitHub, and make sure it’s either set to Send me everything or that both issues and pull requests are selected under Let me select individual events.

GitHub web UI. The configuration page for a webhook. At the bottom of the configuration screen, shows the "Issues" and "Pull requests" boxes are checked.

Now add the code for pull requests after the section for issues. The event type in Github is pull_request, and it will work almost exactly the same as the section for issues, except that the information we want is in a different object. I’m adding this after the if block where we checked whether githubEventType was issues, around line 41. Now we’re checking if githubEventType is pull_request:

    } else if (githubEventType === 'pull_request') {
        dedupKey = `${body.pull_request.id}`; 
        if(body.action === 'opened' || body.action === 'reopened') {
            incidentSummary = `PR opened on ${body.repository.full_name}`;
            githubLinkURL = body.pull_request.html_url;
            customDetails = {
                title: `${body.pull_request.title}`,
                body: `${body.pull_request.body}`,
                user: `${body.pull_request.user.login}`
            };
        } else {
            emitEvent = false;
        }

    // if the github event type is something other than issues, don't send an event    
    } else {
        emitEvent = false;
    }
Enter fullscreen mode Exit fullscreen mode

I’ve changed the incidentSummary slightly from the issues section, just as an example. I’ve added the title, body, and user to the custom details so they’ll show up in the information table in the incident. The body of this PR is empty, so it shows up as “null” in my alert:

PagerDuty web UI. A triggered alert is shown. In the Custom Details section, there are three entries: body, title, and user. The body data is "null".

Adjust Service Notifications

Now that I have the alerts I want, from the GitHub activities I want to hear from, I can decide how to notify the folks on my team.

These are low-priority alerts; we don’t want to miss them, but there’s no reason to wake anyone up about them, or alert on the weekends. I could set the service to “Low urgency notifications, do not escalate”, which will use the on call engineer’s account settings for low urgency alerts. Hopefully that is something quiet, like an email notification.

I could also use the support hours setting for the service that will receive the alerts. I can define a limited window in which these alerts will generate notifications to the team:

PagerDuty web UI. The settings page for a service is shown, and the Assign and Notify section is set to "Based on support hours".

This is also a helpful option. We won’t miss things, and they won’t wake us up!

I can send alerts to this service from as many GitHub repositories as I want to. We have 10 to watch, and because the code is reading data out of the webhook payload, every repository can be configured the same way, and team members won’t have to change their GitHub preferences.

Summary

App event transformers are a powerful way to give teams control of the information they receive from important systems. If your ecosystem components aren’t supported out of the box or via an integration, a bit of JavaScript can help you get organized! If you build a transform for your team, tell us about it! We’d love to hear how you’re getting value from PagerDuty.

Learn more about PagerDuty’s features and sign up for a trial on our website.

Source Code

export function transform(PD) {
    // capture incoming webhook body
    let body = PD.inputRequest.body;
    // capture incoming webhook headers
    const headers = PD.inputRequest.headers;
    let emitEvent = true;

    // GitHub stuff
    let githubEventType = '';

    // capture the GitHub event type
    for (var i = 0; i < headers.length; i++) {
        if('x-github-event' in headers[i]) {
            githubEventType = headers[i]['x-github-event'];
        }
    }
    let githubLinkURL = '';

    // Incident stuff
    let incidentSummary = '';
    let dedupKey = '';
    let customDetails = '';
    let incidentAction = PD.Trigger;

    // only process webhook if it's an 'issues' event type
    if (githubEventType === 'issues') {
        dedupKey = `${body.issue.id}`;

        // if the github issue is opened set incident summary with the issue title
        if (body.action === 'reopened' || body.action === 'opened') {
            incidentSummary = `[${body.repository.full_name}] Issue:  ${body.issue.title}`;
            githubLinkURL = body.issue.html_url;
            customDetails = {
                body: `${body.issue.body}`,
                user: `${body.issue.user.login}`
            };
        // if neither of these actions are occurring, don't send an event   
        } else {
            emitEvent = false;
        }
     // repeat for pull requests
     } else if (githubEventType === 'pull_request') {
        dedupKey = `${body.pull_request.id}`; 
        if(body.action === 'opened' || body.action === 'reopened') {
            incidentSummary = `PR opened on ${body.repository.full_name}`;
            githubLinkURL = body.pull_request.html_url;
            customDetails = {
                title: `${body.pull_request.title}`,
                body: `${body.pull_request.body}`,
                user: `${body.pull_request.user.login}`
            };
        } else {
            emitEvent = false;
        }

    // if the github event type is something other than issues or PRs, don't send an event    
    } else {
        emitEvent = false;
    }

    // preparing the normalized_event to be sent to pagerduty
    let normalized_event = {
        event_action: incidentAction,
        dedup_key: dedupKey,
        payload: {
            summary: incidentSummary,
            source: 'GitHub',
            severity: PD.Critical,
            custom_details: customDetails
        },

        links: [{
            "href": githubLinkURL,
            "text": "View In GitHub"
        }],
    }

    if (emitEvent) PD.emitEventsV2([normalized_event])
  }
Enter fullscreen mode Exit fullscreen mode

Top comments (0)