Skip to content

Commit e4c160d

Browse files
authored
[PM-22437] Add product release notes to GitHub Releases (#5318)
1 parent 0f9f9d9 commit e4c160d

File tree

5 files changed

+343
-18
lines changed

5 files changed

+343
-18
lines changed
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# Get Release Notes from Jira script
2+
3+
Fetches release notes from Jira issues.
4+
5+
## Prerequisites
6+
7+
- Python dev environment - use [uv](https://github.com/astral-sh/uv)
8+
- Jira API token. Generate one at: https://id.atlassian.com/manage-profile/security/api-tokens
9+
- Install dependencies:
10+
11+
```bash
12+
uv pip install -r pyproject.toml
13+
```
14+
15+
## Usage
16+
17+
```bash
18+
./jira_release_notes.py RELEASE-1762 [email protected] T0k3n123
19+
```
20+
21+
# Output Format
22+
23+
The script retrieves the content from a custom field and handles two types of Jira release notes formats:
24+
25+
1. Bullet Points:
26+
```
27+
• Point 1
28+
• Point 2
29+
• Point 3
30+
```
31+
32+
2. Single Line:
33+
```
34+
Single line of release notes text
35+
```
36+
37+
## Jira JSON format example
38+
39+
### Single line
40+
41+
```json
42+
...
43+
"customfield_10335": {
44+
"type": "doc",
45+
"version": 1,
46+
"content": [
47+
{
48+
"type": "paragraph",
49+
"content": [
50+
{
51+
"type": "text",
52+
"text": "Single line release notes"
53+
}
54+
]
55+
}
56+
]
57+
},
58+
...
59+
```
60+
61+
### Bullet points
62+
63+
```json
64+
...
65+
"customfield_10335": {
66+
"type": "doc",
67+
"version": 1,
68+
"content": [
69+
{
70+
"type": "bulletList",
71+
"content": [
72+
{
73+
"type": "listItem",
74+
"content": [
75+
{
76+
"type": "paragraph",
77+
"content": [
78+
{
79+
"type": "text",
80+
"text": "Release notes list item 1"
81+
}
82+
]
83+
}
84+
]
85+
},
86+
{
87+
"type": "listItem",
88+
"content": [
89+
{
90+
"type": "paragraph",
91+
"content": [
92+
{
93+
"type": "text",
94+
"text": "Release notes list item 2"
95+
}
96+
]
97+
}
98+
]
99+
},
100+
{
101+
"type": "listItem",
102+
"content": [
103+
{
104+
"type": "paragraph",
105+
"content": [
106+
{
107+
"type": "text",
108+
"text": "Release notes list item 3"
109+
}
110+
]
111+
}
112+
]
113+
},
114+
{
115+
"type": "listItem",
116+
"content": [
117+
{
118+
"type": "paragraph",
119+
"content": [
120+
{
121+
"type": "text",
122+
"text": "Release notes list item 4"
123+
}
124+
]
125+
}
126+
]
127+
}
128+
]
129+
}
130+
]
131+
},
132+
...
133+
```
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#!/usr/bin/env python3
2+
3+
import sys
4+
import base64
5+
import json
6+
import requests
7+
8+
def extract_text_from_content(content):
9+
if isinstance(content, list):
10+
texts = [extract_text_from_content(item) for item in content]
11+
return '\n'.join(text for text in texts if text.strip())
12+
13+
if isinstance(content, dict):
14+
if content.get('type') == 'text':
15+
return content.get('text', '')
16+
elif content.get('type') == 'paragraph':
17+
return extract_text_from_content(content.get('content', []))
18+
elif content.get('type') == 'bulletList':
19+
return extract_text_from_content(content.get('content', []))
20+
elif content.get('type') == 'listItem':
21+
item_text = extract_text_from_content(content.get('content', []))
22+
return f"* {item_text.strip()}"
23+
24+
return ''
25+
26+
def parse_release_notes(response_json):
27+
try:
28+
fields = response_json.get('fields', {})
29+
release_notes_field = fields.get('customfield_10335', {})
30+
31+
if not release_notes_field or not release_notes_field.get('content'):
32+
return ''
33+
34+
release_notes = extract_text_from_content(release_notes_field.get('content', []))
35+
return release_notes
36+
37+
except Exception as e:
38+
print(f"Error parsing release notes: {str(e)}", file=sys.stderr)
39+
return ''
40+
41+
def main():
42+
if len(sys.argv) != 4:
43+
print(f"Usage: {sys.argv[0]} <issue_id> <jira_email> <jira_api_token>")
44+
sys.exit(1)
45+
46+
jira_issue_id = sys.argv[1]
47+
jira_email = sys.argv[2]
48+
jira_api_token = sys.argv[3]
49+
jira_base_url = "https://bitwarden.atlassian.net"
50+
51+
auth = base64.b64encode(f"{jira_email}:{jira_api_token}".encode()).decode()
52+
headers = {
53+
"Authorization": f"Basic {auth}",
54+
"Content-Type": "application/json"
55+
}
56+
57+
response = requests.get(
58+
f"{jira_base_url}/rest/api/3/issue/{jira_issue_id}",
59+
headers=headers
60+
)
61+
62+
if response.status_code != 200:
63+
print(f"Error fetching Jira issue: {response.status_code}", file=sys.stderr)
64+
sys.exit(1)
65+
66+
release_notes = parse_release_notes(response.json())
67+
print(release_notes)
68+
69+
if __name__ == "__main__":
70+
main()
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[project]
2+
name = "jira-get-release-notes"
3+
version = "0.1.0"
4+
description = "Add your description here"
5+
readme = "README.md"
6+
requires-python = ">=3.12"
7+
dependencies = [
8+
"requests>=2.32.3",
9+
]

.github/scripts/jira-get-release-notes/uv.lock

Lines changed: 91 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)