Skip to content

Hook up Telemetry LSP event and add telemetry event when users opt-out/in to features #1381

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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions src/PowerShellEditorServices/Logging/PsesTelemetryEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

using System.Collections.Generic;
using Newtonsoft.Json.Linq;

namespace Microsoft.PowerShell.EditorServices.Logging
{
// This inheirits from Dictionary so that it can be passed in to SendTelemetryEvent()
// which takes in an IDictionary<string, JToken>
// However, I wanted creation to be easy so you can do
// new PsesTelemetryEvent { EventName = "eventName", Data = data }
internal class PsesTelemetryEvent : Dictionary<string, JToken>
{
public string EventName
{
get
{
return this["EventName"].ToString() ?? "PsesEvent";
}
set
{
this["EventName"] = value;
}
}

public JObject Data
{
get
{
return this["Data"] as JObject ?? new JObject();
}
set
{
this["Data"] = value;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

namespace Microsoft.PowerShell.EditorServices.Handlers
{
[Serial, Method("powerShell/getCommand")]
[Serial, Method("powerShell/getCommand", Direction.ClientToServer)]
internal interface IGetCommandHandler : IJsonRpcRequestHandler<GetCommandParams, List<PSCommandMessage>> { }

internal class GetCommandParams : IRequest<List<PSCommandMessage>> { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@

namespace Microsoft.PowerShell.EditorServices.Handlers
{
[Serial, Method("powerShell/getProjectTemplates")]
[Serial]
[Method("powerShell/getProjectTemplates", Direction.ClientToServer)]
internal interface IGetProjectTemplatesHandler : IJsonRpcRequestHandler<GetProjectTemplatesRequest, GetProjectTemplatesResponse> { }

[Serial, Method("powerShell/newProjectFromTemplate")]
[Serial]
[Method("powerShell/newProjectFromTemplate", Direction.ClientToServer)]
internal interface INewProjectFromTemplateHandler : IJsonRpcRequestHandler<NewProjectFromTemplateRequest, NewProjectFromTemplateResponse> { }

internal class GetProjectTemplatesRequest : IRequest<GetProjectTemplatesResponse>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using MediatR;
using Microsoft.Extensions.Logging;
using Microsoft.PowerShell.EditorServices.Logging;
using Microsoft.PowerShell.EditorServices.Services;
using Microsoft.PowerShell.EditorServices.Services.Configuration;
using MediatR;
using Newtonsoft.Json.Linq;
using OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
using OmniSharp.Extensions.LanguageServer.Protocol.Window;
using OmniSharp.Extensions.LanguageServer.Protocol.Workspace;
using System.IO;

namespace Microsoft.PowerShell.EditorServices.Handlers
{
Expand All @@ -24,6 +28,7 @@ internal class PsesConfigurationHandler : IDidChangeConfigurationHandler
private readonly WorkspaceService _workspaceService;
private readonly ConfigurationService _configurationService;
private readonly PowerShellContextService _powerShellContextService;
private readonly ILanguageServerFacade _languageServer;
private DidChangeConfigurationCapability _capability;
private bool _profilesLoaded;
private bool _consoleReplStarted;
Expand All @@ -34,13 +39,14 @@ public PsesConfigurationHandler(
WorkspaceService workspaceService,
AnalysisService analysisService,
ConfigurationService configurationService,
PowerShellContextService powerShellContextService)
PowerShellContextService powerShellContextService,
ILanguageServerFacade languageServer)
{
_logger = factory.CreateLogger<PsesConfigurationHandler>();
_workspaceService = workspaceService;
_configurationService = configurationService;
_powerShellContextService = powerShellContextService;

_languageServer = languageServer;
ConfigurationUpdated += analysisService.OnConfigurationUpdated;
}

Expand All @@ -57,6 +63,8 @@ public async Task<Unit> Handle(DidChangeConfigurationParams request, Cancellatio
return await Unit.Task.ConfigureAwait(false);
}

SendFeatureChangesTelemetry(incomingSettings);

bool oldLoadProfiles = _configurationService.CurrentSettings.EnableProfileLoading;
bool oldScriptAnalysisEnabled =
_configurationService.CurrentSettings.ScriptAnalysis.Enable ?? false;
Expand Down Expand Up @@ -141,6 +149,61 @@ await _powerShellContextService.SetWorkingDirectoryAsync(
return await Unit.Task.ConfigureAwait(false);
}

private void SendFeatureChangesTelemetry(LanguageServerSettingsWrapper incomingSettings)
{
var configChanges = new Dictionary<string, bool>();
// Send telemetry if the user opted-out of ScriptAnalysis
if (incomingSettings.Powershell.ScriptAnalysis.Enable == false &&
_configurationService.CurrentSettings.ScriptAnalysis.Enable != incomingSettings.Powershell.ScriptAnalysis.Enable)
{
configChanges["ScriptAnalysis"] = incomingSettings.Powershell.ScriptAnalysis.Enable ?? false;
}

// Send telemetry if the user opted-out of CodeFolding
if (!incomingSettings.Powershell.CodeFolding.Enable &&
_configurationService.CurrentSettings.CodeFolding.Enable != incomingSettings.Powershell.CodeFolding.Enable)
{
configChanges["CodeFolding"] = incomingSettings.Powershell.CodeFolding.Enable;
}

// Send telemetry if the user opted-out of the prompt to update PackageManagement
if (!incomingSettings.Powershell.PromptToUpdatePackageManagement &&
_configurationService.CurrentSettings.PromptToUpdatePackageManagement != incomingSettings.Powershell.PromptToUpdatePackageManagement)
{
configChanges["PromptToUpdatePackageManagement"] = incomingSettings.Powershell.PromptToUpdatePackageManagement;
}

// Send telemetry if the user opted-out of Profile loading
if (!incomingSettings.Powershell.EnableProfileLoading &&
_configurationService.CurrentSettings.EnableProfileLoading != incomingSettings.Powershell.EnableProfileLoading)
{
configChanges["ProfileLoading"] = incomingSettings.Powershell.EnableProfileLoading;
}

// Send telemetry if the user opted-in to Pester 5+ CodeLens
if (!incomingSettings.Powershell.Pester.UseLegacyCodeLens &&
_configurationService.CurrentSettings.Pester.UseLegacyCodeLens != incomingSettings.Powershell.Pester.UseLegacyCodeLens)
{
// From our perspective we want to see how many people are opting in to this so we flip the value
configChanges["Pester5CodeLens"] = !incomingSettings.Powershell.Pester.UseLegacyCodeLens;
}

// No need to send any telemetry since nothing changed
if (configChanges.Count == 0)
{
return;
}

_languageServer.Window.SendTelemetryEvent(new TelemetryEventParams
{
Data = new PsesTelemetryEvent
{
EventName = "NonDefaultPsesFeatureConfiguration",
Data = JObject.FromObject(configChanges)
}
});
}

public void SetCapability(DidChangeConfigurationCapability capability)
{
_capability = capability;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ namespace Microsoft.PowerShell.EditorServices.Services.Configuration
internal class LanguageServerSettings
{
private readonly object updateLock = new object();
public bool EnableProfileLoading { get; set; }

public bool PromptToUpdatePackageManagement { get; set; }
public bool EnableProfileLoading { get; set; } = true;

public bool PromptToUpdatePackageManagement { get; set; } = true;

public ScriptAnalysisSettings ScriptAnalysis { get; set; }

Expand Down
29 changes: 18 additions & 11 deletions test/PowerShellEditorServices.Test.E2E/LSPTestsFixures.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.PowerShell.EditorServices.Logging;
using Microsoft.PowerShell.EditorServices.Services.Configuration;
using Newtonsoft.Json.Linq;
using OmniSharp.Extensions.LanguageServer.Client;
using OmniSharp.Extensions.LanguageServer.Protocol;
Expand All @@ -29,7 +31,7 @@ public class LSPTestsFixture : TestsFixture

public ILanguageClient PsesLanguageClient { get; private set; }
public List<Diagnostic> Diagnostics { get; set; }

internal List<PsesTelemetryEvent> TelemetryEvents { get; set; }
public ITestOutputHelper Output { get; set; }

public async override Task CustomInitializeAsync(
Expand All @@ -38,6 +40,7 @@ public async override Task CustomInitializeAsync(
Stream outputStream)
{
Diagnostics = new List<Diagnostic>();
TelemetryEvents = new List<PsesTelemetryEvent>();
DirectoryInfo testdir =
Directory.CreateDirectory(Path.Combine(s_binDir, Path.GetRandomFileName()));

Expand All @@ -48,7 +51,13 @@ public async override Task CustomInitializeAsync(
.WithOutput(outputStream)
.WithRootUri(DocumentUri.FromFileSystemPath(testdir.FullName))
.OnPublishDiagnostics(diagnosticParams => Diagnostics.AddRange(diagnosticParams.Diagnostics.Where(d => d != null)))
.OnLogMessage(logMessageParams => Output?.WriteLine($"{logMessageParams.Type.ToString()}: {logMessageParams.Message}"));
.OnLogMessage(logMessageParams => Output?.WriteLine($"{logMessageParams.Type.ToString()}: {logMessageParams.Message}"))
.OnTelemetryEvent(telemetryEventParams => TelemetryEvents.Add(
new PsesTelemetryEvent
{
EventName = (string) telemetryEventParams.Data["eventName"],
Data = telemetryEventParams.Data["data"] as JObject
}));

// Enable all capabilities this this is for testing.
// This will be a built in feature of the Omnisharp client at some point.
Expand All @@ -63,18 +72,16 @@ public async override Task CustomInitializeAsync(
await PsesLanguageClient.Initialize(CancellationToken.None).ConfigureAwait(false);

// Make sure Script Analysis is enabled because we'll need it in the tests.
// This also makes sure the configuration is set to default values.
PsesLanguageClient.Workspace.DidChangeConfiguration(
new DidChangeConfigurationParams
{
Settings = JObject.Parse(@"
{
""powershell"": {
""scriptAnalysis"": {
""enable"": true
}
}
}
")
Settings = JToken.FromObject(new LanguageServerSettingsWrapper
{
Files = new EditorFileSettings(),
Search = new EditorSearchSettings(),
Powershell = new LanguageServerSettings()
})
});
}

Expand Down
Loading