{"results":{"result":{"added-files":{"code-health":9.058998042590874,"old-code-health":0.0,"files":[{"file":"src/Umbraco.Core/Services/LogViewerServiceBase.cs","loc":186,"code-health":8.816158827775617},{"file":"src/Umbraco.Infrastructure/Services/Implement/LogViewerRepositoryBase.cs","loc":48,"code-health":10.0}]},"external-review-url":"https://github.com/umbraco/Umbraco-CMS/pull/19818","old-code-health":9.156608931368696,"modified-files":{"code-health":9.294366313040348,"old-code-health":9.156608931368696,"files":[{"file":"src/Umbraco.Infrastructure/Logging/Serilog/LoggerConfigExtensions.cs","loc":186,"old-loc":183,"code-health":8.545379580978913,"old-code-health":8.545379580978913},{"file":"src/Umbraco.Infrastructure/Logging/Serilog/SerilogLogger.cs","loc":109,"old-loc":109,"code-health":9.6882083290695,"old-code-health":9.6882083290695},{"file":"src/Umbraco.Infrastructure/Logging/Serilog/UmbracoFileConfiguration.cs","loc":40,"old-loc":38,"code-health":10.0,"old-code-health":10.0},{"file":"src/Umbraco.Core/Services/LogViewerService.cs","loc":48,"old-loc":202,"code-health":10.0,"old-code-health":8.816158827775617},{"file":"src/Umbraco.Infrastructure/Services/Implement/LogViewerRepository.cs","loc":107,"old-loc":141,"code-health":9.387218218812514,"old-code-health":9.387218218812514},{"file":"src/Umbraco.Infrastructure/Logging/Viewer/SerilogLogViewerSourceBase.cs","loc":135,"old-loc":134,"code-health":8.816158827775617,"old-code-health":8.816158827775617},{"file":"src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Uniques.cs","loc":126,"old-loc":123,"code-health":10.0,"old-code-health":10.0}]},"removed-files":{"code-health":0.0,"old-code-health":0.0,"files":[]},"external-review-id":"19818","analysis-time":"2025-08-01T07:13:21Z","negative-impact-count":4,"suppressions":{"number-of-types":0,"number-of-files-touched":0,"findings":[]},"affected-hotspots":0,"commits":["2287d524040463d0825c20dc453767aa35ebb94b","8137426be13bf4c6265b280cf24faeb4bfd389eb","a4049fd82f1a49b0468fd2f336de88896fc373d2","78abf2eb83ac45630ec6b180ea81b6f500f46403","43b07ca09933bdb1e070882205d92467e1fb2d64","bac9fd888b2ecb116e86a07285bb6d12a5b02b6a","916d88df78cb2d697fc2929d695d61da0f6e9db2","dd29ed01c53fa01c0a74b9d7ad9aed41402509ba","46a61c19f54cb4f8450504c1c67d0fbfc558fc47","f7c30c9177a747a5894a94e01059a0387da842c9","46444ae3d4934fe0f3b8765e883a90098d655de5","498d27c4fc22e163434b9015e8197fbd7b0f31d8","0b6745885fe370cb963b9ee8eccc6b0fe7adcf96","1b494428fd6e9ef509c71fe5825b75afdd5e8906"],"is-negative-review":true,"negative-findings":{"number-of-types":3,"number-of-files-touched":1,"findings":[{"why-it-occurs":"Duplicated code often leads to code that's harder to change since the same logical change has to be done in multiple functions. More duplication gives lower code health.","name":"Code Duplication","file":"src/Umbraco.Core/Services/LogViewerServiceBase.cs","refactoring-examples":null,"change-level":"warning","is-hotspot?":false,"line":66,"what-changed":"The module contains 4 functions with similar structure: AddSavedLogQueryAsync,DeleteSavedLogQueryAsync,GetLogLevelCountsAsync,GetMessageTemplatesAsync","how-to-fix":"A certain degree of duplicated code might be acceptable. The problems start when it is the same behavior that is duplicated across the functions in the module, ie. a violation of the Don't Repeat Yourself (DRY) principle. DRY violations lead to code that is changed together in predictable patterns, which is both expensive and risky. DRY violations can be identified using CodeScene's X-Ray analysis to detect clusters of change coupled functions with high code similarity. [Read More](https://codescene.com/blog/software-revolution-part3/)\n\nOnce you have identified the similarities across functions, look to extract and encapsulate the concept that varies into its own function(s). These shared abstractions can then be re-used, which minimizes the amount of duplication and simplifies change.","change-type":"introduced"},{"why-it-occurs":"Code that uses a high degree of built-in, primitives such as integers, strings, floats, lacks a domain language that encapsulates the validation and semantics of function arguments. Primitive Obsession has several consequences: 1) In a statically typed language, the compiler will detect less erroneous assignments. 2) Security impact since the possible value range of a variable/argument isn't retricted.\n\nIn this module, 50 % of all functions have primitive types as arguments.","name":"Primitive Obsession","file":"src/Umbraco.Core/Services/LogViewerServiceBase.cs","refactoring-examples":[{"diff":"diff --git a/primitive-obsession.ts b/primitive-obsession.ts\nindex 38bae186cc..24116eddcc 100644\n--- a/primitive-obsession.ts\n+++ b/primitive-obsession.ts\n@@ -1,8 +1,8 @@\n-// Problem: It's hard to know what this function does, and what values are valid as parameters.\n-function getPopularRepositories(String baseURL, String query, Integer pages, Integer pageSize, String sortorder): Json {\n-\tlet pages == null ? 10 : pages\n-\tlet pageSize == null ? 10 : pageSize\n-  return httpClient.get(`${baseURL}?q=${query}&pages=${pages}&pageSize=${pageSize}&sortorder=${sortorder}`)\n+// Refactoring: extract the pagination & API logic into a class, and it will\n+// attract validation and other logic related to the specific query. It's now\n+// easier to use and to maintain the getPopularRepositories function!\n+function getPopularRepositories(query: PaginatedRepoQuery): Json {\n+  return httpClient.get(query.getURL())\n     .map(json => json.repositories)\n     .filter(repository => repositry.stargazersCount > 1000)\n }\n","language":"c#","improvement-type":"Primitive Obsession"}],"change-level":"warning","is-hotspot?":false,"what-changed":"In this module, 50.0% of all function arguments are primitive types, threshold = 30.0%","how-to-fix":"Primitive Obsession indicates a missing domain language. Introduce data types that encapsulate the details and constraints of your domain. For example, instead of `int userId`, consider `User clicked`.","change-type":"introduced"},{"method":"GetPagedLogsAsync","why-it-occurs":"Functions with many arguments indicate either a) low cohesion where the function has too many responsibilities, or b) a missing abstraction that encapsulates those arguments.\n\nThe threshold for the C# language is 4 function arguments.","name":"Excess Number of Function Arguments","file":"src/Umbraco.Core/Services/LogViewerServiceBase.cs","refactoring-examples":[{"architectural-component-id":null,"author-name":"Kenn Jacobsen","training-data":{"loc-added":"2","loc-deleted":"2","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"kja@umbraco.dk","commit-full-message":"Co-authored-by: Elitsa <elm@umbraco.dk>","commit-date":"2023-06-26T05:33:16Z","current-rev":"2c9d0b2cb1","filename":"Umbraco-CMS/src/Umbraco.Core/DeliveryApi/ApiContentBuilder.cs","previous-rev":"e92ea34098","commit-title":"Add create and update dates to Delivery API response (#14427)","language":"C#","id":"c9f57f2ba9707ffb5bf810e853e28a00b83b45fc","model-score":0.96,"author-id":null,"project-id":33308,"delta-file-score":0.31179166,"diff":"diff --git a/src/Umbraco.Core/DeliveryApi/ApiContentBuilder.cs b/src/Umbraco.Core/DeliveryApi/ApiContentBuilder.cs\nindex 11ea4faf77..135afe068a 100644\n--- a/src/Umbraco.Core/DeliveryApi/ApiContentBuilder.cs\n+++ b/src/Umbraco.Core/DeliveryApi/ApiContentBuilder.cs\n@@ -12,4 +12,4 @@ public ApiContentBuilder(IApiContentNameProvider apiContentNameProvider, IApiCon\n \n-    protected override IApiContent Create(IPublishedContent content, Guid id, string name, string contentType, IApiContentRoute route, IDictionary<string, object?> properties)\n-        => new ApiContent(id, name, contentType, route, properties);\n+    protected override IApiContent Create(IPublishedContent content, string name, IApiContentRoute route, IDictionary<string, object?> properties)\n+        => new ApiContent(content.Key, name, content.ContentType.Alias, content.CreateDate, content.UpdateDate, route, properties);\n }\n","improvement-type":"Excess Number of Function Arguments"},{"architectural-component-id":null,"author-name":"Elitsa Marinovska","training-data":{"loc-added":"1","loc-deleted":"23","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"21998037+elit0451@users.noreply.github.com","commit-full-message":"* Obsoletions related to Delivery API\r\n\r\n* Fix TypeLoader and TypeFinder tests\r\n\r\n* Remove obsolete and default implementations of IFileSource and IFileTypeCollection\r\n\r\n* More Delivery API related obsoletions\r\n\r\n* VariationContextAccessor related\r\n\r\n* ValueFactories obsoletion and fix references\r\n\r\n* ValueSetBuilders obsoletions\r\n\r\n* ValueConverters obsoletions\r\n\r\n* Other obsolete ctors and methods\r\n\r\n* Forgotten VariationContextAccessor obsoletion\r\n\r\n* More obsoletions\r\n\r\n* XPath related obsoletions\r\n\r\n* Revert XmlHelper changes\r\n\r\n* Delete RenamedRootNavigator and its tests\r\n\r\n* Fix test\r\n\r\n* XmlHelper obsoletion\r\n\r\n* Return null instead of GetXPathValue\r\n\r\n* Obsolete entire class instead\r\n\r\n* Remove XPath obsoletions from IPublishedCache\r\n\r\n* Remove XPath-related if-block that is no longer needed\r\n\r\n* Change obsolete msg for classes needed for NuCache\r\n\r\n* Moving classes to NuCache and making them internal\r\n\r\n* Remove more XPath-related obsoletions\r\n\r\n* Remove NavigableNavigator and its tests\r\n\r\n* Cleanup\r\n\r\n* Remove Xpath references from tests\r\n\r\n* Revert interface deletion in MediaCache\r\n\r\n* Using XOR operation\r\n\r\nCo-authored-by: Nuklon <Nuklon@users.noreply.github.com>\r\n\r\n---------\r\n\r\nCo-authored-by: Nuklon <Nuklon@users.noreply.github.com>","commit-date":"2024-04-09T07:06:48Z","current-rev":"9c18cd22e0","filename":"Umbraco-CMS/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs","previous-rev":"187d45860a","commit-title":"V14: Deleted code marked as obsolete for V14 (#15998)","language":"C#","id":"5a0a3e3ca094c91b1e473f50f09c522acaad7c3f","model-score":0.51,"author-id":null,"project-id":33308,"delta-file-score":0.31179166,"diff":"diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs\nindex e3dcd6ae78..23241e8cd9 100644\n--- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs\n+++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs\n@@ -1,3 +1,2 @@\n-﻿using System.Xml;\n-using Umbraco.Cms.Core.Models.PublishedContent;\n+﻿using Umbraco.Cms.Core.Models.PublishedContent;\n \n@@ -58,23 +57,2 @@ public override object ConvertSourceToIntermediate(IPublishedElement owner, IPub\n     }\n-\n-    [Obsolete(\"The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14\")]\n-    public override object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview)\n-    {\n-        var d = new XmlDocument();\n-        XmlElement e = d.CreateElement(\"values\");\n-        d.AppendChild(e);\n-\n-        var values = (IEnumerable<string>?)inter;\n-        if (values is not null)\n-        {\n-            foreach (var value in values)\n-            {\n-                XmlElement ee = d.CreateElement(\"value\");\n-                ee.InnerText = value;\n-                e.AppendChild(ee);\n-            }\n-        }\n-\n-        return d.CreateNavigator();\n-    }\n }\n","improvement-type":"Excess Number of Function Arguments"},{"architectural-component-id":null,"author-name":"Ronald Barendse","training-data":{"loc-added":"37","loc-deleted":"41","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"ronald@barend.se","commit-full-message":"* Fix IndexOutOfRangeException when converting single value to range in SliderValueConverter\r\n\r\n* Fix NullReferenceException while deserializing empty value in TagsValueConverter\r\n\r\n* Use invariant decimal parsing\r\n\r\n* Handle converting from slider to single value\r\n\r\n* Fix parsing range as single value\r\n\r\n* Make Handle methods autonomous\r\n\r\n---------\r\n\r\nCo-authored-by: nikolajlauridsen <nikolajlauridsen@protonmail.ch>","commit-date":"2023-08-23T08:09:43Z","current-rev":"f97e9a9f34","filename":"Umbraco-CMS/src/Umbraco.Core/PropertyEditors/ValueConverters/TagsValueConverter.cs","previous-rev":"19ee8e9254","commit-title":"Fix exceptions in Slider and Tags property value converters (#13782)","language":"C#","id":"69c4f0775fc3beb01b4297882b107deebc0194ae","model-score":0.36,"author-id":null,"project-id":33308,"delta-file-score":0.31179166,"diff":"diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/TagsValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/TagsValueConverter.cs\nindex 3afc5a6596..2dd1c1d56e 100644\n--- a/src/Umbraco.Core/PropertyEditors/ValueConverters/TagsValueConverter.cs\n+++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/TagsValueConverter.cs\n@@ -1,2 +1 @@\n-﻿using System.Collections.Concurrent;\n using Umbraco.Cms.Core.Models;\n@@ -9,2 +8,6 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;\n \n+/// <summary>\n+/// The tags property value converter.\n+/// </summary>\n+/// <seealso cref=\"Umbraco.Cms.Core.PropertyEditors.PropertyValueConverterBase\" />\n [DefaultPropertyValueConverter]\n@@ -12,14 +15,30 @@ public class TagsValueConverter : PropertyValueConverterBase\n {\n-    private static readonly ConcurrentDictionary<int, bool> Storages = new();\n-    private readonly IDataTypeService _dataTypeService;\n     private readonly IJsonSerializer _jsonSerializer;\n \n+    /// <summary>\n+    /// Initializes a new instance of the <see cref=\"TagsValueConverter\" /> class.\n+    /// </summary>\n+    /// <param name=\"jsonSerializer\">The JSON serializer.</param>\n+    /// <exception cref=\"System.ArgumentNullException\">jsonSerializer</exception>\n+    public TagsValueConverter(IJsonSerializer jsonSerializer)\n+        => _jsonSerializer = jsonSerializer ?? throw new ArgumentNullException(nameof(jsonSerializer));\n+\n+    /// <summary>\n+    /// Initializes a new instance of the <see cref=\"TagsValueConverter\" /> class.\n+    /// </summary>\n+    /// <param name=\"dataTypeService\">The data type service.</param>\n+    /// <param name=\"jsonSerializer\">The JSON serializer.</param>\n+    [Obsolete(\"The IDataTypeService is not used anymore. This constructor will be removed in a future version.\")]\n     public TagsValueConverter(IDataTypeService dataTypeService, IJsonSerializer jsonSerializer)\n-    {\n-        _dataTypeService = dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService));\n-        _jsonSerializer = jsonSerializer ?? throw new ArgumentNullException(nameof(jsonSerializer));\n-    }\n+        : this(jsonSerializer)\n+    { }\n \n-    public static void ClearCaches() => Storages.Clear();\n+    /// <summary>\n+    /// Clears the data type configuration caches.\n+    /// </summary>\n+    [Obsolete(\"Caching of data type configuration is not done anymore. This method will be removed in a future version.\")]\n+    public static void ClearCaches()\n+    { }\n \n+    /// <inheritdoc />\n     public override bool IsConverter(IPublishedPropertyType propertyType)\n@@ -27,2 +46,3 @@ public override bool IsConverter(IPublishedPropertyType propertyType)\n \n+    /// <inheritdoc />\n     public override Type GetPropertyValueType(IPublishedPropertyType propertyType)\n@@ -30,2 +50,3 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType)\n \n+    /// <inheritdoc />\n     public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)\n@@ -33,5 +54,7 @@ public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType\n \n+    /// <inheritdoc />\n     public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview)\n     {\n-        if (source == null)\n+        string? sourceString = source?.ToString();\n+        if (string.IsNullOrEmpty(sourceString))\n         {\n@@ -40,36 +63,9 @@ public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType\n \n-        // if Json storage type deserialize and return as string array\n-        if (JsonStorageType(propertyType.DataType.Id))\n-        {\n-            var array = source.ToString() is not null\n-                ? _jsonSerializer.Deserialize<string[]>(source.ToString()!)\n-                : null;\n-            return array ?? Array.Empty<string>();\n-        }\n-\n-        // Otherwise assume CSV storage type and return as string array\n-        return source.ToString()?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries);\n+        return IsJson(propertyType)\n+            ? _jsonSerializer.Deserialize<string[]>(sourceString) ?? Array.Empty<string>()\n+            : sourceString.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries);\n     }\n \n-    public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object? source, bool preview) => (string[]?)source;\n-\n-    /// <summary>\n-    ///     Discovers if the tags data type is storing its data in a Json format\n-    /// </summary>\n-    /// <param name=\"dataTypeId\">\n-    ///     The data type id.\n-    /// </param>\n-    /// <returns>\n-    ///     The <see cref=\"bool\" />.\n-    /// </returns>\n-    private bool JsonStorageType(int dataTypeId) =>\n-\n-        // GetDataType(id) is cached at repository level; still, there is some\n-        // deep-cloning involved (expensive) - better cache here + trigger\n-        // refresh in DataTypeCacheRefresher\n-        Storages.GetOrAdd(dataTypeId, id =>\n-        {\n-            TagConfiguration? configuration = _dataTypeService.GetDataType(id)?.ConfigurationAs<TagConfiguration>();\n-            return configuration?.StorageType == TagsStorageType.Json;\n-        });\n+    private static bool IsJson(IPublishedPropertyType propertyType)\n+        => propertyType.DataType.ConfigurationAs<TagConfiguration>()?.StorageType == TagsStorageType.Json;\n }\n","improvement-type":"Excess Number of Function Arguments"}],"change-level":"warning","is-hotspot?":false,"line":161,"what-changed":"GetPagedLogsAsync has 7 arguments, threshold = 4","how-to-fix":"Start by investigating the responsibilities of the function. Make sure it doesn't do too many things, in which case it should be split into smaller and more cohesive functions. Consider the refactoring [INTRODUCE PARAMETER OBJECT](https://refactoring.com/catalog/introduceParameterObject.html) to encapsulate arguments that refer to the same logical concept.","change-type":"introduced"},{"method":"GetFilteredLogs","why-it-occurs":"Functions with many arguments indicate either a) low cohesion where the function has too many responsibilities, or b) a missing abstraction that encapsulates those arguments.\n\nThe threshold for the C# language is 4 function arguments.","name":"Excess Number of Function Arguments","file":"src/Umbraco.Core/Services/LogViewerServiceBase.cs","refactoring-examples":[{"architectural-component-id":null,"author-name":"Kenn Jacobsen","training-data":{"loc-added":"2","loc-deleted":"2","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"kja@umbraco.dk","commit-full-message":"Co-authored-by: Elitsa <elm@umbraco.dk>","commit-date":"2023-06-26T05:33:16Z","current-rev":"2c9d0b2cb1","filename":"Umbraco-CMS/src/Umbraco.Core/DeliveryApi/ApiContentBuilder.cs","previous-rev":"e92ea34098","commit-title":"Add create and update dates to Delivery API response (#14427)","language":"C#","id":"c9f57f2ba9707ffb5bf810e853e28a00b83b45fc","model-score":0.96,"author-id":null,"project-id":33308,"delta-file-score":0.31179166,"diff":"diff --git a/src/Umbraco.Core/DeliveryApi/ApiContentBuilder.cs b/src/Umbraco.Core/DeliveryApi/ApiContentBuilder.cs\nindex 11ea4faf77..135afe068a 100644\n--- a/src/Umbraco.Core/DeliveryApi/ApiContentBuilder.cs\n+++ b/src/Umbraco.Core/DeliveryApi/ApiContentBuilder.cs\n@@ -12,4 +12,4 @@ public ApiContentBuilder(IApiContentNameProvider apiContentNameProvider, IApiCon\n \n-    protected override IApiContent Create(IPublishedContent content, Guid id, string name, string contentType, IApiContentRoute route, IDictionary<string, object?> properties)\n-        => new ApiContent(id, name, contentType, route, properties);\n+    protected override IApiContent Create(IPublishedContent content, string name, IApiContentRoute route, IDictionary<string, object?> properties)\n+        => new ApiContent(content.Key, name, content.ContentType.Alias, content.CreateDate, content.UpdateDate, route, properties);\n }\n","improvement-type":"Excess Number of Function Arguments"},{"architectural-component-id":null,"author-name":"Elitsa Marinovska","training-data":{"loc-added":"1","loc-deleted":"23","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"21998037+elit0451@users.noreply.github.com","commit-full-message":"* Obsoletions related to Delivery API\r\n\r\n* Fix TypeLoader and TypeFinder tests\r\n\r\n* Remove obsolete and default implementations of IFileSource and IFileTypeCollection\r\n\r\n* More Delivery API related obsoletions\r\n\r\n* VariationContextAccessor related\r\n\r\n* ValueFactories obsoletion and fix references\r\n\r\n* ValueSetBuilders obsoletions\r\n\r\n* ValueConverters obsoletions\r\n\r\n* Other obsolete ctors and methods\r\n\r\n* Forgotten VariationContextAccessor obsoletion\r\n\r\n* More obsoletions\r\n\r\n* XPath related obsoletions\r\n\r\n* Revert XmlHelper changes\r\n\r\n* Delete RenamedRootNavigator and its tests\r\n\r\n* Fix test\r\n\r\n* XmlHelper obsoletion\r\n\r\n* Return null instead of GetXPathValue\r\n\r\n* Obsolete entire class instead\r\n\r\n* Remove XPath obsoletions from IPublishedCache\r\n\r\n* Remove XPath-related if-block that is no longer needed\r\n\r\n* Change obsolete msg for classes needed for NuCache\r\n\r\n* Moving classes to NuCache and making them internal\r\n\r\n* Remove more XPath-related obsoletions\r\n\r\n* Remove NavigableNavigator and its tests\r\n\r\n* Cleanup\r\n\r\n* Remove Xpath references from tests\r\n\r\n* Revert interface deletion in MediaCache\r\n\r\n* Using XOR operation\r\n\r\nCo-authored-by: Nuklon <Nuklon@users.noreply.github.com>\r\n\r\n---------\r\n\r\nCo-authored-by: Nuklon <Nuklon@users.noreply.github.com>","commit-date":"2024-04-09T07:06:48Z","current-rev":"9c18cd22e0","filename":"Umbraco-CMS/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs","previous-rev":"187d45860a","commit-title":"V14: Deleted code marked as obsolete for V14 (#15998)","language":"C#","id":"5a0a3e3ca094c91b1e473f50f09c522acaad7c3f","model-score":0.51,"author-id":null,"project-id":33308,"delta-file-score":0.31179166,"diff":"diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs\nindex e3dcd6ae78..23241e8cd9 100644\n--- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs\n+++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs\n@@ -1,3 +1,2 @@\n-﻿using System.Xml;\n-using Umbraco.Cms.Core.Models.PublishedContent;\n+﻿using Umbraco.Cms.Core.Models.PublishedContent;\n \n@@ -58,23 +57,2 @@ public override object ConvertSourceToIntermediate(IPublishedElement owner, IPub\n     }\n-\n-    [Obsolete(\"The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14\")]\n-    public override object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview)\n-    {\n-        var d = new XmlDocument();\n-        XmlElement e = d.CreateElement(\"values\");\n-        d.AppendChild(e);\n-\n-        var values = (IEnumerable<string>?)inter;\n-        if (values is not null)\n-        {\n-            foreach (var value in values)\n-            {\n-                XmlElement ee = d.CreateElement(\"value\");\n-                ee.InnerText = value;\n-                e.AppendChild(ee);\n-            }\n-        }\n-\n-        return d.CreateNavigator();\n-    }\n }\n","improvement-type":"Excess Number of Function Arguments"},{"architectural-component-id":null,"author-name":"Ronald Barendse","training-data":{"loc-added":"37","loc-deleted":"41","delta-cc-mean":"0.0","delta-cc-total":"0","delta-penalties":"1.0","delta-n-functions":"0","current-file-score":"10.0"},"author-email":"ronald@barend.se","commit-full-message":"* Fix IndexOutOfRangeException when converting single value to range in SliderValueConverter\r\n\r\n* Fix NullReferenceException while deserializing empty value in TagsValueConverter\r\n\r\n* Use invariant decimal parsing\r\n\r\n* Handle converting from slider to single value\r\n\r\n* Fix parsing range as single value\r\n\r\n* Make Handle methods autonomous\r\n\r\n---------\r\n\r\nCo-authored-by: nikolajlauridsen <nikolajlauridsen@protonmail.ch>","commit-date":"2023-08-23T08:09:43Z","current-rev":"f97e9a9f34","filename":"Umbraco-CMS/src/Umbraco.Core/PropertyEditors/ValueConverters/TagsValueConverter.cs","previous-rev":"19ee8e9254","commit-title":"Fix exceptions in Slider and Tags property value converters (#13782)","language":"C#","id":"69c4f0775fc3beb01b4297882b107deebc0194ae","model-score":0.36,"author-id":null,"project-id":33308,"delta-file-score":0.31179166,"diff":"diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/TagsValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/TagsValueConverter.cs\nindex 3afc5a6596..2dd1c1d56e 100644\n--- a/src/Umbraco.Core/PropertyEditors/ValueConverters/TagsValueConverter.cs\n+++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/TagsValueConverter.cs\n@@ -1,2 +1 @@\n-﻿using System.Collections.Concurrent;\n using Umbraco.Cms.Core.Models;\n@@ -9,2 +8,6 @@ namespace Umbraco.Cms.Core.PropertyEditors.ValueConverters;\n \n+/// <summary>\n+/// The tags property value converter.\n+/// </summary>\n+/// <seealso cref=\"Umbraco.Cms.Core.PropertyEditors.PropertyValueConverterBase\" />\n [DefaultPropertyValueConverter]\n@@ -12,14 +15,30 @@ public class TagsValueConverter : PropertyValueConverterBase\n {\n-    private static readonly ConcurrentDictionary<int, bool> Storages = new();\n-    private readonly IDataTypeService _dataTypeService;\n     private readonly IJsonSerializer _jsonSerializer;\n \n+    /// <summary>\n+    /// Initializes a new instance of the <see cref=\"TagsValueConverter\" /> class.\n+    /// </summary>\n+    /// <param name=\"jsonSerializer\">The JSON serializer.</param>\n+    /// <exception cref=\"System.ArgumentNullException\">jsonSerializer</exception>\n+    public TagsValueConverter(IJsonSerializer jsonSerializer)\n+        => _jsonSerializer = jsonSerializer ?? throw new ArgumentNullException(nameof(jsonSerializer));\n+\n+    /// <summary>\n+    /// Initializes a new instance of the <see cref=\"TagsValueConverter\" /> class.\n+    /// </summary>\n+    /// <param name=\"dataTypeService\">The data type service.</param>\n+    /// <param name=\"jsonSerializer\">The JSON serializer.</param>\n+    [Obsolete(\"The IDataTypeService is not used anymore. This constructor will be removed in a future version.\")]\n     public TagsValueConverter(IDataTypeService dataTypeService, IJsonSerializer jsonSerializer)\n-    {\n-        _dataTypeService = dataTypeService ?? throw new ArgumentNullException(nameof(dataTypeService));\n-        _jsonSerializer = jsonSerializer ?? throw new ArgumentNullException(nameof(jsonSerializer));\n-    }\n+        : this(jsonSerializer)\n+    { }\n \n-    public static void ClearCaches() => Storages.Clear();\n+    /// <summary>\n+    /// Clears the data type configuration caches.\n+    /// </summary>\n+    [Obsolete(\"Caching of data type configuration is not done anymore. This method will be removed in a future version.\")]\n+    public static void ClearCaches()\n+    { }\n \n+    /// <inheritdoc />\n     public override bool IsConverter(IPublishedPropertyType propertyType)\n@@ -27,2 +46,3 @@ public override bool IsConverter(IPublishedPropertyType propertyType)\n \n+    /// <inheritdoc />\n     public override Type GetPropertyValueType(IPublishedPropertyType propertyType)\n@@ -30,2 +50,3 @@ public override Type GetPropertyValueType(IPublishedPropertyType propertyType)\n \n+    /// <inheritdoc />\n     public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)\n@@ -33,5 +54,7 @@ public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType\n \n+    /// <inheritdoc />\n     public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview)\n     {\n-        if (source == null)\n+        string? sourceString = source?.ToString();\n+        if (string.IsNullOrEmpty(sourceString))\n         {\n@@ -40,36 +63,9 @@ public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType\n \n-        // if Json storage type deserialize and return as string array\n-        if (JsonStorageType(propertyType.DataType.Id))\n-        {\n-            var array = source.ToString() is not null\n-                ? _jsonSerializer.Deserialize<string[]>(source.ToString()!)\n-                : null;\n-            return array ?? Array.Empty<string>();\n-        }\n-\n-        // Otherwise assume CSV storage type and return as string array\n-        return source.ToString()?.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries);\n+        return IsJson(propertyType)\n+            ? _jsonSerializer.Deserialize<string[]>(sourceString) ?? Array.Empty<string>()\n+            : sourceString.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries);\n     }\n \n-    public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel cacheLevel, object? source, bool preview) => (string[]?)source;\n-\n-    /// <summary>\n-    ///     Discovers if the tags data type is storing its data in a Json format\n-    /// </summary>\n-    /// <param name=\"dataTypeId\">\n-    ///     The data type id.\n-    /// </param>\n-    /// <returns>\n-    ///     The <see cref=\"bool\" />.\n-    /// </returns>\n-    private bool JsonStorageType(int dataTypeId) =>\n-\n-        // GetDataType(id) is cached at repository level; still, there is some\n-        // deep-cloning involved (expensive) - better cache here + trigger\n-        // refresh in DataTypeCacheRefresher\n-        Storages.GetOrAdd(dataTypeId, id =>\n-        {\n-            TagConfiguration? configuration = _dataTypeService.GetDataType(id)?.ConfigurationAs<TagConfiguration>();\n-            return configuration?.StorageType == TagsStorageType.Json;\n-        });\n+    private static bool IsJson(IPublishedPropertyType propertyType)\n+        => propertyType.DataType.ConfigurationAs<TagConfiguration>()?.StorageType == TagsStorageType.Json;\n }\n","improvement-type":"Excess Number of Function Arguments"}],"change-level":"warning","is-hotspot?":false,"line":223,"what-changed":"GetFilteredLogs has 6 arguments, threshold = 4","how-to-fix":"Start by investigating the responsibilities of the function. Make sure it doesn't do too many things, in which case it should be split into smaller and more cohesive functions. Consider the refactoring [INTRODUCE PARAMETER OBJECT](https://refactoring.com/catalog/introduceParameterObject.html) to encapsulate arguments that refer to the same logical concept.","change-type":"introduced"}]},"positive-impact-count":4,"repo":"Umbraco-CMS","code-health":9.23845141427367,"version":"3.0","authors":["Migaroez","Andy Butland"],"directives":{"added":[],"removed":[]},"positive-findings":{"number-of-types":3,"number-of-files-touched":1,"findings":[{"name":"Code Duplication","file":"src/Umbraco.Core/Services/LogViewerService.cs","change-type":"fixed","change-level":"improvement","is-hotspot?":false,"why-it-occurs":"Duplicated code often leads to code that's harder to change since the same logical change has to be done in multiple functions. More duplication gives lower code health.","how-to-fix":"A certain degree of duplicated code might be acceptable. The problems start when it is the same behavior that is duplicated across the functions in the module, ie. a violation of the Don't Repeat Yourself (DRY) principle. DRY violations lead to code that is changed together in predictable patterns, which is both expensive and risky. DRY violations can be identified using CodeScene's X-Ray analysis to detect clusters of change coupled functions with high code similarity. [Read More](https://codescene.com/blog/software-revolution-part3/)\n\nOnce you have identified the similarities across functions, look to extract and encapsulate the concept that varies into its own function(s). These shared abstractions can then be re-used, which minimizes the amount of duplication and simplifies change.","what-changed":"The module no longer contains too many functions with similar structure"},{"name":"Primitive Obsession","file":"src/Umbraco.Core/Services/LogViewerService.cs","change-type":"fixed","change-level":"improvement","is-hotspot?":false,"why-it-occurs":"Code that uses a high degree of built-in, primitives such as integers, strings, floats, lacks a domain language that encapsulates the validation and semantics of function arguments. Primitive Obsession has several consequences: 1) In a statically typed language, the compiler will detect less erroneous assignments. 2) Security impact since the possible value range of a variable/argument isn't retricted.\n\nIn this module, 0 % of all functions have primitive types as arguments.","how-to-fix":"Primitive Obsession indicates a missing domain language. Introduce data types that encapsulate the details and constraints of your domain. For example, instead of `int userId`, consider `User clicked`.","what-changed":"The ratio of primivite types in function arguments is no longer above the threshold"},{"method":"GetPagedLogsAsync","why-it-occurs":"Functions with many arguments indicate either a) low cohesion where the function has too many responsibilities, or b) a missing abstraction that encapsulates those arguments.\n\nThe threshold for the C# language is 4 function arguments.","name":"Excess Number of Function Arguments","file":"src/Umbraco.Core/Services/LogViewerService.cs","change-level":"improvement","is-hotspot?":false,"line":34,"what-changed":"GetPagedLogsAsync is no longer above the threshold for number of arguments","how-to-fix":"Start by investigating the responsibilities of the function. Make sure it doesn't do too many things, in which case it should be split into smaller and more cohesive functions. Consider the refactoring [INTRODUCE PARAMETER OBJECT](https://refactoring.com/catalog/introduceParameterObject.html) to encapsulate arguments that refer to the same logical concept.","change-type":"fixed"},{"method":"GetFilteredLogs","why-it-occurs":"Functions with many arguments indicate either a) low cohesion where the function has too many responsibilities, or b) a missing abstraction that encapsulates those arguments.\n\nThe threshold for the C# language is 4 function arguments.","name":"Excess Number of Function Arguments","file":"src/Umbraco.Core/Services/LogViewerService.cs","change-level":"improvement","is-hotspot?":false,"line":233,"what-changed":"GetFilteredLogs is no longer above the threshold for number of arguments","how-to-fix":"Start by investigating the responsibilities of the function. Make sure it doesn't do too many things, in which case it should be split into smaller and more cohesive functions. Consider the refactoring [INTRODUCE PARAMETER OBJECT](https://refactoring.com/catalog/introduceParameterObject.html) to encapsulate arguments that refer to the same logical concept.","change-type":"fixed"}]},"notices":{"number-of-types":0,"number-of-files-touched":0,"findings":[]},"external-review-provider":"GitHub"},"analysistime":"2025-08-01T07:13:21.000Z","project-name":"Umbraco-CMS","repository":"https://github.com/umbraco/Umbraco-CMS.git"}}