Skip to content

Commit

Permalink
SLVS-1304 Telemetry data migration to SLCore (#5572)
Browse files Browse the repository at this point in the history
  • Loading branch information
vnaskos-sonar authored and gabriela-trutan-sonarsource committed Aug 5, 2024
1 parent ac1ee80 commit 21f2c4d
Show file tree
Hide file tree
Showing 14 changed files with 379 additions and 28 deletions.
27 changes: 27 additions & 0 deletions src/Core/Telemetry/Legacy/ITelemetryDataRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* SonarLint for Visual Studio
* Copyright (C) 2016-2024 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

namespace SonarLint.VisualStudio.Core.Telemetry.Legacy;


public interface ITelemetryDataRepository
{
TelemetryData ReadTelemetryData();
}
49 changes: 49 additions & 0 deletions src/Core/Telemetry/Legacy/TelemetryData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* SonarLint for Visual Studio
* Copyright (C) 2016-2024 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using System.ComponentModel;
using System.Globalization;
using System.Xml.Serialization;

namespace SonarLint.VisualStudio.Core.Telemetry.Legacy;

public sealed class TelemetryData
{
public bool IsAnonymousDataShared { get; set; }

public int NumberOfDaysOfUse { get; set; }

[XmlIgnore] public DateTimeOffset InstallationDate { get; set; }

[XmlElement(nameof(InstallationDate)), EditorBrowsable(EditorBrowsableState.Never)]
public string InstallationDateString
{
get => InstallationDate.ToString("o");
set => InstallationDate = ParseSavedString(value);
}

private static DateTimeOffset ParseSavedString(string data)
{
// ParseExact will throw an exception when value is invalid date, but
// XmlSerializer will swallow it and return default(TelemetryData)
return DateTimeOffset.ParseExact(data, "o", CultureInfo.InvariantCulture,
DateTimeStyles.AssumeLocal | DateTimeStyles.AdjustToUniversal);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* SonarLint for Visual Studio
* Copyright (C) 2016-2024 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using System.IO.Abstractions;
using SonarLint.VisualStudio.Core;
using SonarLint.VisualStudio.Core.Telemetry.Legacy;
using SonarLint.VisualStudio.Integration.Telemetry.Legacy;

namespace SonarLint.VisualStudio.Integration.UnitTests.Telemetry.Legacy;

[TestClass]
public class TelemetryDataRepositoryTests
{
[TestMethod]
public void Data_WhenTelemetryFileDoesNotExist_ReturnNull()
{
var environmentVariableProvider = Substitute.For<IEnvironmentVariableProvider>();
var fileSystem = CreateFileSystem(environmentVariableProvider, false);
var telemetryDataRepository = new TelemetryDataRepository(fileSystem, environmentVariableProvider);

var actualData = telemetryDataRepository.ReadTelemetryData();

actualData.Should().BeNull();
}

[TestMethod]
public void Data_WhenTelemetryFileExist_ShouldProvideTelemetryData()
{
const string fileContent = """
<?xml version="1.0" encoding="utf-16"?>
<TelemetryData xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<IsAnonymousDataShared>false</IsAnonymousDataShared>
<NumberOfDaysOfUse>10</NumberOfDaysOfUse>
<InstallationDate>2010-03-15T06:15:42.1234567+01:00</InstallationDate>
</TelemetryData>
""";
var environmentVariableProvider = Substitute.For<IEnvironmentVariableProvider>();
var fileSystem = CreateFileSystem(environmentVariableProvider, true);
fileSystem.File.ReadAllText(Arg.Any<string>()).Returns(fileContent);
var telemetryDataRepository = new TelemetryDataRepository(fileSystem, environmentVariableProvider);

var actualData = telemetryDataRepository.ReadTelemetryData();

var expectedData = new TelemetryData
{
IsAnonymousDataShared = false,
InstallationDateString = "2010-03-15T06:15:42.1234567+01:00",
NumberOfDaysOfUse = 10
};
actualData.Should().BeEquivalentTo(expectedData);
}

[TestMethod]
public void Data_WhenTelemetryFileIsCorrupted_ShouldDeleteFileAndReturnNull()
{
var environmentVariableProvider = Substitute.For<IEnvironmentVariableProvider>();
var fileSystem = CreateFileSystem(environmentVariableProvider, true);
var telemetryDataRepository = new TelemetryDataRepository(fileSystem, environmentVariableProvider);

var actualData = telemetryDataRepository.ReadTelemetryData();

fileSystem.File.Received().Delete(Arg.Any<string>());
actualData.Should().BeNull();
}

private static IFileSystem CreateFileSystem(IEnvironmentVariableProvider environmentVariableProvider, bool fileExists)
{
var expectedFilePath = TelemetryDataRepository.GetStorageFilePath(environmentVariableProvider);
var fileSystem = Substitute.For<IFileSystem>();
fileSystem.File.Exists(expectedFilePath).Returns(fileExists);
return fileSystem;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
################################
# Assembly references report
# Report date/time: 2024-07-30T07:47:53.0416897Z
# Report date/time: 2024-07-31T07:49:07.8850697Z
################################
#
# Generated by Devtility CheckAsmRefs v0.11.0.223
Expand Down Expand Up @@ -266,9 +266,10 @@ Referenced assemblies:
- 'System.IO.Abstractions, Version=9.0.0.0, Culture=neutral, PublicKeyToken=96bf224d23c43e59'
- 'System.Net.Http, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
- 'System.Xaml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
- 'System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
- 'System.Xml.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
- 'WindowsBase, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'
# Number of references: 29
# Number of references: 30

---
Assembly: 'SonarLint.VisualStudio.Integration.TeamExplorer, Version=8.2.0.0, Culture=neutral, PublicKeyToken=c5b62af9de6d7244'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
################################
# Assembly references report
# Report date/time: 2024-07-30T07:47:53.0416897Z
# Report date/time: 2024-07-31T07:49:07.8850697Z
################################
#
# Generated by Devtility CheckAsmRefs v0.11.0.223
Expand Down Expand Up @@ -266,9 +266,10 @@ Referenced assemblies:
- 'System.IO.Abstractions, Version=9.0.0.0, Culture=neutral, PublicKeyToken=96bf224d23c43e59'
- 'System.Net.Http, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
- 'System.Xaml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
- 'System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
- 'System.Xml.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
- 'WindowsBase, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'
# Number of references: 29
# Number of references: 30

---
Assembly: 'SonarLint.VisualStudio.Integration.TeamExplorer, Version=8.2.0.0, Culture=neutral, PublicKeyToken=null'
Expand Down
83 changes: 83 additions & 0 deletions src/Integration/Telemetry/Legacy/TelemetryDataRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* SonarLint for Visual Studio
* Copyright (C) 2016-2024 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using System.ComponentModel.Composition;
using System.IO;
using System.IO.Abstractions;
using System.Xml.Serialization;
using SonarLint.VisualStudio.Core;
using SonarLint.VisualStudio.Core.Telemetry.Legacy;

namespace SonarLint.VisualStudio.Integration.Telemetry.Legacy;

[Export(typeof(ITelemetryDataRepository))]
[PartCreationPolicy(CreationPolicy.Shared)]
public sealed class TelemetryDataRepository : ITelemetryDataRepository
{
private readonly string storageFilePath;
private readonly XmlSerializer telemetrySerializer = new(typeof(TelemetryData));
private readonly IFileSystem fileSystem;

public TelemetryDataRepository() : this(new FileSystem(), EnvironmentVariableProvider.Instance)
{
}

internal /* for testing */ TelemetryDataRepository(IFileSystem fileSystem,
IEnvironmentVariableProvider environmentVariables)
{
this.fileSystem = fileSystem;
storageFilePath = GetStorageFilePath(environmentVariables);
}

public TelemetryData ReadTelemetryData()
{
TelemetryData data = null;
RetryHelper.RetryOnException(3, TimeSpan.FromSeconds(2), () => { data = ReadXmlFile(); });
return data;
}

private TelemetryData ReadXmlFile()
{
if (!fileSystem.File.Exists(storageFilePath))
{
return null;
}

try
{
var fileContent = fileSystem.File.ReadAllText(storageFilePath);
return telemetrySerializer.Deserialize(new StringReader(fileContent)) as TelemetryData;
}
catch (InvalidOperationException)
{
fileSystem.File.Delete(storageFilePath);
}

return null;
}

internal static string GetStorageFilePath(IEnvironmentVariableProvider environmentVariables)
{
// Note: the data is stored in the roaming profile, so it will be sync across machines for domain-joined users.
var appDataFolder = environmentVariables.GetSLVSAppDataRootPath();
var filePath = Path.Combine(appDataFolder, "telemetry.xml");
return Path.GetFullPath(filePath); // get rid of the .. in file path
}
}
3 changes: 2 additions & 1 deletion src/SLCore.IntegrationTests/SLCoreTestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ public void Start()
noOpActiveSolutionBoundTracker,
noOpConfigScopeUpdater,
slCoreRulesSettingsProvider,
Substitute.For<ISLCoreTelemetryMigrationProvider>(), new NoOpThreadHandler());
Substitute.For<ISlCoreTelemetryMigrationProvider>(),
new NoOpThreadHandler());
slCoreInstanceHandle.Initialize();
}
finally
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* SonarLint for Visual Studio
* Copyright (C) 2016-2024 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using SonarLint.VisualStudio.Core.Telemetry.Legacy;
using SonarLint.VisualStudio.SLCore.Configuration;
using SonarLint.VisualStudio.SLCore.Service.Lifecycle.Models;

namespace SonarLint.VisualStudio.SLCore.UnitTests.Configuration;

[TestClass]
public class SlCoreTelemetryMigrationProviderTests
{
[TestMethod]
public void MefCtor_CheckIsExported()
{
MefTestHelpers.CheckTypeCanBeImported<SlCoreTelemetryMigrationProvider, ISlCoreTelemetryMigrationProvider>(
MefTestHelpers.CreateExport<ITelemetryDataRepository>());
}

[TestMethod]
public void MefCtor_CheckIsSingleton()
{
MefTestHelpers.CheckIsSingletonMefComponent<SlCoreTelemetryMigrationProvider>();
}

[TestMethod]
public void Get_ConvertsOldTelemetryDataToTelemetryMigration()
{
var telemetryDataRepository = Substitute.For<ITelemetryDataRepository>();
telemetryDataRepository.ReadTelemetryData().Returns(new TelemetryData
{
IsAnonymousDataShared = true,
InstallationDateString = "2017-03-15T06:15:42.1234567+01:00",
NumberOfDaysOfUse = 32
});
var slCoreTelemetryMigrationProvider = new SlCoreTelemetryMigrationProvider(telemetryDataRepository);

var telemetryMigrationDto = slCoreTelemetryMigrationProvider.Get();

var expectedDto = new TelemetryMigrationDto(
isEnabled: true,
installTime: new DateTimeOffset(new DateTime(2017, 3, 15, 6, 15, 42, 123, DateTimeKind.Unspecified).AddTicks(4567), TimeSpan.FromHours(1)),
numUseDays: 32);
telemetryMigrationDto.Should().BeEquivalentTo(expectedDto);
}
}
4 changes: 2 additions & 2 deletions src/SLCore.UnitTests/SLCoreInstanceFactoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public void MefCtor_CheckIsExported()
MefTestHelpers.CreateExport<IConfigScopeUpdater>(),
MefTestHelpers.CreateExport<IThreadHandling>(),
MefTestHelpers.CreateExport<ISLCoreRuleSettingsProvider>(),
MefTestHelpers.CreateExport<ISLCoreTelemetryMigrationProvider>());
MefTestHelpers.CreateExport<ISlCoreTelemetryMigrationProvider>());
}

[TestMethod]
Expand All @@ -68,7 +68,7 @@ public void CreateInstance_ReturnsNonNull()
var configScopeUpdater = Substitute.For<IConfigScopeUpdater>();
var threadHandling = Substitute.For<IThreadHandling>();
var slCoreRuleSettingsProvider = Substitute.For<ISLCoreRuleSettingsProvider>();
var telemetryMigrationProvider = Substitute.For<ISLCoreTelemetryMigrationProvider>();
var telemetryMigrationProvider = Substitute.For<ISlCoreTelemetryMigrationProvider>();

var testSubject = new SLCoreInstanceFactory(islCoreRpcFactory,
islCoreConstantsProvider,
Expand Down
Loading

0 comments on commit 21f2c4d

Please sign in to comment.