def start(self): try: self.jira_api = JiraApi() # Check URL and user authentication if not self.jira_api.check_url_and_user(): assert 'Check URL and User call failed!' # We want to run a specific filter in JIRA. # First give the filter Id and get the URL to run filter_url = self.jira_api.get_filter_for_id(config.JIRA_FILTER_ID) # Run the filter and get a list of issues to migrate issues_list = self.jira_api.get_filter(filter_url) # Setup progress bar total = len(issues_list['issues']) total_str = '/' + str(total) pbar_count = 0 pbar = progressbar.ProgressBar(widgets=[ progressbar.Bar(), progressbar.Counter(), total_str, ' ', progressbar.ETA(), ' ', progressbar.Timer() ], maxval=total).start() # Iterate over each issue and get details for issue in issues_list['issues']: key = issue['key'] pbar_count += 1 pbar.update(pbar_count) logger.info( '[{key}] Processing: {pbar_count}{total_str}'.format( key=key, pbar_count=pbar_count, total_str=total_str)) status, issue_info = self.jira_api.get_issue_info(key) if not status: logger.critical( '[{key}] ERROR: Not able to get issue info'.format( key=key)) continue self.jira_api.get_attachment(key, issue_info) # Now create this issue in JitBit self._migrate_to_jitbit(key, issue_info) pbar.finish except Exception as e: logger.critical(e.message) raise
def __init__(self): logger.info('Starting ProcessData ..') self.jitbit_api = JitbitApi() self.jira_api = JiraApi() # All assigned by is set to one user. self.default_assign_id = self.jitbit_api.get_user_id_by_email( config.JITBIT_DEFAULT_ASSIGN_EMAIL) self.start_time = time.time()
class MetricsAggregator: def __init__(self): self.github = GithubApi() self.circleci = CircleciApi() self.jira = JiraApi() def print_lead_time(self, project, limit): lead_times = self.github.get_lead_time_array(project, limit) delta_list = list(map(lambda lead_time: lead_time["delta"], lead_times)) count = len(delta_list) if count == 0: print("No PRs found with current criteria.") return delta_list.sort() average = round(sum(delta_list) / count, 2) median = round(delta_list[int(count / 2)], 2) print( f"Lead time metrics (in hours) for the last {count} feature PRs in {project}: " ) print(f" Median: {median}") print(f" Average: {average}") def print_deployment_frequency(self, max_age): # TODO update to releases/dev/day # TODO see how frequent deployments are on other repos (i.e. mob-api) releases_response = self.github.get_releases(project="mob-api") releases = json.loads(releases_response.text) oldest_release_age = ( datetime.datetime.utcnow() - self.github.format_time(releases[-1]["published_at"])).days deployment_freq = round(oldest_release_age / len(releases), 2) print( f"In the last {max_age} days, got {len(releases)} minor releases. We release, on average, every", f"{deployment_freq} days.") def print_change_fail(self, max_age): # TODO change to all releases, not just minor # TODO fetch more releases if some bugs are older than the oldest retrieved release utcnow = datetime.datetime.utcnow() minor_releases = self.github.get_releases_since( self.github.get_minor_releases(), max_age) oldest_release_age = ( utcnow - self.github.format_time(minor_releases[-1]["published_at"])).days # this query returns tickets created since the oldest considered release that are tagged as bug/support # with priority Highest/High jql_query = "(project = Silvercar) AND (type = Support OR type = Bug) AND " + \ f"(priority = Highest OR priority = High) AND created > -{oldest_release_age}d order by createdDate DESC" bugs_response = self.jira.search_issue(jql_query=jql_query) bugs_count = len(json.loads(bugs_response.text)) print( f"Got {len(minor_releases)} releases and {bugs_count} bugs since the oldest release", f"on {minor_releases[-1]['published_at']}, which is {oldest_release_age} days old." ) print( f" Change fail percentage (avg bugs/release) = {round(bugs_count / len(minor_releases), 3)}" )
def test_jitbit(self): # Use this to test a single post to JitBit. # Hardcoded key will be posted. process_key = 'AR-122' try: self.jira_api = JiraApi() # Check URL and user authentication if not self.jira_api.check_url_and_user(): assert 'Check URL and User call failed!' filter_url = self.jira_api.get_filter_for_id(config.JIRA_FILTER_ID) # Run the filter and get a list of issues to deal with issues_list = self.jira_api.get_filter(filter_url) # Iterate over each issue and get details for issue in issues_list['issues']: key = issue['key'] if key != process_key: continue status, issue_info = self.jira_api.get_issue_info(key) if not status: logger.critical( '[{key}] ERROR: Not able to get issue info'.format( key=key)) continue self.jira_api.get_attachment(key, issue_info) # Now create this issue in JitBit self._migrate_to_jitbit(key, issue_info) except Exception as e: logger.critical(e.message) raise
def jira_login(self): if not self.jira_api and settings.LOGIN and settings.PASSWORD: self.login = settings.LOGIN self.password = settings.PASSWORD else: self.login = input_req('Jira login: '******'Connection to jira failed') except JiraError: return self.error('Login or password is incorrect') else: if not permissions: return self.error('Jira permission denied')
class ProcessData(object): def __init__(self): logger.info('Starting ProcessData ..') self.jitbit_api = JitbitApi() self.jira_api = JiraApi() # All assigned by is set to one user. self.default_assign_id = self.jitbit_api.get_user_id_by_email( config.JITBIT_DEFAULT_ASSIGN_EMAIL) self.start_time = time.time() def __del__(self): end_time = time.time() logger.info('Total time to run: {exec_time} seconds'.format( exec_time=end_time - self.start_time)) def start(self): try: self.jira_api = JiraApi() # Check URL and user authentication if not self.jira_api.check_url_and_user(): assert 'Check URL and User call failed!' # We want to run a specific filter in JIRA. # First give the filter Id and get the URL to run filter_url = self.jira_api.get_filter_for_id(config.JIRA_FILTER_ID) # Run the filter and get a list of issues to migrate issues_list = self.jira_api.get_filter(filter_url) # Setup progress bar total = len(issues_list['issues']) total_str = '/' + str(total) pbar_count = 0 pbar = progressbar.ProgressBar(widgets=[ progressbar.Bar(), progressbar.Counter(), total_str, ' ', progressbar.ETA(), ' ', progressbar.Timer() ], maxval=total).start() # Iterate over each issue and get details for issue in issues_list['issues']: key = issue['key'] pbar_count += 1 pbar.update(pbar_count) logger.info( '[{key}] Processing: {pbar_count}{total_str}'.format( key=key, pbar_count=pbar_count, total_str=total_str)) status, issue_info = self.jira_api.get_issue_info(key) if not status: logger.critical( '[{key}] ERROR: Not able to get issue info'.format( key=key)) continue self.jira_api.get_attachment(key, issue_info) # Now create this issue in JitBit self._migrate_to_jitbit(key, issue_info) pbar.finish except Exception as e: logger.critical(e.message) raise def test_jitbit(self): # Use this to test a single post to JitBit. # Hardcoded key will be posted. process_key = 'AR-122' try: self.jira_api = JiraApi() # Check URL and user authentication if not self.jira_api.check_url_and_user(): assert 'Check URL and User call failed!' filter_url = self.jira_api.get_filter_for_id(config.JIRA_FILTER_ID) # Run the filter and get a list of issues to deal with issues_list = self.jira_api.get_filter(filter_url) # Iterate over each issue and get details for issue in issues_list['issues']: key = issue['key'] if key != process_key: continue status, issue_info = self.jira_api.get_issue_info(key) if not status: logger.critical( '[{key}] ERROR: Not able to get issue info'.format( key=key)) continue self.jira_api.get_attachment(key, issue_info) # Now create this issue in JitBit self._migrate_to_jitbit(key, issue_info) except Exception as e: logger.critical(e.message) raise def _migrate_to_jitbit(self, key, issue_info): try: category_id = config.JITBIT_MIGRATE_CATEGORY_ID # We append JIRA key to subject subject = issue_info['fields']['summary'] + ' (' + key + ')' body = issue_info['fields']['description'] if body is None: body = '' # We will set all of the priorities to Normal (0) priority_id = 0 # Status. JitBit has only New(1) and Closed(3) exposed via API if (issue_info['fields']['status']['name']).lower() == 'closed': status_id = 3 else: status_id = 1 # Created by # By default this will be the person creating the ticket. # You can create 'on-behalf' of another person # This code will get the details of the user from JIRA # created_by_email = issue_info['fields']['creator']['emailAddress'] # created_by_diplay_name = issue_info['fields']['creator']['displayName'] # created_by_first_name, created_by_last_name = created_by_diplay_name.split(' ') # For our purpose we decided that all of the created by will be the # logged in user. So nothing to set. # Assigned to # We are assigning all to one person assign_to_id = self.default_assign_id # Ready to create the ticket ticket_id = int( self.jitbit_api.post_ticket(key, category_id, subject, body, priority_id)) if ticket_id > 0: self.jitbit_api.post_set_assignee(key, ticket_id, assign_to_id) self._add_comments(key, ticket_id, issue_info) self._add_attachments(key, ticket_id, issue_info) # Status should be the last to be set. Otherwise JitBit will change it. self.jitbit_api.post_set_ticket_status(key, ticket_id, status_id) # We update the issue on the JIRA side if the migration was successful. # We use the 'Tag' field in JIRA for this self.jira_api.post_tag(key, config.JITBIT_MIGRATION_SUCCESS) else: logger.critical( 'ERROR: could not create a ticket in JitBit for {key}'. format(key=key)) except Exception as e: # If we have a failure anywhere in the process we have to delete the ticket. # However, JitBit API does not support delete of tickets. # Instead move the ticket to 'Deleted' category. if ticket_id > 0: self.jitbit_api.post_mark_deleted(key, ticket_id) logger.critical(e.message) # Don't raise let continue def _add_comments(self, key, ticket_id, issue_info): assert ticket_id > 0 comments = issue_info['fields']['comment'] for comment in comments['comments']: comment_text = comment['body'] # Comments can be anonymous comment_author = 'Unknown' if 'updateAuthor' in comment: comment_author = comment['updateAuthor']['emailAddress'] comment_data = 'Updated by: ' + '[i]' + comment_author + '[/i]\n\n' + comment_text self.jitbit_api.post_comment(key, ticket_id, comment_data) def _add_attachments(self, key, ticket_id, issue_info): # We will add all the files under the <attachments>/key directory to this issue key_dir = os.path.join(config.ATTACHMENT_FOLDER, key) if not os.path.exists(key_dir): logger.info('[{key}] No attachments to process'.format(key=key)) for root, sub_dirs, files in os.walk(key_dir, topdown=True): for filename in files: file_dir = os.path.join(root, filename) # Ignore attachments of size 5K or less. These are normally logos or icons that we can skip if os.path.getsize(file_dir) > 5120: self.jitbit_api.post_attach_file(key, ticket_id, file_dir) else: logger.info( '[{key}] File size is < 5K. Ignoring. {file_dir}'. format(key=key, file_dir=file_dir))
def __init__(self): self.github = GithubApi() self.circleci = CircleciApi() self.jira = JiraApi()
class Application: def __init__(self): self.login = None self.password = None self.jira_api = None self.bitbucket_api = None self.project = None self.bb_project = None self.repo = None self.fix_version = None def run(self): print(styled('Bitbucket tools', bcolors.HEADER)) if settings.LOGIN and settings.PASSWORD: self.jira_login() if settings.PROJECT: self.select_jira_project() if settings.BITBUCKET_PROJECT: self.select_bitbucket_project() if settings.BITBUCKET_REPOSITORY: self.select_bitbucket_repo() while True: menu = self.get_menu() answer = self.show_menu(menu) menu[answer]['command']() def show_menu(self, menu): questions = [item['title'] for item in menu] answer = input_list(questions) - 1 return answer def get_menu(self): menu = [] if not self.jira_api: menu.append({ 'title': 'Login jira', 'command': self.jira_login }) return menu menu.append({ 'title': 'Make release branch', 'command': self.make_release_branch }) menu.append({ 'title': 'Work with current branch', 'command': self.modify_current_branch }) if self.project: title = 'Change jira project [' + self.project['key'] + ']' else: title = 'Select jira project' menu.append({ 'title': title, 'command': self.select_jira_project }) if self.bb_project: title = 'Change bitbucket project [' + self.bb_project['key'] + ']' else: title = 'Select bitbucket project' menu.append({ 'title': title, 'command': self.select_bitbucket_project }) if self.repo: title = 'Change bitbucket repository [' + self.repo['name'] + ']' else: title = 'Select bitbucket repository' menu.append({ 'title': title, 'command': self.select_bitbucket_repo }) menu.append({ 'title': 'Change jira login [' + self.login + ']' }) return menu def jira_login(self): if not self.jira_api and settings.LOGIN and settings.PASSWORD: self.login = settings.LOGIN self.password = settings.PASSWORD else: self.login = input_req('Jira login: '******'Connection to jira failed') except JiraError: return self.error('Login or password is incorrect') else: if not permissions: return self.error('Jira permission denied') def select_jira_project(self): try: projects = self.jira_api.get_projects() except JiraConnectionError: return self.error('Connection to jira failed') except JiraError: return self.error('Projects not found') if len(projects) == 0: return self.error('Projects not found') project = None if not self.project and settings.PROJECT: project = [project for project in projects if project['key'] == settings.PROJECT] if project: project = project[0] if not project: print('Select project') answer = input_list([project['name'] for project in projects]) project = projects[answer - 1] self.project = project def select_bitbucket_project(self): try: projects = self.bitbucket_api.get_projects() except BitbucketConnectionError: return self.error('Connection to bitbucket failed') except BitbucketError: return self.error('Projects in BitBucket not found') if len(projects) == 0: return self.error('Projects in BitBucket not found') project = None if not self.bb_project and settings.BITBUCKET_PROJECT: project = [project for project in projects if project['key'] == settings.BITBUCKET_PROJECT] if project: project = project[0] if not project: print('Select BitBucket project:') answer = input_list([project['name'] for project in projects]) project = projects[answer - 1] self.bb_project = project self.repo = None def select_bitbucket_repo(self): if not self.bb_project: self.select_bitbucket_project() try: repos = self.bitbucket_api.get_project_repos(self.bb_project['key']) except BitbucketConnectionError: return self.error('Connection to bitbucket failed') except BitbucketError: return self.error('Repositories in selected project not found') if len(repos) == 0: return self.error('Repositories in selected project not found') repo = None if not self.repo and settings.BITBUCKET_REPOSITORY: repo = [repo for repo in repos if repo['name'] == settings.BITBUCKET_REPOSITORY] if repo: repo = repo[0] if not repo: print('Select repository') answer = input_list([repo['name'] for repo in repos]) repo = repos[answer - 1] self.repo = repo def select_fix_version(self): if not self.project: self.select_jira_project() try: versions = self.jira_api.get_project_versions(self.project['key']) except JiraConnectionError: return self.error('Connection to jira failed') except JiraError: return self.error('Fix versions for selected project not found') if len(versions) == 0: return self.error('Fix versions for selected project not found') versions = [version for version in versions if not version['released']] answer = input_list([version['name'] for version in versions]) version = versions[answer - 1] self.fix_version = version def make_release_branch(self): if not self.project or not self.bb_project or not self.repo: print(styled('Select jira project, bitbucket project and bitbucket repostiory', bcolors.ERROR)) return self.select_fix_version() commits_controller = CommitsController( self.jira_api, self.bitbucket_api, self.project, self.bb_project, self.repo, self.fix_version ) commits_controller.run() def modify_current_branch(self): if not self.project or not self.bb_project or not self.repo: print(styled('Select jira project, bitbucket project and bitbucket repostiory', bcolors.ERROR)) return commits_controller = CommitsController( self.jira_api, self.bitbucket_api, self.bb_project, self.project, self.repo ) commits_controller.run() def error(self, message): exit(styled(message, bcolors.ERROR))