-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.py
134 lines (114 loc) · 5.55 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#!/usr/bin/env python3
import sys
import os
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
logging.basicConfig(
filename=dir_path + 'marvin.log',
level=logging.ERROR,
format='%(asctime)s - %(message)s',
)
# Load configuration
try:
config = EnvYAML(dir_path + 'config.yaml', dir_path + '.env')
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:
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)
# 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 + time_range is not yet reached
if hasattr(issue, 'start_date') and issue.start_date is not None \
and date.today() < (issue.start_date + timedelta(days=+int(action['time_range']))):
logging.info(f'Ticket ID: {issue.id}, skipped because start date + time_range not yet reached')
continue
# Skip issue if due date has not yet passed
if (hasattr(issue, 'due_date') and issue.due_date is not None
and date.today() < (issue.due_date + timedelta(days=1))):
logging.info(f'Ticket ID: {issue.id}, skipped because due date has not yet passed')
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):
logging.info(f'Ticket ID: {issue.id}, skipped because no_bot_tag "{no_bot_tag}" was found')
continue
# Render message template
with open(f"{dir_path}templates/{action['template']}", newline='\r\n') as f:
content = f.read()
template = Template(content)
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 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 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)
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()