def search(self, jql_query: str = None, field_list: list = None, paginated: bool = True): assert isinstance(jql_query, str), "jql_query must be a string" assert isinstance(field_list, list), "field_list must be a list" assert all([isinstance(field, str) for field in field_list]), \ "all fields in the field_list must be a string" assert isinstance(paginated, bool), "paginated is a boolean True or False" if paginated: return JiraIssue.search(url=self.url, headers=self.header(), search_request={ 'jql': jql_query, 'fields': field_list }) else: return JiraIssue.search_issues(url=self.url, headers=self.header(), search_request={ 'jql': jql_query, 'fields': field_list })
def add_test(url: str = None, headers: dict = None, project_id: str = None, story_key: str = None, test_description: str = None, test_name: str = None, test_type: str = None): """ Create a Jira "test" entry related to a project and story TODO work on the customfield as it may vary with the jira setting :param url: the jira server url without endpoint :param headers: the request headers :param project_id: the project id not the project key :param story_key: the story key the test is related to :param test_description: the test description :param test_name: the test name :param test_type: the test type (Manual, Cucumber, Generic) :return: the create link response and the test key """ data = { "fields": { "project": { "id": str(project_id) }, "summary": test_name, "description": "", "issuetype": { "id": str( JiraIssue.get_issue_identifier( url=url, headers=headers, issue_type=JiraTests.TEST)) }, "customfield_10202": { "value": "Cucumber" }, "customfield_10203": { "value": str(test_type) }, "customfield_10204": test_description } } response = JiraIssue.create_issue(url=url, headers=headers, issue_data=data) key = response.json()["key"] response = JiraIssue.create_link(url=url, headers=headers, from_key=key, to_key=story_key, link_type="Tests") return response, key
def get_issue_meta(self): """ Get the issues' project meta data :return: """ return JiraIssue.get_issue_meta(url=self.url, headers=self.header(), project_id=self.project_id)
def create_story(url=None, headers=None, project_id=None, title=None, description=None, epic_key=None, actor=None, action=None, benefit=None): """ Request the creation of a story :param project_id: :param headers: :param url: :param action: :param benefit: :param actor: :param epic_key: :param title: :param description: :return: request response """ data = { "fields": { "project": { "id": str(project_id) }, "summary": title, "issuetype": { "id": str( JiraIssue.get_issue_identifier( url=url, headers=headers, issue_type=JiraStories.STORY)) }, "description": description, "customfield_10002": epic_key, JiraStories.ROLE_JIRA_KEY: actor, JiraStories.ACTION_JIRA_KEY: action, JiraStories.BENEFIT_JIRA_KEY: benefit } } return JiraIssue.create_issue(url=url, headers=headers, issue_data=data)
def create_issue(self, issue_data: dict = None): """ Create a new issue in the project. :param issue_data: :return: a request response """ return JiraIssue.create_issue(url=self.url, headers=self.header(), issue_data=issue_data)
def update_issue(self, issue_key: str = None, issue_data: dict = None): """ Update the issue with the given data :param issue_key: the jira issue key :param issue_data: the issue update :return: a request Response """ return JiraIssue.update_issue(url=self.url, headers=self.header(), issue_key=issue_key, issue_data=issue_data)
def get_issue_status(self, issue_key: str = None): """ Retrieve the issue status :param issue_key: a jira issue key :return: a string as the status """ assert isinstance(issue_key, str), "issue_key must be a string" return JiraIssue.get_issue_status(url=self.url, headers=self.header(), issue_key=issue_key)
def get_issue(self, issue_key: str = None): """ Retrieve a Jira issue by its key. :param issue_key: a string as a Jira Key :return: a requests response """ assert isinstance(issue_key, str), "issue_key must be a string" return JiraIssue.get_issue(url=self.url, headers=self.header(), issue_key=issue_key)
def get_issue_identifier(self, issue_type: str = None): """ Retrieve the issue id of a specific issue type. :param issue_type: the issue name :return: a string as a Jira id """ assert isinstance(issue_type, str), "issue_type must be a string" return JiraIssue.get_issue_identifier(url=self.url, headers=self.header(), issue_type=issue_type)
def add_attachments_to_issue(self, issue_key=None, file_name=None): """ :param issue_key: :param file_name: :return: """ assert isinstance(issue_key, str), "issue_key must be a string" return JiraIssue.add_attachments_to_issue(url=self.url, headers=self.header(), issue_key=issue_key, file_name=file_name)
def get_epics(url=None, headers=None, project_id=None): """ Get all epics related to a project :return: a list of key-epic name sets """ data = {"jql": "project = {} AND type = Epic".format(project_id), "fields": ["key", "customfield_10004"]} list_rep = [] response = JiraIssue.search_issues(url=url, headers=headers, search_request=data) issues = response.json()["issues"] for item in issues: list_rep.append((item["key"], item["fields"]["customfield_10004"])) return list_rep
def update_issue_description(self, issue_key: str = None, description: str = None): """ Update the given issue with the new description :param issue_key: a jira issue key :param description: the new description :return: a requests response """ assert isinstance(issue_key, str), "issue_key must be a string" assert isinstance(description, str), "description must be a string" return JiraIssue.update_issue_description(url=self.url, headers=self.header(), issue_key=issue_key, description=description)
def get_project(self): """ Get all the Project issues held in Jira. :raise Exception: Response status code is not 200 ok :return: a Request response. """ response = requests.get("{}/rest/api/2/project".format(self.url), headers=self.header()) if response.status_code != 200: log.error("Getting project cast an error {}".format(response.text)) raise Exception("Getting project cast an error {}".format( response.text)) else: return { "status_code": response.status_code, "content": JiraIssue.sanitize(content=response.content) }
def delete_attachments(url=None, headers=None, issue_key=None, attachment_id=None): """" Delete all issue attachments or only one attachment depending on the input """ if issue_key is not None and attachment_id is None: attachments_id_list = JiraIssue.get_issue_attachments_id( url=url, headers=headers, issue_key=issue_key) for attachment_id in attachments_id_list: JiraAttachments.delete_attachments(url=url, headers=headers, attachment_id=attachment_id) elif issue_key is None and attachment_id is not None: return requests.delete("{}/rest/api/2/attachment/{}".format( url, attachment_id), headers=headers) else: print("Error")
def get_jira_test(self, jira_id=None): # get the test case from JIRA from a jira_id (ex: PFWES-5336) assert jira_id is not None, "Impossible to get jira issue: {}".format( jira_id) log.debug("## Get jira: {}".format(jira_id)) jira_test_json = self.__connection.get_issue(jira_id) jira_test = 0 if jira_test_json.status_code != 200: # if we can't get the test log.error( "Connection to JIRA impossible. Check url, login/password - HTTP: {}" .format(jira_test_json.status_code)) log.debug(jira_test_json.content) quit(1) else: jira_test = JiraIssue.sanitize(jira_test_json.content) log.debug('jira {}\n{}'.format(jira_id, jira_test)) # check if jira's ID is a test case if jira_test["fields"]["issuetype"]['name'] != 'Test': log.error("{} is not a test case!".format(jira_id)) quit(2) return jira_test
def get_test_plan_in_release(url: str = None, headers: dict = None, release_name: str = None): """ :param url: the jira server url without endpoint :param headers: the request headers :param release_name: the release name :return: a dictionary containing the key "issues" which value is a list of test plan key in a dictionary i.e. can be acceded with return_var["issues"][position]["key"] """ data = { "jql": 'fixVersion="{}" AND issueType = "Test Plan"'.format(release_name), "fields": [ "key", ] } return JiraIssue.search_issues(url=url, headers=headers, search_request=data)
def update_test(url=None, headers=None, test_key=None, test_description=None, test_name=None, test_type=None): data = { "fields": { "summary": test_name, "description": "", "customfield_10202": { "value": "Cucumber" }, "customfield_10203": { "value": str(test_type) }, "customfield_10204": test_description } } return JiraIssue.update_issue(url=url, headers=headers, issue_key=test_key, issue_data=data)
def update_story(url=None, headers=None, issue_key=None, title=None, description=None, epic_key=None, actor=None, action=None, benefit=None): data = { "fields": { "summary": title, "description": description, "customfield_10002": epic_key, JiraStories.ROLE_JIRA_KEY: actor, JiraStories.ACTION_JIRA_KEY: action, JiraStories.BENEFIT_JIRA_KEY: benefit } } return JiraIssue.update_issue(url=url, headers=headers, issue_key=issue_key, issue_data=data) # noqa
def retrieve_attachments(url=None, headers=None, issue_key=None, attachment_id=None, folder=None): logging.info("Retrieve attachment with issue_key='{}'," " attachment_id='{}' and folder='{}'".format( issue_key, attachment_id, folder)) attachments = JiraIssue.get_issue_attachments_links( url=url, headers=headers, issue_key=issue_key) logging.info("attachments are '{}'".format(repr(attachments))) if attachment_id is not None and attachment_id in attachments.keys(): JiraAttachments.download_file( url=attachments[attachment_id]["url"], headers=headers, file_absolute_path=join( folder, attachments[attachment_id]["filename"])) # noqa else: for key in attachments.keys(): JiraAttachments.download_file( url=attachments[key]["url"], headers=headers, file_absolute_path=join( folder, attachments[key]["filename"])) # noqa
def __add_execution_to_report(url=None, headers=None, test_execution_key=None, new_report_sheet=None, folder=None, relative_folder=None): # this function add lines in test execution's sheet test_execution = JiraIssue.search( url=url, headers=headers, search_request={ "jql": 'issuekey="{}"'.format(test_execution_key), # noqa "fields": ["key", "summary", "description"] }) # noqa # header definition new_report_sheet.merge_range( 1, 1, 1, 5, test_execution.json()["issues"][0]["fields"]["summary"]) new_report_sheet.write_string('A2', 'Test execution:') # get test data tests = XRayIssues.get_tests_in_execution( url=url, headers=headers, test_execution_key=test_execution_key) evidences = JiraIssue.sanitize(content=tests.content) evidences = {item["key"]: item for item in evidences} current_row = 4 log.debug("{} data: \n{}".format(test_execution_key, tests.json())) log.debug("Evidences {} data: \n{}".format(test_execution_key, evidences)) for index, test in enumerate(tests.json()): # Create the download folder os.makedirs(os.path.join(folder, test["key"])) # Get steps or scenarios steps = JiraIssue.search(url=url, headers=headers, search_request={ "jql": 'issuekey="{}"'.format(test["key"]), "fields": [ "key", "customfield_10204", "customfield_10206", "summary", "issuelinks" ] }) steps = JiraIssue.sanitize(content=steps.content) # log.info("step {} data:\n[{}".format(test["key"], steps)) story_key = '' story_name = '' # try to catch story attached to the test try: # these fields doesn't exist for Release level, only for test plan / exec level short_link = steps["issues"][0]["fields"]["issuelinks"][ 0] # to ease readability story_key = short_link["inwardIssue"]["key"] story_name = short_link["inwardIssue"]["fields"]["summary"] except (KeyError, IndexError): try: # on old version, field use outward instead of inward short_link = steps["issues"][0]["fields"]["issuelinks"][ 0] # ease readability log.debug("step {} data:\n{}".format( test["key"], short_link["outwardIssue"]["key"])) story_key = short_link["outwardIssue"]["key"] story_name = short_link["outwardIssue"]["fields"][ "summary"] except (KeyError, IndexError): log.warning("There is no story / improvement attached to" " test {}".format(test["key"])) if steps["issues"][0]["fields"]["customfield_10204"] is not None: steps_list = [ steps["issues"][0]["fields"]["customfield_10204"] ] else: steps_list = [ item["step"] for item in steps["issues"][0]["fields"] ["customfield_10206"]["steps"] ] file_list = evidences[test["key"]]["evidences"] defect_list = evidences[test["key"]]["defects"] lines_to_write = max( (len(steps_list), len(file_list), len(defect_list))) for step_index, step in enumerate(steps_list): new_report_sheet.write_string(current_row + step_index, 6, step) for evidence_index, evidence in enumerate(file_list): file_name = evidence["fileName"] JiraAttachments.download_file(url=evidence["fileURL"], headers=headers, file_absolute_path=os.path.join( folder, test["key"], file_name)) new_report_sheet.write_url(current_row + evidence_index, 7, os.path.join( relative_folder, test["key"], file_name), string=file_name) # set jira's ID url new_report_sheet.write_url(current_row, 3, "{}/browse/{}".format(url, test["key"]), string=test["key"]) # Test ID if story_key != '': new_report_sheet.write_url(current_row, 1, "{}/browse/{}".format( url, story_key), string=story_key) # US ID new_report_sheet.write_string(current_row, 2, story_name) # US name # merge rows when result is on multi lines if lines_to_write > 1: # set values and merge rows new_report_sheet.merge_range( current_row, 4, current_row + lines_to_write - 1, 4, steps["issues"][0]["fields"]["summary"]) new_report_sheet.merge_range(current_row, 5, current_row + lines_to_write - 1, 5, test["status"]) # value already set, only merge rows new_report_sheet.merge_range( current_row, 1, current_row + lines_to_write - 1, 1, story_key) # US ID field # todo improve format new_report_sheet.merge_range(current_row, 2, current_row + lines_to_write - 1, 2, None) # US title field new_report_sheet.merge_range( current_row, 3, current_row + lines_to_write - 1, 3, test["key"]) # Test ID # todo improve format new_report_sheet.merge_range(current_row, 6, current_row + lines_to_write - 1, 6, None) # Step field else: new_report_sheet.write_string(current_row, 1, story_key) # todo improve format new_report_sheet.write_string( current_row, 3, test["key"]) # todo improve format new_report_sheet.write_string( current_row, 4, steps["issues"][0]["fields"]["summary"]) new_report_sheet.write_string(current_row, 5, test["status"]) current_row = current_row + lines_to_write
def __from_test_plan_report(url=None, headers=None, test_plan_key=None, folder=None): # Retrieve the summary and description test_plan = JiraIssue.search(url=url, headers=headers, search_request={ "jql": 'issuekey="{}"'.format(test_plan_key), "fields": ["key", "summary", "description"] }) # Create a xlsx file per test plan report = JiraReporter.__create_xlsx_file(folder=folder, name=test_plan_key) # Prepare the summary page and fill with Test plan data report["sheets"]["summary"] = report["workbook"].add_worksheet( "Summary") report["sheets"]["summary"].write_string( 'A1', "This page is a summary of the test" " execution(s) for test" " plan {}".format(test_plan_key)) report["sheets"]["summary"].set_column(3, 5, 15) report["sheets"]["summary"].merge_range( 1, 1, 1, 5, test_plan.json()["issues"][0]["fields"]["summary"], report["format"]["main_title"]) report["sheets"]["summary"].merge_range(3, 3, 3, 5, "Description", report["format"]["header"]) report["sheets"]["summary"].write_string(3, 1, "Key", report["format"]["header"]) report["sheets"]["summary"].write_string(3, 3, "Description", report["format"]["header"]) test_executions = XRayIssues.get_tests_execution_of_test_plan( url=url, headers=headers, test_plan_key=test_plan_key) log.info("test_plan_key: {}".format(test_plan_key)) for index, test_execution in enumerate(test_executions.json()): log.debug("Line: {}".format(4 + index)) # Creating the sheet for the execution report["sheets"][ test_execution["key"]] = report["workbook"].add_worksheet( test_execution["key"]) # noqa # Updating the summary's sheet # TODO: check the wrap format as it seems not to work report["sheets"]["summary"].merge_range(4 + index, 3, 4 + index, 5, test_execution["summary"], report["format"]["wrap"]) report["sheets"]["summary"].write_url( 4 + index, 1, "internal:'{}'!A1".format(test_execution["key"])) report["sheets"]["summary"].write_string(4 + index, 1, test_execution["key"], report["format"]["wrap"]) # data JiraReporter.__from_test_execution_report( url=url, headers=headers, test_execution_key=test_execution["key"], new_report_sheet=report["sheets"][ test_execution["key"]], # noqa folder=os.path.join(folder, test_plan_key, test_execution["key"]), relative_folder='{}/{}/'.format(test_plan_key, test_execution["key"])) # noqa # set format JiraReporter.__format_test_report_sheet( report=report, sheet_key=test_execution["key"]) report["workbook"].close()
def update_jira_test(self, jira_id=None, jira_changes=None): # Update the test case from Jira with feature file data # jira_id = jira's id who will be updated # jira_changes = list of all changes assert jira_id is not None, "There is no jira_id to change" assert jira_changes is not None, "There is no change for jira_id{}".format( jira_id) results = { jira_id: {} } # build a json for results = { jira_id: {"field1": "204 OK"}} # Split jira_changes to manage each field individually for field in jira_changes: log.debug("{} - jira_change[field]: {}".format( type(jira_changes[field]), jira_changes[field])) value = str(jira_changes[field]) if field == mapping_feature_jira['story_tags']: # manage linked issue field my_result = self.__connection.create_link(from_key=jira_id, to_key=value, link_type='Tests') else: log.debug("field ---> {}".format(field)) if field == mapping_feature_jira['labels']: # Manage labels field change = [] # update labels and remove unexpected labels for action, data in jira_changes[field].items(): for tag in data: change.append({action: tag}) value = change elif field == mapping_feature_jira['type']: # manage case for field = type (scenario type) value = [{"set": {"value": value}}] else: # manage other fields than "label" "type" & "story tags" value = [{"set": value}] field = field.replace('fields/', 'update/') log.debug('field: {} - value: \n\t{}\n'.format(field, value)) json_data = {} dpath.util.new(json_data, field, value) log.debug("json_data: {}".format(json_data)) # Update jira issue change by change my_result = self.__connection.update_issue(jira_id, json_data) # Manage return message of the issue's update if not (my_result.status_code == 204 or my_result.status_code == 201): # when error return ERROR messages log.warning("HTTP:{} - Error to update field: {}\n\t{}".format( my_result.status_code, field, # noqa JiraIssue.sanitize(my_result.content))) # noqa else: # when not error return INFO messages log.info("HTTP:{} - Field: {} updated successfully".format( my_result.status_code, field)) results[jira_id][field] = my_result # save result for this field # loop end return results
def add_epic(url=None, headers=None, project_id=None, epic_name=None, epic_summary=None): data = {"fields": {"project": {"id": str(project_id)}, "summary": epic_summary, "issuetype": {"id": str(JiraIssue.get_issue_identifier(issue_type=JiraEpics.EPIC))}, # noqa "customfield_10004": epic_name}} return JiraIssue.create_issue(url=url, headers=headers, issue_data=data)
def create_link(self, from_key=None, to_key=None, link_type=None): return JiraIssue.create_link(url=self.url, headers=self.header(), from_key=from_key, to_key=to_key, link_type=link_type)
def get_issue_attachments_id(self, issue_key=None): assert isinstance(issue_key, str), "issue_key must be a string" return JiraIssue.get_issue_attachments_id(url=self.url, headers=self.header(), issue_key=issue_key)