Skip to content

Commit d0e7fb0

Browse files
authored
Refine the logic to check whether a cmdlet is supported. (#21141)
1 parent bdb97bf commit d0e7fb0

File tree

6 files changed

+185
-31
lines changed

6 files changed

+185
-31
lines changed

tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/AzPredictorTelemetryTests.cs

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ public AzPredictorTelemetryTests(ModelFixture modelFixture)
5454
[InlineData(@"C:\Users\MyName\exe param")]
5555
[InlineData(@"C:\Users\MyName\exe")]
5656
[InlineData(@"az storage")]
57+
[InlineData(@"get")]
5758
public async Task VerifyOnCommandLineAcceptedForOneUnsupportedCommandHistory(string inputData)
5859
{
5960
var expectedTelemetryCount = 2;
@@ -551,7 +552,12 @@ public async Task VerifyOnCommandLineAcceptedException()
551552
[InlineData("Get-AzContext", "Get-AzContext")]
552553
[InlineData("Get-AzResourceGroup -Location *** -Name ***", "Get-AzResourceGroup -Location westus2 -Name rg1")]
553554
[InlineData("Get-AzResourceGroup -Location ***", "Get-AzResourceGroup rg1 -Location westus2")]
554-
[InlineData("Get-AzResourceGroup", "Get-AzResourceGroup rg1 westus2")]
555+
[InlineData("get-azresourcegroup", "get-azresourcegroup rg1 westus2")]
556+
[InlineData("Set-Az", "Set-Az")]
557+
[InlineData("Get-azR", "Get-azR")]
558+
[InlineData("start_of_snippet", "NonVerb-AzResource")]
559+
[InlineData("start_of_snippet", "Get")]
560+
[InlineData("start_of_snippet", "Set-")]
555561
public void VerifyUserInputInGetSuggestionEvent(string expectedUserInput, string input)
556562
{
557563
var expectedTelemetryCount = 1;
@@ -564,7 +570,7 @@ public void VerifyUserInputInGetSuggestionEvent(string expectedUserInput, string
564570
VerifyTelemetryDispatchCount(expectedTelemetryCount, telemetryClient);
565571

566572
Assert.EndsWith("Aggregation", telemetryClient.RecordedTelemetry[0].EventName);
567-
var suggestionSessions = JsonSerializer.Deserialize<IList<IDictionary<string, object>>>(telemetryClient.RecordedTelemetry[0].Properties["Suggestion"]);
573+
var suggestionSessions = JsonSerializer.Deserialize<IList<IDictionary<string, object>>>(telemetryClient.RecordedTelemetry[0].Properties[GetSuggestionTelemetryData.PropertyNamePrediction]);
568574
Assert.Equal(expectedUserInput, ((JsonElement)(suggestionSessions[0][GetSuggestionTelemetryData.PropertyNameUserInput])).GetString());
569575
}
570576

@@ -589,7 +595,7 @@ private void VerifySameSuggestionSessionId()
589595

590596
Assert.EndsWith("Aggregation", telemetryClient.RecordedTelemetry[0].EventName);
591597
Assert.Equal(MockObjects.PredictionClient.Name, telemetryClient.RecordedTelemetry[0].Properties["ClientId"]);
592-
var suggestionSessions = JsonSerializer.Deserialize<IList<IDictionary<string, object>>>(telemetryClient.RecordedTelemetry[0].Properties["Suggestion"]);
598+
var suggestionSessions = JsonSerializer.Deserialize<IList<IDictionary<string, object>>>(telemetryClient.RecordedTelemetry[0].Properties[GetSuggestionTelemetryData.PropertyNamePrediction]);
593599
Assert.Equal("Clear-Content -Filter *** -Path ***", ((JsonElement)(suggestionSessions[0][GetSuggestionTelemetryData.PropertyNameUserInput])).GetString());
594600
Assert.Equal(1, ((JsonElement)suggestionSessions[0][GetSuggestionTelemetryData.PropertyNameFound]).GetArrayLength());
595601

@@ -604,7 +610,7 @@ private void VerifySameSuggestionSessionId()
604610

605611
Assert.EndsWith("Aggregation", telemetryClient.RecordedTelemetry[0].EventName);
606612
Assert.Equal(MockObjects.PredictionClient.Name, telemetryClient.RecordedTelemetry[0].Properties["ClientId"]);
607-
suggestionSessions = JsonSerializer.Deserialize<IList<IDictionary<string, object>>>(telemetryClient.RecordedTelemetry[0].Properties["Suggestion"]);
613+
suggestionSessions = JsonSerializer.Deserialize<IList<IDictionary<string, object>>>(telemetryClient.RecordedTelemetry[0].Properties[GetSuggestionTelemetryData.PropertyNamePrediction]);
608614
Assert.Equal((int)SuggestionDisplayMode.ListView, ((JsonElement)suggestionSessions[0][SuggestionDisplayedTelemetryData.PropertyNameDisplayed])[0].GetInt32());
609615
Assert.Equal(Math.Abs(displayCountOrIndex), ((JsonElement)suggestionSessions[0][SuggestionDisplayedTelemetryData.PropertyNameDisplayed])[1].GetInt32());
610616
Assert.Equal(suggestionPackage.Session.Value, ((JsonElement)suggestionSessions[0][GetSuggestionTelemetryData.PropertyNameSuggestionSessionId]).GetUInt32());
@@ -622,7 +628,7 @@ private void VerifySameSuggestionSessionId()
622628

623629
Assert.EndsWith("Aggregation", telemetryClient.RecordedTelemetry[0].EventName);
624630
Assert.Equal(MockObjects.PredictionClient.Name, telemetryClient.RecordedTelemetry[0].Properties["ClientId"]);
625-
suggestionSessions = JsonSerializer.Deserialize<IList<IDictionary<string, object>>>(telemetryClient.RecordedTelemetry[0].Properties["Suggestion"]);
631+
suggestionSessions = JsonSerializer.Deserialize<IList<IDictionary<string, object>>>(telemetryClient.RecordedTelemetry[0].Properties[GetSuggestionTelemetryData.PropertyNamePrediction]);
626632
Assert.Equal(suggestionPackage.Session.Value, ((JsonElement)suggestionSessions[0][GetSuggestionTelemetryData.PropertyNameSuggestionSessionId]).GetUInt32());
627633
Assert.Equal(acceptedSuggestion, ((JsonElement)suggestionSessions[0][SuggestionAcceptedTelemetryData.PropertyNameAccepted]).GetString());
628634
AzPredictorTelemetryTests.EnsureSameRequestId(telemetryClient.GetSuggestionData, telemetryClient.SuggestionDisplayedData);
@@ -676,7 +682,7 @@ public void VerifyListViewInSuggestionDisplayed()
676682

677683
Assert.EndsWith("Aggregation", telemetryClient.RecordedTelemetry[0].EventName);
678684
Assert.Equal(MockObjects.PredictionClient.Name, telemetryClient.RecordedTelemetry[0].Properties["ClientId"]);
679-
var suggestionSessions = JsonSerializer.Deserialize<IList<IDictionary<string, object>>>(telemetryClient.RecordedTelemetry[0].Properties["Suggestion"]);
685+
var suggestionSessions = JsonSerializer.Deserialize<IList<IDictionary<string, object>>>(telemetryClient.RecordedTelemetry[0].Properties[GetSuggestionTelemetryData.PropertyNamePrediction]);
680686
Assert.Equal((int)SuggestionDisplayMode.ListView, ((JsonElement)suggestionSessions[0][SuggestionDisplayedTelemetryData.PropertyNameDisplayed])[0].GetInt32());
681687
Assert.Equal(Math.Abs(suggestionCountOrIndex), ((JsonElement)suggestionSessions[0][SuggestionDisplayedTelemetryData.PropertyNameDisplayed])[1].GetInt32());
682688
Assert.Equal(suggestionSessionId, ((JsonElement)suggestionSessions[0][GetSuggestionTelemetryData.PropertyNameSuggestionSessionId]).GetUInt32());
@@ -705,7 +711,7 @@ public void VerifyInlineViewInSuggestionDisplayedAtEdge()
705711
Assert.EndsWith("Aggregation", telemetryClient.RecordedTelemetry[0].EventName);
706712
Assert.Equal(MockObjects.PredictionClient.Name, telemetryClient.RecordedTelemetry[0].Properties["ClientId"]);
707713

708-
var suggestionSessions = JsonSerializer.Deserialize<IList<IDictionary<string, object>>>(telemetryClient.RecordedTelemetry[0].Properties["Suggestion"]);
714+
var suggestionSessions = JsonSerializer.Deserialize<IList<IDictionary<string, object>>>(telemetryClient.RecordedTelemetry[0].Properties[GetSuggestionTelemetryData.PropertyNamePrediction]);
709715
Assert.Equal((int)SuggestionDisplayMode.InlineView, ((JsonElement)suggestionSessions[0][SuggestionDisplayedTelemetryData.PropertyNameDisplayed])[0].GetInt32());
710716
Assert.Equal(Math.Abs(suggestionCountOrIndex), ((JsonElement)suggestionSessions[0][SuggestionDisplayedTelemetryData.PropertyNameDisplayed])[1].GetInt32());
711717
Assert.Equal(suggestionSessionId, ((JsonElement)suggestionSessions[0][GetSuggestionTelemetryData.PropertyNameSuggestionSessionId]).GetUInt32());
@@ -733,7 +739,7 @@ public void VerifyInlineViewInSuggestionDisplayed()
733739

734740
Assert.EndsWith("Aggregation", telemetryClient.RecordedTelemetry[0].EventName);
735741
Assert.Equal(MockObjects.PredictionClient.Name, telemetryClient.RecordedTelemetry[0].Properties["ClientId"]);
736-
var suggestionSessions = JsonSerializer.Deserialize<IList<IDictionary<string, object>>>(telemetryClient.RecordedTelemetry[0].Properties["Suggestion"]);
742+
var suggestionSessions = JsonSerializer.Deserialize<IList<IDictionary<string, object>>>(telemetryClient.RecordedTelemetry[0].Properties[GetSuggestionTelemetryData.PropertyNamePrediction]);
737743
Assert.Equal((int)SuggestionDisplayMode.InlineView, ((JsonElement)suggestionSessions[0][SuggestionDisplayedTelemetryData.PropertyNameDisplayed])[0].GetInt32());
738744
Assert.Equal(Math.Abs(suggestionCountOrIndex), ((JsonElement)suggestionSessions[0][SuggestionDisplayedTelemetryData.PropertyNameDisplayed])[1].GetInt32());
739745
}
@@ -928,11 +934,11 @@ public void VerifyAggregationDataSplitAtGetSuggestion()
928934
telemetryClient.FlushTelemetry();
929935

930936
var recordedTelemetry = telemetryClient.RecordedTelemetry[0];
931-
var suggestionSessions = JsonSerializer.Deserialize<IList<IDictionary<string, object>>>(recordedTelemetry.Properties["Suggestion"]);
937+
var suggestionSessions = JsonSerializer.Deserialize<IList<IDictionary<string, object>>>(recordedTelemetry.Properties[GetSuggestionTelemetryData.PropertyNamePrediction]);
932938
Assert.Equal(expectedSuggestionSessionInFirstBatch, suggestionSessions.Count());
933939

934940
recordedTelemetry = telemetryClient.RecordedTelemetry[1];
935-
suggestionSessions = JsonSerializer.Deserialize<IList<IDictionary<string, object>>>(recordedTelemetry.Properties["Suggestion"]);
941+
suggestionSessions = JsonSerializer.Deserialize<IList<IDictionary<string, object>>>(recordedTelemetry.Properties[GetSuggestionTelemetryData.PropertyNamePrediction]);
936942
Assert.Equal(expectedSuggestionSessionInSecondBatch, suggestionSessions.Count());
937943
Assert.True(suggestionSessions[0].ContainsKey(GetSuggestionTelemetryData.PropertyNameFound));
938944
}
@@ -980,7 +986,7 @@ public void VerifyAggregationDataSplitAtAcceptSuggestion()
980986
telemetryClient.FlushTelemetry();
981987

982988
var recordedTelemetry = telemetryClient.RecordedTelemetry[0];
983-
var suggestionSessions = JsonSerializer.Deserialize<IList<IDictionary<string, object>>>(recordedTelemetry.Properties["Suggestion"]);
989+
var suggestionSessions = JsonSerializer.Deserialize<IList<IDictionary<string, object>>>(recordedTelemetry.Properties[GetSuggestionTelemetryData.PropertyNamePrediction]);
984990
Assert.Equal(expectedSuggestionSessionInFirstBatch, suggestionSessions.Count());
985991
Assert.True(suggestionSessions.All((s) => s.ContainsKey(GetSuggestionTelemetryData.PropertyNameFound) && s.ContainsKey(GetSuggestionTelemetryData.PropertyNameUserInput)));
986992
Assert.True(suggestionSessions.All((s) => !s.ContainsKey(SuggestionAcceptedTelemetryData.PropertyNameAccepted)));
@@ -990,7 +996,7 @@ public void VerifyAggregationDataSplitAtAcceptSuggestion()
990996
Assert.Equal(1, ((JsonElement)suggestionSessions.Last()[SuggestionDisplayedTelemetryData.PropertyNameDisplayed])[1].GetInt32());
991997

992998
recordedTelemetry = telemetryClient.RecordedTelemetry[1];
993-
suggestionSessions = JsonSerializer.Deserialize<IList<IDictionary<string, object>>>(recordedTelemetry.Properties["Suggestion"]);
999+
suggestionSessions = JsonSerializer.Deserialize<IList<IDictionary<string, object>>>(recordedTelemetry.Properties[GetSuggestionTelemetryData.PropertyNamePrediction]);
9941000
Assert.Equal(expectedSuggestionSessionInSecondBatch, suggestionSessions.Count());
9951001
Assert.False(suggestionSessions[0].ContainsKey(GetSuggestionTelemetryData.PropertyNameFound));
9961002
Assert.False(suggestionSessions[0].ContainsKey(GetSuggestionTelemetryData.PropertyNameUserInput));
@@ -1042,7 +1048,7 @@ public void VerifyAggregationDataSplitAtCommandHistory()
10421048
VerifyTelemetryDispatchCount(expectedTelemetryCount, telemetryClient);
10431049

10441050
var recordedTelemetry = telemetryClient.RecordedTelemetry[0];
1045-
var suggestionSessions = JsonSerializer.Deserialize<IList<IDictionary<string, object>>>(recordedTelemetry.Properties["Suggestion"]);
1051+
var suggestionSessions = JsonSerializer.Deserialize<IList<IDictionary<string, object>>>(recordedTelemetry.Properties[GetSuggestionTelemetryData.PropertyNamePrediction]);
10461052
Assert.Equal(expectedSuggestionSessionInFirstBatch, suggestionSessions.Count());
10471053
Assert.True(suggestionSessions.All((s) => s.ContainsKey(GetSuggestionTelemetryData.PropertyNameFound) && s.ContainsKey(GetSuggestionTelemetryData.PropertyNameUserInput)));
10481054
Assert.True(suggestionSessions.All((s) => !s.ContainsKey(SuggestionAcceptedTelemetryData.PropertyNameAccepted)));
@@ -1052,7 +1058,7 @@ public void VerifyAggregationDataSplitAtCommandHistory()
10521058
Assert.Equal(1, ((JsonElement)suggestionSessions.Last()[SuggestionDisplayedTelemetryData.PropertyNameDisplayed])[1].GetInt32());
10531059

10541060
recordedTelemetry = telemetryClient.RecordedTelemetry[1];
1055-
suggestionSessions = JsonSerializer.Deserialize<IList<IDictionary<string, object>>>(recordedTelemetry.Properties["Suggestion"]);
1061+
suggestionSessions = JsonSerializer.Deserialize<IList<IDictionary<string, object>>>(recordedTelemetry.Properties[GetSuggestionTelemetryData.PropertyNamePrediction]);
10561062
Assert.Equal(expectedSuggestionSessionInSecondBatch, suggestionSessions.Count());
10571063
Assert.False(suggestionSessions[0].ContainsKey(GetSuggestionTelemetryData.PropertyNameFound));
10581064
Assert.False(suggestionSessions[0].ContainsKey(GetSuggestionTelemetryData.PropertyNameUserInput));

tools/Az.Tools.Predictor/Az.Tools.Predictor.Test/Mocks/MockAzPredictorService.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,6 @@ public MockAzPredictorService(string history, IList<PredictiveCommand> suggestio
7272
}
7373
}
7474

