V8: Merge package telemetry from V9 (#11785)

* Merge Telemetry classes from V9

* Use TelemetryService in ReportSiteTask

* Migrate tests
This commit is contained in:
Mole
2022-01-04 10:11:34 +01:00
committed by GitHub
parent bd1c392dff
commit a54c5bb21d
11 changed files with 239 additions and 28 deletions

View File

@@ -8,6 +8,7 @@ using Umbraco.Core.Logging;
using Umbraco.Core.Packaging;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
using Umbraco.Core.Telemetry;
namespace Umbraco.Core.Composing.CompositionExtensions
{
@@ -79,6 +80,8 @@ namespace Umbraco.Core.Composing.CompositionExtensions
factory.GetInstance<CompiledPackageXmlParser>(), factory.GetInstance<IPackageActionRunner>(),
new DirectoryInfo(IOHelper.GetRootDirectorySafe())));
composition.RegisterUnique<ITelemetryService, TelemetryService>();
return composition;
}

View File

@@ -67,7 +67,7 @@ namespace Umbraco.Core.Manifest
/// <summary>
/// Gets all manifests.
/// </summary>
private IEnumerable<PackageManifest> GetManifests()
internal IEnumerable<PackageManifest> GetManifests()
{
var manifests = new List<PackageManifest>();

View File

@@ -1,4 +1,5 @@
using System;
using System.IO;
using Newtonsoft.Json;
using Umbraco.Core.PropertyEditors;
@@ -9,6 +10,28 @@ namespace Umbraco.Core.Manifest
/// </summary>
public class PackageManifest
{
private string _packageName;
[JsonProperty("name")]
public string PackageName
{
get
{
if (string.IsNullOrWhiteSpace(_packageName) is false)
{
return _packageName;
}
if (string.IsNullOrWhiteSpace(Source) is false)
{
_packageName = Path.GetFileName(Path.GetDirectoryName(Source));
}
return _packageName;
}
set => _packageName = value;
}
/// <summary>
/// Gets the source path of the manifest.
/// </summary>
@@ -66,5 +89,17 @@ namespace Umbraco.Core.Manifest
/// </summary>
[JsonProperty("sections")]
public ManifestSection[] Sections { get; set; } = Array.Empty<ManifestSection>();
/// <summary>
/// Gets or sets the version of the package
/// </summary>
[JsonProperty("version")]
public string Version { get; set; } = string.Empty;
/// <summary>
/// Gets or sets a value indicating whether telemetry is allowed
/// </summary>
[JsonProperty("allowPackageTelemetry")]
public bool AllowPackageTelemetry { get; set; } = true;
}
}

View File

