Skip to content

Commit 5acf8ec

Browse files
jjaguirre394Juan Aguirre
andauthored
Prediction/Commands Service API v2 updates (#13767)
* Initial updates for updated v2 service endpoints * Updating tests to work with the new model updates * Fixing service uri, cleaning up using statements * PR feedback * Additional code cleanup: - Removed unnecessary usings - Added copyright - Misc. code clean up Co-authored-by: Juan Aguirre <[email protected]>
1 parent 0a8c538 commit 5acf8ec

17 files changed

+206
-41
lines changed

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,6 @@ public void VerifyParameterValues()
107107
[InlineData("Get-AzKeyVault -VaultName")]
108108
[InlineData("GET-AZSTORAGEACCOUNTKEY -NAME ")]
109109
[InlineData("new-azresourcegroup -name hello")]
110-
[InlineData("Get-AzContext -Name")]
111-
[InlineData("Get-AzContext -ErrorAction")]
112110
public void VerifyUsingCommandBasedPredictor(string userInput)
113111
{
114112
var predictionContext = PredictionContext.Create(userInput);
@@ -150,8 +148,8 @@ public void VerifyUsingCommandBasedPredictor(string userInput)
150148
/// Verifies that when no prediction is in the command based list, we'll use the fallback list.
151149
/// </summary>
152150
[Theory]
153-
[InlineData("Get-AzResource -Name hello -Pre")]
154-
[InlineData("Get-AzADServicePrincipal -ApplicationObject")]
151+
[InlineData("New-AzApiManagementContext -ResourceGroupName hello -Serv")]
152+
[InlineData("Get-AzAlert -TimeRange '1h' -Incl")]
155153
public void VerifyUsingFallbackPredictor(string userInput)
156154
{
157155
var predictionContext = PredictionContext.Create(userInput);

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,6 @@ public void VerifySupportedCommandMasked()
137137
[Theory]
138138
[InlineData("new-azresourcegroup -name hello")]
139139
[InlineData("Get-AzContext -Name")]
140-
[InlineData("Get-AzContext -ErrorAction")]
141-
[InlineData("Get-AzADServicePrincipal -ApplicationObject")]
142140
public void VerifySuggestion(string userInput)
143141
{
144142
var predictionContext = PredictionContext.Create(userInput);
@@ -164,7 +162,7 @@ public void VerifySuggestionOnIncompleteCommand()
164162
null);
165163

166164
var userInput = "New-AzResourceGroup -Name 'ResourceGroup01' -Location 'Central US' -WhatIf -";
167-
var expected = "New-AzResourceGroup -Name 'ResourceGroup01' -Location 'Central US' -WhatIf -Verbose ***";
165+
var expected = "New-AzResourceGroup -Name 'ResourceGroup01' -Location 'Central US' -WhatIf -Tag value1";
168166

169167
var predictionContext = PredictionContext.Create(userInput);
170168
var actual = localAzPredictor.GetSuggestion(predictionContext, CancellationToken.None);

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,6 @@ public void GetPredictionWithCommandName(string userInput)
169169
[InlineData("Get-AzKeyVault -VaultName")]
170170
[InlineData("GET-AZSTORAGEACCOUNTKEY -NAME ")]
171171
[InlineData("new-azresourcegroup -name hello")]
172-
[InlineData("Get-AzContext -Name")]
173172
public void GetPredictionWithCommandNameParameters(string userInput)
174173
{
175174
var predictionContext = PredictionContext.Create(userInput);
@@ -237,7 +236,7 @@ public void VerifyPredictionForCommand()
237236
1,
238237
CancellationToken.None);
239238

240-
Assert.Equal("Connect-AzAccount -Credential <PSCredential> -ServicePrincipal -Tenant <>", result.PredictiveSuggestions.First().SuggestionText);
239+
Assert.Equal("Connect-AzAccount -Identity", result.PredictiveSuggestions.First().SuggestionText);
241240
}
242241

243242
/// <summary>
@@ -260,7 +259,7 @@ public void VerifyPredictionForCommandAndParameters()
260259
1,
261260
CancellationToken.None);
262261

263-
Assert.Equal("Get-AzStorageAccountKey -Name 'ContosoStorage' -ResourceGroupName 'ContosoGroup02'", result.PredictiveSuggestions.First().SuggestionText);
262+
Assert.Equal("Get-AzStorageAccountKey -Name 'myStorageAccount' -ResourceGroupName 'ContosoGroup02'", result.PredictiveSuggestions.First().SuggestionText);
264263
}
265264
}
266265
}
Binary file not shown.
Binary file not shown.
Binary file not shown.

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
// limitations under the License.
1313
// ----------------------------------------------------------------------------------
1414

