From dcec25c2916cd75e26fe1d5f4a91bd0107afbef6 Mon Sep 17 00:00:00 2001 From: Rhys Arkins <rhys@arkins.net> Date: Fri, 21 Sep 2018 09:46:51 +0200 Subject: [PATCH] feat: customisable PR tables (#2544) Adds ability to both redefine column definitions in PRs as well as add or remove columns. --- lib/config/definitions.js | 20 +++ lib/workers/pr/pr-body.js | 124 ++++++++++-------- .../pr/__snapshots__/index.spec.js.snap | 20 +-- test/workers/pr/index.spec.js | 2 + .../__snapshots__/flatten.spec.js.snap | 120 +++++++++++++++++ website/docs/configuration-options.md | 52 ++++++++ 6 files changed, 270 insertions(+), 68 deletions(-) diff --git a/lib/config/definitions.js b/lib/config/definitions.js index eb360166c4..886a527d6a 100644 --- a/lib/config/definitions.js +++ b/lib/config/definitions.js @@ -1160,6 +1160,26 @@ const options = [ cli: true, mergeable: true, }, + { + name: 'prBodyDefinitions', + description: 'Table column definitions for use in PR tables', + type: 'object', + mergeable: true, + default: { + Package: '{{{depName}}}', + Type: '{{{depType}}}', + Update: '{{{updateType}}}', + 'New value': '{{{newValue}}}', + References: '{{{references}}}', + 'Package file': '{{{packageFile}}}', + }, + }, + { + name: 'prBodyColumns', + description: 'List of columns to use in PR bodies', + type: 'list', + default: ['Package', 'Type', 'Update', 'New value', 'References'], + }, ]; function getOptions() { diff --git a/lib/workers/pr/pr-body.js b/lib/workers/pr/pr-body.js index a54f982321..7d1fd16fcb 100644 --- a/lib/workers/pr/pr-body.js +++ b/lib/workers/pr/pr-body.js @@ -2,57 +2,38 @@ const handlebars = require('handlebars'); const releaseNotesHbs = require('./changelog/hbs-template'); module.exports = { - getUpdateHeaders, getPrBody, }; -function getUpdateHeaders(config) { - const updateHeaders = ['Package']; - if (config.upgrades.some(upgrade => upgrade.depType)) { - updateHeaders.push('Type'); +function getTableDefinition(config) { + const res = []; + for (const header of config.prBodyColumns) { + const value = config.prBodyDefinitions[header]; + res.push({ header, value }); } - updateHeaders.push('Update'); - updateHeaders.push('New value'); - if ( - config.upgrades.some( - upgrade => - upgrade.homepage || upgrade.repositoryUrl || upgrade.changelogUrl - ) - ) { - updateHeaders.push('References'); + return res; +} + +function getNonEmptyColumns(definitions, rows) { + const res = []; + for (const column of definitions) { + const { header } = column; + for (const row of rows) { + if (row[header] && row[header].length) { + if (!res.includes(header)) { + res.push(header); + } + } + } } - return updateHeaders; + return res; } async function getPrBody(config) { - let prBody = ''; - // istanbul ignore if - if (config.prBanner && !config.isGroup) { - prBody += handlebars.compile(config.prBanner)(config) + '\n\n'; - } - prBody += '\n\nThis PR contains the following updates:\n\n'; - const updateHeaders = getUpdateHeaders(config); - prBody += '| ' + updateHeaders.join(' | ') + ' |\n'; - prBody += '|' + updateHeaders.map(() => '--|').join('') + '\n'; - const seen = []; - for (const upgrade of config.upgrades) { - const { - depName, - depType, - updateType, - newValue, - newDigestShort, - homepage, - repositoryUrl, - changelogUrl, - } = upgrade; - const key = depName + depType + updateType + newValue; - if (seen.includes(key)) { - // don't have duplicate rows - continue; // eslint-disable-line no-continue - } - seen.push(key); - let references = []; + config.upgrades.forEach(upgrade => { + /* eslint-disable no-param-reassign */ + const { homepage, repositoryUrl, changelogUrl } = upgrade; + const references = []; if (homepage) { references.push(`[homepage](${homepage})`); } @@ -62,31 +43,58 @@ async function getPrBody(config) { if (changelogUrl) { references.push(`[changelog](${changelogUrl})`); } - references = references.join(', '); - let value = ''; + upgrade.references = references.join(', '); + const { newValue, newDigestShort, updateType } = upgrade; if (newDigestShort) { if (updateType === 'pin') { - value = config.newDigestShort; + upgrade.newValue = config.newDigestShort; } if (newValue) { - value = newValue + '@' + newDigestShort; + upgrade.newVaue = newValue + '@' + newDigestShort; } else { - value = newDigestShort; + upgrade.newValue = newDigestShort; } } else if (updateType !== 'lockFileMaintenance') { - value = newValue; + upgrade.newValue = newValue; + } + /* eslint-enable no-param-reassign */ + }); + const tableDefinitions = getTableDefinition(config); + const tableValues = config.upgrades.map(upgrade => { + const res = {}; + for (const column of tableDefinitions) { + const { header, value } = column; + try { + res[header] = handlebars + .compile(value)(upgrade) + .replace(/^``$/, ''); + } catch (err) /* istanbul ignore next */ { + logger.info({ header, value, err }, 'Handlebars compilation error'); + } } - const name = - upgrade.updateType === 'lockFileMaintenance' - ? 'all' - : '`' + depName + '`'; - // prettier-ignore - prBody += `| ${name} | ${updateHeaders.includes('Type') ? depType + ' |' : ''} ${updateType} | ${value} |`; - if (updateHeaders.includes('References')) { - prBody += references + ' |'; + return res; + }); + const tableColumns = getNonEmptyColumns(tableDefinitions, tableValues); + logger.info({ tableDefinitions, tableValues, tableColumns }); + let prBody = ''; + // istanbul ignore if + if (config.prBanner && !config.isGroup) { + prBody += handlebars.compile(config.prBanner)(config) + '\n\n'; + } + prBody += '\n\nThis PR contains the following updates:\n\n'; + prBody += '| ' + tableColumns.join(' | ') + ' |\n'; + prBody += '|' + tableColumns.map(() => '--|').join('') + '\n'; + const rows = []; + for (const row of tableValues) { + let val = '|'; + for (const column of tableColumns) { + val += ` ${row[column]} |`; } - prBody += '\n'; + val += '\n'; + rows.push(val); } + const uniqueRows = [...new Set(rows)]; + prBody += uniqueRows.join(''); prBody += '\n\n'; if (config.upgrades.some(upgrade => upgrade.gitRef)) { diff --git a/test/workers/pr/__snapshots__/index.spec.js.snap b/test/workers/pr/__snapshots__/index.spec.js.snap index a0318deec0..69a8009cf3 100644 --- a/test/workers/pr/__snapshots__/index.spec.js.snap +++ b/test/workers/pr/__snapshots__/index.spec.js.snap @@ -32,7 +32,7 @@ Array [ | Package | Type | Update | New value | References | |--|--|--|--|--| -| \`dummy\` | devDependencies | undefined | 1.1.0 |[homepage](https://dummy.com), [source](https://github.com/renovateapp/dummy), [changelog](https://github.com/renovateapp/dummy/changelog.md) | +| dummy | devDependencies | pin | 1.1.0 | [homepage](https://dummy.com), [source](https://github.com/renovateapp/dummy), [changelog](https://github.com/renovateapp/dummy/changelog.md) | :pushpin: **Important**: Renovate will wait until you have merged this Pin PR before creating any *upgrade* PRs for the affected packages. Add the preset \`:preserveSemverRanges\` your config if you instead don't wish to pin dependencies. @@ -74,7 +74,7 @@ Array [ | Package | Type | Update | New value | References | |--|--|--|--|--| -| \`dummy\` | devDependencies | undefined | 1.1.0 |[homepage](https://dummy.com), [source](https://github.com/renovateapp/dummy), [changelog](https://github.com/renovateapp/dummy/changelog.md) | +| dummy | devDependencies | minor | 1.1.0 | [homepage](https://dummy.com), [source](https://github.com/renovateapp/dummy), [changelog](https://github.com/renovateapp/dummy/changelog.md) | --- @@ -114,11 +114,11 @@ Array [ | Package | Type | Update | New value | References | |--|--|--|--|--| -| all | devDependencies | lockFileMaintenance | |[homepage](https://dummy.com), [source](https://github.com/renovateapp/dummy), [changelog](https://github.com/renovateapp/dummy/changelog.md) | -| \`a\` | undefined | undefined | aaaaaaa | | -| \`b\` | undefined | pin | some_new_value@bbbbbbb | | -| \`c\` | undefined | undefined | undefined | | -| all | undefined | lockFileMaintenance | | | +| dummy | devDependencies | lockFileMaintenance | 1.1.0 | [homepage](https://dummy.com), [source](https://github.com/renovateapp/dummy), [changelog](https://github.com/renovateapp/dummy/changelog.md) | +| a | | | aaaaaaa | | +| b | | pin | | | +| c | | | | | +| d | | lockFileMaintenance | | | :abcd: If you wish to disable git hash updates, add \`\\":disableDigestUpdates\\"\` to the extends array in your config. @@ -167,7 +167,7 @@ Array [ | Package | Type | Update | New value | References | |--|--|--|--|--| -| \`dummy\` | devDependencies | undefined | 1.1.0 |[homepage](https://dummy.com), [source](https://github.com/renovateapp/dummy), [changelog](https://github.com/renovateapp/dummy/changelog.md) | +| dummy | devDependencies | minor | 1.1.0 | [homepage](https://dummy.com), [source](https://github.com/renovateapp/dummy), [changelog](https://github.com/renovateapp/dummy/changelog.md) | --- @@ -205,7 +205,7 @@ Object { | Package | Type | Update | New value | References | |--|--|--|--|--| -| \`dummy\` | devDependencies | undefined | 1.1.0 |[homepage](https://dummy.com), [source](https://github.com/renovateapp/dummy), [changelog](https://github.com/renovateapp/dummy/changelog.md) | +| dummy | devDependencies | minor | 1.1.0 | [homepage](https://dummy.com), [source](https://github.com/renovateapp/dummy), [changelog](https://github.com/renovateapp/dummy/changelog.md) | --- @@ -243,7 +243,7 @@ Object { | Package | Type | Update | New value | References | |--|--|--|--|--| -| \`dummy\` | devDependencies | undefined | 1.1.0 |[homepage](https://dummy.com), [source](https://github.com/renovateapp/dummy), [changelog](https://github.com/renovateapp/dummy/changelog.md) | +| dummy | devDependencies | minor | 1.1.0 | [homepage](https://dummy.com), [source](https://github.com/renovateapp/dummy), [changelog](https://github.com/renovateapp/dummy/changelog.md) | --- diff --git a/test/workers/pr/index.spec.js b/test/workers/pr/index.spec.js index 1f42eba6fb..c1f947352b 100644 --- a/test/workers/pr/index.spec.js +++ b/test/workers/pr/index.spec.js @@ -117,6 +117,7 @@ describe('workers/pr', () => { config.privateRepo = true; config.currentValue = '1.0.0'; config.newValue = '1.1.0'; + config.updateType = 'minor'; config.homepage = 'https://dummy.com'; config.repositoryUrl = 'https://github.com/renovateapp/dummy'; config.changelogUrl = 'https://github.com/renovateapp/dummy/changelog.md'; @@ -183,6 +184,7 @@ describe('workers/pr', () => { platform.getBranchStatus.mockReturnValueOnce('success'); config.prCreation = 'status-success'; config.isPin = true; + config.updateType = 'pin'; config.schedule = 'before 5am'; config.timezone = 'some timezone'; config.rebaseStalePrs = true; diff --git a/test/workers/repository/updates/__snapshots__/flatten.spec.js.snap b/test/workers/repository/updates/__snapshots__/flatten.spec.js.snap index 3aaf8a1f59..e85bd437dd 100644 --- a/test/workers/repository/updates/__snapshots__/flatten.spec.js.snap +++ b/test/workers/repository/updates/__snapshots__/flatten.spec.js.snap @@ -40,6 +40,21 @@ Array [ "npmrc": null, "packageFile": "package.json", "platform": "github", + "prBodyColumns": Array [ + "Package", + "Type", + "Update", + "New value", + "References", + ], + "prBodyDefinitions": Object { + "New value": "{{{newValue}}}", + "Package": "{{{depName}}}", + "Package file": "{{{packageFile}}}", + "References": "{{{references}}}", + "Type": "{{{depType}}}", + "Update": "{{{updateType}}}", + }, "prConcurrentLimit": 0, "prCreation": "immediate", "prHourlyLimit": 0, @@ -111,6 +126,21 @@ Array [ "npmrc": null, "packageFile": "package.json", "platform": "github", + "prBodyColumns": Array [ + "Package", + "Type", + "Update", + "New value", + "References", + ], + "prBodyDefinitions": Object { + "New value": "{{{newValue}}}", + "Package": "{{{depName}}}", + "Package file": "{{{packageFile}}}", + "References": "{{{references}}}", + "Type": "{{{depType}}}", + "Update": "{{{updateType}}}", + }, "prConcurrentLimit": 0, "prCreation": "immediate", "prHourlyLimit": 0, @@ -179,6 +209,21 @@ Array [ "npmrc": null, "packageFile": "package.json", "platform": "github", + "prBodyColumns": Array [ + "Package", + "Type", + "Update", + "New value", + "References", + ], + "prBodyDefinitions": Object { + "New value": "{{{newValue}}}", + "Package": "{{{depName}}}", + "Package file": "{{{packageFile}}}", + "References": "{{{references}}}", + "Type": "{{{depType}}}", + "Update": "{{{updateType}}}", + }, "prConcurrentLimit": 0, "prCreation": "immediate", "prHourlyLimit": 0, @@ -253,6 +298,21 @@ Array [ "npmrc": null, "packageFile": "backend/package.json", "platform": "github", + "prBodyColumns": Array [ + "Package", + "Type", + "Update", + "New value", + "References", + ], + "prBodyDefinitions": Object { + "New value": "{{{newValue}}}", + "Package": "{{{depName}}}", + "Package file": "{{{packageFile}}}", + "References": "{{{references}}}", + "Type": "{{{depType}}}", + "Update": "{{{updateType}}}", + }, "prConcurrentLimit": 0, "prCreation": "immediate", "prHourlyLimit": 0, @@ -321,6 +381,21 @@ Array [ "npmrc": null, "packageFile": "backend/package.json", "platform": "github", + "prBodyColumns": Array [ + "Package", + "Type", + "Update", + "New value", + "References", + ], + "prBodyDefinitions": Object { + "New value": "{{{newValue}}}", + "Package": "{{{depName}}}", + "Package file": "{{{packageFile}}}", + "References": "{{{references}}}", + "Type": "{{{depType}}}", + "Update": "{{{updateType}}}", + }, "prConcurrentLimit": 0, "prCreation": "immediate", "prHourlyLimit": 0, @@ -395,6 +470,21 @@ Array [ "npmrc": null, "packageFile": "frontend/package.json", "platform": "github", + "prBodyColumns": Array [ + "Package", + "Type", + "Update", + "New value", + "References", + ], + "prBodyDefinitions": Object { + "New value": "{{{newValue}}}", + "Package": "{{{depName}}}", + "Package file": "{{{packageFile}}}", + "References": "{{{references}}}", + "Type": "{{{depType}}}", + "Update": "{{{updateType}}}", + }, "prConcurrentLimit": 0, "prCreation": "immediate", "prHourlyLimit": 0, @@ -466,6 +556,21 @@ Array [ "npmrc": null, "packageFile": "Dockerfile", "platform": "github", + "prBodyColumns": Array [ + "Package", + "Type", + "Update", + "New value", + "References", + ], + "prBodyDefinitions": Object { + "New value": "{{{newValue}}}", + "Package": "{{{depName}}}", + "Package file": "{{{packageFile}}}", + "References": "{{{references}}}", + "Type": "{{{depType}}}", + "Update": "{{{updateType}}}", + }, "prConcurrentLimit": 0, "prCreation": "immediate", "prHourlyLimit": 0, @@ -537,6 +642,21 @@ Array [ "npmrc": null, "packageFile": "Dockerfile", "platform": "github", + "prBodyColumns": Array [ + "Package", + "Type", + "Update", + "New value", + "References", + ], + "prBodyDefinitions": Object { + "New value": "{{{newValue}}}", + "Package": "{{{depName}}}", + "Package file": "{{{packageFile}}}", + "References": "{{{references}}}", + "Type": "{{{depType}}}", + "Update": "{{{updateType}}}", + }, "prConcurrentLimit": 0, "prCreation": "immediate", "prHourlyLimit": 0, diff --git a/website/docs/configuration-options.md b/website/docs/configuration-options.md index fb08523b5c..978ed72ae5 100644 --- a/website/docs/configuration-options.md +++ b/website/docs/configuration-options.md @@ -543,6 +543,58 @@ By default, Renovate will add sha256 digests to Docker source images so that the Add configuration here to specifically override settings for `pip` requirements files. Supports `requirements.txt` and `requirements.pip` files. The default file pattern is fairly flexible in an attempt to catch similarly named ones too but may be extended/changed. +## prBodyColumns + +Use this array to provide a list of column names you wish to include in the PR tables. + +For example, if you wish to add the package file name to the table, you would add this to your config: + +```json +{ + "prBodyColumns": [ + "Package", + "Update", + "Type", + "New value", + "Package file", + "References" + ] +} +``` + +Note: "Package file" is predefined in the default `prBodyDefinitions` object so does not require a definition before it can be used. + +## prBodyDefinitions + +You can configure this object if you with to either (a) modify the template for an existing table column in PR bodies, or (b) you wish to _add_ a definition for a new/additional column. + +Here is an example of modifying the default value for the "Package" column to put it inside a `<code></code>` block: + +```json + "prBodyDefinitions": { + "Package": "`{{{depName}}}`" + } +``` + +Here is an example of adding a custom "Sourcegraph" column definition: + +```json +{ + "prBodyDefinitions": { + "Sourcegraph": "[](https://sourcegraph.com/search?q=repo:%5Egithub%5C.com/{{{repository}}}%24+case:yes+-file:package%28-lock%29%3F%5C.json+{{{depName}}})" + }, + "prBodyColumns": [ + "Package", + "Update", + "New value", + "References", + "Sourcegraph" + ] +} +``` + +Note: Columns must also be included in the `prBodyColumns` array in order to be used, so that's why it's included above in the example. + ## prConcurrentLimit This setting - if enabled - limits Renovate to a maximum of x concurrent PRs open at any time. -- GitLab