mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
* Remove the no longer used release-notes script * Commit missing file Co-authored-by: Tim Roes <tim.roes@elastic.co>
This commit is contained in:
parent
654548f6ad
commit
59efc977b7
25 changed files with 10 additions and 1508 deletions
|
@ -1,5 +1,4 @@
|
|||
**/*.js.snap
|
||||
**/graphql/types.ts
|
||||
/.es
|
||||
/.chromium
|
||||
/build
|
||||
|
|
24
package.json
24
package.json
|
@ -74,7 +74,6 @@
|
|||
"**/deepmerge": "^4.2.2",
|
||||
"**/fast-deep-equal": "^3.1.1",
|
||||
"globby/fast-glob": "3.2.5",
|
||||
"**/graphql-toolkit/lodash": "^4.17.21",
|
||||
"**/hoist-non-react-statics": "^3.3.2",
|
||||
"**/isomorphic-fetch/node-fetch": "^2.6.1",
|
||||
"**/istanbul-instrumenter-loader/schema-utils": "1.0.0",
|
||||
|
@ -190,10 +189,10 @@
|
|||
"compare-versions": "3.5.1",
|
||||
"concat-stream": "1.6.2",
|
||||
"constate": "^1.3.2",
|
||||
"cronstrue": "^1.51.0",
|
||||
"content-disposition": "0.5.3",
|
||||
"copy-to-clipboard": "^3.0.8",
|
||||
"core-js": "^3.6.5",
|
||||
"cronstrue": "^1.51.0",
|
||||
"cytoscape": "^3.10.0",
|
||||
"cytoscape-dagre": "^2.2.2",
|
||||
"d3": "3.5.17",
|
||||
|
@ -228,8 +227,6 @@
|
|||
"glob": "^7.1.2",
|
||||
"glob-all": "^3.2.1",
|
||||
"globby": "^11.0.3",
|
||||
"graphql": "^0.13.2",
|
||||
"graphql-tag": "^2.10.3",
|
||||
"handlebars": "4.7.7",
|
||||
"he": "^1.2.0",
|
||||
"history": "^4.9.0",
|
||||
|
@ -271,9 +268,9 @@
|
|||
"lodash": "^4.17.21",
|
||||
"lru-cache": "^4.1.5",
|
||||
"lz-string": "^1.4.4",
|
||||
"markdown-it": "^10.0.0",
|
||||
"mapbox-gl": "1.13.1",
|
||||
"mapbox-gl-draw-rectangle-mode": "^1.0.4",
|
||||
"markdown-it": "^10.0.0",
|
||||
"md5": "^2.1.0",
|
||||
"memoize-one": "^5.0.0",
|
||||
"mime": "^2.4.4",
|
||||
|
@ -295,12 +292,12 @@
|
|||
"object-path-immutable": "^3.1.1",
|
||||
"opn": "^5.5.0",
|
||||
"oppsy": "^2.0.0",
|
||||
"p-limit": "^3.0.1",
|
||||
"p-map": "^4.0.0",
|
||||
"p-retry": "^4.2.0",
|
||||
"papaparse": "^5.2.0",
|
||||
"pdfmake": "^0.1.65",
|
||||
"pegjs": "0.10.0",
|
||||
"p-limit": "^3.0.1",
|
||||
"pluralize": "3.1.0",
|
||||
"pngjs": "^3.4.0",
|
||||
"polished": "^1.9.2",
|
||||
|
@ -332,19 +329,19 @@
|
|||
"react-monaco-editor": "^0.41.2",
|
||||
"react-popper-tooltip": "^2.10.1",
|
||||
"react-query": "^3.13.10",
|
||||
"react-redux": "^7.2.0",
|
||||
"react-resizable": "^1.7.5",
|
||||
"react-resize-detector": "^4.2.0",
|
||||
"react-reverse-portal": "^1.0.4",
|
||||
"react-router": "^5.2.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-router-redux": "^4.0.8",
|
||||
"react-shortcuts": "^2.0.0",
|
||||
"react-sizeme": "^2.3.6",
|
||||
"react-syntax-highlighter": "^15.3.1",
|
||||
"react-redux": "^7.2.0",
|
||||
"react-resizable": "^1.7.5",
|
||||
"react-router": "^5.2.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-tiny-virtual-list": "^2.2.0",
|
||||
"react-virtualized": "^9.21.2",
|
||||
"react-use": "^15.3.8",
|
||||
"react-virtualized": "^9.21.2",
|
||||
"react-vis": "^1.8.1",
|
||||
"react-visibility-sensor": "^5.1.1",
|
||||
"reactcss": "1.2.3",
|
||||
|
@ -373,8 +370,8 @@
|
|||
"strip-ansi": "^6.0.0",
|
||||
"style-it": "^2.1.3",
|
||||
"styled-components": "^5.1.0",
|
||||
"symbol-observable": "^1.2.0",
|
||||
"suricata-sid-db": "^1.0.2",
|
||||
"symbol-observable": "^1.2.0",
|
||||
"tabbable": "1.1.3",
|
||||
"tar": "4.4.13",
|
||||
"tinycolor2": "1.4.1",
|
||||
|
@ -518,7 +515,6 @@
|
|||
"@types/getos": "^3.0.0",
|
||||
"@types/git-url-parse": "^9.0.0",
|
||||
"@types/glob": "^7.1.2",
|
||||
"@types/graphql": "^0.13.2",
|
||||
"@types/gulp": "^4.0.6",
|
||||
"@types/gulp-zip": "^4.0.1",
|
||||
"@types/hapi__cookie": "^10.1.1",
|
||||
|
@ -732,8 +728,8 @@
|
|||
"jest-circus": "^26.6.3",
|
||||
"jest-cli": "^26.6.3",
|
||||
"jest-diff": "^26.6.2",
|
||||
"jest-environment-jsdom-thirteen": "^1.0.1",
|
||||
"jest-environment-jsdom": "^26.6.2",
|
||||
"jest-environment-jsdom-thirteen": "^1.0.1",
|
||||
"jest-raw-loader": "^1.0.1",
|
||||
"jest-silent-reporter": "^0.2.1",
|
||||
"jest-snapshot": "^26.6.2",
|
||||
|
|
|
@ -6,5 +6,4 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export * from './release_notes';
|
||||
export * from './api_docs';
|
||||
|
|
|
@ -1,152 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import Fs from 'fs';
|
||||
import Path from 'path';
|
||||
import { inspect } from 'util';
|
||||
|
||||
import { REPO_ROOT } from '@kbn/utils';
|
||||
import { run, createFlagError, createFailError } from '@kbn/dev-utils';
|
||||
|
||||
import { FORMATS, SomeFormat } from './formats';
|
||||
import {
|
||||
PrApi,
|
||||
Version,
|
||||
ClassifiedPr,
|
||||
streamFromIterable,
|
||||
asyncPipeline,
|
||||
IrrelevantPrSummary,
|
||||
isPrRelevant,
|
||||
classifyPr,
|
||||
} from './lib';
|
||||
|
||||
const rootPackageJson = JSON.parse(
|
||||
Fs.readFileSync(Path.resolve(REPO_ROOT, 'package.json'), 'utf8')
|
||||
);
|
||||
const extensions = FORMATS.map((f) => f.extension);
|
||||
|
||||
export function runReleaseNotesCli() {
|
||||
run(
|
||||
async ({ flags, log }) => {
|
||||
const token = flags.token;
|
||||
if (!token || typeof token !== 'string') {
|
||||
throw createFlagError('--token must be defined');
|
||||
}
|
||||
const prApi = new PrApi(log, token);
|
||||
|
||||
const version = Version.fromFlag(flags.version);
|
||||
if (!version) {
|
||||
throw createFlagError('unable to parse --version, use format "v{major}.{minor}.{patch}"');
|
||||
}
|
||||
|
||||
const includeVersions = Version.fromFlags(flags.include || []);
|
||||
if (!includeVersions) {
|
||||
throw createFlagError('unable to parse --include, use format "v{major}.{minor}.{patch}"');
|
||||
}
|
||||
|
||||
const Formats: SomeFormat[] = [];
|
||||
for (const flag of Array.isArray(flags.format) ? flags.format : [flags.format]) {
|
||||
const Format = FORMATS.find((F) => F.extension === flag);
|
||||
if (!Format) {
|
||||
throw createFlagError(`--format must be one of "${extensions.join('", "')}"`);
|
||||
}
|
||||
Formats.push(Format);
|
||||
}
|
||||
|
||||
const filename = flags.filename;
|
||||
if (!filename || typeof filename !== 'string') {
|
||||
throw createFlagError('--filename must be a string');
|
||||
}
|
||||
|
||||
if (flags['debug-pr']) {
|
||||
const number = parseInt(String(flags['debug-pr']), 10);
|
||||
if (Number.isNaN(number)) {
|
||||
throw createFlagError('--debug-pr must be a pr number when specified');
|
||||
}
|
||||
|
||||
const summary = new IrrelevantPrSummary(log);
|
||||
const pr = await prApi.getPr(number);
|
||||
log.success(
|
||||
inspect(
|
||||
{
|
||||
version: version.label,
|
||||
includeVersions: includeVersions.map((v) => v.label),
|
||||
isPrRelevant: isPrRelevant(pr, version, includeVersions, summary),
|
||||
...classifyPr(pr, log),
|
||||
pr,
|
||||
},
|
||||
{ depth: 100 }
|
||||
)
|
||||
);
|
||||
summary.logStats();
|
||||
return;
|
||||
}
|
||||
|
||||
log.info(`Loading all PRs with label [${version.label}] to build release notes...`);
|
||||
|
||||
const summary = new IrrelevantPrSummary(log);
|
||||
const prsToReport: ClassifiedPr[] = [];
|
||||
const prIterable = prApi.iterRelevantPullRequests(version);
|
||||
for await (const pr of prIterable) {
|
||||
if (!isPrRelevant(pr, version, includeVersions, summary)) {
|
||||
continue;
|
||||
}
|
||||
prsToReport.push(classifyPr(pr, log));
|
||||
}
|
||||
summary.logStats();
|
||||
|
||||
if (!prsToReport.length) {
|
||||
throw createFailError(
|
||||
`All PRs with label [${version.label}] were filtered out by the config. Run again with --debug for more info.`
|
||||
);
|
||||
}
|
||||
|
||||
log.info(`Found ${prsToReport.length} prs to report on`);
|
||||
|
||||
for (const Format of Formats) {
|
||||
const format = new Format(version, prsToReport, log);
|
||||
const outputPath = Path.resolve(`${filename}.${Format.extension}`);
|
||||
await asyncPipeline(streamFromIterable(format.print()), Fs.createWriteStream(outputPath));
|
||||
log.success(`[${Format.extension}] report written to ${outputPath}`);
|
||||
}
|
||||
},
|
||||
{
|
||||
usage: `node scripts/release_notes --token {token} --version {version}`,
|
||||
flags: {
|
||||
alias: {
|
||||
version: 'v',
|
||||
include: 'i',
|
||||
},
|
||||
string: ['token', 'version', 'format', 'filename', 'include', 'debug-pr'],
|
||||
default: {
|
||||
filename: 'report',
|
||||
version: rootPackageJson.version,
|
||||
format: extensions,
|
||||
},
|
||||
help: `
|
||||
--token (required) The Github access token to use for requests
|
||||
--version, -v The version to fetch PRs by, PRs with version labels prior to
|
||||
this one will be ignored (see --include-version) (default ${
|
||||
rootPackageJson.version
|
||||
})
|
||||
--include, -i A version that is before --version but shouldn't be considered
|
||||
"released" and cause PRs with a matching label to be excluded from
|
||||
release notes. Use this when PRs are labeled with a version that
|
||||
is less that --version and is expected to be released after
|
||||
--version, can be specified multiple times.
|
||||
--format Only produce a certain format, options: "${extensions.join('", "')}"
|
||||
--filename Output filename, defaults to "report"
|
||||
--debug-pr Fetch and print the details for a single PR, disabling reporting
|
||||
`,
|
||||
},
|
||||
description: `
|
||||
Fetch details from Github PRs for generating release notes
|
||||
`,
|
||||
}
|
||||
);
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import dedent from 'dedent';
|
||||
|
||||
import { Format } from './format';
|
||||
import {
|
||||
ASCIIDOC_SECTIONS,
|
||||
UNKNOWN_ASCIIDOC_SECTION,
|
||||
AREAS,
|
||||
UNKNOWN_AREA,
|
||||
} from '../release_notes_config';
|
||||
|
||||
function* lines(body: string) {
|
||||
for (const line of dedent(body).split('\n')) {
|
||||
yield `${line}\n`;
|
||||
}
|
||||
}
|
||||
|
||||
export class AsciidocFormat extends Format {
|
||||
static extension = 'asciidoc';
|
||||
|
||||
*print() {
|
||||
const sortedAreas = [
|
||||
...AREAS.slice().sort((a, b) => a.title.localeCompare(b.title)),
|
||||
UNKNOWN_AREA,
|
||||
];
|
||||
|
||||
yield* lines(`
|
||||
[[release-notes-${this.version.label}]]
|
||||
== ${this.version.label} Release Notes
|
||||
|
||||
Also see <<breaking-changes-${this.version.major}.${this.version.minor}>>.
|
||||
`);
|
||||
|
||||
for (const section of [...ASCIIDOC_SECTIONS, UNKNOWN_ASCIIDOC_SECTION]) {
|
||||
const prsInSection = this.prs.filter((pr) => pr.asciidocSection === section);
|
||||
if (!prsInSection.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
yield '\n';
|
||||
yield* lines(`
|
||||
[float]
|
||||
[[${section.id}-${this.version.label}]]
|
||||
=== ${section.title}
|
||||
`);
|
||||
|
||||
for (const area of sortedAreas) {
|
||||
const prsInArea = prsInSection.filter((pr) => pr.area === area);
|
||||
|
||||
if (!prsInArea.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
yield `${area.title}::\n`;
|
||||
for (const pr of prsInArea) {
|
||||
const fixes = pr.fixes.length ? `[Fixes ${pr.fixes.join(', ')}] ` : '';
|
||||
const strippedTitle = pr.title.replace(/^\s*\[[^\]]+\]\s*/, '');
|
||||
yield `* ${fixes}${strippedTitle} {kibana-pull}${pr.number}[#${pr.number}]\n`;
|
||||
if (pr.note) {
|
||||
yield ` - ${pr.note}\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { Format } from './format';
|
||||
|
||||
/**
|
||||
* Escape a value to conform to field and header encoding defined at https://tools.ietf.org/html/rfc4180
|
||||
*/
|
||||
function esc(value: string | number) {
|
||||
if (typeof value === 'number') {
|
||||
return String(value);
|
||||
}
|
||||
|
||||
if (!value.includes(',') && !value.includes('\n') && !value.includes('"')) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return `"${value.split('"').join('""')}"`;
|
||||
}
|
||||
|
||||
function row(...fields: Array<string | number>) {
|
||||
return fields.map(esc).join(',') + '\r\n';
|
||||
}
|
||||
|
||||
export class CsvFormat extends Format {
|
||||
static extension = 'csv';
|
||||
|
||||
*print() {
|
||||
// columns
|
||||
yield row(
|
||||
'areas',
|
||||
'versions',
|
||||
'user',
|
||||
'title',
|
||||
'number',
|
||||
'url',
|
||||
'date',
|
||||
'fixes',
|
||||
'labels',
|
||||
'state'
|
||||
);
|
||||
|
||||
for (const pr of this.prs) {
|
||||
yield row(
|
||||
pr.area.title,
|
||||
pr.versions.map((v) => v.label).join(', '),
|
||||
pr.user.name || pr.user.login,
|
||||
pr.title,
|
||||
pr.number,
|
||||
pr.url,
|
||||
pr.mergedAt,
|
||||
pr.fixes.join(', '),
|
||||
pr.labels.join(', '),
|
||||
pr.state
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ToolingLog } from '@kbn/dev-utils';
|
||||
|
||||
import { Version, ClassifiedPr } from '../lib';
|
||||
|
||||
export abstract class Format {
|
||||
static extension: string;
|
||||
|
||||
constructor(
|
||||
protected readonly version: Version,
|
||||
protected readonly prs: ClassifiedPr[],
|
||||
protected readonly log: ToolingLog
|
||||
) {}
|
||||
|
||||
abstract print(): Iterator<string>;
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ArrayItem } from '../lib';
|
||||
import { AsciidocFormat } from './asciidoc';
|
||||
import { CsvFormat } from './csv';
|
||||
|
||||
export const FORMATS = [CsvFormat, AsciidocFormat] as const;
|
||||
export type SomeFormat = ArrayItem<typeof FORMATS>;
|
|
@ -1,9 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export * from './cli';
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ToolingLog } from '@kbn/dev-utils';
|
||||
|
||||
import {
|
||||
Area,
|
||||
AREAS,
|
||||
UNKNOWN_AREA,
|
||||
AsciidocSection,
|
||||
ASCIIDOC_SECTIONS,
|
||||
UNKNOWN_ASCIIDOC_SECTION,
|
||||
} from '../release_notes_config';
|
||||
import { PullRequest } from './pr_api';
|
||||
|
||||
export interface ClassifiedPr extends PullRequest {
|
||||
area: Area;
|
||||
asciidocSection: AsciidocSection;
|
||||
}
|
||||
|
||||
export function classifyPr(pr: PullRequest, log: ToolingLog): ClassifiedPr {
|
||||
const filter = (a: Area | AsciidocSection) =>
|
||||
a.labels.some((test) =>
|
||||
typeof test === 'string' ? pr.labels.includes(test) : pr.labels.some((l) => l.match(test))
|
||||
);
|
||||
|
||||
const areas = AREAS.filter(filter);
|
||||
const asciidocSections = ASCIIDOC_SECTIONS.filter(filter);
|
||||
|
||||
const pickOne = <T extends Area | AsciidocSection>(name: string, options: T[]) => {
|
||||
if (options.length > 1) {
|
||||
const matches = options.map((o) => o.title).join(', ');
|
||||
log.warning(`[${pr.terminalLink}] ambiguous ${name}, mulitple match [${matches}]`);
|
||||
return options[0];
|
||||
}
|
||||
|
||||
if (options.length === 0) {
|
||||
log.error(`[${pr.terminalLink}] unable to determine ${name} because none match`);
|
||||
return;
|
||||
}
|
||||
|
||||
return options[0];
|
||||
};
|
||||
|
||||
return {
|
||||
...pr,
|
||||
area: pickOne('area', areas) || UNKNOWN_AREA,
|
||||
asciidocSection: pickOne('asciidoc section', asciidocSections) || UNKNOWN_ASCIIDOC_SECTION,
|
||||
};
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { getFixReferences } from './get_fix_references';
|
||||
|
||||
it('returns all fixed issue mentions in the PR text', () => {
|
||||
expect(
|
||||
getFixReferences(`
|
||||
clOses #1
|
||||
closes: #2
|
||||
clOse #3
|
||||
close: #4
|
||||
clOsed #5
|
||||
closed: #6
|
||||
fiX #7
|
||||
fix: #8
|
||||
fiXes #9
|
||||
fixes: #10
|
||||
fiXed #11
|
||||
fixed: #12
|
||||
reSolve #13
|
||||
resolve: #14
|
||||
reSolves #15
|
||||
resolves: #16
|
||||
reSolved #17
|
||||
resolved: #18
|
||||
fixed
|
||||
#19
|
||||
`)
|
||||
).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"#1",
|
||||
"#2",
|
||||
"#3",
|
||||
"#4",
|
||||
"#5",
|
||||
"#6",
|
||||
"#7",
|
||||
"#8",
|
||||
"#9",
|
||||
"#10",
|
||||
"#11",
|
||||
"#12",
|
||||
"#13",
|
||||
"#14",
|
||||
"#15",
|
||||
"#16",
|
||||
"#17",
|
||||
"#18",
|
||||
]
|
||||
`);
|
||||
});
|
|
@ -1,18 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
const FIXES_RE = /(?:closes|close|closed|fix|fixes|fixed|resolve|resolves|resolved)[ :]*(#\d*)/gi;
|
||||
|
||||
export function getFixReferences(prText: string) {
|
||||
const fixes: string[] = [];
|
||||
let match;
|
||||
while ((match = FIXES_RE.exec(prText))) {
|
||||
fixes.push(match[1]);
|
||||
}
|
||||
return fixes;
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import MarkdownIt from 'markdown-it';
|
||||
import dedent from 'dedent';
|
||||
|
||||
import { getNoteFromDescription } from './get_note_from_description';
|
||||
|
||||
it('extracts expected components from html', () => {
|
||||
const mk = new MarkdownIt();
|
||||
|
||||
expect(
|
||||
getNoteFromDescription(
|
||||
mk.render(dedent`
|
||||
My PR description
|
||||
|
||||
Fixes: #1234
|
||||
|
||||
## Release Note:
|
||||
|
||||
Checkout this feature
|
||||
`),
|
||||
'release note'
|
||||
)
|
||||
).toMatchInlineSnapshot(`"Checkout this feature"`);
|
||||
|
||||
expect(
|
||||
getNoteFromDescription(
|
||||
mk.render(dedent`
|
||||
My PR description
|
||||
|
||||
Fixes: #1234
|
||||
|
||||
#### Dev docs:
|
||||
|
||||
We fixed an issue
|
||||
`),
|
||||
'dev docs'
|
||||
)
|
||||
).toMatchInlineSnapshot(`"We fixed an issue"`);
|
||||
|
||||
expect(
|
||||
getNoteFromDescription(
|
||||
mk.render(dedent`
|
||||
My PR description
|
||||
|
||||
Fixes: #1234
|
||||
|
||||
OTHER TITLE: Checkout feature foo
|
||||
`),
|
||||
'other title'
|
||||
)
|
||||
).toMatchInlineSnapshot(`"Checkout feature foo"`);
|
||||
|
||||
expect(
|
||||
getNoteFromDescription(
|
||||
mk.render(dedent`
|
||||
# Summary
|
||||
|
||||
My PR description
|
||||
|
||||
release note : bar
|
||||
`),
|
||||
'release note'
|
||||
)
|
||||
).toMatchInlineSnapshot(`"bar"`);
|
||||
});
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import cheerio from 'cheerio';
|
||||
|
||||
export function getNoteFromDescription(descriptionHtml: string, header: string) {
|
||||
const re = new RegExp(`^(\\s*${header.toLowerCase()}(?:s)?\\s*:?\\s*)`, 'i');
|
||||
const $ = cheerio.load(descriptionHtml);
|
||||
for (const el of $('p,h1,h2,h3,h4,h5').toArray()) {
|
||||
const text = $(el).text();
|
||||
const match = text.match(re);
|
||||
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const note = text.replace(match[1], '').trim();
|
||||
return note || $(el).next().text().trim();
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export * from './pr_api';
|
||||
export * from './version';
|
||||
export * from './is_pr_relevant';
|
||||
export * from './streams';
|
||||
export * from './type_helpers';
|
||||
export * from './irrelevant_pr_summary';
|
||||
export * from './classify_pr';
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ToolingLog } from '@kbn/dev-utils';
|
||||
|
||||
import { PullRequest } from './pr_api';
|
||||
import { Version } from './version';
|
||||
|
||||
export class IrrelevantPrSummary {
|
||||
private readonly stats = {
|
||||
'skipped by label': new Map<string, number>(),
|
||||
'skipped by label regexp': new Map<string, number>(),
|
||||
'skipped by version': new Map<string, number>(),
|
||||
};
|
||||
|
||||
constructor(private readonly log: ToolingLog) {}
|
||||
|
||||
skippedByLabel(pr: PullRequest, label: string) {
|
||||
this.log.debug(`${pr.terminalLink} skipped, label [${label}] is ignored`);
|
||||
this.increment('skipped by label', label);
|
||||
}
|
||||
|
||||
skippedByLabelRegExp(pr: PullRequest, regexp: RegExp, label: string) {
|
||||
this.log.debug(`${pr.terminalLink} skipped, label [${label}] matches regexp [${regexp}]`);
|
||||
this.increment('skipped by label regexp', `${regexp}`);
|
||||
}
|
||||
|
||||
skippedByVersion(pr: PullRequest, earliestVersion: Version) {
|
||||
this.log.debug(`${pr.terminalLink} skipped, earliest version is [${earliestVersion.label}]`);
|
||||
this.increment('skipped by version', earliestVersion.label);
|
||||
}
|
||||
|
||||
private increment(stat: keyof IrrelevantPrSummary['stats'], key: string) {
|
||||
const n = this.stats[stat].get(key) || 0;
|
||||
this.stats[stat].set(key, n + 1);
|
||||
}
|
||||
|
||||
logStats() {
|
||||
for (const [description, stats] of Object.entries(this.stats)) {
|
||||
for (const [key, count] of stats) {
|
||||
this.log.warning(`${count} ${count === 1 ? 'pr was' : 'prs were'} ${description} [${key}]`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { Version } from './version';
|
||||
import { PullRequest } from './pr_api';
|
||||
import { IGNORE_LABELS } from '../release_notes_config';
|
||||
import { IrrelevantPrSummary } from './irrelevant_pr_summary';
|
||||
|
||||
export function isPrRelevant(
|
||||
pr: PullRequest,
|
||||
version: Version,
|
||||
includeVersions: Version[],
|
||||
summary: IrrelevantPrSummary
|
||||
) {
|
||||
for (const label of IGNORE_LABELS) {
|
||||
if (typeof label === 'string') {
|
||||
if (pr.labels.includes(label)) {
|
||||
summary.skippedByLabel(pr, label);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (label instanceof RegExp) {
|
||||
const matching = pr.labels.find((l) => label.test(l));
|
||||
if (matching) {
|
||||
summary.skippedByLabelRegExp(pr, label, matching);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const [earliestVersion] = Version.sort(
|
||||
// filter out `includeVersions` so that they won't be considered the "earliest version", only
|
||||
// versions which are actually before the current `version` or the `version` itself are eligible
|
||||
pr.versions.filter((v) => !includeVersions.includes(v)),
|
||||
'asc'
|
||||
);
|
||||
|
||||
if (version !== earliestVersion) {
|
||||
summary.skippedByVersion(pr, earliestVersion);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
|
@ -1,222 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { inspect } from 'util';
|
||||
|
||||
import Axios from 'axios';
|
||||
import gql from 'graphql-tag';
|
||||
import * as GraphqlPrinter from 'graphql/language/printer';
|
||||
import { DocumentNode } from 'graphql/language/ast';
|
||||
import makeTerminalLink from 'terminal-link';
|
||||
import { ToolingLog, isAxiosResponseError } from '@kbn/dev-utils';
|
||||
|
||||
import { Version } from './version';
|
||||
import { getFixReferences } from './get_fix_references';
|
||||
import { getNoteFromDescription } from './get_note_from_description';
|
||||
|
||||
const PrNodeFragment = gql`
|
||||
fragment PrNode on PullRequest {
|
||||
number
|
||||
url
|
||||
title
|
||||
bodyText
|
||||
bodyHTML
|
||||
mergedAt
|
||||
baseRefName
|
||||
state
|
||||
author {
|
||||
login
|
||||
... on User {
|
||||
name
|
||||
}
|
||||
}
|
||||
labels(first: 100) {
|
||||
nodes {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export interface PullRequest {
|
||||
number: number;
|
||||
url: string;
|
||||
title: string;
|
||||
targetBranch: string;
|
||||
mergedAt: string;
|
||||
state: string;
|
||||
labels: string[];
|
||||
fixes: string[];
|
||||
user: {
|
||||
name: string;
|
||||
login: string;
|
||||
};
|
||||
versions: Version[];
|
||||
terminalLink: string;
|
||||
note?: string;
|
||||
}
|
||||
|
||||
export class PrApi {
|
||||
constructor(private readonly log: ToolingLog, private readonly token: string) {}
|
||||
|
||||
async getPr(number: number) {
|
||||
const resp = await this.gqlRequest(
|
||||
gql`
|
||||
query($number: Int!) {
|
||||
repository(owner: "elastic", name: "kibana") {
|
||||
pullRequest(number: $number) {
|
||||
...PrNode
|
||||
}
|
||||
}
|
||||
}
|
||||
${PrNodeFragment}
|
||||
`,
|
||||
{
|
||||
number,
|
||||
}
|
||||
);
|
||||
|
||||
const node = resp.data?.repository?.pullRequest;
|
||||
if (!node) {
|
||||
throw new Error(`unexpected github response, unable to fetch PR: ${inspect(resp)}`);
|
||||
}
|
||||
|
||||
return this.parsePullRequestNode(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate all of the PRs which have the `version` label
|
||||
*/
|
||||
async *iterRelevantPullRequests(version: Version) {
|
||||
let nextCursor: string | undefined;
|
||||
let hasNextPage = true;
|
||||
|
||||
while (hasNextPage) {
|
||||
const resp = await this.gqlRequest(
|
||||
gql`
|
||||
query($cursor: String, $labels: [String!]) {
|
||||
repository(owner: "elastic", name: "kibana") {
|
||||
pullRequests(first: 100, after: $cursor, labels: $labels, states: MERGED) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
endCursor
|
||||
}
|
||||
nodes {
|
||||
...PrNode
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${PrNodeFragment}
|
||||
`,
|
||||
{
|
||||
cursor: nextCursor,
|
||||
labels: [version.label],
|
||||
}
|
||||
);
|
||||
|
||||
const pullRequests = resp.data?.repository?.pullRequests;
|
||||
if (!pullRequests) {
|
||||
throw new Error(`unexpected github response, unable to fetch PRs: ${inspect(resp)}`);
|
||||
}
|
||||
|
||||
hasNextPage = pullRequests.pageInfo?.hasNextPage;
|
||||
nextCursor = pullRequests.pageInfo?.endCursor;
|
||||
|
||||
if (hasNextPage === undefined || (hasNextPage && !nextCursor)) {
|
||||
throw new Error(
|
||||
`github response does not include valid pagination information: ${inspect(resp)}`
|
||||
);
|
||||
}
|
||||
|
||||
for (const node of pullRequests.nodes) {
|
||||
yield this.parsePullRequestNode(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the Github API response into the structure used by this tool
|
||||
*
|
||||
* @param node A GraphQL response from Github using the PrNode fragment
|
||||
*/
|
||||
private parsePullRequestNode(node: any): PullRequest {
|
||||
const terminalLink = makeTerminalLink(`#${node.number}`, node.url);
|
||||
|
||||
const labels: string[] = node.labels.nodes.map((l: { name: string }) => l.name);
|
||||
|
||||
return {
|
||||
number: node.number,
|
||||
url: node.url,
|
||||
terminalLink,
|
||||
title: node.title,
|
||||
targetBranch: node.baseRefName,
|
||||
state: node.state,
|
||||
mergedAt: node.mergedAt,
|
||||
labels,
|
||||
fixes: getFixReferences(node.bodyText),
|
||||
user: {
|
||||
login: node.author?.login || 'deleted user',
|
||||
name: node.author?.name,
|
||||
},
|
||||
versions: labels
|
||||
.map((l) => Version.fromLabel(l))
|
||||
.filter((v): v is Version => v instanceof Version),
|
||||
note:
|
||||
getNoteFromDescription(node.bodyHTML, 'release note') ||
|
||||
getNoteFromDescription(node.bodyHTML, 'dev docs'),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a single request to the Github v4 GraphQL API
|
||||
*/
|
||||
private async gqlRequest(query: DocumentNode, variables: Record<string, unknown> = {}) {
|
||||
let attempt = 0;
|
||||
|
||||
while (true) {
|
||||
attempt += 1;
|
||||
|
||||
try {
|
||||
const resp = await Axios.request({
|
||||
url: 'https://api.github.com/graphql',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'user-agent': '@kbn/release-notes',
|
||||
authorization: `bearer ${this.token}`,
|
||||
},
|
||||
data: {
|
||||
query: GraphqlPrinter.print(query),
|
||||
variables,
|
||||
},
|
||||
});
|
||||
|
||||
return resp.data;
|
||||
} catch (error) {
|
||||
if (!isAxiosResponseError(error) || error.response.status < 500) {
|
||||
// rethrow error unless it is a 500+ response from github
|
||||
throw error;
|
||||
}
|
||||
|
||||
const { status, data } = error.response;
|
||||
const resp = inspect(data);
|
||||
|
||||
if (attempt === 5) {
|
||||
throw new Error(
|
||||
`${status} response from Github, attempted request ${attempt} times: [${resp}]`
|
||||
);
|
||||
}
|
||||
|
||||
const delay = attempt * 2000;
|
||||
this.log.debug(`Github responded with ${status}, retrying in ${delay} ms: [${resp}]`);
|
||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { promisify } from 'util';
|
||||
import { Readable, pipeline } from 'stream';
|
||||
|
||||
/**
|
||||
* @types/node still doesn't have this method that was added
|
||||
* in 10.17.0 https://nodejs.org/api/stream.html#stream_stream_readable_from_iterable_options
|
||||
*/
|
||||
export function streamFromIterable(
|
||||
iter: Iterable<string | Buffer> | AsyncIterable<string | Buffer>
|
||||
): Readable {
|
||||
// @ts-ignore
|
||||
return Readable.from(iter);
|
||||
}
|
||||
|
||||
export const asyncPipeline = promisify(pipeline);
|
|
@ -1,9 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export type ArrayItem<T extends readonly any[]> = T extends ReadonlyArray<infer X> ? X : never;
|
|
@ -1,135 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { Version } from './version';
|
||||
|
||||
it('parses version labels, returns null on failure', () => {
|
||||
expect(Version.fromLabel('v1.0.2')).toMatchInlineSnapshot(`
|
||||
Version {
|
||||
"label": "v1.0.2",
|
||||
"major": 1,
|
||||
"minor": 0,
|
||||
"patch": 2,
|
||||
"tag": undefined,
|
||||
"tagNum": undefined,
|
||||
"tagOrder": Infinity,
|
||||
}
|
||||
`);
|
||||
expect(Version.fromLabel('v1.0.0')).toMatchInlineSnapshot(`
|
||||
Version {
|
||||
"label": "v1.0.0",
|
||||
"major": 1,
|
||||
"minor": 0,
|
||||
"patch": 0,
|
||||
"tag": undefined,
|
||||
"tagNum": undefined,
|
||||
"tagOrder": Infinity,
|
||||
}
|
||||
`);
|
||||
expect(Version.fromLabel('v9.0.2')).toMatchInlineSnapshot(`
|
||||
Version {
|
||||
"label": "v9.0.2",
|
||||
"major": 9,
|
||||
"minor": 0,
|
||||
"patch": 2,
|
||||
"tag": undefined,
|
||||
"tagNum": undefined,
|
||||
"tagOrder": Infinity,
|
||||
}
|
||||
`);
|
||||
expect(Version.fromLabel('v9.0.2-alpha0')).toMatchInlineSnapshot(`
|
||||
Version {
|
||||
"label": "v9.0.2-alpha0",
|
||||
"major": 9,
|
||||
"minor": 0,
|
||||
"patch": 2,
|
||||
"tag": "alpha",
|
||||
"tagNum": 0,
|
||||
"tagOrder": 1,
|
||||
}
|
||||
`);
|
||||
expect(Version.fromLabel('v9.0.2-beta1')).toMatchInlineSnapshot(`
|
||||
Version {
|
||||
"label": "v9.0.2-beta1",
|
||||
"major": 9,
|
||||
"minor": 0,
|
||||
"patch": 2,
|
||||
"tag": "beta",
|
||||
"tagNum": 1,
|
||||
"tagOrder": 2,
|
||||
}
|
||||
`);
|
||||
expect(Version.fromLabel('v9.0')).toMatchInlineSnapshot(`undefined`);
|
||||
expect(Version.fromLabel('some:area')).toMatchInlineSnapshot(`undefined`);
|
||||
});
|
||||
|
||||
it('sorts versions in ascending order', () => {
|
||||
const versions = [
|
||||
'v1.7.3',
|
||||
'v1.7.0',
|
||||
'v1.5.0',
|
||||
'v2.7.0',
|
||||
'v7.0.0-beta2',
|
||||
'v7.0.0-alpha1',
|
||||
'v2.0.0',
|
||||
'v0.0.0',
|
||||
'v7.0.0-beta1',
|
||||
'v7.0.0',
|
||||
].map((l) => Version.fromLabel(l)!);
|
||||
|
||||
const sorted = Version.sort(versions);
|
||||
|
||||
expect(sorted.map((v) => v.label)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"v0.0.0",
|
||||
"v1.5.0",
|
||||
"v1.7.0",
|
||||
"v1.7.3",
|
||||
"v2.0.0",
|
||||
"v2.7.0",
|
||||
"v7.0.0-alpha1",
|
||||
"v7.0.0-beta1",
|
||||
"v7.0.0-beta2",
|
||||
"v7.0.0",
|
||||
]
|
||||
`);
|
||||
|
||||
// ensure versions was not mutated
|
||||
expect(sorted).not.toEqual(versions);
|
||||
});
|
||||
|
||||
it('sorts versions in decending order', () => {
|
||||
const versions = [
|
||||
'v1.7.3',
|
||||
'v1.7.0',
|
||||
'v1.5.0',
|
||||
'v7.0.0-beta1',
|
||||
'v2.7.0',
|
||||
'v2.0.0',
|
||||
'v0.0.0',
|
||||
'v7.0.0',
|
||||
].map((l) => Version.fromLabel(l)!);
|
||||
|
||||
const sorted = Version.sort(versions, 'desc');
|
||||
|
||||
expect(sorted.map((v) => v.label)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"v7.0.0",
|
||||
"v7.0.0-beta1",
|
||||
"v2.7.0",
|
||||
"v2.0.0",
|
||||
"v1.7.3",
|
||||
"v1.7.0",
|
||||
"v1.5.0",
|
||||
"v0.0.0",
|
||||
]
|
||||
`);
|
||||
|
||||
// ensure versions was not mutated
|
||||
expect(sorted).not.toEqual(versions);
|
||||
});
|
|
@ -1,112 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
const LABEL_RE = /^v(\d+)\.(\d+)\.(\d+)(?:-(alpha|beta)(\d+))?$/;
|
||||
|
||||
const versionCache = new Map<string, Version>();
|
||||
|
||||
const multiCompare = (...diffs: number[]) => {
|
||||
for (const diff of diffs) {
|
||||
if (diff !== 0) {
|
||||
return diff;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
export class Version {
|
||||
static fromFlag(flag: string | string[] | boolean | undefined) {
|
||||
if (typeof flag !== 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
return Version.fromLabel(flag) || Version.fromLabel(`v${flag}`);
|
||||
}
|
||||
|
||||
static fromFlags(flag: string | string[] | boolean | undefined) {
|
||||
const flags = Array.isArray(flag) ? flag : [flag];
|
||||
const versions: Version[] = [];
|
||||
|
||||
for (const f of flags) {
|
||||
const version = Version.fromFlag(f);
|
||||
if (!version) {
|
||||
return;
|
||||
}
|
||||
versions.push(version);
|
||||
}
|
||||
|
||||
return versions;
|
||||
}
|
||||
|
||||
static fromLabel(label: string) {
|
||||
const match = label.match(LABEL_RE);
|
||||
if (!match) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cached = versionCache.get(label);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const [, major, minor, patch, tag, tagNum] = match;
|
||||
const version = new Version(
|
||||
parseInt(major, 10),
|
||||
parseInt(minor, 10),
|
||||
parseInt(patch, 10),
|
||||
tag as 'alpha' | 'beta' | undefined,
|
||||
tagNum ? parseInt(tagNum, 10) : undefined
|
||||
);
|
||||
|
||||
versionCache.set(label, version);
|
||||
return version;
|
||||
}
|
||||
|
||||
static sort(versions: Version[], dir: 'asc' | 'desc' = 'asc') {
|
||||
const order = dir === 'asc' ? 1 : -1;
|
||||
|
||||
return versions.slice().sort((a, b) => a.compare(b) * order);
|
||||
}
|
||||
|
||||
public readonly label = `v${this.major}.${this.minor}.${this.patch}${
|
||||
this.tag ? `-${this.tag}${this.tagNum}` : ''
|
||||
}`;
|
||||
private readonly tagOrder: number;
|
||||
|
||||
constructor(
|
||||
public readonly major: number,
|
||||
public readonly minor: number,
|
||||
public readonly patch: number,
|
||||
public readonly tag: 'alpha' | 'beta' | undefined,
|
||||
public readonly tagNum: number | undefined
|
||||
) {
|
||||
switch (tag) {
|
||||
case undefined:
|
||||
this.tagOrder = Infinity;
|
||||
break;
|
||||
case 'alpha':
|
||||
this.tagOrder = 1;
|
||||
break;
|
||||
case 'beta':
|
||||
this.tagOrder = 2;
|
||||
break;
|
||||
default:
|
||||
throw new Error('unexpected tag');
|
||||
}
|
||||
}
|
||||
|
||||
compare(other: Version) {
|
||||
return multiCompare(
|
||||
this.major - other.major,
|
||||
this.minor - other.minor,
|
||||
this.patch - other.patch,
|
||||
this.tagOrder - other.tagOrder,
|
||||
(this.tagNum ?? 0) - (other.tagNum ?? 0)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,283 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Exclude any PR from release notes that has a matching label. String
|
||||
* labels must match exactly, for more complicated use a RegExp
|
||||
*/
|
||||
export const IGNORE_LABELS: Array<RegExp | string> = [
|
||||
'Team:Docs',
|
||||
':KibanaApp/fix-it-week',
|
||||
'reverted',
|
||||
/^test/,
|
||||
'non-issue',
|
||||
'jenkins',
|
||||
'build',
|
||||
'chore',
|
||||
'backport',
|
||||
'release_note:skip',
|
||||
'release_note:dev_docs',
|
||||
];
|
||||
|
||||
/**
|
||||
* Define areas that are used to categorize changes in the release notes
|
||||
* based on the labels a PR has. the `labels` array can contain strings, which
|
||||
* are matched exactly, or regular expressions. The first area, in definition
|
||||
* order, which has a `label` which matches and label on a PR is the area
|
||||
* assigned to that PR.
|
||||
*/
|
||||
|
||||
export interface Area {
|
||||
title: string;
|
||||
labels: Array<string | RegExp>;
|
||||
}
|
||||
|
||||
export const AREAS: Area[] = [
|
||||
{
|
||||
title: 'Design',
|
||||
labels: ['Team:Design', 'Project:Accessibility'],
|
||||
},
|
||||
{
|
||||
title: 'Logstash',
|
||||
labels: ['App:Logstash', 'Feature:Logstash Pipelines'],
|
||||
},
|
||||
{
|
||||
title: 'Management',
|
||||
labels: [
|
||||
'Feature:license',
|
||||
'Feature:Console',
|
||||
'Feature:Search Profiler',
|
||||
'Feature:watcher',
|
||||
'Feature:Index Patterns',
|
||||
'Feature:Kibana Management',
|
||||
'Feature:Dev Tools',
|
||||
'Feature:Inspector',
|
||||
'Feature:Index Management',
|
||||
'Feature:Snapshot and Restore',
|
||||
'Team:Elasticsearch UI',
|
||||
'Feature:FieldFormatters',
|
||||
'Feature:CCR',
|
||||
'Feature:ILM',
|
||||
'Feature:Transforms',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Monitoring',
|
||||
labels: ['Team:Monitoring', 'Feature:Telemetry', 'Feature:Stack Monitoring'],
|
||||
},
|
||||
{
|
||||
title: 'Operations',
|
||||
labels: ['Team:Operations', 'Feature:License'],
|
||||
},
|
||||
{
|
||||
title: 'Kibana UI',
|
||||
labels: ['Kibana UI', 'Team:Core UI', 'Feature:Header'],
|
||||
},
|
||||
{
|
||||
title: 'Platform',
|
||||
labels: [
|
||||
'Team:Platform',
|
||||
'Feature:Plugins',
|
||||
'Feature:New Platform',
|
||||
'Project:i18n',
|
||||
'Feature:ExpressionLanguage',
|
||||
'Feature:Saved Objects',
|
||||
'Team:Stack Services',
|
||||
'Feature:NP Migration',
|
||||
'Feature:Task Manager',
|
||||
'Team:Pulse',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Machine Learning',
|
||||
labels: [
|
||||
':ml',
|
||||
'Feature:Anomaly Detection',
|
||||
'Feature:Data Frames',
|
||||
'Feature:File Data Viz',
|
||||
'Feature:ml-results',
|
||||
'Feature:Data Frame Analytics',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Maps',
|
||||
labels: ['Team:Geo'],
|
||||
},
|
||||
{
|
||||
title: 'QA',
|
||||
labels: ['Team:QA'],
|
||||
},
|
||||
{
|
||||
title: 'Security',
|
||||
labels: [
|
||||
'Team:Security',
|
||||
'Feature:Security/Spaces',
|
||||
'Feature:users and roles',
|
||||
'Feature:Security/Authentication',
|
||||
'Feature:Security/Authorization',
|
||||
'Feature:Security/Feature Controls',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Canvas',
|
||||
labels: ['Feature:Canvas'],
|
||||
},
|
||||
{
|
||||
title: 'Dashboard',
|
||||
labels: ['Feature:Dashboard', 'Feature:Drilldowns'],
|
||||
},
|
||||
{
|
||||
title: 'Discover',
|
||||
labels: ['Feature:Discover'],
|
||||
},
|
||||
{
|
||||
title: 'Kibana Home & Add Data',
|
||||
labels: ['Feature:Add Data', 'Feature:Home'],
|
||||
},
|
||||
{
|
||||
title: 'Querying & Filtering',
|
||||
labels: [
|
||||
'Feature:Query Bar',
|
||||
'Feature:Courier',
|
||||
'Feature:Filters',
|
||||
'Feature:Timepicker',
|
||||
'Feature:Highlight',
|
||||
'Feature:KQL',
|
||||
'Feature:Rollups',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Reporting',
|
||||
labels: ['Feature:Reporting', 'Team:Reporting Services'],
|
||||
},
|
||||
{
|
||||
title: 'Sharing',
|
||||
labels: ['Feature:Embedding', 'Feature:SharingURLs'],
|
||||
},
|
||||
{
|
||||
title: 'Visualizations',
|
||||
labels: [
|
||||
'Feature:Timelion',
|
||||
'Feature:TSVB',
|
||||
'Feature:Coordinate Map',
|
||||
'Feature:Region Map',
|
||||
'Feature:Vega',
|
||||
'Feature:Gauge Vis',
|
||||
'Feature:Tagcloud',
|
||||
'Feature:Vis Loader',
|
||||
'Feature:Vislib',
|
||||
'Feature:Vis Editor',
|
||||
'Feature:Aggregations',
|
||||
'Feature:Input Control',
|
||||
'Feature:Visualizations',
|
||||
'Feature:Markdown',
|
||||
'Feature:Data Table',
|
||||
'Feature:Heatmap',
|
||||
'Feature:Pie Chart',
|
||||
'Feature:XYAxis',
|
||||
'Feature:Graph',
|
||||
'Feature:New Feature',
|
||||
'Feature:MetricVis',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'SIEM',
|
||||
labels: ['Team:SIEM'],
|
||||
},
|
||||
{
|
||||
title: 'Code',
|
||||
labels: ['Team:Code'],
|
||||
},
|
||||
{
|
||||
title: 'Infrastructure',
|
||||
labels: ['App:Infrastructure', 'Feature:Infra UI', 'Feature:Service Maps'],
|
||||
},
|
||||
{
|
||||
title: 'Logs',
|
||||
labels: ['App:Logs', 'Feature:Logs UI'],
|
||||
},
|
||||
{
|
||||
title: 'Uptime',
|
||||
labels: ['App:Uptime', 'Feature:Uptime', 'Team:uptime'],
|
||||
},
|
||||
{
|
||||
title: 'Beats Management',
|
||||
labels: ['App:Beats', 'Feature:beats-cm', 'Team:Beats'],
|
||||
},
|
||||
{
|
||||
title: 'APM',
|
||||
labels: ['Team:apm', /^apm[:\-]/],
|
||||
},
|
||||
{
|
||||
title: 'Lens',
|
||||
labels: ['App:Lens', 'Feature:Lens'],
|
||||
},
|
||||
{
|
||||
title: 'Alerting',
|
||||
labels: ['App:Alerting', 'Feature:Alerting', 'Team:Alerting Services', 'Feature:Actions'],
|
||||
},
|
||||
{
|
||||
title: 'Metrics',
|
||||
labels: ['App:Metrics', 'Feature:Metrics UI', 'Team:logs-metrics-ui'],
|
||||
},
|
||||
{
|
||||
title: 'Data ingest',
|
||||
labels: ['Ingest', 'Feature:Ingest Node Pipelines'],
|
||||
},
|
||||
];
|
||||
|
||||
export const UNKNOWN_AREA: Area = {
|
||||
title: 'Unknown',
|
||||
labels: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* Define the sections that will be assigned to PRs when generating the
|
||||
* asciidoc formatted report. The order of the sections determines the
|
||||
* order they will be rendered in the report
|
||||
*/
|
||||
|
||||
export interface AsciidocSection {
|
||||
title: string;
|
||||
labels: Array<string | RegExp>;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export const ASCIIDOC_SECTIONS: AsciidocSection[] = [
|
||||
{
|
||||
id: 'enhancement',
|
||||
title: 'Enhancements',
|
||||
labels: ['release_note:enhancement'],
|
||||
},
|
||||
{
|
||||
id: 'bug',
|
||||
title: 'Bug fixes',
|
||||
labels: ['release_note:fix'],
|
||||
},
|
||||
{
|
||||
id: 'roadmap',
|
||||
title: 'Roadmap',
|
||||
labels: ['release_note:roadmap'],
|
||||
},
|
||||
{
|
||||
id: 'deprecation',
|
||||
title: 'Deprecations',
|
||||
labels: ['release_note:deprecation'],
|
||||
},
|
||||
{
|
||||
id: 'breaking',
|
||||
title: 'Breaking Changes',
|
||||
labels: ['release_note:breaking'],
|
||||
},
|
||||
];
|
||||
|
||||
export const UNKNOWN_ASCIIDOC_SECTION: AsciidocSection = {
|
||||
id: 'unknown',
|
||||
title: 'Unknown',
|
||||
labels: [],
|
||||
};
|
|
@ -1,10 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
require('../src/setup_node_env/no_transpilation');
|
||||
require('@kbn/docs-utils').runReleaseNotesCli();
|
22
yarn.lock
22
yarn.lock
|
@ -4881,11 +4881,6 @@
|
|||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/graphql@^0.13.2":
|
||||
version "0.13.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.13.4.tgz#55ae9c29f0fd6b85ee536f5c72b4769d5c5e06b1"
|
||||
integrity sha512-B4yel4ro2nTb3v0pYO8vO6SjgvFJSrwUY+IO6TUSLdOSB+gQFslylrhRCHxvXMIhxB71mv5PEE9dAX+24S8sew==
|
||||
|
||||
"@types/gulp-zip@^4.0.1":
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/gulp-zip/-/gulp-zip-4.0.1.tgz#96cd0b994219f9ae3bbbec7ec3baa043fba9d9ef"
|
||||
|
@ -14705,18 +14700,6 @@ graphlib@^2.1.8:
|
|||
dependencies:
|
||||
lodash "^4.17.15"
|
||||
|
||||
graphql-tag@^2.10.3:
|
||||
version "2.10.3"
|
||||
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.3.tgz#ea1baba5eb8fc6339e4c4cf049dabe522b0edf03"
|
||||
integrity sha512-4FOv3ZKfA4WdOKJeHdz6B3F/vxBLSgmBcGeAFPf4n1F64ltJUvOOerNj0rsJxONQGdhUMynQIvd6LzB+1J5oKA==
|
||||
|
||||
graphql@^0.13.2:
|
||||
version "0.13.2"
|
||||
resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.13.2.tgz#4c740ae3c222823e7004096f832e7b93b2108270"
|
||||
integrity sha512-QZ5BL8ZO/B20VA8APauGBg3GyEgZ19eduvpLWoq5x7gMmWnHoy8rlQWPLmWgFvo1yNgjSEFMesmS4R6pPr7xog==
|
||||
dependencies:
|
||||
iterall "^1.2.1"
|
||||
|
||||
grid-index@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/grid-index/-/grid-index-1.1.0.tgz#97f8221edec1026c8377b86446a7c71e79522ea7"
|
||||
|
@ -16770,11 +16753,6 @@ istanbul-reports@^3.0.2:
|
|||
html-escaper "^2.0.0"
|
||||
istanbul-lib-report "^3.0.0"
|
||||
|
||||
iterall@^1.2.1:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.3.0.tgz#afcb08492e2915cbd8a0884eb93a8c94d0d72fea"
|
||||
integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg==
|
||||
|
||||
iterate-iterator@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/iterate-iterator/-/iterate-iterator-1.0.1.tgz#1693a768c1ddd79c969051459453f082fe82e9f6"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue