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 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 display_view(self, jira_manager: 'JiraManager') -> None: df = DisplayFilter.default() working_issues = list(self.get_issues().values()) while True: try: issues = df.display_and_return_sorted_issues( jira_manager, working_issues) print_separator(60) print('[JiraView operations for {}]'.format(self.name)) input_prompt = ( "[f] to manually enter a substring to regex issues in the view\n" "[c] to clear all regex filtering\n" "[#] Integer value to open ticket in browser\n" "[q] to quit\n" ":") custom = get_input(input_prompt) if custom == 'q': return elif custom == 'f': string = get_input('substring to match:', lowered=False) new_issues = [] for jira_issue in working_issues: if jira_issue.matches(self.jira_connection, string): new_issues.append(jira_issue) working_issues = new_issues elif custom == 'c': working_issues = list(self.get_issues().values()) elif len(issues) == 0: print( 'No matching jira issues found. Skipping attempt to open.' ) pause() else: try: JiraUtils.open_issue_in_browser( self.jira_connection.url, issues[int(custom) - 1].issue_key) except ValueError: print('Bad input. Try again.') except JIRAError as je: print('Caught an error: {}'.format(je)) traceback.print_exc() return
def _print_member_details(jira_manager: 'JiraManager', tickets: MemberIssuesByStatus, report_filter: ReportFilter) -> None: # We need to re-populate this report filter with this user for matching logic to work report_filter.clear() report_filter.process_issues(tickets) displayed_issues = tickets.display_member_issues(jira_manager, report_filter) while True: if len(displayed_issues) == 0: print('No issues found matching category.') pause() break cmd = get_input('[#] Integer value to open JIRA issue in browser. [q] to return to report results:') if cmd == 'q': break try: jira_issue = displayed_issues[int(cmd) - 1] jira_connection = jira_manager.get_jira_connection(jira_issue.jira_connection_name) JiraUtils.open_issue_in_browser(jira_connection.url, jira_issue.issue_key) except ValueError as ve: print('Bad input. Try again.') print('ValueError : {}'.format(ve)) pause()
def display_view(self, jira_manager): # type: (JiraManager) -> None df = DisplayFilter.default() working_issues = list(self.get_issues().values()) while True: try: issues = df.display_and_return_sorted_issues( jira_manager, working_issues) print_separator(60) print('[JiraView operations for {}]'.format(self.name)) custom = get_input( '[f] to manually enter a substring to regex issues in the view' + os.linesep + '[c] to clear all regex filtering' + os.linesep + '[#] Integer value to open ticket in browser ' + os.linesep + '[q] to quit' + os.linesep + ':') if custom == 'q': return elif custom == 'f': string = get_input('substring to match:', lowered=False) new_issues = [] for jira_issue in working_issues: if jira_issue.matches(self.jira_connection, string): new_issues.append(jira_issue) working_issues = new_issues elif custom == 'c': working_issues = list(self.get_issues().values()) else: try: JiraUtils.open_issue_in_browser( self.jira_connection.url, issues[int(custom) - 1].issue_key) except ValueError: print('Bad input. Try again.') except JIRAError as je: print('Caught an error: {}'.format(je)) traceback.print_exc() return
def save_config(self) -> None: # .cfg file config_parser = configparser.RawConfigParser() config_parser.add_section('Config') if self.jira_connection is not None: config_parser.set('Config', 'connection_name', self.jira_connection.connection_name) config_parser.set('Config', 'project_name', self.project_name) config_parser.set('Config', 'updated', self.updated) config_parser.set('Config', 'url', self._url) config_parser.set('Config', 'custom_fields', ','.join(list(self._custom_fields.keys()))) for field in list(self._custom_fields.keys()): config_parser.set('Config', field, self._custom_fields[field]) save_argus_config(config_parser, self.config_file()) # Protect against saving during init wiping out the local data file. Shouldn't be an issue but seen it pop up # during dev once or twice. if len(self.jira_issues.keys()) > 0: JiraUtils.save_argus_data(list(self.jira_issues.values()), self._data_file())
def refresh(self): new_issues = JiraUtils.get_issues_for_project(self.jira_connection, self.project_name, self.updated) if len(new_issues) > 0: print('Found {} updated/new issues for {}. Saving to disk.'.format( len(new_issues), self.project_name)) for jira_issue in new_issues: if 'updated' not in jira_issue: print( 'Missing updated field in issue: {}. Skipping in latest updated calculation.' .format(jira_issue.issue_key)) else: clean_ts = JiraProject.clean_ts(jira_issue['updated']) if clean_ts > self.updated: self.updated = clean_ts self.jira_issues[jira_issue.issue_key] = jira_issue self.save_config()
def _build_issue_row(self, jira_manager: 'JiraManager', issue: 'JiraIssue', filters: Dict['Column', str], dependency: Optional['JiraDependency'] = None) -> str: """ :param filters: Inclusion-based column-value filters: skips entire row based on this inclusion. :param dependency: Optional JiraDependency. Presence of this field indicates this JiraIssue is a dependent ticket, which changes our logic somewhat on how we format things (paren, indentation, etc) """ if dependency is None: issue_key = issue.issue_key else: # Preface with a hyphen per dependency depth issue_key = '{}{}'.format('-' * DisplayFilter._current_depth, dependency.target.issue_key) issue_string = '{:4}:{}'.format( self._current_index, str(issue_key)[:self._key_len].ljust(self._key_len)) for column in list(self.included_columns): if dependency is not None and column.pretty_name == DisplayFilter.RELATIONSHIP_STRING: val = dependency.pretty_type() else: val = JiraUtils.retrieve_field_value(jira_manager, issue, column.name) # filters are include-only, so if we don't have a value but do have includes, drop it if filters is not None: # On non-matches, we don't return this row at all if val is None and column.name in filters: return '' elif column in filters and val is not None and filters[ column] not in val: return '' val = '' if val is None else val issue_string += '| {} '.format( str(val)[:column.width].ljust(column.width)) issue_string += os.linesep self._current_index += 1 return issue_string
def report_fix_version(self) -> None: """ Creates a report of all tickets, including dependencies, to the input FixVersion. """ # Only support creating of this on a single JiraConnection, with the assumption that multiple projects on that # connection can share a FixVersion, but these won't straddle to exterior Jira instances target_connection = self.pick_jira_connection( 'FixVersion report for which JiraConnection?') if target_connection is None: return open_only = is_yes('Show only unresolved issues?') to_match = get_input('Input substring to search fixversions for:', False) available_versions = set() for jira_project in target_connection.cached_projects: for jira_issue in jira_project.jira_issues.values(): for fix in jira_issue['fixVersions'].split(','): if to_match in fix: available_versions.add(fix) report_version = pick_value('Generate report for which FixVersion?', list(available_versions)) if report_version is None: return print('Generating report on: {}'.format(report_version)) # Now find all "primary root" members on this FixVersion, generate a list of matching, then display w/dependency # chains enabled matching_issues = set() for jira_project in target_connection.cached_projects: for jira_issue in jira_project.jira_issues.values(): if jira_issue.has_fix_version(report_version): if (open_only and jira_issue.is_open) or not open_only: matching_issues.add(jira_issue) df = DisplayFilter.default() df.open_only = open_only df.include_column('fixVersions', 'FixVersion', 10, 2) # sort our keys by issuekey sorted_results = JiraUtils.sort_custom_jiraissues_by_key( list(matching_issues)) del matching_issues issues = df.display_and_return_sorted_issues(self, sorted_results, 1, None, True) while True: choice = get_input( '[#] to open an issue in browser, [p] to print report again, [q] to quit report: ' ) if choice == 'q': break elif choice == 'p': df.display_and_return_sorted_issues(self, sorted_results, 1, None, True) try: int_choice = int(choice) - 1 if int_choice < 0 or int_choice > len(issues) - 1: raise ValueError('oops') chosen_issue = issues[int_choice] if not chosen_issue.is_cached: print( 'Cannot open browser for non-cached issue (don\'t know url). Cache offline to inspect {}.' .format(chosen_issue.issue_key)) else: jira_conn = self._jira_connections[ chosen_issue.jira_connection_name] JiraUtils.open_issue_in_browser(jira_conn.url, chosen_issue.issue_key) except ValueError: print('Bad input. Try again.')
def sort_tickets(self): self.assigned = JiraUtils.sort_jira_issues(self.assigned) self.closed = JiraUtils.sort_jira_issues(self.closed) self.reviewer = JiraUtils.sort_jira_issues(self.reviewer) self.reviewed = JiraUtils.sort_jira_issues(self.reviewed)
def _run_report(jira_manager, team, report_filter): # type: (JiraManager, Team, ReportFilter) -> None # We prompt for a new 'since' on each iteration of the loop if report_filter.needs_duration: report_filter.since = time_utils.since_now( ReportFilter.get_since()) try: sorted_member_issues = sorted( team.members, key=lambda s: s.primary_name.user_name) while True: # Print out a menu of the meta information for each team member print_separator(40) print('[{}]'.format(report_filter.header)) print(report_filter.column_headers()) count = 1 for member_issues in sorted_member_issues: report_filter.clear() # We perform pre-processing and one-off prompting for time duration in .process call report_filter.process_issues(member_issues) print('{:5}: {}'.format( count, report_filter.print_all_counts( member_issues.primary_name.user_name))) count += 1 print_separator(40) cmd = get_input( '[#] Integer value to see a detailed breakdown by category. [q] to return to menu:' ) if cmd == 'q': break # Received detailed breakdown input try: c_input = int(cmd) - 1 # Pull out the MemberIssuesByStatus object for the chosen member for detailed printing # We need to re-populate this report filter with this user for matching logic to work full_member_issues = sorted_member_issues[c_input] report_filter.clear() report_filter.process_issues(full_member_issues) displayed_issues = full_member_issues.display_member_issues( jira_manager, report_filter) while True: cmd = get_input( '[#] Integer value to open JIRA issue in browser. [q] to return to report results:' ) if cmd == 'q': break try: jira_issue = displayed_issues[int(cmd) - 1] jira_connection = jira_manager.get_jira_connection( jira_issue.jira_connection_name) JiraUtils.open_issue_in_browser( jira_connection.url, jira_issue.issue_key) except ValueError as ve: print('Bad input. Try again.') print('ValueError : {}'.format(ve)) pause() except ValueError: break except JIRAError as je: print( 'Caught a JIRAError attempting to run a query: {}'.format(je)) pause()