At work we use GitHub and ZenHub to manage our work. In particular, we have issues and the code written on those is in a PR which we connect to the issue in ZenHub. We also have a convention of linking to the issue in the PR description. This was kind of annoying because we basically have the information in two places, the link in the PR description and actually linking it in ZenHub - so I decided to automated it. You can find the final product here: https://git.sr.ht/~nds/connect-issue-to-pr.

This project ended up being quite interesting because I had to reverse engineer parts of the ZenHub API using their web client and the network tab because whilst they have a public API it doesn’t support everything I needed. Most notably, you can’t connect PRs to issues using their public API, you have to use an undocumented GraphQL one. This wasn’t too difficult, all I needed to go was observe what happens in the web client when you connect a PR to an issue and copy that request. It did require a little bit of twiddling because I was just writing out the raw GraphQL mutation, rather than using a dedicated library.

Connecting PRs to issues using the ZenHub API

To see exactly what is done to connect to the issue, have a look at the source. Otherwise, here’s a quick overview.

ZenHub has an undocumented GraphQL API where one of the functionalities it exposes is to connect PRs to issues. We do this via a mutation called CreateIssuePrConnection. Which is used like this:

mutation CreateIssuePrConnection($input: CreateIssuePrConnectionInput!) {
        createIssuePrConnection(input: $input) {
                issue {
                        id
                        __typename
                }
                __typename
        }
}

This takes one variable input which is an object with two fields “pullRequestId” and “issueId”. They’re both the ZenHub IDs for the pull request and issue respectively. Most of the code in connect-pr-to-issue is just going from the GitHub pull request and issue numbers to their corresponding ZenHub IDs.

To actually make the request, the we need it in JSON format which would look like:

{
    "operationName": "CreateIssuePrConnection",
    "query": "mutation CreateIssuePrConnection($input: CreateIssuePrConnectionInput!) {\ncreateIssuePrConnection(input: $input) {\nissue {\nid\n__typename\n}\n__typename\n}\n}",
    "variables": {
       "input": {
           "pullRequestId": "<pr-id>",
           "issueId": "<issue-id>",
       }
    }
}

Using it

In the end I ended up with a tool imaginatively called connect-pr-to-issue. You need to configure it a bit with the GitHub token, ZenHub workspace and two ZenHub tokens, one for the public API and one of the private APIs. Getting the private API token is a bit of a pain because you have to go through the network console and I’m not sure how long it will last for. Though, I’ve been using this tool for over a month now and haven’t had any issues. There’s also the looming issue of it being a private API so they may change it at any point and I’ll have to reverse engineer it again.

If you’re interested in the tool I’ve written up how to use it exactly in the README. Once you have it configured, it boils down to:

connect-pr-to-issue <pr-url>

and it’ll do the magic.

But wait there’s more!

I also use hub to create my PRs so why don’t we just combine that two!

That’s what git-zh-pr does. You provide the same CLI args as with hub pull-request (usually -p). Those get passed straight in to hub pull-request and it creates the PR as usual. The script then grabs the PR URL and passes it in to connect-pr-to-issue which does the magic. BAM! No more forgetting to connect PRs to issues.