15+
using System;
1516
using System.Collections.Generic;
1617

1718
namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Test.Mocks
@@ -32,7 +33,7 @@ sealed class MockAzPredictorService : AzPredictorService
3233
/// <param name="history">The history that the suggestion is for</param>
3334
/// <param name="suggestions">The suggestions collection</param>
3435
/// <param name="commands">The commands collection</param>
35-
public MockAzPredictorService(string history, IList<string> suggestions, IList<string> commands)
36+
public MockAzPredictorService(string history, IList<PredictiveCommand> suggestions, IList<PredictiveCommand> commands)
3637
{
3738
if (history != null)
3839
{
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// ----------------------------------------------------------------------------------
2+
//
3+
// Copyright Microsoft Corporation
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
// ----------------------------------------------------------------------------------
14+
15+
using Newtonsoft.Json;
16+
using Newtonsoft.Json.Serialization;
17+
18+
namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Test
19+
{
20+
/// <summary>
21+
/// Represents a command entry in the model files.
22+
/// </summary>
23+
[JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
24+
public class ModelEntry
25+
{
26+
/// <summary>
27+
/// The command in the model.
28+
/// </summary>
29+
[JsonProperty("suggestion", Required = Required.Always)]
30+
public string Command { get; set; }
31+
32+
/// <summary>
33+
/// The description of the command in the model.
34+
/// </summary>
35+
[JsonProperty(Required = Required.Always)]
36+
public string Description { get; set; }
37+
38+
/// <summary>
39+
/// The prediction count in the model.
40+
/// </summary>
41+
[JsonProperty("suggestion count", Required = Required.Always)]
42+
public int PredictionCount { get; set; }
43+
44+
/// <summary>
45+
/// The history count in the model.
46+
/// </summary>
47+
[JsonProperty("history count", Required = Required.Always)]
48+
public int HistoryCount { get; set; }
49+
50+
/// <summary>
51+
/// Transforms the model entry into the client PredictiveCommand object.
52+
/// </summary>
53+
/// <returns>The PredictiveCommand object used on the client.</returns>
54+
public PredictiveCommand TransformEntry()
55+
{
56+
return new PredictiveCommand()
57+
{
58+
Command = this.Command,
59+
Description = this.Description
60+
};
61+
}
62+
}
63+
}

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

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

15-
using Microsoft.Azure.PowerShell.Tools.AzPredictor.Utilities;
15+
using Newtonsoft.Json;
1616
using System;
1717
using System.Collections.Generic;
1818
using System.IO;
1919
using System.IO.Compression;
20-
using System.Text.Json;
20+
using System.Linq;
2121
using Xunit;
2222

2323
namespace Microsoft.Azure.PowerShell.Tools.AzPredictor.Test
@@ -32,16 +32,18 @@ public sealed class ModelFixture : IDisposable
3232
private const string PredictionsModelZip = "PredictionsModel.zip";
3333
private const string PredictionsModelJson = "PredictionsModel.json";
3434
private const string DataDirectoryName = "Data";
35+
private static readonly Version CommandsVersionToUse = new Version("5.1.0");
36+
private static readonly Version PredictionsVersionToUse = new Version("5.1.0");
3537

3638
/// <summary>
3739
/// Gets a list of string for the commands.
3840
/// </summary>
39-
public IList<string> CommandCollection { get; private set; }
41+
public IList<PredictiveCommand> CommandCollection { get; private set; }
4042

4143
/// <summary>
4244
/// Gets a dictionary for the predictions.
4345
/// </summary>
44-
public IDictionary<string, IList<string>> PredictionCollection { get; private set; }
46+
public IDictionary<string, IList<PredictiveCommand>> PredictionCollection { get; private set; }
4547

4648
/// <summary>
4749
/// Constructs a new instance of <see cref="ModelFixture" />
@@ -52,11 +54,24 @@ public ModelFixture()
5254
var fileInfo = new FileInfo(currentLocation);
5355
var directory = fileInfo.DirectoryName;
5456
var dataDirectory = Path.Join(directory, ModelFixture.DataDirectoryName);
55-
var commandsModel = ModelFixture.ReadZipEntry(Path.Join(dataDirectory, ModelFixture.CommandsModelZip), ModelFixture.CommandsModelJson);
56-
var predictionsModel = ModelFixture.ReadZipEntry(Path.Join(dataDirectory, ModelFixture.PredictionsModelZip), ModelFixture.PredictionsModelJson);
57+
var commandsModelVersions= JsonConvert.DeserializeObject<IDictionary<Version, IList<ModelEntry>>>(ModelFixture.ReadZipEntry(Path.Join(dataDirectory, ModelFixture.CommandsModelZip), ModelFixture.CommandsModelJson));
58+
var predictionsModelVersions = JsonConvert.DeserializeObject<IDictionary<Version, Dictionary<string, IList<ModelEntry>>>>(ModelFixture.ReadZipEntry(Path.Join(dataDirectory, ModelFixture.PredictionsModelZip), ModelFixture.PredictionsModelJson));
5759

58-
this.CommandCollection = JsonSerializer.Deserialize<IList<string>>(commandsModel, JsonUtilities.DefaultSerializerOptions);
59-
this.PredictionCollection = JsonSerializer.Deserialize<IDictionary<string, IList<string>>>(predictionsModel, JsonUtilities.DefaultSerializerOptions);
60+
var commandsModel = commandsModelVersions[CommandsVersionToUse];
61+
var predictionsModel = predictionsModelVersions[PredictionsVersionToUse];
62+
63+
this.CommandCollection = commandsModel.Select(x => x.TransformEntry()).ToList();
64+
var predictiveCollection = new Dictionary<string, IList<PredictiveCommand>>();
65+
foreach (var command in predictionsModel)
66+
{
67+
var predictiveCommandEntries = new List<PredictiveCommand>();
68+
foreach (var modelEntry in command.Value)
69+
{
70+
predictiveCommandEntries.Add(modelEntry.TransformEntry());
71+
}
72+
predictiveCollection.Add(command.Key, predictiveCommandEntries);
73+
}
74+
this.PredictionCollection = predictiveCollection;
6075
}
6176

6277
/// <inheritdoc/>

tools/Az.Tools.Predictor/Az.Tools.Predictor/AzContext.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ internal sealed class AzContext : IAzContext
3232
{
3333
private static readonly Version DefaultVersion = new Version("0.0.0.0");
3434

35+
/// <inheritdoc/>
36+
public Version AzVersion { get; private set; } = DefaultVersion;
37+
3538
/// <inheritdoc/>
3639
public string UserId { get; private set; } = string.Empty;
3740

@@ -100,6 +103,7 @@ public Version ModuleVersion
100103
/// <inheritdoc/>
101104
public void UpdateContext()
102105
{
106+
AzVersion = GetAzVersion();
103107
UserId = GenerateSha256HashString(GetUserAccountId());
104108
}
105109

@@ -120,6 +124,34 @@ private string GetUserAccountId()
120124
return string.Empty;
121125
}
122126

127+
/// <summary>
128+
/// Gets the latest version from the loaded Az modules.
129+
/// </summary>
130+
private Version GetAzVersion()
131+
{
132+
Version latestAz = DefaultVersion;
133+
134+
try
135+
{
136+
var outputs = AzContext.ExecuteScript<PSObject>("Get-Module -Name Az -ListAvailable");
137+
foreach (PSObject obj in outputs)
138+
{
139+
string psVersion = obj.Properties["Version"].Value.ToString();
140+
int pos = psVersion.IndexOf('-');
141+
Version currentAz = (pos == -1) ? new Version(psVersion) : new Version(psVersion.Substring(0, pos));
142+
if (currentAz > latestAz)
143+
{
144+
latestAz = currentAz;
145+
}
146+
}
147+
}
148+
catch (Exception)
149+
{
150+
}
151+
152+
return latestAz;
153+
}
154+
123155
/// <summary>
124156
/// Executes the PowerShell cmdlet in the current powershell session.
125157
/// </summary>

tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictorService.cs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@ public sealed class RequestContext
4444
public Version VersionNumber{ get; set; } = new Version(0, 0);
4545
}
4646

47-
public string History { get; set; }
47+
public IEnumerable<string> History { get; set; }
4848
public string ClientType { get; set; } = AzPredictorService.ClientType;
4949
public RequestContext Context { get; set; } = new RequestContext();
5050

51-
public PredictionRequestBody(string command) => History = command;
51+
public PredictionRequestBody(IEnumerable<string> commands) => History = commands;
5252
};
5353

