From c46a233e9af7be07a1e6f5b69846da37f8cee4ad Mon Sep 17 00:00:00 2001 From: Minos Galanakis Date: Tue, 23 Apr 2024 12:11:06 +0100 Subject: [PATCH 1/5] Archive-Epic: Added script to help moving project items. Signed-off-by: Minos Galanakis --- tools/bin/mbedtls-archive-epic.py | 330 ++++++++++++++++++++++++++++++ 1 file changed, 330 insertions(+) create mode 100644 tools/bin/mbedtls-archive-epic.py diff --git a/tools/bin/mbedtls-archive-epic.py b/tools/bin/mbedtls-archive-epic.py new file mode 100644 index 00000000..0bedbef0 --- /dev/null +++ b/tools/bin/mbedtls-archive-epic.py @@ -0,0 +1,330 @@ +#!/usr/bin/env python3 +""" Assist with the transition of a projectv1 to a projectv2. + This is currently used for management purposes and archiving EPICS. + + It will only move public issues/pr's that have been closed/merged. + + Requires a fully woking gh cli https://cli.github.com/ set-up + and an access token with the appropriate permissions. +""" +# Copyright The Mbed TLS Contributors +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed 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 os +import sys +import json +import requests +from shlex import split +from string import Template +from subprocess import Popen, check_output, PIPE +from pprint import pprint + +token = os.environ['GITHUB_TOKEN'] + + +######################### User Config ########################### +# Instuctions +# - Please ensure that your github cli token is present in your enviroment +# - Set the epic name to tranfer here. +# - Create the epic column to the arhive board, matching the src name. +# - If needed adjust the closed_after date to be in a resonable window +# - Fine tune max entries selection if some entries are missing. +# - Private issues and drafts are not picked up by the script. + +epic = "Mbed TLS 3.6 release" # Name of epic that needs to be moved + # (It needs to match an empty epic on v2 board) +closed_after_date = "2023-10-01" # Only include entries closed after that date + +max_entries = 500 # How many maximum entries the backend will populate. +debug = False # Set to True to print out the github cli commands. + +############################# Constants ############################# +""" +The following values should remain the same across the project. +If need to modify them, they can querried as follows: + +gh project list --owner Mbed-TLS number 13 + NUMBER TITLE STATE ID + 13 Past EPICs open PVT_kwDOBcuPHc4AgPo7 + +gh project --owner Mbed-TLS field-list 13 + NAME DATA TYPE ID + Status ProjectV2SingleSelectField PVTSSF_lADOBcuPHc4AgPo7zgVai1o + +""" + +projectv2_project_past_epics = "PVT_kwDOBcuPHc4AgPo7" +projectv2_field_status = "PVTSSF_lADOBcuPHc4AgPo7zgVai1o" +projectv2_field_owner = "Mbed-TLS" +projectsv1_repo = "Mbed-TLS/mbedtls" +active_project = "EPICs for Mbed TLS" +archive_project = "Past EPICs" +archive_project_board_no = "13" + +############################ Github Cli gap ######################### +""" This snippet is covering functionality missing for the current version of github cli. + We need to querry the node-id for the Status SingleSelectField, since that is used as + epic name columns. """ + +gql_get_ids_querry = Template( + """ + query{ + node(id: "$project_uid") { + ... on ProjectV2 { + fields(first: 100) { + nodes { + ... on ProjectV2SingleSelectField { + name options { + id + name + } + } + } + } + } + } +}""" +) + +########################### Helpers ################################# + +class ShellExecResult: + + """ + Class to encapsulate the returns from a shell execution. + """ + + def __init__(self, success, stdout, stderr): + + self.success = success + self.stdout = stdout + self.stderr = stderr + + +def do_shell_exec(exec_string, expected_result=0): + """ + Helper function to do shell execution + exec_string - String to execute (as is - function will split) + expected_result - Expected return code. + """ + + if debug: + print("CMD", exec_string) + shell_process = Popen( + split(exec_string), + stdin=PIPE, + stdout=PIPE, + stderr=PIPE) + + (shell_stdout, shell_stderr) = shell_process.communicate() + + return ShellExecResult(shell_process.returncode == expected_result, + shell_stdout.decode("utf-8"), + shell_stderr.decode("utf-8")) + + +def github_querry(query, token=token): + """ Create an authenticated querry to github""" + + request = requests.post('https://api.github.com/graphql', + json={'query': query}, + headers={"Authorization": "token " + token}) + if request.status_code == 200: + return request.json() + else: + raise Exception( + "Query failed with code {}. Query: {}".format( + request.status_code, query)) + + +def projectv2_get_label_ids(project_id=projectv2_project_past_epics): + """Use a gql querry to retrieve the node-ids for the column names of projectV2 """ + + labels = [] + querry = gql_get_ids_querry.substitute(project_uid=project_id) + result = github_querry(querry) + # clean up the result + nodes = [n for n in result["data"]["node"]["fields"]["nodes"] if n] + return nodes.pop()["options"] + + +####################### Github Cli Wrappers ######################### +def get_issues_prs_for_project_v1(issues=True, + from_date=closed_after_date, + count=max_entries, + repo=projectsv1_repo): + """ Retrieve a maximum n=count number issues or pull requests closed after a date """ + + cmd = ("gh {0} --repo {3} list -L {1} -s all --search \"is:{0} is:closed " + "closed:>{2}\" --json 'number,projectCards'").format("issue" if issues else "pr", + count, from_date, + repo) + ret = do_shell_exec(cmd) + data = json.loads(ret.stdout) + data = [n for n in data if n["projectCards"]] + data = [{n["number"]: n["projectCards"]} for n in data] + return data + + +def get_issues_prs_for_name_for_project_v1(column_name, + project_name=active_project, + count=max_entries): + """ Retrieve all issues AND pull requests under a column of a projectv1 """ + + output = {"epic": column_name, + "issues": [], "prs": []} + + issues = get_issues_prs_for_project_v1(count=count) + + for n in issues: + for issue_num, issue_proj_list in n.items(): + # An item can have multiple projects assosiated with it. + for issue_proj in issue_proj_list: + n_pname = issue_proj["project"]["name"] + n_cname = issue_proj["column"]["name"] + # Matches the expected project board + if n_pname == project_name and n_cname == column_name: + output["issues"].append(str(issue_num)) + else: + continue + + prs = get_issues_prs_for_project_v1(issues=False, count=count) + for n in prs: + for issue_num, issue_proj_list in n.items(): + # An item can have multiple projects assosiated with it. + for issue_proj in issue_proj_list: + n_pname = issue_proj["project"]["name"] + n_cname = issue_proj["column"]["name"] + # Matches the expected project board + if n_pname == project_name and n_cname == column_name: + output["prs"].append(str(issue_num)) + else: + continue + return output + + +def get_no_status_issues_prs_for_project_v2(count=100): + """ Get the contents for the No Status Column in project v2 """ + + cmd = "gh project item-list --owner {} {} -L {} --format json".format( + projectv2_field_owner, archive_project_board_no, count) + + output = {} + ret = do_shell_exec(cmd) + data = json.loads(ret.stdout) + + for entry in data["items"]: + if "status" not in entry.keys(): + if entry["content"]["type"] == "Issue": + output[entry["content"]["number"]] = entry["id"] + else: + output[entry["content"]["number"]] = entry["id"] + return output + + +def move_issue_to_proj(issue, old_proj, new_proj, is_pr=False, repo=projectsv1_repo): + """ Moves an issue across projects. It works across legacy and v2 projects """ + + # Remove the issue from old project + cmd = "gh {} edit {} --repo {} --{}-project \"{}\"".format( + "pr" if is_pr else "issue", issue, repo, "remove", old_proj) + ret = do_shell_exec(cmd) + if ret.success: + # #Add it to the new project + cmd = "gh {} edit {} --repo {} --{}-project \"{}\"".format( + "pr" if is_pr else "issue", issue, repo, "add", new_proj) + ret = do_shell_exec(cmd) + if ret.success: + print( + "Successuflly moved issue {} from project: {} -> {}".format(issue, old_proj, new_proj)) + else: + print(cmd) + print( + "Failed to remove issue {} from project: {} sterr: {}".format( + issue, old_proj, ret.stderr)) + else: + print(cmd) + print( + "Failed to remove issue {} from project: {} sterr: {}".format( + issue, + old_proj, + ret.stderr)) + + +def move_to_column_project_v2(item_node_id, + column_node_id, + field_node_id=projectv2_field_status, + project_node_id=projectv2_project_past_epics): + """ Move an issue/pr by its' node-id on a project v2 project """ + cmd = ("gh project item-edit --id {} --field-id {} --project-id {} " + "--single-select-option-id {}").format(item_node_id, + field_node_id, + project_node_id, + column_node_id) + ret = do_shell_exec(cmd) + if not ret.success: + print( + "Failed to move issue {} sterr: {}".format( + issue, + old_proj, + old_proj)) + + +if __name__ == "__main__": + + # Do not proceed if an empty epic has not been created + label_matches = [n for n in projectv2_get_label_ids() if n["name"] == epic] + + if not label_matches: + print( + "Error, please create an epic with name \"{}\" and re-run. ".format(epic)) + sys.exit(1) + + # Extract the node_id if for the match + column_node_id = label_matches[0]["id"] + + # Querry all the items (prs and issues) + epic_items = get_issues_prs_for_name_for_project_v1(epic) + epic_name = epic_items["epic"] + epic_issues = epic_items["issues"] + epic_prs = epic_items["prs"] + + # Ask for user confirmation + print("Found the following items:") + print("\nEpic Name:", epic_name) + print("\nIssues") + pprint(epic_issues) + print("\nPRs") + pprint(epic_prs) + + usr = input("Procceed with move? Y/N?") + if usr.lower() != "y": + print("Error, aborting on user request") + sys.exit(1) + + # Move items to new project + for issue in epic_issues: + move_issue_to_proj(issue, active_project, archive_project) + + for pr in epic_prs: + move_issue_to_proj(pr, active_project, archive_project, is_pr=True) + + # Get the new items by the No Status collumn + no_stat_isues = get_no_status_issues_prs_for_project_v2() + + # Move them to the matching epic + for pr_no, node_id in no_stat_isues.items(): + move_to_column_project_v2(node_id, column_node_id) + print("Moving {} to {}".format(pr_no, epic)) From 0be49b6244871189925c4331d66951d2370b7cf5 Mon Sep 17 00:00:00 2001 From: Minos Galanakis Date: Fri, 25 Oct 2024 12:28:18 +0100 Subject: [PATCH 2/5] archive-epic: Moved project V1 logic on its own function Signed-off-by: Minos Galanakis --- tools/bin/mbedtls-archive-epic.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/tools/bin/mbedtls-archive-epic.py b/tools/bin/mbedtls-archive-epic.py index 0bedbef0..3d563b2b 100644 --- a/tools/bin/mbedtls-archive-epic.py +++ b/tools/bin/mbedtls-archive-epic.py @@ -281,17 +281,7 @@ def move_to_column_project_v2(item_node_id, old_proj, old_proj)) - -if __name__ == "__main__": - - # Do not proceed if an empty epic has not been created - label_matches = [n for n in projectv2_get_label_ids() if n["name"] == epic] - - if not label_matches: - print( - "Error, please create an epic with name \"{}\" and re-run. ".format(epic)) - sys.exit(1) - +def archive_project_v1(label_matches): # Extract the node_id if for the match column_node_id = label_matches[0]["id"] @@ -328,3 +318,17 @@ def move_to_column_project_v2(item_node_id, for pr_no, node_id in no_stat_isues.items(): move_to_column_project_v2(node_id, column_node_id) print("Moving {} to {}".format(pr_no, epic)) + +if __name__ == "__main__": + + # Do not proceed if an empty epic has not been created + label_matches = [n for n in projectv2_get_label_ids() if n["name"] == epic] + + if not label_matches: + print( + "Error, please create an epic with name \"{}\" and re-run. ".format(epic)) + sys.exit(1) + + archive_project_v1(label_matches) + + From e6d748c8a9cd63ef4ccd23fa51bf2e080df0f4c8 Mon Sep 17 00:00:00 2001 From: Minos Galanakis Date: Fri, 25 Oct 2024 12:29:20 +0100 Subject: [PATCH 3/5] archive-epic: Added id for Epics for MbedTLS Signed-off-by: Minos Galanakis --- tools/bin/mbedtls-archive-epic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/bin/mbedtls-archive-epic.py b/tools/bin/mbedtls-archive-epic.py index 3d563b2b..cc86538f 100644 --- a/tools/bin/mbedtls-archive-epic.py +++ b/tools/bin/mbedtls-archive-epic.py @@ -64,7 +64,7 @@ Status ProjectV2SingleSelectField PVTSSF_lADOBcuPHc4AgPo7zgVai1o """ - +projectv2_project_epics_for_mbedtls = "PVT_kwDOBcuPHc4AnF0W" projectv2_project_past_epics = "PVT_kwDOBcuPHc4AgPo7" projectv2_field_status = "PVTSSF_lADOBcuPHc4AgPo7zgVai1o" projectv2_field_owner = "Mbed-TLS" From e70212f95c821dd806b2074a2ae018fa791c8f8c Mon Sep 17 00:00:00 2001 From: Minos Galanakis Date: Mon, 28 Oct 2024 14:00:33 +0000 Subject: [PATCH 4/5] archive-epic: Updated script to handle migration of v2 to v2 boards Signed-off-by: Minos Galanakis --- tools/bin/mbedtls-archive-epic.py | 105 ++++++++++++++++++++++++++++-- 1 file changed, 101 insertions(+), 4 deletions(-) diff --git a/tools/bin/mbedtls-archive-epic.py b/tools/bin/mbedtls-archive-epic.py index cc86538f..9bd769ba 100644 --- a/tools/bin/mbedtls-archive-epic.py +++ b/tools/bin/mbedtls-archive-epic.py @@ -43,12 +43,12 @@ # - Fine tune max entries selection if some entries are missing. # - Private issues and drafts are not picked up by the script. -epic = "Mbed TLS 3.6 release" # Name of epic that needs to be moved +epic = "3.6.1 patch release" # Name of epic that needs to be moved # (It needs to match an empty epic on v2 board) -closed_after_date = "2023-10-01" # Only include entries closed after that date +closed_after_date = "2024-01-01" # Only include entries closed after that date max_entries = 500 # How many maximum entries the backend will populate. -debug = False # Set to True to print out the github cli commands. +debug = True # Set to True to print out the github cli commands. ############################# Constants ############################# """ @@ -69,6 +69,7 @@ projectv2_field_status = "PVTSSF_lADOBcuPHc4AgPo7zgVai1o" projectv2_field_owner = "Mbed-TLS" projectsv1_repo = "Mbed-TLS/mbedtls" +projects_repo = projectsv1_repo active_project = "EPICs for Mbed TLS" archive_project = "Past EPICs" archive_project_board_no = "13" @@ -177,6 +178,66 @@ def get_issues_prs_for_project_v1(issues=True, data = [{n["number"]: n["projectCards"]} for n in data] return data +def get_issues_prs_for_project(issues=True, + is_closed=True, + from_date=closed_after_date, + count=max_entries, + repo=projects_repo): + """ Retrieve a maximum n=count number issues or pull requests closed after a date """ + + cmd = ("gh {0} --repo {5} list -L {3} -s all --search \"is:{0} is:{1} " + "{2}:>{4}\" --json 'number,projectItems'").format("issue" if issues else "pr", + "closed" if is_closed else "open", + "closed" if is_closed else "created", + count, from_date, + repo) + ret = do_shell_exec(cmd) + data = json.loads(ret.stdout) + data = [n for n in data if n["projectItems"]] + + data = [{n["number"]: n["projectItems"]} for n in data] + return data + +def link_issues_pr_linked_to_epic(raw_data, epic_name, projectboard = ""): + """ Parse a raw data output and return a list of items linked to a selected epic.""" + + linked = [] + for i in raw_data: + for k, v in i.items(): + for e in v: + if epic_name and projectboard: + if e["status"]["name"] == epic_name and e['title'] == projectboard: + linked.append(k) + else: + if e["status"]["name"] == epic_name: + linked.append(k) + return linked + +def get_epic_items(column_name, status="both", projectboard=active_project, repo=projects_repo): + """ Return a structured data-set containing an epic name and all the open/closed issues in it.""" + + output = {"epic": column_name, + "issues": [], "prs": []} + + if status not in ["open", "closed", "both"]: + print("Error: Invalid entry for status:", status) + return + is_closed = "closed" == status + issues = get_issues_prs_for_project(issues=True, is_closed=is_closed, repo=repo) + output["issues"] = link_issues_pr_linked_to_epic(issues, column_name, projectboard) + + prs = get_issues_prs_for_project(issues=False, is_closed=is_closed, repo=repo) + output["prs"] = link_issues_pr_linked_to_epic(prs, column_name, projectboard) + + # If both is selected just toggle the is_closed state and append the results + if status == "both": + is_closed ^=True + issues = get_issues_prs_for_project(issues=True, is_closed=is_closed, repo=repo) + output["issues"] += link_issues_pr_linked_to_epic(issues, column_name, projectboard) + + prs = get_issues_prs_for_project(issues=False, is_closed=is_closed, repo=repo) + output["prs"] += link_issues_pr_linked_to_epic(prs, column_name, projectboard) + return output def get_issues_prs_for_name_for_project_v1(column_name, project_name=active_project, @@ -319,6 +380,42 @@ def archive_project_v1(label_matches): move_to_column_project_v2(node_id, column_node_id) print("Moving {} to {}".format(pr_no, epic)) +def archive_project_v2(label_matches): + print(label_matches) + epic_items = get_epic_items(epic) + from pprint import pprint + epic_name = epic_items["epic"] + epic_issues = epic_items["issues"] + epic_prs = epic_items["prs"] + + # Ask for user confirmatwion + print("Found the following items:") + print("\nEpic Name:", epic_name) + print("\nIssues") + pprint(epic_issues) + print("\nPRs") + pprint(epic_prs) + + usr = input("Procceed with move? Y/N?") + if usr.lower() != "y": + print("Error, aborting on user request") + sys.exit(1) + + # Move items to new project + for issue in epic_issues: + move_issue_to_proj(issue, active_project, archive_project) + + for pr in epic_prs: + move_issue_to_proj(pr, active_project, archive_project, is_pr=True) + + # Get the new items by the No Status collumn + no_stat_isues = get_no_status_issues_prs_for_project_v2() + + # Move them to the matching epic + for pr_no, node_id in no_stat_isues.items(): + move_to_column_project_v2(node_id, column_node_id) + print("Moving {} to {}".format(pr_no, epic)) + if __name__ == "__main__": # Do not proceed if an empty epic has not been created @@ -329,6 +426,6 @@ def archive_project_v1(label_matches): "Error, please create an epic with name \"{}\" and re-run. ".format(epic)) sys.exit(1) - archive_project_v1(label_matches) + archive_project_v2(label_matches) From b71f8d8bcf2df41b82e7c8a8c74975edd582a977 Mon Sep 17 00:00:00 2001 From: Minos Galanakis Date: Mon, 28 Oct 2024 14:21:06 +0000 Subject: [PATCH 5/5] Archive-epic: Removed obsolete projectv1 code and refferences Signed-off-by: Minos Galanakis --- tools/bin/mbedtls-archive-epic.py | 131 +++++------------------------- 1 file changed, 20 insertions(+), 111 deletions(-) diff --git a/tools/bin/mbedtls-archive-epic.py b/tools/bin/mbedtls-archive-epic.py index 9bd769ba..9736996b 100644 --- a/tools/bin/mbedtls-archive-epic.py +++ b/tools/bin/mbedtls-archive-epic.py @@ -64,12 +64,12 @@ Status ProjectV2SingleSelectField PVTSSF_lADOBcuPHc4AgPo7zgVai1o """ -projectv2_project_epics_for_mbedtls = "PVT_kwDOBcuPHc4AnF0W" -projectv2_project_past_epics = "PVT_kwDOBcuPHc4AgPo7" -projectv2_field_status = "PVTSSF_lADOBcuPHc4AgPo7zgVai1o" -projectv2_field_owner = "Mbed-TLS" -projectsv1_repo = "Mbed-TLS/mbedtls" -projects_repo = projectsv1_repo +project_epics_for_mbedtls = "PVT_kwDOBcuPHc4AnF0W" +project_past_epics = "PVT_kwDOBcuPHc4AgPo7" +project_field_status = "PVTSSF_lADOBcuPHc4AgPo7zgVai1o" +project_field_owner = "Mbed-TLS" + +projects_repo = "Mbed-TLS/mbedtls" active_project = "EPICs for Mbed TLS" archive_project = "Past EPICs" archive_project_board_no = "13" @@ -150,7 +150,7 @@ def github_querry(query, token=token): request.status_code, query)) -def projectv2_get_label_ids(project_id=projectv2_project_past_epics): +def project_get_label_ids(project_id=project_past_epics): """Use a gql querry to retrieve the node-ids for the column names of projectV2 """ labels = [] @@ -162,22 +162,6 @@ def projectv2_get_label_ids(project_id=projectv2_project_past_epics): ####################### Github Cli Wrappers ######################### -def get_issues_prs_for_project_v1(issues=True, - from_date=closed_after_date, - count=max_entries, - repo=projectsv1_repo): - """ Retrieve a maximum n=count number issues or pull requests closed after a date """ - - cmd = ("gh {0} --repo {3} list -L {1} -s all --search \"is:{0} is:closed " - "closed:>{2}\" --json 'number,projectCards'").format("issue" if issues else "pr", - count, from_date, - repo) - ret = do_shell_exec(cmd) - data = json.loads(ret.stdout) - data = [n for n in data if n["projectCards"]] - data = [{n["number"]: n["projectCards"]} for n in data] - return data - def get_issues_prs_for_project(issues=True, is_closed=True, from_date=closed_after_date, @@ -239,48 +223,11 @@ def get_epic_items(column_name, status="both", projectboard=active_project, repo output["prs"] += link_issues_pr_linked_to_epic(prs, column_name, projectboard) return output -def get_issues_prs_for_name_for_project_v1(column_name, - project_name=active_project, - count=max_entries): - """ Retrieve all issues AND pull requests under a column of a projectv1 """ - - output = {"epic": column_name, - "issues": [], "prs": []} - - issues = get_issues_prs_for_project_v1(count=count) - - for n in issues: - for issue_num, issue_proj_list in n.items(): - # An item can have multiple projects assosiated with it. - for issue_proj in issue_proj_list: - n_pname = issue_proj["project"]["name"] - n_cname = issue_proj["column"]["name"] - # Matches the expected project board - if n_pname == project_name and n_cname == column_name: - output["issues"].append(str(issue_num)) - else: - continue - - prs = get_issues_prs_for_project_v1(issues=False, count=count) - for n in prs: - for issue_num, issue_proj_list in n.items(): - # An item can have multiple projects assosiated with it. - for issue_proj in issue_proj_list: - n_pname = issue_proj["project"]["name"] - n_cname = issue_proj["column"]["name"] - # Matches the expected project board - if n_pname == project_name and n_cname == column_name: - output["prs"].append(str(issue_num)) - else: - continue - return output - - -def get_no_status_issues_prs_for_project_v2(count=100): +def get_no_status_issues_prs_for_project(count=100): """ Get the contents for the No Status Column in project v2 """ cmd = "gh project item-list --owner {} {} -L {} --format json".format( - projectv2_field_owner, archive_project_board_no, count) + project_field_owner, archive_project_board_no, count) output = {} ret = do_shell_exec(cmd) @@ -295,7 +242,7 @@ def get_no_status_issues_prs_for_project_v2(count=100): return output -def move_issue_to_proj(issue, old_proj, new_proj, is_pr=False, repo=projectsv1_repo): +def move_issue_to_proj(issue, old_proj, new_proj, is_pr=False, repo=projects_repo): """ Moves an issue across projects. It works across legacy and v2 projects """ # Remove the issue from old project @@ -324,10 +271,10 @@ def move_issue_to_proj(issue, old_proj, new_proj, is_pr=False, repo=projectsv1_r ret.stderr)) -def move_to_column_project_v2(item_node_id, +def move_to_column_project(item_node_id, column_node_id, - field_node_id=projectv2_field_status, - project_node_id=projectv2_project_past_epics): + field_node_id=project_field_status, + project_node_id=project_past_epics): """ Move an issue/pr by its' node-id on a project v2 project """ cmd = ("gh project item-edit --id {} --field-id {} --project-id {} " "--single-select-option-id {}").format(item_node_id, @@ -338,49 +285,11 @@ def move_to_column_project_v2(item_node_id, if not ret.success: print( "Failed to move issue {} sterr: {}".format( - issue, - old_proj, - old_proj)) + item_node_id, ret.stderr)) -def archive_project_v1(label_matches): - # Extract the node_id if for the match +def migrate_epic_to_archive(label_matches): + """"Contains logic required to move a project to the archives.""" column_node_id = label_matches[0]["id"] - - # Querry all the items (prs and issues) - epic_items = get_issues_prs_for_name_for_project_v1(epic) - epic_name = epic_items["epic"] - epic_issues = epic_items["issues"] - epic_prs = epic_items["prs"] - - # Ask for user confirmation - print("Found the following items:") - print("\nEpic Name:", epic_name) - print("\nIssues") - pprint(epic_issues) - print("\nPRs") - pprint(epic_prs) - - usr = input("Procceed with move? Y/N?") - if usr.lower() != "y": - print("Error, aborting on user request") - sys.exit(1) - - # Move items to new project - for issue in epic_issues: - move_issue_to_proj(issue, active_project, archive_project) - - for pr in epic_prs: - move_issue_to_proj(pr, active_project, archive_project, is_pr=True) - - # Get the new items by the No Status collumn - no_stat_isues = get_no_status_issues_prs_for_project_v2() - - # Move them to the matching epic - for pr_no, node_id in no_stat_isues.items(): - move_to_column_project_v2(node_id, column_node_id) - print("Moving {} to {}".format(pr_no, epic)) - -def archive_project_v2(label_matches): print(label_matches) epic_items = get_epic_items(epic) from pprint import pprint @@ -409,23 +318,23 @@ def archive_project_v2(label_matches): move_issue_to_proj(pr, active_project, archive_project, is_pr=True) # Get the new items by the No Status collumn - no_stat_isues = get_no_status_issues_prs_for_project_v2() + no_stat_isues = get_no_status_issues_prs_for_project() # Move them to the matching epic for pr_no, node_id in no_stat_isues.items(): - move_to_column_project_v2(node_id, column_node_id) + move_to_column_project(node_id, column_node_id) print("Moving {} to {}".format(pr_no, epic)) if __name__ == "__main__": # Do not proceed if an empty epic has not been created - label_matches = [n for n in projectv2_get_label_ids() if n["name"] == epic] + label_matches = [n for n in project_get_label_ids() if n["name"] == epic] if not label_matches: print( "Error, please create an epic with name \"{}\" and re-run. ".format(epic)) sys.exit(1) - archive_project_v2(label_matches) + migrate_epic_to_archive(label_matches)