@@ -0,0 +1,15 @@
using Umbraco.Core.Telemetry.Models;
namespace Umbraco.Core.Telemetry
{
/// <summary>
/// Service which gathers the data for telemetry reporting
/// </summary>
public interface ITelemetryService
{
/// <summary>
/// Try and get the <see cref="TelemetryReportData"/>
/// </summary>
bool TryGetTelemetryReportData(out TelemetryReportData telemetryReportData);
}
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Runtime.Serialization;
namespace Umbraco.Core.Telemetry.Models
{
/// <summary>
/// Serializable class containing information about an installed package.
/// </summary>
[Serializable]
[DataContract(Name = "packageTelemetry")]
public class PackageTelemetry
{
/// <summary>
/// Gets or sets the name of the installed package.
/// </summary>
[DataMember(Name = "name")]
public string Name { get; set; }
/// <summary>
/// Gets or sets the version of the installed package.
/// </summary>
/// <remarks>
/// This may be an empty string if no version is specified, or if package telemetry has been restricted.
/// </remarks>
[DataMember(Name = "version")]
public string Version { get; set; }
}
}

View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace Umbraco.Core.Telemetry.Models
{
/// <summary>
/// Serializable class containing telemetry information.
/// </summary>
[DataContract]
public class TelemetryReportData
{
/// <summary>
/// Gets or sets a random GUID to prevent an instance posting multiple times pr. day.
/// </summary>
[DataMember(Name = "id")]
public Guid Id { get; set; }
/// <summary>
/// Gets or sets the Umbraco CMS version.
/// </summary>
[DataMember(Name = "version")]
public string Version { get; set; }
/// <summary>
/// Gets or sets an enumerable containing information about packages.
/// </summary>
/// <remarks>
/// Contains only the name and version of the packages, unless no version is specified.
/// </remarks>
[DataMember(Name = "packages")]
public IEnumerable<PackageTelemetry> Packages { get; set; }
}
}

View File

@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Manifest;
using Umbraco.Core.Telemetry.Models;
namespace Umbraco.Core.Telemetry
{
/// <inheritdoc/>
internal class TelemetryService : ITelemetryService
{
private readonly IUmbracoSettingsSection _settings;
private readonly ManifestParser _manifestParser;
/// <summary>
/// Initializes a new instance of the <see cref="TelemetryService"/> class.
/// </summary>
public TelemetryService(
ManifestParser manifestParser,
IUmbracoSettingsSection settings)
{
_manifestParser = manifestParser;
_settings = settings;
}
/// <inheritdoc/>
public bool TryGetTelemetryReportData(out TelemetryReportData telemetryReportData)
{
if (TryGetTelemetryId(out Guid telemetryId) is false)
{
telemetryReportData = null;
return false;
}
telemetryReportData = new TelemetryReportData
{
Id = telemetryId,
Version = UmbracoVersion.SemanticVersion.ToSemanticString(),
Packages = GetPackageTelemetry()
};
return true;
}
private bool TryGetTelemetryId(out Guid telemetryId)
{
// Parse telemetry string as a GUID & verify its a GUID and not some random string
// since users may have messed with or decided to empty the app setting or put in something random
if (Guid.TryParse(_settings.BackOffice.Id, out var parsedTelemetryId) is false)
{
telemetryId = Guid.Empty;
return false;
}
telemetryId = parsedTelemetryId;
return true;
}
private IEnumerable<PackageTelemetry> GetPackageTelemetry()
{
List<PackageTelemetry> packages = new ();
var manifests = _manifestParser.GetManifests();
foreach (var manifest in manifests)
{
if (manifest.AllowPackageTelemetry is false)
{
continue;
}
packages.Add(new PackageTelemetry
{
Name = manifest.PackageName,
Version = manifest.Version ?? string.Empty
});
}
return packages;
}
}
}

View File

@@ -396,6 +396,10 @@
<Compile Include="Services\DateTypeServiceExtensions.cs" />
<Compile Include="Services\PropertyValidationService.cs" />
<Compile Include="Composing\TypeCollectionBuilderBase.cs" />
<Compile Include="Telemetry\ITelemetryService.cs" />
<Compile Include="Telemetry\Models\PackageTelemetry.cs" />
<Compile Include="Telemetry\Models\TelemetryReportData.cs" />
<Compile Include="Telemetry\TelemetryService.cs" />
<Compile Include="TypeLoaderExtensions.cs" />
<Compile Include="Composing\WeightAttribute.cs" />
<Compile Include="Composing\WeightedCollectionBuilderBase.cs" />

View File

@@ -443,5 +443,27 @@ javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2
Assert.AreEqual("Content", manifest.Sections[0].Name);
Assert.AreEqual("World", manifest.Sections[1].Name);
}
[Test]
public void CanParseManifest_Version()
{
const string json = @"{""name"": ""VersionPackage"", ""version"": ""1.0.0""}";
PackageManifest manifest = _parser.ParseManifest(json);
Assert.Multiple(() =>
{
Assert.AreEqual("VersionPackage", manifest.PackageName);
Assert.AreEqual("1.0.0", manifest.Version);
});
}
[Test]
public void CanParseManifest_TrackingAllowed()
{
const string json = @"{""allowPackageTelemetry"": false }";
PackageManifest manifest = _parser.ParseManifest(json);
Assert.IsFalse(manifest.AllowPackageTelemetry);
}
}
}

View File

