snippets

More or less useful code snippets
Log | Files | Refs

gitlab-issue-bot.py (4839B)


      1 #!/usr/bin/python3
      2 from datetime import datetime as dt
      3 import requests
      4 import json
      5 import sys
      6 from os.path import join, expanduser
      7 
      8 DEBUG_MODE = False
      9 # Number of days that issues can be inactive before being considered stale,
     10 # depending on the issue label
     11 LABELS_EXPIRY_DAYS = {
     12         "Doing": 14,
     13         "Current sprint": 30,
     14         "Next sprint": 90,
     15         }
     16 
     17 STALE_LABEL = "Stale"
     18 ISSUE_NOTE_STALE = "Labeled `Stale`, as issue hasn't been updated in a long time"
     19 LOGMSG_STALE_ISSUE = "Issue \u001b[1m{}\u001b[0m with label '{}' is more than {} days old. Marking as stale..."
     20 
     21 # API URLS
     22 URL_BASE = "https://gl.haflan.dev/api/v4"
     23 URL_ISSUES = URL_BASE + "/issues?state=opened&per_page=100"
     24 URL_PROJECTS = URL_BASE + "/projects"
     25 URL_ISSUE_NOTES = URL_BASE + "/projects/{}/issues/{}/notes?sort=desc&order_by=updated_at&per_page=1"
     26 
     27 URL_ISSUE_PUT = URL_BASE + "/projects/{}/issues/{}"  # format(project_id, issue_iid)
     28 URL_NOTE_POST = URL_BASE + "/projects/{}/issues/{}/notes"  # format(project_id, issue_iid)
     29 
     30 # Load token from file
     31 TOKENFILE = join(expanduser("~"), ".secrets/gitlab-token")
     32 if len(sys.argv) > 2:
     33     TOKENFILE = sys.argv[1]
     34 
     35 TOKEN=open(TOKENFILE).readlines()[0].strip()
     36 TOKENHEADER = {"PRIVATE-TOKEN": TOKEN }
     37 
     38 def gitget(URL):
     39     return requests.get(URL, headers=TOKENHEADER).json()
     40 
     41 def gitput(URL, json_data):
     42     return
     43 
     44 # Not sure if this covers all kinds of activity. Check thoroughly
     45 def is_stale(issue, days_before_stale):
     46     latest_update_time = issue["updated_at"]
     47     notes = gitget(URL_ISSUE_NOTES.format(issue["project_id"], issue["iid"]))
     48     if notes:
     49         latest_update_time = notes[0]["updated_at"]
     50     latest_update_time = latest_update_time.split("T")[0] # only Y-m-d needed
     51     dt_diff = dt.now() - dt.strptime(latest_update_time, "%Y-%m-%d")
     52     return dt_diff.days > days_before_stale
     53 
     54 def find_relevant_label(issue):
     55     for label in issue["labels"]:
     56         if label in LABELS_EXPIRY_DAYS:
     57             return label
     58     return None
     59 
     60 def mark_as_stale(issue, old_label):
     61     issue["labels"].remove(old_label)
     62     # Shouldn't be possible, but just in case:
     63     if STALE_LABEL not in issue["labels"]:
     64         issue["labels"].append(STALE_LABEL)
     65     updated_issue = {
     66             "id": issue["id"],
     67             "iid": issue["iid"],
     68             # labels is a comma-separated list, not an array
     69             "labels": "{}".format(','.join(issue["labels"]))
     70             }
     71     issue_url = URL_ISSUE_PUT.format(issue["project_id"], issue["iid"])
     72     note_url = URL_ISSUE_NOTES.format(issue["project_id"], issue["iid"])
     73     information_note = {"body": ISSUE_NOTE_STALE }
     74     if not DEBUG_MODE:
     75         requests.put(issue_url, headers=TOKENHEADER, data=updated_issue)
     76         requests.post(note_url, headers=TOKENHEADER, data=information_note)
     77 
     78 if __name__ == "__main__":
     79     # Make mapping from project ids to names (for logging only)
     80     project_id_name = {}
     81     for project in gitget(URL_PROJECTS):
     82         project_id_name[project["id"]] = project["name"]
     83     # Find and check *all* issues
     84     all_issues = gitget(URL_ISSUES)
     85     for issue in all_issues:
     86         issue_name = "{}#{}".format(project_id_name[issue["project_id"]], issue["iid"])
     87         relevant_label = find_relevant_label(issue)
     88         if relevant_label and is_stale(issue, LABELS_EXPIRY_DAYS[relevant_label]):
     89             print(LOGMSG_STALE_ISSUE.format(issue_name, relevant_label,
     90                     LABELS_EXPIRY_DAYS[relevant_label]))
     91             mark_as_stale(issue, relevant_label)
     92 
     93 """
     94 PROJECT_URL_ISSUES = URL_BASE + "/projects/{}/issues?state=opened&per_page=100"
     95 
     96 # Like groups: No reason to go via project names either
     97 project_id_name = {}
     98 project_name_id = {}
     99 for project in gitget(URL_PROJECTS):
    100     if project["id"] not in project_id_name:
    101         project_id_name[project["id"]] = project["name"]
    102         project_name_id[project["name"]] = project["id"]
    103 
    104 for pid in project_id_name:
    105     #print("\u001b[1m" + project_id_name[pid] + ":\u001b[0m")
    106     for issue in gitget(PROJECT_URL_ISSUES.format(pid)):
    107         if has_relevant_label(issue):
    108             check_expiry(issue)
    109 
    110 """
    111 
    112 """
    113 GROUPS_URL = URL_BASE + "/groups"
    114 # Projects are addressed by their ID, so group is not that important
    115 groups = [  {"id": group["id"], "name": group["name"]} for group in gitget(GROUPS_URL)]
    116 for group in groups:
    117     print("\u001b[1m" + group["name"] + ":\u001b[0m ", end="")
    118     print(len(gitget(GROUPS_URL + "/" + str(group["id"]) + "/issues?per_page=100")))
    119 """
    120 
    121 """
    122 for issue in all_open_issues:
    123     print()
    124     project = project_id_name[int(issue["project_id"])]
    125     issue_str_id = project + "#" + str(issue["iid"])
    126     issue_string_ids[issue_str_id] = issue
    127     print(issue_str_id + ": \u001b[1m" + issue["title"] + "\u001b[0m")
    128     print(issue["description"])
    129     prompt = input()
    130     if prompt == "exit":
    131         exit()
    132 """