diff --git a/Directory.Packages.props b/Directory.Packages.props index 0f28210de3..7279fa21c0 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,17 +2,14 @@ true - true - - + - @@ -37,16 +34,14 @@ - - - + @@ -83,14 +78,15 @@ - - + - + - - + + + + diff --git a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Umbraco.Cms.Persistence.EFCore.SqlServer.csproj b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Umbraco.Cms.Persistence.EFCore.SqlServer.csproj index 5229f513a2..7e6fc6153d 100644 --- a/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Umbraco.Cms.Persistence.EFCore.SqlServer.csproj +++ b/src/Umbraco.Cms.Persistence.EFCore.SqlServer/Umbraco.Cms.Persistence.EFCore.SqlServer.csproj @@ -5,6 +5,8 @@ + + diff --git a/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj b/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj index 39f035407b..4500c2812b 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj +++ b/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj @@ -5,6 +5,8 @@ + + diff --git a/src/Umbraco.Cms.Persistence.SqlServer/Umbraco.Cms.Persistence.SqlServer.csproj b/src/Umbraco.Cms.Persistence.SqlServer/Umbraco.Cms.Persistence.SqlServer.csproj index 0de13a39b6..75e2a6fe60 100644 --- a/src/Umbraco.Cms.Persistence.SqlServer/Umbraco.Cms.Persistence.SqlServer.csproj +++ b/src/Umbraco.Cms.Persistence.SqlServer/Umbraco.Cms.Persistence.SqlServer.csproj @@ -5,6 +5,8 @@ + + diff --git a/src/Umbraco.Core/Constants-HttpClients.cs b/src/Umbraco.Core/Constants-HttpClients.cs index 677f442085..a9c693f39d 100644 --- a/src/Umbraco.Core/Constants-HttpClients.cs +++ b/src/Umbraco.Core/Constants-HttpClients.cs @@ -14,5 +14,18 @@ public static partial class Constants /// Name for http client which ignores certificate errors. /// public const string IgnoreCertificateErrors = "Umbraco:HttpClients:IgnoreCertificateErrors"; + + /// + /// Name for http client which sends webhook requests. + /// + public const string WebhookFiring = "Umbraco:HttpClients:WebhookFiring"; + + public static class Headers + { + /// + /// User agent name for the product name. + /// + public const string UserAgentProductName = "Umbraco-Cms"; + } } } diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml index 19edc6f1ba..1a69568c79 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml @@ -1013,7 +1013,7 @@ Tilføj dokument type Tilføj medie Type Opret header - Logs + Leverancer Der er ikke tilføjet nogen webhook headers diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml index 35ccb53a3b..3249a25668 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml @@ -2001,7 +2001,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Add Document Type Add Media Type Create header - Logs + Deliveries No webhook headers have been added No events were found. Enabled diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml index 6428955197..2697098a71 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml @@ -2022,7 +2022,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Add Document Type Add Media Type Create header - Logs + Deliveries No webhook headers have been added No events were found. Enabled diff --git a/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs b/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs index 9385dcf6c9..59d0f171ef 100644 --- a/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs +++ b/src/Umbraco.Core/Media/EmbedProviders/OEmbedProviderBase.cs @@ -55,7 +55,7 @@ public abstract class OEmbedProviderBase : IEmbedProvider if (_httpClient == null) { _httpClient = new HttpClient(); - _httpClient.DefaultRequestHeaders.UserAgent.TryParseAdd("Umbraco-CMS"); + _httpClient.DefaultRequestHeaders.UserAgent.TryParseAdd(Constants.HttpClients.Headers.UserAgentProductName); } using (var request = new HttpRequestMessage(HttpMethod.Get, url)) diff --git a/src/Umbraco.Core/Models/WebhookLog.cs b/src/Umbraco.Core/Models/WebhookLog.cs index e65abdf990..aba3b15713 100644 --- a/src/Umbraco.Core/Models/WebhookLog.cs +++ b/src/Umbraco.Core/Models/WebhookLog.cs @@ -25,4 +25,6 @@ public class WebhookLog public string ResponseHeaders { get; set; } = string.Empty; public string ResponseBody { get; set; } = string.Empty; + + public bool ExceptionOccured { get; set; } } diff --git a/src/Umbraco.Core/Models/WebhookResponseModel.cs b/src/Umbraco.Core/Models/WebhookResponseModel.cs deleted file mode 100644 index 1f40443806..0000000000 --- a/src/Umbraco.Core/Models/WebhookResponseModel.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Umbraco.Cms.Core.Models; - -public class WebhookResponseModel -{ - public HttpResponseMessage? HttpResponseMessage { get; set; } - - public int RetryCount { get; set; } -} diff --git a/src/Umbraco.Core/Services/IWebhookLogFactory.cs b/src/Umbraco.Core/Services/IWebhookLogFactory.cs index feaa12ef4a..87b43ab5ab 100644 --- a/src/Umbraco.Core/Services/IWebhookLogFactory.cs +++ b/src/Umbraco.Core/Services/IWebhookLogFactory.cs @@ -1,9 +1,15 @@ using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Webhooks; namespace Umbraco.Cms.Core.Services; public interface IWebhookLogFactory { - Task CreateAsync(string eventAlias, WebhookResponseModel responseModel, IWebhook webhook, CancellationToken cancellationToken); + Task CreateAsync( + string eventAlias, + HttpRequestMessage requestMessage, + HttpResponseMessage? httpResponseMessage, + int retryCount, + Exception? exception, + IWebhook webhook, + CancellationToken cancellationToken); } diff --git a/src/Umbraco.Core/Services/WebhookLogFactory.cs b/src/Umbraco.Core/Services/WebhookLogFactory.cs index ec88bb52c4..2c80f15e27 100644 --- a/src/Umbraco.Core/Services/WebhookLogFactory.cs +++ b/src/Umbraco.Core/Services/WebhookLogFactory.cs @@ -1,12 +1,12 @@ using System.Net; +using System.Text; using Umbraco.Cms.Core.Models; -using Umbraco.Cms.Core.Webhooks; namespace Umbraco.Cms.Core.Services; public class WebhookLogFactory : IWebhookLogFactory { - public async Task CreateAsync(string eventAlias, WebhookResponseModel responseModel, IWebhook webhook, CancellationToken cancellationToken) + public async Task CreateAsync(string eventAlias, HttpRequestMessage requestMessage, HttpResponseMessage? httpResponseMessage, int retryCount, Exception? exception, IWebhook webhook, CancellationToken cancellationToken) { var log = new WebhookLog { @@ -15,20 +15,52 @@ public class WebhookLogFactory : IWebhookLogFactory Key = Guid.NewGuid(), Url = webhook.Url, WebhookKey = webhook.Key, + RetryCount = retryCount, + RequestHeaders = requestMessage.Headers.ToString(), + RequestBody = await requestMessage.Content?.ReadAsStringAsync(cancellationToken)!, + ExceptionOccured = exception is not null, }; - if (responseModel.HttpResponseMessage is not null) + if (httpResponseMessage is not null) { - log.RequestBody = await responseModel.HttpResponseMessage!.RequestMessage!.Content!.ReadAsStringAsync(cancellationToken); - log.ResponseBody = await responseModel.HttpResponseMessage.Content.ReadAsStringAsync(cancellationToken); - log.StatusCode = MapStatusCodeToMessage(responseModel.HttpResponseMessage.StatusCode); - log.RetryCount = responseModel.RetryCount; - log.ResponseHeaders = responseModel.HttpResponseMessage.Headers.ToString(); - log.RequestHeaders = responseModel.HttpResponseMessage.RequestMessage.Headers.ToString(); + log.StatusCode = MapStatusCodeToMessage(httpResponseMessage.StatusCode); + log.ResponseHeaders = httpResponseMessage.Headers.ToString(); + log.ResponseBody = await httpResponseMessage.Content.ReadAsStringAsync(cancellationToken); + } + else if (exception is HttpRequestException httpRequestException) + { + if (httpRequestException.StatusCode is not null) + { + log.StatusCode = MapStatusCodeToMessage(httpRequestException.StatusCode.Value); + } + else + { + log.StatusCode = httpRequestException.HttpRequestError.ToString(); + } + + log.ResponseBody = $"{httpRequestException.HttpRequestError}: {httpRequestException.Message}"; + } + else if (exception is not null) + { + log.ResponseBody = exception.Message; } return log; } private string MapStatusCodeToMessage(HttpStatusCode statusCode) => $"{statusCode.ToString()} ({(int)statusCode})"; + + private string CalculateHeaders(HttpResponseMessage responseMessage) + { + IEnumerable>> headers = responseMessage.RequestMessage!.Headers.Concat(responseMessage.RequestMessage.Content!.Headers); + + var result = new StringBuilder(); + + foreach (KeyValuePair> header in headers) + { + result.AppendLine($"{header.Key}: {string.Join(", ", header.Value)}\n"); + } + + return result.ToString(); + } } diff --git a/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj b/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj index 76349f9671..7e28eb6971 100644 --- a/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj +++ b/src/Umbraco.Examine.Lucene/Umbraco.Examine.Lucene.csproj @@ -8,6 +8,8 @@ + + diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs index 11eaa3bb8a..bda15fd389 100644 --- a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs +++ b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs @@ -6,7 +6,6 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs; @@ -15,11 +14,11 @@ public class WebhookFiring : IRecurringBackgroundJob { private readonly ILogger _logger; private readonly IWebhookRequestService _webhookRequestService; - private readonly IJsonSerializer _jsonSerializer; private readonly IWebhookLogFactory _webhookLogFactory; private readonly IWebhookLogService _webhookLogService; private readonly IWebhookService _webHookService; private readonly ICoreScopeProvider _coreScopeProvider; + private readonly IHttpClientFactory _httpClientFactory; private WebhookSettings _webhookSettings; public TimeSpan Period => _webhookSettings.Period; @@ -32,20 +31,20 @@ public class WebhookFiring : IRecurringBackgroundJob public WebhookFiring( ILogger logger, IWebhookRequestService webhookRequestService, - IJsonSerializer jsonSerializer, IWebhookLogFactory webhookLogFactory, IWebhookLogService webhookLogService, IWebhookService webHookService, IOptionsMonitor webhookSettings, - ICoreScopeProvider coreScopeProvider) + ICoreScopeProvider coreScopeProvider, + IHttpClientFactory httpClientFactory) { _logger = logger; _webhookRequestService = webhookRequestService; - _jsonSerializer = jsonSerializer; _webhookLogFactory = webhookLogFactory; _webhookLogService = webhookLogService; _webHookService = webHookService; _coreScopeProvider = coreScopeProvider; + _httpClientFactory = httpClientFactory; _webhookSettings = webhookSettings.CurrentValue; webhookSettings.OnChange(x => _webhookSettings = x); } @@ -72,8 +71,7 @@ public class WebhookFiring : IRecurringBackgroundJob return; } - HttpResponseMessage? response = await SendRequestAsync(webhook, request.EventAlias, request.RequestObject, request.RetryCount, CancellationToken.None); - + using HttpResponseMessage? response = await SendRequestAsync(webhook, request.EventAlias, request.RequestObject, request.RetryCount, CancellationToken.None); if ((response?.IsSuccessStatusCode ?? false) || request.RetryCount >= _webhookSettings.MaximumRetries) { await _webhookRequestService.DeleteAsync(request); @@ -90,33 +88,41 @@ public class WebhookFiring : IRecurringBackgroundJob private async Task SendRequestAsync(IWebhook webhook, string eventName, string? serializedObject, int retryCount, CancellationToken cancellationToken) { - using var httpClient = new HttpClient(); + using HttpClient httpClient = _httpClientFactory.CreateClient(Constants.HttpClients.WebhookFiring); - var stringContent = new StringContent(serializedObject ?? string.Empty, Encoding.UTF8, MediaTypeNames.Application.Json); - stringContent.Headers.TryAddWithoutValidation("Umb-Webhook-Event", eventName); - - foreach (KeyValuePair header in webhook.Headers) + using var request = new HttpRequestMessage(HttpMethod.Post, webhook.Url) { - stringContent.Headers.TryAddWithoutValidation(header.Key, header.Value); - } + Version = httpClient.DefaultRequestVersion, + VersionPolicy = httpClient.DefaultVersionPolicy, + }; HttpResponseMessage? response = null; + Exception? exception = null; try { - response = await httpClient.PostAsync(webhook.Url, stringContent, cancellationToken); + // Add headers + request.Headers.Add("Umb-Webhook-Event", eventName); + request.Headers.Add("Umb-Webhook-RetryCount", retryCount.ToString()); + request.Headers.Add("Umb-Webhook-Date", DateTime.Now.ToString("R")); + + foreach (KeyValuePair header in webhook.Headers) + { + request.Headers.Add(header.Key, header.Value); + } + + // Set content + request.Content = new StringContent(serializedObject ?? string.Empty, Encoding.UTF8, MediaTypeNames.Application.Json); + + // Send request + response = await httpClient.SendAsync(request, cancellationToken); } catch (Exception ex) { - _logger.LogError(ex, "Error while sending webhook request for webhook {WebhookKey}.", webhook); + exception = ex; + _logger.LogError(ex, "Error while sending webhook request for webhook {WebhookKey}.", webhook.Key); } - var webhookResponseModel = new WebhookResponseModel - { - HttpResponseMessage = response, - RetryCount = retryCount, - }; - - WebhookLog log = await _webhookLogFactory.CreateAsync(eventName, webhookResponseModel, webhook, cancellationToken); + WebhookLog log = await _webhookLogFactory.CreateAsync(eventName, request, response, retryCount, exception, webhook, cancellationToken); await _webhookLogService.CreateAsync(log); return response; diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index 3cacfd3eac..3503a85b57 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -103,5 +103,7 @@ public class UmbracoPlan : MigrationPlan To("{23BA95A4-FCCE-49B0-8AA1-45312B103A9B}"); To("{7DDCE198-9CA4-430C-8BBC-A66D80CA209F}"); To("{F74CDA0C-7AAA-48C8-94C6-C6EC3C06F599}"); + To("{21C42760-5109-4C03-AB4F-7EA53577D1F5}"); + To("{6158F3A3-4902-4201-835E-1ED7F810B2D8}"); } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_13_0_0/AddExceptionOccured.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_13_0_0/AddExceptionOccured.cs new file mode 100644 index 0000000000..2ef666867b --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_13_0_0/AddExceptionOccured.cs @@ -0,0 +1,25 @@ +using Umbraco.Cms.Core; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; + +namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_13_0_0; + +public class AddExceptionOccured : MigrationBase +{ + public AddExceptionOccured(IMigrationContext context) : base(context) + { + } + + protected override void Migrate() + { + if (ColumnExists(Constants.DatabaseSchema.Tables.WebhookLog, "exceptionOccured") == false) + { + // Use a custom SQL query to prevent selecting explicit columns (sortOrder doesn't exist yet) + List webhookLogDtos = Database.Fetch($"SELECT * FROM {Constants.DatabaseSchema.Tables.WebhookLog}"); + + Delete.Table(Constants.DatabaseSchema.Tables.WebhookLog).Do(); + Create.Table().Do(); + + Database.InsertBatch(webhookLogDtos); + } + } +} diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_13_0_0/ChangeWebhookUrlColumnsToNvarcharMax.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_13_0_0/ChangeWebhookUrlColumnsToNvarcharMax.cs new file mode 100644 index 0000000000..1e24cb9497 --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_13_0_0/ChangeWebhookUrlColumnsToNvarcharMax.cs @@ -0,0 +1,89 @@ +using System.Linq.Expressions; +using System.Text; +using NPoco; +using Umbraco.Cms.Core; +using Umbraco.Cms.Infrastructure.Migrations.Expressions.Create.Column; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; + +namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_13_0_0; + +public class ChangeWebhookUrlColumnsToNvarcharMax : MigrationBase +{ + public ChangeWebhookUrlColumnsToNvarcharMax(IMigrationContext context) : base(context) + { + } + + protected override void Migrate() + { + // We don't need to run this migration for SQLite, since ntext is not a thing there, text is just text. + if (DatabaseType == DatabaseType.SQLite) + { + return; + } + + MigrateNtextColumn("url", Constants.DatabaseSchema.Tables.Webhook, x => x.Url); + MigrateNtextColumn("url", Constants.DatabaseSchema.Tables.WebhookLog, x => x.Url); + } + + private void MigrateNtextColumn(string columnName, string tableName, Expression> fieldSelector, bool nullable = true) + { + var columnType = ColumnType(tableName, columnName); + if (columnType is null || columnType.Equals("nvarchar", StringComparison.InvariantCultureIgnoreCase) is false) + { + return; + } + + var oldColumnName = $"Old{columnName}"; + + // Rename the column so we can create the new one and copy over the data. + Rename + .Column(columnName) + .OnTable(tableName) + .To(oldColumnName) + .Do(); + + // Create new column with the correct type + // This is pretty ugly, but we have to do ti this way because the CacheInstruction.Instruction column doesn't support nullable. + // So we have to populate with some temporary placeholder value before we copy over the actual data. + ICreateColumnOptionBuilder builder = Create + .Column(columnName) + .OnTable(tableName) + .AsCustom("nvarchar(max)"); + + if (nullable is false) + { + builder + .NotNullable() + .WithDefaultValue("Placeholder"); + } + else + { + builder.Nullable(); + } + + builder.Do(); + + // Copy over data NPOCO doesn't support this for some reason, so we'll have to do it like so + // While we're add it we'll also set all the old values to be NULL since it's recommended here: + // https://learn.microsoft.com/en-us/sql/t-sql/data-types/ntext-text-and-image-transact-sql?view=sql-server-ver16#remarks + StringBuilder queryBuilder = new StringBuilder() + .AppendLine($"UPDATE {tableName}") + .AppendLine("SET") + .Append($"\t{SqlSyntax.GetFieldNameForUpdate(fieldSelector)} = {SqlSyntax.GetQuotedTableName(tableName)}.{SqlSyntax.GetQuotedColumnName(oldColumnName)}"); + + if (nullable) + { + queryBuilder.AppendLine($"\n,\t{SqlSyntax.GetQuotedColumnName(oldColumnName)} = NULL"); + } + + Sql copyDataQuery = Database.SqlContext.Sql(queryBuilder.ToString()); + Database.Execute(copyDataQuery); + + // Delete old column + Delete + .Column(oldColumnName) + .FromTable(tableName) + .Do(); + } +} diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookDto.cs index abcf160b03..2fb0d13555 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookDto.cs @@ -19,6 +19,7 @@ internal class WebhookDto public Guid Key { get; set; } [Column(Name = "url")] + [SpecialDbType(SpecialDbTypes.NVARCHARMAX)] [NullSetting(NullSetting = NullSettings.NotNull)] public string Url { get; set; } = string.Empty; diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookLogDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookLogDto.cs index f98226248e..8e409cd0b3 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookLogDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookLogDto.cs @@ -30,6 +30,7 @@ internal class WebhookLogDto public DateTime Date { get; set; } [Column(Name = "url")] + [SpecialDbType(SpecialDbTypes.NVARCHARMAX)] [NullSetting(NullSetting = NullSettings.NotNull)] public string Url { get; set; } = string.Empty; @@ -60,4 +61,7 @@ internal class WebhookLogDto [SpecialDbType(SpecialDbTypes.NVARCHARMAX)] [NullSetting(NullSetting = NullSettings.NotNull)] public string ResponseBody { get; set; } = string.Empty; + + [Column(Name = "exceptionOccured")] + public bool ExceptionOccured { get; set; } } diff --git a/src/Umbraco.Infrastructure/Persistence/Factories/WebhookLogFactory.cs b/src/Umbraco.Infrastructure/Persistence/Factories/WebhookLogFactory.cs index ff1378ed2d..f060525624 100644 --- a/src/Umbraco.Infrastructure/Persistence/Factories/WebhookLogFactory.cs +++ b/src/Umbraco.Infrastructure/Persistence/Factories/WebhookLogFactory.cs @@ -21,6 +21,7 @@ internal static class WebhookLogFactory RequestHeaders = log.RequestHeaders, ResponseHeaders = log.ResponseHeaders, WebhookKey = log.WebhookKey, + ExceptionOccured = log.ExceptionOccured, }; public static WebhookLog DtoToEntity(WebhookLogDto dto) => @@ -38,5 +39,6 @@ internal static class WebhookLogFactory RequestHeaders = dto.RequestHeaders, ResponseHeaders = dto.ResponseHeaders, WebhookKey = dto.WebhookKey, + ExceptionOccured = dto.ExceptionOccured, }; } diff --git a/src/Umbraco.Web.BackOffice/Mapping/WebhookMapDefinition.cs b/src/Umbraco.Web.BackOffice/Mapping/WebhookMapDefinition.cs index 197a06278c..a7564fb6ac 100644 --- a/src/Umbraco.Web.BackOffice/Mapping/WebhookMapDefinition.cs +++ b/src/Umbraco.Web.BackOffice/Mapping/WebhookMapDefinition.cs @@ -47,5 +47,6 @@ public class WebhookMapDefinition : IMapDefinition target.RequestHeaders = source.RequestHeaders; target.ResponseHeaders = source.ResponseHeaders; target.WebhookKey = source.WebhookKey; + target.ExceptionOccured = source.ExceptionOccured; } } diff --git a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs index 90c6e52671..794dbfba52 100644 --- a/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/DependencyInjection/UmbracoBuilderExtensions.cs @@ -1,4 +1,5 @@ using System.Data.Common; +using System.Net.Http.Headers; using System.Reflection; using Dazinator.Extensions.FileProviders.GlobPatternFilter; using Microsoft.AspNetCore.Builder; @@ -10,7 +11,6 @@ using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Serilog.Extensions.Logging; @@ -23,6 +23,7 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.Blocks; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Diagnostics; @@ -260,6 +261,11 @@ public static partial class UmbracoBuilderExtensions ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator, }); + builder.Services.AddHttpClient(Constants.HttpClients.WebhookFiring, (services, client) => + { + var productVersion = services.GetRequiredService().SemanticVersion.ToSemanticStringWithoutBuild(); + client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(Constants.HttpClients.Headers.UserAgentProductName, productVersion)); + }); return builder; } diff --git a/src/Umbraco.Web.Common/Models/WebhookLogViewModel.cs b/src/Umbraco.Web.Common/Models/WebhookLogViewModel.cs index f63282b7cb..0b3ff552a7 100644 --- a/src/Umbraco.Web.Common/Models/WebhookLogViewModel.cs +++ b/src/Umbraco.Web.Common/Models/WebhookLogViewModel.cs @@ -37,4 +37,7 @@ public class WebhookLogViewModel [DataMember(Name = "responseBody")] public string ResponseBody { get; set; } = string.Empty; + + [DataMember(Name = "exceptionOccured")] + public bool ExceptionOccured { get; set; } } diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj index c7e259d283..02a039a6db 100644 --- a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj +++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj @@ -20,6 +20,10 @@ + + + + diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js index 8bf8eecf91..5cf975bf8e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbdataformatter.service.js @@ -295,13 +295,13 @@ saveModel.memberGroups = _.keys(_.pick(prop.value, value => value === true)); break; case '_umb_approved': - saveModel.isApproved = prop.value; + saveModel.isApproved = prop.value == true; break; case '_umb_lockedOut': - saveModel.isLockedOut = prop.value; + saveModel.isLockedOut = prop.value == true; break; case '_umb_twoFactorEnabled': - saveModel.isTwoFactorEnabled = prop.value; + saveModel.isTwoFactorEnabled = prop.value == true; break; } } diff --git a/src/Umbraco.Web.UI.Client/src/views/webhooks/overlays/details.html b/src/Umbraco.Web.UI.Client/src/views/webhooks/overlays/details.html index 4e60ed2d63..1c07398dba 100644 --- a/src/Umbraco.Web.UI.Client/src/views/webhooks/overlays/details.html +++ b/src/Umbraco.Web.UI.Client/src/views/webhooks/overlays/details.html @@ -1,79 +1,94 @@ -
+
+ name="model.title" + name-locked="true" + hide-alias="true" + hide-icon="true" + hide-description="true"> - - - + + + - -
-
- - -
-
{{model.log.statusCode}}
-
-
+ +
+
+ + +
+
{{model.log.statusCode}}
+
+
- -
{{model.log.formattedLogDate}}
-
+ +
{{model.log.formattedLogDate}}
+
- -
{{model.log.url}}
-
+ +
{{model.log.url}}
+
- -
{{model.log.eventAlias}}
-
+ +
{{model.log.eventAlias}}
+
- -
{{model.log.retryCount}}
-
+ +
{{model.log.retryCount}}
+
-
+
-
- - - - -
{{model.log.requestHeaders}}
- {{vm.formatData(model.log.requestBody) | json}} -
-
+ + + + +
{{model.log.requestHeaders}}
+ {{vm.formatData(model.log.requestBody) | json}} + +
+
{{model.log.responseHeaders}}
- {{vm.formatData(model.log.responseBody) | json}} + {{vm.formatData(model.log.responseBody) | json}} +
+ +
- - - - + + + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/webhooks/overview.controller.js b/src/Umbraco.Web.UI.Client/src/views/webhooks/overview.controller.js index 790b199d71..a8dcf8658b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/webhooks/overview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/webhooks/overview.controller.js @@ -23,7 +23,7 @@ function loadNavigation() { - const labelKeys = ["treeHeaders_webhooks", "webhooks_logs"]; + const labelKeys = ["treeHeaders_webhooks", "webhooks_deliveries"]; localizationService.localizeMany(labelKeys).then(data => { vm.page.labels.webhooks = data[0]; diff --git a/tests/Directory.Packages.props b/tests/Directory.Packages.props index a8031cc500..1ecbee80a5 100644 --- a/tests/Directory.Packages.props +++ b/tests/Directory.Packages.props @@ -4,7 +4,7 @@ - + @@ -22,4 +22,4 @@ - + \ No newline at end of file