mirror of https://github.com/apache/kafka.git
				
				
				
			
		
			
				
	
	
		
			175 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			175 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Python
		
	
	
	
#
 | 
						|
# Licensed to the Apache Software Foundation (ASF) under one or more
 | 
						|
# contributor license agreements.  See the NOTICE file distributed with
 | 
						|
# this work for additional information regarding copyright ownership.
 | 
						|
# The ASF 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.
 | 
						|
#
 | 
						|
 | 
						|
"""
 | 
						|
Usage: python notes.py <version> > RELEASE_NOTES.html
 | 
						|
 | 
						|
Generates release notes for a release in HTML format containing
 | 
						|
introductory information about the release with links to the
 | 
						|
Kafka docs and the list of issues resolved in the release.
 | 
						|
 | 
						|
The script will fail if there are any unresolved issues still
 | 
						|
marked with the target release. This script should be run after either
 | 
						|
resolving all issues or moving outstanding issues to a later release.
 | 
						|
"""
 | 
						|
 | 
						|
from jira import JIRA
 | 
						|
import itertools, sys
 | 
						|
 | 
						|
 | 
						|
JIRA_BASE_URL = 'https://issues.apache.org/jira'
 | 
						|
MAX_RESULTS = 100 # This is constrained for cloud instances so we need to fix this value
 | 
						|
 | 
						|
 | 
						|
def query(query, **kwargs):
 | 
						|
    """
 | 
						|
    Fetch all issues matching the JQL query from JIRA and expand paginated results.
 | 
						|
    Any additional keyword arguments are forwarded to jira.search_issues.
 | 
						|
    """
 | 
						|
    results = []
 | 
						|
    start_at = 0
 | 
						|
    new_results = None
 | 
						|
    jira = JIRA(JIRA_BASE_URL)
 | 
						|
    while new_results is None or len(new_results) == MAX_RESULTS:
 | 
						|
        new_results = jira.search_issues(query, startAt=start_at, maxResults=MAX_RESULTS, **kwargs)
 | 
						|
        results += new_results
 | 
						|
        start_at += len(new_results)
 | 
						|
    return results
 | 
						|
 | 
						|
 | 
						|
def filter_unresolved(issues):
 | 
						|
    """
 | 
						|
    Some resolutions, including a lack of resolution, indicate that
 | 
						|
    the bug hasn't actually been addressed and we shouldn't even
 | 
						|
    be able to create a release until they are fixed
 | 
						|
    """
 | 
						|
    UNRESOLVED_RESOLUTIONS = [None,
 | 
						|
                              "Unresolved",
 | 
						|
                              "Duplicate",
 | 
						|
                              "Invalid",
 | 
						|
                              "Not A Problem",
 | 
						|
                              "Not A Bug",
 | 
						|
                              "Won't Fix",
 | 
						|
                              "Incomplete",
 | 
						|
                              "Cannot Reproduce",
 | 
						|
                              "Later",
 | 
						|
                              "Works for Me",
 | 
						|
                              "Workaround",
 | 
						|
                              "Information Provided"
 | 
						|
                              ]
 | 
						|
    return [issue for issue in issues if issue.fields.resolution in UNRESOLVED_RESOLUTIONS or issue.fields.resolution.name in UNRESOLVED_RESOLUTIONS]
 | 
						|
 | 
						|
 | 
						|
def issue_link(issue):
 | 
						|
    """
 | 
						|
    Generates a link to the specified JIRA issue.
 | 
						|
    """
 | 
						|
    return f"{JIRA_BASE_URL}/browse/{issue.key}"
 | 
						|
 | 
						|
 | 
						|
