From 01e603200c1a2cab273b18d0f831ed90a46bccf6 Mon Sep 17 00:00:00 2001 From: Gabriel Dufresne Date: Thu, 2 Apr 2026 11:00:56 -0400 Subject: [PATCH 01/11] added the telemetry call in all the avalonia view --- .../AvaloniaPackageOperationHelper.cs | 7 +++++++ .../SoftwarePages/PackagesPageViewModel.cs | 3 +++ .../DialogPages/PackageDetailsWindow.axaml.cs | 18 ++++++++++++++++++ .../SoftwarePages/InstalledPackagesPage.cs | 9 +++++++++ .../Views/SoftwarePages/PackageBundlesPage.cs | 2 ++ .../Views/SoftwarePages/SoftwareUpdatesPage.cs | 9 +++++++++ 6 files changed, 48 insertions(+) diff --git a/src/UniGetUI.Avalonia/Infrastructure/AvaloniaPackageOperationHelper.cs b/src/UniGetUI.Avalonia/Infrastructure/AvaloniaPackageOperationHelper.cs index 7e60a11607..5ad40d1adb 100644 --- a/src/UniGetUI.Avalonia/Infrastructure/AvaloniaPackageOperationHelper.cs +++ b/src/UniGetUI.Avalonia/Infrastructure/AvaloniaPackageOperationHelper.cs @@ -1,5 +1,6 @@ using UniGetUI.Core.Logging; using UniGetUI.Interface.Enums; +using UniGetUI.Interface.Telemetry; using UniGetUI.PackageEngine.Enums; using UniGetUI.PackageEngine.Operations; using UniGetUI.PackageEngine.PackageClasses; @@ -20,6 +21,8 @@ public static async Task UpdateAllAsync() if (pkg.Tag is PackageTag.BeingProcessed or PackageTag.OnQueue) continue; var opts = await InstallOptionsFactory.LoadApplicableAsync(pkg); var op = new UpdatePackageOperation(pkg, opts); + op.OperationSucceeded += (_, _) => TelemetryHandler.UpdatePackage(pkg, TEL_OP_RESULT.SUCCESS); + op.OperationFailed += (_, _) => TelemetryHandler.UpdatePackage(pkg, TEL_OP_RESULT.FAILED); AvaloniaOperationRegistry.Add(op); _ = op.MainThread(); } @@ -34,6 +37,8 @@ public static async Task UpdateAllForManagerAsync(string managerName) if (pkg.Tag is PackageTag.BeingProcessed or PackageTag.OnQueue) continue; var opts = await InstallOptionsFactory.LoadApplicableAsync(pkg); var op = new UpdatePackageOperation(pkg, opts); + op.OperationSucceeded += (_, _) => TelemetryHandler.UpdatePackage(pkg, TEL_OP_RESULT.SUCCESS); + op.OperationFailed += (_, _) => TelemetryHandler.UpdatePackage(pkg, TEL_OP_RESULT.FAILED); AvaloniaOperationRegistry.Add(op); _ = op.MainThread(); } @@ -50,6 +55,8 @@ public static async Task UpdateForIdAsync(string packageId) var opts = await InstallOptionsFactory.LoadApplicableAsync(pkg); var op = new UpdatePackageOperation(pkg, opts); + op.OperationSucceeded += (_, _) => TelemetryHandler.UpdatePackage(pkg, TEL_OP_RESULT.SUCCESS); + op.OperationFailed += (_, _) => TelemetryHandler.UpdatePackage(pkg, TEL_OP_RESULT.FAILED); AvaloniaOperationRegistry.Add(op); _ = op.MainThread(); } diff --git a/src/UniGetUI.Avalonia/ViewModels/SoftwarePages/PackagesPageViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/SoftwarePages/PackagesPageViewModel.cs index 69c8ea8dc8..5e08fc8a00 100644 --- a/src/UniGetUI.Avalonia/ViewModels/SoftwarePages/PackagesPageViewModel.cs +++ b/src/UniGetUI.Avalonia/ViewModels/SoftwarePages/PackagesPageViewModel.cs @@ -17,6 +17,7 @@ using UniGetUI.Core.Tools; using UniGetUI.PackageEngine.Enums; using UniGetUI.PackageEngine.Interfaces; +using UniGetUI.Interface.Telemetry; using UniGetUI.PackageEngine.Operations; using UniGetUI.PackageEngine.PackageClasses; using UniGetUI.PackageEngine.PackageLoader; @@ -784,6 +785,8 @@ public static async Task LaunchInstall( var opts = await InstallOptionsFactory.LoadApplicableAsync( pkg, elevated: elevated, interactive: interactive, no_integrity: no_integrity); var op = new InstallPackageOperation(pkg, opts); + op.OperationSucceeded += (_, _) => TelemetryHandler.InstallPackage(pkg, TEL_OP_RESULT.SUCCESS, TEL_InstallReferral.DIRECT_SEARCH); + op.OperationFailed += (_, _) => TelemetryHandler.InstallPackage(pkg, TEL_OP_RESULT.FAILED, TEL_InstallReferral.DIRECT_SEARCH); AvaloniaOperationRegistry.Add(op); _ = op.MainThread(); } diff --git a/src/UniGetUI.Avalonia/Views/DialogPages/PackageDetailsWindow.axaml.cs b/src/UniGetUI.Avalonia/Views/DialogPages/PackageDetailsWindow.axaml.cs index 27b07b8245..fb6a764a4c 100644 --- a/src/UniGetUI.Avalonia/Views/DialogPages/PackageDetailsWindow.axaml.cs +++ b/src/UniGetUI.Avalonia/Views/DialogPages/PackageDetailsWindow.axaml.cs @@ -1,6 +1,7 @@ using Avalonia.Controls; using UniGetUI.Avalonia.Infrastructure; using UniGetUI.Avalonia.ViewModels; +using UniGetUI.Interface.Telemetry; using UniGetUI.PackageEngine.Enums; using UniGetUI.PackageEngine.Interfaces; using UniGetUI.PackageEngine.Operations; @@ -35,6 +36,7 @@ protected override void OnOpened(EventArgs e) { base.OnOpened(e); _ = _vm.LoadDetailsAsync(); + TelemetryHandler.PackageDetails(_vm.Package, _vm.OperationRole.ToString()); } private MenuFlyout BuildActionFlyout() @@ -110,6 +112,22 @@ private async Task LaunchAndClose( _ => throw new ArgumentOutOfRangeException(nameof(role)), }; + switch (role) + { + case OperationType.Install: + op.OperationSucceeded += (_, _) => TelemetryHandler.InstallPackage(pkg, TEL_OP_RESULT.SUCCESS, TEL_InstallReferral.DIRECT_SEARCH); + op.OperationFailed += (_, _) => TelemetryHandler.InstallPackage(pkg, TEL_OP_RESULT.FAILED, TEL_InstallReferral.DIRECT_SEARCH); + break; + case OperationType.Update: + op.OperationSucceeded += (_, _) => TelemetryHandler.UpdatePackage(pkg, TEL_OP_RESULT.SUCCESS); + op.OperationFailed += (_, _) => TelemetryHandler.UpdatePackage(pkg, TEL_OP_RESULT.FAILED); + break; + case OperationType.Uninstall: + op.OperationSucceeded += (_, _) => TelemetryHandler.UninstallPackage(pkg, TEL_OP_RESULT.SUCCESS); + op.OperationFailed += (_, _) => TelemetryHandler.UninstallPackage(pkg, TEL_OP_RESULT.FAILED); + break; + } + AvaloniaOperationRegistry.Add(op); _ = op.MainThread(); } diff --git a/src/UniGetUI.Avalonia/Views/SoftwarePages/InstalledPackagesPage.cs b/src/UniGetUI.Avalonia/Views/SoftwarePages/InstalledPackagesPage.cs index 3855d829c0..46994c76f1 100644 --- a/src/UniGetUI.Avalonia/Views/SoftwarePages/InstalledPackagesPage.cs +++ b/src/UniGetUI.Avalonia/Views/SoftwarePages/InstalledPackagesPage.cs @@ -10,6 +10,7 @@ using UniGetUI.PackageEngine.Classes.Manager.Classes; using UniGetUI.PackageEngine.Enums; using UniGetUI.PackageEngine.Interfaces; +using UniGetUI.Interface.Telemetry; using UniGetUI.PackageEngine.Operations; using UniGetUI.PackageEngine.PackageClasses; using UniGetUI.PackageEngine.PackageLoader; @@ -320,6 +321,8 @@ private static async Task LaunchUninstall( var opts = await InstallOptionsFactory.LoadApplicableAsync( pkg, elevated: elevated, interactive: interactive, remove_data: remove_data); var op = new UninstallPackageOperation(pkg, opts); + op.OperationSucceeded += (_, _) => TelemetryHandler.UninstallPackage(pkg, TEL_OP_RESULT.SUCCESS); + op.OperationFailed += (_, _) => TelemetryHandler.UninstallPackage(pkg, TEL_OP_RESULT.FAILED); AvaloniaOperationRegistry.Add(op); _ = op.MainThread(); } @@ -330,6 +333,8 @@ private static async Task LaunchReinstall(IPackage? package) if (package is null || package.Source.IsVirtualManager) return; var opts = await InstallOptionsFactory.LoadApplicableAsync(package); var op = new InstallPackageOperation(package, opts); + op.OperationSucceeded += (_, _) => TelemetryHandler.InstallPackage(package, TEL_OP_RESULT.SUCCESS, TEL_InstallReferral.ALREADY_INSTALLED); + op.OperationFailed += (_, _) => TelemetryHandler.InstallPackage(package, TEL_OP_RESULT.FAILED, TEL_InstallReferral.ALREADY_INSTALLED); AvaloniaOperationRegistry.Add(op); _ = op.MainThread(); } @@ -340,7 +345,11 @@ private static async Task LaunchUninstallThenReinstall(IPackage? package) var uninstallOpts = await InstallOptionsFactory.LoadApplicableAsync(package); var reinstallOpts = await InstallOptionsFactory.LoadApplicableAsync(package); var uninstallOp = new UninstallPackageOperation(package, uninstallOpts); + uninstallOp.OperationSucceeded += (_, _) => TelemetryHandler.UninstallPackage(package, TEL_OP_RESULT.SUCCESS); + uninstallOp.OperationFailed += (_, _) => TelemetryHandler.UninstallPackage(package, TEL_OP_RESULT.FAILED); var reinstallOp = new InstallPackageOperation(package, reinstallOpts, req: uninstallOp); + reinstallOp.OperationSucceeded += (_, _) => TelemetryHandler.InstallPackage(package, TEL_OP_RESULT.SUCCESS, TEL_InstallReferral.ALREADY_INSTALLED); + reinstallOp.OperationFailed += (_, _) => TelemetryHandler.InstallPackage(package, TEL_OP_RESULT.FAILED, TEL_InstallReferral.ALREADY_INSTALLED); AvaloniaOperationRegistry.Add(uninstallOp); AvaloniaOperationRegistry.Add(reinstallOp); _ = uninstallOp.MainThread(); diff --git a/src/UniGetUI.Avalonia/Views/SoftwarePages/PackageBundlesPage.cs b/src/UniGetUI.Avalonia/Views/SoftwarePages/PackageBundlesPage.cs index 71da3e9245..2821e900be 100644 --- a/src/UniGetUI.Avalonia/Views/SoftwarePages/PackageBundlesPage.cs +++ b/src/UniGetUI.Avalonia/Views/SoftwarePages/PackageBundlesPage.cs @@ -282,6 +282,8 @@ public async Task ImportAndInstallPackage( var opts = await InstallOptionsFactory.LoadApplicableAsync( pkg, elevated: elevated, interactive: interactive, no_integrity: skiphash); var op = new InstallPackageOperation(pkg, opts); + op.OperationSucceeded += (_, _) => TelemetryHandler.InstallPackage(pkg, TEL_OP_RESULT.SUCCESS, TEL_InstallReferral.FROM_BUNDLE); + op.OperationFailed += (_, _) => TelemetryHandler.InstallPackage(pkg, TEL_OP_RESULT.FAILED, TEL_InstallReferral.FROM_BUNDLE); AvaloniaOperationRegistry.Add(op); _ = op.MainThread(); } diff --git a/src/UniGetUI.Avalonia/Views/SoftwarePages/SoftwareUpdatesPage.cs b/src/UniGetUI.Avalonia/Views/SoftwarePages/SoftwareUpdatesPage.cs index abed54b85c..afa76774b9 100644 --- a/src/UniGetUI.Avalonia/Views/SoftwarePages/SoftwareUpdatesPage.cs +++ b/src/UniGetUI.Avalonia/Views/SoftwarePages/SoftwareUpdatesPage.cs @@ -9,6 +9,7 @@ using UniGetUI.PackageEngine.Classes.Packages.Classes; using UniGetUI.PackageEngine.Enums; using UniGetUI.PackageEngine.Interfaces; +using UniGetUI.Interface.Telemetry; using UniGetUI.PackageEngine.Operations; using UniGetUI.PackageEngine.PackageClasses; using UniGetUI.PackageEngine.PackageLoader; @@ -339,6 +340,8 @@ private static async Task LaunchUpdate( var opts = await InstallOptionsFactory.LoadApplicableAsync( pkg, elevated: elevated, interactive: interactive, no_integrity: no_integrity); var op = new UpdatePackageOperation(pkg, opts); + op.OperationSucceeded += (_, _) => TelemetryHandler.UpdatePackage(pkg, TEL_OP_RESULT.SUCCESS); + op.OperationFailed += (_, _) => TelemetryHandler.UpdatePackage(pkg, TEL_OP_RESULT.FAILED); AvaloniaOperationRegistry.Add(op); _ = op.MainThread(); } @@ -350,6 +353,8 @@ private static async Task LaunchUninstallFromUpdates(IEnumerable packa { var opts = await InstallOptionsFactory.LoadApplicableAsync(pkg); var op = new UninstallPackageOperation(pkg, opts); + op.OperationSucceeded += (_, _) => TelemetryHandler.UninstallPackage(pkg, TEL_OP_RESULT.SUCCESS); + op.OperationFailed += (_, _) => TelemetryHandler.UninstallPackage(pkg, TEL_OP_RESULT.FAILED); AvaloniaOperationRegistry.Add(op); _ = op.MainThread(); } @@ -361,7 +366,11 @@ private static async Task LaunchUninstallThenUpdate(IPackage? package) var uninstallOpts = await InstallOptionsFactory.LoadApplicableAsync(package); var updateOpts = await InstallOptionsFactory.LoadApplicableAsync(package); var uninstallOp = new UninstallPackageOperation(package, uninstallOpts); + uninstallOp.OperationSucceeded += (_, _) => TelemetryHandler.UninstallPackage(package, TEL_OP_RESULT.SUCCESS); + uninstallOp.OperationFailed += (_, _) => TelemetryHandler.UninstallPackage(package, TEL_OP_RESULT.FAILED); var updateOp = new UpdatePackageOperation(package, updateOpts, req: uninstallOp); + updateOp.OperationSucceeded += (_, _) => TelemetryHandler.UpdatePackage(package, TEL_OP_RESULT.SUCCESS); + updateOp.OperationFailed += (_, _) => TelemetryHandler.UpdatePackage(package, TEL_OP_RESULT.FAILED); AvaloniaOperationRegistry.Add(uninstallOp); AvaloniaOperationRegistry.Add(updateOp); _ = uninstallOp.MainThread(); From d86cee0ef8640ebff56e1ebc5c30ce67ee2d376d Mon Sep 17 00:00:00 2001 From: Gabriel Dufresne Date: Thu, 2 Apr 2026 13:36:19 -0400 Subject: [PATCH 02/11] telemetry --- .github/workflows/build-release.yml | 4 + src/UniGetUI.Avalonia/App.axaml.cs | 2 +- .../Infrastructure/AvaloniaBootstrapper.cs | 5 +- .../Infrastructure/Secrets.cs | 2 + .../Infrastructure/generate-secrets.ps1 | 6 + .../Infrastructure/generate-secrets.sh | 6 + .../Events/TelemetryEventBase.cs | 67 ++++ .../Events/UniGetUIActivityEvent.cs | 15 + .../Events/UniGetUIBundleEvent.cs | 12 + .../Events/UniGetUIPackageEvent.cs | 24 ++ .../TelemetryHandler.cs | 299 +++++++++++------- src/UniGetUI/App.xaml.cs | 3 + src/UniGetUI/Services/Secrets.cs | 4 + src/UniGetUI/Services/generate-secrets.ps1 | 6 + 14 files changed, 335 insertions(+), 120 deletions(-) create mode 100644 src/UniGetUI.Interface.Telemetry/Events/TelemetryEventBase.cs create mode 100644 src/UniGetUI.Interface.Telemetry/Events/UniGetUIActivityEvent.cs create mode 100644 src/UniGetUI.Interface.Telemetry/Events/UniGetUIBundleEvent.cs create mode 100644 src/UniGetUI.Interface.Telemetry/Events/UniGetUIPackageEvent.cs diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index c1d50d8022..2aa006d89a 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -91,6 +91,8 @@ jobs: env: UNIGETUI_GITHUB_CLIENT_ID: ${{ secrets.UNIGETUI_GITHUB_CLIENT_ID }} UNIGETUI_GITHUB_CLIENT_SECRET: ${{ secrets.UNIGETUI_GITHUB_CLIENT_SECRET }} + UNIGETUI_OPENSEARCH_USERNAME: ${{ secrets.UNIGETUI_OPENSEARCH_USERNAME }} + UNIGETUI_OPENSEARCH_PASSWORD: ${{ secrets.UNIGETUI_OPENSEARCH_PASSWORD }} NUGET_PACKAGES: ${{ github.workspace }}\.nuget\packages strategy: fail-fast: false @@ -318,6 +320,8 @@ jobs: env: UNIGETUI_GITHUB_CLIENT_ID: ${{ secrets.UNIGETUI_GITHUB_CLIENT_ID }} UNIGETUI_GITHUB_CLIENT_SECRET: ${{ secrets.UNIGETUI_GITHUB_CLIENT_SECRET }} + UNIGETUI_OPENSEARCH_USERNAME: ${{ secrets.UNIGETUI_OPENSEARCH_USERNAME }} + UNIGETUI_OPENSEARCH_PASSWORD: ${{ secrets.UNIGETUI_OPENSEARCH_PASSWORD }} NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages strategy: fail-fast: false diff --git a/src/UniGetUI.Avalonia/App.axaml.cs b/src/UniGetUI.Avalonia/App.axaml.cs index 8ea526ca4a..59d9c1431e 100644 --- a/src/UniGetUI.Avalonia/App.axaml.cs +++ b/src/UniGetUI.Avalonia/App.axaml.cs @@ -47,7 +47,7 @@ public override void OnFrameworkInitializationCompleted() ApplyTheme(CoreSettings.GetValue(CoreSettings.K.PreferredTheme)); var mainWindow = new MainWindow(); desktop.MainWindow = mainWindow; - _ = Task.Run(PEInterface.LoadManagers); + _ = AvaloniaBootstrapper.InitializeAsync(); } base.OnFrameworkInitializationCompleted(); diff --git a/src/UniGetUI.Avalonia/Infrastructure/AvaloniaBootstrapper.cs b/src/UniGetUI.Avalonia/Infrastructure/AvaloniaBootstrapper.cs index 60eef1c97f..b8d1876c9a 100644 --- a/src/UniGetUI.Avalonia/Infrastructure/AvaloniaBootstrapper.cs +++ b/src/UniGetUI.Avalonia/Infrastructure/AvaloniaBootstrapper.cs @@ -53,6 +53,9 @@ private static Task InitializeSharedServicesAsync() CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.Default); + TelemetryHandler.Configure( + Secrets.GetOpenSearchUsername(), + Secrets.GetOpenSearchPassword()); _ = TelemetryHandler.InitializeAsync() .ContinueWith( t => Logger.Error(t.Exception!), @@ -82,7 +85,7 @@ private static Task InitializeSharedServicesAsync() private static async Task InitializePackageEngineAsync() { - await Task.Run(PEInterface.LoadLoaders); + // LoadLoaders is called synchronously in App.axaml.cs before MainWindow creation await Task.Run(PEInterface.LoadManagers); } diff --git a/src/UniGetUI.Avalonia/Infrastructure/Secrets.cs b/src/UniGetUI.Avalonia/Infrastructure/Secrets.cs index 2e38224e35..a050b5ca05 100644 --- a/src/UniGetUI.Avalonia/Infrastructure/Secrets.cs +++ b/src/UniGetUI.Avalonia/Infrastructure/Secrets.cs @@ -8,5 +8,7 @@ internal static partial class Secrets * Seeing errors? Build the project (maybe twice) */ public static partial string GetGitHubClientId(); + public static partial string GetOpenSearchUsername(); + public static partial string GetOpenSearchPassword(); /* ------------------------------------------------------------------------ */ } diff --git a/src/UniGetUI.Avalonia/Infrastructure/generate-secrets.ps1 b/src/UniGetUI.Avalonia/Infrastructure/generate-secrets.ps1 index 6860fc41f6..53fc0d0d3b 100644 --- a/src/UniGetUI.Avalonia/Infrastructure/generate-secrets.ps1 +++ b/src/UniGetUI.Avalonia/Infrastructure/generate-secrets.ps1 @@ -12,8 +12,12 @@ if (-not (Test-Path -Path $generatedDir)) { } $clientId = $env:UNIGETUI_GITHUB_CLIENT_ID +$openSearchUsername = $env:UNIGETUI_OPENSEARCH_USERNAME +$openSearchPassword = $env:UNIGETUI_OPENSEARCH_PASSWORD if (-not $clientId) { $clientId = "CLIENT_ID_UNSET" } +if (-not $openSearchUsername) { $openSearchUsername = "OPENSEARCH_USERNAME_UNSET" } +if (-not $openSearchPassword) { $openSearchPassword = "OPENSEARCH_PASSWORD_UNSET" } @" // Auto-generated file - do not modify @@ -22,6 +26,8 @@ namespace UniGetUI.Avalonia.Infrastructure internal static partial class Secrets { public static partial string GetGitHubClientId() => `"$clientId`"; + public static partial string GetOpenSearchUsername() => `"$openSearchUsername`"; + public static partial string GetOpenSearchPassword() => `"$openSearchPassword`"; } } "@ | Set-Content -Encoding UTF8 "Generated Files\Secrets.Generated.cs" diff --git a/src/UniGetUI.Avalonia/Infrastructure/generate-secrets.sh b/src/UniGetUI.Avalonia/Infrastructure/generate-secrets.sh index d70a305f9c..59583f81bb 100755 --- a/src/UniGetUI.Avalonia/Infrastructure/generate-secrets.sh +++ b/src/UniGetUI.Avalonia/Infrastructure/generate-secrets.sh @@ -5,8 +5,12 @@ if [ ! -d "Generated Files" ]; then mkdir -p "Generated Files"; fi if [ ! -d "${OUTPUT_PATH}Generated Files" ]; then mkdir -p "${OUTPUT_PATH}Generated Files"; fi CLIENT_ID="${UNIGETUI_GITHUB_CLIENT_ID}" +OPENSEARCH_USERNAME="${UNIGETUI_OPENSEARCH_USERNAME}" +OPENSEARCH_PASSWORD="${UNIGETUI_OPENSEARCH_PASSWORD}" if [ -z "$CLIENT_ID" ]; then CLIENT_ID="CLIENT_ID_UNSET"; fi +if [ -z "$OPENSEARCH_USERNAME" ]; then OPENSEARCH_USERNAME="OPENSEARCH_USERNAME_UNSET"; fi +if [ -z "$OPENSEARCH_PASSWORD" ]; then OPENSEARCH_PASSWORD="OPENSEARCH_PASSWORD_UNSET"; fi cat > "Generated Files/Secrets.Generated.cs" << CSEOF // Auto-generated file - do not modify @@ -15,6 +19,8 @@ namespace UniGetUI.Avalonia.Infrastructure internal static partial class Secrets { public static partial string GetGitHubClientId() => "$CLIENT_ID"; + public static partial string GetOpenSearchUsername() => "$OPENSEARCH_USERNAME"; + public static partial string GetOpenSearchPassword() => "$OPENSEARCH_PASSWORD"; } } CSEOF diff --git a/src/UniGetUI.Interface.Telemetry/Events/TelemetryEventBase.cs b/src/UniGetUI.Interface.Telemetry/Events/TelemetryEventBase.cs new file mode 100644 index 0000000000..c021458df8 --- /dev/null +++ b/src/UniGetUI.Interface.Telemetry/Events/TelemetryEventBase.cs @@ -0,0 +1,67 @@ +using System.Text.Json.Serialization; + +namespace UniGetUI.Interface.Telemetry; + +/// +/// Common fields shared by all UniGetUI telemetry events. +/// Mirrors the field names expected by the Devolutions OpenSearch schema. +/// +public abstract class TelemetryEventBase +{ + protected TelemetryEventBase() + { + EventID = Guid.NewGuid().ToString(); + EventDate = DateTime.UtcNow; + } + + [JsonPropertyName("eventID")] + public string EventID { get; set; } + + [JsonPropertyName("eventDate")] + public DateTime EventDate { get; set; } + + [JsonPropertyName("installID")] + public required string InstallID { get; set; } + + [JsonPropertyName("locale")] + public string? Locale { get; set; } + + [JsonPropertyName("application")] + public required TelemetryApplicationInfo Application { get; set; } + + [JsonPropertyName("platform")] + public TelemetryPlatformInfo? Platform { get; set; } +} + +public sealed class TelemetryApplicationInfo +{ + [JsonPropertyName("name")] + public required string Name { get; set; } + + [JsonPropertyName("version")] + public required string Version { get; set; } + + [JsonPropertyName("dataSource")] + public required string DataSource { get; set; } + + [JsonPropertyName("pricing")] + public required string Pricing { get; set; } + + [JsonPropertyName("language")] + public string? Language { get; set; } + + [JsonPropertyName("architectureType")] + public string? ArchitectureType { get; set; } +} + +public sealed class TelemetryPlatformInfo +{ + [JsonPropertyName("name")] + public required string Name { get; set; } + + [JsonPropertyName("version")] + public string? Version { get; set; } + + [JsonPropertyName("architecture")] + public string? Architecture { get; set; } +} diff --git a/src/UniGetUI.Interface.Telemetry/Events/UniGetUIActivityEvent.cs b/src/UniGetUI.Interface.Telemetry/Events/UniGetUIActivityEvent.cs new file mode 100644 index 0000000000..7c5154521e --- /dev/null +++ b/src/UniGetUI.Interface.Telemetry/Events/UniGetUIActivityEvent.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace UniGetUI.Interface.Telemetry; + +public sealed class UniGetUIActivityEvent : TelemetryEventBase +{ + [JsonPropertyName("enabledManagers")] + public string[] EnabledManagers { get; set; } = []; + + [JsonPropertyName("foundManagers")] + public string[] FoundManagers { get; set; } = []; + + [JsonPropertyName("activeSettings")] + public int ActiveSettings { get; set; } +} diff --git a/src/UniGetUI.Interface.Telemetry/Events/UniGetUIBundleEvent.cs b/src/UniGetUI.Interface.Telemetry/Events/UniGetUIBundleEvent.cs new file mode 100644 index 0000000000..7d54bc2502 --- /dev/null +++ b/src/UniGetUI.Interface.Telemetry/Events/UniGetUIBundleEvent.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace UniGetUI.Interface.Telemetry; + +public sealed class UniGetUIBundleEvent : TelemetryEventBase +{ + [JsonPropertyName("operation")] + public required string Operation { get; set; } + + [JsonPropertyName("bundleType")] + public required string BundleType { get; set; } +} diff --git a/src/UniGetUI.Interface.Telemetry/Events/UniGetUIPackageEvent.cs b/src/UniGetUI.Interface.Telemetry/Events/UniGetUIPackageEvent.cs new file mode 100644 index 0000000000..7b5f35b1d4 --- /dev/null +++ b/src/UniGetUI.Interface.Telemetry/Events/UniGetUIPackageEvent.cs @@ -0,0 +1,24 @@ +using System.Text.Json.Serialization; + +namespace UniGetUI.Interface.Telemetry; + +public sealed class UniGetUIPackageEvent : TelemetryEventBase +{ + [JsonPropertyName("operation")] + public required string Operation { get; set; } + + [JsonPropertyName("packageId")] + public required string PackageId { get; set; } + + [JsonPropertyName("managerName")] + public required string ManagerName { get; set; } + + [JsonPropertyName("sourceName")] + public required string SourceName { get; set; } + + [JsonPropertyName("operationResult")] + public string? OperationResult { get; set; } + + [JsonPropertyName("eventSource")] + public string? EventSource { get; set; } +} diff --git a/src/UniGetUI.Interface.Telemetry/TelemetryHandler.cs b/src/UniGetUI.Interface.Telemetry/TelemetryHandler.cs index cf17d40aa4..c87062d7e4 100644 --- a/src/UniGetUI.Interface.Telemetry/TelemetryHandler.cs +++ b/src/UniGetUI.Interface.Telemetry/TelemetryHandler.cs @@ -1,3 +1,8 @@ +using System.Net.Http.Headers; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; using UniGetUI.Core.Data; using UniGetUI.Core.Language; using UniGetUI.Core.Logging; @@ -26,11 +31,44 @@ public enum TEL_OP_RESULT public static class TelemetryHandler { -#if DEBUG - private const string HOST = "http://localhost:3000"; -#else - private const string HOST = "https://marticliment.com/unigetui/statistics"; -#endif + private const string OpenSearchUrl = "https://telemetry2.devolutions.net:9200"; + private static string _openSearchUsername = ""; + private static string _openSearchPassword = ""; + + public static void Configure(string username, string password) + { + _openSearchUsername = username; + _openSearchPassword = password; + } + + // Index names — to be created on the OpenSearch server + private const string IndexActivity = "unigetui_activity_events"; + private const string IndexPackage = "unigetui_package_events"; + private const string IndexBundle = "unigetui_bundle_events"; + +// #if DEBUG +// private const string IndexPrefix = "dev-"; +// #else +// private const string IndexPrefix = ""; +// #endif + + private const string IndexPrefix = ""; + + private static readonly JsonSerializerOptions JsonOptions = new() + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }; + + private static readonly HttpClient _httpClient; + + static TelemetryHandler() + { + _httpClient = new HttpClient(CoreTools.GenericHttpClientParameters) + { + Timeout = TimeSpan.FromSeconds(30), + }; + _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(CoreData.UserAgentString); + } private static readonly Settings.K[] SettingsToSend = [ @@ -56,85 +94,67 @@ public static async Task InitializeAsync() { if (Settings.Get(Settings.K.DisableTelemetry)) return; - await CoreTools.WaitForInternetConnection(); - string ID = GetRandomizedId(); - int mask = 0x1; - int ManagerMagicValue = 0; + await CoreTools.WaitForInternetConnection(); - foreach (var manager in PEInterface.Managers) - { - if (manager.IsEnabled()) - ManagerMagicValue |= mask; - mask = mask << 1; - if (manager.IsEnabled() && manager.Status.Found) - ManagerMagicValue |= mask; - mask = mask << 1; + string[] enabledManagers = PEInterface.Managers + .Where(m => m.IsEnabled()) + .Select(m => m.Name) + .ToArray(); - if (mask == 0x1) - throw new OverflowException(); - } + string[] foundManagers = PEInterface.Managers + .Where(m => m.IsEnabled() && m.Status.Found) + .Select(m => m.Name) + .ToArray(); - int SettingsMagicValue = 0; - mask = 0x1; + int settingsMagicValue = 0; + int mask = 0x1; foreach (var setting in SettingsToSend) { bool enabled = Settings.Get( key: setting, - invert: Settings.ResolveKey(setting).StartsWith("Disable") - ); + invert: Settings.ResolveKey(setting).StartsWith("Disable")); if (enabled) - SettingsMagicValue |= mask; - mask = mask << 1; + settingsMagicValue |= mask; + mask <<= 1; if (mask == 0x1) throw new OverflowException(); } - foreach (var setting in new[] { "SP1", "SP2" }) + foreach (var sp in new[] { "SP1", "SP2" }) { - bool enabled; - if (setting == "SP1") - enabled = File.Exists("ForceUniGetUIPortable"); - else if (setting == "SP2") - enabled = CoreData.WasDaemon; - else - throw new NotImplementedException(); + bool enabled = sp switch + { + "SP1" => File.Exists("ForceUniGetUIPortable"), + "SP2" => CoreData.WasDaemon, + _ => throw new NotImplementedException(), + }; if (enabled) - SettingsMagicValue |= mask; - mask = mask << 1; + settingsMagicValue |= mask; + mask <<= 1; if (mask == 0x1) throw new OverflowException(); } - var request = new HttpRequestMessage(HttpMethod.Post, $"{HOST}/activity"); - - request.Headers.Add("clientId", ID); - request.Headers.Add("clientVersion", CoreData.VersionName); - request.Headers.Add("activeManagers", ManagerMagicValue.ToString()); - request.Headers.Add("activeSettings", SettingsMagicValue.ToString()); - request.Headers.Add("language", LanguageEngine.SelectedLocale); - - HttpClient _httpClient = new(CoreTools.GenericHttpClientParameters); - _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(CoreData.UserAgentString); - HttpResponseMessage response = await _httpClient.SendAsync(request); - - if (response.IsSuccessStatusCode) + var ev = new UniGetUIActivityEvent { - Logger.Debug("[Telemetry] Call to /activity succeeded"); - } - else - { - Logger.Warn( - $"[Telemetry] Call to /activity failed with error code {response.StatusCode}" - ); - } + InstallID = GetRandomizedId(), + Locale = LanguageEngine.SelectedLocale, + EnabledManagers = enabledManagers, + FoundManagers = foundManagers, + ActiveSettings = settingsMagicValue, + Application = BuildApplicationInfo(), + Platform = BuildPlatformInfo(), + }; + + await PostToOpenSearchAsync(IndexActivity, ev); } catch (Exception ex) { - Logger.Error("[Telemetry] Hard crash when calling /activity"); + Logger.Error("[Telemetry] Hard crash in InitializeAsync"); Logger.Error(ex); } } @@ -145,32 +165,31 @@ public static void InstallPackage( IPackage package, TEL_OP_RESULT status, TEL_InstallReferral source - ) => PackageEndpoint(package, "install", status, source.ToString()); + ) => _ = TrackPackageEventAsync(package, "install", status, source.ToString()); public static void UpdatePackage(IPackage package, TEL_OP_RESULT status) => - PackageEndpoint(package, "update", status); + _ = TrackPackageEventAsync(package, "update", status); public static void DownloadPackage( IPackage package, TEL_OP_RESULT status, TEL_InstallReferral source - ) => PackageEndpoint(package, "download", status, source.ToString()); + ) => _ = TrackPackageEventAsync(package, "download", status, source.ToString()); public static void UninstallPackage(IPackage package, TEL_OP_RESULT status) => - PackageEndpoint(package, "uninstall", status); + _ = TrackPackageEventAsync(package, "uninstall", status); public static void PackageDetails(IPackage package, string eventSource) => - PackageEndpoint(package, "details", eventSource: eventSource); + _ = TrackPackageEventAsync(package, "details", eventSource: eventSource); public static void SharedPackage(IPackage package, string eventSource) => - PackageEndpoint(package, "share", eventSource: eventSource); + _ = TrackPackageEventAsync(package, "share", eventSource: eventSource); - private static async void PackageEndpoint( + private static async Task TrackPackageEventAsync( IPackage package, - string endpoint, + string operation, TEL_OP_RESULT? result = null, - string? eventSource = null - ) + string? eventSource = null) { try { @@ -178,39 +197,28 @@ private static async void PackageEndpoint( throw new ArgumentException("result and eventSource cannot both be null"); if (Settings.Get(Settings.K.DisableTelemetry)) return; + await CoreTools.WaitForInternetConnection(); - string ID = GetRandomizedId(); - - var request = new HttpRequestMessage(HttpMethod.Post, $"{HOST}/package/{endpoint}"); - - request.Headers.Add("clientId", ID); - request.Headers.Add("clientVersion", CoreData.VersionName); - request.Headers.Add("packageId", package.Id); - request.Headers.Add("managerName", package.Manager.Name); - request.Headers.Add("sourceName", package.Source.Name); - if (result is not null) - request.Headers.Add("operationResult", result.ToString()); - if (eventSource is not null) - request.Headers.Add("eventSource", eventSource); - - HttpClient _httpClient = new(CoreTools.GenericHttpClientParameters); - _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(CoreData.UserAgentString); - HttpResponseMessage response = await _httpClient.SendAsync(request); - if (response.IsSuccessStatusCode) - { - Logger.Debug($"[Telemetry] Call to /package/{endpoint} succeeded"); - } - else + var ev = new UniGetUIPackageEvent { - Logger.Warn( - $"[Telemetry] Call to /package/{endpoint} failed with error code {response.StatusCode}" - ); - } + InstallID = GetRandomizedId(), + Locale = LanguageEngine.SelectedLocale, + Application = BuildApplicationInfo(), + Platform = BuildPlatformInfo(), + Operation = operation, + PackageId = package.Id, + ManagerName = package.Manager.Name, + SourceName = package.Source.Name, + OperationResult = result?.ToString(), + EventSource = eventSource, + }; + + await PostToOpenSearchAsync(IndexPackage, ev); } catch (Exception ex) { - Logger.Error($"[Telemetry] Hard crash when calling /package/{endpoint}"); + Logger.Error($"[Telemetry] Hard crash in TrackPackageEventAsync ({operation})"); Logger.Error(ex); } } @@ -218,59 +226,114 @@ private static async void PackageEndpoint( // ------------------------------------------------------------------------- public static void ImportBundle(BundleFormatType type) => - BundlesEndpoint("import", type.ToString()); + _ = TrackBundleEventAsync("import", type.ToString()); public static void ExportBundle(BundleFormatType type) => - BundlesEndpoint("export", type.ToString()); + _ = TrackBundleEventAsync("export", type.ToString()); - public static void ExportBatch() => BundlesEndpoint("export", "PS1_SCRIPT"); + public static void ExportBatch() => + _ = TrackBundleEventAsync("export", "PS1_SCRIPT"); - private static async void BundlesEndpoint(string endpoint, string type) + private static async Task TrackBundleEventAsync(string operation, string bundleType) { try { if (Settings.Get(Settings.K.DisableTelemetry)) return; + await CoreTools.WaitForInternetConnection(); - string ID = GetRandomizedId(); - var request = new HttpRequestMessage(HttpMethod.Post, $"{HOST}/bundles/{endpoint}"); + var ev = new UniGetUIBundleEvent + { + InstallID = GetRandomizedId(), + Locale = LanguageEngine.SelectedLocale, + Application = BuildApplicationInfo(), + Platform = BuildPlatformInfo(), + Operation = operation, + BundleType = bundleType, + }; + + await PostToOpenSearchAsync(IndexBundle, ev); + } + catch (Exception ex) + { + Logger.Error($"[Telemetry] Hard crash in TrackBundleEventAsync ({operation})"); + Logger.Error(ex); + } + } + + // ─── OpenSearch HTTP ────────────────────────────────────────────────────── + + private static async Task PostToOpenSearchAsync(string indexName, T eventData) + { + try + { + string fullIndex = IndexPrefix + indexName; + string json = JsonSerializer.Serialize(eventData, JsonOptions); + + string credentials = Convert.ToBase64String( + Encoding.UTF8.GetBytes($"{_openSearchUsername}:{_openSearchPassword}")); - request.Headers.Add("clientId", ID); - request.Headers.Add("clientVersion", CoreData.VersionName); - request.Headers.Add("bundleType", type); + using var content = new StringContent(json, Encoding.UTF8, "application/json"); + using var request = new HttpRequestMessage(HttpMethod.Post, $"{OpenSearchUrl}/{fullIndex}/_doc") + { + Content = content, + }; + request.Headers.Authorization = new AuthenticationHeaderValue("Basic", credentials); - HttpClient _httpClient = new(CoreTools.GenericHttpClientParameters); - _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(CoreData.UserAgentString); HttpResponseMessage response = await _httpClient.SendAsync(request); if (response.IsSuccessStatusCode) - { - Logger.Debug($"[Telemetry] Call to /bundles/{endpoint} succeeded"); - } + Logger.Debug($"[Telemetry] Sent to {fullIndex}"); else { - Logger.Warn( - $"[Telemetry] Call to /bundles/{endpoint} failed with error code {response.StatusCode}" - ); + string body = await response.Content.ReadAsStringAsync(); + Logger.Warn($"[Telemetry] {fullIndex} returned {(int)response.StatusCode}: {body}"); } } catch (Exception ex) { - Logger.Error($"[Telemetry] Hard crash when calling /bundles/{endpoint}"); + Logger.Error($"[Telemetry] Hard crash posting to {indexName}"); Logger.Error(ex); } } + // ─── Helpers ───────────────────────────────────────────────────────────── + private static string GetRandomizedId() { - string ID = Settings.GetValue(Settings.K.TelemetryClientToken); - if (ID.Length != 64) + string id = Settings.GetValue(Settings.K.TelemetryClientToken); + if (id.Length != 64) { - ID = CoreTools.RandomString(64); - Settings.SetValue(Settings.K.TelemetryClientToken, ID); + id = CoreTools.RandomString(64); + Settings.SetValue(Settings.K.TelemetryClientToken, id); } + return id; + } - return ID; + private static TelemetryApplicationInfo BuildApplicationInfo() => + new() + { + Name = "UniGetUI", + Version = CoreData.VersionName, + DataSource = "NotApplicable", + Pricing = "Free", + Language = LanguageEngine.SelectedLocale, + ArchitectureType = RuntimeInformation.ProcessArchitecture.ToString(), + }; + + private static TelemetryPlatformInfo BuildPlatformInfo() => + new() + { + Name = GetPlatformName(), + Version = Environment.OSVersion.VersionString, + Architecture = RuntimeInformation.OSArchitecture.ToString(), + }; + + private static string GetPlatformName() + { + if (OperatingSystem.IsWindows()) return "Windows"; + if (OperatingSystem.IsMacOS()) return "Mac"; + return "Linux"; } } diff --git a/src/UniGetUI/App.xaml.cs b/src/UniGetUI/App.xaml.cs index dfb70bb391..e94f0cdf88 100644 --- a/src/UniGetUI/App.xaml.cs +++ b/src/UniGetUI/App.xaml.cs @@ -346,6 +346,9 @@ private async Task LoadComponentsAsync() await Task.WhenAll(iniTasks); // Load non-essential components + TelemetryHandler.Configure( + Secrets.GetOpenSearchUsername(), + Secrets.GetOpenSearchPassword()); _ = TelemetryHandler.InitializeAsync(); _ = IconDatabase.Instance.LoadIconAndScreenshotsDatabaseAsync(); diff --git a/src/UniGetUI/Services/Secrets.cs b/src/UniGetUI/Services/Secrets.cs index 675ecab94e..fd77d1af9b 100644 --- a/src/UniGetUI/Services/Secrets.cs +++ b/src/UniGetUI/Services/Secrets.cs @@ -10,6 +10,10 @@ internal static partial class Secrets public static partial string GetGitHubClientId(); public static partial string GetGitHubClientSecret(); + + public static partial string GetOpenSearchUsername(); + + public static partial string GetOpenSearchPassword(); /* ------------------------------------------------------------------------ */ } } diff --git a/src/UniGetUI/Services/generate-secrets.ps1 b/src/UniGetUI/Services/generate-secrets.ps1 index c6ce3180a5..8681eef882 100644 --- a/src/UniGetUI/Services/generate-secrets.ps1 +++ b/src/UniGetUI/Services/generate-secrets.ps1 @@ -14,9 +14,13 @@ if (-not (Test-Path -Path "Generated Files")) { $clientId = $env:UNIGETUI_GITHUB_CLIENT_ID $clientSecret = $env:UNIGETUI_GITHUB_CLIENT_SECRET +$openSearchUsername = $env:UNIGETUI_OPENSEARCH_USERNAME +$openSearchPassword = $env:UNIGETUI_OPENSEARCH_PASSWORD if (-not $clientId) { $clientId = "CLIENT_ID_UNSET" } if (-not $clientSecret) { $clientSecret = "CLIENT_SECRET_UNSET" } +if (-not $openSearchUsername) { $openSearchUsername = "OPENSEARCH_USERNAME_UNSET" } +if (-not $openSearchPassword) { $openSearchPassword = "OPENSEARCH_PASSWORD_UNSET" } @" // Auto-generated file - do not modify @@ -26,6 +30,8 @@ namespace UniGetUI.Services { public static partial string GetGitHubClientId() => `"$clientId`"; public static partial string GetGitHubClientSecret() => `"$clientSecret`"; + public static partial string GetOpenSearchUsername() => `"$openSearchUsername`"; + public static partial string GetOpenSearchPassword() => `"$openSearchPassword`"; } } "@ | Set-Content -Encoding UTF8 "Generated Files\Secrets.Generated.cs" From a99d681334a0a07488c08460980b5a3303d53c1f Mon Sep 17 00:00:00 2001 From: GabrielDuf Date: Wed, 8 Apr 2026 13:14:49 -0400 Subject: [PATCH 03/11] Fix for windows --- .../TelemetryHandler.cs | 23 +++++++++++-------- src/UniGetUI/App.xaml.cs | 1 + 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/UniGetUI.Interface.Telemetry/TelemetryHandler.cs b/src/UniGetUI.Interface.Telemetry/TelemetryHandler.cs index c87062d7e4..5f64a8a093 100644 --- a/src/UniGetUI.Interface.Telemetry/TelemetryHandler.cs +++ b/src/UniGetUI.Interface.Telemetry/TelemetryHandler.cs @@ -3,6 +3,7 @@ using System.Text; using System.Text.Json; using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; using UniGetUI.Core.Data; using UniGetUI.Core.Language; using UniGetUI.Core.Logging; @@ -54,10 +55,6 @@ public static void Configure(string username, string password) private const string IndexPrefix = ""; - private static readonly JsonSerializerOptions JsonOptions = new() - { - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - }; private static readonly HttpClient _httpClient; @@ -150,7 +147,7 @@ public static async Task InitializeAsync() Platform = BuildPlatformInfo(), }; - await PostToOpenSearchAsync(IndexActivity, ev); + await PostToOpenSearchAsync(IndexActivity, ev, TelemetrySerializerContext.Default.UniGetUIActivityEvent); } catch (Exception ex) { @@ -214,7 +211,7 @@ private static async Task TrackPackageEventAsync( EventSource = eventSource, }; - await PostToOpenSearchAsync(IndexPackage, ev); + await PostToOpenSearchAsync(IndexPackage, ev, TelemetrySerializerContext.Default.UniGetUIPackageEvent); } catch (Exception ex) { @@ -253,7 +250,7 @@ private static async Task TrackBundleEventAsync(string operation, string bundleT BundleType = bundleType, }; - await PostToOpenSearchAsync(IndexBundle, ev); + await PostToOpenSearchAsync(IndexBundle, ev, TelemetrySerializerContext.Default.UniGetUIBundleEvent); } catch (Exception ex) { @@ -264,12 +261,12 @@ private static async Task TrackBundleEventAsync(string operation, string bundleT // ─── OpenSearch HTTP ────────────────────────────────────────────────────── - private static async Task PostToOpenSearchAsync(string indexName, T eventData) + private static async Task PostToOpenSearchAsync(string indexName, T eventData, JsonTypeInfo typeInfo) { try { string fullIndex = IndexPrefix + indexName; - string json = JsonSerializer.Serialize(eventData, JsonOptions); + string json = JsonSerializer.Serialize(eventData, typeInfo); string credentials = Convert.ToBase64String( Encoding.UTF8.GetBytes($"{_openSearchUsername}:{_openSearchPassword}")); @@ -337,3 +334,11 @@ private static string GetPlatformName() return "Linux"; } } + +// Source-generated JSON context — required for AOT/trimmed builds (WinUI). +// Reflection-based serialization is disabled in that configuration. +[JsonSourceGenerationOptions(DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(UniGetUIActivityEvent))] +[JsonSerializable(typeof(UniGetUIPackageEvent))] +[JsonSerializable(typeof(UniGetUIBundleEvent))] +internal partial class TelemetrySerializerContext : JsonSerializerContext { } diff --git a/src/UniGetUI/App.xaml.cs b/src/UniGetUI/App.xaml.cs index e94f0cdf88..70e5c49875 100644 --- a/src/UniGetUI/App.xaml.cs +++ b/src/UniGetUI/App.xaml.cs @@ -19,6 +19,7 @@ using UniGetUI.PackageEngine.Interfaces; using UniGetUI.Pages.DialogPages; using Windows.ApplicationModel.Activation; +using UniGetUI.Services; using LaunchActivatedEventArgs = Microsoft.UI.Xaml.LaunchActivatedEventArgs; namespace UniGetUI From 64c4c459d2152b44884aba8a8586811d71201eeb Mon Sep 17 00:00:00 2001 From: Gabriel Dufresne Date: Wed, 8 Apr 2026 13:37:33 -0400 Subject: [PATCH 04/11] revert comment --- src/UniGetUI.Interface.Telemetry/TelemetryHandler.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/UniGetUI.Interface.Telemetry/TelemetryHandler.cs b/src/UniGetUI.Interface.Telemetry/TelemetryHandler.cs index 5f64a8a093..24db94ac3e 100644 --- a/src/UniGetUI.Interface.Telemetry/TelemetryHandler.cs +++ b/src/UniGetUI.Interface.Telemetry/TelemetryHandler.cs @@ -47,13 +47,11 @@ public static void Configure(string username, string password) private const string IndexPackage = "unigetui_package_events"; private const string IndexBundle = "unigetui_bundle_events"; -// #if DEBUG -// private const string IndexPrefix = "dev-"; -// #else -// private const string IndexPrefix = ""; -// #endif - +#if DEBUG + private const string IndexPrefix = "dev-"; +#else private const string IndexPrefix = ""; +#endif private static readonly HttpClient _httpClient; From 4d2a8e3fa946a36b5c80b04447c499ecf73f4399 Mon Sep 17 00:00:00 2001 From: Gabriel Dufresne Date: Wed, 8 Apr 2026 13:57:21 -0400 Subject: [PATCH 05/11] whiteSpace --- src/UniGetUI.Interface.Telemetry/Events/TelemetryEventBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UniGetUI.Interface.Telemetry/Events/TelemetryEventBase.cs b/src/UniGetUI.Interface.Telemetry/Events/TelemetryEventBase.cs index c021458df8..731fb28a23 100644 --- a/src/UniGetUI.Interface.Telemetry/Events/TelemetryEventBase.cs +++ b/src/UniGetUI.Interface.Telemetry/Events/TelemetryEventBase.cs @@ -10,7 +10,7 @@ public abstract class TelemetryEventBase { protected TelemetryEventBase() { - EventID = Guid.NewGuid().ToString(); + EventID = Guid.NewGuid().ToString(); EventDate = DateTime.UtcNow; } From ba9b40ae05de6a22bad0c7808338e2eb51b8d0a4 Mon Sep 17 00:00:00 2001 From: Gabriel Dufresne Date: Wed, 8 Apr 2026 14:18:16 -0400 Subject: [PATCH 06/11] made some change mentioned by copilot --- .../TelemetryHandler.cs | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/UniGetUI.Interface.Telemetry/TelemetryHandler.cs b/src/UniGetUI.Interface.Telemetry/TelemetryHandler.cs index 24db94ac3e..0ad1cbed4d 100644 --- a/src/UniGetUI.Interface.Telemetry/TelemetryHandler.cs +++ b/src/UniGetUI.Interface.Telemetry/TelemetryHandler.cs @@ -35,6 +35,7 @@ public static class TelemetryHandler private const string OpenSearchUrl = "https://telemetry2.devolutions.net:9200"; private static string _openSearchUsername = ""; private static string _openSearchPassword = ""; + private static bool _credentialsWarningLogged; public static void Configure(string username, string password) { @@ -42,6 +43,23 @@ public static void Configure(string username, string password) _openSearchPassword = password; } + private static bool CredentialsConfigured() + { + if (!string.IsNullOrEmpty(_openSearchUsername) + && !_openSearchUsername.EndsWith("_UNSET") + && !string.IsNullOrEmpty(_openSearchPassword) + && !_openSearchPassword.EndsWith("_UNSET")) + return true; + + if (!_credentialsWarningLogged) + { + Logger.Warn("[Telemetry] OpenSearch credentials are not configured — telemetry is disabled for this build."); + _credentialsWarningLogged = true; + } + + return false; + } + // Index names — to be created on the OpenSearch server private const string IndexActivity = "unigetui_activity_events"; private const string IndexPackage = "unigetui_package_events"; @@ -261,6 +279,9 @@ private static async Task TrackBundleEventAsync(string operation, string bundleT private static async Task PostToOpenSearchAsync(string indexName, T eventData, JsonTypeInfo typeInfo) { + if (!CredentialsConfigured()) + return; + try { string fullIndex = IndexPrefix + indexName; @@ -281,10 +302,7 @@ private static async Task PostToOpenSearchAsync(string indexName, T eventData if (response.IsSuccessStatusCode) Logger.Debug($"[Telemetry] Sent to {fullIndex}"); else - { - string body = await response.Content.ReadAsStringAsync(); - Logger.Warn($"[Telemetry] {fullIndex} returned {(int)response.StatusCode}: {body}"); - } + Logger.Warn($"[Telemetry] {fullIndex} returned {(int)response.StatusCode}"); } catch (Exception ex) { From c77edeef6d16a88f3f736fc272f15e0747c19af9 Mon Sep 17 00:00:00 2001 From: Gabriel Dufresne Date: Wed, 8 Apr 2026 14:29:12 -0400 Subject: [PATCH 07/11] fix wrong using order --- src/UniGetUI/App.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UniGetUI/App.xaml.cs b/src/UniGetUI/App.xaml.cs index 70e5c49875..970f5826cb 100644 --- a/src/UniGetUI/App.xaml.cs +++ b/src/UniGetUI/App.xaml.cs @@ -18,8 +18,8 @@ using UniGetUI.PackageEngine.Classes.Manager.Classes; using UniGetUI.PackageEngine.Interfaces; using UniGetUI.Pages.DialogPages; -using Windows.ApplicationModel.Activation; using UniGetUI.Services; +using Windows.ApplicationModel.Activation; using LaunchActivatedEventArgs = Microsoft.UI.Xaml.LaunchActivatedEventArgs; namespace UniGetUI From c3b515d556a9eb6db14447149fc30f11894826e1 Mon Sep 17 00:00:00 2001 From: Gabriel Dufresne Date: Wed, 8 Apr 2026 14:37:00 -0400 Subject: [PATCH 08/11] fixed multiple line --- src/UniGetUI.Interface.Telemetry/TelemetryHandler.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/UniGetUI.Interface.Telemetry/TelemetryHandler.cs b/src/UniGetUI.Interface.Telemetry/TelemetryHandler.cs index 0ad1cbed4d..6c6be93599 100644 --- a/src/UniGetUI.Interface.Telemetry/TelemetryHandler.cs +++ b/src/UniGetUI.Interface.Telemetry/TelemetryHandler.cs @@ -71,7 +71,6 @@ private static bool CredentialsConfigured() private const string IndexPrefix = ""; #endif - private static readonly HttpClient _httpClient; static TelemetryHandler() From d9d69750637c4d111a1f9095b49e5abf61321ce3 Mon Sep 17 00:00:00 2001 From: Gabriel Dufresne Date: Wed, 8 Apr 2026 14:44:29 -0400 Subject: [PATCH 09/11] fix wrong order of using --- .../ViewModels/SoftwarePages/PackagesPageViewModel.cs | 2 +- .../Views/SoftwarePages/InstalledPackagesPage.cs | 2 +- .../Views/SoftwarePages/SoftwareUpdatesPage.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/UniGetUI.Avalonia/ViewModels/SoftwarePages/PackagesPageViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/SoftwarePages/PackagesPageViewModel.cs index 5e08fc8a00..6af392f7ee 100644 --- a/src/UniGetUI.Avalonia/ViewModels/SoftwarePages/PackagesPageViewModel.cs +++ b/src/UniGetUI.Avalonia/ViewModels/SoftwarePages/PackagesPageViewModel.cs @@ -15,9 +15,9 @@ using UniGetUI.Avalonia.Views.Controls; using UniGetUI.Core.SettingsEngine; using UniGetUI.Core.Tools; +using UniGetUI.Interface.Telemetry; using UniGetUI.PackageEngine.Enums; using UniGetUI.PackageEngine.Interfaces; -using UniGetUI.Interface.Telemetry; using UniGetUI.PackageEngine.Operations; using UniGetUI.PackageEngine.PackageClasses; using UniGetUI.PackageEngine.PackageLoader; diff --git a/src/UniGetUI.Avalonia/Views/SoftwarePages/InstalledPackagesPage.cs b/src/UniGetUI.Avalonia/Views/SoftwarePages/InstalledPackagesPage.cs index 46994c76f1..f4bee10a36 100644 --- a/src/UniGetUI.Avalonia/Views/SoftwarePages/InstalledPackagesPage.cs +++ b/src/UniGetUI.Avalonia/Views/SoftwarePages/InstalledPackagesPage.cs @@ -9,8 +9,8 @@ using UniGetUI.Core.Tools; using UniGetUI.PackageEngine.Classes.Manager.Classes; using UniGetUI.PackageEngine.Enums; -using UniGetUI.PackageEngine.Interfaces; using UniGetUI.Interface.Telemetry; +using UniGetUI.PackageEngine.Interfaces; using UniGetUI.PackageEngine.Operations; using UniGetUI.PackageEngine.PackageClasses; using UniGetUI.PackageEngine.PackageLoader; diff --git a/src/UniGetUI.Avalonia/Views/SoftwarePages/SoftwareUpdatesPage.cs b/src/UniGetUI.Avalonia/Views/SoftwarePages/SoftwareUpdatesPage.cs index afa76774b9..b19831764d 100644 --- a/src/UniGetUI.Avalonia/Views/SoftwarePages/SoftwareUpdatesPage.cs +++ b/src/UniGetUI.Avalonia/Views/SoftwarePages/SoftwareUpdatesPage.cs @@ -8,8 +8,8 @@ using UniGetUI.PackageEngine.Classes.Manager.Classes; using UniGetUI.PackageEngine.Classes.Packages.Classes; using UniGetUI.PackageEngine.Enums; -using UniGetUI.PackageEngine.Interfaces; using UniGetUI.Interface.Telemetry; +using UniGetUI.PackageEngine.Interfaces; using UniGetUI.PackageEngine.Operations; using UniGetUI.PackageEngine.PackageClasses; using UniGetUI.PackageEngine.PackageLoader; From 10aaefe852baabd4a619095a6d5ffb0e919215b1 Mon Sep 17 00:00:00 2001 From: Gabriel Dufresne Date: Wed, 8 Apr 2026 14:50:59 -0400 Subject: [PATCH 10/11] wip --- .../Views/SoftwarePages/InstalledPackagesPage.cs | 2 +- .../Views/SoftwarePages/SoftwareUpdatesPage.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/UniGetUI.Avalonia/Views/SoftwarePages/InstalledPackagesPage.cs b/src/UniGetUI.Avalonia/Views/SoftwarePages/InstalledPackagesPage.cs index f4bee10a36..04b28814c3 100644 --- a/src/UniGetUI.Avalonia/Views/SoftwarePages/InstalledPackagesPage.cs +++ b/src/UniGetUI.Avalonia/Views/SoftwarePages/InstalledPackagesPage.cs @@ -7,9 +7,9 @@ using UniGetUI.Core.Logging; using UniGetUI.Core.SettingsEngine; using UniGetUI.Core.Tools; +using UniGetUI.Interface.Telemetry; using UniGetUI.PackageEngine.Classes.Manager.Classes; using UniGetUI.PackageEngine.Enums; -using UniGetUI.Interface.Telemetry; using UniGetUI.PackageEngine.Interfaces; using UniGetUI.PackageEngine.Operations; using UniGetUI.PackageEngine.PackageClasses; diff --git a/src/UniGetUI.Avalonia/Views/SoftwarePages/SoftwareUpdatesPage.cs b/src/UniGetUI.Avalonia/Views/SoftwarePages/SoftwareUpdatesPage.cs index b19831764d..d9c01a396a 100644 --- a/src/UniGetUI.Avalonia/Views/SoftwarePages/SoftwareUpdatesPage.cs +++ b/src/UniGetUI.Avalonia/Views/SoftwarePages/SoftwareUpdatesPage.cs @@ -5,10 +5,10 @@ using UniGetUI.Avalonia.Views; using UniGetUI.Core.Logging; using UniGetUI.Core.Tools; +using UniGetUI.Interface.Telemetry; using UniGetUI.PackageEngine.Classes.Manager.Classes; using UniGetUI.PackageEngine.Classes.Packages.Classes; using UniGetUI.PackageEngine.Enums; -using UniGetUI.Interface.Telemetry; using UniGetUI.PackageEngine.Interfaces; using UniGetUI.PackageEngine.Operations; using UniGetUI.PackageEngine.PackageClasses; From 5a55b15165102352209ff9063c5be5d2abf89e7f Mon Sep 17 00:00:00 2001 From: Gabriel Dufresne Date: Wed, 8 Apr 2026 15:27:31 -0400 Subject: [PATCH 11/11] missing SerializationHelpers.DefaultOptions --- .../TelemetryHandler.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/UniGetUI.Interface.Telemetry/TelemetryHandler.cs b/src/UniGetUI.Interface.Telemetry/TelemetryHandler.cs index 6c6be93599..a63caa4ab5 100644 --- a/src/UniGetUI.Interface.Telemetry/TelemetryHandler.cs +++ b/src/UniGetUI.Interface.Telemetry/TelemetryHandler.cs @@ -162,7 +162,7 @@ public static async Task InitializeAsync() Platform = BuildPlatformInfo(), }; - await PostToOpenSearchAsync(IndexActivity, ev, TelemetrySerializerContext.Default.UniGetUIActivityEvent); + await PostToOpenSearchAsync(IndexActivity, ev, TelemetrySerializerContext.Trimming.UniGetUIActivityEvent); } catch (Exception ex) { @@ -226,7 +226,7 @@ private static async Task TrackPackageEventAsync( EventSource = eventSource, }; - await PostToOpenSearchAsync(IndexPackage, ev, TelemetrySerializerContext.Default.UniGetUIPackageEvent); + await PostToOpenSearchAsync(IndexPackage, ev, TelemetrySerializerContext.Trimming.UniGetUIPackageEvent); } catch (Exception ex) { @@ -265,7 +265,7 @@ private static async Task TrackBundleEventAsync(string operation, string bundleT BundleType = bundleType, }; - await PostToOpenSearchAsync(IndexBundle, ev, TelemetrySerializerContext.Default.UniGetUIBundleEvent); + await PostToOpenSearchAsync(IndexBundle, ev, TelemetrySerializerContext.Trimming.UniGetUIBundleEvent); } catch (Exception ex) { @@ -356,4 +356,11 @@ private static string GetPlatformName() [JsonSerializable(typeof(UniGetUIActivityEvent))] [JsonSerializable(typeof(UniGetUIPackageEvent))] [JsonSerializable(typeof(UniGetUIBundleEvent))] -internal partial class TelemetrySerializerContext : JsonSerializerContext { } +internal partial class TelemetrySerializerContext : JsonSerializerContext +{ + internal static readonly TelemetrySerializerContext Trimming = + new(new JsonSerializerOptions(SerializationHelpers.DefaultOptions) + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }); +}