diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index 5113beeb7eabb322b8739e2431a079c031ba9d8a..315d8fe7a89413f2032e3dc814d534b9595731da 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 50add5714023bdd88b7fca012104f2e05461d4a4..1f7c3e633376d49b15c75a283e2c510960393e74 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 a5537c876beb6b44573df57b4a8ddff28dae9420..bf0f91aabd156022925bebd869328fe79fefb825 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 10b6eed37baf637f4fae58f5b51210b792bf8965..974515f4741a9b93104f7222699b7196e64b5486 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 82e3cc4e0ef18a689e67dd16bacf490f48273ae4..ef5b454e0f911068e5238b734df2ea2b638f1301 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 4c82f73aa2d0ae4e1037b62ddd5ed462a2099922..17637d55528f65aadb5b2048e49980dcf80d9d86 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 44594e625a74a59eedc5435aeee4a496562d0645..f45ba82d608de502e378deebcb00515b2ce4079f 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 079e613b92085184fac29ee273c28e3146f8eb3e..e03985838b955e2ca9bf1c1bcb75937db2a6ef7a 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 d8d0869a56c6be9639bbadd37d7629d74048ca79..ceec9eef576d62e90b2a616cc21e4da71854ffb8 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 6217dabd0a7ec74531c9982765343dc73b8c3bf0..6e44674dc37ce7d96b77400c7b691652f080a08d 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 b53516bb7f25bb780ce1198b4aa397633b394936..052f0c092a30fbe599d36ecb581edbf6c0df9127 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 11e7dd9de0b1e7afec6c23027fff8acc1bb323ba..7cbf3595cf19499d350930ee2528ddf5a59288e8 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 a3eb028734f0a755d50298ce42e90f3d9ad645e0..d1810a68c2a42f7e17e7e1553f52caa01c0ebbeb 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 5dd9eb2c6c5f567256219f367f6cd86b195cc9f5..ecc41bad9f8872cc5d26b8c51113e7e5e5d656f4 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 fda834bf60e8abb82985343d4d9d6148241ba631..baf161ff74522d15352368cadd86c4696db39727 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 9f90ba48fdec68468c6a3ccc668f113908d0662a..c08cf42b866132183afcb3b3344529936e14e965 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 46317be6153b8ab119c70a6c9935cb2639e78f8f..824c90eda19017a6f3d9eafbeddeb53d4d28610f 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 1ec0d3cee996317b0d4f04aa3562007804a4ad70..a9050579770071360702465cd379c5d1eadb2248 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 e40f141aaa13777722dd8fa14999e556cce322e0..f7967bc0a1c0357de29f73eddd5098c321755263 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 52f5ed5a33674f20dbf05a8e8a8e8dea5ce79e00..cd8f65224176e457ad4a4c5a8f75a7e368a362f1 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 47f474ebbf23bb6b928ff79ee7566b1e9d1af700..5082cb40eb8c1e89af8e81326826aad8dce97f4d 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 2a36a600ecf6904388d7945955adf72572a40806..df9939baedec779e8fe93e1b91c498bc4f266c75 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 70405d8394ce560fe90de4c532a0f8ad11208a1c..c50b153aa39f631f54116d248bb6f4b10024cb48 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 e0eba00ed767f8053ed4c96625b9f7d089f1e100..1330d436491315f50bbcbb8b3b18d06e56621c66 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 8a6f9567a2e21d169fc30936ec91b66b5f4411ac..a3c51068a96acd0492c1803cd1f3c2803fca1c4c 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 1aaf4c612fe5cd22ae80234be8c018d9aa619b0a..08cbe35d31442b88d4959ffec8867d4c8dae4c30 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 301dea48ea2eefd81b2b855c891c843731d64b82..37d2fdc5b0d067fbfcf19fb4604bb4cf1ccd21e9 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 0fc49186c1cb90dabfa502fdf37dc04579d34d70..5bcb226c2e0c916a901be9a025750daf8a6db058 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 39f0bff86c8acbe1d573150a716147eb54c1ad42..fede9288983e0f39d155e79c28b52f37a052f0e0 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 00c39bbcfdd1cd3f27edc5a5af394db5996b1696..6ddab59c0f5f304088a1660c347e99790483d3b7 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 e5e47bbdaac5fd724e3db5e14ec38588f13f756c..c80d7289728048e0cfe4b087de3f976a3e930ba0 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 8c672386e8dfb2136cd0bf1a9cc198c9f1d9b5b3..37e91c4c7022fe6f77d60025ef7c10bafcb11fc6 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 2da948ad7a471cf3b8f0a2e44dcb1494f6fc24da..471ace38dbf36b06ec79163d943661ed10b21c80 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 4d4dc0874ddc49cf5f7815b0c8520a3c54ba5a12..e9c552604aa31c373b0b67398ef7a9a60775445b 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);