def render(version, issues):
 | 
						|
    """
 | 
						|
    Renders the release notes HTML with the given version and issues.
 | 
						|
    """
 | 
						|
    base_url = "https://kafka.apache.org/"
 | 
						|
    docs_path = "documentation.html"
 | 
						|
    minor_version_dotless = "".join(version.split(".")[:2]) # i.e., 10 if version == 1.0.1
 | 
						|
    def issue_type_key(issue):
 | 
						|
        if issue.fields.issuetype.name == 'New Feature':
 | 
						|
            return -2
 | 
						|
        if issue.fields.issuetype.name == 'Improvement':
 | 
						|
            return -1
 | 
						|
        return int(issue.fields.issuetype.id)
 | 
						|
    by_group = [(k,sorted(g, key=lambda issue: issue.id)) for k,g in itertools.groupby(sorted(issues, key=issue_type_key), lambda issue: issue.fields.issuetype.name)]
 | 
						|
    parts = [f"""
 | 
						|
<h1>Release Notes - Kafka - Version {version}</h1>
 | 
						|
<p>
 | 
						|
    Below is a summary of the JIRA issues addressed in the {version}
 | 
						|
    release of Kafka. For full documentation of the release, a guide
 | 
						|
    to get started, and information about the project, see the
 | 
						|
    <a href="{base_url}">Kafka project site</a>.
 | 
						|
</p>
 | 
						|
<p>
 | 
						|
    <b>Note about upgrades:</b> Please carefully review the
 | 
						|
    <a href="{base_url}{minor_version_dotless}/{docs_path}#upgrade">
 | 
						|
    upgrade documentation</a> for this release thoroughly before upgrading
 | 
						|
    your cluster. The upgrade notes discuss any critical information about
 | 
						|
    incompatibilities and breaking changes, performance changes, and any
 | 
						|
    other changes that might impact your production deployment of Kafka.
 | 
						|
</p>
 | 
						|
<p>
 | 
						|
    The documentation for the most recent release can be found at
 | 
						|
    <a href="{base_url}{docs_path}">{base_url}{docs_path}</a>.
 | 
						|
</p>
 | 
						|
"""]
 | 
						|
    for itype, issues in by_group:
 | 
						|
        parts.append(f"<h2>{itype}</h2>")
 | 
						|
        parts.append("</ul>")
 | 
						|
        for issue in issues:
 | 
						|
            link = issue_link(issue)
 | 
						|
            key = issue.key
 | 
						|
            summary = issue.fields.summary
 | 
						|
            parts.append(f"<li>[<a href=\"{link}\">{key}</a>] - {summary}</li>")
 | 
						|
        parts.append("</ul>")
 | 
						|
    return "\n".join(parts)
 | 
						|
 | 
						|
 | 
						|
def issue_str(issue):
 | 
						|
    """
 | 
						|
    Provides a human readable string representation for the given issue.
 | 
						|
    """
 | 
						|
    key = "%15s" % issue.key
 | 
						|
    resolution = "%15s" % issue.fields.resolution
 | 
						|
    link = issue_link(issue)
 | 
						|
    return f"{key} {resolution} {link}"
 | 
						|
 | 
						|
def generate(version):
 | 
						|
    """
 | 
						|
    Generates the release notes in HTML format for given version.
 | 
						|
    Raises an error if there are unresolved issues or no issues
 | 
						|
    at all for the specified version.
 | 
						|
    """
 | 
						|
    issues = query(f"project=KAFKA and fixVersion={version}")
 | 
						|
    if not issues:
 | 
						|
        raise Exception(f"Didn't find any issues for version {version}")
 | 
						|
    unresolved_issues = filter_unresolved(issues)
 | 
						|
    if unresolved_issues:
 | 
						|
        issue_list = "\n".join([issue_str(issue) for issue in unresolved_issues])
 | 
						|
        raise Exception(f"""
 | 
						|
Release {version} is not complete since there are unresolved or improperly
 | 
						|
resolved issues tagged {version} as the fix version:
 | 
						|
 | 
						|
{issue_list}
 | 
						|
 | 
						|
Note that for some resolutions, you should simply remove the fix version
 | 
						|
as they have not been truly fixed in this release.
 | 
						|
        """)
 | 
						|
    return render(version, issues)
 | 
						|
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    if len(sys.argv) != 2:
 | 
						|
        print("Usage: python notes.py <version>", file=sys.stderr)
 | 
						|
        sys.exit(1)
 | 
						|
 | 
						|
    version = sys.argv[1]
 | 
						|
    try:
 | 
						|
       print(generate(version))
 | 
						|
    except Exception as e:
 | 
						|
        print(e, file=sys.stderr)
 | 
						|
        sys.exit(1)
 |