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()