From a8f27e0bf384d7298a655b44ba0a08e2e8839338 Mon Sep 17 00:00:00 2001 From: Rhys Arkins <rhys@arkins.net> Date: Sat, 11 Jul 2020 11:55:30 +0200 Subject: [PATCH] feat: dependencyDashboard (#6729) --- docs/usage/configuration-options.md | 64 ++++++++--------- .../__snapshots__/migration.spec.ts.snap | 3 + lib/config/common.ts | 12 ++-- lib/config/definitions.ts | 23 +++--- lib/config/migration.spec.ts | 4 +- lib/config/migration.ts | 10 +++ lib/config/presets/internal/default.ts | 12 ++-- lib/platform/bitbucket-server/index.ts | 8 +-- .../__snapshots__/index.spec.ts.snap | 11 +++ lib/platform/bitbucket/index.spec.ts | 10 ++- lib/platform/bitbucket/index.ts | 13 +++- lib/platform/common.ts | 1 + lib/platform/gitea/index.spec.ts | 2 + lib/platform/gitea/index.ts | 14 +++- .../github/__snapshots__/index.spec.ts.snap | 4 +- lib/platform/github/index.spec.ts | 3 +- lib/platform/github/index.ts | 17 ++++- .../gitlab/__snapshots__/index.spec.ts.snap | 4 +- lib/platform/gitlab/index.ts | 12 ++-- lib/workers/branch/index.ts | 41 +++++++---- lib/workers/common.ts | 2 +- lib/workers/pr/changelog/source-github.ts | 2 +- lib/workers/pr/index.ts | 11 ++- .../master-issue_with_2_PR_closed_ignored.txt | 2 +- .../master-issue_with_2_PR_edited.txt | 2 +- .../master-issue_with_3_PR_in_approval.txt | 2 +- .../master-issue_with_3_PR_in_progress.txt | 2 +- .../__fixtures__/master-issue_with_8_PR.txt | 2 +- .../__snapshots__/vulnerability.spec.ts.snap | 6 +- lib/workers/repository/master-issue.spec.ts | 72 +++++++++---------- lib/workers/repository/master-issue.ts | 33 +++++---- lib/workers/repository/process/index.ts | 19 ++--- .../__snapshots__/generate.spec.ts.snap | 8 +-- lib/workers/repository/updates/generate.ts | 6 +- 34 files changed, 266 insertions(+), 171 deletions(-) diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index 5113beeb7e..315d8fe7a8 100644 --- a/docs/usage/configuration-options.md +++ b/docs/usage/configuration-options.md @@ -230,6 +230,35 @@ This is used to manually restrict which versions are possible to upgrade to base Renovate's default behaviour is to reuse/reopen a single Config Warning issue in each repository so as to keep the "noise" down. However for some people this has the downside that the config warning won't be sorted near the top if you view issues by creation date. Configure this option to `false` if you prefer Renovate to open a new issue whenever there is a config warning. +## dependencyDashboard + +Configuring `dependencyDashboard` to `true` will lead to the creation of a "Dependency Dashboard" issue within the repository. This issue contains a list of all PRs pending, open, closed (unmerged) or in error. The goal of this issue is to give visibility into all updates that Renovate is managing. + +Examples of what having a Dependency Dashboard will allow you to do: + +- View all PRs in one place, rather than having to filter PRs by author +- Rebase/retry multiple PRs without having to open each individually +- Override any rate limiting (e.g. concurrent PRs) or scheduling to force Renovate to create a PR that would otherwise be suppressed +- Recreate an unmerged PR (e.g. for a major update that you postponed by closing the original PR) + +Note: Enabling the Dependency Dashboard does not itself change any of the "control flow" of Renovate, e.g. it will otherwise still create and manage PRs exactly as it always has, including scheduling and rate limiting. The Dependency Dashboard therefore provides visibility as well as additional control. + +## dependencyDashboardApproval + +Setting `dependencyDashboardApproval` to `true` means that Renovate will no longer create branches/PRs automatically but instead wait for manual approval from within the Dependency Dashboard. + +In this case, the Dependency Dashboard _does_ change the flow of Renovate, because PRs will stop appearing until you approve them within the issue. Instead of enabling this repository-wide, you may instead with to use package rules to enable it selectively, e.g. for major updates only, or for certain package managers, etc. i.e. it is possible to require approval for only certain types of updates only. + +Note: Enabling Dependency Dashboard Approval implicitly enables `dependencyDashboard` too, so it is not necessary to configure both to `true`. + +## dependencyDashboardAutoclose + +You can configure this to `true` if you prefer Renovate to close an existing Dependency Dashboard whenever there are no outstanding PRs left. + +## dependencyDashboardTitle + +Configure this option if you prefer a different title for the Dependency Dashboard. + ## description The description field is used by config presets to describe what they do. They are then collated as part of the onboarding description. @@ -705,35 +734,6 @@ Add to this object if you wish to define rules that apply only to major updates. This value defaults to an empty string, because historically no prefix was necessary for when Renovate was JS-only. Now - for example - we use `docker-` for Docker branches, so they may look like `renovate/docker-ubuntu-16.x`. You normally don't need to configure this. -## masterIssue - -Configuring `masterIssue` to `true` will lead to the creation of a mini-dashboard "Master Issue" within the repository. This Master Issue contains a list of all PRs pending, open, closed (unmerged) or in error. The goal of this master issue is to give visibility into all updates that Renovate is managing. - -Examples of what having a master issue will allow you to do: - -- View all PRs in one place, rather than having to filter PRs by author -- Rebase/retry multiple PRs without having to open each individually -- Override any rate limiting (e.g. concurrent PRs) or scheduling to force Renovate to create a PR that would otherwise be suppressed -- Recreate an unmerged PR (e.g. for a major update that you postponed by closing the original PR) - -Note: Enabling the Master Issue does not itself change any of the "control flow" of Renovate, e.g. it will otherwise still create and manage PRs exactly as it always has, including scheduling and rate limiting. The Master Issue therefore provides visibility as well as additional control. - -## masterIssueApproval - -Setting `masterIssueApproval` to `true` means that Renovate will no longer create branches/PRs automatically but instead wait for manual approval from within the Master Issue. - -In this case, the Master Issue _does_ change the flow of Renovate, because PRs will stop appearing until you approve them within the issue. Instead of enabling this repository-wide, you may instead with to use package rules to enable it selectively, e.g. for major updates only, or for certain package managers, etc. i.e. it is possible to require approval for only certain types of updates only. - -Note: Enabling Master Issue Approval implicitly enables `masterIssue` too, so it is not necessary to configure both to `true`. - -## masterIssueAutoclose - -You can configure this to `true` if you prefer Renovate to close an existing Master Issue whenever there are no outstanding PRs left. - -## masterIssueTitle - -Configure this option if you prefer a different title for the Master Issue. - ## minor Add to this object if you wish to define rules that apply only to minor updates. @@ -813,14 +813,14 @@ Path rules are convenient to use if you wish to apply configuration rules to cer } ``` -If you wish to limit renovate to apply configuration rules to certain files in the root repository directory, you have to use `paths` with either a partial string match or a minimatch pattern. For example you have multiple `package.json` and want to use `masterIssueApproval` only on the root `package.json`: +If you wish to limit renovate to apply configuration rules to certain files in the root repository directory, you have to use `paths` with either a partial string match or a minimatch pattern. For example you have multiple `package.json` and want to use `dependencyDashboardApproval` only on the root `package.json`: ```json { "packageRules": [ { "paths": ["+(package.json)"], - "masterIssueApproval": true + "dependencyDashboardApproval": true } ] } @@ -1466,7 +1466,7 @@ There are a couple of uses for this: #### Suppress branch/PR creation for X days -If you combine `stabilityDays=3` and `prCreation="not-pending"` then Renovate will hold back from creating branches until 3 or more days have elapsed since the version was released. It's recommended that you enable `masterIssue=true` so you don't lose visibility of these pending PRs. +If you combine `stabilityDays=3` and `prCreation="not-pending"` then Renovate will hold back from creating branches until 3 or more days have elapsed since the version was released. It's recommended that you enable `dependencyDashboard=true` so you don't lose visibility of these pending PRs. #### Await X days before Automerging diff --git a/lib/config/__snapshots__/migration.spec.ts.snap b/lib/config/__snapshots__/migration.spec.ts.snap index 50add57140..1f7c3e6333 100644 --- a/lib/config/__snapshots__/migration.spec.ts.snap +++ b/lib/config/__snapshots__/migration.spec.ts.snap @@ -16,10 +16,13 @@ Object { ], "commitMessage": "{{#if semanticCommitType}}{{semanticCommitType}}{{#if semanticCommitScope}}({{semanticCommitScope}}){{/if}}: {{/if}}some commit message", "commitMessageExtra": "{{currentValue}} something", + "dependencyDashboard": true, + "dependencyDashboardTitle": "foo", "enabled": true, "extends": Array [ "config:js-app", "config:js-lib", + ":dependencyDashboard", ], "hostRules": Array [ Object {}, diff --git a/lib/config/common.ts b/lib/config/common.ts index a5537c876b..bf0f91aabd 100644 --- a/lib/config/common.ts +++ b/lib/config/common.ts @@ -35,7 +35,7 @@ export interface RenovateSharedConfig { ignorePaths?: string[]; labels?: string[]; managers?: string | string[]; - masterIssueApproval?: boolean; + dependencyDashboardApproval?: boolean; npmrc?: string; platform?: string; postUpgradeTasks?: PostUpgradeTasks; @@ -151,11 +151,11 @@ export interface RenovateConfig fileList?: string[]; - masterIssue?: boolean; - masterIssueAutoclose?: boolean; - masterIssueChecks?: Record<string, string>; - masterIssueRebaseAllOpen?: boolean; - masterIssueTitle?: string; + dependencyDashboard?: boolean; + dependencyDashboardAutoclose?: boolean; + dependencyDashboardChecks?: Record<string, string>; + dependencyDashboardRebaseAllOpen?: boolean; + dependencyDashboardTitle?: string; packageFile?: string; packageRules?: PackageRule[]; prConcurrentLimit?: number; diff --git a/lib/config/definitions.ts b/lib/config/definitions.ts index 10b6eed37b..974515f474 100644 --- a/lib/config/definitions.ts +++ b/lib/config/definitions.ts @@ -387,32 +387,33 @@ const options: RenovateOptions[] = [ default: false, admin: true, }, - // Master Issue + // Dependency Dashboard { - name: 'masterIssue', - description: 'Whether to create a "Master Issue" within the repository.', + name: 'dependencyDashboard', + description: + 'Whether to create a "Dependency Dashboard" issue within the repository.', type: 'boolean', default: false, }, { - name: 'masterIssueApproval', + name: 'dependencyDashboardApproval', description: - 'Whether updates should require manual approval from within the Master Issue before creation.', + 'Whether updates should require manual approval from within the Dependency Dashboard issue before creation.', type: 'boolean', default: false, }, { - name: 'masterIssueAutoclose', + name: 'dependencyDashboardAutoclose', description: - 'Set to `true` and Renovate will autoclose the Master Issue if there are no updates.', + 'Set to `true` and Renovate will autoclose the Dependency Dashboard issue if there are no updates.', type: 'boolean', default: false, }, { - name: 'masterIssueTitle', - description: 'Title to use for the Master Issue', + name: 'dependencyDashboardTitle', + description: 'Title to use for the Dependency Dashboard issue', type: 'string', - default: `Update Dependencies (Renovate Bot)`, + default: `Dependency Dashboard`, }, { name: 'configWarningReuseIssue', @@ -1189,7 +1190,7 @@ const options: RenovateOptions[] = [ default: { groupName: null, schedule: [], - masterIssueApproval: false, + dependencyDashboardApproval: false, rangeStrategy: 'update-lockfile', commitMessageSuffix: '[SECURITY]', }, diff --git a/lib/config/migration.spec.ts b/lib/config/migration.spec.ts index 82e3cc4e0e..ef5b454e0f 100644 --- a/lib/config/migration.spec.ts +++ b/lib/config/migration.spec.ts @@ -25,7 +25,7 @@ describe('config/migration', () => { password: 'some-password', }, ], - extends: [':js-app', 'config:library'], + extends: [':js-app', 'config:library', ':masterIssue'], maintainYarnLock: true, onboarding: 'false' as never, multipleMajorPrs: true, @@ -37,6 +37,8 @@ describe('config/migration', () => { automergeMajor: false, automergeMinor: true, automergePatch: true, + masterIssue: true, + masterIssueTitle: 'foo', gomodTidy: true, upgradeInRange: true, automergeType: 'branch-push', diff --git a/lib/config/migration.ts b/lib/config/migration.ts index 4c82f73aa2..17637d5552 100644 --- a/lib/config/migration.ts +++ b/lib/config/migration.ts @@ -83,6 +83,10 @@ export function migrateConfig( (item) => item !== 'prEditNotification' ); } + } else if (key.startsWith('masterIssue')) { + isMigrated = true; + migratedConfig[key.replace('masterIssue', 'dependencyDashboard')] = val; + delete migratedConfig[key]; } else if (key === 'gomodTidy') { isMigrated = true; if (val) { @@ -211,6 +215,12 @@ export function migrateConfig( } else if (val[i] === ':library' || val[i] === 'config:library') { isMigrated = true; migratedConfig.extends[i] = 'config:js-lib'; + } else if (val[i].startsWith(':masterIssue')) { + isMigrated = true; + migratedConfig.extends[i] = val[i].replace( + 'masterIssue', + 'dependencyDashboard' + ); } } } else if (key === 'versionScheme') { diff --git a/lib/config/presets/internal/default.ts b/lib/config/presets/internal/default.ts index 44594e625a..f45ba82d60 100644 --- a/lib/config/presets/internal/default.ts +++ b/lib/config/presets/internal/default.ts @@ -539,13 +539,13 @@ export const presets: Record<string, Preset> = { }, ], }, - masterIssue: { - description: 'Enable Renovate master issue creation', - masterIssue: true, + dependencyDashboard: { + description: 'Enable Renovate Dependency Dashboard creation', + dependencyDashboard: true, }, - masterIssueApproval: { - description: 'Enable Renovate master issue approval workflow', - masterIssueApproval: true, + dependencyDashboardApproval: { + description: 'Enable Renovate Dependency Dashboard approval workflow', + dependencyDashboardApproval: true, }, timezone: { description: 'Evaluate schedules according to timezone {{arg0}}', diff --git a/lib/platform/bitbucket-server/index.ts b/lib/platform/bitbucket-server/index.ts index 079e613b92..e03985838b 100644 --- a/lib/platform/bitbucket-server/index.ts +++ b/lib/platform/bitbucket-server/index.ts @@ -576,7 +576,7 @@ export async function setBranchStatus({ // function getIssueList() { // logger.debug(`getIssueList()`); // // TODO: Needs implementation -// // This is used by Renovate when creating its own issues, e.g. for deprecated package warnings, config error notifications, or "masterIssue" +// // This is used by Renovate when creating its own issues, e.g. for deprecated package warnings, config error notifications, or "dependencyDashboard" // // BB Server doesnt have issues // return []; // } @@ -586,7 +586,7 @@ export /* istanbul ignore next */ function findIssue( ): Promise<Issue | null> { logger.debug(`findIssue(${title})`); // TODO: Needs implementation - // This is used by Renovate when creating its own issues, e.g. for deprecated package warnings, config error notifications, or "masterIssue" + // This is used by Renovate when creating its own issues, e.g. for deprecated package warnings, config error notifications, or "dependencyDashboard" // BB Server doesnt have issues return null; } @@ -596,7 +596,7 @@ export /* istanbul ignore next */ function ensureIssue({ }: EnsureIssueConfig): Promise<EnsureIssueResult | null> { logger.warn({ title }, 'Cannot ensure issue'); // TODO: Needs implementation - // This is used by Renovate when creating its own issues, e.g. for deprecated package warnings, config error notifications, or "masterIssue" + // This is used by Renovate when creating its own issues, e.g. for deprecated package warnings, config error notifications, or "dependencyDashboard" // BB Server doesnt have issues return null; } @@ -612,7 +612,7 @@ export /* istanbul ignore next */ function ensureIssueClosing( ): Promise<void> { logger.debug(`ensureIssueClosing(${title})`); // TODO: Needs implementation - // This is used by Renovate when creating its own issues, e.g. for deprecated package warnings, config error notifications, or "masterIssue" + // This is used by Renovate when creating its own issues, e.g. for deprecated package warnings, config error notifications, or "dependencyDashboard" // BB Server doesnt have issues return Promise.resolve(); } diff --git a/lib/platform/bitbucket/__snapshots__/index.spec.ts.snap b/lib/platform/bitbucket/__snapshots__/index.spec.ts.snap index d8d0869a56..ceec9eef57 100644 --- a/lib/platform/bitbucket/__snapshots__/index.spec.ts.snap +++ b/lib/platform/bitbucket/__snapshots__/index.spec.ts.snap @@ -216,6 +216,17 @@ Array [ "method": "GET", "url": "https://api.bitbucket.org/2.0/repositories/some/empty/issues?q=title%3D%22title%22%20AND%20(state%20%3D%20%22new%22%20OR%20state%20%3D%20%22open%22)%20AND%20reporter.username%3D%22abc%22", }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate", + "authorization": "Basic YWJjOjEyMw==", + "host": "api.bitbucket.org", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://api.bitbucket.org/2.0/repositories/some/empty/issues?q=title%3D%22old-title%22%20AND%20(state%20%3D%20%22new%22%20OR%20state%20%3D%20%22open%22)%20AND%20reporter.username%3D%22abc%22", + }, Object { "body": "{\\"title\\":\\"title\\",\\"content\\":{\\"raw\\":\\"body\\",\\"markup\\":\\"markdown\\"}}", "headers": Object { diff --git a/lib/platform/bitbucket/index.spec.ts b/lib/platform/bitbucket/index.spec.ts index 6217dabd0a..6e44674dc3 100644 --- a/lib/platform/bitbucket/index.spec.ts +++ b/lib/platform/bitbucket/index.spec.ts @@ -531,10 +531,18 @@ describe('platform/bitbucket', () => { '/2.0/repositories/some/empty/issues?q=title%3D%22title%22%20AND%20(state%20%3D%20%22new%22%20OR%20state%20%3D%20%22open%22)%20AND%20reporter.username%3D%22abc%22' ) .reply(200, { values: [] }) + .get( + '/2.0/repositories/some/empty/issues?q=title%3D%22old-title%22%20AND%20(state%20%3D%20%22new%22%20OR%20state%20%3D%20%22open%22)%20AND%20reporter.username%3D%22abc%22' + ) + .reply(200, { values: [] }) .post('/2.0/repositories/some/empty/issues') .reply(200); expect( - await bitbucket.ensureIssue({ title: 'title', body: 'body' }) + await bitbucket.ensureIssue({ + title: 'title', + reuseTitle: 'old-title', + body: 'body', + }) ).toEqual('created'); expect(httpMock.getTrace()).toMatchSnapshot(); }); diff --git a/lib/platform/bitbucket/index.ts b/lib/platform/bitbucket/index.ts index b53516bb7f..052f0c092a 100644 --- a/lib/platform/bitbucket/index.ts +++ b/lib/platform/bitbucket/index.ts @@ -459,7 +459,7 @@ export async function setBranchStatus({ await getStatus(branchName, false); } -type BbIssue = { id: number; content?: { raw: string } }; +type BbIssue = { id: number; title: string; content?: { raw: string } }; async function findOpenIssues(title: string): Promise<BbIssue[]> { try { @@ -526,6 +526,7 @@ export function getPrBody(input: string): string { export async function ensureIssue({ title, + reuseTitle, body, }: EnsureIssueConfig): Promise<EnsureIssueResult | null> { logger.debug(`ensureIssue()`); @@ -538,14 +539,20 @@ export async function ensureIssue({ return null; } try { - const issues = await findOpenIssues(title); + let issues = await findOpenIssues(title); + if (!issues.length) { + issues = await findOpenIssues(reuseTitle); + } if (issues.length) { // Close any duplicates for (const issue of issues.slice(1)) { await closeIssue(issue.id); } const [issue] = issues; - if (String(issue.content.raw).trim() !== description.trim()) { + if ( + issue.title !== title || + String(issue.content.raw).trim() !== description.trim() + ) { logger.debug('Issue updated'); await bitbucketHttp.putJson( `/2.0/repositories/${config.repository}/issues/${issue.id}`, diff --git a/lib/platform/common.ts b/lib/platform/common.ts index 11e7dd9de0..7cbf3595cf 100644 --- a/lib/platform/common.ts +++ b/lib/platform/common.ts @@ -81,6 +81,7 @@ export interface CreatePRConfig { } export interface EnsureIssueConfig { title: string; + reuseTitle?: string; body: string; once?: boolean; shouldReOpen?: boolean; diff --git a/lib/platform/gitea/index.spec.ts b/lib/platform/gitea/index.spec.ts index a3eb028734..d1810a68c2 100644 --- a/lib/platform/gitea/index.spec.ts +++ b/lib/platform/gitea/index.spec.ts @@ -956,6 +956,7 @@ describe('platform/gitea', () => { { body: closedIssue.body, state: closedIssue.state, + title: 'closed-issue', } ); }); @@ -980,6 +981,7 @@ describe('platform/gitea', () => { { body: closedIssue.body, state: 'open', + title: 'closed-issue', } ); }); diff --git a/lib/platform/gitea/index.ts b/lib/platform/gitea/index.ts index 5dd9eb2c6c..ecc41bad9f 100644 --- a/lib/platform/gitea/index.ts +++ b/lib/platform/gitea/index.ts @@ -641,6 +641,7 @@ const platform: Platform = { async ensureIssue({ title, + reuseTitle, body, shouldReOpen, once, @@ -648,8 +649,10 @@ const platform: Platform = { logger.debug(`ensureIssue(${title})`); try { const issueList = await platform.getIssueList(); - const issues = issueList.filter((i) => i.title === title); - + let issues = issueList.filter((i) => i.title === title); + if (!issues.length) { + issues = issueList.filter((i) => i.title === reuseTitle); + } // Update any matching issues which currently exist if (issues.length) { let activeIssue = issues.find((i) => i.state === 'open'); @@ -677,7 +680,11 @@ const platform: Platform = { } // Check if issue has already correct state - if (activeIssue.body === body && activeIssue.state === 'open') { + if ( + activeIssue.title === title && + activeIssue.body === body && + activeIssue.state === 'open' + ) { logger.debug( `Issue #${activeIssue.number} is open and up to date - nothing to do` ); @@ -688,6 +695,7 @@ const platform: Platform = { logger.debug(`Updating Issue #${activeIssue.number}`); await helper.updateIssue(config.repository, activeIssue.number, { body, + title, state: shouldReOpen ? 'open' : (activeIssue.state as helper.IssueState), diff --git a/lib/platform/github/__snapshots__/index.spec.ts.snap b/lib/platform/github/__snapshots__/index.spec.ts.snap index fda834bf60..baf161ff74 100644 --- a/lib/platform/github/__snapshots__/index.spec.ts.snap +++ b/lib/platform/github/__snapshots__/index.spec.ts.snap @@ -1312,12 +1312,12 @@ Array [ "url": "https://api.github.com/repos/undefined/issues/2", }, Object { - "body": "{\\"body\\":\\"newer-content\\",\\"state\\":\\"open\\"}", + "body": "{\\"body\\":\\"newer-content\\",\\"state\\":\\"open\\",\\"title\\":\\"title-3\\"}", "headers": Object { "accept": "application/vnd.github.v3+json", "accept-encoding": "gzip, deflate", "authorization": "token abc123", - "content-length": 39, + "content-length": 57, "content-type": "application/json", "host": "api.github.com", "user-agent": "https://github.com/renovatebot/renovate", diff --git a/lib/platform/github/index.spec.ts b/lib/platform/github/index.spec.ts index 9f90ba48fd..c08cf42b86 100644 --- a/lib/platform/github/index.spec.ts +++ b/lib/platform/github/index.spec.ts @@ -1072,7 +1072,8 @@ describe('platform/github', () => { .patch('/repos/undefined/issues/2') .reply(200); const res = await github.ensureIssue({ - title: 'title-2', + title: 'title-3', + reuseTitle: 'title-2', body: 'newer-content', }); expect(res).toEqual('updated'); diff --git a/lib/platform/github/index.ts b/lib/platform/github/index.ts index 46317be615..824c90eda1 100644 --- a/lib/platform/github/index.ts +++ b/lib/platform/github/index.ts @@ -1204,6 +1204,7 @@ async function closeIssue(issueNumber: number): Promise<void> { export async function ensureIssue({ title, + reuseTitle, body: rawBody, once = false, shouldReOpen = true, @@ -1212,7 +1213,13 @@ export async function ensureIssue({ const body = sanitize(rawBody); try { const issueList = await getIssueList(); - const issues = issueList.filter((i) => i.title === title); + let issues = issueList.filter((i) => i.title === title); + if (!issues.length) { + issues = issueList.filter((i) => i.title === reuseTitle); + if (issues.length) { + logger.debug({ reuseTitle, title }, 'Reusing issue title'); + } + } if (issues.length) { let issue = issues.find((i) => i.state === 'open'); if (!issue) { @@ -1238,7 +1245,11 @@ export async function ensureIssue({ }` ) ).body.body; - if (issueBody === body && issue.state === 'open') { + if ( + issue.title === title && + issueBody === body && + issue.state === 'open' + ) { logger.debug('Issue is open and up to date - nothing to do'); return null; } @@ -1249,7 +1260,7 @@ export async function ensureIssue({ issue.number }`, { - body: { body, state: 'open' }, + body: { body, state: 'open', title }, } ); logger.debug('Issue updated'); diff --git a/lib/platform/gitlab/__snapshots__/index.spec.ts.snap b/lib/platform/gitlab/__snapshots__/index.spec.ts.snap index 1ec0d3cee9..a905057977 100644 --- a/lib/platform/gitlab/__snapshots__/index.spec.ts.snap +++ b/lib/platform/gitlab/__snapshots__/index.spec.ts.snap @@ -641,11 +641,11 @@ Array [ "url": "https://gitlab.com/api/v4/projects/undefined/issues/2", }, Object { - "body": "{\\"description\\":\\"newer-content\\"}", + "body": "{\\"title\\":\\"title-2\\",\\"description\\":\\"newer-content\\"}", "headers": Object { "accept": "application/json", "accept-encoding": "gzip, deflate", - "content-length": 31, + "content-length": 49, "content-type": "application/json", "host": "gitlab.com", "private-token": "abc123", diff --git a/lib/platform/gitlab/index.ts b/lib/platform/gitlab/index.ts index e40f141aaa..f7967bc0a1 100644 --- a/lib/platform/gitlab/index.ts +++ b/lib/platform/gitlab/index.ts @@ -720,25 +720,29 @@ export async function findIssue(title: string): Promise<Issue | null> { export async function ensureIssue({ title, + reuseTitle, body, }: EnsureIssueConfig): Promise<'updated' | 'created' | null> { logger.debug(`ensureIssue()`); const description = getPrBody(sanitize(body)); try { const issueList = await getIssueList(); - const issue = issueList.find((i: { title: string }) => i.title === title); + let issue = issueList.find((i: { title: string }) => i.title === title); + if (!issue) { + issue = issueList.find((i: { title: string }) => i.title === reuseTitle); + } if (issue) { const existingDescription = ( await gitlabApi.getJson<{ description: string }>( `projects/${config.repository}/issues/${issue.iid}` ) ).body.description; - if (existingDescription !== description) { - logger.debug('Updating issue body'); + if (issue.title !== title || existingDescription !== description) { + logger.debug('Updating issue'); await gitlabApi.putJson( `projects/${config.repository}/issues/${issue.iid}`, { - body: { description }, + body: { title, description }, } ); return 'updated'; diff --git a/lib/workers/branch/index.ts b/lib/workers/branch/index.ts index 52f5ed5a33..cd8f652241 100644 --- a/lib/workers/branch/index.ts +++ b/lib/workers/branch/index.ts @@ -68,11 +68,14 @@ export async function processBranch( const branchExists = await gitBranchExists(config.branchName); const branchPr = await platform.getBranchPr(config.branchName); logger.debug(`branchExists=${branchExists}`); - const masterIssueCheck = (config.masterIssueChecks || {})[config.branchName]; + const dependencyDashboardCheck = (config.dependencyDashboardChecks || {})[ + config.branchName + ]; // istanbul ignore if - if (masterIssueCheck) { + if (dependencyDashboardCheck) { logger.debug( - 'Branch has been checked in master issue: ' + masterIssueCheck + 'Branch has been checked in Dependency Dashboard: ' + + dependencyDashboardCheck ); } if (branchPr) { @@ -84,7 +87,7 @@ export async function processBranch( // Check if branch already existed const existingPr = branchPr ? undefined : await prAlreadyExisted(config); - if (existingPr && !masterIssueCheck) { + if (existingPr && !dependencyDashboardCheck) { logger.debug( { prTitle: config.prTitle }, 'Closed PR already exists. Skipping branch.' @@ -131,8 +134,8 @@ export async function processBranch( return 'already-existed'; } // istanbul ignore if - if (!branchExists && config.masterIssueApproval) { - if (masterIssueCheck) { + if (!branchExists && config.dependencyDashboardApproval) { + if (dependencyDashboardCheck) { logger.debug(`Branch ${config.branchName} is approved for creation`); } else { logger.debug(`Branch ${config.branchName} needs approval`); @@ -142,7 +145,7 @@ export async function processBranch( if ( !branchExists && prHourlyLimitReached && - !masterIssueCheck && + !dependencyDashboardCheck && !config.vulnerabilityAlert ) { logger.debug( @@ -166,7 +169,7 @@ export async function processBranch( branchPr.targetBranch !== branchConfig.baseBranch) ) { logger.debug({ prNo: branchPr.number }, 'PR has been edited'); - if (masterIssueCheck || config.rebaseRequested) { + if (dependencyDashboardCheck || config.rebaseRequested) { logger.debug('Manual rebase has been requested for PR'); } else { const newBody = branchPr.body?.replace( @@ -187,7 +190,7 @@ export async function processBranch( // Check schedule config.isScheduledNow = isScheduledNow(config); - if (!config.isScheduledNow && !masterIssueCheck) { + if (!config.isScheduledNow && !dependencyDashboardCheck) { if (!branchExists) { logger.debug('Skipping branch creation as not within schedule'); return 'not-scheduled'; @@ -236,7 +239,10 @@ export async function processBranch( new Date(upgrade.releaseTimestamp).getTime()) / oneDay ); - if (!masterIssueCheck && daysElapsed < upgrade.stabilityDays) { + if ( + !dependencyDashboardCheck && + daysElapsed < upgrade.stabilityDays + ) { logger.debug( { depName: upgrade.depName, @@ -251,7 +257,7 @@ export async function processBranch( } // Don't create a branch if we know it will be status 'pending' if ( - !masterIssueCheck && + !dependencyDashboardCheck && !branchExists && config.stabilityStatus === BranchStatus.yellow && ['not-pending', 'status-success'].includes(config.prCreation) @@ -262,8 +268,11 @@ export async function processBranch( } // istanbul ignore if - if (masterIssueCheck === 'rebase' || config.masterIssueRebaseAllOpen) { - logger.debug('Manual rebase requested via master issue'); + if ( + dependencyDashboardCheck === 'rebase' || + config.dependencyDashboardRebaseAllOpen + ) { + logger.debug('Manual rebase requested via Dependency Dashboard'); config.reuseExistingBranch = false; } else { Object.assign(config, await shouldReuseExistingBranch(config)); @@ -419,7 +428,9 @@ export async function processBranch( } } config.forceCommit = - !!masterIssueCheck || config.rebaseRequested || branchPr?.isConflicted; + !!dependencyDashboardCheck || + config.rebaseRequested || + branchPr?.isConflicted; const commitHash = await commitFilesToBranch(config); // istanbul ignore if if (branchPr && platform.refreshPr) { @@ -438,7 +449,7 @@ export async function processBranch( // break if we pushed a new commit because status check are pretty sure pending but maybe not reported yet if ( - !masterIssueCheck && + !dependencyDashboardCheck && !config.rebaseRequested && commitHash && (config.requiredStatusChecks?.length || config.prCreation !== 'immediate') diff --git a/lib/workers/common.ts b/lib/workers/common.ts index 47f474ebbf..5082cb40eb 100644 --- a/lib/workers/common.ts +++ b/lib/workers/common.ts @@ -99,7 +99,7 @@ export interface BranchConfig canBeUnpublished?: boolean; errors?: ValidationMessage[]; hasTypes?: boolean; - masterIssueChecks?: Record<string, string>; + dependencyDashboardChecks?: Record<string, string>; releaseTimestamp?: string; forceCommit?: boolean; rebaseRequested?: boolean; diff --git a/lib/workers/pr/changelog/source-github.ts b/lib/workers/pr/changelog/source-github.ts index 2a36a600ec..df9939baed 100644 --- a/lib/workers/pr/changelog/source-github.ts +++ b/lib/workers/pr/changelog/source-github.ts @@ -67,7 +67,7 @@ export async function getChangeLogJSON({ manager, }: BranchUpgradeConfig): Promise<ChangeLogResult | null> { if (sourceUrl === 'https://github.com/DefinitelyTyped/DefinitelyTyped') { - logger.debug('No release notes for @types'); + logger.trace('No release notes for @types'); return null; } const version = allVersioning.get(versioning); diff --git a/lib/workers/pr/index.ts b/lib/workers/pr/index.ts index 70405d8394..c50b153aa3 100644 --- a/lib/workers/pr/index.ts +++ b/lib/workers/pr/index.ts @@ -102,7 +102,9 @@ export async function ensurePr( logger.trace({ config }, 'ensurePr'); // If there is a group, it will use the config of the first upgrade in the array const { branchName, prTitle, upgrades } = config; - const masterIssueCheck = (config.masterIssueChecks || {})[config.branchName]; + const dependencyDashboardCheck = (config.dependencyDashboardChecks || {})[ + config.branchName + ]; // Check if existing PR exists const existingPr = await platform.getBranchPr(branchName); if (existingPr) { @@ -168,7 +170,7 @@ export async function ensurePr( } else if ( config.prCreation === 'approval' && !existingPr && - masterIssueCheck !== 'approvePr' + dependencyDashboardCheck !== 'approvePr' ) { return { prResult: PrResult.AwaitingApproval }; } else if ( @@ -187,7 +189,10 @@ export async function ensurePr( const elapsedHours = Math.round( (currentTime.getTime() - lastCommitTime.getTime()) / millisecondsPerHour ); - if (!masterIssueCheck && elapsedHours < config.prNotPendingHours) { + if ( + !dependencyDashboardCheck && + elapsedHours < config.prNotPendingHours + ) { logger.debug( `Branch is ${elapsedHours} hours old - skipping PR creation` ); diff --git a/lib/workers/repository/__fixtures__/master-issue_with_2_PR_closed_ignored.txt b/lib/workers/repository/__fixtures__/master-issue_with_2_PR_closed_ignored.txt index e0eba00ed7..1330d43649 100644 --- a/lib/workers/repository/__fixtures__/master-issue_with_2_PR_closed_ignored.txt +++ b/lib/workers/repository/__fixtures__/master-issue_with_2_PR_closed_ignored.txt @@ -1,4 +1,4 @@ -This [master issue](https://renovatebot.com/blog/master-issue) contains a list of Renovate updates and their statuses. +This issue contains a list of Renovate updates and their statuses. ## Closed/Ignored diff --git a/lib/workers/repository/__fixtures__/master-issue_with_2_PR_edited.txt b/lib/workers/repository/__fixtures__/master-issue_with_2_PR_edited.txt index 8a6f9567a2..a3c51068a9 100644 --- a/lib/workers/repository/__fixtures__/master-issue_with_2_PR_edited.txt +++ b/lib/workers/repository/__fixtures__/master-issue_with_2_PR_edited.txt @@ -1,4 +1,4 @@ -This [master issue](https://renovatebot.com/blog/master-issue) contains a list of Renovate updates and their statuses. +This issue contains a list of Renovate updates and their statuses. ## Edited/Blocked diff --git a/lib/workers/repository/__fixtures__/master-issue_with_3_PR_in_approval.txt b/lib/workers/repository/__fixtures__/master-issue_with_3_PR_in_approval.txt index 1aaf4c612f..08cbe35d31 100644 --- a/lib/workers/repository/__fixtures__/master-issue_with_3_PR_in_approval.txt +++ b/lib/workers/repository/__fixtures__/master-issue_with_3_PR_in_approval.txt @@ -1,4 +1,4 @@ -This [master issue](https://renovatebot.com/blog/master-issue) contains a list of Renovate updates and their statuses. +This issue contains a list of Renovate updates and their statuses. ## PR Creation Approval Required diff --git a/lib/workers/repository/__fixtures__/master-issue_with_3_PR_in_progress.txt b/lib/workers/repository/__fixtures__/master-issue_with_3_PR_in_progress.txt index 301dea48ea..37d2fdc5b0 100644 --- a/lib/workers/repository/__fixtures__/master-issue_with_3_PR_in_progress.txt +++ b/lib/workers/repository/__fixtures__/master-issue_with_3_PR_in_progress.txt @@ -1,4 +1,4 @@ -This [master issue](https://renovatebot.com/blog/master-issue) contains a list of Renovate updates and their statuses. +This issue contains a list of Renovate updates and their statuses. ## Open diff --git a/lib/workers/repository/__fixtures__/master-issue_with_8_PR.txt b/lib/workers/repository/__fixtures__/master-issue_with_8_PR.txt index 0fc49186c1..5bcb226c2e 100644 --- a/lib/workers/repository/__fixtures__/master-issue_with_8_PR.txt +++ b/lib/workers/repository/__fixtures__/master-issue_with_8_PR.txt @@ -1,4 +1,4 @@ -This [master issue](https://renovatebot.com/blog/master-issue) contains a list of Renovate updates and their statuses. +This issue contains a list of Renovate updates and their statuses. ## Pending Approval diff --git a/lib/workers/repository/init/__snapshots__/vulnerability.spec.ts.snap b/lib/workers/repository/init/__snapshots__/vulnerability.spec.ts.snap index 39f0bff86c..fede928898 100644 --- a/lib/workers/repository/init/__snapshots__/vulnerability.spec.ts.snap +++ b/lib/workers/repository/init/__snapshots__/vulnerability.spec.ts.snap @@ -10,8 +10,8 @@ Array [ "force": Object { "branchTopic": "npm-electron-vulnerability", "commitMessageSuffix": "[SECURITY]", + "dependencyDashboardApproval": false, "groupName": null, - "masterIssueApproval": false, "rangeStrategy": "update-lockfile", "schedule": Array [], "vulnerabilityAlert": true, @@ -35,8 +35,8 @@ Electron version 1.7 up to 1.7.12; 1.8 up to 1.8.3 and 2.0.0 up to 2.0.0-beta.3 "force": Object { "branchTopic": "pypi-ansible-vulnerability", "commitMessageSuffix": "[SECURITY]", + "dependencyDashboardApproval": false, "groupName": null, - "masterIssueApproval": false, "rangeStrategy": "update-lockfile", "schedule": Array [], "vulnerabilityAlert": true, @@ -75,8 +75,8 @@ Ansible before versions 2.1.4, 2.2.1 is vulnerable to an improper input validati "force": Object { "branchTopic": "maven-com.fasterxml.jackson.core:jackson-databind-vulnerability", "commitMessageSuffix": "[SECURITY]", + "dependencyDashboardApproval": false, "groupName": null, - "masterIssueApproval": false, "rangeStrategy": "update-lockfile", "schedule": Array [], "vulnerabilityAlert": true, diff --git a/lib/workers/repository/master-issue.spec.ts b/lib/workers/repository/master-issue.spec.ts index 00c39bbcfd..6ddab59c0f 100644 --- a/lib/workers/repository/master-issue.spec.ts +++ b/lib/workers/repository/master-issue.spec.ts @@ -5,7 +5,7 @@ import { PLATFORM_TYPE_GITHUB } from '../../constants/platforms'; import { PR_STATE_NOT_OPEN } from '../../constants/pull-requests'; import { Platform, Pr } from '../../platform'; import { BranchConfig, BranchUpgradeConfig } from '../common'; -import * as masterIssue from './master-issue'; +import * as dependencyDashboard from './master-issue'; type PrUpgrade = BranchUpgradeConfig; @@ -29,7 +29,7 @@ async function dryRun( ) { jest.resetAllMocks(); config.dryRun = true; - await masterIssue.ensureMasterIssue(config, branches); + await dependencyDashboard.ensureMasterIssue(config, branches); expect(platform.ensureIssueClosing).toHaveBeenCalledTimes( ensureIssueClosingCalls ); @@ -42,7 +42,7 @@ describe('workers/repository/master-issue', () => { describe('ensureMasterIssue()', () => { it('do nothing if masterissue is disable', async () => { const branches: BranchConfig[] = []; - await masterIssue.ensureMasterIssue(config, branches); + await dependencyDashboard.ensureMasterIssue(config, branches); expect(platform.ensureIssueClosing).toHaveBeenCalledTimes(0); expect(platform.ensureIssue).toHaveBeenCalledTimes(0); expect(platform.getBranchPr).toHaveBeenCalledTimes(0); @@ -61,10 +61,10 @@ describe('workers/repository/master-issue', () => { { ...mock<BranchConfig>(), prTitle: 'pr2', - masterIssueApproval: false, + dependencyDashboardApproval: false, }, ]; - await masterIssue.ensureMasterIssue(config, branches); + await dependencyDashboard.ensureMasterIssue(config, branches); expect(platform.ensureIssueClosing).toHaveBeenCalledTimes(0); expect(platform.ensureIssue).toHaveBeenCalledTimes(0); expect(platform.getBranchPr).toHaveBeenCalledTimes(0); @@ -74,14 +74,14 @@ describe('workers/repository/master-issue', () => { await dryRun(branches, platform); }); - it('closes master issue when there is 0 PR opened and masterIssueAutoclose is true', async () => { + it('closes Dependency Dashboard when there is 0 PR opened and dependencyDashboardAutoclose is true', async () => { const branches: BranchConfig[] = []; - config.masterIssue = true; - config.masterIssueAutoclose = true; - await masterIssue.ensureMasterIssue(config, branches); + config.dependencyDashboard = true; + config.dependencyDashboardAutoclose = true; + await dependencyDashboard.ensureMasterIssue(config, branches); expect(platform.ensureIssueClosing).toHaveBeenCalledTimes(1); expect(platform.ensureIssueClosing.mock.calls[0][0]).toBe( - config.masterIssueTitle + config.dependencyDashboardTitle ); expect(platform.ensureIssue).toHaveBeenCalledTimes(0); expect(platform.getBranchPr).toHaveBeenCalledTimes(0); @@ -91,22 +91,22 @@ describe('workers/repository/master-issue', () => { await dryRun(branches, platform); }); - it('closes master issue when all branches are automerged and masterIssueAutoclose is true', async () => { + it('closes Dependency Dashboard when all branches are automerged and dependencyDashboardAutoclose is true', async () => { const branches: BranchConfig[] = [ { ...mock<BranchConfig>(), prTitle: 'pr1', res: 'automerged' }, { ...mock<BranchConfig>(), prTitle: 'pr2', res: 'automerged', - masterIssueApproval: false, + dependencyDashboardApproval: false, }, ]; - config.masterIssue = true; - config.masterIssueAutoclose = true; - await masterIssue.ensureMasterIssue(config, branches); + config.dependencyDashboard = true; + config.dependencyDashboardAutoclose = true; + await dependencyDashboard.ensureMasterIssue(config, branches); expect(platform.ensureIssueClosing).toHaveBeenCalledTimes(1); expect(platform.ensureIssueClosing.mock.calls[0][0]).toBe( - config.masterIssueTitle + config.dependencyDashboardTitle ); expect(platform.ensureIssue).toHaveBeenCalledTimes(0); expect(platform.getBranchPr).toHaveBeenCalledTimes(0); @@ -116,14 +116,14 @@ describe('workers/repository/master-issue', () => { await dryRun(branches, platform); }); - it('open or update master issue when all branches are closed and masterIssueAutoclose is false', async () => { + it('open or update Dependency Dashboard when all branches are closed and dependencyDashboardAutoclose is false', async () => { const branches: BranchConfig[] = []; - config.masterIssue = true; - await masterIssue.ensureMasterIssue(config, branches); + config.dependencyDashboard = true; + await dependencyDashboard.ensureMasterIssue(config, branches); expect(platform.ensureIssueClosing).toHaveBeenCalledTimes(0); expect(platform.ensureIssue).toHaveBeenCalledTimes(1); expect(platform.ensureIssue.mock.calls[0][0].title).toBe( - config.masterIssueTitle + config.dependencyDashboardTitle ); expect(platform.ensureIssue.mock.calls[0][0].body).toBe( 'This repository is up-to-date and has no outstanding updates open or pending.' @@ -194,12 +194,12 @@ describe('workers/repository/master-issue', () => { branchName: 'branchName8', }, ]; - config.masterIssue = true; - await masterIssue.ensureMasterIssue(config, branches); + config.dependencyDashboard = true; + await dependencyDashboard.ensureMasterIssue(config, branches); expect(platform.ensureIssueClosing).toHaveBeenCalledTimes(0); expect(platform.ensureIssue).toHaveBeenCalledTimes(1); expect(platform.ensureIssue.mock.calls[0][0].title).toBe( - config.masterIssueTitle + config.dependencyDashboardTitle ); expect(platform.ensureIssue.mock.calls[0][0].body).toBe( fs.readFileSync( @@ -234,15 +234,15 @@ describe('workers/repository/master-issue', () => { branchName: 'branchName2', }, ]; - config.masterIssue = true; + config.dependencyDashboard = true; platform.getBranchPr .mockResolvedValueOnce({ ...mock<Pr>(), number: 1 }) .mockResolvedValueOnce(undefined); - await masterIssue.ensureMasterIssue(config, branches); + await dependencyDashboard.ensureMasterIssue(config, branches); expect(platform.ensureIssueClosing).toHaveBeenCalledTimes(0); expect(platform.ensureIssue).toHaveBeenCalledTimes(1); expect(platform.ensureIssue.mock.calls[0][0].title).toBe( - config.masterIssueTitle + config.dependencyDashboardTitle ); expect(platform.ensureIssue.mock.calls[0][0].body).toBe( fs.readFileSync( @@ -286,16 +286,16 @@ describe('workers/repository/master-issue', () => { branchName: 'branchName3', }, ]; - config.masterIssue = true; + config.dependencyDashboard = true; platform.getBranchPr .mockResolvedValueOnce({ ...mock<Pr>(), number: 1 }) .mockResolvedValueOnce(undefined) .mockResolvedValueOnce({ ...mock<Pr>(), number: 3 }); - await masterIssue.ensureMasterIssue(config, branches); + await dependencyDashboard.ensureMasterIssue(config, branches); expect(platform.ensureIssueClosing).toHaveBeenCalledTimes(0); expect(platform.ensureIssue).toHaveBeenCalledTimes(1); expect(platform.ensureIssue.mock.calls[0][0].title).toBe( - config.masterIssueTitle + config.dependencyDashboardTitle ); expect(platform.ensureIssue.mock.calls[0][0].body).toBe( fs.readFileSync( @@ -333,16 +333,16 @@ describe('workers/repository/master-issue', () => { branchName: 'branchName2', }, ]; - config.masterIssue = true; + config.dependencyDashboard = true; platform.getBranchPr .mockResolvedValueOnce({ ...mock<Pr>(), number: 1 }) .mockResolvedValueOnce(undefined) .mockResolvedValueOnce({ ...mock<Pr>(), number: 3 }); - await masterIssue.ensureMasterIssue(config, branches); + await dependencyDashboard.ensureMasterIssue(config, branches); expect(platform.ensureIssueClosing).toHaveBeenCalledTimes(0); expect(platform.ensureIssue).toHaveBeenCalledTimes(1); expect(platform.ensureIssue.mock.calls[0][0].title).toBe( - config.masterIssueTitle + config.dependencyDashboardTitle ); expect(platform.ensureIssue.mock.calls[0][0].body).toBe( fs.readFileSync( @@ -397,13 +397,13 @@ describe('workers/repository/master-issue', () => { branchName: 'branchName4', }, ]; - config.masterIssue = true; - config.masterIssuePrApproval = true; - await masterIssue.ensureMasterIssue(config, branches); + config.dependencyDashboard = true; + config.dependencyDashboardPrApproval = true; + await dependencyDashboard.ensureMasterIssue(config, branches); expect(platform.ensureIssueClosing).toHaveBeenCalledTimes(0); expect(platform.ensureIssue).toHaveBeenCalledTimes(1); expect(platform.ensureIssue.mock.calls[0][0].title).toBe( - config.masterIssueTitle + config.dependencyDashboardTitle ); expect(platform.ensureIssue.mock.calls[0][0].body).toBe( fs.readFileSync( diff --git a/lib/workers/repository/master-issue.ts b/lib/workers/repository/master-issue.ts index e5e47bbdaa..c80d728972 100644 --- a/lib/workers/repository/master-issue.ts +++ b/lib/workers/repository/master-issue.ts @@ -25,46 +25,53 @@ export async function ensureMasterIssue( config: RenovateConfig, branches: BranchConfig[] ): Promise<void> { + // legacy/migrated issue + const reuseTitle = 'Update Dependencies (Renovate Bot)'; if ( !( - config.masterIssue || + config.dependencyDashboard || branches.some( - (branch) => branch.masterIssueApproval || branch.masterIssuePrApproval + (branch) => + branch.dependencyDashboardApproval || + branch.dependencyDashboardPrApproval ) ) ) { return; } - logger.debug('Ensuring master issue'); + logger.debug('Ensuring Dependency Dashboard'); if ( !branches.length || branches.every((branch) => branch.res === 'automerged') ) { - if (config.masterIssueAutoclose) { - logger.debug('Closing master issue'); + if (config.dependencyDashboardAutoclose) { + logger.debug('Closing Dependency Dashboard'); if (config.dryRun) { logger.info( - 'DRY-RUN: Would close Master Issue ' + config.masterIssueTitle + 'DRY-RUN: Would close Dependency Dashboard ' + + config.dependencyDashboardTitle ); } else { - await platform.ensureIssueClosing(config.masterIssueTitle); + await platform.ensureIssueClosing(config.dependencyDashboardTitle); } return; } if (config.dryRun) { logger.info( - 'DRY-RUN: Would ensure Master Issue ' + config.masterIssueTitle + 'DRY-RUN: Would ensure Dependency Dashboard ' + + config.dependencyDashboardTitle ); } else { await platform.ensureIssue({ - title: config.masterIssueTitle, + title: config.dependencyDashboardTitle, + reuseTitle, body: 'This repository is up-to-date and has no outstanding updates open or pending.', }); } return; } - let issueBody = `This [master issue](https://renovatebot.com/blog/master-issue) contains a list of Renovate updates and their statuses.\n\n`; + let issueBody = `This issue contains a list of Renovate updates and their statuses.\n\n`; const pendingApprovals = branches.filter( (branch) => branch.res === 'needs-approval' ); @@ -200,11 +207,13 @@ export async function ensureMasterIssue( if (config.dryRun) { logger.info( - 'DRY-RUN: Would ensure Master Issue ' + config.masterIssueTitle + 'DRY-RUN: Would ensure Dependency Dashboard ' + + config.dependencyDashboardTitle ); } else { await platform.ensureIssue({ - title: config.masterIssueTitle, + title: config.dependencyDashboardTitle, + reuseTitle, body: issueBody, }); } diff --git a/lib/workers/repository/process/index.ts b/lib/workers/repository/process/index.ts index 8c672386e8..37e91c4c70 100644 --- a/lib/workers/repository/process/index.ts +++ b/lib/workers/repository/process/index.ts @@ -26,20 +26,21 @@ export async function extractDependencies( ): Promise<ExtractResult> { logger.debug('processRepo()'); /* eslint-disable no-param-reassign */ - config.masterIssueChecks = {}; + config.dependencyDashboardChecks = {}; // istanbul ignore next if ( - config.masterIssue || - config.masterIssueApproval || + config.dependencyDashboard || + config.dependencyDashboardApproval || config.prCreation === 'approval' || (config.packageRules && config.packageRules.some( - (rule) => rule.masterIssueApproval || rule.prCreation === 'approval' + (rule) => + rule.dependencyDashboardApproval || rule.prCreation === 'approval' )) ) { - config.masterIssueTitle = - config.masterIssueTitle || `Update Dependencies (Renovate Bot)`; - const issue = await platform.findIssue(config.masterIssueTitle); + config.dependencyDashboardTitle = + config.dependencyDashboardTitle || `Dependency Dashboard`; + const issue = await platform.findIssue(config.dependencyDashboardTitle); if (issue) { const checkMatch = ' - \\[x\\] <!-- ([a-zA-Z]+)-branch=([^\\s]+) -->'; const checked = issue.body.match(new RegExp(checkMatch, 'g')); @@ -47,14 +48,14 @@ export async function extractDependencies( const re = new RegExp(checkMatch); checked.forEach((check) => { const [, type, branchName] = re.exec(check); - config.masterIssueChecks[branchName] = type; + config.dependencyDashboardChecks[branchName] = type; }); } const checkedRebaseAll = issue.body.includes( ' - [x] <!-- rebase-all-open-prs -->' ); if (checkedRebaseAll) { - config.masterIssueRebaseAllOpen = true; + config.dependencyDashboardRebaseAllOpen = true; /* eslint-enable no-param-reassign */ } } diff --git a/lib/workers/repository/updates/__snapshots__/generate.spec.ts.snap b/lib/workers/repository/updates/__snapshots__/generate.spec.ts.snap index 2da948ad7a..471ace38db 100644 --- a/lib/workers/repository/updates/__snapshots__/generate.spec.ts.snap +++ b/lib/workers/repository/updates/__snapshots__/generate.spec.ts.snap @@ -14,10 +14,10 @@ Object { "canBeUnpublished": false, "commitMessage": "", "depName": "some-dep", + "dependencyDashboardApproval": false, + "dependencyDashboardPrApproval": false, "displayFrom": "", "displayTo": "0.6.0", - "masterIssueApproval": false, - "masterIssuePrApproval": false, "newValue": "0.6.0", "prTitle": "some-title", "prettyDepType": "dependency", @@ -75,12 +75,12 @@ Object { ", "datasource": "npm", "depName": "some-dep", + "dependencyDashboardApproval": false, + "dependencyDashboardPrApproval": false, "displayFrom": "", "displayTo": "0.6.0", "hasTypes": true, "isRange": false, - "masterIssueApproval": false, - "masterIssuePrApproval": false, "newValue": "0.6.0", "prTitle": "some-title", "prettyDepType": "dependency", diff --git a/lib/workers/repository/updates/generate.ts b/lib/workers/repository/updates/generate.ts index 4d4dc0874d..e9c552604a 100644 --- a/lib/workers/repository/updates/generate.ts +++ b/lib/workers/repository/updates/generate.ts @@ -298,10 +298,10 @@ export function generateBranchConfig( config.reuseLockFiles = config.upgrades.every( (upgrade) => upgrade.updateType !== 'lockFileMaintenance' ); - config.masterIssueApproval = config.upgrades.some( - (upgrade) => upgrade.masterIssueApproval + config.dependencyDashboardApproval = config.upgrades.some( + (upgrade) => upgrade.dependencyDashboardApproval ); - config.masterIssuePrApproval = config.upgrades.some( + config.dependencyDashboardPrApproval = config.upgrades.some( (upgrade) => upgrade.prCreation === 'approval' ); config.automerge = config.upgrades.every((upgrade) => upgrade.automerge); -- GitLab