5454
private sealed class CommandRequestContext
@@ -98,7 +98,7 @@ public AzPredictorService(string serviceUri, ITelemetryClient telemetryClient, I
9898
Validation.CheckArgument(telemetryClient, $"{nameof(telemetryClient)} cannot be null.");
9999
Validation.CheckArgument(azContext, $"{nameof(azContext)} cannot be null.");
100100

101-
_commandsEndpoint = $"{serviceUri}{AzPredictorConstants.CommandsEndpoint}?clientType={AzPredictorService.ClientType}&context={JsonSerializer.Serialize(new CommandRequestContext(), JsonUtilities.DefaultSerializerOptions)}";
101+
_commandsEndpoint = $"{serviceUri}{AzPredictorConstants.CommandsEndpoint}?clientType={AzPredictorService.ClientType}&context.versionNumber={azContext.AzVersion}";
102102
_predictionsEndpoint = serviceUri + AzPredictorConstants.PredictionsEndpoint;
103103
_telemetryClient = telemetryClient;
104104
_azContext = azContext;
@@ -264,9 +264,10 @@ public virtual void RequestPredictions(IEnumerable<string> commands)
264264
{
265265
SessionId = _telemetryClient.SessionId,
266266
CorrelationId = _telemetryClient.CorrelationId,
267+
VersionNumber = this._azContext.AzVersion
267268
};
268269

269-
var requestBody = new PredictionRequestBody(localCommands)
270+
var requestBody = new PredictionRequestBody(commands)
270271
{
271272
Context = requestContext,
272273
};
@@ -277,7 +278,7 @@ public virtual void RequestPredictions(IEnumerable<string> commands)
277278

278279
httpResponseMessage.EnsureSuccessStatusCode();
279280
var reply = await httpResponseMessage.Content.ReadAsStreamAsync(cancellationToken);
280-
var suggestionsList = await JsonSerializer.DeserializeAsync<IList<string>>(reply, JsonUtilities.DefaultSerializerOptions);
281+
var suggestionsList = await JsonSerializer.DeserializeAsync<IList<PredictiveCommand>>(reply, JsonUtilities.DefaultSerializerOptions);
281282

282283
SetCommandBasedPreditor(localCommands, suggestionsList);
283284
}
@@ -336,7 +337,7 @@ protected virtual void RequestAllPredictiveCommands()
336337

