Added logging and try/catch around retrieval of references, so we don't block critical operations following an incompatible data type change (#18576)
* Added logging and try/catch around retrieval of references, so we don't block critical operations following an incompatible data type change. * Added a little more detail to the log message. * Added a little more detail to the log message. * Fix unittest mock dependency --------- Co-authored-by: Migaroez <geusens@gmail.com>
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core.Composing;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Editors;
|
||||
|
||||
@@ -12,14 +15,27 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase<IDataVa
|
||||
// TODO: We could further reduce circular dependencies with PropertyEditorCollection by not having IDataValueReference implemented
|
||||
// by property editors and instead just use the already built in IDataValueReferenceFactory and/or refactor that into a more normal collection
|
||||
|
||||
private readonly ILogger<DataValueReferenceFactoryCollection> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DataValueReferenceFactoryCollection" /> class.
|
||||
/// </summary>
|
||||
/// <param name="items">The items.</param>
|
||||
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
|
||||
public DataValueReferenceFactoryCollection(Func<IEnumerable<IDataValueReferenceFactory>> items)
|
||||
: base(items)
|
||||
: this(
|
||||
items,
|
||||
StaticServiceProvider.Instance.GetRequiredService<ILogger<DataValueReferenceFactoryCollection>>())
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DataValueReferenceFactoryCollection" /> class.
|
||||
/// </summary>
|
||||
/// <param name="items">The items.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
public DataValueReferenceFactoryCollection(Func<IEnumerable<IDataValueReferenceFactory>> items, ILogger<DataValueReferenceFactoryCollection> logger)
|
||||
: base(items) => _logger = logger;
|
||||
|
||||
/// <summary>
|
||||
/// Gets all unique references from the specified properties.
|
||||
/// </summary>
|
||||
@@ -33,7 +49,7 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase<IDataVa
|
||||
var references = new HashSet<UmbracoEntityReference>();
|
||||
|
||||
// Group by property editor alias to avoid duplicate lookups and optimize value parsing
|
||||
foreach (var propertyValuesByPropertyEditorAlias in properties.GroupBy(x => x.PropertyType.PropertyEditorAlias, x => x.Values))
|
||||
foreach (IGrouping<string, IReadOnlyCollection<IPropertyValue>> propertyValuesByPropertyEditorAlias in properties.GroupBy(x => x.PropertyType.PropertyEditorAlias, x => x.Values))
|
||||
{
|
||||
if (!propertyEditors.TryGet(propertyValuesByPropertyEditorAlias.Key, out IDataEditor? dataEditor))
|
||||
{
|
||||
@@ -48,7 +64,7 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase<IDataVa
|
||||
values.Add(propertyValue.PublishedValue);
|
||||
}
|
||||
|
||||
references.UnionWith(GetReferences(dataEditor, values));
|
||||
references.UnionWith(GetReferences(dataEditor, values, propertyValuesByPropertyEditorAlias.Key));
|
||||
}
|
||||
|
||||
return references;
|
||||
@@ -74,14 +90,18 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase<IDataVa
|
||||
/// The references.
|
||||
/// </returns>
|
||||
public ISet<UmbracoEntityReference> GetReferences(IDataEditor dataEditor, IEnumerable<object?> values) =>
|
||||
GetReferencesEnumerable(dataEditor, values).ToHashSet();
|
||||
private IEnumerable<UmbracoEntityReference> GetReferencesEnumerable(IDataEditor dataEditor, IEnumerable<object?> values)
|
||||
GetReferencesEnumerable(dataEditor, values, null).ToHashSet();
|
||||
|
||||
private ISet<UmbracoEntityReference> GetReferences(IDataEditor dataEditor, IEnumerable<object?> values, string propertyEditorAlias) =>
|
||||
GetReferencesEnumerable(dataEditor, values, propertyEditorAlias).ToHashSet();
|
||||
|
||||
private IEnumerable<UmbracoEntityReference> GetReferencesEnumerable(IDataEditor dataEditor, IEnumerable<object?> values, string? propertyEditorAlias)
|
||||
{
|
||||
// TODO: We will need to change this once we support tracking via variants/segments
|
||||
// for now, we are tracking values from ALL variants
|
||||
if (dataEditor.GetValueEditor() is IDataValueReference dataValueReference)
|
||||
{
|
||||
foreach (UmbracoEntityReference reference in values.SelectMany(dataValueReference.GetReferences))
|
||||
foreach (UmbracoEntityReference reference in GetReferencesFromPropertyValues(values, dataValueReference, propertyEditorAlias))
|
||||
{
|
||||
yield return reference;
|
||||
}
|
||||
@@ -107,6 +127,38 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase<IDataVa
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<UmbracoEntityReference> GetReferencesFromPropertyValues(IEnumerable<object?> values, IDataValueReference dataValueReference, string? propertyEditorAlias)
|
||||
{
|
||||
var result = new List<UmbracoEntityReference>();
|
||||
foreach (var value in values)
|
||||
{
|
||||
// When property editors on data types are changed, we could have values that are incompatible with the new editor.
|
||||
// Leading to issues such as:
|
||||
// - https://github.com/umbraco/Umbraco-CMS/issues/17628
|
||||
// - https://github.com/umbraco/Umbraco-CMS/issues/17725
|
||||
// Although some changes like this are not intended to be compatible, we should handle them gracefully and not
|
||||
// error in retrieving references, which would prevent manipulating or deleting the content that uses the data type.
|
||||
try
|
||||
{
|
||||
IEnumerable<UmbracoEntityReference> references = dataValueReference.GetReferences(value);
|
||||
result.AddRange(references);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log the exception but don't throw, continue with the next value.
|
||||
_logger.LogError(
|
||||
ex,
|
||||
"Error getting references from value {Value} with data editor {DataEditor} and property editor alias {PropertyEditorAlias}.",
|
||||
value,
|
||||
dataValueReference.GetType().FullName,
|
||||
propertyEditorAlias ?? "n/a");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all relation type aliases that are automatically tracked.
|
||||
/// </summary>
|
||||
@@ -117,6 +169,7 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase<IDataVa
|
||||
[Obsolete("Use GetAllAutomaticRelationTypesAliases. This will be removed in Umbraco 15.")]
|
||||
public ISet<string> GetAutomaticRelationTypesAliases(PropertyEditorCollection propertyEditors) =>
|
||||
GetAllAutomaticRelationTypesAliases(propertyEditors);
|
||||
|
||||
public ISet<string> GetAllAutomaticRelationTypesAliases(PropertyEditorCollection propertyEditors)
|
||||
{
|
||||
// Always add default automatic relation types
|
||||
|
||||
@@ -99,17 +99,17 @@ public abstract class BlockValuePropertyValueEditorBase<TValue, TLayout> : DataV
|
||||
continue;
|
||||
}
|
||||
|
||||
var districtValues = valuesByPropertyEditorAlias.Distinct().ToArray();
|
||||
var distinctValues = valuesByPropertyEditorAlias.Distinct().ToArray();
|
||||
|
||||
if (dataEditor.GetValueEditor() is IDataValueReference reference)
|
||||
{
|
||||
foreach (UmbracoEntityReference value in districtValues.SelectMany(reference.GetReferences))
|
||||
foreach (UmbracoEntityReference value in distinctValues.SelectMany(reference.GetReferences))
|
||||
{
|
||||
result.Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<UmbracoEntityReference> references = _dataValueReferenceFactoryCollection.GetReferences(dataEditor, districtValues);
|
||||
IEnumerable<UmbracoEntityReference> references = _dataValueReferenceFactoryCollection.GetReferences(dataEditor, distinctValues);
|
||||
|
||||
foreach (UmbracoEntityReference value in references)
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -129,7 +130,7 @@ public class DocumentRepositoryTest : UmbracoIntegrationTest
|
||||
var propertyEditors =
|
||||
new PropertyEditorCollection(new DataEditorCollection(() => Enumerable.Empty<IDataEditor>()));
|
||||
var dataValueReferences =
|
||||
new DataValueReferenceFactoryCollection(() => Enumerable.Empty<IDataValueReferenceFactory>());
|
||||
new DataValueReferenceFactoryCollection(() => Enumerable.Empty<IDataValueReferenceFactory>(), new NullLogger<DataValueReferenceFactoryCollection>());
|
||||
var repository = new DocumentRepository(
|
||||
scopeAccessor,
|
||||
appCaches,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
@@ -64,7 +65,7 @@ public class MediaRepositoryTest : UmbracoIntegrationTest
|
||||
new PropertyEditorCollection(new DataEditorCollection(() => Enumerable.Empty<IDataEditor>()));
|
||||
var mediaUrlGenerators = new MediaUrlGeneratorCollection(() => Enumerable.Empty<IMediaUrlGenerator>());
|
||||
var dataValueReferences =
|
||||
new DataValueReferenceFactoryCollection(() => Enumerable.Empty<IDataValueReferenceFactory>());
|
||||
new DataValueReferenceFactoryCollection(() => Enumerable.Empty<IDataValueReferenceFactory>(), new NullLogger<DataValueReferenceFactoryCollection>());
|
||||
var repository = new MediaRepository(
|
||||
scopeAccessor,
|
||||
appCaches,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using NPoco;
|
||||
@@ -53,7 +54,7 @@ public class MemberRepositoryTest : UmbracoIntegrationTest
|
||||
var propertyEditors =
|
||||
new PropertyEditorCollection(new DataEditorCollection(() => Enumerable.Empty<IDataEditor>()));
|
||||
var dataValueReferences =
|
||||
new DataValueReferenceFactoryCollection(() => Enumerable.Empty<IDataValueReferenceFactory>());
|
||||
new DataValueReferenceFactoryCollection(() => Enumerable.Empty<IDataValueReferenceFactory>(), new NullLogger<DataValueReferenceFactoryCollection>());
|
||||
return new MemberRepository(
|
||||
accessor,
|
||||
AppCaches.Disabled,
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -272,7 +273,7 @@ public class TemplateRepositoryTest : UmbracoIntegrationTest
|
||||
var propertyEditors =
|
||||
new PropertyEditorCollection(new DataEditorCollection(() => Enumerable.Empty<IDataEditor>()));
|
||||
var dataValueReferences =
|
||||
new DataValueReferenceFactoryCollection(() => Enumerable.Empty<IDataValueReferenceFactory>());
|
||||
new DataValueReferenceFactoryCollection(() => Enumerable.Empty<IDataValueReferenceFactory>(), new NullLogger<DataValueReferenceFactoryCollection>());
|
||||
var contentRepo = new DocumentRepository(
|
||||
scopeAccessor,
|
||||
AppCaches.Disabled,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Globalization;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
@@ -148,7 +149,7 @@ public class BlockListEditorPropertyValueEditorTests
|
||||
new DataEditorAttribute("alias"),
|
||||
new BlockListEditorDataConverter(jsonSerializer),
|
||||
new(new DataEditorCollection(() => [])),
|
||||
new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>),
|
||||
new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>, Mock.Of<ILogger<DataValueReferenceFactoryCollection>>()),
|
||||
Mock.Of<IDataTypeConfigurationCache>(),
|
||||
Mock.Of<IBlockEditorElementTypeCache>(),
|
||||
localizedTextServiceMock.Object,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
@@ -35,7 +36,7 @@ public class DataValueEditorReuseTests
|
||||
Mock.Of<IIOHelper>()));
|
||||
|
||||
_propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>));
|
||||
_dataValueReferenceFactories = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>);
|
||||
_dataValueReferenceFactories = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>, new NullLogger<DataValueReferenceFactoryCollection>());
|
||||
|
||||
var blockVarianceHandler = new BlockEditorVarianceHandler(Mock.Of<ILanguageService>(), Mock.Of<IContentTypeService>());
|
||||
_dataValueEditorFactoryMock
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
@@ -49,7 +50,7 @@ public class DataValueReferenceFactoryCollectionTests
|
||||
[Test]
|
||||
public void GetAllReferences_All_Variants_With_IDataValueReferenceFactory()
|
||||
{
|
||||
var collection = new DataValueReferenceFactoryCollection(() => new TestDataValueReferenceFactory().Yield());
|
||||
var collection = new DataValueReferenceFactoryCollection(() => new TestDataValueReferenceFactory().Yield(), new NullLogger<DataValueReferenceFactoryCollection>());
|
||||
|
||||
// label does not implement IDataValueReference
|
||||
var labelEditor = new LabelPropertyEditor(
|
||||
@@ -93,7 +94,7 @@ public class DataValueReferenceFactoryCollectionTests
|
||||
[Test]
|
||||
public void GetAllReferences_All_Variants_With_IDataValueReference_Editor()
|
||||
{
|
||||
var collection = new DataValueReferenceFactoryCollection(() => Enumerable.Empty<IDataValueReferenceFactory>());
|
||||
var collection = new DataValueReferenceFactoryCollection(() => Enumerable.Empty<IDataValueReferenceFactory>(), new NullLogger<DataValueReferenceFactoryCollection>());
|
||||
|
||||
// mediaPicker does implement IDataValueReference
|
||||
var mediaPicker = new MediaPicker3PropertyEditor(
|
||||
@@ -137,7 +138,7 @@ public class DataValueReferenceFactoryCollectionTests
|
||||
[Test]
|
||||
public void GetAllReferences_Invariant_With_IDataValueReference_Editor()
|
||||
{
|
||||
var collection = new DataValueReferenceFactoryCollection(() => Enumerable.Empty<IDataValueReferenceFactory>());
|
||||
var collection = new DataValueReferenceFactoryCollection(() => Enumerable.Empty<IDataValueReferenceFactory>(), new NullLogger<DataValueReferenceFactoryCollection>());
|
||||
|
||||
// mediaPicker does implement IDataValueReference
|
||||
var mediaPicker = new MediaPicker3PropertyEditor(
|
||||
@@ -181,7 +182,7 @@ public class DataValueReferenceFactoryCollectionTests
|
||||
[Test]
|
||||
public void GetAutomaticRelationTypesAliases_ContainsDefault()
|
||||
{
|
||||
var collection = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>);
|
||||
var collection = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>, new NullLogger<DataValueReferenceFactoryCollection>());
|
||||
var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>));
|
||||
|
||||
var result = collection.GetAllAutomaticRelationTypesAliases(propertyEditors).ToArray();
|
||||
@@ -193,7 +194,7 @@ public class DataValueReferenceFactoryCollectionTests
|
||||
[Test]
|
||||
public void GetAutomaticRelationTypesAliases_ContainsCustom()
|
||||
{
|
||||
var collection = new DataValueReferenceFactoryCollection(() => new TestDataValueReferenceFactory().Yield());
|
||||
var collection = new DataValueReferenceFactoryCollection(() => new TestDataValueReferenceFactory().Yield(), new NullLogger<DataValueReferenceFactoryCollection>());
|
||||
|
||||
var labelPropertyEditor = new LabelPropertyEditor(DataValueEditorFactory, IOHelper);
|
||||
var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(() => labelPropertyEditor.Yield()));
|
||||
|
||||
Reference in New Issue
Block a user