- introduce new configuration duplicate_filter allowing to drop duplicated elements from the changelog

- restructure transform.ts to simplify code
  - introduce new test cases to verify new deduplication feature
This commit is contained in:
Mike Penz
2021-08-27 13:28:56 +02:00
parent 59a6af0516
commit 7a28de97be
6 changed files with 260 additions and 121 deletions
+3 -1
View File
@@ -3,13 +3,14 @@ export interface Configuration {
max_pull_requests: number
max_back_track_time_days: number
exclude_merge_branches: string[]
sort: string
sort: string // "ASC" or "DESC"
template: string
pr_template: string
empty_template: string
categories: Category[]
ignore_labels: string[]
label_extractor: Extractor[]
duplicate_filter?: Extractor // extract an identifier from a PR used to detect duplicates, will keep the last match (depends on `sort`)
transformers: Transformer[]
tag_resolver: TagResolver
base_branches: string[]
@@ -61,6 +62,7 @@ export const DefaultConfiguration: Configuration = {
], // the categories to support for the ordering
ignore_labels: ['ignore'], // list of lables being ignored from the changelog
label_extractor: [], // extracts additional labels from the commit message given a regex
duplicate_filter: undefined, // extract an identifier from a PR used to detect duplicates, will keep the last match (depends on `sort`)
transformers: [], // transformers to apply on the PR description according to the `pr_template`
tag_resolver: {
// defines the logic on how to resolve the previous tag, only relevant if `fromTag` is not specified
+104 -53
View File
@@ -19,37 +19,42 @@ export function buildChangelog(
prs = sortPullRequests(prs, sortAsc)
core.info(`️ Sorted all pull requests ascending: ${sort}`)
// drop duplicate pull requests
if (config.duplicate_filter !== undefined) {
const extractor = validateTransformer(config.duplicate_filter)
if (extractor != null) {
core.info(`️ Remove duplicated pull requests using \`duplicate_filter\``)
const deduplicatedMap = new Map<string, PullRequestInfo>()
for (const pr of prs) {
const extracted = extractValues(pr, extractor, 'dupliate_filter')
if (extracted !== null && extracted.length > 0) {
deduplicatedMap.set(extracted[0], pr)
} else {
core.debug(
`️ PR (${pr.number}) did not resolve a ID using the \`duplicate_filter\``
)
}
}
const deduplicatedPRs = Array.from(deduplicatedMap.values())
const removedElements = prs.length - deduplicatedPRs.length
core.info(
`️ Removed ${removedElements} pull requests during deduplication`
)
prs = deduplicatedPRs
} else {
core.warning(`⚠️ Configured \`duplicate_filter\` invalid.`)
}
}
// extract additional labels from the commit message
const labelExtractors = validateTransformers(config.label_extractor)
for (const extractor of labelExtractors) {
if (extractor.pattern != null) {
for (const pr of prs) {
let onValue
if (extractor.onProperty !== undefined) {
let value: string = pr[extractor.onProperty]
if (value === undefined) {
core.warning(
`⚠️ the provided property '${extractor.onProperty}' for \`label_extractor\` is not valid`
)
value = pr['body']
}
onValue = value
} else {
onValue = pr.body
}
if (extractor.method === 'match') {
const lables = onValue.match(extractor.pattern)
if (lables !== null) {
for (const label of lables) {
pr.labels.add(label.toLocaleLowerCase())
}
}
} else {
const label = onValue.replace(extractor.pattern, extractor.target)
if (label !== '') {
pr.labels.add(label.toLocaleLowerCase())
}
for (const pr of prs) {
const extracted = extractValues(pr, extractor, 'label_extractor')
if (extracted !== null) {
for (const label of extracted) {
pr.labels.add(label)
}
}
}
@@ -276,32 +281,78 @@ function validateTransformers(
specifiedTransformers || DefaultConfiguration.transformers
return transformers
.map(transformer => {
try {
let onProperty = undefined
let method = undefined
if (transformer.hasOwnProperty('on_property')) {
onProperty = (transformer as Extractor).on_property
method = (transformer as Extractor).method
}
return {
pattern: new RegExp(
transformer.pattern.replace('\\\\', '\\'),
transformer.flags ?? 'gu'
),
target: transformer.target || '',
onProperty,
method
}
} catch (e) {
core.warning(`⚠️ Bad replacer regex: ${transformer.pattern}`)
return {
pattern: null,
target: ''
}
}
return validateTransformer(transformer)
})
.filter(transformer => transformer.pattern != null)
.filter(transformer => transformer?.pattern != null)
.map(transformer => {
return transformer as RegexTransformer
})
}
function validateTransformer(
transformer?: Transformer
): RegexTransformer | null {
if (transformer === undefined) {
return null
}
try {
let onProperty = undefined
let method = undefined
if (transformer.hasOwnProperty('on_property')) {
onProperty = (transformer as Extractor).on_property
method = (transformer as Extractor).method
}
return {
pattern: new RegExp(
transformer.pattern.replace('\\\\', '\\'),
transformer.flags ?? 'gu'
),
target: transformer.target || '',
onProperty,
method
}
} catch (e) {
core.warning(`⚠️ Bad replacer regex: ${transformer.pattern}`)
return null
}
}
function extractValues(
pr: PullRequestInfo,
extractor: RegexTransformer,
extractor_usecase: string
): string[] | null {
if (extractor.pattern == null) {
return null
}
let onValue
if (extractor.onProperty !== undefined) {
let value: string = pr[extractor.onProperty]
if (value === undefined) {
core.warning(
`⚠️ the provided property '${extractor.onProperty}' for \`${extractor_usecase}\` is not valid`
)
value = pr['body']
}
onValue = value
} else {
onValue = pr.body
}
if (extractor.method === 'match') {
const lables = onValue.match(extractor.pattern)
if (lables !== null) {
return lables.map(label => label.toLocaleLowerCase())
}
} else {
const label = onValue.replace(extractor.pattern, extractor.target)
if (label !== '') {
return [label.toLocaleLowerCase()]
}
}
return null
}
interface RegexTransformer {