337338
httpResponseMessage.EnsureSuccessStatusCode();
338339
var reply = await httpResponseMessage.Content.ReadAsStringAsync();
339-
var commandsReply = JsonSerializer.Deserialize<IList<string>>(reply, JsonUtilities.DefaultSerializerOptions);
340+
var commandsReply = JsonSerializer.Deserialize<IList<PredictiveCommand>>(reply, JsonUtilities.DefaultSerializerOptions);
340341
SetFallbackPredictor(commandsReply);
341342
}
342343
catch (Exception e)
@@ -359,20 +360,20 @@ protected virtual void RequestAllPredictiveCommands()
359360
/// Sets the fallback predictor.
360361
/// </summary>
361362
/// <param name="commands">The command collection to set the predictor</param>
362-
protected void SetFallbackPredictor(IList<string> commands)
363+
protected void SetFallbackPredictor(IList<PredictiveCommand> commands)
363364
{
364365
Validation.CheckArgument(commands, $"{nameof(commands)} cannot be null.");
365366

366367
_fallbackPredictor = new CommandLinePredictor(commands, _parameterValuePredictor);
367-
_allPredictiveCommands = commands.Select(x => AzPredictorService.GetCommandName(x)).ToHashSet<string>(StringComparer.OrdinalIgnoreCase); // this could be slow
368+
_allPredictiveCommands = commands.Select(x => AzPredictorService.GetCommandName(x.Command)).ToHashSet<string>(StringComparer.OrdinalIgnoreCase); // this could be slow
368369
}
369370

