flink_jira_rule.py (89 lines of code) (raw):

################################################################################ # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ################################################################################ import abc import logging class FlinkJiraRule: __metaclass__ = abc.ABCMeta def __init__(self, jira_client, config, is_dry_run): self.jira_client = jira_client self.is_dry_run = is_dry_run self.stale_days = config["stale_days"].get() self.warning_days = config["warning_days"].get() self.warning_label = config["warning_label"].get() self.done_label = config["done_label"].get() self.done_comment = config["done_comment"].get() self.warning_comment = config["warning_comment"].get() self.ticket_limit = config["ticket_limit"].get() def get_issues(self, jql_query, limit=10000): """Queries the JIRA PI for all issues that match the given JQL Query This method is necessary as requests tend to time out if the number of results reaches a certain number. So, this method requests the results in multiple queries and returns a final list of all issues. :param jql_query: the search query :param limit: the maximum number of issues that should returned :return: a list of issues matching the query """ limit_per_api_request = min(100, limit) current = 0 total = 1 issues = [] while current < min(total, limit): response = self.jira_client.jql( jql_query, limit=limit_per_api_request, start=current ) total = response["total"] issues = issues + response["issues"] current = len(issues) logging.info(f'"{jql_query}" returned {len(issues)} issues') return issues[: min(limit, len(issues))] def has_recently_updated_subtask(self, parent, updated_within_days): find_subtasks_updated_within = ( f"parent = {parent} AND updated > startOfDay(-{updated_within_days}d)" ) issues = self.get_issues(find_subtasks_updated_within, 1) return len(issues) > 0 def add_label_with_comment(self, key, label, comment): if not self.is_dry_run: self.jira_client.edit_issue( key, {"labels": [{"add": label}], "comment": [{"add": {"body": comment}}]}, ) else: logging.info(f'DRY RUN ({key}): Adding label "{label}".') @abc.abstractmethod def run(self): return @abc.abstractmethod def handle_stale_ticket(self, key, warning_label, done_label, comment): return def mark_stale_tickets_stale(self, jql_query): logging.info(f"Looking for stale tickets.") issues = self.get_issues(jql_query) updated_issues_counter = 0 i = 0 while i < len(issues) and updated_issues_counter <= self.ticket_limit: issue = issues[i] key = issue["key"] if not self.has_recently_updated_subtask(key, self.stale_days): logging.info( f"Found https://issues.apache.org/jira/browse/{key}. It is marked stale now." ) formatted_comment = self.warning_comment.format( stale_days=self.stale_days, warning_days=self.warning_days, warning_label=self.warning_label, ) self.add_label_with_comment(key, self.warning_label, formatted_comment) updated_issues_counter += 1 else: logging.info( f"Found https://issues.apache.org/jira/browse/{key}, but is has recently updated Subtasks. " f"Ignoring for now." ) i += 1 def handle_tickets_marked_stale(self, jql_query): logging.info(f"Looking for ticket previously marked as {self.warning_label}.") issues = self.get_issues(jql_query, self.ticket_limit) for issue in issues: key = issue["key"] logging.info( f"Found https://issues.apache.org/jira/browse/{key}. It is now processed as stale." ) formatted_comment = self.done_comment.format( warning_days=self.warning_days, warning_label=self.warning_label, done_label=self.done_label, ) self.handle_stale_ticket( key, self.warning_label, self.done_label, formatted_comment )