Skip to content

Commit 34f75ac

Browse files
committed
Implement Send-Feedback cmdlet.
1 parent d34458c commit 34f75ac

File tree

12 files changed

+346
-2
lines changed

12 files changed

+346
-2
lines changed

src/Common/Commands.Common/AzurePSCmdlet.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ protected bool CheckIfInteractive()
224224
}
225225
}
226226

227-
if (!interactive && !_dataCollectionProfile.EnableAzureDataCollection.HasValue)
227+
if (!interactive && _dataCollectionProfile != null && !_dataCollectionProfile.EnableAzureDataCollection.HasValue)
228228
{
229229
_dataCollectionProfile.EnableAzureDataCollection = false;
230230
}

src/Common/Commands.Common/MetricHelper.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
using Microsoft.ApplicationInsights.DataContracts;
1717
using Microsoft.ApplicationInsights.Extensibility;
1818
using Microsoft.WindowsAzure.Commands.Utilities.Common;
19+
using Newtonsoft.Json;
20+
using Newtonsoft.Json.Serialization;
1921
using System;
2022
using System.Collections.Generic;
2123
using System.Diagnostics;
@@ -104,6 +106,19 @@ public void LogQoSEvent(AzurePSQoSEvent qos, bool isUsageMetricEnabled, bool isE
104106
}
105107
}
106108

109+
public void LogCustomEvent<T>(string eventName, T payload, bool force = false)
110+
{
111+
if (!force && !IsMetricTermAccepted())
112+
{
113+
return;
114+
}
115+
116+
foreach (TelemetryClient client in TelemetryClients)
117+
{
118+
client.TrackEvent(eventName, SerializeCustomEventPayload(payload));
119+
}
120+
}
121+
107122
private void LogUsageEvent(AzurePSQoSEvent qos)
108123
{
109124
foreach (TelemetryClient client in TelemetryClients)
@@ -218,6 +233,21 @@ public static string GenerateSha256HashString(string originInput)
218233
return BitConverter.ToString(bytes);
219234
}
220235
}
236+
237+
/// <summary>
238+
/// Generate a serialized payload for custom events.
239+
/// </summary>
240+
/// <param name="payload">The payload object for the custom event.</param>
241+
/// <returns>The serialized payload.</returns>
242+
public static Dictionary<string, string> SerializeCustomEventPayload<T>(T payload)
243+
{
244+
var payloadAsJson = JsonConvert.SerializeObject(payload, new JsonSerializerSettings
245+
{
246+
ContractResolver = new CamelCasePropertyNamesContractResolver()
247+
});
248+
249+
return JsonConvert.DeserializeObject<Dictionary<string, string>>(payloadAsJson);
250+
}
221251
}
222252
}
223253