370371
/// <summary>
371372
/// Sets the predictor based on the command history.
372373
/// </summary>
373374
/// <param name="commands">The commands that the suggestions are for</param>
374375
/// <param name="suggestions">The suggestion collection to set the predictor</param>
375-
protected void SetCommandBasedPreditor(string commands, IList<string> suggestions)
376+
protected void SetCommandBasedPreditor(string commands, IList<PredictiveCommand> suggestions)
376377
{
377378
Validation.CheckArgument(!string.IsNullOrWhiteSpace(commands), $"{nameof(commands)} cannot be null or whitespace.");
378379
Validation.CheckArgument(suggestions, $"{nameof(suggestions)} cannot be null.");
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"maxAllowedCommandDuplicate": 1,
3-
"serviceUri": "https://app.aladdin.microsoft.com/api/v1",
3+
"serviceUri": "https://app.aladdin.microsoft.com/api/v2.0",
44
"suggestionCount": 7
55
}

tools/Az.Tools.Predictor/Az.Tools.Predictor/CommandLine.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ sealed class CommandLine
2727
/// </summary>
2828
public string Name { get; }
2929

30+
/// <summary>
31+
/// Gets the description text for the command.
32+
/// </summary>
33+
public string Description { get; }
34+
3035
/// <summary>
3136
/// Gets the <see cref="ParameterSet "/>.
3237
/// </summary>
@@ -36,12 +41,14 @@ sealed class CommandLine
3641
/// Create a new instance of <see cref="CommandLine"/> with the command name and parameter set.
3742
/// </summary>
3843
/// <param name="name">The command name.</param>
44+
/// <param name="description">The command's description</param>
3945
/// <param name="parameterSet">The parameter set.</param>
40-
public CommandLine(string name, ParameterSet parameterSet)
46+
public CommandLine(string name, string description, ParameterSet parameterSet)
4147
{
4248
Validation.CheckArgument(!string.IsNullOrWhiteSpace(name), $"{nameof(name)} must not be null or whitespace.");
4349

4450
Name = name;
51+
Description = description;
4552
ParameterSet = parameterSet;
4653
}
4754
}

0 commit comments

Comments
 (0)