OpenSearch/dev-tools/es_release_notes.pl
Daniel Mitterdorfer 42e0d63b36 Align generated release notes with doc standards ()
With this commit we change the script that generates the release notes
for Elasticsearch to align with the docs standards in the following
aspects:

* The release notes title changes to include the product name
* The link text to the breaking changes page is explicitly defined

Relates 
2019-02-22 07:41:16 +01:00

271 lines
7.8 KiB
Perl
Executable File

#!/usr/bin/env perl
# Licensed to Elasticsearch under one or more contributor
# license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright
# ownership. Elasticsearch licenses this file to you under
# the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on
# an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
use strict;
use warnings;
use HTTP::Tiny 0.070;
use IO::Socket::SSL 1.52;
use utf8;
my $Github_Key = load_github_key();
my $Base_URL = "https://${Github_Key}api.github.com/repos/";
my $User_Repo = 'elastic/elasticsearch/';
my $Issue_URL = "http://github.com/${User_Repo}issues/";
my @Groups = (
">breaking", ">breaking-java", ">deprecation", ">feature",
">enhancement", ">bug", ">regression", ">upgrade"
);
my %Ignore = map { $_ => 1 }
( ">non-issue", ">refactoring", ">docs", ">test", ">test-failure", ">test-mute", ":Core/Infra/Build", "backport" );
my %Group_Labels = (
'>breaking' => 'Breaking changes',
'>breaking-java' => 'Breaking Java changes',
'>deprecation' => 'Deprecations',
'>feature' => 'New features',
'>enhancement' => 'Enhancements',
'>bug' => 'Bug fixes',
'>regression' => 'Regressions',
'>upgrade' => 'Upgrades',
'other' => 'NOT CLASSIFIED',
);
my %Area_Overrides = (
':ml' => 'Machine Learning',
':Beats' => 'Beats Plugin',
':Docs' => 'Docs Infrastructure'
);
use JSON();
use Encode qw(encode_utf8);
my $json = JSON->new->utf8(1);
my %All_Labels = fetch_labels();
my $version = shift @ARGV
or dump_labels();
dump_labels("Unknown version '$version'")
unless $All_Labels{$version};
my $issues = fetch_issues($version);
dump_issues( $version, $issues );
#===================================
sub dump_issues {
#===================================
my $version = shift;
my $issues = shift;
$version =~ s/v//;
my $branch = $version;
$branch =~ s/\.\d+$//;
my ( $day, $month, $year ) = (gmtime)[ 3 .. 5 ];
$month++;
$year += 1900;
print <<"ASCIIDOC";
:issue: https://github.com/${User_Repo}issues/
:pull: https://github.com/${User_Repo}pull/
[[release-notes-$version]]
== {es} version $version
coming[$version]
Also see <<breaking-changes-$branch,Breaking changes in $branch>>.
ASCIIDOC
for my $group ( @Groups, 'other' ) {
my $group_issues = $issues->{$group} or next;
my $group_id = $group;
$group_id =~ s/^>//;
print "[[$group_id-$version]]\n"
. "[float]\n"
. "=== $Group_Labels{$group}\n\n";
for my $header ( sort keys %$group_issues ) {
my $header_issues = $group_issues->{$header};
print( $header || 'HEADER MISSING', "::\n" );
for my $issue (@$header_issues) {
my $title = $issue->{title};
if ( $issue->{state} eq 'open' ) {
$title .= " [OPEN]";
}
unless ( $issue->{pull_request} ) {
$title .= " [ISSUE]";
}
my $number = $issue->{number};
print encode_utf8("* $title {pull}${number}[#${number}]");
if ( my $related = $issue->{related_issues} ) {
my %uniq = map { $_ => 1 } @$related;
print keys %uniq > 1
? " (issues: "
: " (issue: ";
print join ", ", map {"{issue}${_}[#${_}]"}
sort keys %uniq;
print ")";
}
print "\n";
}
print "\n";
}
print "\n\n";
}
}
#===================================
sub fetch_issues {
#===================================
my $version = shift;
my @issues;
my %seen;
for my $state ( 'open', 'closed' ) {
my $page = 1;
while (1) {
my $tranche
= fetch( $User_Repo
. 'issues?labels='
. $version
. '&pagesize=100&state='
. $state
. '&page='
. $page )
or die "Couldn't fetch issues for version '$version'";
push @issues, @$tranche;
for my $issue (@$tranche) {
next unless $issue->{pull_request};
for ( $issue->{body} =~ m{(?:#|${User_Repo}issues/)(\d+)}g ) {
$seen{$_}++;
push @{ $issue->{related_issues} }, $_;
}
}
$page++;
last unless @$tranche;
}
}
my %group;
ISSUE:
for my $issue (@issues) {
next if $seen{ $issue->{number} } && !$issue->{pull_request};
for ( @{ $issue->{labels} } ) {
next ISSUE if $Ignore{ $_->{name} };
}
# uncomment for including/excluding PRs already issued in other versions
# next if grep {$_->{name}=~/^v2/} @{$issue->{labels}};
my %labels = map { $_->{name} => 1 } @{ $issue->{labels} };
my @area_labels = grep {/^:/} sort keys %labels;
my ($header) = map { m{:[^/]+/(.+)} && $1 } @area_labels;
if (scalar @area_labels > 1) {
$header = "MULTIPLE AREA LABELS";
}
if (scalar @area_labels == 1 && exists $Area_Overrides{$area_labels[0]}) {
$header = $Area_Overrides{$area_labels[0]};
}
$header ||= 'NOT CLASSIFIED';
for (@Groups) {
if ( $labels{$_} ) {
push @{ $group{$_}{$header} }, $issue;
next ISSUE;
}
}
push @{ $group{other}{$header} }, $issue;
}
return \%group;
}
#===================================
sub fetch_labels {
#===================================
my %all;
my $page = 1;
while (1) {
my $labels = fetch( $User_Repo . 'labels?page=' . $page++ )
or die "Couldn't retrieve version labels";
last unless @$labels;
for (@$labels) {
my $name = $_->{name};
next unless $name =~ /^v/;
$all{$name} = 1;
}
}
return %all;
}
#===================================
sub fetch {
#===================================
my $url = $Base_URL . shift();
my $response = HTTP::Tiny->new->get($url);
die "$response->{status} $response->{reason}\n"
unless $response->{success};
# print $response->{content};
return $json->decode( $response->{content} );
}
#===================================
sub load_github_key {
#===================================
my ($file) = glob("~/.github_auth");
unless ( -e $file ) {
warn "File ~/.github_auth doesn't exist - using anonymous API. "
. "Generate a Personal Access Token at https://github.com/settings/applications\n";
return '';
}
open my $fh, $file or die "Couldn't open $file: $!";
my ($key) = <$fh> || die "Couldn't read $file: $!";
$key =~ s/^\s+//;
$key =~ s/\s+$//;
die "Invalid GitHub key: $key"
unless $key =~ /^[0-9a-f]{40}$/;
return "$key:x-oauth-basic@";
}
#===================================
sub dump_labels {
#===================================
my $error = shift || '';
if ($error) {
$error = "\nERROR: $error\n";
}
my $labels = join( "\n - ", '', ( sort keys %All_Labels ) );
die <<USAGE
$error
USAGE: $0 version > outfile
Known versions:$labels
USAGE
}