diff --git a/.gitignore b/.gitignore index e69de29..c6d2df4 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,6 @@ +# Ignore venv +venv/ + +# Ignore Python dependency files +*.pyc +__pycache__/ \ No newline at end of file diff --git a/README.md b/README.md index 51b05d9..f2414b2 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,8 @@ Please visit [dev.jamasoftware.com](http://dev.jamasoftware.com) for additional Please note that this script is distrubuted as-is as an example and will likely require modification to work for your specific use-case. This example omits error checking. Jama Support will not assist with the use or modification of the script. ### Before you begin -- Install Python 2.7 or higher and the requests library. [Python](https://www.python.org/) and [Requests](http://docs.python-requests.org/en/latest/) +- Install Python 3.7 or higher. To install all dependencies from requirements.txt file, run pip3 install. +- [Python Download](https://www.python.org/) ### Setup 1. As always, set up a test environment and project to test the script. diff --git a/relationship_converter.py b/relationship_converter.py index 695c4e9..69b2d97 100755 --- a/relationship_converter.py +++ b/relationship_converter.py @@ -1,173 +1,147 @@ +import sys +import logging +import datetime +import os +from py_jama_rest_client.client import JamaClient, APIException #######--CONFIG--####### -username = "username" -password = "password" -base_url = "{base_url}/rest/latest/" -project_id = 20183 # project ID to modify relationship types in +username = "exampleUsername" +password = "examplePassword" +client_id = "yourClientId" +client_secret = "yourClientSecret" +base_url = "https://yourBaseUrl" +project_id = 123 # project ID to modify relationship types in +oauth = True # follow the examples below to convert relationships between other itemTypes in Jama -# example of converting all 'Related to' relationships between epics (upstream) and features (downstream) into -# 'Epic to Feature' relationships +# example of converting all 'Related to' relationships between requirements (upstream) and texts (downstream) into +# 'Verified By' relationships epicToFeature = {} -epicToFeature.__setitem__("from_item_type_id", "89025") # epic itemType ID -epicToFeature.__setitem__("to_item_type_id", "89008") # feature itemType ID +epicToFeature.__setitem__("from_item_type_id", "24") # requirement itemType ID +epicToFeature.__setitem__("to_item_type_id", "33") # text itemType ID epicToFeature.__setitem__("old_relationship_type", "Related to") # old relationship name -epicToFeature.__setitem__("new_relationship_type", "Epic to Feature") # new relationship name +epicToFeature.__setitem__("new_relationship_type", "Verified By") # new relationship name # example of converting all 'Related to' relationships between features (upstream) and stories (downstream) into # 'Feature to Story' relationships -featureToStory = {} -featureToStory.__setitem__("from_item_type_id", "89008") # feature itemType ID -featureToStory.__setitem__("to_item_type_id", "89014") # story itemType ID -featureToStory.__setitem__("old_relationship_type", "Related to") # old relationship name -featureToStory.__setitem__("new_relationship_type", "Feature to Story") # new relationship name +testcaseToRequirement = {} +testcaseToRequirement.__setitem__("from_item_type_id", "26") # test case itemType ID +testcaseToRequirement.__setitem__("to_item_type_id", "24") # requirement itemType ID +testcaseToRequirement.__setitem__("old_relationship_type", "Related to") # old relationship name +testcaseToRequirement.__setitem__("new_relationship_type", "Caused by") # new relationship name #####--END CONFIG--##### -import requests -import json -import sys +logger = logging.getLogger(__name__) + + +def init_logging(): + try: + os.makedirs('logs') + except FileExistsError: + pass + current_date_time = datetime.datetime.now().strftime("%Y-%m-%d %H_%M_%S") + log_file = 'logs/harm-severity-updater_' + str(current_date_time) + '.log' + logging.basicConfig(filename=log_file, level=logging.INFO) + logging.getLogger().addHandler(logging.StreamHandler(sys.stdout)) + def main(): convert(epicToFeature) - convert(featureToStory) + convert(testcaseToRequirement) + +def create_jama_client(): + credentials = (username, password) + if oauth: + credentials = (client_id, client_secret) + global jama_client + jama_client = JamaClient(host_domain=base_url, credentials=credentials, oauth=oauth) -def convert(conversionMapping): - new_relationship_type_id = get_relationship_type_id(conversionMapping.get("new_relationship_type")) - if conversionMapping.get("old_relationship_type") == "Any": +def convert(conversion_mapping): + + new_relationship_type_id = get_relationship_type_id(conversion_mapping.get("new_relationship_type")) + if conversion_mapping.get("old_relationship_type") == "Any": old_relationship_type_id = -1 else: - old_relationship_type_id = get_relationship_type_id(conversionMapping.get("old_relationship_type")) - get_items_of_type( project_id, - conversionMapping.get("from_item_type_id"), - conversionMapping.get("to_item_type_id"), - new_relationship_type_id, - old_relationship_type_id) + old_relationship_type_id = get_relationship_type_id(conversion_mapping.get("old_relationship_type")) + get_items_of_type(project_id, + conversion_mapping.get("from_item_type_id"), + conversion_mapping.get("to_item_type_id"), + new_relationship_type_id, + old_relationship_type_id) def get_relationship_type_id(type_name): - remaining_results = -1 - start_index = 0 - - while remaining_results != 0: - start_at = "startAt=" + str(start_index) - - url = base_url + "relationshiptypes?" + start_at - response = requests.get(url, auth=(username, password)) - if response.status_code >= 400: - print response.text - - json_response = json.loads(response.text) - - if "pageInfo" in json_response["meta"]: - page_info = json_response["meta"]["pageInfo"] - total_results = page_info["totalResults"] - result_count = page_info["resultCount"] - remaining_results = total_results - (start_index + result_count) - start_index += 20 - else: - remaining_results = 0; - - relationship_types = json_response["data"] - for relationship_type in relationship_types: - if relationship_type["name"] == type_name: - return relationship_type["id"] - - print "Unable to locate relationship type: " + str(type_name) + relationship_types = jama_client.get_relationship_types() + for relationship_type in relationship_types: + if relationship_type["name"] == type_name: + return relationship_type["id"] + + print("Unable to locate relationship type: " + str(type_name)) sys.exit(1) + def get_items_of_type(project_id, from_type, to_type, new_type, old_type): successes = 0 attempts = 0 - remaining_results = -1 - start_index = 0 - - print "Checking items:" - while remaining_results != 0: - start_at = "startAt=" + str(start_index) + print("Checking items:") - url = base_url + "abstractitems?" + start_at + "&project=" + str(project_id) + "&itemType=" + str(from_type) - response = requests.get(url, auth=(username, password)) - if response.status_code >= 400: - print response.text - json_response = json.loads(response.text) + items = jama_client.get_abstract_items(project=[project_id], item_type=[int(from_type)]) - page_info = json_response["meta"]["pageInfo"] - total_results = page_info["totalResults"] - result_count = page_info["resultCount"] - remaining_results = total_results - (start_index + result_count) - start_index += 20 - - items = json_response["data"] - for item in items: + for item in items: + try: attempts += 1 - sys.stdout.write("\r{0} / {1}".format(attempts, total_results)) + sys.stdout.write("\r{0} / {1}".format(attempts, len(items))) sys.stdout.flush() - successes += evaluate_relationships(project_id, item["id"], to_type, old_type, new_type) + successes += evaluate_relationships(jama_client, project_id, item, to_type, old_type, new_type) + except APIException as err: + logger.error(msg="Unable to process item {}.".format(item)) + print("\nSuccessfully updated {0} relationships".format(successes)) - print "\nSuccesfully updated {0} relationships".format(successes) -def evaluate_relationships(project_id, from_item, to_type, old_type, new_type): +def evaluate_relationships(jama_client, project_id, from_item, to_type, old_type, new_type): + relationships = jama_client.get_relationships(project_id) successes = 0 - remaining_results = -1 - start_index = 0 - - while remaining_results != 0: - start_at = "startAt=" + str(start_index) - url = base_url + "items/" + str(from_item) + "/downstreamrelationships?" + start_at - response = requests.get(url, auth=(username, password)) - if response.status_code >= 400: - print response.text - json_response = json.loads(response.text) - - if "pageInfo" in json_response["meta"]: - page_info = json_response["meta"]["pageInfo"] - total_results = page_info["totalResults"] - result_count = page_info["resultCount"] - remaining_results = total_results - (start_index + result_count) - start_index += 20 - else: - remaining_results = 0; - - relationships = json_response["data"] - for relationship in relationships: + + for relationship in relationships: + try: to_item = relationship["toItem"] relationship_type = relationship["relationshipType"] - if relationship_type != new_type and is_item_of_type(to_item, to_type): + if relationship_type != new_type and is_item_of_type(to_item, int(to_type)): if old_type == -1 or old_type == relationship_type: - print "\nUpdating relationship " + str(relationship["id"]) + " from relationshipType " + str(old_type) + " to relationshipType " + str(new_type) + print("\nUpdating relationship " + str(relationship["id"]) + " from relationshipType " + str( + old_type) + " to relationshipType " + str(new_type)) successes += update_relationship(relationship["id"], from_item, to_item, new_type) + except APIException as err: + logger.error(msg="Unable to process relationship {}.".format(relationship["id"])) + logger.error(msg=str(err)) + return successes def is_item_of_type(item_id, type_id): - url = base_url + "abstractitems/" + str(item_id) - response = requests.get(url, auth=(username, password)) - if response.status_code >= 400: - print response.text - json_response = json.loads(response.text) - returnValue = type_id == str(json_response["data"]["itemType"]) - return returnValue - # return type_id == json_response["data"]["itemType"] + item = jama_client.get_item(item_id) + return type_id == int(item["itemType"]) + def update_relationship(relationship_id, from_item, to_item, relationship_type): - payload = { - "fromItem": from_item, - "toItem": to_item, - "relationshipType": relationship_type - } - url = base_url + "relationships/" + str(relationship_id) - response = requests.put(url, json=payload, auth=(username, password)) - if response.status_code >= 400: - print response.text + try: + jama_client.put_relationship(relationship_id=relationship_id, from_item=from_item["id"], to_item=to_item, + relationship_type=relationship_type) + return 1 + except APIException as err: + logger.error(msg="Unable to update relationship {}".format(relationship_id)) + logger.error(msg=str(err)) return 0 - return 1 + if __name__ == '__main__': - sys.exit(main()) + init_logging() + create_jama_client() + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b4cbae7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,16 @@ +beautifulsoup4==4.9.3 +bs4==0.0.1 +certifi==2021.5.30 +chardet==4.0.0 +colorama==0.4.4 +halo==0.0.31 +idna==2.10 +log-symbols==0.0.14 +progress==1.5 +py-jama-rest-client==1.17.1 +requests==2.25.1 +six==1.16.0 +soupsieve==2.2.1 +spinners==0.0.24 +termcolor==1.1.0 +urllib3==1.26.5