Skip to content

Commit

Permalink
Condition the survey and correlate survey and telemetry.
Browse files Browse the repository at this point in the history
- Check if the user has used the module in the past 30 days and whether
  the user uses at least 3 times. If so, prompt the survey.
- Create a survey id based on user id and use it to associate the survey
  and the telemetry.
- Add a field in the telemetry to check if the telemetry data is from an
  internal user.
  • Loading branch information
kceiw committed Feb 1, 2021
1 parent 7605317 commit 734fb53
Show file tree
Hide file tree
Showing 9 changed files with 228 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ For more information on Az Predictor, please visit the following: https://aka.ms

<ItemGroup>
<None Include="Az.Tools.Predictor.psd1" CopyToOutputDirectory="PreserveNewest" />
<None Include="ImportPrompt.ps1" CopyToOutputDirectory="PreserveNewest" />
<None Include="InterceptSurvey.ps1" CopyToOutputDirectory="PreserveNewest" />
<None Include="AzPredictorSettings.json" CopyToOutputDirectory="PreserveNewest" />
<None Include="command_param_to_resource_map.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ PowerShellVersion = '7.1'

NestedModules = @("Microsoft.Azure.PowerShell.Tools.AzPredictor.dll")

ScriptsToProcess = @("ImportPrompt.ps1")
ScriptsToProcess = @("InterceptSurvey.ps1")

# Format files (.ps1xml) to be loaded when importing this module

Expand Down
15 changes: 14 additions & 1 deletion tools/Az.Tools.Predictor/Az.Tools.Predictor/AzContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ namespace Microsoft.Azure.PowerShell.Tools.AzPredictor
/// </summary>
internal sealed class AzContext : IAzContext
{
private const string InternalUserSuffix = "@microsoft.com";
private static readonly Version DefaultVersion = new Version("0.0.0.0");

/// <inheritdoc/>
Expand Down Expand Up @@ -100,13 +101,25 @@ public Version ModuleVersion
}
}

public bool IsInternal { get; internal set; }

internal string SurveyId { get; set; }

/// <inheritdoc/>
public void UpdateContext()
{
AzVersion = GetAzVersion();
UserId = GenerateSha256HashString(GetUserAccountId());
RawUserId = GetUserAccountId();
UserId = GenerateSha256HashString(RawUserId);

if (!IsInternal)
{
IsInternal = RawUserId.EndsWith(AzContext.InternalUserSuffix, StringComparison.OrdinalIgnoreCase);
}
}

internal string RawUserId { get; set; }

