Skip to content

Commit d1b2cdb

Browse files
Bugfix/85 wrong type used in chapters definition (#122)
* #85 - Wrong type used in chapters definition - Implemented yaml array type as input ofr chapters. - New library in project dependencies. - Change format of input from json string to yaml array.
1 parent 303a57a commit d1b2cdb

File tree

12 files changed

+100
-85
lines changed

12 files changed

+100
-85
lines changed

.github/workflows/release_draft.yml

+10-14
Original file line numberDiff line numberDiff line change
@@ -65,20 +65,16 @@ jobs:
6565
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
6666
with:
6767
tag-name: ${{ github.event.inputs.tag-name }}
68-
from-tag-name: ${{ github.event.inputs.from-tag-name }}
69-
chapters: '[
70-
{"title": "No entry 🚫", "label": "duplicate"},
71-
{"title": "No entry 🚫", "label": "invalid"},
72-
{"title": "No entry 🚫", "label": "wontfix"},
73-
{"title": "No entry 🚫", "label": "no RN"},
74-
{"title": "Breaking Changes 💥", "label": "breaking-change"},
75-
{"title": "New Features 🎉", "label": "enhancement"},
76-
{"title": "New Features 🎉", "label": "feature"},
77-
{"title": "Bugfixes 🛠", "label": "bug"},
78-
{"title": "Infrastructure ⚙️", "label": "infrastructure"},
79-
{"title": "Silent-live 🤫", "label": "silent-live"},
80-
{"title": "Documentation 📜", "label": "documentation"}
81-
]'
68+
chapters: |
69+
- { title: No entry 🚫, label: duplicate }
70+
- { title: Breaking Changes 💥, label: breaking-change }
71+
- { title: New Features 🎉, label: enhancement }
72+
- { title: New Features 🎉, label: feature }
73+
- { title: Bugfixes 🛠, label: bug }
74+
- { title: Infrastructure ⚙️, label: infrastructure }
75+
- { title: Silent-live 🤫, label: silent-live }
76+
- { title: Documentation 📜, label: documentation }
77+
8278
skip-release-notes-label: 'no RN'
8379
verbose: true
8480

README.md

+21-17
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ Generate Release Notes action is dedicated to enhance the quality and organizati
5151
- **Required**: Yes
5252

5353
### `chapters`
54-
- **Description**: A JSON string defining chapters and corresponding labels for categorization. Each chapter should have a title and a label matching your GitHub issues and PRs.
54+
- **Description**: An YAML array defining chapters and corresponding labels for categorization. Each chapter should have a title and a label matching your GitHub issues and PRs.
5555
- **Required**: Yes
5656

