From cfcdc9c98d94c61271ff07c769e8ff23ec3089f6 Mon Sep 17 00:00:00 2001 From: Rasmus John Pedersen Date: Mon, 6 May 2024 13:14:11 +0200 Subject: [PATCH] Webhook log improvements (#16200) * fix: include all headers in webhook log * feat: return webhook log status from server * feat: make webhook logs deep linkable * feat: add webhook log pagination * feat: improve webhook request/response body preview --- src/Umbraco.Core/Models/WebhookLog.cs | 2 ++ .../Services/WebhookLogFactory.cs | 5 ++-- .../Factories/WebhookLogFactory.cs | 4 ++- .../Mapping/WebhookMapDefinition.cs | 1 + .../Models/WebhookLogViewModel.cs | 3 +++ .../src/views/webhooks/logs.controller.js | 26 +++++++++++++++---- .../src/views/webhooks/logs.html | 23 +++++++++++----- .../webhooks/overlays/details.controller.js | 18 ++++++++++++- .../src/views/webhooks/overlays/details.html | 14 +++++----- .../src/views/webhooks/overview.controller.js | 10 +++---- 10 files changed, 80 insertions(+), 26 deletions(-) diff --git a/src/Umbraco.Core/Models/WebhookLog.cs b/src/Umbraco.Core/Models/WebhookLog.cs index aba3b15713..5340b0f738 100644 --- a/src/Umbraco.Core/Models/WebhookLog.cs +++ b/src/Umbraco.Core/Models/WebhookLog.cs @@ -27,4 +27,6 @@ public class WebhookLog public string ResponseBody { get; set; } = string.Empty; public bool ExceptionOccured { get; set; } + + public bool IsSuccessStatusCode { get; set; } } diff --git a/src/Umbraco.Core/Services/WebhookLogFactory.cs b/src/Umbraco.Core/Services/WebhookLogFactory.cs index 2c80f15e27..3c260c017f 100644 --- a/src/Umbraco.Core/Services/WebhookLogFactory.cs +++ b/src/Umbraco.Core/Services/WebhookLogFactory.cs @@ -16,7 +16,7 @@ public class WebhookLogFactory : IWebhookLogFactory Url = webhook.Url, WebhookKey = webhook.Key, RetryCount = retryCount, - RequestHeaders = requestMessage.Headers.ToString(), + RequestHeaders = $"{requestMessage.Content?.Headers}{requestMessage.Headers}", RequestBody = await requestMessage.Content?.ReadAsStringAsync(cancellationToken)!, ExceptionOccured = exception is not null, }; @@ -24,7 +24,8 @@ public class WebhookLogFactory : IWebhookLogFactory if (httpResponseMessage is not null) { log.StatusCode = MapStatusCodeToMessage(httpResponseMessage.StatusCode); - log.ResponseHeaders = httpResponseMessage.Headers.ToString(); + log.IsSuccessStatusCode = httpResponseMessage.IsSuccessStatusCode; + log.ResponseHeaders = $"{httpResponseMessage.Content.Headers}{httpResponseMessage.Headers}"; log.ResponseBody = await httpResponseMessage.Content.ReadAsStringAsync(cancellationToken); } else if (exception is HttpRequestException httpRequestException) diff --git a/src/Umbraco.Infrastructure/Persistence/Factories/WebhookLogFactory.cs b/src/Umbraco.Infrastructure/Persistence/Factories/WebhookLogFactory.cs index f060525624..886ee58eaf 100644 --- a/src/Umbraco.Infrastructure/Persistence/Factories/WebhookLogFactory.cs +++ b/src/Umbraco.Infrastructure/Persistence/Factories/WebhookLogFactory.cs @@ -1,4 +1,5 @@ -using Umbraco.Cms.Core.Models; +using System.Text.RegularExpressions; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Webhooks; using Umbraco.Cms.Infrastructure.Persistence.Dtos; @@ -33,6 +34,7 @@ internal static class WebhookLogFactory ResponseBody = dto.ResponseBody, RetryCount = dto.RetryCount, StatusCode = dto.StatusCode, + IsSuccessStatusCode = Regex.IsMatch(dto.StatusCode, "^.*\\(2(\\d{2})\\)$"), Key = dto.Key, Id = dto.Id, Url = dto.Url, diff --git a/src/Umbraco.Web.BackOffice/Mapping/WebhookMapDefinition.cs b/src/Umbraco.Web.BackOffice/Mapping/WebhookMapDefinition.cs index 1d1558026c..017004e266 100644 --- a/src/Umbraco.Web.BackOffice/Mapping/WebhookMapDefinition.cs +++ b/src/Umbraco.Web.BackOffice/Mapping/WebhookMapDefinition.cs @@ -67,6 +67,7 @@ public class WebhookMapDefinition : IMapDefinition target.Url = source.Url; target.RequestHeaders = source.RequestHeaders; target.WebhookKey = source.WebhookKey; + target.IsSuccessStatusCode = source.IsSuccessStatusCode; if (_hostingEnvironment.IsDebugMode) { diff --git a/src/Umbraco.Web.Common/Models/WebhookLogViewModel.cs b/src/Umbraco.Web.Common/Models/WebhookLogViewModel.cs index 0b3ff552a7..4518c77637 100644 --- a/src/Umbraco.Web.Common/Models/WebhookLogViewModel.cs +++ b/src/Umbraco.Web.Common/Models/WebhookLogViewModel.cs @@ -14,6 +14,9 @@ public class WebhookLogViewModel [DataMember(Name = "statusCode")] public string StatusCode { get; set; } = string.Empty; + [DataMember(Name = "isSuccessStatusCode")] + public bool IsSuccessStatusCode { get; set; } + [DataMember(Name = "date")] public DateTime Date { get; set; } diff --git a/src/Umbraco.Web.UI.Client/src/views/webhooks/logs.controller.js b/src/Umbraco.Web.UI.Client/src/views/webhooks/logs.controller.js index c4d032b806..5afa4fd00d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/webhooks/logs.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/webhooks/logs.controller.js @@ -5,9 +5,13 @@ const vm = this; + vm.pagination = { + pageNumber: 1, + pageSize: 25 + }; + vm.logs = []; vm.openLogOverlay = openLogOverlay; - vm.isChecked = isChecked; function init() { vm.loading = true; @@ -22,9 +26,14 @@ } function loadLogs() { - return webhooksResource.getLogs() + const take = vm.pagination.pageSize; + const skip = (vm.pagination.pageNumber - 1) * take; + + return webhooksResource.getLogs(skip, take) .then(data => { vm.logs = data.items; + vm.pagination.totalPages = Math.ceil(data.totalItems/vm.pagination.pageSize); + vm.logs.forEach(log => { formatDatesToLocal(log); }); @@ -54,9 +63,16 @@ editorService.open(dialog); } - function isChecked(log) { - return log.statusCode === "OK (200)"; - } + vm.previousPage = () => vm.goToPage(vm.pagination.pageNumber - 1); + vm.nextPage = () => vm.goToPage(vm.pagination.pageNumber + 1); + + vm.goToPage = (pageNumber) => { + vm.pagination.pageNumber = pageNumber; + vm.loading = true; + loadLogs().then(() => { + vm.loading = false; + }); + }; init(); } diff --git a/src/Umbraco.Web.UI.Client/src/views/webhooks/logs.html b/src/Umbraco.Web.UI.Client/src/views/webhooks/logs.html index 3a92c04a56..c6857f1eb1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/webhooks/logs.html +++ b/src/Umbraco.Web.UI.Client/src/views/webhooks/logs.html @@ -3,7 +3,6 @@ - Webhook key Date Url Event @@ -14,13 +13,13 @@ + ng-if="log.isSuccessStatusCode" + checked="true" + size="m" + title="{{ log.statusCode }}"> - + - {{ log.webhookKey }} {{ log.formattedLogDate }} {{ log.url }} {{ log.eventAlias }} @@ -28,4 +27,16 @@ + + + There are no items show in the list. + + + + diff --git a/src/Umbraco.Web.UI.Client/src/views/webhooks/overlays/details.controller.js b/src/Umbraco.Web.UI.Client/src/views/webhooks/overlays/details.controller.js index 39d25c0205..b7940ac657 100644 --- a/src/Umbraco.Web.UI.Client/src/views/webhooks/overlays/details.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/webhooks/overlays/details.controller.js @@ -5,6 +5,7 @@ vm.close = close; vm.formatData = formatData; + vm.detectLanguage = detectLanguage; function formatData(data) { @@ -12,7 +13,7 @@ if (data.detectIsJson()) { try { - obj = Utilities.fromJson(data) + obj = JSON.stringify(Utilities.fromJson(data), null, 2); } catch (err) { obj = data; } @@ -21,6 +22,21 @@ return obj; } + function detectLanguage(headers, defaultLanguage) { + const matches = headers.match(/^Content-Type:\s*(?[a-z\/+.-]+)(\;?.*?)$/mi) + if (matches) { + const contentType = matches.groups["type"]; + if (contentType === "application/json") + return "JSON"; + if (contentType === "text/html") + return "HTML"; + if (contentType === "application/xml" || contentType === "text/xml") + return "XML"; + } + + return defaultLanguage || "TEXT"; + } + function close() { if ($scope.model && $scope.model.close) { $scope.model.close(); 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 1c07398dba..8654f8f47b 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 @@ -21,9 +21,9 @@
+ ng-if="model.log.isSuccessStatusCode"> + ng-if="!model.log.isSuccessStatusCode">
{{model.log.statusCode}}
@@ -45,6 +45,10 @@
{{model.log.retryCount}}
+ +
{{model.log.webhookKey}}
+
+ @@ -53,16 +57,14 @@
{{model.log.requestHeaders}}
- {{vm.formatData(model.log.requestBody) | json}} - + {{vm.formatData(model.log.requestBody)}}
{{model.log.responseHeaders}}
- {{vm.formatData(model.log.responseBody) | json}} - + {{vm.formatData(model.log.responseBody)}}
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 a8dcf8658b..7dc99f84a9 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 @@ -10,7 +10,7 @@ vm.page.name = ""; vm.page.navigation = []; - let webhookUri = $routeParams.method; + let webhookUri = $routeParams.id; onInit(); @@ -33,8 +33,8 @@ { "name": vm.page.labels.webhooks, "icon": "icon-webhook", - "view": "views/webhooks/webhooks.html", - "active": webhookUri === 'overview', + "view": !webhookUri ? "views/webhooks/webhooks.html" : null, + "active": !webhookUri, "alias": "umbWebhooks", "action": function () { $location.path("/settings/webhooks/overview"); @@ -43,11 +43,11 @@ { "name": vm.page.labels.logs, "icon": "icon-box-alt", - "view": "views/webhooks/logs.html", + "view": webhookUri === 'logs' ? "views/webhooks/logs.html" : null, "active": webhookUri === 'logs', "alias": "umbWebhookLogs", "action": function () { - $location.path("/settings/webhooks/overview"); + $location.path("/settings/webhooks/overview/logs"); } } ];