diff --git a/docs/index.md b/docs/index.md index 71a6922..5178017 100644 --- a/docs/index.md +++ b/docs/index.md @@ -57,7 +57,7 @@ You can find the [resulting markdown here](sample_notebook_activity). For repositories that use multiple branches, it may be necessary to filter PRs by a branch name. This can be done using the `--branch` parameter in the CLI. Other git references can be used as well in place of a branch name. ``` -### Splitting PRs by tags and prefixes +### Split PRs by tags and prefixes Often you wish to split your PRs into multiple categories so that they are easier to scan and parse. You may also _only_ want to keep some PRs (e.g. features, or API @@ -80,6 +80,22 @@ You can choose to *remove* some types of PRs from your changelog by passing the left-most column above. ``` +## Pull release notes from PR descriptions + +You can optionally include snippets of release notes directly from the descriptions of Pull Requests. +These will be included just underneath the bullet point text for each PR in the markdown output. + +To do so, follow these steps: + +1. In your PR description, include a markdown header that begins with `# Release notes`. + The header can be of any level, and capitalization does not matter. + All subsequent text in the PR description will be treated as the release notes, until a header of equal or lesser level is encountered. +2. Use the `--include-release-notes` flag. For example: + + ``` + github-activity --include-release-notes + ``` + ## Use a GitHub API token `github-activity` uses the GitHub API to pull information about a repository's activity. diff --git a/github_activity/cli.py b/github_activity/cli.py index 473268d..122f9a9 100644 --- a/github_activity/cli.py +++ b/github_activity/cli.py @@ -81,6 +81,12 @@ action="store_true", help="Include a list of opened items in the markdown output", ) +parser.add_argument( + "--include-release-notes", + default=False, + action="store_true", + help="Include the `# release notes` block of each PR in the output markdown.", +) parser.add_argument( "--strip-brackets", default=False, @@ -165,6 +171,7 @@ def main(): tags=tags, include_issues=bool(args.include_issues), include_opened=bool(args.include_opened), + include_release_notes=bool(args.include_release_notes), strip_brackets=bool(args.strip_brackets), branch=args.branch, ) diff --git a/github_activity/github_activity.py b/github_activity/github_activity.py index fbe6c5b..b30fed4 100644 --- a/github_activity/github_activity.py +++ b/github_activity/github_activity.py @@ -5,11 +5,11 @@ import shlex import subprocess import sys -import urllib from pathlib import Path from subprocess import PIPE from subprocess import run from tempfile import TemporaryDirectory +from textwrap import indent import dateutil import numpy as np @@ -176,6 +176,7 @@ def generate_all_activity_md( tags=None, include_issues=False, include_opened=False, + include_release_notes=False, strip_brackets=False, branch=None, ): @@ -207,6 +208,8 @@ def generate_all_activity_md( Include Issues in the markdown output. Default is False. include_opened : bool Include a list of opened items in the markdown output. Default is False. + include_release_notes : bool + Include the release notes for any PRs with `# Release notes` in the description. strip_brackets : bool If True, strip any text between brackets at the beginning of the issue/PR title. E.g., [MRG], [DOC], etc. @@ -300,6 +303,7 @@ def generate_activity_md( tags=None, include_issues=False, include_opened=False, + include_release_notes=False, strip_brackets=False, heading_level=1, branch=None, @@ -338,6 +342,9 @@ def generate_activity_md( Include Issues in the markdown output. Default is False. include_opened : bool Include a list of opened items in the markdown output. Default is False. + include_release_notes : bool + Search PR descriptions for a `# Release Notes` block. If found, include + the contents of this block with the changelog output for the PR. strip_brackets : bool If True, strip any text between brackets at the beginning of the issue/PR title. E.g., [MRG], [DOC], etc. @@ -537,9 +544,34 @@ def generate_activity_md( for irow, irowdata in items["data"].iterrows(): author = irowdata["author"] ititle = irowdata["title"] + description = irowdata["body"] if strip_brackets and ititle.strip().startswith("[") and "]" in ititle: ititle = ititle.split("]", 1)[-1].strip() this_md = f"- {ititle} [#{irowdata['number']}]({irowdata['url']}) ([@{author}](https://github.com/{author}))" + + # Search the description for release notes and add them if they exist + if include_release_notes: + release_notes = [] + in_release_notes = False + n_levels = None + for ii in description.split("\n"): + if in_release_notes: + # If we detect a header of equal or lesser level, stop looking + if ii.startswith("#") and len(ii.split(" ")[0]) <= n_levels: + break + # Otherwise append the line and keep going + release_notes.append(ii) + elif "# release notes" in ii.lower(): + # When we detect a release notes header, + # start collecting lines underneath and define the header + # level so we know when to stop + in_release_notes = True + n_levels = len(ii.split(" ")[0]) + + if release_notes: + this_md += "\n\n" + indent( + "\n".join(release_notes).strip(), " " + ) items["md"].append(this_md) # Get functional GitHub references: any git reference or master@{YY-mm-dd} diff --git a/github_activity/graphql.py b/github_activity/graphql.py index 00f7af6..05a4e1a 100644 --- a/github_activity/graphql.py +++ b/github_activity/graphql.py @@ -27,6 +27,7 @@ id title url + body createdAt updatedAt closedAt diff --git a/tests/test_cli.py b/tests/test_cli.py index 6e191a0..5c275e8 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -80,3 +80,19 @@ def test_cli_all(tmpdir, file_regression): md = path_output.read_text() index = md.index("## v0.2.0") file_regression.check(md[index:], extension=".md") + + +def test_release_notes(tmpdir, file_regression): + """Release notes that are automatically pulled from PR descriptions.""" + path_tmp = Path(tmpdir) + path_output = path_tmp.joinpath("out.md") + url = "https://github.com/executablebooks/jupyter-book" + + # This release range covers PRs with + cmd = f"github-activity {url} -s v0.7.1 -u v0.7.3 --include-release-notes -o {path_output}" + + run(cmd.split(), check=True) + + md = path_output.read_text() + test_md = md[md.index("## New features added") : md.index("## Bugs fixed")] + file_regression.check(test_md, extension=".md") diff --git a/tests/test_cli/test_release_notes.md b/tests/test_cli/test_release_notes.md new file mode 100644 index 0000000..c780531 --- /dev/null +++ b/tests/test_cli/test_release_notes.md @@ -0,0 +1,40 @@ +## New features added + +- ✨ NEW: Adding - chapter entries to _toc.yml [#817](https://github.com/executablebooks/jupyter-book/pull/817) ([@choldgraf](https://github.com/choldgraf)) + + Here's an example of the TOC structure: + + ```yaml + - file: intro + numbered: true + + - chapter: Get started + sections: + - file: start/overview + - file: start/build + + - chapter: Book pages and types + sections: + - file: content/markdown + - file: content/notebooks + + - chapter: Reference and test pages + sections: + - file: test_pages/test + sections: + - file: test_pages/layout_elements + - file: test_pages/equations + ``` + +## Enhancements made + +- 👌 IMPROVE: improving numbered sections [#826](https://github.com/executablebooks/jupyter-book/pull/826) ([@choldgraf](https://github.com/choldgraf)) +- checking for toc modification time [#772](https://github.com/executablebooks/jupyter-book/pull/772) ([@choldgraf](https://github.com/choldgraf)) + + This is an attempt at checking for the modification time of the table of contents file and forcing a re-build of all pages if this happens. We need to do this because otherwise people could change the toc, but Sphinx won't know to re-build some pages and these TOC changes won't be reflected. + + #### This heading will be included in release notes + + Here's a sub-heading +- first pass toc directive [#757](https://github.com/executablebooks/jupyter-book/pull/757) ([@choldgraf](https://github.com/choldgraf)) +