Repository: DefinitelyTyped/dt-mergebot
Pull request #367 (2021-03-03 21:30:23)

Avoid redundant pings
Don't ping people who have approving reviews

Shouldn't happen in general, but due to another bug it does.  That
should be fixed too, but it's still good to run a filter to be safe.

(The bug will probably not get fixed if I manage to revise the workflow
according to #359.)


Patch:
diff --git a/src/comments.ts b/src/comments.ts
index 9c50bf4..3bd9cc2 100644
--- a/src/comments.ts
+++ b/src/comments.ts
@@ -97,11 +97,11 @@ export const StalenessExplanations: { [k: string]: string } = {
 };
 
 // Comments to post for the staleness timeline (the tag is computed in `makeStaleness`)
-export const StalenessComment = (author: string, otherOwners: string[], expires: string) => {
-    const owners = otherOwners.length === 0 ? "«anyone?»" : otherOwners.map(o => "@"+o).join(", ");
+export const StalenessComment = (author: string, ownersToPing: string[], expires: string) => {
+    const ownerPing = ownersToPing.map(o => "@"+o).join(", ");
     return {
         // --Unmerged--
-        "Unmerged:nearly": `Re-ping @${author} / ${owners}:
+        "Unmerged:nearly": `Re-ping @${author} / ${ownerPing}:
 
 This PR has been ready to merge for over a week, and I haven't seen any requests to merge it. I will close it on ${expires} (in three weeks) if this doesn't happen.
 
@@ -111,7 +111,7 @@ This PR has been ready to merge for over a week, and I haven't seen any requests
         "Abandoned:nearly": `@${author} I haven't seen any activity on this PR in more than three weeks, and it still has problems that prevent it from being merged. The PR will be closed on ${expires} (in a week) if the issues aren't addressed.`,
         "Abandoned:done": `@${author} To keep things tidy, we have to close PRs that aren't mergeable and don't have activity in the last month. No worries, though — please open a new PR if you'd like to continue with this change. Thank you!`,
         // --Unreviewed--
-        "Unreviewed:nearly": `Re-ping ${owners}:
+        "Unreviewed:nearly": `Re-ping ${ownerPing}:
 
 This PR has been out for over a week, yet I haven't seen any reviews.
 
@@ -120,5 +120,5 @@ Could someone please give it some attention? Thanks!`,
 
 I'll bump it to the DT maintainer queue. Thank you for your patience, @${author}.
 
-(Ping ${owners}.)`} as { [k: string]: string };
+(Ping ${ownerPing}.)`} as { [k: string]: string };
 };
diff --git a/src/compute-pr-actions.ts b/src/compute-pr-actions.ts
index 20e5cb7..935115d 100644
--- a/src/compute-pr-actions.ts
+++ b/src/compute-pr-actions.ts
@@ -172,7 +172,9 @@ function extendPrInfo(info: PrInfo): ExtendedPrInfo {
 
     // Staleness timeline configurations (except for texts that are all in `comments.ts`)
     function getStaleness() {
-        const mkStaleness = makeStaleness(info.now, info.author, otherOwners);
+        const ownersToPing = otherOwners.length === 0 ? ["«anyone?»"]
+            : otherOwners.filter(o => !approvedReviews.some(r => o === r.reviewer));
+        const mkStaleness = makeStaleness(info.now, info.author, ownersToPing);
         if (canBeSelfMerged && info.mergeOfferDate) return mkStaleness(
             "Unmerged", info.mergeOfferDate, 4, 9, 30, "CLOSE");
         if (needsAuthorAction) return mkStaleness(
@@ -351,7 +353,7 @@ export function process(prInfo: BotResult,
     return actions;
 }
 
-function makeStaleness(now: Date, author: string, otherOwners: string[]) { // curried for convenience
+function makeStaleness(now: Date, author: string, ownersToPing: string[]) { // curried for convenience
     return (kind: StalenessKind, since: Date,
             freshDays: number, attnDays: number, nearDays: number,
             doneColumn: ColumnName | "CLOSE") => {
@@ -360,7 +362,7 @@ function makeStaleness(now: Date, author: string, otherOwners: string[]) { // cu
         const kindAndState = `${kind}:${state}`;
         const explanation = Comments.StalenessExplanations[kindAndState];
         const expires = dayjs(since).add(nearDays, "days").format("MMM Do");
-        const comment = Comments.StalenessComment(author, otherOwners, expires)[kindAndState];
+        const comment = Comments.StalenessComment(author, ownersToPing, expires)[kindAndState];
         const doTimelineActions = (actions: Actions) => {
             if (comment !== undefined) {
                 const tag = state === "done" ? kindAndState


Test patch:
diff --git a/src/_tests/fixtures/43695-duplicate-comment/mutations.json b/src/_tests/fixtures/43695-duplicate-comment/mutations.json
index fd72ae0..e765b73 100644
--- a/src/_tests/fixtures/43695-duplicate-comment/mutations.json
+++ b/src/_tests/fixtures/43695-duplicate-comment/mutations.json
@@ -63,7 +63,7 @@
     "variables": {
       "input": {
         "subjectId": "MDExOlB1bGxSZXF1ZXN0NDAwMTAwMTk2",
-        "body": "It has been more than two weeks and this PR still has no reviews.\n\nI'll bump it to the DT maintainer queue. Thank you for your patience, @alexandercerutti.\n\n(Ping «anyone?».)\n<!--typescript_bot_Unreviewed:done-->"
+        "body": "It has been more than two weeks and this PR still has no reviews.\n\nI'll bump it to the DT maintainer queue. Thank you for your patience, @alexandercerutti.\n\n(Ping @«anyone?».)\n<!--typescript_bot_Unreviewed:done-->"
       }
     }
   }
diff --git a/src/_tests/fixtures/43695-duplicate-comment/result.json b/src/_tests/fixtures/43695-duplicate-comment/result.json
index 635381c..f9835ef 100644
--- a/src/_tests/fixtures/43695-duplicate-comment/result.json
+++ b/src/_tests/fixtures/43695-duplicate-comment/result.json
@@ -19,7 +19,7 @@
     },
     {
       "tag": "Unreviewed:done",
-      "status": "It has been more than two weeks and this PR still has no reviews.\n\nI'll bump it to the DT maintainer queue. Thank you for your patience, @alexandercerutti.\n\n(Ping «anyone?».)"
+      "status": "It has been more than two weeks and this PR still has no reviews.\n\nI'll bump it to the DT maintainer queue. Thank you for your patience, @alexandercerutti.\n\n(Ping @«anyone?».)"
     }
   ],
   "shouldClose": false,
diff --git a/src/_tests/fixtures/44989-14days/mutations.json b/src/_tests/fixtures/44989-14days/mutations.json
index 1f765ca..5596f7a 100644
--- a/src/_tests/fixtures/44989-14days/mutations.json
+++ b/src/_tests/fixtures/44989-14days/mutations.json
@@ -33,7 +33,7 @@
     "variables": {
       "input": {
         "subjectId": "MDExOlB1bGxSZXF1ZXN0NDIyMDc5Mjk3",
-        "body": "Re-ping @petr-motejlek / @TheHandsomeCoder, @donnut, @mdekrey, @sbking, @afharo, @teves-castro, @1M0reBug, @hojberg, @samsonkeung, @angeloocana, @raynerd, @moshensky, @ethanresnick, @deftomat, @blimusiek, @biern, @rayhaneh, @rgm, @drewwyatt, @jottenlips, @minitesh, @krantisinh, @pirix-gh, @brekk, @nemo108, @jituanlin, @Philippe-mills, @Saul-Mirone, @Nicholaiii:\n\nThis PR has been ready to merge for over a week, and I haven't seen any requests to merge it. I will close it on Jun 22nd (in three weeks) if this doesn't happen.\n\n(If there's no reason to avoid merging it, please do so.  Otherwise, if it shouldn't be merged or if it needs more time, please close it or turn it into a draft.)\n<!--typescript_bot_Unmerged:nearly:2020-05-23-->"
+        "body": "Re-ping @petr-motejlek / @TheHandsomeCoder, @donnut, @mdekrey, @sbking, @afharo, @teves-castro, @1M0reBug, @hojberg, @samsonkeung, @angeloocana, @raynerd, @moshensky, @ethanresnick, @deftomat, @blimusiek, @biern, @rayhaneh, @rgm, @drewwyatt, @jottenlips, @minitesh, @krantisinh, @pirix-gh, @brekk, @nemo108, @jituanlin, @Philippe-mills, @Saul-Mirone:\n\nThis PR has been ready to merge for over a week, and I haven't seen any requests to merge it. I will close it on Jun 22nd (in three weeks) if this doesn't happen.\n\n(If there's no reason to avoid merging it, please do so.  Otherwise, if it shouldn't be merged or if it needs more time, please close it or turn it into a draft.)\n<!--typescript_bot_Unmerged:nearly:2020-05-23-->"
       }
     }
   }
diff --git a/src/_tests/fixtures/44989-14days/result.json b/src/_tests/fixtures/44989-14days/result.json
index 6d2503a..b2a9348 100644
--- a/src/_tests/fixtures/44989-14days/result.json
+++ b/src/_tests/fixtures/44989-14days/result.json
@@ -17,7 +17,7 @@
     },
     {
       "tag": "Unmerged:nearly:2020-05-23",
-      "status": "Re-ping @petr-motejlek / @TheHandsomeCoder, @donnut, @mdekrey, @sbking, @afharo, @teves-castro, @1M0reBug, @hojberg, @samsonkeung, @angeloocana, @raynerd, @moshensky, @ethanresnick, @deftomat, @blimusiek, @biern, @rayhaneh, @rgm, @drewwyatt, @jottenlips, @minitesh, @krantisinh, @pirix-gh, @brekk, @nemo108, @jituanlin, @Philippe-mills, @Saul-Mirone, @Nicholaiii:\n\nThis PR has been ready to merge for over a week, and I haven't seen any requests to merge it. I will close it on Jun 22nd (in three weeks) if this doesn't happen.\n\n(If there's no reason to avoid merging it, please do so.  Otherwise, if it shouldn't be merged or if it needs more time, please close it or turn it into a draft.)"
+      "status": "Re-ping @petr-motejlek / @TheHandsomeCoder, @donnut, @mdekrey, @sbking, @afharo, @teves-castro, @1M0reBug, @hojberg, @samsonkeung, @angeloocana, @raynerd, @moshensky, @ethanresnick, @deftomat, @blimusiek, @biern, @rayhaneh, @rgm, @drewwyatt, @jottenlips, @minitesh, @krantisinh, @pirix-gh, @brekk, @nemo108, @jituanlin, @Philippe-mills, @Saul-Mirone:\n\nThis PR has been ready to merge for over a week, and I haven't seen any requests to merge it. I will close it on Jun 22nd (in three weeks) if this doesn't happen.\n\n(If there's no reason to avoid merging it, please do so.  Otherwise, if it shouldn't be merged or if it needs more time, please close it or turn it into a draft.)"
     }
   ],
   "shouldClose": false,
diff --git a/src/_tests/fixtures/49548/mutations.json b/src/_tests/fixtures/49548/mutations.json
index a2a0c42..20a3c47 100644
--- a/src/_tests/fixtures/49548/mutations.json
+++ b/src/_tests/fixtures/49548/mutations.json
@@ -24,7 +24,7 @@
     "variables": {
       "input": {
         "subjectId": "MDExOlB1bGxSZXF1ZXN0NTIwODA1MjIx",
-        "body": "Re-ping «anyone?»:\n\nThis PR has been out for over a week, yet I haven't seen any reviews.\n\nCould someone please give it some attention? Thanks!\n<!--typescript_bot_Unreviewed:nearly:2020-11-16-->"
+        "body": "Re-ping @«anyone?»:\n\nThis PR has been out for over a week, yet I haven't seen any reviews.\n\nCould someone please give it some attention? Thanks!\n<!--typescript_bot_Unreviewed:nearly:2020-11-16-->"
       }
     }
   }
diff --git a/src/_tests/fixtures/49548/result.json b/src/_tests/fixtures/49548/result.json
index fba6c09..c55bb17 100644
--- a/src/_tests/fixtures/49548/result.json
+++ b/src/_tests/fixtures/49548/result.json
@@ -16,7 +16,7 @@
     },
     {
       "tag": "Unreviewed:nearly:2020-11-16",
-      "status": "Re-ping «anyone?»:\n\nThis PR has been out for over a week, yet I haven't seen any reviews.\n\nCould someone please give it some attention? Thanks!"
+      "status": "Re-ping @«anyone?»:\n\nThis PR has been out for over a week, yet I haven't seen any reviews.\n\nCould someone please give it some attention? Thanks!"
     }
   ],
   "shouldClose": false,
diff --git a/src/_tests/fixtures/51338/_downloads.json b/src/_tests/fixtures/51338/_downloads.json
new file mode 100644
index 0000000..d742820
--- /dev/null
+++ b/src/_tests/fixtures/51338/_downloads.json
@@ -0,0 +1,3 @@
+{
+  "koa": 5708514
+}
diff --git a/src/_tests/fixtures/51338/_files.json b/src/_tests/fixtures/51338/_files.json
new file mode 100644
index 0000000..0bfa4bb
--- /dev/null
+++ b/src/_tests/fixtures/51338/_files.json
@@ -0,0 +1,6 @@
+{
+  "4a7712f61d17eb33e89171f317deeb31b3d555ae:types/koa/tsconfig.json": "{\n    \"files\": [\n        \"index.d.ts\",\n        \"test/index.ts\",\n        \"test/default.ts\",\n        \"test/response-body.ts\",\n        \"test/settings.ts\"\n    ],\n    \"compilerOptions\": {\n        \"module\": \"commonjs\",\n        \"lib\": [\n            \"es6\"\n        ],\n        \"noImplicitAny\": true,\n        \"noImplicitThis\": true,\n        \"strictNullChecks\": true,\n        \"strictFunctionTypes\": true,\n        \"baseUrl\": \"../\",\n        \"typeRoots\": [\n            \"../\"\n        ],\n        \"types\": [],\n        \"noEmit\": true,\n        \"forceConsistentCasingInFileNames\": true,\n        \"esModuleInterop\": true\n    }\n}\n",
+  "master:types/koa/tsconfig.json": "{\n    \"files\": [\n        \"index.d.ts\",\n        \"test/index.ts\",\n        \"test/default.ts\",\n        \"test/response-body.ts\",\n        \"test/settings.ts\"\n    ],\n    \"compilerOptions\": {\n        \"module\": \"commonjs\",\n        \"lib\": [\n            \"es6\"\n        ],\n        \"noImplicitAny\": true,\n        \"noImplicitThis\": true,\n        \"strictNullChecks\": true,\n        \"strictFunctionTypes\": true,\n        \"baseUrl\": \"../\",\n        \"typeRoots\": [\n            \"../\"\n        ],\n        \"types\": [],\n        \"noEmit\": true,\n        \"forceConsistentCasingInFileNames\": true\n    }\n}\n",
+  "master:types/koa/index.d.ts": "// Type definitions for Koa 2.13.0\n// Project: http://koajs.com\n// Definitions by: DavidCai1993 <https://github.com/DavidCai1993>\n//                 jKey Lu <https://github.com/jkeylu>\n//                 Brice Bernard <https://github.com/brikou>\n//                 harryparkdotio <https://github.com/harryparkdotio>\n//                 Wooram Jun <https://github.com/chatoo2412>\n//                 Christian Vaagland Tellnes <https://github.com/tellnes>\n//                 Piotr Kuczynski <https://github.com/pkuczynski>\n// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped\n// TypeScript Version: 3.0\n\n/* =================== USAGE ===================\n\n    import * as Koa from \"koa\"\n    const app = new Koa()\n\n    async function (ctx: Koa.Context, next: Koa.Next) {\n      // ...\n    }\n\n =============================================== */\n/// <reference types=\"node\" />\nimport * as accepts from 'accepts';\nimport * as Cookies from 'cookies';\nimport { EventEmitter } from 'events';\nimport { IncomingMessage, ServerResponse, Server, IncomingHttpHeaders, OutgoingHttpHeaders } from 'http';\nimport { Http2ServerRequest, Http2ServerResponse } from 'http2';\nimport * as httpAssert from 'http-assert';\nimport * as HttpErrors from 'http-errors';\nimport * as Keygrip from 'keygrip';\nimport * as compose from 'koa-compose';\nimport { Socket, ListenOptions } from 'net';\nimport * as url from 'url';\nimport * as contentDisposition from 'content-disposition';\nimport { ParsedUrlQuery } from 'querystring';\n\ndeclare interface ContextDelegatedRequest {\n    /**\n     * Return request header.\n     */\n    header: IncomingHttpHeaders;\n\n    /**\n     * Return request header, alias as request.header\n     */\n    headers: IncomingHttpHeaders;\n\n    /**\n     * Get/Set request URL.\n     */\n    url: string;\n\n    /**\n     * Get origin of URL.\n     */\n    origin: string;\n\n    /**\n     * Get full request URL.\n     */\n    href: string;\n\n    /**\n     * Get/Set request method.\n     */\n    method: string;\n\n    /**\n     * Get request pathname.\n     * Set pathname, retaining the query-string when present.\n     */\n    path: string;\n\n    /**\n     * Get parsed query-string.\n     * Set query-string as an object.\n     */\n    query: ParsedUrlQuery;\n\n    /**\n     * Get/Set query string.\n     */\n    querystring: string;\n\n    /**\n     * Get the search string. Same as the querystring\n     * except it includes the leading ?.\n     *\n     * Set the search string. Same as\n     * response.querystring= but included for ubiquity.\n     */\n    search: string;\n\n    /**\n     * Parse the \"Host\" header field host\n     * and support X-Forwarded-Host when a\n     * proxy is enabled.\n     */\n    host: string;\n\n    /**\n     * Parse the \"Host\" header field hostname\n     * and support X-Forwarded-Host when a\n     * proxy is enabled.\n     */\n    hostname: string;\n\n    /**\n     * Get WHATWG parsed URL object.\n     */\n    URL: url.URL;\n\n    /**\n     * Check if the request is fresh, aka\n     * Last-Modified and/or the ETag\n     * still match.\n     */\n    fresh: boolean;\n\n    /**\n     * Check if the request is stale, aka\n     * \"Last-Modified\" and / or the \"ETag\" for the\n     * resource has changed.\n     */\n    stale: boolean;\n\n    /**\n     * Check if the request is idempotent.\n     */\n    idempotent: boolean;\n\n    /**\n     * Return the request socket.\n     */\n    socket: Socket;\n\n    /**\n     * Return the protocol string \"http\" or \"https\"\n     * when requested with TLS. When the proxy setting\n     * is enabled the \"X-Forwarded-Proto\" header\n     * field will be trusted. If you're running behind\n     * a reverse proxy that supplies https for you this\n     * may be enabled.\n     */\n    protocol: string;\n\n    /**\n     * Short-hand for:\n     *\n     *    this.protocol == 'https'\n     */\n    secure: boolean;\n\n    /**\n     * Request remote address. Supports X-Forwarded-For when app.proxy is true.\n     */\n    ip: string;\n\n    /**\n     * When `app.proxy` is `true`, parse\n     * the \"X-Forwarded-For\" ip address list.\n     *\n     * For example if the value were \"client, proxy1, proxy2\"\n     * you would receive the array `[\"client\", \"proxy1\", \"proxy2\"]`\n     * where \"proxy2\" is the furthest down-stream.\n     */\n    ips: string[];\n\n    /**\n     * Return subdomains as an array.\n     *\n     * Subdomains are the dot-separated parts of the host before the main domain\n     * of the app. By default, the domain of the app is assumed to be the last two\n     * parts of the host. This can be changed by setting `app.subdomainOffset`.\n     *\n     * For example, if the domain is \"tobi.ferrets.example.com\":\n     * If `app.subdomainOffset` is not set, this.subdomains is\n     * `[\"ferrets\", \"tobi\"]`.\n     * If `app.subdomainOffset` is 3, this.subdomains is `[\"tobi\"]`.\n     */\n    subdomains: string[];\n\n    /**\n     * Check if the given `type(s)` is acceptable, returning\n     * the best match when true, otherwise `false`, in which\n     * case you should respond with 406 \"Not Acceptable\".\n     *\n     * The `type` value may be a single mime type string\n     * such as \"application/json\", the extension name\n     * such as \"json\" or an array `[\"json\", \"html\", \"text/plain\"]`. When a list\n     * or array is given the _best_ match, if any is returned.\n     *\n     * Examples:\n     *\n     *     // Accept: text/html\n     *     this.accepts('html');\n     *     // => \"html\"\n     *\n     *     // Accept: text/*, application/json\n     *     this.accepts('html');\n     *     // => \"html\"\n     *     this.accepts('text/html');\n     *     // => \"text/html\"\n     *     this.accepts('json', 'text');\n     *     // => \"json\"\n     *     this.accepts('application/json');\n     *     // => \"application/json\"\n     *\n     *     // Accept: text/*, application/json\n     *     this.accepts('image/png');\n     *     this.accepts('png');\n     *     // => undefined\n     *\n     *     // Accept: text/*;q=.5, application/json\n     *     this.accepts(['html', 'json']);\n     *     this.accepts('html', 'json');\n     *     // => \"json\"\n     */\n    accepts(): string[];\n    accepts(...types: string[]): string | false;\n    accepts(types: string[]): string | false;\n\n    /**\n     * Return accepted encodings or best fit based on `encodings`.\n     *\n     * Given `Accept-Encoding: gzip, deflate`\n     * an array sorted by quality is returned:\n     *\n     *     ['gzip', 'deflate']\n     */\n    acceptsEncodings(): string[];\n    acceptsEncodings(...encodings: string[]): string | false;\n    acceptsEncodings(encodings: string[]): string | false;\n\n    /**\n     * Return accepted charsets or best fit based on `charsets`.\n     *\n     * Given `Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5`\n     * an array sorted by quality is returned:\n     *\n     *     ['utf-8', 'utf-7', 'iso-8859-1']\n     */\n    acceptsCharsets(): string[];\n    acceptsCharsets(...charsets: string[]): string | false;\n    acceptsCharsets(charsets: string[]): string | false;\n\n    /**\n     * Return accepted languages or best fit based on `langs`.\n     *\n     * Given `Accept-Language: en;q=0.8, es, pt`\n     * an array sorted by quality is returned:\n     *\n     *     ['es', 'pt', 'en']\n     */\n    acceptsLanguages(): string[];\n    acceptsLanguages(...langs: string[]): string | false;\n    acceptsLanguages(langs: string[]): string | false;\n\n    /**\n     * Check if the incoming request contains the \"Content-Type\"\n     * header field, and it contains any of the give mime `type`s.\n     * If there is no request body, `null` is returned.\n     * If there is no content type, `false` is returned.\n     * Otherwise, it returns the first `type` that matches.\n     *\n     * Examples:\n     *\n     *     // With Content-Type: text/html; charset=utf-8\n     *     this.is('html'); // => 'html'\n     *     this.is('text/html'); // => 'text/html'\n     *     this.is('text/*', 'application/json'); // => 'text/html'\n     *\n     *     // When Content-Type is application/json\n     *     this.is('json', 'urlencoded'); // => 'json'\n     *     this.is('application/json'); // => 'application/json'\n     *     this.is('html', 'application/*'); // => 'application/json'\n     *\n     *     this.is('html'); // => false\n     */\n    // is(): string | boolean;\n    is(...types: string[]): string | false | null;\n    is(types: string[]): string | false | null;\n\n    /**\n     * Return request header. If the header is not set, will return an empty\n     * string.\n     *\n     * The `Referrer` header field is special-cased, both `Referrer` and\n     * `Referer` are interchangeable.\n     *\n     * Examples:\n     *\n     *     this.get('Content-Type');\n     *     // => \"text/plain\"\n     *\n     *     this.get('content-type');\n     *     // => \"text/plain\"\n     *\n     *     this.get('Something');\n     *     // => ''\n     */\n    get(field: string): string;\n}\n\ndeclare interface ContextDelegatedResponse {\n    /**\n     * Get/Set response status code.\n     */\n    status: number;\n\n    /**\n     * Get response status message\n     */\n    message: string;\n\n    /**\n     * Get/Set response body.\n     */\n    body: unknown;\n\n    /**\n     * Return parsed response Content-Length when present.\n     * Set Content-Length field to `n`.\n     */\n    length: number;\n\n    /**\n     * Check if a header has been written to the socket.\n     */\n    headerSent: boolean;\n\n    /**\n     * Vary on `field`.\n     */\n    vary(field: string): void;\n\n    /**\n     * Perform a 302 redirect to `url`.\n     *\n     * The string \"back\" is special-cased\n     * to provide Referrer support, when Referrer\n     * is not present `alt` or \"/\" is used.\n     *\n     * Examples:\n     *\n     *    this.redirect('back');\n     *    this.redirect('back', '/index.html');\n     *    this.redirect('/login');\n     *    this.redirect('http://google.com');\n     */\n    redirect(url: string, alt?: string): void;\n\n    /**\n     * Set Content-Disposition to \"attachment\" to signal the client to prompt for download.\n     * Optionally specify the filename of the download and some options.\n     */\n    attachment(filename?: string, options?: contentDisposition.Options): void;\n\n    /**\n     * Return the response mime type void of\n     * parameters such as \"charset\".\n     *\n     * Set Content-Type response header with `type` through `mime.lookup()`\n     * when it does not contain a charset.\n",
+  "4a7712f61d17eb33e89171f317deeb31b3d555ae:types/koa/index.d.ts": "// Type definitions for Koa 2.13.0\n// Project: http://koajs.com\n// Definitions by: DavidCai1993 <https://github.com/DavidCai1993>\n//                 jKey Lu <https://github.com/jkeylu>\n//                 Brice Bernard <https://github.com/brikou>\n//                 harryparkdotio <https://github.com/harryparkdotio>\n//                 Wooram Jun <https://github.com/chatoo2412>\n//                 Christian Vaagland Tellnes <https://github.com/tellnes>\n//                 Piotr Kuczynski <https://github.com/pkuczynski>\n// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped\n// TypeScript Version: 3.0\n\n/* =================== USAGE ===================\n\n    import * as Koa from \"koa\"\n    const app = new Koa()\n\n    async function (ctx: Koa.Context, next: Koa.Next) {\n      // ...\n    }\n\n =============================================== */\n/// <reference types=\"node\" />\nimport * as accepts from 'accepts';\nimport * as Cookies from 'cookies';\nimport { EventEmitter } from 'events';\nimport { IncomingMessage, ServerResponse, Server, IncomingHttpHeaders, OutgoingHttpHeaders } from 'http';\nimport { Http2ServerRequest, Http2ServerResponse } from 'http2';\nimport httpAssert = require('http-assert');\nimport * as HttpErrors from 'http-errors';\nimport * as Keygrip from 'keygrip';\nimport * as compose from 'koa-compose';\nimport { Socket, ListenOptions } from 'net';\nimport * as url from 'url';\nimport * as contentDisposition from 'content-disposition';\nimport { ParsedUrlQuery } from 'querystring';\n\ndeclare interface ContextDelegatedRequest {\n    /**\n     * Return request header.\n     */\n    header: IncomingHttpHeaders;\n\n    /**\n     * Return request header, alias as request.header\n     */\n    headers: IncomingHttpHeaders;\n\n    /**\n     * Get/Set request URL.\n     */\n    url: string;\n\n    /**\n     * Get origin of URL.\n     */\n    origin: string;\n\n    /**\n     * Get full request URL.\n     */\n    href: string;\n\n    /**\n     * Get/Set request method.\n     */\n    method: string;\n\n    /**\n     * Get request pathname.\n     * Set pathname, retaining the query-string when present.\n     */\n    path: string;\n\n    /**\n     * Get parsed query-string.\n     * Set query-string as an object.\n     */\n    query: ParsedUrlQuery;\n\n    /**\n     * Get/Set query string.\n     */\n    querystring: string;\n\n    /**\n     * Get the search string. Same as the querystring\n     * except it includes the leading ?.\n     *\n     * Set the search string. Same as\n     * response.querystring= but included for ubiquity.\n     */\n    search: string;\n\n    /**\n     * Parse the \"Host\" header field host\n     * and support X-Forwarded-Host when a\n     * proxy is enabled.\n     */\n    host: string;\n\n    /**\n     * Parse the \"Host\" header field hostname\n     * and support X-Forwarded-Host when a\n     * proxy is enabled.\n     */\n    hostname: string;\n\n    /**\n     * Get WHATWG parsed URL object.\n     */\n    URL: url.URL;\n\n    /**\n     * Check if the request is fresh, aka\n     * Last-Modified and/or the ETag\n     * still match.\n     */\n    fresh: boolean;\n\n    /**\n     * Check if the request is stale, aka\n     * \"Last-Modified\" and / or the \"ETag\" for the\n     * resource has changed.\n     */\n    stale: boolean;\n\n    /**\n     * Check if the request is idempotent.\n     */\n    idempotent: boolean;\n\n    /**\n     * Return the request socket.\n     */\n    socket: Socket;\n\n    /**\n     * Return the protocol string \"http\" or \"https\"\n     * when requested with TLS. When the proxy setting\n     * is enabled the \"X-Forwarded-Proto\" header\n     * field will be trusted. If you're running behind\n     * a reverse proxy that supplies https for you this\n     * may be enabled.\n     */\n    protocol: string;\n\n    /**\n     * Short-hand for:\n     *\n     *    this.protocol == 'https'\n     */\n    secure: boolean;\n\n    /**\n     * Request remote address. Supports X-Forwarded-For when app.proxy is true.\n     */\n    ip: string;\n\n    /**\n     * When `app.proxy` is `true`, parse\n     * the \"X-Forwarded-For\" ip address list.\n     *\n     * For example if the value were \"client, proxy1, proxy2\"\n     * you would receive the array `[\"client\", \"proxy1\", \"proxy2\"]`\n     * where \"proxy2\" is the furthest down-stream.\n     */\n    ips: string[];\n\n    /**\n     * Return subdomains as an array.\n     *\n     * Subdomains are the dot-separated parts of the host before the main domain\n     * of the app. By default, the domain of the app is assumed to be the last two\n     * parts of the host. This can be changed by setting `app.subdomainOffset`.\n     *\n     * For example, if the domain is \"tobi.ferrets.example.com\":\n     * If `app.subdomainOffset` is not set, this.subdomains is\n     * `[\"ferrets\", \"tobi\"]`.\n     * If `app.subdomainOffset` is 3, this.subdomains is `[\"tobi\"]`.\n     */\n    subdomains: string[];\n\n    /**\n     * Check if the given `type(s)` is acceptable, returning\n     * the best match when true, otherwise `false`, in which\n     * case you should respond with 406 \"Not Acceptable\".\n     *\n     * The `type` value may be a single mime type string\n     * such as \"application/json\", the extension name\n     * such as \"json\" or an array `[\"json\", \"html\", \"text/plain\"]`. When a list\n     * or array is given the _best_ match, if any is returned.\n     *\n     * Examples:\n     *\n     *     // Accept: text/html\n     *     this.accepts('html');\n     *     // => \"html\"\n     *\n     *     // Accept: text/*, application/json\n     *     this.accepts('html');\n     *     // => \"html\"\n     *     this.accepts('text/html');\n     *     // => \"text/html\"\n     *     this.accepts('json', 'text');\n     *     // => \"json\"\n     *     this.accepts('application/json');\n     *     // => \"application/json\"\n     *\n     *     // Accept: text/*, application/json\n     *     this.accepts('image/png');\n     *     this.accepts('png');\n     *     // => undefined\n     *\n     *     // Accept: text/*;q=.5, application/json\n     *     this.accepts(['html', 'json']);\n     *     this.accepts('html', 'json');\n     *     // => \"json\"\n     */\n    accepts(): string[];\n    accepts(...types: string[]): string | false;\n    accepts(types: string[]): string | false;\n\n    /**\n     * Return accepted encodings or best fit based on `encodings`.\n     *\n     * Given `Accept-Encoding: gzip, deflate`\n     * an array sorted by quality is returned:\n     *\n     *     ['gzip', 'deflate']\n     */\n    acceptsEncodings(): string[];\n    acceptsEncodings(...encodings: string[]): string | false;\n    acceptsEncodings(encodings: string[]): string | false;\n\n    /**\n     * Return accepted charsets or best fit based on `charsets`.\n     *\n     * Given `Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5`\n     * an array sorted by quality is returned:\n     *\n     *     ['utf-8', 'utf-7', 'iso-8859-1']\n     */\n    acceptsCharsets(): string[];\n    acceptsCharsets(...charsets: string[]): string | false;\n    acceptsCharsets(charsets: string[]): string | false;\n\n    /**\n     * Return accepted languages or best fit based on `langs`.\n     *\n     * Given `Accept-Language: en;q=0.8, es, pt`\n     * an array sorted by quality is returned:\n     *\n     *     ['es', 'pt', 'en']\n     */\n    acceptsLanguages(): string[];\n    acceptsLanguages(...langs: string[]): string | false;\n    acceptsLanguages(langs: string[]): string | false;\n\n    /**\n     * Check if the incoming request contains the \"Content-Type\"\n     * header field, and it contains any of the give mime `type`s.\n     * If there is no request body, `null` is returned.\n     * If there is no content type, `false` is returned.\n     * Otherwise, it returns the first `type` that matches.\n     *\n     * Examples:\n     *\n     *     // With Content-Type: text/html; charset=utf-8\n     *     this.is('html'); // => 'html'\n     *     this.is('text/html'); // => 'text/html'\n     *     this.is('text/*', 'application/json'); // => 'text/html'\n     *\n     *     // When Content-Type is application/json\n     *     this.is('json', 'urlencoded'); // => 'json'\n     *     this.is('application/json'); // => 'application/json'\n     *     this.is('html', 'application/*'); // => 'application/json'\n     *\n     *     this.is('html'); // => false\n     */\n    // is(): string | boolean;\n    is(...types: string[]): string | false | null;\n    is(types: string[]): string | false | null;\n\n    /**\n     * Return request header. If the header is not set, will return an empty\n     * string.\n     *\n     * The `Referrer` header field is special-cased, both `Referrer` and\n     * `Referer` are interchangeable.\n     *\n     * Examples:\n     *\n     *     this.get('Content-Type');\n     *     // => \"text/plain\"\n     *\n     *     this.get('content-type');\n     *     // => \"text/plain\"\n     *\n     *     this.get('Something');\n     *     // => ''\n     */\n    get(field: string): string;\n}\n\ndeclare interface ContextDelegatedResponse {\n    /**\n     * Get/Set response status code.\n     */\n    status: number;\n\n    /**\n     * Get response status message\n     */\n    message: string;\n\n    /**\n     * Get/Set response body.\n     */\n    body: unknown;\n\n    /**\n     * Return parsed response Content-Length when present.\n     * Set Content-Length field to `n`.\n     */\n    length: number;\n\n    /**\n     * Check if a header has been written to the socket.\n     */\n    headerSent: boolean;\n\n    /**\n     * Vary on `field`.\n     */\n    vary(field: string): void;\n\n    /**\n     * Perform a 302 redirect to `url`.\n     *\n     * The string \"back\" is special-cased\n     * to provide Referrer support, when Referrer\n     * is not present `alt` or \"/\" is used.\n     *\n     * Examples:\n     *\n     *    this.redirect('back');\n     *    this.redirect('back', '/index.html');\n     *    this.redirect('/login');\n     *    this.redirect('http://google.com');\n     */\n    redirect(url: string, alt?: string): void;\n\n    /**\n     * Set Content-Disposition to \"attachment\" to signal the client to prompt for download.\n     * Optionally specify the filename of the download and some options.\n     */\n    attachment(filename?: string, options?: contentDisposition.Options): void;\n\n    /**\n     * Return the response mime type void of\n     * parameters such as \"charset\".\n     *\n     * Set Content-Type response header with `type` through `mime.lookup()`\n     * when it does not contain a charset."
+}
diff --git a/src/_tests/fixtures/51338/_response.json b/src/_tests/fixtures/51338/_response.json
new file mode 100644
index 0000000..b748f14
--- /dev/null
+++ b/src/_tests/fixtures/51338/_response.json
@@ -0,0 +1,306 @@
+{
+  "data": {
+    "repository": {
+      "id": "MDEwOlJlcG9zaXRvcnk2MDkzMzE2",
+      "pullRequest": {
+        "id": "MDExOlB1bGxSZXF1ZXN0NTc2NTk4OTQ2",
+        "title": "[koa] Fix http-assert import issue",
+        "createdAt": "2021-02-19T17:14:27Z",
+        "author": {
+          "login": "pkuczynski",
+          "__typename": "User"
+        },
+        "authorAssociation": "CONTRIBUTOR",
+        "baseRef": {
+          "name": "master",
+          "__typename": "Ref"
+        },
+        "labels": {
+          "nodes": [
+            {
+              "name": "Author is Owner",
+              "__typename": "Label"
+            },
+            {
+              "name": "Check Config",
+              "__typename": "Label"
+            },
+            {
+              "name": "Critical package",
+              "__typename": "Label"
+            },
+            {
+              "name": "Owner Approved",
+              "__typename": "Label"
+            },
+            {
+              "name": "Perf: Same",
+              "__typename": "Label"
+            },
+            {
+              "name": "Untested Change",
+              "__typename": "Label"
+            }
+          ],
+          "__typename": "LabelConnection"
+        },
+        "isDraft": false,
+        "mergeable": "MERGEABLE",
+        "number": 51338,
+        "state": "OPEN",
+        "headRefOid": "4a7712f61d17eb33e89171f317deeb31b3d555ae",
+        "timelineItems": {
+          "nodes": [
+            {
+              "actor": {
+                "login": "typescript-bot",
+                "__typename": "User"
+              },
+              "createdAt": "2021-02-19T18:52:03Z",
+              "__typename": "MovedColumnsInProjectEvent"
+            }
+          ],
+          "__typename": "PullRequestTimelineItemsConnection"
+        },
+        "reviews": {
+          "nodes": [
+            {
+              "author": {
+                "login": "tellnes",
+                "__typename": "User"
+              },
+              "commit": {
+                "oid": "4a7712f61d17eb33e89171f317deeb31b3d555ae",
+                "__typename": "Commit"
+              },
+              "comments": {
+                "nodes": [],
+                "__typename": "PullRequestReviewCommentConnection"
+              },
+              "authorAssociation": "CONTRIBUTOR",
+              "state": "APPROVED",
+              "submittedAt": "2021-02-20T14:48:26Z",
+              "url": "https://github.com/DefinitelyTyped/DefinitelyTyped/pull/51338#pullrequestreview-594758328",
+              "__typename": "PullRequestReview"
+            }
+          ],
+          "__typename": "PullRequestReviewConnection"
+        },
+        "commits": {
+          "totalCount": 2,
+          "nodes": [
+            {
+              "commit": {
+                "checkSuites": {
+                  "nodes": [
+                    {
+                      "app": {
+                        "name": "GitHub Actions",
+                        "__typename": "App"
+                      },
+                      "conclusion": "SUCCESS",
+                      "resourcePath": "/DefinitelyTyped/DefinitelyTyped/commit/4a7712f61d17eb33e89171f317deeb31b3d555ae/checks?check_suite_id=2079549099",
+                      "status": "COMPLETED",
+                      "url": "https://github.com/DefinitelyTyped/DefinitelyTyped/commit/4a7712f61d17eb33e89171f317deeb31b3d555ae/checks?check_suite_id=2079549099",
+                      "checkRuns": {
+                        "nodes": [
+                          {
+                            "title": null,
+                            "__typename": "CheckRun"
+                          }
+                        ],
+                        "__typename": "CheckRunConnection"
+                      },
+                      "__typename": "CheckSuite"
+                    },
+                    {
+                      "app": {
+                        "name": "Azure Pipelines",
+                        "__typename": "App"
+                      },
+                      "conclusion": "SUCCESS",
+                      "resourcePath": "/DefinitelyTyped/DefinitelyTyped/commit/4a7712f61d17eb33e89171f317deeb31b3d555ae/checks?check_suite_id=2079551193",
+                      "status": "COMPLETED",
+                      "url": "https://github.com/DefinitelyTyped/DefinitelyTyped/commit/4a7712f61d17eb33e89171f317deeb31b3d555ae/checks?check_suite_id=2079551193",
+                      "checkRuns": {
+                        "nodes": [
+                          {
+                            "title": "Build #87669 succeeded",
+                            "__typename": "CheckRun"
+                          }
+                        ],
+                        "__typename": "CheckRunConnection"
+                      },
+                      "__typename": "CheckSuite"
+                    },
+                    {
+                      "app": {
+                        "name": "GitHub Actions",
+                        "__typename": "App"
+                      },
+                      "conclusion": "SUCCESS",
+                      "resourcePath": "/DefinitelyTyped/DefinitelyTyped/commit/4a7712f61d17eb33e89171f317deeb31b3d555ae/checks?check_suite_id=2084628457",
+                      "status": "COMPLETED",
+                      "url": "https://github.com/DefinitelyTyped/DefinitelyTyped/commit/4a7712f61d17eb33e89171f317deeb31b3d555ae/checks?check_suite_id=2084628457",
+                      "checkRuns": {
+                        "nodes": [
+                          {
+                            "title": "build",
+                            "__typename": "CheckRun"
+                          }
+                        ],
+                        "__typename": "CheckRunConnection"
+                      },
+                      "__typename": "CheckSuite"
+                    }
+                  ],
+                  "__typename": "CheckSuiteConnection"
+                },
+                "status": null,
+                "authoredDate": "2021-02-19T17:22:04Z",
+                "committedDate": "2021-02-19T17:22:04Z",
+                "pushedDate": "2021-02-19T17:22:27Z",
+                "oid": "4a7712f61d17eb33e89171f317deeb31b3d555ae",
+                "__typename": "Commit"
+              },
+              "__typename": "PullRequestCommit"
+            }
+          ],
+          "__typename": "PullRequestCommitConnection"
+        },
+        "comments": {
+          "totalCount": 7,
+          "nodes": [
+            {
+              "id": "MDEyOklzc3VlQ29tbWVudDc4MjIxMzI3OQ==",
+              "author": {
+                "login": "typescript-bot",
+                "__typename": "User"
+              },
+              "body": "Hey @pkuczynski,\n\n:unamused: Your PR doesn't modify any tests, so it's hard to know what's being fixed, and your changes might regress in the future. Please consider [adding tests](https://github.com/DefinitelyTyped/DefinitelyTyped#user-content-test-editing-an-existing-package) to cover the change you're making. Including tests allows this PR to be merged by yourself and the owners of this module. This can potentially save days of time for you.\n<!--typescript_bot_suggest-testing-->",
+              "createdAt": "2021-02-19T17:15:04Z",
+              "reactions": {
+                "nodes": [],
+                "__typename": "ReactionConnection"
+              },
+              "__typename": "IssueComment"
+            },
+            {
+              "id": "MDEyOklzc3VlQ29tbWVudDc4MjIxMzI4Nw==",
+              "author": {
+                "login": "typescript-bot",
+                "__typename": "User"
+              },
+              "body": "@pkuczynski Thank you for submitting this PR!\n\n***This is a live comment which I will keep updated.***\n\n## 1 package in this PR\n\n* `koa` [on npm](https://www.npmjs.com/package/koa), [on unpkg](https://unpkg.com/browse/koa@latest/) (author is owner)\n  - owner-approval: @tellnes\n  - Config files to check:\n    - [`koa/tsconfig.json`](https://github.com/DefinitelyTyped/DefinitelyTyped/pull/51338/files/4a7712f61d17eb33e89171f317deeb31b3d555ae#diff-1730c5405d0220a98f28feeec2de3f5a511ebc6a04ceadf12415b81de841672e): not [the expected form](https://github.com/DefinitelyTyped/DefinitelyTyped#user-content-tsconfigjson) and not moving towards it\n\n## Code Reviews\n\nBecause this is a widely-used package, a DT maintainer will need to review it before it can be merged.\n\n## Status\n\n * ✅ No merge conflicts\n * ✅ Continuous integration tests have passed\n * ❌ A DT maintainer needs to approve changes which affect module config files\n\nOnce every item on this list is checked, I'll ask you for permission to merge and publish the changes.\n\n## Inactive\n\nThis PR has been inactive for 11 days — please try to get reviewers!\n\n----------------------\n... diagnostics scrubbed ...\n<!--typescript_bot_welcome-->",
+              "createdAt": "2021-02-19T17:15:05Z",
+              "reactions": {
+                "nodes": [],
+                "__typename": "ReactionConnection"
+              },
+              "__typename": "IssueComment"
+            },
+            {
+              "id": "MDEyOklzc3VlQ29tbWVudDc4MjIxMzMwMA==",
+              "author": {
+                "login": "typescript-bot",
+                "__typename": "User"
+              },
+              "body": "🔔 @DavidCai1993 @jkeylu @brikou @harryparkdotio @chatoo2412 @tellnes — please [review this PR](https://github.com/DefinitelyTyped/DefinitelyTyped/pull/51338/files) in the next few days. Be sure to explicitly select **`Approve`** or **`Request Changes`** in the GitHub UI so I know what's going on.\n<!--typescript_bot_pinging-reviewers-->",
+              "createdAt": "2021-02-19T17:15:06Z",
+              "reactions": {
+                "nodes": [],
+                "__typename": "ReactionConnection"
+              },
+              "__typename": "IssueComment"
+            },
+            {
+              "id": "MDEyOklzc3VlQ29tbWVudDc4MjIxNzk2MA==",
+              "author": {
+                "login": "pkuczynski",
+                "__typename": "User"
+              },
+              "body": "I added `\"esModuleInterop\": true` to `tsconfig` as this is the case when `import * as httpAssert from 'http-assert';` fails.",
+              "createdAt": "2021-02-19T17:23:10Z",
+              "reactions": {
+                "nodes": [],
+                "__typename": "ReactionConnection"
+              },
+              "__typename": "IssueComment"
+            },
+            {
+              "id": "MDEyOklzc3VlQ29tbWVudDc4MjIxODU2Mg==",
+              "author": {
+                "login": "pkuczynski",
+                "__typename": "User"
+              },
+              "body": "@tellnes can you please have a look at this as an urgent matter, as our last merge #50249 broke the types ",
+              "createdAt": "2021-02-19T17:24:17Z",
+              "reactions": {
+                "nodes": [],
+                "__typename": "ReactionConnection"
+              },
+              "__typename": "IssueComment"
+            },
+            {
+              "id": "MDEyOklzc3VlQ29tbWVudDc4MjIzMDI5OQ==",
+              "author": {
+                "login": "typescript-bot",
+                "__typename": "User"
+              },
+              "body": "<!-- @dt-perf {\"version\":2,\"data\":{\"overallChange\":0,\"benchmarks\":[{\"createdAt\":\"2021-02-19T17:44:31.050Z\"}]}} -->\n\n👋 **Hi there!** I’ve run some quick measurements against master and your PR. These metrics should help the humans reviewing this PR gauge whether it might negatively affect compile times or editor responsiveness for users who install these typings.\n\n\nLet’s review the numbers, shall we?\n\n\n\n<details>\n<summary><strong>Comparison details</strong> 📊</summary>\n\n|                                                                                           | master                                                                                                               | #51338                                                                                                                     | diff  |\n| ----------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ----- |\n| **Batch compilation**                                                                     |                                                                                                                      |                                                                                                                            |       |\n| Memory usage (MiB)                                                                        | 80.2                                                                                                                 | 81.9                                                                                                                       | +2.1% |\n| Type count                                                                                | 12179                                                                                                                | 12239                                                                                                                      | 0%    |\n| Assignability cache size                                                                  | 2743                                                                                                                 | 2743                                                                                                                       | 0%    |\n|                                                                                           |                                                                                                                      |                                                                                                                            |       |\n| **Language service**                                                                      |                                                                                                                      |                                                                                                                            |       |\n| Samples taken                                                                             | 270                                                                                                                  | 270                                                                                                                        | 0%    |\n| Identifiers in tests                                                                      | 270                                                                                                                  | 270                                                                                                                        | 0%    |\n| **`getCompletionsAtPosition`**                                                            |                                                                                                                      |                                                                                                                            |       |\n| &nbsp;&nbsp;&nbsp;&nbsp;Mean duration (ms)                                                | 367.8                                                                                                                | 370.4                                                                                                                      | +0.7% |\n| &nbsp;&nbsp;&nbsp;&nbsp;Mean [CV](https://en.wikipedia.org/wiki/Coefficient_of_variation) | 9.6%                                                                                                                 | 10.2%                                                                                                                      |       |\n| &nbsp;&nbsp;&nbsp;&nbsp;Worst duration (ms)                                               | 474.6                                                                                                                | 478.1                                                                                                                      | +0.7% |\n| &nbsp;&nbsp;&nbsp;&nbsp;Worst identifier                                                  | [assert](/DefinitelyTyped/DefinitelyTyped/blob/21c58cfbf416717f8b69973bddce00dbe82bddfd/types/koa/test/index.ts#L46) | [attachment](/DefinitelyTyped/DefinitelyTyped/blob/bbb8d7db2b15488a6824ac7047fba7f08229bffd/types/koa/test/default.ts#L56) |       |\n| **`getQuickInfoAtPosition`**                                                              |                                                                                                                      |                                                                                                                            |       |\n| &nbsp;&nbsp;&nbsp;&nbsp;Mean duration (ms)                                                | 371.7                                                                                                                | 376.7                                                                                                                      | +1.3% |\n| &nbsp;&nbsp;&nbsp;&nbsp;Mean [CV](https://en.wikipedia.org/wiki/Coefficient_of_variation) | 9.5%                                                                                                                 | 9.4%                                                                                                                       |       |\n| &nbsp;&nbsp;&nbsp;&nbsp;Worst duration (ms)                                               | 468.4                                                                                                                | 484.4                                                                                                                      | +3.4% |\n| &nbsp;&nbsp;&nbsp;&nbsp;Worst identifier                                                  | [use](/DefinitelyTyped/DefinitelyTyped/blob/21c58cfbf416717f8b69973bddce00dbe82bddfd/types/koa/test/default.ts#L36)  | [assert](/DefinitelyTyped/DefinitelyTyped/blob/bbb8d7db2b15488a6824ac7047fba7f08229bffd/types/koa/test/index.ts#L46)       |       |\n\n\n</details>\n\nIt looks like nothing changed too much. I won’t post performance data again unless it gets worse.",
+              "createdAt": "2021-02-19T17:44:31Z",
+              "reactions": {
+                "nodes": [],
+                "__typename": "ReactionConnection"
+              },
+              "__typename": "IssueComment"
+            }
+          ],
+          "__typename": "IssueCommentConnection"
+        },
+        "files": {
+          "totalCount": 2,
+          "nodes": [
+            {
+              "path": "types/koa/index.d.ts",
+              "additions": 2,
+              "deletions": 2,
+              "__typename": "PullRequestChangedFile"
+            },
+            {
+              "path": "types/koa/tsconfig.json",
+              "additions": 2,
+              "deletions": 1,
+              "__typename": "PullRequestChangedFile"
+            }
+          ],
+          "__typename": "PullRequestChangedFileConnection"
+        },
+        "projectCards": {
+          "nodes": [
+            {
+              "id": "MDExOlByb2plY3RDYXJkNTUyOTAyODI=",
+              "project": {
+                "id": "MDc6UHJvamVjdDM3NDExMDQ=",
+                "number": 5,
+                "name": "New Pull Request Status Board",
+                "__typename": "Project"
+              },
+              "column": {
+                "id": "MDEzOlByb2plY3RDb2x1bW43NTUyOTIy",
+                "name": "Needs Maintainer Review",
+                "__typename": "ProjectColumn"
+              },
+              "__typename": "ProjectCard"
+            }
+          ],
+          "__typename": "ProjectCardConnection"
+        },
+        "__typename": "PullRequest"
+      },
+      "__typename": "Repository"
+    }
+  },
+  "loading": false,
+  "networkStatus": 7
+}
diff --git a/src/_tests/fixtures/51338/derived.json b/src/_tests/fixtures/51338/derived.json
new file mode 100644
index 0000000..9fff5f7
--- /dev/null
+++ b/src/_tests/fixtures/51338/derived.json
@@ -0,0 +1,51 @@
+{
+  "type": "info",
+  "now": "2021-03-02T21:43:39.396Z",
+  "pr_number": 51338,
+  "author": "pkuczynski",
+  "headCommitOid": "4a7712f61d17eb33e89171f317deeb31b3d555ae",
+  "lastPushDate": "2021-02-19T17:22:27.000Z",
+  "lastActivityDate": "2021-02-20T14:48:26.000Z",
+  "maintainerBlessed": false,
+  "hasMergeConflict": false,
+  "isFirstContribution": false,
+  "popularityLevel": "Critical",
+  "pkgInfo": [
+    {
+      "name": "koa",
+      "kind": "edit",
+      "files": [
+        {
+          "path": "types/koa/index.d.ts",
+          "kind": "definition"
+        },
+        {
+          "path": "types/koa/tsconfig.json",
+          "kind": "package-meta",
+          "suspect": "not [the expected form](https://github.com/DefinitelyTyped/DefinitelyTyped#user-content-tsconfigjson) and not moving towards it"
+        }
+      ],
+      "owners": [
+        "DavidCai1993",
+        "jkeylu",
+        "brikou",
+        "harryparkdotio",
+        "chatoo2412",
+        "tellnes",
+        "pkuczynski"
+      ],
+      "addedOwners": [],
+      "deletedOwners": [],
+      "popularityLevel": "Critical"
+    }
+  ],
+  "reviews": [
+    {
+      "type": "approved",
+      "reviewer": "tellnes",
+      "date": "2021-02-20T14:48:26.000Z",
+      "isMaintainer": false
+    }
+  ],
+  "ciResult": "pass"
+}
diff --git a/src/_tests/fixtures/51338/mutations.json b/src/_tests/fixtures/51338/mutations.json
new file mode 100644
index 0000000..27f6fd9
--- /dev/null
+++ b/src/_tests/fixtures/51338/mutations.json
@@ -0,0 +1,31 @@
+[
+  {
+    "mutation": "mutation ($input: AddLabelsToLabelableInput!) {\n  addLabelsToLabelable(input: $input) {\n    __typename\n  }\n}\n",
+    "variables": {
+      "input": {
+        "labelIds": [
+          "MDU6TGFiZWwyNDYyODA0MzE1"
+        ],
+        "labelableId": "MDExOlB1bGxSZXF1ZXN0NTc2NTk4OTQ2"
+      }
+    }
+  },
+  {
+    "mutation": "mutation ($input: UpdateIssueCommentInput!) {\n  updateIssueComment(input: $input) {\n    __typename\n  }\n}\n",
+    "variables": {
+      "input": {
+        "id": "MDEyOklzc3VlQ29tbWVudDc4MjIxMzI4Nw==",
+        "body": "@pkuczynski Thank you for submitting this PR!\n\n***This is a live comment which I will keep updated.***\n\n## 1 package in this PR\n\n* `koa` [on npm](https://www.npmjs.com/package/koa), [on unpkg](https://unpkg.com/browse/koa@latest/) (author is owner)\n  - owner-approval: @tellnes\n  - Config files to check:\n    - [`koa/tsconfig.json`](https://github.com/DefinitelyTyped/DefinitelyTyped/pull/51338/files/4a7712f61d17eb33e89171f317deeb31b3d555ae#diff-1730c5405d0220a98f28feeec2de3f5a511ebc6a04ceadf12415b81de841672e): not [the expected form](https://github.com/DefinitelyTyped/DefinitelyTyped#user-content-tsconfigjson) and not moving towards it\n\n## Code Reviews\n\nBecause this is a widely-used package, a DT maintainer will need to review it before it can be merged.\n\n## Status\n\n * ✅ No merge conflicts\n * ✅ Continuous integration tests have passed\n * ❌ A DT maintainer needs to approve changes which affect module config files\n\nOnce every item on this list is checked, I'll ask you for permission to merge and publish the changes.\n\n## Inactive\n\nThis PR has been inactive for 11 days — please try to get reviewers!\n\n----------------------\n... diagnostics scrubbed ...\n<!--typescript_bot_welcome-->"
+      }
+    }
+  },
+  {
+    "mutation": "mutation ($input: AddCommentInput!) {\n  addComment(input: $input) {\n    __typename\n  }\n}\n",
+    "variables": {
+      "input": {
+        "subjectId": "MDExOlB1bGxSZXF1ZXN0NTc2NTk4OTQ2",
+        "body": "Re-ping @DavidCai1993, @jkeylu, @brikou, @harryparkdotio, @chatoo2412:\n\nThis PR has been out for over a week, yet I haven't seen any reviews.\n\nCould someone please give it some attention? Thanks!\n<!--typescript_bot_Unreviewed:nearly:2021-02-19-->"
+      }
+    }
+  }
+]
diff --git a/src/_tests/fixtures/51338/result.json b/src/_tests/fixtures/51338/result.json
new file mode 100644
index 0000000..660ead4
--- /dev/null
+++ b/src/_tests/fixtures/51338/result.json
@@ -0,0 +1,28 @@
+{
+  "projectColumn": "Needs Maintainer Review",
+  "labels": [
+    "Critical package",
+    "Owner Approved",
+    "Author is Owner",
+    "Check Config",
+    "Untested Change",
+    "Unreviewed"
+  ],
+  "responseComments": [
+    {
+      "tag": "suggest-testing",
+      "status": "Hey @pkuczynski,\n\n:unamused: Your PR doesn't modify any tests, so it's hard to know what's being fixed, and your changes might regress in the future. Please consider [adding tests](https://github.com/DefinitelyTyped/DefinitelyTyped#user-content-test-editing-an-existing-package) to cover the change you're making. Including tests allows this PR to be merged by yourself and the owners of this module. This can potentially save days of time for you."
+    },
+    {
+      "tag": "welcome",
+      "status": "@pkuczynski Thank you for submitting this PR!\n\n***This is a live comment which I will keep updated.***\n\n## 1 package in this PR\n\n* `koa` [on npm](https://www.npmjs.com/package/koa), [on unpkg](https://unpkg.com/browse/koa@latest/) (author is owner)\n  - owner-approval: @tellnes\n  - Config files to check:\n    - [`koa/tsconfig.json`](https://github.com/DefinitelyTyped/DefinitelyTyped/pull/51338/files/4a7712f61d17eb33e89171f317deeb31b3d555ae#diff-1730c5405d0220a98f28feeec2de3f5a511ebc6a04ceadf12415b81de841672e): not [the expected form](https://github.com/DefinitelyTyped/DefinitelyTyped#user-content-tsconfigjson) and not moving towards it\n\n## Code Reviews\n\nBecause this is a widely-used package, a DT maintainer will need to review it before it can be merged.\n\n## Status\n\n * ✅ No merge conflicts\n * ✅ Continuous integration tests have passed\n * ❌ A DT maintainer needs to approve changes which affect module config files\n\nOnce every item on this list is checked, I'll ask you for permission to merge and publish the changes.\n\n## Inactive\n\nThis PR has been inactive for 11 days — please try to get reviewers!\n\n----------------------\n... diagnostics scrubbed ..."
+    },
+    {
+      "tag": "Unreviewed:nearly:2021-02-19",
+      "status": "Re-ping @DavidCai1993, @jkeylu, @brikou, @harryparkdotio, @chatoo2412:\n\nThis PR has been out for over a week, yet I haven't seen any reviews.\n\nCould someone please give it some attention? Thanks!"
+    }
+  ],
+  "shouldClose": false,
+  "shouldMerge": false,
+  "shouldUpdateLabels": true
+}