add a script to update issue labels and descriptions (#46)

diff --git a/migration/README.md b/migration/README.md
index f029737..700d864 100644
--- a/migration/README.md
+++ b/migration/README.md
@@ -123,6 +123,17 @@
 [2022-07-06 15:35:06,532] INFO:update_issues: Done.
 ```
 
+### 7. Update issue labels
+
+`src/update_issue_labels.py` updates issue colors and descriptions.
+
+```
+(.venv) migration $ python src/update_issue_labels.py 
+[2022-07-16 09:18:39,764] INFO:update_issue_labels: Retrieving labels.
+[2022-07-16 09:18:42,274] INFO:update_issue_labels: 63 labels are found.
+Done.
+```
+
 ### How to Generate Account Mapping
 
 This optional step creates Jira username - GitHub account mapping. To associate Jira user with GitHub account, Jira user's "Full Name" and GitHub account's "Name" needs to be set to exactly the same value. See https://github.com/apache/lucene-jira-archive/issues/3.
diff --git a/migration/src/github_issues_util.py b/migration/src/github_issues_util.py
index 14fb12e..e97b97e 100644
--- a/migration/src/github_issues_util.py
+++ b/migration/src/github_issues_util.py
@@ -29,10 +29,10 @@
     url = GITHUB_API_BASE + f"/repos/{repo}/issues/{issue_number}"
     headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json"}
     res = requests.get(url, headers=headers)
+    time.sleep(INTERVAL_IN_SECONDS)
     if res.status_code != 200:
         logger.error(f"Failed to get issue {issue_number}; status_code={res.status_code}, message={res.text}")
         return None
-    time.sleep(INTERVAL_IN_SECONDS)
     return res.json().get("body")
 
 
@@ -41,10 +41,10 @@
     headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json"}
     data = {"body": body}
     res = requests.patch(url, headers=headers, json=data)
+    time.sleep(INTERVAL_IN_SECONDS)
     if res.status_code != 200:
         logger.error(f"Failed to update issue {issue_number}; status_code={res.status_code}, message={res.text}")
         return False
-    time.sleep(INTERVAL_IN_SECONDS)
     return True
 
 
@@ -57,6 +57,7 @@
     while not stop:
         url_with_paging = url + f"&page={page}"
         res = requests.get(url_with_paging, headers=headers)
+        time.sleep(INTERVAL_IN_SECONDS)
         if res.status_code != 200:
             logger.error(f"Failed to get issue comments for {issue_number}; status_code={res.status_code}, message={res.text}")
             break
@@ -65,7 +66,6 @@
         for comment in res.json():
             li.append(GHIssueComment(id=comment.get("id"), body=comment.get("body")))
         page += 1
-        time.sleep(INTERVAL_IN_SECONDS)
     return li
 
 
@@ -74,10 +74,10 @@
     headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json"}
     data = {"body": body}
     res = requests.patch(url, headers=headers, json=data)
+    time.sleep(INTERVAL_IN_SECONDS)
     if res.status_code != 200:
         logger.error(f"Failed to update comment {comment_id}; status_code={res.status_code}, message={res.text}")
         return False
-    time.sleep(INTERVAL_IN_SECONDS)
     return True
 
 
@@ -85,9 +85,9 @@
     url = GITHUB_API_BASE + f"/repos/{repo}/import/issues"
     headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github.golden-comet-preview+json"}
     res = requests.post(url, headers=headers, json=issue_data)
+    time.sleep(INTERVAL_IN_SECONDS)
     if res.status_code != 202:
         logger.error(f"Failed to import issue {issue_data['issue']['title']}; status_code={res.status_code}, message={res.text}")
-    time.sleep(INTERVAL_IN_SECONDS)
     return res.json().get("url")
 
 
@@ -116,10 +116,10 @@
     url = GITHUB_API_BASE + f"/search/users?q={quote_plus(q)}"
     headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json"}
     res = requests.get(url, headers=headers)
+    time.sleep(SEARCH_INTERVAL_IN_SECONDS)    
     if res.status_code != 200:
         logger.error(f"Failed to search users with query {q}; status_code={res.status_code}, message={res.text}")
         return []
-    time.sleep(SEARCH_INTERVAL_IN_SECONDS)
     return [item["login"] for item in res.json()["items"]]
 
 
@@ -127,10 +127,10 @@
     url = GITHUB_API_BASE + f"/users/{username}"
     headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json"}
     res = requests.get(url, headers=headers)
+    time.sleep(INTERVAL_IN_SECONDS)
     if res.status_code != 200:
         logger.error(f"Failed to get user {username}; status_code={res.status_code}, message={res.text}")
         return None
-    time.sleep(INTERVAL_IN_SECONDS)
     return res.json()
 
 
@@ -141,15 +141,15 @@
     users = []
     while True:
         res = requests.get(f"{url}&page={page}", headers=headers)
-        if len(res.json()) == 0:
-            break
+        time.sleep(INTERVAL_IN_SECONDS)
         if res.status_code != 200:
             logger.error(f"Failed to get organization members for {org}; status_code={res.status_code}, message={res.text}")
             return users
+        if len(res.json()) == 0:
+            break
         users.extend(x["login"] for x in res.json())
         logger.debug(f"{len(users)} members found.")
         page += 1
-        time.sleep(INTERVAL_IN_SECONDS)
     return users
 
 
@@ -160,17 +160,49 @@
     authors = set([])
     while True:
         res = requests.get(f"{url}&page={page}", headers=headers)
-        if len(res.json()) == 0:
-            break
+        time.sleep(INTERVAL_IN_SECONDS)
         if res.status_code != 200:
             logger.error(f"Failed to get commits for {repo}; status_code={res.status_code}, message={res.text}")
             return authors
+        if len(res.json()) == 0:
+            break
         for commit in res.json():
             author = commit.get("author")
             if author:
                 authors.add(author["login"])
         logger.debug(f"{len(authors)} authors found.")
         page += 1
-        time.sleep(INTERVAL_IN_SECONDS)
     return authors
 
+
+def list_labels(token: str, repo: str, logger: Logger) -> list[str]:
+    url = GITHUB_API_BASE + f"/repos/{repo}/labels?per_page=100"
+    headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json"}
+    page = 1
+    labels = []
+    while True:
+        res = requests.get(f"{url}&page={page}", headers=headers)
+        time.sleep(INTERVAL_IN_SECONDS)
+        if res.status_code != 200:
+            logger.error(f"Failed to get labels for {repo}; status_code={res.status_code}, message={res.text}")
+            return labels
+        if len(res.json()) == 0:
+            break
+        for l in res.json():
+            name = l["name"]
+            labels.append(name)
+        page += 1
+    return labels
+
+
+def update_label(token: str, repo: str, name: str, color: str, description: str, logger: Logger) -> bool:
+    url = GITHUB_API_BASE + f"/repos/{repo}/labels/{name}"
+    headers = {"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json"}
+    data = {"color": color, "description": description}
+    res = requests.patch(url, headers=headers, json=data)
+    time.sleep(INTERVAL_IN_SECONDS)
+    if res.status_code != 200:
+        logger.error(f"Failed to update label {name}; status_code={res.status_code}, message={res.text}")
+        return False
+    logger.debug(f"Label {name} updated.")
+    return True
\ No newline at end of file
diff --git a/migration/src/update_issue_labels.py b/migration/src/update_issue_labels.py
new file mode 100644
index 0000000..dfb2f2b
--- /dev/null
+++ b/migration/src/update_issue_labels.py
@@ -0,0 +1,42 @@
+from pathlib import Path
+import os
+import sys
+from common import LOG_DIRNAME, logging_setup
+from github_issues_util import *
+
+log_dir = Path(__file__).resolve().parent.parent.joinpath(LOG_DIRNAME)
+logger = logging_setup(log_dir, "update_issue_labels")
+
+
+# label prefix -> color, description
+LABEL_DETAILS_MAP = {
+    "type:": ("ffbb00", ""),
+    "fixVersion:": ("7ebea5", ""),
+    "affectsVersion:": ("f19072", ""),
+    "component:": ("a0d8ef", "")
+}
+
+
+if __name__ == "__main__":
+    github_token = os.getenv("GITHUB_PAT")
+    if not github_token:
+        print("Please set your GitHub token to GITHUB_PAT environment variable.")
+        sys.exit(1)
+    github_repo = os.getenv("GITHUB_REPO")
+    if not github_repo:
+        print("Please set GitHub repo location to GITHUB_REPO environment varialbe.")
+        sys.exit(1)
+
+    check_authentication(github_token)
+
+    logger.info("Retrieving labels.")
+
+    labels = list_labels(github_token, github_repo, logger)
+    logger.info(f"{len(labels)} labels are found.")
+
+    for label in labels:
+        for prefix, detail in LABEL_DETAILS_MAP.items():
+            if label.startswith(prefix):
+                update_label(github_token, github_repo, label, detail[0], detail[1], logger)
+
+    print("Done.")
\ No newline at end of file