75-
// <inheritdoc/>
76-
// public override bool IsSupportedCommand(string cmd) => IsRecognizedCommand(cmd) || cmd.IndexOf(AzPredictorConstants.AzCommandMoniker, StringComparison.OrdinalIgnoreCase) > 0;
77-
7875
/// <inheritdoc/>
7976
public override Task<bool?> RequestPredictionsAsync(IEnumerable<string> commands, string requestId, CancellationToken cancellationToken)
8077
{

tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictor.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,10 +318,16 @@ public SuggestionPackage GetSuggestion(PredictionClient client, PredictionContex
318318
}
319319
finally
320320
{
321+
// The user input may be partial command line. So we mark it as supported when
322+
// 1. We find some suggestions (e.g. if the input is "get" and we find "Get-AzVM")
323+
// 2. or command name from the input contains "-Az", which indicates that the command is Az command and we don't have it in our suggestion list.
324+
var isSupported = (suggestions is not null)
325+
&& (suggestions.PredictiveSuggestions.Any()
326+
|| _service.IsSupportedCommand(suggestions.CommandAst?.GetCommandName()));
321327
_telemetryClient.OnGetSuggestion(new GetSuggestionTelemetryData(client,
322328
localSuggestionSessionId,
323329
suggestions?.CommandAst,
324-
_service.IsSupportedCommand(suggestions?.CommandAst?.GetCommandName()),
330+
isSupported,
325331
suggestions,
326332
cancellationToken.IsCancellationRequested,
327333
exception));

tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorConstants.cs

Lines changed: 125 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,27 @@
1212
// limitations under the License.
1313
// ----------------------------------------------------------------------------------
1414

15+
using System;
16+
using System.Collections;
17+
using System.Collections.Generic;
18+
using System.Collections.Immutable;
19+
1520
namespace Microsoft.Azure.PowerShell.Tools.AzPredictor
1621
{
1722
/// <summary>
1823
/// The constants shared in the project.
1924
/// </summary>
2025
internal static class AzPredictorConstants
2126
{
27+
/// <summary>
28+
/// The substring that an Az cmdlet has.
29+
/// </summary>
30+
public const string AzCommandMoniker = "Az";
31+
2232
/// <summary>
2333
/// The value to check to determine if it's an Az command.
2434
/// </summary>
25-
public const string AzCommandMoniker = "-Az";
35+
public const string AzComandSeparator = $"{PowerShellCommandSeparator}{AzCommandMoniker}";
2636

2737
/// <summary>
2838
/// The value of number of cohort groups.
@@ -57,7 +67,7 @@ internal static class AzPredictorConstants
5767
/// <summary>
5868
/// The character that separates verb and noun in the cmdlet.
5969
/// </summary>
60-
public const string CommandSeparator = "-";
70+
public const string PowerShellCommandSeparator = "-";
6171

6272
/// <summary>
6373
/// The special parameter name for "-" which is not a parameter name but an indication of a parameter.
@@ -103,6 +113,119 @@ internal static class AzPredictorConstants
103113
/// </summary>
104114
// See AzureDirectoryName in https://github.com/Azure/azure-powershell/blob/master/src/Accounts/Authentication/Properties/Resources.resx
105115
public const string AzureProfileDirectoryName = ".Azure";
116+
117+
/// <summary>
118+
/// The list of the PowerShell verbs that can be used in Az cmdlets.
119+
/// See https://learn.microsoft.com/en-us/powershell/scripting/developer/cmdlet/approved-verbs-for-windows-powershell-commands
120+
/// </summary>
121+
public static IReadOnlySet<string> ApprovedPowerShellVerbs = new HashSet<string>(new string[] {
122+
// Common Verbs
123+
"Add",
124+
"Clear",
125+
"Close",
126+
"Copy",
127+
"Enter",
128+
"Exit",
129+
"Find",
130+
"Format",
131+
"Get",
132+
"Hide",
133+
"Join",
134+
"Lock",
135+
"Move",
136+
"New",
137+
"Open",
138+
"Optimize",
139+
"Pop",
140+
"Push",
141+
"Redo",
142+
"Remove",
143+
"Rename",
144+
"Reset",
145+
"Resize",
146+
"Search",
147+
"Select",
148+
"Set",
149+
"Show",
150+
"Skip",
151+
"Split",
152+
"Step",
153+
"Switch",
154+
"Undo",
155+
"Unlock",
156+
"Watch",
157+
// Communication Verbs
158+
"Connect",
159+
"Disconnect",
160+
"Read",
161+
"Receive",
162+
"Send",
163+
"Write",
164+
// Data Verbs
165+
"Backup",
166+
"Checkpoint",
167+
"Compare",
168+
"Convert",
169+
"ConvertFrom",
170+
"ConvertTo",
171+
"Dismount",
172+
"Edit",
173+
"Expand",
174+
"Export",
175+
"Group",
176+
"Import",
177+
"Initialize",
178+
"Limit",
179+
"Merge",
180+
"Mount",
181+
"Out",
182+
"Publish",
183+
"Restore",
184+
"Save",
185+
"Sync",
186+
"Unpublish",
187+
"Update",
188+
// Diagnostic Verbs
189+
"Debug",
190+
"Measure",
191+
"Ping",
192+
"Repair",
193+
"Resolve",
194+
"Test",
195+
"Trace",
196+
// Lifecycle Verbs
197+
"Approve",
198+
"Assert",
199+
"Build",
200+
"Complete",
201+
"Confirm",
202+
"Deny",
203+
"Deploy",
204+
"Disable",
205+
"Enable",
206+
"Install",
207+
"Invoke",
208+
"Register",
209+
"Request",
210+
"Restart",
211+
"Resume",
212+
"Start",
213+
"Stop",
214+
"Submit",
215+
"Suspend",
216+
"Uninstall",
217+
"Unregister",
218+
"Wait",
219+
// Security Verbs
220+
"Block",
221+
"Grant",
222+
"Protect",
223+
"Revoke",
224+
"Unblock",
225+
"Unprotect",
226+
// Other Verbs
227+
"Use"
228+
}, StringComparer.OrdinalIgnoreCase);
106229
}
107230
}
108231

0 commit comments

Comments
 (0)