Skip to content

Draft release notes on tag #6381

Draft release notes on tag

Draft release notes on tag #6381

name: Draft release notes on tag
on:
create:
workflow_dispatch:
jobs:
draft_release_notes:
name: Draft release notes
permissions:
contents: write # Required to create a release
if: (github.event.ref_type == 'tag' && github.event.master_branch == 'master') || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- name: Get milestone title
id: milestoneTitle
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # 7.0.1
with:
result-encoding: string
script: |
// Get the milestone title ("X.Y.Z") from tag name ("vX.Y.Z(-rc)")
const match = '${{github.event.ref}}'.match(/v(\d+\.\d+\.\d+)(-rc\d+)?/i)
if (!match) {
core.setFailed('Failed to parse tag name into milestone name: ${{github.event.ref}}')
return
}
const milestoneTitle = match[1]
const isReleaseCandidate = match[2] !== undefined
// Look for the milestone
const milestone = (await github.paginate('GET /repos/{owner}/{repo}/milestones', {
owner: context.repo.owner,
repo: context.repo.repo,
state: 'all'
})).find(m => m.title == milestoneTitle)
if (!milestone) {
core.setFailed(`Failed to find milestone: ${milestoneTitle}`)
return
}
// Get pull requests of the milestone
const pullRequests = (await github.paginate('/repos/{owner}/{repo}/issues', {
owner: context.repo.owner,
repo: context.repo.repo,
milestone: milestone.number,
state: 'closed'
}))
.filter(i => i.pull_request && i.pull_request.merged_at) // Skip closed but not merged
.filter(p => !p.labels.find(label => label.name == 'tag: no release notes')) // Skip excluded
// Group PR by components and instrumentations
var prByComponents = new Map()
var prByInstrumentations = new Map()
var otherPRs = new Array()
for (let pullRequest of pullRequests) {
var captured = false
for (let label of pullRequest.labels) {
const index = label.name.indexOf(':')
if (index == -1) {
core.notice('Unsupported label: ${label.name}')
continue
}
const labelKey = label.name.substring(0, index)
const labelValue = label.name.slice(index + 1)
var map = null
if (labelKey == 'comp') {
map = prByComponents
} else if (labelKey == 'inst') {
map = prByInstrumentations
}
if (map) {
var prs = map.get(label.description)
if (!prs) {
prs = new Array()
map.set(label.description, prs)
}
prs.push(pullRequest)
captured = true
}
}
if (!captured) {
otherPRs.push(pullRequest)
}
}
// Sort components and instrumenations
prByComponents = new Map([...prByComponents].sort());
const lastInstrumentation = 'All other instrumentations'
prByInstrumentations = new Map([...prByInstrumentations].sort(
(a, b) => {
if (a[0] == lastInstrumentation) {
return 1
} else if (b[0] == lastInstrumentation) {
return -1
}
return String(a[0]).localeCompare(b[0])
}
));
// Generate changelog
const decorators = {
'tag: breaking change': ':warning:',
'tag: experimental': ':test_tube:',
'tag: diagnostics': ':mag:',
'tag: performance': ':zap:',
'tag: security': ':closed_lock_with_key:',
'type: bug': ':bug:',
'type: documentation': ':book:',
'type: enhancement': ':sparkles:',
'type: feature request': ':bulb:',
'type: refactoring': ':broom:'
}
function decorate(pullRequest) {
var line = ''
var decorated = false;
for (let label of pullRequest.labels) {
if (decorators[label.name]) {
line += decorators[label.name]
decorated = true
}
}
if (decorated) {
line += ' '
}
return line
}
function cleanUpTitle(title) {
// Remove tags between brackets
return title.replace(/\[[^\]]+\]/g, '')
}
function format(pullRequest) {
var line = `${decorate(pullRequest)}${cleanUpTitle(pullRequest.title)} (#${pullRequest.number} - @${pullRequest.user.login}`
// Add special thanks if community labeled
if (pullRequest.labels.some(label => label.name == "tag: community")) {
line += ` - thanks for the contribution!`
}
line += ')'
return line;
}
var changelog = ''
if (isReleaseCandidate) {
changelog += '> [!WARNING]\n' +
'> This is a **release candidate** and is **not** intended for use in production. \n' +
'Please [open an issue](https://github.com/DataDog/dd-trace-java/issues/new) regarding any problems in this release candidate.\n\n'
}
if (prByComponents.size > 0) {
changelog += '# Components\n\n';
for (let pair of prByComponents) {
changelog += '## '+pair[0]+'\n\n'
for (let pullRequest of pair[1]) {
changelog += '* ' + format(pullRequest) + '\n'
}
changelog += '\n'
}
}
if (prByInstrumentations.size > 0) {
changelog += '# Instrumentations\n\n'
for (let pair of prByInstrumentations) {
changelog += '## '+pair[0]+'\n\n'
for (let pullRequest of pair[1]) {
changelog += '* ' + format(pullRequest) + '\n'
}
changelog += '\n'
}
}
if (otherPRs.length > 0) {
changelog += '# Other changes\n\n'
for (let pullRequest of otherPRs) {
changelog += '* ' + format(pullRequest) + '\n'
}
}
// Create release with the draft changelog
await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: '${{ github.event.ref }}',
name: milestoneTitle,
draft: true,
body: changelog
})