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 a38d571a76c617d222e3fab5488c0ffe6d44119f
Author: Konstantin Knauf <[email protected]>
AuthorDate: Fri Apr 16 10:57:55 2021 +0200

    [FLINK-22033] add rule 2, which unassign stale assigned tickets after some 
time
---
 README.md         |   8 ++--
 config.yaml       |   8 ++++
 flink_jira_bot.py | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++----
 3 files changed, 121 insertions(+), 11 deletions(-)

diff --git a/README.md b/README.md
index befa400..503a762 100644
--- a/README.md
+++ b/README.md
@@ -42,11 +42,13 @@ The configuration of the rules can be found in 
[config.yaml](config.yaml).
 
 ### Rule 1 (not implemented yet)
 
-### Rule 2 (not implemented yet)
+### Rule 2: Unassign Stale Assigned Tickets
 
-### Rule 3
+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.
 
-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.
+### Rule 3: Close Stale Minor Tickets
+
+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.
 
 ## About Apache Flink
 
diff --git a/config.yaml b/config.yaml
index 1afae72..d1cb3bb 100644
--- a/config.yaml
+++ b/config.yaml
@@ -16,6 +16,14 @@
 # limitations under the License.
 
################################################################################
 
+stale_assigned:
+    stale_days: 7
+    warning_days: 7
+    warning_label: "stale-assigned"
+    warning_comment: 'This issue is assigned but has not received an update in 
{stale_days} days so it has been labeled "{warning_label}". If you are still 
working on the issue, please give an update and remove the label. If you are no 
longer working on the issue, please unassign so someone else may work on it. In 
{warning_days} days the issue will be automatically unassigned.'
+    done_label: "auto-unassigned"
+    done_comment: 'This issue was marked "{warning_label}" and has not 
received an update in {warning_days} days. It is now automatically unassigned. 
If you are still working on it, you can assign it to yourself again. Please 
also give an update about the status of the work.'
+
 stale_minor:
     stale_days: 180
     warning_days: 7
diff --git a/flink_jira_bot.py b/flink_jira_bot.py
index f67e57e..e1cdc28 100644
--- a/flink_jira_bot.py
+++ b/flink_jira_bot.py
@@ -33,12 +33,32 @@ class FlinkJiraRule:
         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.jira_client.jql(find_subtasks_updated_within, limit=1)
-        return issues["total"] > 0
+        issues = self.get_issues(find_subtasks_updated_within)
+        return len(issues) > 0
 
     def add_label(self, issue, label):
         labels = issue["fields"]["labels"] + [label]
@@ -75,6 +95,12 @@ class FlinkJiraRule:
         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
@@ -101,11 +127,11 @@ class Rule3(FlinkJiraRule):
             f'("{self.warning_label}") AND updated < 
startOfDay(-{self.warning_days}d)'
         )
         logging.info(
-            f"Looking for minor tickets, which were previously marked as 
stale: {minor_tickets_marked_stale}"
+            f"Looking for minor tickets, which were previously marked as 
{self.warning_label}."
         )
-        issues = jira.jql(minor_tickets_marked_stale, limit=10000)
+        issues = self.get_issues(minor_tickets_marked_stale)
 
-        for issue in issues["issues"]:
+        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."
@@ -127,12 +153,84 @@ class Rule3(FlinkJiraRule):
             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):
+    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 minor tickets, which are stale: 
{stale_minor_tickets}"
+            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)"
         )
-        issues = self.jira_client.jql(stale_minor_tickets, limit=10000)
+        logging.info(f"Looking for assigned tickets, which are stale.")
+        issues = self.get_issues(stale_assigned_tickets)
 
-        for issue in issues["issues"]:
+        for issue in issues:
             key = issue["key"]
             issue = self.jira_client.get_issue(key)
 
@@ -190,5 +288,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()

Reply via email to