Hello community, here is the log from the commit of package azure-cli-feedback for openSUSE:Factory checked in at 2019-10-31 18:16:10 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/azure-cli-feedback (Old) and /work/SRC/openSUSE:Factory/.azure-cli-feedback.new.2990 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "azure-cli-feedback" Thu Oct 31 18:16:10 2019 rev:7 rq:742692 version:2.2.1 Changes: -------- --- /work/SRC/openSUSE:Factory/azure-cli-feedback/azure-cli-feedback.changes 2019-06-05 11:43:33.635035040 +0200 +++ /work/SRC/openSUSE:Factory/.azure-cli-feedback.new.2990/azure-cli-feedback.changes 2019-10-31 18:16:12.170069368 +0100 @@ -1,0 +2,8 @@ +Thu Oct 24 12:10:24 UTC 2019 - John Paul Adrian Glaubitz <adrian.glaub...@suse.com> + +- New upstream release + + Version 2.2.1 + + For detailed information about changes see the + HISTORY.txt file provided with this package + +------------------------------------------------------------------- Old: ---- azure-cli-feedback-2.2.0.tar.gz New: ---- azure-cli-feedback-2.2.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ azure-cli-feedback.spec ++++++ --- /var/tmp/diff_new_pack.jszJb2/_old 2019-10-31 18:16:13.014070242 +0100 +++ /var/tmp/diff_new_pack.jszJb2/_new 2019-10-31 18:16:13.022070250 +0100 @@ -17,7 +17,7 @@ Name: azure-cli-feedback -Version: 2.2.0 +Version: 2.2.1 Release: 0 Summary: Microsoft Azure CLI 'feedback' Command Module License: MIT ++++++ azure-cli-feedback-2.2.0.tar.gz -> azure-cli-feedback-2.2.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-feedback-2.2.0/HISTORY.rst new/azure-cli-feedback-2.2.1/HISTORY.rst --- old/azure-cli-feedback-2.2.0/HISTORY.rst 2019-04-05 00:46:52.000000000 +0200 +++ new/azure-cli-feedback-2.2.1/HISTORY.rst 2019-04-19 00:15:19.000000000 +0200 @@ -3,6 +3,10 @@ Release History =============== +2.2.1 ++++++ +* Minor fixes + 2.2.0 +++++ * `az feedback` now shows metadata on recently run commands diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-feedback-2.2.0/PKG-INFO new/azure-cli-feedback-2.2.1/PKG-INFO --- old/azure-cli-feedback-2.2.0/PKG-INFO 2019-04-05 00:47:37.000000000 +0200 +++ new/azure-cli-feedback-2.2.1/PKG-INFO 2019-04-19 00:16:08.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: azure-cli-feedback -Version: 2.2.0 +Version: 2.2.1 Summary: Microsoft Azure Command-Line Tools Feedback Command Module Home-page: https://github.com/Azure/azure-cli Author: Microsoft Corporation @@ -20,6 +20,10 @@ Release History =============== + 2.2.1 + +++++ + * Minor fixes + 2.2.0 +++++ * `az feedback` now shows metadata on recently run commands diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-feedback-2.2.0/azure/cli/command_modules/feedback/custom.py new/azure-cli-feedback-2.2.1/azure/cli/command_modules/feedback/custom.py --- old/azure-cli-feedback-2.2.0/azure/cli/command_modules/feedback/custom.py 2019-04-05 00:46:52.000000000 +0200 +++ new/azure-cli-feedback-2.2.1/azure/cli/command_modules/feedback/custom.py 2019-04-19 00:15:19.000000000 +0200 @@ -5,6 +5,7 @@ from __future__ import print_function import os +import re import math import platform import datetime @@ -23,12 +24,14 @@ from azure.cli.core.util import get_az_version_string from azure.cli.core.azlogging import _UNKNOWN_COMMAND, _CMD_LOG_LINE_PREFIX from azure.cli.core.util import open_page_in_browser -import pyperclip _ONE_MIN_IN_SECS = 60 _ONE_HR_IN_SECS = 3600 +# see: https://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers +_MAX_URL_LENGTH = 2035 + logger = get_logger(__name__) @@ -36,8 +39,12 @@ _GET_STARTED_URL = "aka.ms/azcli/get-started" _QUESTIONS_URL = "aka.ms/azcli/questions" + _CLI_ISSUES_URL = "aka.ms/azcli/issues" +_RAW_CLI_ISSUES_URL = "https://github.com/Azure/azure-cli/issues/new" + _EXTENSIONS_ISSUES_URL = "aka.ms/azcli/ext/issues" +_RAW_EXTENSIONS_ISSUES_URL = "https://github.com/Azure/azure-cli-extensions/issues/new" _MSG_INTR = \ '\nWe appreciate your feedback!\n\n' \ @@ -54,12 +61,12 @@ BEGIN TEMPLATE =============== **A browser has been opened to {} to create an issue.** -**The issue template has been copied to your clipboard. You can also run `az feedback --verbose` to emit the output to stdout.** +**You can also run `az feedback --verbose` to emit the full output to stderr.** """ _ISSUES_TEMPLATE = """ -### **This is an autogenerated template. Please review and update as needed.** +### **This is autogenerated. Please review and update as needed.** ## Describe the bug @@ -70,15 +77,13 @@ {errors_string} ## To Reproduce: -Steps to reproduce the behavior. Note: Command arguments have been redacted. +Steps to reproduce the behavior. Note that argument values have been redacted, as they may contain sensitive information. -- `Fill in additional info here` -- `Run: {executed_command}` +- _Put any pre-requisite steps here..._ +- `{executed_command}` ## Expected Behavior -A clear and concise description of what you expected to happen. - ## Environment Summary ``` {platform} @@ -88,9 +93,8 @@ {cli_version} ``` ## Additional Context -Add any other context about the problem here. -<!-- Please do not remove these markdown comments --> +<!--Please don't remove this:--> {auto_gen_comment} """ @@ -167,6 +171,12 @@ success_msg = "SUCCESS" if was_successful else "FAILURE" return success_msg + def failed(self): + if not self.command_data_dict: + return False + + return not self.command_data_dict.get("success", True) + def get_command_time_str(self): if not self.metadata_tup: return "" @@ -268,7 +278,7 @@ if status_msg.startswith("exit code"): idx = status_msg.index(":") # raises ValueError exit_code = int(log_record_list[-1].log_msg[idx + 1:].strip()) - log_data["success"] = True if not exit_code else False + log_data["success"] = bool(not exit_code) except (IndexError, ValueError): logger.debug("Couldn't extract exit code from command log %s.", file_name) @@ -328,9 +338,147 @@ if parts[0] != p_id: # ensure that this is indeed a valid log. return None + # add newline at end of log + if not parts[-1].endswith("\n"): + parts[-1] += "\n" + return CommandLogFile._LogRecordType(*parts) +class ErrorMinifier(object): + + _FILE_RE = re.compile(r'File "(.*)"') + _CONTINUATION_STR = "...\n" + + def __init__(self, errors_list): + self._errors_list = errors_list + self._capacity = None + self._minified_error = "\n".join(self._errors_list) + + def set_capacity(self, capacity): + logger.debug("Capacity for error string: %s", capacity) + + self._capacity = int(capacity) + self._minified_error = self._get_minified_errors() + + def _get_minified_errors(self): # pylint: disable=too-many-return-statements + errors_list = self._errors_list + errors_string = "\n".join(errors_list) + if self._capacity is None: + return errors_string + + if not errors_list: + return "" + + # if within capacity return string + if len(errors_string) <= self._capacity: + return errors_string + + # shorten file names and try again + for i, error in enumerate(errors_list): + errors_list[i] = self._minify_by_shortening_file_names(error, levels=5) + errors_string = "\n".join(errors_list) + if len(errors_string) <= self._capacity: + return errors_string + + # shorten file names and try again + for i, error in enumerate(errors_list): + errors_list[i] = self._minify_by_shortening_file_names(error, levels=4) + errors_string = "\n".join(errors_list) + if len(errors_string) <= self._capacity: + return errors_string + + # return first exception if multiple exceptions occurs + for i, error in enumerate(errors_list): + errors_list[i] = self._minify_by_removing_nested_exceptions(error) + errors_string = "\n".join(errors_list) + if len(errors_string) <= self._capacity: + return errors_string + + # last resort keep removing middle lines + while len(errors_string) > self._capacity: + errors_string = self._minify_by_removing_lines(errors_string) + + return errors_string + + @staticmethod + def _minify_by_shortening_file_names(error_string, levels=5): + new_lines = [] + for line in error_string.splitlines(): + # if original exception + if line.strip().startswith("File") and ", line" in line: + parts = line.split(",") + match = ErrorMinifier._FILE_RE.search(parts[0]) + if match: + parts[0] = ErrorMinifier._shorten_file_name(match.group(1), levels) + parts[1] = parts[1].replace("line", "ln") + line = ",".join(parts) + # if cleaned exceptions + elif ".py" in line and ", ln" in line: + parts = line.split(",") + parts[0] = ErrorMinifier._shorten_file_name(parts[0], levels) + line = ",".join(parts) + # remove this line + elif "here is the traceback" in line.lower(): + continue + + new_lines.append(line) + + return "\n".join(new_lines) + + @staticmethod + def _shorten_file_name(file_name, levels=5): + if levels > 0: + new_name = os.path.basename(file_name) + file_name = os.path.dirname(file_name) + for _ in range(levels - 1): + new_name = os.path.join(os.path.basename(file_name), new_name) + file_name = os.path.dirname(file_name) + return new_name + return file_name + + @staticmethod + def _minify_by_removing_nested_exceptions(error_string): + lines = error_string.splitlines() + + idx = len(lines) - 1 + for i, line in enumerate(lines): + if "During handling of the above exception" in line: + idx = i + break + + # if unchanged return error_string + if idx == len(lines) - 1: + return error_string + + lines = lines[:idx] + [ErrorMinifier._CONTINUATION_STR] + lines[-3:] + return "\n".join(lines) + + @staticmethod + def _minify_by_removing_lines(error_string): + error_string = error_string.replace(ErrorMinifier._CONTINUATION_STR, "") + lines = error_string.splitlines() + + mid = int(len(lines) / 2) + 1 + if not (".py" in lines[mid] and ", ln" in lines[mid]): + mid -= 1 + + new_lines = [] + for i, line in enumerate(lines): + if i == mid: + new_lines.append(ErrorMinifier._CONTINUATION_STR.strip()) + if i in range(mid, mid + 4): + continue + new_lines.append(line) + + return "\n".join(new_lines) + + def __str__(self): + if self._minified_error: + return "```\n{}\n```".format(self._minified_error.strip()) + return "" + + def _build_issue_info_tup(command_log_file=None): def _get_parent_proc_name(): import psutil @@ -354,7 +502,7 @@ format_dict["command_name"] = command_name if command_log_file.command_data_dict: - errors = "\n".join(command_log_file.command_data_dict.get("errors", [])) + errors_list = command_log_file.command_data_dict.get("errors", []) executed_command = command_log_file.command_data_dict.get("command_args", "") extension_name = command_log_file.command_data_dict.get("extension_name", "") extension_version = command_log_file.command_data_dict.get("extension_version", "") @@ -364,16 +512,7 @@ extension_info = "\nExtension Name: {}. Version: {}.".format(extension_name, extension_version) is_ext = True - if errors: - num_lines = errors.count("\n") - reaction = ":confused:" - if num_lines >= 100: - reaction = ":expressionless:" - elif num_lines >= 15: - reaction = ":open_mouth:" - errors = "\n{}\n\n```{}```".format(reaction, errors) - - format_dict["errors_string"] = errors + format_dict["errors_string"] = ErrorMinifier(errors_list) format_dict["executed_command"] = "az " + executed_command if executed_command else executed_command format_dict["command_name"] += extension_info @@ -384,17 +523,64 @@ format_dict["shell"] = "Shell: {}".format(_get_parent_proc_name()) format_dict["auto_gen_comment"] = _AUTO_GEN_COMMENT - issues_url = _EXTENSIONS_ISSUES_URL if is_ext else _CLI_ISSUES_URL + pretty_url_name = _EXTENSIONS_ISSUES_URL if is_ext else _CLI_ISSUES_URL + # get issue body without minification + original_issue_body = _ISSUES_TEMPLATE.format(**format_dict) + + # First try + capacity = _MAX_URL_LENGTH # some browsers support a max of roughly 2000 characters + res = _get_minified_issue_url(command_log_file, format_dict.copy(), is_ext, capacity) + formatted_issues_url, minified_issue_body = res + capacity = capacity - (len(formatted_issues_url) - _MAX_URL_LENGTH) + + # while formatted issue to long, minify to new capacity + tries = 0 + while len(formatted_issues_url) > _MAX_URL_LENGTH and tries < 25: + # reduce capacity by difference if formatted_issues_url is too long because of url escaping + res = _get_minified_issue_url(command_log_file, format_dict.copy(), is_ext, capacity) + formatted_issues_url, minified_issue_body = res + capacity = capacity - (len(formatted_issues_url) - _MAX_URL_LENGTH) + tries += 1 + + # if something went wrong with minification (i.e. another part of the issue is unexpectedly too long) + # then truncate the whole issue body and warn the user. + if len(formatted_issues_url) > _MAX_URL_LENGTH: + formatted_issues_url = formatted_issues_url[:_MAX_URL_LENGTH] + logger.warning("Failed to properly minify issue url. " + "Please use 'az feedback --verbose' to get the full issue output.") + + logger.debug("Total minified issue length is %s", len(minified_issue_body)) + logger.debug("Total formatted url length is %s", len(formatted_issues_url)) + + return _ISSUES_TEMPLATE_PREFIX.format(pretty_url_name), formatted_issues_url, original_issue_body + + +def _get_minified_issue_url(command_log_file, format_dict, is_ext, capacity): + # get issue body without errors + minified_errors = format_dict["errors_string"] + format_dict["errors_string"] = "" + no_errors_issue_body = _ISSUES_TEMPLATE.format(**format_dict) + + # get minified issue body + format_dict["errors_string"] = minified_errors + if hasattr(minified_errors, "set_capacity"): + logger.debug("Length of issue body before errors added: %s", len(no_errors_issue_body)) + minified_errors.set_capacity( + capacity - len(no_errors_issue_body)) # factor in length of url and expansion of url escaped characters + minified_issue_body = _ISSUES_TEMPLATE.format(**format_dict) # prefix formatted url with 'https://' if necessary and supply empty body to remove any existing issue template - formatted_issues_url = issues_url + # aka.ms doesn't work well for long urls / query params + formatted_issues_url = _RAW_EXTENSIONS_ISSUES_URL if is_ext else _RAW_CLI_ISSUES_URL if not formatted_issues_url.startswith("http"): formatted_issues_url = "https://" + formatted_issues_url - new_placeholder = urlencode({'body': "The issue has been copied to your clipboard. Paste it here!" - "\nTo print out the issue body locally, run `az feedback --verbose`"}) + query_dict = {'body': minified_issue_body} + if command_log_file and command_log_file.failed(): + query_dict['template'] = 'Bug_report.md' + new_placeholder = urlencode(query_dict) formatted_issues_url = "{}?{}".format(formatted_issues_url, new_placeholder) - return _ISSUES_TEMPLATE_PREFIX.format(issues_url), _ISSUES_TEMPLATE.format(**format_dict), formatted_issues_url + return formatted_issues_url, minified_issue_body def _get_az_version_summary(): @@ -411,6 +597,7 @@ legal_line = -1 for i, line in enumerate(lines): if line.startswith("azure-cli"): + line = " ".join(line.split()) new_lines.append(line) if line.lower().startswith("extensions:"): ext_line = i @@ -424,7 +611,12 @@ if 0 < ext_line < legal_line: for i in range(ext_line, legal_line): - new_lines.append(lines[i]) + l_lower = lines[i].lower() + if "python location" in l_lower or "extensions directory" in l_lower: + break + + line = " ".join(lines[i].split()) + new_lines.append(line) return "\n".join(new_lines) @@ -515,26 +707,27 @@ if ans in ["y", "n"]: if ans == "y": - prefix, body, url = _build_issue_info_tup() + prefix, url, original_issue = _build_issue_info_tup() else: return False else: if ans in ["q", "quit"]: return False if ans == 0: - prefix, body, url = _build_issue_info_tup() + prefix, url, original_issue = _build_issue_info_tup() else: - prefix, body, url = _build_issue_info_tup(recent_command_list[ans]) + prefix, url, original_issue = _build_issue_info_tup(recent_command_list[ans]) print(prefix) # open issues page in browser and copy issue body to clipboard - try: - pyperclip.copy(body) - except pyperclip.PyperclipException as ex: - logger.debug(ex) + # import pyperclip + # try: # todo: if no longer using clipboard, remove dependency + # pyperclip.copy(original_issue) + # except pyperclip.PyperclipException as ex: + # logger.debug(ex) - logger.info(body) + logger.info(original_issue) open_page_in_browser(url) return True diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-feedback-2.2.0/azure_cli_feedback.egg-info/PKG-INFO new/azure-cli-feedback-2.2.1/azure_cli_feedback.egg-info/PKG-INFO --- old/azure-cli-feedback-2.2.0/azure_cli_feedback.egg-info/PKG-INFO 2019-04-05 00:47:37.000000000 +0200 +++ new/azure-cli-feedback-2.2.1/azure_cli_feedback.egg-info/PKG-INFO 2019-04-19 00:16:08.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: azure-cli-feedback -Version: 2.2.0 +Version: 2.2.1 Summary: Microsoft Azure Command-Line Tools Feedback Command Module Home-page: https://github.com/Azure/azure-cli Author: Microsoft Corporation @@ -20,6 +20,10 @@ Release History =============== + 2.2.1 + +++++ + * Minor fixes + 2.2.0 +++++ * `az feedback` now shows metadata on recently run commands diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/azure-cli-feedback-2.2.0/setup.py new/azure-cli-feedback-2.2.1/setup.py --- old/azure-cli-feedback-2.2.0/setup.py 2019-04-05 00:46:52.000000000 +0200 +++ new/azure-cli-feedback-2.2.1/setup.py 2019-04-19 00:15:19.000000000 +0200 @@ -15,7 +15,7 @@ cmdclass = {} -VERSION = "2.2.0" +VERSION = "2.2.1" CLASSIFIERS = [ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers',