From cb9437f72d9baae1296eac01cc13723bb6edfb97 Mon Sep 17 00:00:00 2001
From: praveshtora <pravesh.tora@gmail.com>
Date: Thu, 15 Aug 2019 11:11:01 +0530
Subject: [PATCH] feat: configWarningReuseIssue (#4101)

---
 lib/config/definitions.js              |  8 +++
 lib/platform/github/index.ts           | 35 ++++++++-----
 lib/workers/repository/error-config.js |  6 ++-
 renovate-schema.json                   |  5 ++
 test/platform/github/index.spec.ts     | 70 ++++++++++++++++++++++++++
 website/docs/configuration-options.md  |  4 ++
 6 files changed, 114 insertions(+), 14 deletions(-)

diff --git a/lib/config/definitions.js b/lib/config/definitions.js
index ba5c51df3e..86cae83959 100644
--- a/lib/config/definitions.js
+++ b/lib/config/definitions.js
@@ -209,6 +209,14 @@ const options = [
     type: 'string',
     default: `Update Dependencies (${appName} Bot)`,
   },
+  {
+    name: 'configWarningReuseIssue',
+    description:
+      'Set this to false and Renovate will open each config warning in a new issue instead of reopening/reusing an existing issue.',
+    type: 'boolean',
+    default: true,
+  },
+
   // encryption
   {
     name: 'privateKey',
diff --git a/lib/platform/github/index.ts b/lib/platform/github/index.ts
index 367b5bc39c..48fa7fe937 100644
--- a/lib/platform/github/index.ts
+++ b/lib/platform/github/index.ts
@@ -820,7 +820,12 @@ export async function findIssue(title: string) {
   };
 }
 
-export async function ensureIssue(title: string, body: string, once = false) {
+export async function ensureIssue(
+  title: string,
+  body: string,
+  once = false,
+  reopen = true
+) {
   logger.debug(`ensureIssue()`);
   try {
     const issueList = await getIssueList();
@@ -832,7 +837,9 @@ export async function ensureIssue(title: string, body: string, once = false) {
           logger.debug('Issue already closed - skipping recreation');
           return null;
         }
-        logger.info('Reopening previously closed issue');
+        if (reopen) {
+          logger.info('Reopening previously closed issue');
+        }
         issue = issues[issues.length - 1];
       }
       for (const i of issues) {
@@ -848,17 +855,19 @@ export async function ensureIssue(title: string, body: string, once = false) {
         logger.info('Issue is open and up to date - nothing to do');
         return null;
       }
-      logger.info('Patching issue');
-      await api.patch(
-        `repos/${config.parentRepo || config.repository}/issues/${
-          issue.number
-        }`,
-        {
-          body: { body, state: 'open' },
-        }
-      );
-      logger.info('Issue updated');
-      return 'updated';
+      if (reopen) {
+        logger.info('Patching issue');
+        await api.patch(
+          `repos/${config.parentRepo || config.repository}/issues/${
+            issue.number
+          }`,
+          {
+            body: { body, state: 'open' },
+          }
+        );
+        logger.info('Issue updated');
+        return 'updated';
+      }
     }
     await api.post(`repos/${config.parentRepo || config.repository}/issues`, {
       body: {
diff --git a/lib/workers/repository/error-config.js b/lib/workers/repository/error-config.js
index 11ac39443d..0813b27f25 100644
--- a/lib/workers/repository/error-config.js
+++ b/lib/workers/repository/error-config.js
@@ -30,9 +30,13 @@ async function raiseConfigWarningIssue(config, error) {
   } else if (config.dryRun) {
     logger.info('DRY-RUN: Would ensure config error issue');
   } else {
+    const once = false;
+    const shouldReopen = config.configWarningReuseIssue;
     const res = await platform.ensureIssue(
       `Action Required: Fix ${appName} Configuration`,
-      body
+      body,
+      once,
+      shouldReopen
     );
     if (res === 'created') {
       logger.warn({ configError: error, res }, 'Config Warning');
diff --git a/renovate-schema.json b/renovate-schema.json
index 05b0e7cb29..227a18e851 100644
--- a/renovate-schema.json
+++ b/renovate-schema.json
@@ -131,6 +131,11 @@
       "type": "string",
       "default": "Update Dependencies (Renovate Bot)"
     },
+    "configWarningReuseIssue": {
+      "description": "Set this to false and Renovate will open each config warning in a new issue instead of reopening/reusing an existing issue.",
+      "type": "boolean",
+      "default": true
+    },
     "privateKey": {
       "description": "Server-side private key",
       "type": "string"
diff --git a/test/platform/github/index.spec.ts b/test/platform/github/index.spec.ts
index 92e886f0d2..555a643888 100644
--- a/test/platform/github/index.spec.ts
+++ b/test/platform/github/index.spec.ts
@@ -1175,6 +1175,76 @@ describe('platform/github', () => {
       const res = await github.ensureIssue('title-1', 'newer-content');
       expect(res).toBeNull();
     });
+    it('creates issue if reopen flag false and issue is not open', async () => {
+      api.post.mockImplementationOnce(
+        () =>
+          ({
+            body: JSON.stringify({
+              data: {
+                repository: {
+                  issues: {
+                    pageInfo: {
+                      startCursor: null,
+                      hasNextPage: false,
+                      endCursor: null,
+                    },
+                    nodes: [
+                      {
+                        number: 2,
+                        state: 'close',
+                        title: 'title-2',
+                      },
+                    ],
+                  },
+                },
+              },
+            }),
+          } as any)
+      );
+      api.get.mockReturnValueOnce({ body: { body: 'new-content' } } as any);
+      const res = await github.ensureIssue(
+        'title-2',
+        'new-content',
+        false,
+        false
+      );
+      expect(res).toEqual('created');
+    });
+    it('does not create issue if reopen flag false and issue is already open', async () => {
+      api.post.mockImplementationOnce(
+        () =>
+          ({
+            body: JSON.stringify({
+              data: {
+                repository: {
+                  issues: {
+                    pageInfo: {
+                      startCursor: null,
+                      hasNextPage: false,
+                      endCursor: null,
+                    },
+                    nodes: [
+                      {
+                        number: 2,
+                        state: 'open',
+                        title: 'title-2',
+                      },
+                    ],
+                  },
+                },
+              },
+            }),
+          } as any)
+      );
+      api.get.mockReturnValueOnce({ body: { body: 'new-content' } } as any);
+      const res = await github.ensureIssue(
+        'title-2',
+        'new-content',
+        false,
+        false
+      );
+      expect(res).toEqual(null);
+    });
   });
   describe('ensureIssueClosing()', () => {
     it('closes issue', async () => {
diff --git a/website/docs/configuration-options.md b/website/docs/configuration-options.md
index 2fc23429ef..0dfbb267eb 100644
--- a/website/docs/configuration-options.md
+++ b/website/docs/configuration-options.md
@@ -192,6 +192,10 @@ This is used to manually restrict which versions are possible to upgrade to base
 
 Warning: composer support is in alpha stage so you probably only want to run this if you are helping get it feature-ready.
 
+## configWarningReuseIssue
+
+Set this option to false if you prefer Renovate to open a new issue whenever there is config warning.
+
 ## deps-edn
 
 ## description
-- 
GitLab