Skip to content

Commit a5fbcdf

Browse files
committed
script/redmine-upkeep: add Filter class
To add priority and clarity of organization. Signed-off-by: Patrick Donnelly <[email protected]>
1 parent a5bb6da commit a5fbcdf

File tree

1 file changed

+85
-38
lines changed

1 file changed

+85
-38
lines changed

src/script/redmine-upkeep.py

Lines changed: 85 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import argparse
1313
import copy
14+
import inspect
1415
import itertools
1516
import json
1617
import logging
@@ -319,6 +320,15 @@ class RedmineUpkeep:
319320
GITHUB_RATE_LIMITED = False
320321
MAX_UPKEEP_FAILURES = 5
321322

323+
class Filter:
324+
@staticmethod
325+
def get_filters():
326+
raise NotImplementedError("NI")
327+
328+
@staticmethod
329+
def requires_github_api():
330+
raise NotImplementedError("NI")
331+
322332
def __init__(self, args):
323333
self.G = git.Repo(args.git)
324334
self.R = self._redmine_connect()
@@ -355,15 +365,15 @@ def __init__(self, args):
355365
self.transform_methods.sort(key=lambda x: x.__name__)
356366
log.debug(f"Sorted transformation methods: {[m.__name__ for m in self.transform_methods]}")
357367

358-
# Discover filter methods based on prefix
359-
self.filter_methods = []
360-
for name in dir(self):
361-
if name.startswith('_filter_') and callable(getattr(self, name)):
362-
self.filter_methods.append(getattr(self, name))
363-
log.debug(f"Discovered filter methods: {[f.__name__ for f in self.filter_methods]}")
364-
365-
random.shuffle(self.filter_methods)
366-
log.debug(f"Shuffled filter methods for processing order: {[f.__name__ for f in self.filter_methods]}")
368+
# Discover filters based on prefix
369+
self.filters = []
370+
for name, v in RedmineUpkeep.__dict__.items():
371+
if inspect.isclass(v) and issubclass(v, self.Filter) and v != self.Filter:
372+
log.debug("discovered %s", v.NAME)
373+
self.filters.append(v)
374+
random.shuffle(self.filters) # to shuffle equivalent PRIORITY
375+
self.filters.sort(key = lambda filter: filter.PRIORITY, reverse=True)
376+
log.debug(f"Discovered filters: {[f.__name__ for f in self.filters]}")
367377

368378
def _redmine_connect(self):
369379
log.info("Connecting to %s", REDMINE_ENDPOINT)
@@ -373,15 +383,28 @@ def _redmine_connect(self):
373383

374384
# Transformations:
375385

376-
def _filter_merged(self, filters):
377-
log.debug("Applying _filter_merged criteria.")
378-
filters[f"cf_{REDMINE_CUSTOM_FIELD_ID_PULL_REQUEST_ID}"] = '>=0'
379-
filters[f"cf_{REDMINE_CUSTOM_FIELD_ID_MERGE_COMMIT}"] = '!*'
380-
filters["status_id"] = [
381-
REDMINE_STATUS_ID_PENDING_BACKPORT,
382-
REDMINE_STATUS_ID_RESOLVED
383-
]
384-
return True # needs github API
386+
class FilterMerged(Filter):
387+
"""
388+
Filter issues that are closed but no merge commit is set.
389+
"""
390+
391+
PRIORITY = 1000
392+
NAME = "Merged"
393+
394+
@staticmethod
395+
def get_filters():
396+
return {
397+
f"cf_{REDMINE_CUSTOM_FIELD_ID_PULL_REQUEST_ID}": '>=0',
398+
f"cf_{REDMINE_CUSTOM_FIELD_ID_MERGE_COMMIT}": '!*',
399+
"status_id": [
400+
REDMINE_STATUS_ID_PENDING_BACKPORT,
401+
REDMINE_STATUS_ID_RESOLVED,
402+
],
403+
}
404+
405+
@staticmethod
406+
def requires_github_api():
407+
return True
385408

