commit 60760d488028282b84ceb26e95d7fe6c308898d9 Author: Patryk Hegenberg Date: Mon Sep 23 16:29:59 2024 +0200 initial commit to copy repo diff --git a/README.md b/README.md new file mode 100644 index 0000000..6e513b7 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# Changelog Generator + +## Description + +This Python script allows you to generate changelogs and release notes based on Redmine tickets. +It uses the OpenAI API to automatically create the changelog entries. + +## Features + +- Retrieve projects and versions from Redmine +- Retrieve tickets for a specific project and version +- Generate changelog entries based on the ticket information using the OpenAI API +- Output the changelog in AsciiDoc format + +## Prerequisites + +- Python 3.x +- The following Python packages: + - urllib + - json + - dotenv + - ssl + - argparse +- Redmine API key +- OpenAI API key + +## Usage + +1. Copy the `.env.example` file and rename it to `.env`. Fill in the environment variables `REDMINE_HOST`, `REDMINE_API_KEY`, and `OPENAI_API_KEY` with your access credentials. +2. Run the script with the following arguments: + +- `-p, --project`: The project ID for which the changelog should be generated. +- `-f, --fixed-version`: The version ID for which the changelog should be generated. +- `-t, --type`: The type of text to be generated (Changelog or Release Notes). + +3. If you don't provide a project ID or version ID, a list of available projects and versions will be displayed for you to choose from. +4. The generated changelog will be output in AsciiDoc format on the console. + +## Examples + +```bash +python3 changelog_generator.py -p 47 -f 755 -t Changelog +python3 changelog_generator.py -p 47 -f 755 -t ReleaseNotes +``` diff --git a/ai_generator.py b/ai_generator.py new file mode 100644 index 0000000..cc6bed9 --- /dev/null +++ b/ai_generator.py @@ -0,0 +1,219 @@ +import urllib.request as request +import json +from dotenv import load_dotenv +import os +import ssl +import argparse +import textwrap + + +def makeRedmineRequest(url: str, APIKey: str): + context = ssl.create_default_context() + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + + req = request.Request(url, method="GET") + req.add_header("Content-Type", "application/json") + req.add_header("X-Redmine-API-Key", APIKey) + return request.urlopen(req, context=context) + + +def makeProjectRequest(url: str, APIKey: str) -> dict: + res = makeRedmineRequest(url, APIKey) + json_data = json.load(res) + project_info = {project["name"]: project["id"] for project in json_data["projects"]} + + return project_info + + +def makeVersionRequest(url: str, APIKey: str, project_id: int) -> dict: + res = makeRedmineRequest(url, APIKey) + json_data = json.load(res) + + version_info = { + version["name"]: version["id"] + for version in json_data["versions"] + if version["project"]["id"] == project_id and version["status"] == "open" + } + + return version_info + + +def makeIssueRequest(url: str, APIKey: str) -> list: + res = makeRedmineRequest(url, APIKey) + json_data = json.load(res) + issue_info = [] + for issue in json_data["issues"]: + issue_dict = { + "tracker": issue["tracker"]["name"], + "subject": issue["subject"], + "id": issue["id"], + } + issue_info.append(issue_dict) + + return issue_info + + +def openaiRequest(prompt: str, issues: str, api_key: str): + endpoint = "https://api.openai.com/v1/chat/completions" + + request_body = { + "model": "gpt-4-0125-preview", + "messages": [ + { + "role": "system", + "content": "You are a technical writer and responsible for writing the user documentation.", + }, + {"role": "user", "content": prompt + issues}, + ], + } + + headers = {"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"} + + req = request.Request( + endpoint, method="POST", headers=headers, data=json.dumps(request_body).encode() + ) + res = request.urlopen(req) + response_data = json.load(res) + generated_text = response_data["choices"][0]["message"]["content"] + print(generated_text) + + +def main(): + try: + load_dotenv("./.env") + rdmnHost = os.getenv("REDMINE_HOST") + rdmnAPIKey = os.getenv("REDMINE_API_KEY") + openaiAPIKey = os.getenv("OPENAI_API_KEY") + except Exception as e: + print("No Env_variables set: ", e) + finally: + rdmnHost = "https://redmine.tixeltec.de/redmine" + if rdmnAPIKey is None or openaiAPIKey is None: + print( + "Please set environment variables for REDMINE_API_KEY and/or OPENAI_API_KEY" + ) + exit(1) + + desc = """This is a simple tool that allows you to generate changelog messages from Redmine using AI. + +Usage: + 1. Create a `.env` file . Fill in the environment variables as follows: + ``` + REDMINE_HOST=your_redmine_host + REDMINE_API_KEY=your_redmine_api_key + OPENAI_API_KEY=your_openai_api_key + ``` + 2. Run the script with the following arguments:\n + - `-p, --project`: The project ID for which the changelog should be generated. + - `-f, --fixed-version`: The version ID for which the changelog should be generated. + - `-t, --type`: The type of text to be generated (Changelog or Release Notes). + 3. If you don't provide a project ID or version ID, a list of available projects and versions will be displayed for you to choose from. + 4. The generated changelog will be output in AsciiDoc format on the console. + +Examples: + python3 changelog_generator.py -p 47 -f 755 -t Changelog + python3 changelog_generator.py -p 47 -f 755 -t ReleaseNotes +""" + clp = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description=textwrap.dedent(desc), + add_help=True, + ) + clp.add_argument( + "-p", + "--project", + help="The project for which text is to be generated", + type=int, + ) + clp.add_argument( + "-f", + "--fixed-version", + help="The version of the project for which text is to be generated", + type=int, + ) + clp.add_argument( + "-t", + "--type", + help="The type of text to be generated", + choices=["Changelog", "ReleaseNotes"], + type=str, + ) + args = clp.parse_args() + if not args.project: + project_infos = makeProjectRequest( + url=rdmnHost + "/projects.json", APIKey=rdmnAPIKey + ) + print("Enter a project id when calling. There is a choice:") + for project_name, project_id in project_infos.items(): + print(f"{project_name} (ID: {project_id})") + if args.project and not args.fixed_version: + print("Enter a version id when calling. There is a choice:") + fixed_version = makeVersionRequest( + url=rdmnHost + "/projects/" + str(args.project) + "/versions.json", + APIKey=rdmnAPIKey, + project_id=args.project, + ) + for version_name, version_id in fixed_version.items(): + print(f"{version_name}: {version_id}") + if args.project and args.fixed_version and not args.type: + args.type = input("Please choose a type (Changelog/ReleaseNotes)") + customField = "" + prompt = "" + if args.project and args.fixed_version: + if args.type == "Changelog": + customField = "&cf_21=Changelog" + prompt = """Your task is to create the changelog for the new software version from the Redmine tickets: + {{%input%}}. + + == Changelog == + + {{#each tickets}} + === _({{id}})_ {{subject}} === + * {{Your generated changelog entry}} _(#{{ticket_id}})_ + {{/each}} + + Do not include any additional text. + Write your Output in AsciDoc-Format. + If no Redmine Tickets are given, output "No Tickets".""" + elif args.type == "ReleaseNotes": + customField = "&cf_21=Release_notes" + prompt = """Your task is to write a Changelog based on the following redmine tickets: + \{\{\%input\%\}\} + To make the review easier, always enter the subject from the ticket and then a short, descriptive and customer-readable sentence for the respective ticket. + Insert the ticket ID at the end of each sentence in the form _(ticket-id)_. + Write your output in AsciDoc-Format + """ + else: + prompt = """Your task is to create the changelog for the new software version from the Redmine tickets: + {{%input%}}. + + == Changelog == + + {{#each tickets}} + === _({{id}})_ {{subject}} === + * {{Your generated changelog entry}} _(#{{ticket_id}})_ + {{/each}} + + Do not include any additional text. + Write your Output in AsciDoc-Format. + If no Redmine Tickets are given, output "No Tickets".""" + issues = makeIssueRequest( + rdmnHost + + "/issues.json?project_id=" + + str(args.project) + + "&fixed_version_id=" + + str(args.fixed_version) + + "&status_id=*" + + customField, + rdmnAPIKey, + ) + issue_str = "" + for issue in issues: + issue_str += str(issue) + issue_str += ", " + openaiRequest(prompt=prompt, issues=issue_str, api_key=openaiAPIKey) + + +if __name__ == "__main__": + main() diff --git a/env.example b/env.example new file mode 100644 index 0000000..d8fef17 --- /dev/null +++ b/env.example @@ -0,0 +1,3 @@ +REDMINE_API_KEY = "Your redmine api key" +REDMINE_HOST = "ypur redmine host address" +OPENAI_API_KEY = "your openai apikey"