Merge branch 'dev' into main

This commit is contained in:
Marc Michalsky 2021-11-08 10:33:14 +01:00
commit 3fcd50ad45
6 changed files with 85 additions and 13 deletions

3
.gitignore vendored
View file

@ -14,3 +14,6 @@ templates/*
# Logs
marvin.log
# pycache
__pycache__/

View file

@ -4,8 +4,9 @@ redmine:
url: ${REDMINE_URL}
version: ${REDMINE_VERSION}
api_key: ${REDMINE_API_KEY}
issue_closed_id: 5 # Id of the "closed" status
time_zone: "Europe/Berlin"
issue_closed_status: "Done" # Name of the "closed" status
no_bot_tag: "#nobot" # a tag that signals bot not to handle an issue
actions:
@ -26,7 +27,7 @@ actions:
- "IT Tickets"
status:
- "In revision"
change_status_to: 4
change_status_to: "Waiting for feedback"
template: "nudge_ticket"

6
exceptions.py Normal file
View file

@ -0,0 +1,6 @@
class IssueStatusNotFoundException(Exception):
def __init__(self, status_name: str):
self.status_name = status_name
def __str__(self):
return f"No issue status with the name \"{self.status_name}\" could be found. Please check your config.yaml."

69
main.py
View file

@ -1,15 +1,27 @@
#!/usr/bin/env python3
import sys
import os
from datetime import date, timedelta
from datetime import date, datetime, timedelta
import pytz
import logging
from redminelib import Redmine
from envyaml import EnvYAML
from jinja2 import Template
from exceptions import IssueStatusNotFoundException
def get_issue_status_id(status_name: str, redmine: Redmine):
all_status = redmine.issue_status.all()
statuses = all_status.filter(name=status_name)
if len(statuses) < 1:
raise IssueStatusNotFoundException(status_name)
elif len(statuses) > 1:
logging.warning(f'Expected one issue status with the name {status_name} but found '
f'{len(statuses)}')
return statuses[0].id
def treat_issues():
dir_path = os.path.dirname(os.path.realpath(__file__)) + '/'
# Set up logging
@ -25,6 +37,9 @@ def treat_issues():
url = config['redmine']['url']
version = config['redmine']['version']
api_key = config['redmine']['api_key']
time_zone = pytz.timezone(config['redmine']['time_zone'])
issue_closed_status = config['redmine']['issue_closed_status']
no_bot_tag = config['redmine']['no_bot_tag']
actions = config['actions']
logging.getLogger().setLevel(config['logging']['level'].upper())
except Exception as e:
@ -38,28 +53,68 @@ def treat_issues():
logging.error('Could not instantiate Redmine object', exc_info=True)
sys.exit(1)
# Get status id for closed issues
issue_closed_status_id = None
try:
issue_closed_status_id = get_issue_status_id(issue_closed_status, redmine)
except IssueStatusNotFoundException as e:
logging.error(e, exc_info=True)
# 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']))
# Get change_status_id
change_status_id = None
if action.get('change_status_to'):
try:
change_status_id = get_issue_status_id(action['change_status_to'], redmine)
except IssueStatusNotFoundException as e:
logging.error(e, exc_info=True)
continue
# 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):
# Skip issue if start date is not yet reached
if hasattr(issue, 'start_date') and date.today() < issue.start_date:
continue
# Skip issue if due date is not yet reached
if hasattr(issue, 'due_date') and date.today() < issue.due_date:
continue
# Skip issue if a no_bot_tag is found in the issue description or any of its journals
def find_no_bot_tag_in_journals(journals):
for journal in journals:
if hasattr(journal, 'notes') and no_bot_tag in journal.notes:
return True
return False
if no_bot_tag in issue.description or find_no_bot_tag_in_journals(issue.journals):
continue
# Render message template
with open(f"{dir_path}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)
days_since_last_update = (datetime.now(time_zone) - issue.updated_on.replace(tzinfo=time_zone)).days + 1
notes = template.render(
issue=issue,
time_range=action['time_range'],
days_since_last_update=days_since_last_update
)
# Update issue
if action.get('close_ticket') is True:
redmine.issue.update(issue.id, notes=notes, status_id=config['redmine']['issue_closed_id'])
if action.get('close_ticket') is True and issue_closed_status_id is not None:
redmine.issue.update(issue.id, notes=notes, status_id=issue_closed_status_id)
logging.info(f"Ticket ID: {issue.id}, ticket closed")
elif action.get('change_status_to') is not None and isinstance(action.get('change_status_to'), int):
redmine.issue.update(issue.id, notes=notes, status_id=action['change_status_to'])
elif change_status_id is not None:
redmine.issue.update(issue.id, notes=notes, status_id=change_status_id)
logging.info(f"Ticket ID: {issue.id}, changed ticket status")
else:
redmine.issue.update(issue.id, notes=notes)

View file

@ -8,3 +8,4 @@ python-redmine==2.3.0
PyYAML==5.4.1
requests==2.25.1
urllib3==1.26.5
pytz~=2021.3

View file

@ -1,7 +1,13 @@
Hello {{ author }},
{% if issue.assigned_to is defined %}
Hello {{ issue.assigned_to }},
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 }}
this ticket is assigned to you but wasn't updated for at least {{ days_since_last_update }}. 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.
{% else %}
Hello {{ issue.author }},
this ticket wasn't updated for at least {{ days_since_last_update }}. 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.
{% endif %}
Here is the ticket: {{ issue.url }}
Yours sincerely
Your IT Team