-
Notifications
You must be signed in to change notification settings - Fork 3.3k
[Core] New error output #16257
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Core] New error output #16257
Conversation
|
Wait for service's readiness. |
|
Which parts are pending on Service? |
Aladdin service's new response :) |
|
Ready for review. Aladdin endpoint is for testing now and needs to be replaced. |
|
@chenlomis for awareness and possible comments. |
|
/azp run |
|
Azure Pipelines successfully started running 2 pipeline(s). |
Very cool! Thank you for putting this together! Quick questions: What's the diff for the following 2 commands aside from the fact that one has optional tag and the other doesn't? |
Thanks @chenlomis for the comments. These are the recommended commands from Aladdin service, @christopher-o-toole may want to say somthing here? For me, I think they are not duplicate commands for users. Because for every command, it always has a minimum set of arguments that could make it work. And all other argument combinations need to include these necessary arguments. We just do more advanced customizations with more arguments. Take |
Thank you for the elaboration! @houk-ms In the case of the screenshot though, despite the 2nd command having an additional --tags param, the descriptions are the same So imagine someone coming in genuinely seeking for next steps, how would (s)he know which one to pick....(?) And if (s)he doesn't know and can't follow through, then doesn't having both of them there cause additional confusion? Am actually open to keeping both as long as they mean and do different things, and that the differences are reflected in the description maybe that's a Q for @christopher-o-toole ? |
| print(recommendation, file=sys.stderr) | ||
|
|
||
| if self.aladdin_recommendations: | ||
| print('\nTRY THIS:', file=sys.stderr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use print_styled_text("\nTRY THIS:") or print_styled_text((Style.PRIMARY, "\nTRY THIS:")) once #16258 is merged.
| if self.aladdin_recommendations: | ||
| print('\nTRY THIS:', file=sys.stderr) | ||
| for recommendation, description in self.aladdin_recommendations: | ||
| print_styled_text(recommendation, file=sys.stderr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sys.stderr is now the default value for file.
| return [candidate['recommendation'] for candidate in candidates] | ||
|
|
||
| return candidates | ||
| def decorate_command(raw_command): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The functionality for colorization can be extracted and put in style.py as a common util.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
|
@chenlomis If we simply replace the contextual values, there are cases that we will make users confused because the description are not changed accordingly.What do you think about this? @christopher-o-toole and I tend to keep the original placeholders in the recommendations before we could find a proper method to solve it. |
|
One more comment on why I insisted on "deduping the recommendations" Below are some customer interview raw note, folks did have a reaction to seeing seemingly similar recommendations: if the 2nd rec with --tags actually do offer something more unique, then we shall totally keep it |
It's difficult to adjust the description, since it's more about semantic annlysis than a simple replacement. Let me give a more complex case Anyway, i think there are even more complicated cases ... |
For the commands deduping, i think @christopher-o-toole will keep improving this |
Oh I see how the the description is "locked" with the original recommendation now... Hmm I don't know how easy it is to implement, and it's probably a conversation for future releases, but is it possible to run the description through some form of syntactic analyzer and identifies the corresponding matching words with the command's values and set those as variables before displaying it here? That way when the values of the commands are replaced/updated, the descriptions will also get updated Otherwise I'd upvote for keeping the original description more general, so that it doesn't create confusion even if we replace/update the command values If that's not possible either.. (since it also requires updates to the description), then perhaps we should go with what you suggested earlier which is to keep the values non contextual for this release, until we figure out a workaround to update the description as well (maybe next release?). Because otherwise there'd be a mismatch between the the commands and the description @houk-ms @christopher-o-toole thoughts? |
Synched with @christopher-o-toole , seems both of the methods related with description adjustments are not easy. Let's keep the original values in this release and leave more time to figure out the better way. |
| error_type = AladdinUserFaultType.StorageAccountNotFound | ||
| elif 'resource_group' in error_msg or 'resource group' in error_msg: | ||
| error_type = AladdinUserFaultType.ResourceGroupNotFound | ||
| elif 'pattern' in error_msg or 'is not a valid value' in error_msg or 'invalid' in error_msg: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it enough to just match pattern instead of somethign like invalid pattern
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This are the rules provided by Aladdin Team for their internal error categories used to train model. I think it's ok as long as it makes sense to them.
| return ''.join(formatted_parts) | ||
|
|
||
|
|
||
| def get_styled_command(raw_command): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can call this function highlight_command, just as pygments does in Knack:
def format_json_color(obj):
from pygments import highlight, lexers, formatters
return highlight(format_json(obj), lexers.JsonLexer(), formatters.TerminalFormatter()) # pylint: disable=no-memberThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with jiasli.
Moreover, as far as I know that the UX design from Lomis adds the <> to wrap the parameter values. Such as
az webapp log config --name <WebappName> --resource-group <ResourceGroup> --detailed-error-messages <True or False>
Because only the color of each part is returned here, and the style content of text is not processed, I think the function of the style content of text processing also needs another separate and general method. (That method might call this method to get the color and then modify the content style of text)
So the naming of the method should be more accurate to distinguish the two methods with different processing degrees.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's move this discussion to #16349 which is solely for the highlighting functionality.
| styled_command = [] | ||
| argument_begins = False | ||
|
|
||
| for index, arg in enumerate(raw_command.split()): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While str.split works here, shlex.split is safer which doesn't splits spaces within quoted text, like "some quoted text".
| self.exit_code = cli_ctx.invoke(shlex.split(command), out_file=stdout_buf) or 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
str.split works fine here, except for the cases where we have space in the command name. shlex.split will remove the quotation marks in the command thus bring more complexity when we join the command args.
Let's use str.split here @zhoxing-ms for awareness
| spaced_arg = ' {}'.format(arg) if index > 0 else arg | ||
| style = Style.PRIMARY | ||
|
|
||
| if arg.startswith('-') and '=' not in arg: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic of = handling is a little bit tricky. It works for az config set a=b, but it doesn't work for az config unset output. It is the nature of positional argument that we can NOT decide whether an arg is
- part of a command, or
- a positional argument
until semantic analysis is involved.
Let's keep it simple and treat it as part of the command by now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agree. @zhoxing-ms for awareness.
| return ''.join(formatted_parts) | ||
|
|
||
|
|
||
| def get_styled_command(raw_command): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about we add some function doc and give some examples about the behavior?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, moved to #16349
| if 'unrecognized' in error_msg: | ||
| error_type = AladdinUserFaultType.UnrecognizedArguments | ||
| elif 'expected one argument' in error_msg or 'expected at least one argument' in error_msg \ | ||
| or 'value required' in error_msg: | ||
| error_type = AladdinUserFaultType.ExpectedArgument | ||
| elif 'misspelled' in error_msg: | ||
| error_type = AladdinUserFaultType.UnknownSubcommand | ||
| elif 'arguments are required' in error_msg or 'argument required' in error_msg: | ||
| error_type = AladdinUserFaultType.MissingRequiredParameters | ||
| if '_subcommand' in error_msg: | ||
| error_type = AladdinUserFaultType.MissingRequiredSubcommand | ||
| elif '_command_package' in error_msg: | ||
| error_type = AladdinUserFaultType.UnableToParseCommandInput | ||
| elif 'not found' in error_msg or 'could not be found' in error_msg \ | ||
| or 'resource not found' in error_msg: | ||
| error_type = AladdinUserFaultType.AzureResourceNotFound | ||
| if 'storage_account' in error_msg or 'storage account' in error_msg: | ||
| error_type = AladdinUserFaultType.StorageAccountNotFound | ||
| elif 'resource_group' in error_msg or 'resource group' in error_msg: | ||
| error_type = AladdinUserFaultType.ResourceGroupNotFound |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we make the mapping relationship between these error messages and error codes into a dict, and then match the error types directly through the dict?
So we don't need to write so many judgment statements, and the code may be more readable
Such as:
error_type_dict = {
'not found': AladdinUserFaultType.UnableToParseCommandInput,
'misspelled': AladdinUserFaultType.UnknownSubcommand,
'arguments are required': {
'_subcommand': AladdinUserFaultType.MissingRequiredSubcommand,
'_command_package': AladdinUserFaultType.UnableToParseCommandInput
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a good idea!
But i am afraid it may bring extra complexity. For example, the statements here need 15 key-value pairs?
elif 'not found' in error_msg or 'could not be found' in error_msg \
or 'resource not found' in error_msg:
error_type = AladdinUserFaultType.AzureResourceNotFound
if 'storage_account' in error_msg or 'storage account' in error_msg:
error_type = AladdinUserFaultType.StorageAccountNotFound
elif 'resource_group' in error_msg or 'resource group' in error_msg:
error_type = AladdinUserFaultType.ResourceGroupNotFoundBesides, can we deal with the default logic? E.g, the error_type = AladdinUserFaultType.AzureResourceNotFound above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For example, the statements here need 15 key-value pairs?
Yes, this makes it easier to find and modify the error type corresponding to an error message
error_type_dict = {
'not found': {
"default error": AladdinUserFaultType.UnableToParseCommandInput, // default error type
"storage_account": AladdinUserFaultType.StorageAccountNotFound,
"storage account": AladdinUserFaultType.StorageAccountNotFound,
"resource_group": AladdinUserFaultType.ResourceGroupNotFound,
"resource group": AladdinUserFaultType.ResourceGroupNotFound
},
...
}
In fact, the essential difference between the two ways is that one is aggregated according to the error message and the other is aggregated according to the error type returned.
I personally think that we are more concerned about how to get the error type according to error_msg rather than what error_msg corresponds to an error type.
| The sorting rules below are applied in order: | ||
| 1. Commands starting with the user's input command name are ahead of those don't | ||
| 2. Commands having more matched arguments are ahead of those having less | ||
| 3. Commands having less arguments are ahead of those having more |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does Aladdin recommendation service have its own ranking rules?
I personally think that the content of relevance ranking might be better implemented by the recommendation server side.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, we discussed with Aladdin team about this. We agreed that before the service could return better ordered recommendtions, we would control the orders in client side.
What's more, the rules will also be applied to example recommendations.
| param_mappings = { | ||
| '-h': '--help', | ||
| '-o': '--output', | ||
| '--only-show-errors': None, | ||
| '--help': None, | ||
| '--output': None, | ||
| '--query': None, | ||
| '--debug': None, | ||
| '--verbose': None | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What kind of parameters need to be put here?
How about --yes, --no-wait?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a good catch, let me confirm. I think we need to add all the global parameters here.
| if self.aladdin_recommendations: | ||
| telemetry.set_debug_info('AladdinRecommendCommand', ';'.join(raw_commands)) | ||
| else: | ||
| telemetry.set_debug_info('ExampleRecommendCommand', ';'.join(raw_commands)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will this contain user input value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, these are the raw recommendations.
| 3. In testing environments | ||
| :return: whether Aladdin service need to be disabled or not | ||
| :type: bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in 2,3, we could still provide recommendation from help right? Seems need to separate it in command_recommender
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They have already been separated, we could provide recommendations from help examples even though Aladdin service is disabled.







Description
This PR applies the new error output to support the feature requests #15693.
1. The UI changes of new error output includes
An overview of the new error output.
2. The none-UI changes
core.disable_error_recommendation=truewith az config to disable recommendationsTesting Guide
Type any command that will triger the client side userfault. E.g, the command below, you shold see the output above.
Disable the error recommendation and then test the same command, you could see the recommendations are disabled.