🎉 initial commit
This commit is contained in:
commit
1d32ebfbba
7 changed files with 184 additions and 0 deletions
4
.example_env
Normal file
4
.example_env
Normal file
|
@ -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"
|
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# PyCharm
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Config
|
||||||
|
config.yaml
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
.env
|
||||||
|
venv/
|
||||||
|
|
||||||
|
# Templates
|
||||||
|
templates/*
|
||||||
|
!templates/example_template
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
marvin.log
|
52
README.md
Normal file
52
README.md
Normal file
|
@ -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.
|
||||||
|
|
34
example_config.yaml
Normal file
34
example_config.yaml
Normal file
|
@ -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"
|
68
main.py
Normal file
68
main.py
Normal file
|
@ -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()
|
3
requirements
Normal file
3
requirements
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
python-redmine~=2.3.0
|
||||||
|
envyaml~=1.8.210417
|
||||||
|
jinja2~=3.0.1
|
7
templates/example_template
Normal file
7
templates/example_template
Normal file
|
@ -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
|
Loading…
Add table
Add a link
Reference in a new issue