From 1d32ebfbbacd6b9776edf0908fb502bd6f6bfc92 Mon Sep 17 00:00:00 2001 From: Marc Michalsky Date: Tue, 22 Jun 2021 19:43:49 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=89=20initial=20commit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .example_env | 4 +++ .gitignore | 16 +++++++++ README.md | 52 +++++++++++++++++++++++++++++ example_config.yaml | 34 +++++++++++++++++++ main.py | 68 ++++++++++++++++++++++++++++++++++++++ requirements | 3 ++ templates/example_template | 7 ++++ 7 files changed, 184 insertions(+) create mode 100644 .example_env create mode 100644 .gitignore create mode 100644 README.md create mode 100644 example_config.yaml create mode 100644 main.py create mode 100644 requirements create mode 100644 templates/example_template diff --git a/.example_env b/.example_env new file mode 100644 index 0000000..6027133 --- /dev/null +++ b/.example_env @@ -0,0 +1,4 @@ +# Copy this file and name it '.env' +REDMINE_URL="https://redmine.my-awesome-ngo.org" +REDMINE_VERSION="4.1.1" +REDMINE_API_KEY="xxxxxxxxxxx" \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e592bf9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# PyCharm +.idea/ + +# Config +config.yaml + +# Environment +.env +venv/ + +# Templates +templates/* +!templates/example_template + +# Logs +marvin.log diff --git a/README.md b/README.md new file mode 100644 index 0000000..e3916ee --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# Marvin +Marvin is a bot for Redmine. He helps you to tidy up your ticket system by nudging and/or closing abandoned tickets. + +## Configuration +After pulling the repository you have to fulfill the requirements with: +```bash +pip3 install -r requirements +``` +Next copy the example files, rename them to `.env` and `config.yaml` and fill in the right values. + +### Configure actions +You can define different actions for the bot to perform. +Below the `actions` section in the `config.yaml` you can add your own actions. + +```yaml +actions: + + waiting_for_feedback: + start_date: "2021-01-01" # Date from which tickets are to be processed (all tickets from yyyy-mm-dd) + time_range: "14" # Number of days that should be exceeded without updates on the ticket + projects: + - "IT Tickets" # List of redmine projects + status: + - "Waiting for feedback" # List of all issue status you want to treat + close_ticket: true # Should the ticket be closed after the update? + template: "close_ticket" # Template for the update massage + + in_revision: + start_date: "2021-01-01" + time_range: "14" + projects: + - "IT Tickets" + status: + - "In revision" + close_ticket: false + template: "nudge_ticket" +``` + +## Usage +To run the script at regular intervals, simply add it to the crontab. + +Open crontab +```bash +crontab -e +``` + +Add entry +``` +30 8 * * * python3 path/to/main.py +``` +This executes the script every day at 8.30 am. + diff --git a/example_config.yaml b/example_config.yaml new file mode 100644 index 0000000..9e85226 --- /dev/null +++ b/example_config.yaml @@ -0,0 +1,34 @@ +# Copy this file and name it "config.yaml" + +redmine: + url: ${REDMINE_URL} + version: ${REDMINE_VERSION} + api_key: ${REDMINE_API_KEY} + issue_closed_id: 5 # Id of the "closed" status + + +actions: + + waiting_for_feedback: + start_date: "2021-01-01" # Date from which tickets are to be processed (all tickets from yyyy-mm-dd) + time_range: "14" # Number of days that should be exceeded without updates on the ticket + projects: + - "IT Tickets" # List of redmine projects + status: + - "Waiting for feedback" # List of all issue status you want to treat + close_ticket: true # Should the ticket be closed after the update? + template: "close_ticket" # Template for the update massage + + in_revision: + start_date: "2021-01-01" + time_range: "14" + projects: + - "IT Tickets" + status: + - "In revision" + close_ticket: false + template: "nudge_ticket" + + +logging: + level: "ERROR" diff --git a/main.py b/main.py new file mode 100644 index 0000000..f25ad65 --- /dev/null +++ b/main.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +import sys +from datetime import date, timedelta +import logging +from redminelib import Redmine +from envyaml import EnvYAML +from jinja2 import Template + + +def treat_issues(): + + # Set up logging + logging.basicConfig( + filename='marvin.log', + level=logging.ERROR, + format='%(asctime)s - %(message)s', + ) + + # Load configuration + try: + config = EnvYAML("config.yaml") + url = config['redmine']['url'] + version = config['redmine']['version'] + api_key = config['redmine']['api_key'] + actions = config['actions'] + logging.getLogger().setLevel(config['logging']['level'].upper()) + except Exception as e: + logging.error('Could not load config.yaml', exc_info=True) + sys.exit(1) + + # Create Redmine object instance + try: + redmine = Redmine(url, version=version, key=api_key) + except Exception as e: + logging.error('Could not instantiate Redmine object', exc_info=True) + sys.exit(1) + + # Loop through all actions defined in config.yaml + for action in actions.values(): + + # Calculate end_date + end_date = date.today() - timedelta(days=+int(action['time_range'])) + + # Loop through affected issues + try: + for issue in redmine.issue \ + .filter(updated_on=f"><{action['start_date']}|{end_date.isoformat()}") \ + .filter(project__name__in=action['projects'], status__name__in=action['status'], closed_on=None): + with open(f"templates/{action['template']}", newline='\r\n') as f: + content = f.read() + template = Template(content) + notes = template.render(author=issue.author.name, time_range=action['time_range'], url=issue.url) + + # Update issue + if action['close_ticket']: + redmine.issue.update(issue.id, notes=notes, status_id=config['redmine']['issue_closed_id']) + logging.info(f"Ticket ID: {issue.id}, ticket closed") + else: + redmine.issue.update(issue.id, notes=notes) + logging.info(f"Ticket ID: {issue.id}") + + except Exception as e: + logging.error('Could not process issues', exc_info=True) + sys.exit(1) + + +if __name__ == "__main__": + treat_issues() diff --git a/requirements b/requirements new file mode 100644 index 0000000..515bbc2 --- /dev/null +++ b/requirements @@ -0,0 +1,3 @@ +python-redmine~=2.3.0 +envyaml~=1.8.210417 +jinja2~=3.0.1 \ No newline at end of file diff --git a/templates/example_template b/templates/example_template new file mode 100644 index 0000000..94a5b1f --- /dev/null +++ b/templates/example_template @@ -0,0 +1,7 @@ +Hello {{ author }}, + +this ticket wasn't updated for at least {{ time_range }}. We therefore assume that the problem has been solved in the meantime. If the issue persists, please reopen the ticket and give us a brief update on the situation. +Here is the ticket: {{ url }} + +Yours sincerely +Your IT Team \ No newline at end of file