src/ResourceManager/Profile/ChangeLog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
- Additional information about change #1
1919
-->
2020
## Current Release
21+
* Added `Send-Feedback' cmdlet: allows a user to initiate a set of prompts which sends feedback to the Azure PowerShell team.
2122

2223
## Version 2.8.0
2324
* *Obsolete*: Save-AzureRmProfile is renamed to Save-AzureRmContext, there is an alias to the old cmdlet name, the alias will be removed in the next release.

src/ResourceManager/Profile/Commands.Profile.Test/Commands.Profile.Test.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@
191191
<Compile Include="ProfileController.cs" />
192192
<Compile Include="RPRegistrationDelegatingHandlerTests.cs" />
193193
<Compile Include="SubscriptionCmdletTests.cs" />
194+
<Compile Include="SendFeedbackTests.cs" />
194195
<Compile Include="TelemetryTests.cs" />
195196
<Compile Include="TenantCmdletTests.cs" />
196197
<Compile Include="LoginCmdletTests.cs" />
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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 Microsoft.Azure.Commands.Common.Authentication;
16+
using Microsoft.Azure.Commands.Common.Authentication.Models;
17+
using Microsoft.Azure.Commands.Profile.Models;
18+
using Microsoft.Azure.ServiceManagemenet.Common.Models;
19+
using Microsoft.WindowsAzure.Commands.Common;
20+
using Microsoft.WindowsAzure.Commands.Common.Test.Mocks;
21+
using Microsoft.WindowsAzure.Commands.ScenarioTest;
22+
using Microsoft.WindowsAzure.Commands.Utilities.Common;
23+
using System;
24+
using Xunit;
25+
using Xunit.Abstractions;
26+
27+
namespace Microsoft.Azure.Commands.Profile.Test
28+
{
29+
public class SendFeedbackTests
30+
{
31+
private MemoryDataStore dataStore;
32+
private MockCommandRuntime commandRuntimeMock;
33+
34+
public SendFeedbackTests(ITestOutputHelper output)
35+
{
36+
XunitTracingInterceptor.AddToContext(new XunitTracingInterceptor(output));
37+
dataStore = new MemoryDataStore();
38+
AzureSession.DataStore = dataStore;
39+
commandRuntimeMock = new MockCommandRuntime();
40+
AzureRmProfileProvider.Instance.Profile = new AzureRMProfile();
41+
}
42+
43+
[Fact]
44+
[Trait(Category.RunType, Category.LiveOnly)]
45+
public void SendFeedbackFailsInNonInteractive()
46+
{
47+
var cmdlet = new SendFeedbackCommand();
48+
49+
// Setup
50+
cmdlet.CommandRuntime = commandRuntimeMock;
51+
52+
// Act
53+
Assert.ThrowsAny<Exception>(() =>
54+
{
55+
cmdlet.InvokeBeginProcessing();
56+
});
57+
}
58+
59+
[Fact]
60+
[Trait(Category.AcceptanceType, Category.CheckIn)]
61+
public void CanSerializeSimpleFeedbackPayloadIntoProperForm()
62+
{
63+
var payload = new PSAzureFeedback
64+
{
65+
ModuleName = "Module",
66+
ModuleVersion = "1.0.0",
67+
SubscriptionId = Guid.NewGuid(),
68+
TenantId = Guid.NewGuid(),
69+
Environment = "AzureCloud",
70+
Recommendation = 10,
71+
PositiveComments = "Positive",
72+
NegativeComments = "Negative",
73+
74+
};
75+
76+
var serializedPayload = MetricHelper.SerializeCustomEventPayload(payload);
77+
78+
Assert.Equal(payload.ModuleName, serializedPayload["moduleName"]);
79+
Assert.Equal(payload.ModuleVersion, serializedPayload["moduleVersion"]);
80+
Assert.Equal(payload.SubscriptionId.ToString(), serializedPayload["subscriptionId"]);
81+
Assert.Equal(payload.TenantId.ToString(), serializedPayload["tenantId"]);
82+
Assert.Equal(payload.Environment, serializedPayload["environment"]);
83+
Assert.Equal(payload.Recommendation.ToString(), serializedPayload["recommendation"]);
84+
Assert.Equal(payload.PositiveComments, serializedPayload["positiveComments"]);
85+
Assert.Equal(payload.NegativeComments, serializedPayload["negativeComments"]);
86+
Assert.Equal(payload.Email, serializedPayload["email"]);
87+
}
88+
}
89+
}

src/ResourceManager/Profile/Commands.Profile/Commands.Profile.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,11 @@
131131
<Compile Include="Environment\GetAzureRMEnvironment.cs" />
132132
<Compile Include="Environment\SetAzureRMEnvironment.cs" />
133133
<Compile Include="Environment\AddAzureRMEnvironment.cs" />
134+
<Compile Include="Feedback\SendFeedback.cs" />
134135
<Compile Include="Models\ModelExtensions.cs" />
135136
<Compile Include="Models\PSAzureContext.cs" />
136137
<Compile Include="Models\PSAzureEnvironment.cs" />
138+
<Compile Include="Models\PSAzureFeedback.cs" />
137139
<Compile Include="Models\PSAzureProfile.cs" />
138140
<Compile Include="Models\PSAzureRmAccount.cs" />
139141
<Compile Include="Models\PSAzureSubscription.cs" />
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
//
2+
// Copyright Microsoft Corporation
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
// Unless required by applicable law or agreed to in writing, software
8+
// distributed under the License is distributed on an "AS IS" BASIS,
9+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
// See the License for the specific language governing permissions and
11+
// limitations under the License.
12+
// ----------------------------------------------------------------------------------
13+
14+
using Microsoft.Azure.Commands.Profile.Models;
15+
using Microsoft.Azure.Commands.Profile.Properties;
16+
using Microsoft.Azure.Commands.ResourceManager.Common;
17+
using Microsoft.WindowsAzure.Commands.Common;
18+
using System;
19+
using System.Linq;
20+
using System.Management.Automation;
21+
22+
namespace Microsoft.Azure.Commands.Profile
23+
{
24+
[Cmdlet(VerbsCommunications.Send, "Feedback"), OutputType(typeof(void))]
25+
public class SendFeedbackCommand : AzureRMCmdlet
26+
{
27+
private const string _eventName = "feedback";
28+
29+
protected override void BeginProcessing()
30+
{
31+
// Do not call base.BeginProcessing(), as context is not required for this cmdlet.
32+
33+
if (!this.CheckIfInteractive())
34+
throw new PSInvalidOperationException(String.Format(Resources.SendFeedbackNonInteractiveMessage, nameof(SendFeedbackCommand)));
35+
}
36+
37+
public override void ExecuteCmdlet()
38+
{
39+
this.WriteQuestion(Resources.SendFeedbackRecommendationQuestion);
40+
if (!int.TryParse(this.Host.UI.ReadLine(), out var recommendation) || recommendation < 0 || recommendation > 10)
41+
throw new PSArgumentOutOfRangeException(Resources.SendFeedbackOutOfRangeMessage);
42+
43+
this.WriteQuestion(Resources.SendFeedbackPositiveCommentsQuestion);
44+
var positiveComments = this.Host.UI.ReadLine();
45+
46+
this.WriteQuestion(Resources.SendFeedbackNegativeCommentsQuestion);
47+
var negativeComments = this.Host.UI.ReadLine();
48+
49+
this.WriteQuestion(Resources.SendFeedbackEmailQuestion);
50+
var email = this.Host.UI.ReadLine();
51+
52+
var loggedIn = this.DefaultProfile != null && this.DefaultProfile.Context != null;
53+
54+
var feedbackPayload = new PSAzureFeedback
55+
{
56+
ModuleName = this.ModuleName,
57+
ModuleVersion = this.ModuleVersion,
58+
SubscriptionId = loggedIn ? this.DefaultContext.Subscription.Id : Guid.Empty,
59+
TenantId = loggedIn ? this.DefaultContext.Tenant.Id : Guid.Empty,
60+
Environment = loggedIn ? this.DefaultContext.Environment.Name : null,
61+
Recommendation = recommendation,
62+
PositiveComments = positiveComments,
63+
NegativeComments = negativeComments,
64+
Email = email
65+
};
66+
67+
this.Host.UI.WriteLine();
68+
69+
// Log the event with force since the user specifically issued this command to provide feedback.
70+
this._metricHelper.LogCustomEvent(_eventName, feedbackPayload, true /* force */);
71+
}
72+
73+
private void WriteQuestion(string question)
74+
{
75+
this.Host.UI.WriteLine(ConsoleColor.Cyan, this.Host.UI.RawUI.BackgroundColor, $"{Environment.NewLine}{question}{Environment.NewLine}");
76+
}
77+
}
78+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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 System;
16+
17+
namespace Microsoft.Azure.Commands.Profile.Models
18+
{
19+
/// <summary>
20+
/// Feedback data for Application Insights endpoint.
21+
/// </summary>
22+
public class PSAzureFeedback
23+
{
24+
public string ModuleName { get; set; }
25+
public string ModuleVersion { get; set; }
26+
public Guid SubscriptionId { get; set; }
27+
public Guid TenantId { get; set; }
28+
public string Environment { get; set; }
29+
public int Recommendation { get; set; }
30+
public string PositiveComments { get; set; }
31+
public string NegativeComments { get; set; }
32+
public string Email { get; set; }
33+
}
34+
}

src/ResourceManager/Profile/Commands.Profile/Properties/Resources.Designer.cs

Lines changed: 54 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/ResourceManager/Profile/Commands.Profile/Properties/Resources.resx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,25 @@
220220
<data name="SelectedSubscriptionNotActive" xml:space="preserve">
221221
<value>Selected subscription is in '{0}' state. </value>
222222
</data>
223+
<data name="SendFeedbackEmailQuestion" xml:space="preserve">
224+
<value>Please enter your email if you are interested in providing follow up information:</value>
225+
</data>
226+
<data name="SendFeedbackNegativeCommentsQuestion" xml:space="preserve">
227+
<value>Upon what could Azure PowerShell improve? </value>
228+
</data>
229+
<data name="SendFeedbackNonInteractiveMessage" xml:space="preserve">
230+
<value>{0} must be issued in interactive mode.</value>
231+
<comment>{0} is nameof feedback cmdlet</comment>
232+
</data>
233+
<data name="SendFeedbackOutOfRangeMessage" xml:space="preserve">
234+
<value>The value entered was either not convertible to an integer or out of range [0, 10].</value>
235+
</data>
236+
<data name="SendFeedbackPositiveCommentsQuestion" xml:space="preserve">
237+
<value>What does Azure PowerShell do well?</value>
238+
</data>
239+
<data name="SendFeedbackRecommendationQuestion" xml:space="preserve">
240+
<value>With zero (0) being the least and ten (10) being the most, how likely are you to recommend Azure PowerShell to a friend or colleague?</value>
241+
</data>
223242
<data name="SetAzureRmContextNoParameterSet" xml:space="preserve">
224243
<value>Please provide either a subscription ID, subscription name, tenant Id or domain.</value>
225244
</data>

src/ResourceManager/Profile/Commands.Profile/Tenant/GetAzureRMTenant.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public override void ExecuteCmdlet()
3737
{
3838
var profileClient = new RMProfileClient(AzureRmProfileProvider.Instance.Profile);
3939

40-
WriteObject(profileClient.ListTenants(TenantId).Select((t) => (PSAzureTenant)t), enumerateCollection: true);
40+
WriteObject(profileClient.ListTenants(TenantId).Cast<PSAzureTenant>(), enumerateCollection: true);
4141
}
4242
}
4343
}

0 commit comments

Comments
 (0)