386409
def _transform_merged(self, issue_update):
387410
"""
@@ -464,11 +487,23 @@ def _transform_backport_resolved(self, issue_update):
464487
issue_update.logger.info("Issue is already in 'Resolved' status. No change needed.")
465488
return False
466489

467-
def _filter_released(self, filters):
468-
log.debug("Applying _filter_released criteria.")
469-
filters[f"cf_{REDMINE_CUSTOM_FIELD_ID_MERGE_COMMIT}"] = '*'
470-
filters[f"cf_{REDMINE_CUSTOM_FIELD_ID_RELEASED_IN}"] = '!*'
471-
return False
490+
class FilterReleased(Filter):
491+
"""
492+
Filter for issues that are merged but not yet released.
493+
"""
494+
495+
PRIORITY = 10
496+
NAME = "Released"
497+
498+
@staticmethod
499+
def get_filters():
500+
return {
501+
"status_id": REDMINE_STATUS_ID_PENDING_BACKPORT,
502+
}
503+
504+
@staticmethod
505+
def requires_github_api():
506+
return False
472507

473508
def _transform_released(self, issue_update):
474509
"""
@@ -497,15 +532,27 @@ def _transform_released(self, issue_update):
497532
issue_update.logger.info(f"Commit {commit} not yet in a release. 'Released In' field will not be updated.")
498533
return False
499534

500-
def _filter_issues_pending_backport(self, filters):
535+
536+
class FilterPendingBackport(Filter):
501537
"""
502538
Filter for issues that are in 'Pending Backport' status. The
503539
transformation will then check if they are non-backport trackers and if
504540
all their 'Copied to' backports are resolved.
505541
"""
506-
log.debug("Applying _filter_issues_pending_backport criteria.")
507-
filters["status_id"] = REDMINE_STATUS_ID_PENDING_BACKPORT
508-
return False
542+
543+
PRIORITY = 10
544+
NAME = "Pending Backport"
545+
546+
@staticmethod
547+
def get_filters():
548+
return {
549+
"status_id": REDMINE_STATUS_ID_PENDING_BACKPORT,
550+
}
551+
552+
@staticmethod
553+
def requires_github_api():
554+
return False
555+
509556

510557
def _transform_resolve_main_issue_from_backports(self, issue_update):
511558
"""
@@ -914,35 +961,35 @@ def _execute_filters(self):
914961
# This reduces Redmine API calls for filtering
915962
common_filters = {
916963
"project_id": self.project_id,
917-
"limit": limit,
918964
"sort": f'cf_{REDMINE_CUSTOM_FIELD_ID_UPKEEP_TIMESTAMP}',
919965
"status_id": "*",
920966
f"cf_{REDMINE_CUSTOM_FIELD_ID_TAGS}": "!upkeep-failed",
921967
}
922968
#f"cf_{REDMINE_CUSTOM_FIELD_ID_UPKEEP_TIMESTAMP}": f"<={cutoff_date}", # Not updated recently
923-
log.info("Beginning to loop through shuffled filters.")
924-
for filter_method in self.filter_methods:
969+
970+
log.info("Beginning to loop through filters.")
971+
for f in self.filters:
925972
if limit <= 0:
926973
log.info("Issue processing limit reached. Stopping filter execution.")
927974
break
928-
common_filters['limit'] = limit
929-
filters = copy.deepcopy(common_filters)
930-
needs_github_api = filter_method(filters)
975+
issue_filter = {**common_filters, **f.get_filters()}
976+
issue_filter['limit'] = limit
977+
needs_github_api = f.requires_github_api()
931978
try:
932-
log.info(f"Running filter {filter_method.__name__} with criteria: {filters}")
933-
issues = self.R.issue.filter(**filters)
979+
log.info(f"Running filter {f.NAME} with criteria: {issue_filter}")
980+
issues = self.R.issue.filter(**issue_filter)
934981
issue_count = len(issues)
935-
log.info(f"Filter {filter_method.__name__} returned {issue_count} issue(s).")
982+
log.info(f"Filter {f.NAME} returned {issue_count} issue(s).")
936983
for issue in issues:
937984
if needs_github_api and self.GITHUB_RATE_LIMITED:
938-
log.warning(f"Stopping filter {filter_method.__name__} due to Github rate limits.")
985+
log.warning(f"Stopping filter {f.NAME} due to Github rate limits.")
939986
break
940987
limit = limit - 1
941988
self._process_issue_transformations(issue)
942989
if limit <= 0:
943990
break
944991
except redminelib.exceptions.ResourceAttrError as e:
945-
log.warning(f"Redmine API error with filter {filters}: {e}")
992+
log.warning(f"Redmine API error with filter {issue_filter}: {e}")
946993

947994
def main():
948995
parser = argparse.ArgumentParser(description="Ceph redmine upkeep tool")

0 commit comments

Comments
 (0)