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 fe1238cb0f..2c80f15e27 100644 --- a/src/Umbraco.Core/Services/WebhookLogFactory.cs +++ b/src/Umbraco.Core/Services/WebhookLogFactory.cs @@ -6,7 +6,7 @@ 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,34 @@ public class WebhookLogFactory : IWebhookLogFactory Key = Guid.NewGuid(), Url = webhook.Url, WebhookKey = webhook.Key, - RetryCount = responseModel.RetryCount, + 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) { - if (responseModel.HttpResponseMessage.RequestMessage?.Content is not null) + 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.RequestBody = await responseModel.HttpResponseMessage.RequestMessage.Content.ReadAsStringAsync(cancellationToken); - log.RequestHeaders = CalculateHeaders(responseModel.HttpResponseMessage); + log.StatusCode = MapStatusCodeToMessage(httpRequestException.StatusCode.Value); + } + else + { + log.StatusCode = httpRequestException.HttpRequestError.ToString(); } - log.ResponseBody = await responseModel.HttpResponseMessage.Content.ReadAsStringAsync(cancellationToken); - log.ResponseHeaders = responseModel.HttpResponseMessage.Headers.ToString(); - log.StatusCode = MapStatusCodeToMessage(responseModel.HttpResponseMessage.StatusCode); + log.ResponseBody = $"{httpRequestException.HttpRequestError}: {httpRequestException.Message}"; + } + else if (exception is not null) + { + log.ResponseBody = exception.Message; } return log; diff --git a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs index 3d16960144..bda15fd389 100644 --- a/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs +++ b/src/Umbraco.Infrastructure/BackgroundJobs/Jobs/WebhookFiring.cs @@ -97,6 +97,7 @@ public class WebhookFiring : IRecurringBackgroundJob }; HttpResponseMessage? response = null; + Exception? exception = null; try { // Add headers @@ -117,16 +118,11 @@ public class WebhookFiring : IRecurringBackgroundJob } catch (Exception ex) { + 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 74be791185..3503a85b57 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -104,5 +104,6 @@ public class UmbracoPlan : MigrationPlan 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/Persistence/Dtos/WebhookLogDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookLogDto.cs index 27adcee54f..8e409cd0b3 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookLogDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/WebhookLogDto.cs @@ -61,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/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.UI.Client/src/views/webhooks/overlays/details.html b/src/Umbraco.Web.UI.Client/src/views/webhooks/overlays/details.html index def552114a..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,69 +1,71 @@ -
+
+ 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}} +
-
-
+
@@ -78,15 +80,15 @@ - - - - + + + +