@@ -1,14 +1,11 @@
using Newtonsoft.Json;
using System;
using System.Net.Http;
using System.Runtime.Serialization;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Logging;
using Umbraco.Core.Telemetry;
using Umbraco.Web.Scheduling;
namespace Umbraco.Web.Telemetry
@@ -17,14 +14,19 @@ namespace Umbraco.Web.Telemetry
{
private readonly IProfilingLogger _logger;
private static HttpClient _httpClient;
private readonly IUmbracoSettingsSection _settings;
private readonly ITelemetryService _telemetryService;
public ReportSiteTask(IBackgroundTaskRunner<RecurringTaskBase> runner, int delayBeforeWeStart, int howOftenWeRepeat, IProfilingLogger logger, IUmbracoSettingsSection settings)
public ReportSiteTask(
IBackgroundTaskRunner<RecurringTaskBase> runner,
int delayBeforeWeStart,
int howOftenWeRepeat,
IProfilingLogger logger,
ITelemetryService telemetryService)
: base(runner, delayBeforeWeStart, howOftenWeRepeat)
{
_logger = logger;
_httpClient = new HttpClient();
_settings = settings;
_telemetryService = telemetryService;
}
/// <summary>
@@ -34,12 +36,9 @@ namespace Umbraco.Web.Telemetry
/// <returns>A value indicating whether to repeat the task.</returns>
public override async Task<bool> PerformRunAsync(CancellationToken token)
{
// Try & get a value stored in umbracoSettings.config on the backoffice XML element ID attribute
var backofficeIdentifierRaw = _settings.BackOffice.Id;
// Parse as a GUID & verify its a GUID and not some random string
// In case of users may have messed or decided to empty the file contents or put in something random
if (Guid.TryParse(backofficeIdentifierRaw, out var telemetrySiteIdentifier) == false)
if (_telemetryService.TryGetTelemetryReportData(out var telemetryReportData) is false)
{
// Some users may have decided to mess with the XML attribute and put in something else
// Stop repeating this task (no need to keep checking)
@@ -61,8 +60,7 @@ namespace Umbraco.Web.Telemetry
using (var request = new HttpRequestMessage(HttpMethod.Post, "installs/"))
{
var postData = new TelemetryReportData { Id = telemetrySiteIdentifier, Version = UmbracoVersion.SemanticVersion.ToSemanticString() };
request.Content = new StringContent(JsonConvert.SerializeObject(postData), Encoding.UTF8, "application/json"); //CONTENT-TYPE header
request.Content = new StringContent(JsonConvert.SerializeObject(telemetryReportData), Encoding.UTF8, "application/json"); //CONTENT-TYPE header
// Set a low timeout - no need to use a larger default timeout for this POST request
_httpClient.Timeout = new TimeSpan(0, 0, 1);
@@ -86,15 +84,5 @@ namespace Umbraco.Web.Telemetry
}
public override bool IsAsync => true;
[DataContract]
private class TelemetryReportData
{
[DataMember(Name = "id")]
public Guid Id { get; set; }
[DataMember(Name = "version")]
public string Version { get; set; }
}
}
}

View File

@@ -1,6 +1,7 @@
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Logging;
using Umbraco.Core.Telemetry;
using Umbraco.Web.Scheduling;
namespace Umbraco.Web.Telemetry
@@ -8,13 +9,13 @@ namespace Umbraco.Web.Telemetry
public class TelemetryComponent : IComponent
{
private readonly IProfilingLogger _logger;
private readonly IUmbracoSettingsSection _settings;
private readonly ITelemetryService _telemetryService;
private BackgroundTaskRunner<IBackgroundTask> _telemetryReporterRunner;
public TelemetryComponent(IProfilingLogger logger, IUmbracoSettingsSection settings)
public TelemetryComponent(IProfilingLogger logger, IUmbracoSettingsSection settings, ITelemetryService telemetryService)
{
_logger = logger;
_settings = settings;
_telemetryService = telemetryService;
}
public void Initialize()
@@ -26,7 +27,7 @@ namespace Umbraco.Web.Telemetry
const int howOftenWeRepeat = 60 * 1000 * 60 * 24; // 60 * 1000 * 60 * 24 = 24hrs (86400000)
// As soon as we add our task to the runner it will start to run (after its delay period)
var task = new ReportSiteTask(_telemetryReporterRunner, delayBeforeWeStart, howOftenWeRepeat, _logger, _settings);
var task = new ReportSiteTask(_telemetryReporterRunner, delayBeforeWeStart, howOftenWeRepeat, _logger, _telemetryService);
_telemetryReporterRunner.TryAdd(task);
}