/// <summary>
/// Gets the user account id if the user logs in, otherwise empty string.
/// </summary>
Expand Down
8 changes: 7 additions & 1 deletion tools/Az.Tools.Predictor/Az.Tools.Predictor/AzPredictor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using Microsoft.Azure.PowerShell.Tools.AzPredictor.Utilities;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Language;
Expand Down Expand Up @@ -225,7 +226,12 @@ public class PredictorInitializer : IModuleAssemblyInitializer
public void OnImport()
{
var settings = Settings.GetSettings();
var azContext = new AzContext();
var azContext = new AzContext()
{
IsInternal = (settings.SetAsInternal == true) ? true : false,
SurveyId = settings.SurveyId?.ToString(CultureInfo.InvariantCulture) ?? string.Empty,
};

azContext.UpdateContext();
var telemetryClient = new AzPredictorTelemetryClient(azContext);
var azPredictorService = new AzPredictorService(settings.ServiceUri, telemetryClient, azContext);
Expand Down
5 changes: 5 additions & 0 deletions tools/Az.Tools.Predictor/Az.Tools.Predictor/IAzContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ internal interface IAzContext
/// </summary>
public Version AzVersion { get; }

/// <summary>
/// Gets whether the user is an internal user.
/// </summary>
public bool IsInternal { get; }

/// <summary>
/// Updates the Az context.
/// </summary>
Expand Down
3 changes: 0 additions & 3 deletions tools/Az.Tools.Predictor/Az.Tools.Predictor/ImportPrompt.ps1

This file was deleted.

175 changes: 175 additions & 0 deletions tools/Az.Tools.Predictor/Az.Tools.Predictor/InterceptSurvey.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
param (
[Parameter(Mandatory)]
[string] $moduleName,
[Parameter(Mandatory)]
[int] $majorVersion
)

if ([string]::IsNullOrWhiteSpace($moduleName))
{
return
}

$mutexName = "AzModulesInterceptSurvey"
$mutexTiimeout = 1000
$interceptDays = 30
$interceptLoadTimes = 3
$today = Get-Date

function ConvertTo-String {
param (
[Parameter(Mandatory)]
[DateTime] $date
)

return $date.ToString("yyyy-MM-dd")
}

function Init-InterceptFile {
$interceptContent = @{
"lastInterceptCheckDate"=ConvertTo-String($today);
"interceptTriggered"=$false;
"modules"=@(@{
"name"=$moduleName;
"majorVersion"=$majorVersion;
"activeDays"=1;
"lastActiveDate"=ConvertTo-String($today);
})
}

ConvertTo-Json -InputObject $interceptContent | Out-File -FilePath $interceptFilePath -Encoding utf8
}

# Update the intercept object and return $true if we need to show the survey.
function Update-InterceptObject {
param (
$interceptObject
)

$thisModule = $null

foreach ($m in $interceptObject.modules) {
if ($m.name -eq $moduleName) {
$thisModule = $m
break
}
}

if ($thisModule -eq $null) {
# There is no information about this module. The file could be created by another module or in some other way.
# We need to add this module to the list.

$thisModule = @{
"name"=$moduleName;
"majorVersion"=$majorVersion;
"activeDays"=1;
"lastActiveDate"=ConvertTo-String($today);
}

$interceptObject.modules += $thisModule

return $false
}

$recordedMajorVersion = $thisModule.majorVersion
$thisModule.majorVersion = $majorVersion

if ($recordedMajorVersion -ne $majorVersion) {
$thisModule.activeDays = 1
$thisModule.lastActiveDate = ConvertTo-String($today)
$interceptObject.interceptTriggered = $false

return $false
}

$recordedLastActiveDate = Get-Date $thisModule.lastActiveDate
$recordedActiveDays = $thisModule.activeDays

$elapsedDays = ($today - $recordedLastActiveDate).Days

if ($elapsedDays -gt $interceptDays) {
$thisModule.activeDays = 1
$thisModule.lastActiveDate = ConvertTo-String($today)

return $false
}

$newActiveDays = $recordedActiveDays

if ($today -ne $recordedLastActiveDate) {
$newActiveDays++
}

if ($newActiveDays -ge $interceptLoadTimes) {
$thisModule.activeDays = 0
$thisModule.lastActiveDate = ConvertTo-String($today)
$interceptObject.interceptTriggered = $true
return $true
}

$thisModule.activeDays = $newActiveDays
$thisModule.lastActiveDate = ConvertTo-String($today)
}

$mutex = New-Object System.Threading.Mutex($false, $mutexName)

$mutex.WaitOne($mutexTimeout)
$shouldIntercept = $false

try
{
$interceptFilePath = Join-Path -Path (Join-Path -Path $env:USERPROFILE -ChildPath ".Azure") -ChildPath "InterceptSurvey.json"

if (-not (Test-Path $interceptFilePath)) {
New-Item -ItemType File -Force -Path $interceptFilePath
Init-InterceptFile
} else {
$interceptObject = $null
try {
$fileContent = Get-Content $interceptFilePath | Out-String
$interceptObject = ConvertFrom-Json $fileContent
} catch {
Init-InterceptFile
}

if (-not ($interceptObject -eq $null)) {
$shouldIntercept = Update-InterceptObject($interceptObject)

ConvertTo-Json -InputObject $interceptObject | Out-File $interceptFilePath -Encoding utf8
}
}
} finally
{
$mutex.ReleaseMutex()
}

Write-Host "To enable suggestions from Az predictor, run: Set-PSReadLineOption -PredictionSource HistoryAndPlugin"

if ($shouldIntercept) {
$userId = (Get-AzContext).Account.Id
if ($userId -ne $null)
{
$surveyId = Get-Random -Maximum 1000000 -SetSeed $userId.GetHashCode()
Write-Host "We are listening, please share your feedback about Az Predictor: http://aka.ms/azpredictorsurvey?iQ_CHL=intercept&surveyId=$surveyId"

try {
$azPredictorSettingFilePath = Join-Path -Path (Join-Path -Path $env:USERPROFILE -ChildPath ".Azure") -ChildPath "AzPredictorSettings.json"
$setting = @{
"surveyId"=$surveyId;
}

if (Test-Path $azPredictorSettingFilePath) {
try {
$setting = Get-Content $azPredictorSettingFilePath | Out-String | ConvertFrom-Json
$setting | Add-Member -NotePropertyName "surveyId" -NotePropertyValue $surveyId
} catch {
}
}

ConvertTo-Json -InputObject $setting | Out-File -FilePath $azPredictorSettingFilePath -Encoding utf8
} catch {
}
} else {
Write-Host "We are listening, please share your feedback about Az Predictor: http://aka.ms/azpredictorsurvey?iQ_CHL=intercept"
}
}
24 changes: 23 additions & 1 deletion tools/Az.Tools.Predictor/Az.Tools.Predictor/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,30 @@ sealed class Settings
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};

/// <summary>
/// The maximum number of suggestions that have the same command name.
/// </summary>
public int? MaxAllowedCommandDuplicate { get; set; }

/// <summary>
/// The service to get the prediction results back.
/// </summary>
public string ServiceUri { get; set; }

/// <summary>
/// Set the user as an internal user.
/// </summary>
public bool? SetAsInternal { get; set; }

/// <summary>
/// The number of suggestions to return to PSReadLine.
/// </summary>
public int? SuggestionCount { get; set; }
public int? MaxAllowedCommandDuplicate { get; set; }

/// <summary>
/// The survey id. It should be internal but make it public so that we can read/write to Json.
/// </summary>
public int? SurveyId { get; set; }

private static bool? _isContinueOnTimeout;
/// <summary>
Expand Down Expand Up @@ -127,6 +141,14 @@ private void OverrideSettingsFromProfile()
{
this.MaxAllowedCommandDuplicate = profileSettings.MaxAllowedCommandDuplicate;
}

this.SetAsInternal = profileSettings.SetAsInternal;
this.SurveyId = profileSettings.SurveyId;

profileSettings.SurveyId = null;

fileContent = JsonSerializer.Serialize<Settings>(profileSettings, new JsonSerializerOptions(Settings._jsonSerializerOptions) { IgnoreNullValues = true });
File.WriteAllText(profileSettingFilePath, fileContent, Encoding.UTF8);
}
catch
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,8 @@ private IDictionary<string, string> CreateProperties(ITelemetryData telemetryDat
{ "SessionId", telemetryData.SessionId },
{ "CorrelationId", telemetryData.CorrelationId },
{ "UserId", _azContext.UserId },
{ "IsInternal", _azContext.IsInternal.ToString(CultureInfo.InvariantCulture) },
{ "SurveyId", (_azContext as AzContext)?.SurveyId },
{ "HashMacAddress", _azContext.MacAddress },
{ "PowerShellVersion", _azContext.PowerShellVersion.ToString() },
{ "ModuleVersion", _azContext.ModuleVersion.ToString() },
Expand Down

0 comments on commit 734fb53

Please sign in to comment.