def add_view(self): view_name = pick_value('Which Jenkins view would you like to save?', self.active_connection.get_list_of_views()) if view_name: if view_name == 'Dev': dev_view_name = pick_value( 'Which Jenkins dev view would you like to save?', self.active_connection.get_list_of_views(view_name)) if dev_view_name: dev_view_name = 'Dev-{}'.format(dev_view_name) self.active_connection.jenkins_views[ dev_view_name] = self.active_connection.get_view( dev_view_name) self.active_connection.save_connection_config() view_name = dev_view_name else: self.active_connection.jenkins_views[ view_name] = self.active_connection.get_view(view_name) self.active_connection.save_connection_config() print('Successfully added view: {}'.format(view_name)) pause() if is_yes('Would you like to download jobs for this view now?'): self.active_connection.download_jobs(view_name) self.active_connection.save_job_data() print('Successfully downloaded jobs for view: {}'.format( view_name)) pause()
def add_custom_report_job(self): if self.connection_names: connection_name = pick_value( 'Which Jenkins Connection would you like to add jobs from?', self.connection_names) if connection_name: connection = self.jenkins_connections[connection_name] if connection_name in self.active_report.connection_names: job_options = [ job_name for job_name in connection.job_names if job_name not in self.active_report.connection_dict[connection_name] ] else: job_options = connection.job_names while True: job_name = pick_value( 'Which Jenkins job would you like to add?', job_options) if job_name: self.active_report.add_job_to_report( job_name, connection_name) self.active_report.save_report_config() print('Successfully added job: {}'.format(job_name)) job_options.remove(job_name) else: break else: if is_yes( 'No Jenkins connections to add jobs from. Would you like to add one now?' ): self.active_connection = self.add_connection() self._main_menu.go_to_jenkins_connection_menu()
def download_jobs(self): download_method = pick_value( 'Would you like to download jobs by view or individually?', ['By View', 'Individually'], sort=False) if download_method: if download_method == 'By View': if self.active_connection.jenkins_views: view_options = self.active_connection.view_names all_views = '* All Views' view_options.append(all_views) view_name = pick_value( 'Which saved view would you like to download jobs for?', view_options) if view_name: if view_name == all_views: self.active_connection.download_jobs() self.active_connection.save_job_data() print( 'Successfully downloaded jobs for all views.') pause() else: self.active_connection.download_jobs(view_name) self.active_connection.save_job_data() print('Successfully downloaded jobs for view: {}'. format(view_name)) pause() else: if is_yes( 'No Jenkins views. Would you like to add one now?' ): self.add_view() view_name = self.active_connection.view_names[0] if is_yes('Download jobs for this view?'): self.active_connection.download_jobs(view_name) self.active_connection.save_job_data() print('Successfully downloaded jobs for view: {}'. format(view_name)) pause() elif download_method == 'Individually': job_name = get_input( 'Enter the exact, case-sensitive name of the job, or enter nothing to exit.\n>', lowered=False) if job_name: if self.active_connection.download_single_job(job_name): self.active_connection.save_job_data() print('Successfully downloaded Jenkins job: {}'.format( job_name)) pause() else: print('Failed to download Jenkins job: {}'.format( job_name)) pause()
def remove_filter(self) -> None: print('Removing from JiraFilter: {}'.format(self)) action = get_input('Remove [i]nclude, [e]xclude, or [q]uit') if action == 'i': to_remove = pick_value('Remove which include?', self._includes) if to_remove is None: return self._includes.remove(to_remove) elif action == 'e': to_remove = pick_value('Remove which exclude?', self._excludes) if to_remove is None: return self._excludes.remove(to_remove)
def edit_team(self, jira_manager, team_name=None): # type: (JiraManager, str) -> None if team_name is None: team_name = pick_value('Edit which team?', list(self._teams.keys()), True, 'Cancel') if team_name is None: return None team = self._teams[team_name] jira_connection = jira_manager.get_jira_connection( team.jira_connection_name) while True: clear() print('-------------------------') print('[Edit {}]'.format(team)) print('-------------------------') cmd = get_input( '[A]dd more members, [R]emove a member, or [Q]uit?') if cmd == 'a': assignees = jira_connection.pick_assignees(sys.maxsize) if assignees is None or len(assignees) == 0: print('No assignees chosen. Returning.') return None for assignee in assignees: if assignee in team.member_names: print( 'Assignee already exists in {}. Skipping.'.format( team.name)) continue else: team.add_member(assignee, jira_connection) print('Added {} to {}.'.format(assignee, team.name)) self._save_config() elif cmd == 'r': assignee = pick_value('Remove which assignee?', team.member_names, True, 'Cancel') if assignee is None: continue confirm = get_input('Delete {} from {}: Are you sure?'.format( assignee, team.name)) if confirm == 'y': team.delete_member(assignee) elif cmd == 'q': break else: print('Bad input. Valid input: A, R, or Q.') pause() self._save_config()
def edit_team(self, team_manager): if len(self._teams) > 0: print('Currently contained teams:') for t in list(self._teams.keys()): team = self._teams[t] print(' Name: {}'.format(team.name)) print(' Assignees: {}'.format(','.join(team.members))) cmd = get_input('[A]dd a team, [R]emove a team, or [Q]uit?') if cmd == 'q': return elif cmd == 'a': if 'assignee' in self._jira_filters or 'reviewer' in self._jira_filters or 'reviewer2' in self._jira_filters: conf = get_input( 'Adding a team will remove all active assignee or reviewer filters. Are you sure?' ) if conf == 'n': return del self._jira_filters['assignee'] del self._jira_filters['reviewer'] del self._jira_filters['reviewer2'] to_add = team_manager.pick_team() if to_add is None: return self._teams[to_add.name] = to_add elif cmd == 'r': tr = pick_value('Remove which team?', list(self._teams.keys()), True, 'Cancel') if tr is None: return conf = get_input('About to delete {}. Are you sure?'.format(tr)) if conf == 'y': del self._teams[tr]
def display_escalations(self): jira_connection_name = pick_value( 'Select a JIRA Connection to view Escalation type tickets:', list(self._jira_connections.keys())) jira_connection = self._jira_connections[jira_connection_name] jira_issues = JiraUtils.get_issues_by_query( jira_connection, 'type = \'Escalation\' AND resolution = unresolved') issue_keys = [] for issue in jira_issues: issue_keys.append(issue.issue_key) df = DisplayFilter.default() while True: print_separator(30) print(os.linesep + 'Escalations' + os.linesep) print_separator(30) clear() df.display_and_return_sorted_issues(self, jira_issues) i = get_input('[#] Integer to open issue in browser. [q] to quit.') if i == 'q': break try: c_input = int(i) - 1 JiraUtils.open_issue_in_browser(jira_connection.url, issue_keys[c_input]) except ValueError: print('Bad input. Try again') pause()
def pick_team(self): # type: () -> Optional[Team] team_name = pick_value('Select a team', list(self._teams.keys()), True, 'Cancel') if team_name is None: return None return self._teams[team_name]
def add_label_view(self): name = get_input('Name this view: ') jira_connection_name = pick_value( 'Which JIRA Connection does this belong to? ', list(self._jira_connections.keys())) jira_connection = self._jira_connections[jira_connection_name] new_view = JiraView(name, jira_connection) self.jira_views[name] = new_view jira_filter = JiraFilter('labels', jira_connection) while True: label = get_input('Add which label? ([q] to quit)') if label == 'q': break if label.isspace() or label == '': continue print('Adding label: [{}]'.format(label)) jira_filter.include(label) res_jf = JiraFilter('Resolution', jira_connection) res_jf.include('unresolved') new_view.add_raw_filter(jira_filter) new_view.add_raw_filter(res_jf) self._save_config() new_view.display_view(self) print('Creating new view with label(s): {}'.format(','.join( jira_filter._includes)))
def remove_view(self): if len(self.jira_views) == 0: print('No views to remove.') return to_remove = pick_value('Select a view to delete or [q]uit: ', list(self.jira_views.keys())) if to_remove is None: return # Build list of Dashboards this is going to invalidate to confirm affected_dashes = [] for dash in list(self.jira_dashboards.values()): if dash.contains_jira_view(to_remove): print( 'WARNING: Removing this view will also remove JiraDashboard: {}.' .format(dash)) affected_dashes.append(dash.name) if is_yes('Are you sure you want to delete {}?'.format(to_remove)): self.jira_views[to_remove].delete_config() del self.jira_views[to_remove] # Determine if any dashboards exist w/this view and delete them for dash_name in affected_dashes: del self.jira_dashboards[dash_name] self._save_config()
def remove_custom_report_job(self): if self.active_report.job_names: job_options = self.active_report.job_names while True: job_name = pick_value( 'Which Jenkins job would you like to remove?', job_options) if job_name: for connection_name, job_name_list in self.active_report.connection_dict.iteritems( ): if job_name in job_name_list: self.active_report.remove_job_from_report( job_name, connection_name) for connection_name in self.active_report.connection_names: if not self.active_report.connection_dict[ connection_name]: self.active_report.connection_dict.pop( connection_name) self.active_report.save_report_config() print('Successfully removed job: {}'.format(job_name)) job_options.remove(job_name) else: break else: print('No Jenkins jobs to remove from report.') pause()
def display_view(self): view_name = pick_value('Which view?', list(self.jira_views.keys()), True, 'Back') if view_name is None: return self.jira_views[view_name].display_view(self) self._save_config()
def view_cached_jobs(self): if self.active_connection.job_names: view_options = sorted(self.active_connection.view_names) all_jobs = '* All Jobs' view_options.append(all_jobs) view_name = pick_value('Which jobs would you like to view?', view_options) if view_name: if view_name == all_jobs: self.print_job_options(self.active_connection.jobs, connection=True) else: jobs_to_print = [] job_names = self.active_connection.jenkins_views[ view_name].job_names if job_names: for job_name in job_names: jobs_to_print.append( self.active_connection.jenkins_jobs[job_name]) self.print_job_options(jobs_to_print, connection=True) else: print('No jobs to print in this view.') else: if is_yes( 'There are no cached jobs for the current connection. Would you like to download jobs now?' ): self.download_jobs()
def add_filter(self): filter_value = None # Adding to prevent PEP complaint. filter_name = None while filter_value is None: filters = JiraView.PRE_FILTERS[:] filters.append('Remove a filter') filters.append('Other') filter_name = pick_value('Select a field to filter on:', filters, True, 'Cancel view edit') if filter_name is None: return # Special logic to handle things we have known values for if filter_name in self._known_fields: filter_value = pick_value('Select {}'.format(filter_name), self._known_fields[filter_name]) # since potential assignees vary by project, we don't store them in _known_fields elif filter_name == 'assignee' or filter_name == 'reviewer' or filter_name == 'reviewer2' or filter_name == 'reporter': # Disallow addition of assignee/reviewer/reviewer2 if a team filter is active if len(self._teams) > 0: if filter_name != 'reporter': print( 'Cannot add a[n] {} filter when a team filter is active. Remove team filter if you would like to add this.' .format(filter_name)) return filter_value = self.jira_connection.pick_single_assignee() if filter_value is None: return elif filter_name == 'Project': filter_value = self.jira_connection.pick_project() if filter_value is None: return elif filter_name == 'Remove a filter': self.remove_filter() return else: filter_value = get_input('{}:'.format(filter_name)) while True: filter_type = get_input('[i]nclude or [e]xclude?') if not filter_type == 'i' and not filter_type == 'e': print('Try again.') else: break self.add_single_filter(filter_name, filter_value, filter_type, 'AND')
def edit_dashboard(self): dn = pick_value('Which dashboard?', list(self.jira_dashboards.keys()), True, 'Cancel') if dn is None: return dash = self.jira_dashboards[dn] dash.edit_dashboard(self.jira_views) self._save_config()
def prompt_for_team_addition(self, jira_manager: 'JiraManager') -> None: name = get_input('Name this new team:', lowered=False) jira_connection_name = pick_value('Which JIRA Connection owns this team?', jira_manager.possible_connections(), True, 'Cancel') if jira_connection_name is None: return self._teams[name] = Team(name, jira_connection_name) self.edit_team(jira_manager, name)
def _pick_member_for_linkage_operation(self, action: str) -> Optional[MemberIssuesByStatus]: """ Prompts for both team to remove from and then member. Used on both addition and deletion paths. """ target_team_name = pick_value('{} a linked member on which team?'.format(action), list(self._teams.keys())) if target_team_name is None: return None target_team = self._teams[target_team_name] print_separator(40) print('Detailed status of team members:') for member in target_team.members: print(' {}'.format(member)) print_separator(40) target_member_name = pick_value('{} a linked JIRA user-name on which member?'.format(action), target_team.member_names) if target_member_name is None: return None return target_team.get_member_issues(target_member_name)
def remove_dashboard(self): dn = pick_value('Remove which dashboard?', list(self.jira_dashboards.keys()), True, 'Cancel') if dn is None: return prompt = get_input('About to delete [{}]. Are you sure?'.format(dn)) if prompt == 'y': del self.jira_dashboards[dn] self._save_config()
def remove_filter(self): to_remove = pick_value('Remove value from which JiraFilter?', list(self._jira_filters.keys())) if to_remove is None: return self._jira_filters[to_remove].remove_filter() if self._jira_filters[to_remove].is_empty(): del self._jira_filters[to_remove] self.save_config()
def remove_custom_report(self): report_name = pick_value( 'Which custom report would you like to remove?', list(self.jenkins_reports.keys())) if report_name: self.jenkins_reports.pop(report_name) self.save_jenkins_config() print('Successfully removed custom report: {}'.format(report_name)) pause()
def pick_team(self, skip_list: Optional[List[str]] = None) -> Optional[Team]: if skip_list is None: valid_names = list(self._teams.keys()) else: valid_names = [x for x in self._teams.keys() if x not in skip_list] team_name = pick_value('Select a team', valid_names, True, 'Cancel') if team_name is None: return None return self._teams[team_name]
def display_dashboard(self): if len(self.jira_dashboards) == 0: print('No dashboards. Create one first.') return dn = pick_value('Display which dashboard\'s results?', list(self.jira_dashboards.keys()), True, 'Cancel') if dn is None: return self.jira_dashboards[dn].display_dashboard(self, self.jira_views)
def delete_cached_jira_project(self): jira_connection_name = pick_value( 'Delete cached project data for which JiraConnection?', [x.connection_name for x in self.jira_connections()]) if jira_connection_name is None: return jira_connection = self._jira_connections[jira_connection_name] project_cache_to_delete = pick_value( 'Delete cached data for which JiraProject?', jira_connection.cached_project_names) if project_cache_to_delete is None: return if is_yes('About to delete locally cached content: {}. Are you sure?'. format( jira_connection.maybe_get_cached_jira_project( project_cache_to_delete))): jira_connection.delete_cached_jira_project(project_cache_to_delete)
def run_org_report(self, jira_manager: 'JiraManager') -> None: """ Sub-menu driven method to run a specific type of report across multiple teams within an organization """ org_name = None if len(self._organizations) == 0: # We don't prompt for addition now since we'd have to pass in main menu context to do that from here. print('No organizations found. Please use the Team Management menu to define a new organization before running a report.') pause() return while True: clear() if org_name is None: print_separator(40) org_name = pick_value('Run reports against which organization?', list(self._organizations.keys())) # None return from pick_team == cancel if org_name is None: return for team_name in sorted(self._organizations[org_name]): active_team = self._teams[team_name] print('Populating tickets for team: {}'.format(active_team.name)) TeamManager.populate_owned_jira_issues(jira_manager, active_team.members) print('---------------------') print('- Org Menu -') print('---------------------') print('t: Change active org. Current: {}'.format(org_name)) self._print_report_menu() print('q: Cancel') print('---------------------') choice = get_input(':') if choice == 'q': return elif choice == 't': org_name = None try: report_type = ReportType.from_int(int(choice)) if report_type == ReportType.UNKNOWN: print('Bad input: {}. Try again.'.format(choice)) pause() else: report_to_run = TeamManager.reports[report_type] if TeamManager.reports[report_type].needs_duration: report_to_run.since = time_utils.since_now(ReportFilter.get_since()) # making mypy happy assert org_name is not None self._run_org_report(jira_manager, org_name, report_to_run) pause() except (ValueError, TypeError) as e: print('Error on input: {}. Try again'.format(e)) traceback.print_exc() pause()
def remove_alias(self) -> bool: if len(self._aliased_names) == 0: print('No aliases. Returning.') return False to_remove = pick_value('Remove which alias from this member?', list(self._aliased_names.keys())) if to_remove is None: return False del self._aliased_names[to_remove] return True
def prompt_to_remove_member(self): # type: () -> bool """ :return: Whether deletion too place or not so parent can save team config on change """ to_remove = pick_value('Remove which member?', list(self._team_members.keys())) if to_remove is None: return False del self._team_members[to_remove] return True
def pick_jira_connection( self, prompt: str = 'Which JIRA connection?' ) -> Optional[JiraConnection]: if not self._prompt_connection_add_if_none(): return None choice = pick_value(prompt, list(self._jira_connections.keys()), True) if choice is None: return None return self._jira_connections[choice]
def search_projects(self): """ Does a one-off ad-hoc search for strings in all cached fields for all cached JiraProjects. Keeping at scope of JiraManager as search is a meta-scoped search of all cached JiraProjects independent of JiraConnection :return: """ tinput = get_input('Search [o]pen issues only, [c]losed, or [a]ll?') substring = get_input('Search for what substring?') matches = [] jira_projects = self.get_all_cached_jira_projects() # cache list of seen columns for the query columns = {} for project in list(jira_projects.values()): results = project.get_matching_issues(substring, tinput) for r in results: matches.append(r) for k, v in r.items(): # This is going to blast out our ability to filter on reviewer or reviewer 2. For now. if 'custom' not in k: columns[k] = True original_list = JiraUtils.sort_custom_jiraissues_by_key(matches) display_list = original_list df = DisplayFilter.default() while True: df.display_and_return_sorted_issues(self, display_list) print_separator(30) cinput = get_input( '[#] to open an issue in browser, [c] to clear column filters, [f] to specify a specific field to match on, [q] to return to menu:' ) if str.lower(cinput) == 'f': col_name = pick_value('Filter on which column?', list(columns.keys()), False) newlist = [] for ji in display_list: if col_name in ji: if substring in ji[col_name]: newlist.append(ji) display_list = newlist elif str.lower(cinput) == 'c': display_list = original_list elif not str.lower(cinput) == 'q': try: jira_issue = display_list[int(cinput) - 1] JiraUtils.open_issue_in_browser( self._jira_connections[ jira_issue.jira_connection_name].url, jira_issue.issue_key) except ValueError: print('Bad input. Try again.') elif str.lower(cinput) == 'q': break
def prompt_to_remove_member(self) -> bool: """ :return: Whether deletion took place or not so parent can save team config on change """ to_remove = pick_value('Remove which member?', self.member_names) if to_remove is None: return False to_del = self.convert_name_to_jira_user_name(to_remove) if to_del is None: return False del self._team_members[to_del] return True
def build(cls, jira_views: Dict[str, JiraView]) -> Optional['JiraDashboard']: """ Links 2 or more JiraViews together, combining their results for display """ if len(jira_views) <= 1: print( 'Need at least 2 JiraViews to create a dashboard. Please create more JiraViews first.' ) return None view_name_options = list(jira_views.keys()) dash_name = get_input('Name this dashboard:', lowered=False) view_name = pick_value('Include which view?', view_name_options, True, 'Cancel') if view_name is None: return None dash_views = {view_name: jira_views[view_name]} view_name_options.remove(view_name) view_name = pick_value('Second view?', view_name_options, False) # make mypy happy - doesn't realize False means we can't have None if view_name is None: return None dash_views[view_name] = jira_views[view_name] view_name_options.remove(view_name) while len(view_name_options) > 0: if is_yes('Add another?'): view_name = pick_value('What view?', view_name_options, True, '[q] Cancel') if view_name == 'q' or view_name is None: break dash_views[view_name] = jira_views[view_name] view_name_options.remove(view_name) else: break return JiraDashboard(dash_name, dash_views)