You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@flink.apache.org by kn...@apache.org on 2021/05/19 08:09:47 UTC
[flink-jira-bot] 25/47: [hotfix] give rule classes more descriptive
names and refactor to separate files
This is an automated email from the ASF dual-hosted git repository.
knaufk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink-jira-bot.git
commit 425f75f6f56e35e3fe91a7f2804c02e26fd895f3
Author: Konstantin Knauf <kn...@gmail.com>
AuthorDate: Wed Apr 21 11:16:15 2021 +0200
[hotfix] give rule classes more descriptive names and refactor to separate files
---
.gitignore | 1 +
flink_jira_bot.py | 253 ++-----------------------------------------------
flink_jira_rule.py | 103 ++++++++++++++++++++
stale_assigned_rule.py | 101 ++++++++++++++++++++
stale_minor_rule.py | 101 ++++++++++++++++++++
5 files changed, 312 insertions(+), 247 deletions(-)
diff --git a/.gitignore b/.gitignore
index 4b8ee1e..2d85626 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,3 +17,4 @@
################################################################################
venv
+__pycache__
diff --git a/flink_jira_bot.py b/flink_jira_bot.py
index 8b46132..211f855 100644
--- a/flink_jira_bot.py
+++ b/flink_jira_bot.py
@@ -20,252 +20,11 @@ from atlassian import Jira
import logging
import confuse
import os
-import abc
from argparse import ArgumentParser
from pathlib import Path
-
-class FlinkJiraRule:
- __metaclass__ = abc.ABCMeta
-
- def __init__(self, jira_client, config, is_dry_run):
- self.jira_client = jira_client
- self.config = config
- self.is_dry_run = is_dry_run
-
- def get_issues(self, jql_query):
- """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
- :return: a list of issues matching the query
- """
- limit = 200
- current = 0
- total = 1
- issues = []
- while current < total:
- response = self.jira_client.jql(jql_query, limit=limit, start=current)
- total = response["total"]
- issues = issues + response["issues"]
- current = len(issues)
- logging.info(f'"{jql_query}" returned {len(issues)} issues')
- return 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)
- return len(issues) > 0
-
- def add_label(self, issue, label):
- labels = issue["fields"]["labels"] + [label]
- fields = {"labels": labels}
- key = issue["key"]
-
- if not self.is_dry_run:
- self.jira_client.update_issue_field(key, fields)
- else:
- logging.info(f'DRY RUN ({key}): Adding label "{label}".')
-
- def replace_label(self, issue, old_label, new_label):
- labels = issue["fields"]["labels"] + [new_label]
- labels.remove(old_label)
- fields = {"labels": labels}
- key = issue["key"]
-
- if not self.is_dry_run:
- self.jira_client.update_issue_field(key, fields)
- else:
- logging.info(
- f'DRY RUN ({key}): Replace label "{old_label}" for "{new_label}".'
- )
-
- def add_comment(self, key, comment):
- if not self.is_dry_run:
- self.jira_client.issue_add_comment(key, comment)
- else:
- logging.info(f'DRY_RUN ({key}): Adding comment "{comment}".')
-
- def close_issue(self, key):
- if not self.is_dry_run:
- self.jira_client.set_issue_status(
- key, "Closed", fields={"resolution": {"name": "Auto Closed"}}
- )
- else:
- logging.info(f"DRY_RUN (({key})): Closing.")
-
- def unassign(self, key):
- if not self.is_dry_run:
- self.jira_client.assign_issue(key, None)
- else:
- logging.info(f"DRY_RUN (({key})): Unassigning.")
-
- @abc.abstractmethod
- def run(self):
- return
-
-
-class Rule3(FlinkJiraRule):
- """
- An unresolved Minor ticket without an update for {stale_minor.stale_days} is closed after a warning period of
- {stale_minor.warning_days} with a comment that encourages users to watch, comment and simply reopen with a higher
- priority if the problem insists.
- """
-
- def __init__(self, jira_client, config, is_dry_run):
- super().__init__(jira_client, config, is_dry_run)
- self.stale_days = config["stale_minor"]["stale_days"].get()
- self.warning_days = config["stale_minor"]["warning_days"].get()
- self.warning_label = config["stale_minor"]["warning_label"].get()
- self.done_label = config["stale_minor"]["done_label"].get()
- self.done_comment = config["stale_minor"]["done_comment"].get()
- self.warning_comment = config["stale_minor"]["warning_comment"].get()
-
- def run(self):
- self.close_tickets_marked_stale()
- self.mark_stale_tickets_stale()
-
- def close_tickets_marked_stale(self):
-
- minor_tickets_marked_stale = (
- f"project=FLINK AND Priority = Minor AND resolution = Unresolved AND labels in "
- f'("{self.warning_label}") AND updated < startOfDay(-{self.warning_days}d)'
- )
- logging.info(
- f"Looking for minor tickets, which were previously marked as {self.warning_label}."
- )
- issues = self.get_issues(minor_tickets_marked_stale)
-
- for issue in issues:
- key = issue["key"]
- logging.info(
- f"Found https://issues.apache.org/jira/browse/{key}. It is now closed due to inactivity."
- )
-
- formatted_comment = self.done_comment.format(
- warning_days=self.warning_days,
- warning_label=self.warning_label,
- done_label=self.done_label,
- )
-
- self.add_comment(key, formatted_comment)
- self.replace_label(issue, self.warning_label, self.done_label)
- self.close_issue(key)
-
- def mark_stale_tickets_stale(self):
-
- stale_minor_tickets = (
- f"project = FLINK AND Priority = Minor AND resolution = Unresolved AND updated < "
- f"startOfDay(-{self.stale_days}d)"
- )
- logging.info(f"Looking for minor tickets, which are stale.")
- issues = self.get_issues(stale_minor_tickets)
-
- for issue in issues:
- key = issue["key"]
- issue = self.jira_client.get_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(issue, self.warning_label)
- self.add_comment(key, formatted_comment)
-
- else:
- logging.info(
- f"Found https://issues.apache.org/jira/browse/{key}, but is has recently updated Subtasks. "
- f"Ignoring for now."
- )
-
-
-class Rule2(FlinkJiraRule):
- """
- Assigned tickets without an update for {stale_assigned.stale_days} are unassigned after a warning period of
- {stale_assigned.warning_days}. Before this happens the assignee is notified that this is about to happen and
- asked for an update on the status of her contribution.
- """
-
- def __init__(self, jira_client, config, is_dry_run):
- super().__init__(jira_client, config, is_dry_run)
- self.stale_days = config["stale_assigned"]["stale_days"].get()
- self.warning_days = config["stale_assigned"]["warning_days"].get()
- self.warning_label = config["stale_assigned"]["warning_label"].get()
- self.done_label = config["stale_assigned"]["done_label"].get()
- self.done_comment = config["stale_assigned"]["done_comment"].get()
- self.warning_comment = config["stale_assigned"]["warning_comment"].get()
-
- def run(self):
- self.unassign_tickets_marked_stale()
- self.mark_stale_tickets_stale()
-
- def unassign_tickets_marked_stale(self):
-
- assigned_tickets_marked_stale = (
- f"project=FLINK AND resolution = Unresolved AND labels in "
- f'("{self.warning_label}") AND updated < startOfDay(-{self.warning_days}d)'
- )
- logging.info(
- f"Looking for assigned tickets, which were previously marked as {self.warning_label}."
- )
- issues = self.get_issues(assigned_tickets_marked_stale)
-
- for issue in issues:
- key = issue["key"]
- logging.info(
- f"Found https://issues.apache.org/jira/browse/{key}. It is now unassigned due to inactivity."
- )
-
- formatted_comment = self.done_comment.format(
- warning_days=self.warning_days,
- warning_label=self.warning_label,
- done_label=self.done_label,
- )
-
- self.add_comment(key, formatted_comment)
- self.replace_label(issue, self.warning_label, self.done_label)
- self.unassign(key)
-
- def mark_stale_tickets_stale(self):
-
- stale_assigned_tickets = (
- f"project = FLINK AND resolution = Unresolved AND assignee is not EMPTY AND updated < "
- f"startOfDay(-{self.stale_days}d)"
- )
- logging.info(f"Looking for assigned tickets, which are stale.")
- issues = self.get_issues(stale_assigned_tickets)
-
- for issue in issues:
- key = issue["key"]
- issue = self.jira_client.get_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(issue, self.warning_label)
- self.add_comment(key, formatted_comment)
-
- else:
- logging.info(
- f"Found https://issues.apache.org/jira/browse/{key}, but is has recently updated Subtasks. "
- f"Ignoring for now."
- )
+from stale_assigned_rule import StaleAssignedRule
+from stale_minor_rule import StaleMinorRule
def get_args():
@@ -302,7 +61,7 @@ if __name__ == "__main__":
password=os.environ["JIRA_PASSWORD"],
)
- rule_2 = Rule2(jira, jira_bot_config, args.dryrun)
- rule_3 = Rule3(jira, jira_bot_config, args.dryrun)
- rule_2.run()
- rule_3.run()
+ stale_assigned_rule = StaleAssignedRule(jira, jira_bot_config, args.dryrun)
+ stale_minor_rule = StaleMinorRule(jira, jira_bot_config, args.dryrun)
+ stale_assigned_rule.run()
+ stale_minor_rule.run()
diff --git a/flink_jira_rule.py b/flink_jira_rule.py
new file mode 100644
index 0000000..9960aca
--- /dev/null
+++ b/flink_jira_rule.py
@@ -0,0 +1,103 @@
+################################################################################
+# 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.config = config
+ self.is_dry_run = is_dry_run
+
+ def get_issues(self, jql_query):
+ """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
+ :return: a list of issues matching the query
+ """
+ limit = 200
+ current = 0
+ total = 1
+ issues = []
+ while current < total:
+ response = self.jira_client.jql(jql_query, limit=limit, start=current)
+ total = response["total"]
+ issues = issues + response["issues"]
+ current = len(issues)
+ logging.info(f'"{jql_query}" returned {len(issues)} issues')
+ return 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)
+ return len(issues) > 0
+
+ def add_label(self, issue, label):
+ labels = issue["fields"]["labels"] + [label]
+ fields = {"labels": labels}
+ key = issue["key"]
+
+ if not self.is_dry_run:
+ self.jira_client.update_issue_field(key, fields)
+ else:
+ logging.info(f'DRY RUN ({key}): Adding label "{label}".')
+
+ def replace_label(self, issue, old_label, new_label):
+ labels = issue["fields"]["labels"] + [new_label]
+ labels.remove(old_label)
+ fields = {"labels": labels}
+ key = issue["key"]
+
+ if not self.is_dry_run:
+ self.jira_client.update_issue_field(key, fields)
+ else:
+ logging.info(
+ f'DRY RUN ({key}): Replace label "{old_label}" for "{new_label}".'
+ )
+
+ def add_comment(self, key, comment):
+ if not self.is_dry_run:
+ self.jira_client.issue_add_comment(key, comment)
+ else:
+ logging.info(f'DRY_RUN ({key}): Adding comment "{comment}".')
+
+ def close_issue(self, key):
+ if not self.is_dry_run:
+ self.jira_client.set_issue_status(
+ key, "Closed", fields={"resolution": {"name": "Auto Closed"}}
+ )
+ else:
+ logging.info(f"DRY_RUN (({key})): Closing.")
+
+ def unassign(self, key):
+ if not self.is_dry_run:
+ self.jira_client.assign_issue(key, None)
+ else:
+ logging.info(f"DRY_RUN (({key})): Unassigning.")
+
+ @abc.abstractmethod
+ def run(self):
+ return
diff --git a/stale_assigned_rule.py b/stale_assigned_rule.py
new file mode 100644
index 0000000..e781a9d
--- /dev/null
+++ b/stale_assigned_rule.py
@@ -0,0 +1,101 @@
+################################################################################
+# 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 logging
+
+from flink_jira_rule import FlinkJiraRule
+
+
+class StaleAssignedRule(FlinkJiraRule):
+ """
+ Assigned tickets without an update for {stale_assigned.stale_days} are unassigned after a warning period of
+ {stale_assigned.warning_days}. Before this happens the assignee is notified that this is about to happen and
+ asked for an update on the status of her contribution.
+ """
+
+ def __init__(self, jira_client, config, is_dry_run):
+ super().__init__(jira_client, config, is_dry_run)
+ self.stale_days = config["stale_assigned"]["stale_days"].get()
+ self.warning_days = config["stale_assigned"]["warning_days"].get()
+ self.warning_label = config["stale_assigned"]["warning_label"].get()
+ self.done_label = config["stale_assigned"]["done_label"].get()
+ self.done_comment = config["stale_assigned"]["done_comment"].get()
+ self.warning_comment = config["stale_assigned"]["warning_comment"].get()
+
+ def run(self):
+ self.unassign_tickets_marked_stale()
+ self.mark_stale_tickets_stale()
+
+ def unassign_tickets_marked_stale(self):
+
+ assigned_tickets_marked_stale = (
+ f"project=FLINK AND resolution = Unresolved AND labels in "
+ f'("{self.warning_label}") AND updated < startOfDay(-{self.warning_days}d)'
+ )
+ logging.info(
+ f"Looking for assigned tickets, which were previously marked as {self.warning_label}."
+ )
+ issues = self.get_issues(assigned_tickets_marked_stale)
+
+ for issue in issues:
+ key = issue["key"]
+ logging.info(
+ f"Found https://issues.apache.org/jira/browse/{key}. It is now unassigned due to inactivity."
+ )
+
+ formatted_comment = self.done_comment.format(
+ warning_days=self.warning_days,
+ warning_label=self.warning_label,
+ done_label=self.done_label,
+ )
+
+ self.add_comment(key, formatted_comment)
+ self.replace_label(issue, self.warning_label, self.done_label)
+ self.unassign(key)
+
+ def mark_stale_tickets_stale(self):
+
+ stale_assigned_tickets = (
+ f"project = FLINK AND resolution = Unresolved AND assignee is not EMPTY AND updated < "
+ f"startOfDay(-{self.stale_days}d)"
+ )
+ logging.info(f"Looking for assigned tickets, which are stale.")
+ issues = self.get_issues(stale_assigned_tickets)
+
+ for issue in issues:
+ key = issue["key"]
+ issue = self.jira_client.get_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(issue, self.warning_label)
+ self.add_comment(key, formatted_comment)
+
+ else:
+ logging.info(
+ f"Found https://issues.apache.org/jira/browse/{key}, but is has recently updated Subtasks. "
+ f"Ignoring for now."
+ )
diff --git a/stale_minor_rule.py b/stale_minor_rule.py
new file mode 100644
index 0000000..78f7b8d
--- /dev/null
+++ b/stale_minor_rule.py
@@ -0,0 +1,101 @@
+################################################################################
+# 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 logging
+
+from flink_jira_rule import FlinkJiraRule
+
+
+class StaleMinorRule(FlinkJiraRule):
+ """
+ An unresolved Minor ticket without an update for {stale_minor.stale_days} is closed after a warning period of
+ {stale_minor.warning_days} with a comment that encourages users to watch, comment and simply reopen with a higher
+ priority if the problem insists.
+ """
+
+ def __init__(self, jira_client, config, is_dry_run):
+ super().__init__(jira_client, config, is_dry_run)
+ self.stale_days = config["stale_minor"]["stale_days"].get()
+ self.warning_days = config["stale_minor"]["warning_days"].get()
+ self.warning_label = config["stale_minor"]["warning_label"].get()
+ self.done_label = config["stale_minor"]["done_label"].get()
+ self.done_comment = config["stale_minor"]["done_comment"].get()
+ self.warning_comment = config["stale_minor"]["warning_comment"].get()
+
+ def run(self):
+ self.close_tickets_marked_stale()
+ self.mark_stale_tickets_stale()
+
+ def close_tickets_marked_stale(self):
+
+ minor_tickets_marked_stale = (
+ f"project=FLINK AND Priority = Minor AND resolution = Unresolved AND labels in "
+ f'("{self.warning_label}") AND updated < startOfDay(-{self.warning_days}d)'
+ )
+ logging.info(
+ f"Looking for minor tickets, which were previously marked as {self.warning_label}."
+ )
+ issues = self.get_issues(minor_tickets_marked_stale)
+
+ for issue in issues:
+ key = issue["key"]
+ logging.info(
+ f"Found https://issues.apache.org/jira/browse/{key}. It is now closed due to inactivity."
+ )
+
+ formatted_comment = self.done_comment.format(
+ warning_days=self.warning_days,
+ warning_label=self.warning_label,
+ done_label=self.done_label,
+ )
+
+ self.add_comment(key, formatted_comment)
+ self.replace_label(issue, self.warning_label, self.done_label)
+ self.close_issue(key)
+
+ def mark_stale_tickets_stale(self):
+
+ stale_minor_tickets = (
+ f"project = FLINK AND Priority = Minor AND resolution = Unresolved AND updated < "
+ f"startOfDay(-{self.stale_days}d)"
+ )
+ logging.info(f"Looking for minor tickets, which are stale.")
+ issues = self.get_issues(stale_minor_tickets)
+
+ for issue in issues:
+ key = issue["key"]
+ issue = self.jira_client.get_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(issue, self.warning_label)
+ self.add_comment(key, formatted_comment)
+
+ else:
+ logging.info(
+ f"Found https://issues.apache.org/jira/browse/{key}, but is has recently updated Subtasks. "
+ f"Ignoring for now."
+ )