5757
### `row-format-issue`
@@ -140,12 +140,11 @@ Add the following step to your GitHub workflow (in example are used non-default
140140
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
141141
with:
142142
tag-name: "v0.1.0"
143-
chapters: '[
144-
{"title": "Breaking Changes 💥", "label": "breaking-change"},
145-
{"title": "New Features 🎉", "label": "enhancement"},
146-
{"title": "New Features 🎉", "label": "feature"},
147-
{"title": "Bugfixes 🛠", "label": "bug"}
148-
]'
143+
chapters: |
144+
- {"title": "Breaking Changes 💥", "label": "breaking-change"}
145+
- {"title": "New Features 🎉", "label": "enhancement"}
146+
- {"title": "New Features 🎉", "label": "feature"}
147+
- {"title": "Bugfixes 🛠", "label": "bug"}
149148
```
150149
151150
#### Full example
@@ -157,12 +156,12 @@ Add the following step to your GitHub workflow (in example are used non-default
157156
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
158157
with:
159158
tag-name: "v0.1.0"
160-
chapters: '[
161-
{"title": "Breaking Changes 💥", "label": "breaking-change"},
162-
{"title": "New Features 🎉", "label": "enhancement"},
163-
{"title": "New Features 🎉", "label": "feature"},
164-
{"title": "Bugfixes 🛠", "label": "bug"}
165-
]'
159+
chapters: |
160+
- {"title": "Breaking Changes 💥", "label": "breaking-change"}
161+
- {"title": "New Features 🎉", "label": "enhancement"}
162+
- {"title": "New Features 🎉", "label": "feature"}
163+
- {"title": "Bugfixes 🛠", "label": "bug"}
164+
166165
duplicity-scope: 'service'
167166
duplicity-icon: '🔁'
168167
published-at: true
@@ -375,11 +374,16 @@ Create *.sh file and place it in the project root.
375374
376375
# Set environment variables based on the action inputs
377376
export INPUT_TAG_NAME="v0.2.0"
377+
378378
export INPUT_CHAPTERS='[
379-
{"title": "Breaking Changes 💥", "label": "breaking-change"},
380-
{"title": "New Features 🎉", "label": "enhancement"},
381-
{"title": "New Features 🎉", "label": "feature"},
382-
{"title": "Bugfixes 🛠", "label": "bug"}
379+
{ title: No entry 🚫, label: duplicate },
380+
{ title: Breaking Changes 💥, label: breaking-change },
381+
{ title: New Features 🎉, label: enhancement },
382+
{ title: New Features 🎉, label: feature },
383+
{ title: Bugfixes 🛠, label: bug },
384+
{ title: Infrastructure ⚙️, label: infrastructure },
385+
{ title: Silent-live 🤫, label: silent-live },
386+
{ title: Documentation 📜, label: documentation }
383387
]'
384388
export INPUT_WARNINGS="true"
385389
export INPUT_PUBLISHED_AT="true"

action.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ inputs:
2121
description: 'The tag name of the release to generate notes for.'
2222
required: true
2323
chapters:
24-
description: 'JSON string defining chapters and corresponding labels for categorization.'
24+
description: 'An string representation of YAML array defining chapters and corresponding labels for categorization. (Created by used "|".)'
2525
required: false
26+
default: ''
2627
duplicity-scope:
2728
description: 'Allow duplicity of issue lines in chapters. Scopes: custom, service, both, none.'
2829
required: false

examples/release_draft.yml

+10-14
Original file line numberDiff line numberDiff line change
@@ -51,20 +51,16 @@ jobs:
5151
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
5252
with:
5353
tag-name: ${{ github.event.inputs.tag-name }}
54-
from-tag-name: ${{ github.event.inputs.from-tag-name }}
55-
chapters: '[
56-
{"title": "No entry 🚫", "label": "duplicate"},
57-
{"title": "No entry 🚫", "label": "invalid"},
58-
{"title": "No entry 🚫", "label": "wontfix"},
59-
{"title": "No entry 🚫", "label": "no RN"},
60-
{"title": "Breaking Changes 💥", "label": "breaking-change"},
61-
{"title": "New Features 🎉", "label": "enhancement"},
62-
{"title": "New Features 🎉", "label": "feature"},
63-
{"title": "Bugfixes 🛠", "label": "bug"},
64-
{"title": "Infrastructure ⚙️", "label": "infrastructure"},
65-
{"title": "Silent-live 🤫", "label": "silent-live"},
66-
{"title": "Documentation 📜", "label": "documentation"}
67-
]'
54+
chapters: |
55+
- { title: No entry 🚫, label: duplicate }
56+
- { title: Breaking Changes 💥, label: breaking-change }
57+
- { title: New Features 🎉, label: enhancement }
58+
- { title: New Features 🎉, label: feature }
59+
- { title: Bugfixes 🛠, label: bug }
60+
- { title: Infrastructure ⚙️, label: infrastructure }
61+
- { title: Silent-live 🤫, label: silent-live }
62+
- { title: Documentation 📜, label: documentation }
63+
6864
skip-release-notes-label: 'no RN'
6965
verbose: true
7066

main.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ def run() -> None:
4646

4747
ActionInputs.validate_inputs()
4848
# Load custom chapters configuration
49-
custom_chapters = CustomChapters(print_empty_chapters=ActionInputs.get_print_empty_chapters()).from_json(
50-
ActionInputs.get_chapters_json()
49+
custom_chapters = CustomChapters(print_empty_chapters=ActionInputs.get_print_empty_chapters()).from_yaml_array(
50+
ActionInputs.get_chapters()
5151
)
5252

5353
generator = ReleaseNotesGenerator(py_github, custom_chapters)

release_notes_generator/action_inputs.py

+24-10
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@
1818
This module contains the ActionInputs class which is responsible for handling the inputs provided to the GH action.
1919
"""
2020

21-
import json
2221
import logging
2322
import os
2423
import sys
2524
import re
2625

26+
from typing import Optional
27+
28+
import yaml
29+
2730
from release_notes_generator.utils.constants import (
2831
GITHUB_REPOSITORY,
2932
GITHUB_TOKEN,
@@ -98,11 +101,24 @@ def is_from_tag_name_defined() -> bool:
98101
return value.strip() != ""
99102

100103
@staticmethod
101-
def get_chapters_json() -> str:
104+
def get_chapters() -> Optional[list[dict[str, str]]]:
102105
"""
103-
Get the chapters JSON from the action inputs.
106+
Get list of the chapters from the action inputs. Each chapter is a dict.
104107
"""
105-
return get_action_input(CHAPTERS)
108+
# Get the 'chapters' input from environment variables
109+
chapters_input: str = get_action_input(CHAPTERS, default="")
110+
111+
# Parse the received string back to YAML array input.
112+
try:
113+
chapters = yaml.safe_load(chapters_input)
114+
if not isinstance(chapters, list):
115+
logger.error("Error: 'chapters' input is not a valid YAML list.")
116+
return None
117+
except yaml.YAMLError as exc:
118+
logger.error("Error parsing 'chapters' input: {%s}", exc)
119+
return None
120+
121+
return chapters
106122

107123
@staticmethod
108124
def get_duplicity_scope() -> DuplicityScopeEnum:
@@ -243,11 +259,9 @@ def validate_inputs() -> None:
243259
if not isinstance(from_tag_name, str):
244260
errors.append("From tag name must be a string.")
245261

246-
chapters_json = ActionInputs.get_chapters_json()
247-
try:
248-
json.loads(chapters_json)
249-
except json.JSONDecodeError:
250-
errors.append("Chapters JSON must be a valid JSON string.")
262+
chapters = ActionInputs.get_chapters()
263+
if chapters is None:
264+
errors.append("Chapters must be a valid yaml array.")
251265

252266
duplicity_icon = ActionInputs.get_duplicity_icon()
253267
if not isinstance(duplicity_icon, str) or not duplicity_icon.strip() or len(duplicity_icon) != 1:
@@ -293,7 +307,7 @@ def validate_inputs() -> None:
293307

294308
logger.debug("Repository: %s/%s", owner, repo_name)
295309
logger.debug("Tag name: %s", tag_name)
296-
logger.debug("Chapters JSON: %s", chapters_json)
310+
logger.debug("Chapters: %s", chapters)
297311
logger.debug("Published at: %s", published_at)
298312
logger.debug("Skip release notes labels: %s", ActionInputs.get_skip_release_notes_labels())
299313
logger.debug("Verbose logging: %s", verbose)

release_notes_generator/model/custom_chapters.py

+5-8
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919
notes.
2020
"""
2121

22-
import json
23-
2422
from release_notes_generator.action_inputs import ActionInputs
2523
from release_notes_generator.model.base_chapters import BaseChapters
2624
from release_notes_generator.model.chapter import Chapter
@@ -58,17 +56,16 @@ def populate(self, records: dict[int, Record]) -> None:
5856
ch.add_row(nr, records[nr].to_chapter_row())
5957
self.populated_record_numbers_list.append(nr)
6058

61-
def from_json(self, json_string: str) -> "CustomChapters":
59+
def from_yaml_array(self, chapters: list[dict[str, str]]) -> "CustomChapters":
6260
"""
6361
Populates the custom chapters from a JSON string.
6462
65-
@param json_string: The JSON string containing the custom chapters.
63+
@param chapters: A list of dictionaries where each dictionary represents a chapter.
6664
@return: The instance of the CustomChapters class.
6765
"""
68-
data = json.loads(json_string)
69-
for item in data:
70-
title = item["title"]
71-
labels = [item["label"]]
66+
for chapter in chapters:
67+
title = chapter["title"]
68+
labels = [chapter["label"]]
7269
if title not in self.chapters:
7370
self.chapters[title] = Chapter(title, labels)
7471
else:

release_notes_generator/utils/pull_reuqest_utils.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
#
1616

1717
"""
18-
This module contains the PullRequestUtils class which is responsible for extracting information from pull requests.
18+
This module contains the PullRequestRecord class which is responsible for representing a record in the release notes.
1919
"""
2020

2121
import re

requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ PyGithub==1.59.0
55
pylint==3.2.6
66
requests==2.31.0
77
black==24.8.0
8+
PyYAML==6.0.2
89
semver==3.0.2

tests/release_notes/model/test_custom_chapters.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -149,17 +149,16 @@ def test_populate_none_duplicity_scope(custom_chapters, mocker):
149149
# from_json
150150

151151

152-
def test_custom_chapters_from_json():
152+
def test_custom_chapters_from_yaml_array():
153153
custom_chapters = CustomChapters()
154-
json_string = """
155-
[
154+
yaml_array_in_string = [
156155
{"title": "Breaking Changes 💥", "label": "breaking-change"},
157156
{"title": "New Features 🎉", "label": "enhancement"},
158157
{"title": "New Features 🎉", "label": "feature"},
159158
{"title": "Bugfixes 🛠", "label": "bug"}
160159
]
161-
"""
162-
custom_chapters.from_json(json_string)
160+
161+
custom_chapters.from_yaml_array(yaml_array_in_string)
163162

164163
assert "Breaking Changes 💥" in custom_chapters.titles
165164
assert "New Features 🎉" in custom_chapters.titles

tests/release_notes/test_release_notes_builder.py

+5-7
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,12 @@ def __init__(self, name):
7878

7979

8080
DEFAULT_CHANGELOG_URL = "http://example.com/changelog"
81-
default_chapters_json = json.dumps(
82-
[
81+
default_chapters = [
8382
{"title": "Breaking Changes 💥", "label": "breaking-change"},
8483
{"title": "New Features 🎉", "label": "feature"},
8584
{"title": "New Features 🎉", "label": "enhancement"},
8685
{"title": "Bugfixes 🛠", "label": "bug"},
8786
]
88-
)
8987

9088
RELEASE_NOTES_NO_DATA = """### Breaking Changes 💥
9189
No entries detected.
@@ -302,7 +300,7 @@ def __init__(self, name):
302300

303301
def test_build_no_data():
304302
custom_chapters = CustomChapters()
305-
custom_chapters.from_json(default_chapters_json)
303+
custom_chapters.from_yaml_array(default_chapters)
306304

307305
expected_release_notes = RELEASE_NOTES_NO_DATA
308306

@@ -318,7 +316,7 @@ def test_build_no_data():
318316

319317
def test_build_no_data_no_warnings(mocker):
320318
custom_chapters = CustomChapters()
321-
custom_chapters.from_json(default_chapters_json)
319+
custom_chapters.from_yaml_array(default_chapters)
322320
mocker.patch("release_notes_generator.builder.ActionInputs.get_warnings", return_value=False)
323321

324322
expected_release_notes = RELEASE_NOTES_NO_DATA_NO_WARNING
@@ -335,7 +333,7 @@ def test_build_no_data_no_warnings(mocker):
335333

336334
def test_build_no_data_no_warnings_no_empty_chapters(mocker):
337335
custom_chapters_no_empty_chapters = CustomChapters()
338-
custom_chapters_no_empty_chapters.from_json(default_chapters_json)
336+
custom_chapters_no_empty_chapters.from_yaml_array(default_chapters)
339337
custom_chapters_no_empty_chapters.print_empty_chapters = False
340338
mocker.patch("release_notes_generator.builder.ActionInputs.get_warnings", return_value=False)
341339
mocker.patch("release_notes_generator.builder.ActionInputs.get_print_empty_chapters", return_value=False)
@@ -354,7 +352,7 @@ def test_build_no_data_no_warnings_no_empty_chapters(mocker):
354352

355353
def test_build_no_data_no_empty_chapters(mocker):
356354
custom_chapters_no_empty_chapters = CustomChapters()
357-
custom_chapters_no_empty_chapters.from_json(default_chapters_json)
355+
custom_chapters_no_empty_chapters.from_yaml_array(default_chapters)
358356
custom_chapters_no_empty_chapters.print_empty_chapters = False
359357
mocker.patch("release_notes_generator.builder.ActionInputs.get_print_empty_chapters", return_value=False)
360358

tests/test_action_inputs.py

+15-6
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@
2323
success_case = {
2424
"get_github_repository": "owner/repo_name",
2525
"get_tag_name": "tag_name",
26-
"get_from_tag_name": "from_tag_name",
27-
"get_chapters_json": '{"chapter": "content"}',
26+
"get_chapters": [{"title": "Title", "label": "Label"}],
2827
"get_duplicity_scope": "custom",
2928
"get_duplicity_icon": "🔁",
3029
"get_warnings": True,
@@ -40,7 +39,7 @@
4039
("get_github_repository", "owner/", "Owner and Repo must be a non-empty string."),
4140
("get_tag_name", "", "Tag name must be a non-empty string."),
4241
("get_from_tag_name", 1, "From tag name must be a string."),
43-
("get_chapters_json", "invalid_json", "Chapters JSON must be a valid JSON string."),
42+
("get_chapters", None, "Chapters must be a valid yaml array."),
4443
("get_warnings", "not_bool", "Warnings must be a boolean."),
4544
("get_published_at", "not_bool", "Published at must be a boolean."),
4645
("get_print_empty_chapters", "not_bool", "Print empty chapters must be a boolean."),
@@ -108,9 +107,19 @@ def test_get_tag_name(mocker):
108107
assert ActionInputs.get_tag_name() == "v1.0.0"
109108

110109

111-
def test_get_chapters_json(mocker):
112-
mocker.patch("release_notes_generator.action_inputs.get_action_input", return_value='{"chapters": []}')
113-
assert ActionInputs.get_chapters_json() == '{"chapters": []}'
110+
def test_get_chapters_success(mocker):
111+
mocker.patch("release_notes_generator.action_inputs.get_action_input", return_value="[{\"title\": \"Title\", \"label\": \"Label\"}]")
112+
assert ActionInputs.get_chapters() == [{"title": "Title", "label": "Label"}]
113+
114+
115+
def test_get_chapters_exception(mocker):
116+
mocker.patch("release_notes_generator.action_inputs.get_action_input", return_value="wrong value")
117+
assert None == ActionInputs.get_chapters()
118+
119+
120+
def test_get_chapters_yaml_error(mocker):
121+
mocker.patch("release_notes_generator.action_inputs.get_action_input", return_value="[{\"title\": \"Title\" \"label\": \"Label\"}]")
122+
assert None == ActionInputs.get_chapters()
114123

115124

116125
def test_get_warnings(mocker):

0 commit comments

Comments
 (0)