Skip to content

Commit 3f10bd8

Browse files
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 <[email protected]>
1 parent b13eb8a commit 3f10bd8

File tree

9 files changed

+81
-21
lines changed

9 files changed

+81
-21
lines changed

src/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollection.cs

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Microsoft.Extensions.Logging;
13
using Umbraco.Cms.Core.Composing;
4+
using Umbraco.Cms.Core.DependencyInjection;
25
using Umbraco.Cms.Core.Models;
36
using Umbraco.Cms.Core.Models.Editors;
47

@@ -12,14 +15,27 @@ public class DataValueReferenceFactoryCollection : BuilderCollectionBase<IDataVa
1215
// TODO: We could further reduce circular dependencies with PropertyEditorCollection by not having IDataValueReference implemented
1316
// by property editors and instead just use the already built in IDataValueReferenceFactory and/or refactor that into a more normal collection
1417

18+
private readonly ILogger<DataValueReferenceFactoryCollection> _logger;
19+
1520
/// <summary>
1621
/// Initializes a new instance of the <see cref="DataValueReferenceFactoryCollection" /> class.
1722
/// </summary>
1823
/// <param name="items">The items.</param>
24+
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 17.")]
1925
public DataValueReferenceFactoryCollection(Func<IEnumerable<IDataValueReferenceFactory>> items)
20-
: base(items)
26+
: this(
27+
items,
28+
StaticServiceProvider.Instance.GetRequiredService<ILogger<DataValueReferenceFactoryCollection>>())
2129
{ }
2230

31+
/// <summary>
32+
/// Initializes a new instance of the <see cref="DataValueReferenceFactoryCollection" /> class.
33+
/// </summary>
34+
/// <param name="items">The items.</param>
35+
/// <param name="logger">The logger.</param>
36+
public DataValueReferenceFactoryCollection(Func<IEnumerable<IDataValueReferenceFactory>> items, ILogger<DataValueReferenceFactoryCollection> logger)
37+
: base(items) => _logger = logger;
38+
2339
/// <summary>
2440
/// Gets all unique references from the specified properties.
2541
/// </summary>
@@ -33,7 +49,7 @@ public ISet<UmbracoEntityReference> GetAllReferences(IPropertyCollection propert
3349
var references = new HashSet<UmbracoEntityReference>();
3450

3551
// Group by property editor alias to avoid duplicate lookups and optimize value parsing
36-
foreach (var propertyValuesByPropertyEditorAlias in properties.GroupBy(x => x.PropertyType.PropertyEditorAlias, x => x.Values))
52+
foreach (IGrouping<string, IReadOnlyCollection<IPropertyValue>> propertyValuesByPropertyEditorAlias in properties.GroupBy(x => x.PropertyType.PropertyEditorAlias, x => x.Values))
3753
{
3854
if (!propertyEditors.TryGet(propertyValuesByPropertyEditorAlias.Key, out IDataEditor? dataEditor))
3955
{
@@ -48,7 +64,7 @@ public ISet<UmbracoEntityReference> GetAllReferences(IPropertyCollection propert
4864
values.Add(propertyValue.PublishedValue);
4965
}
5066

51-
references.UnionWith(GetReferences(dataEditor, values));
67+
references.UnionWith(GetReferences(dataEditor, values, propertyValuesByPropertyEditorAlias.Key));
5268
}
5369

5470
return references;
@@ -74,14 +90,18 @@ public IEnumerable<UmbracoEntityReference> GetReferences(IDataEditor dataEditor,
7490
/// The references.
7591
/// </returns>
7692
public ISet<UmbracoEntityReference> GetReferences(IDataEditor dataEditor, IEnumerable<object?> values) =>
77-
GetReferencesEnumerable(dataEditor, values).ToHashSet();
78-
private IEnumerable<UmbracoEntityReference> GetReferencesEnumerable(IDataEditor dataEditor, IEnumerable<object?> values)
93+
GetReferencesEnumerable(dataEditor, values, null).ToHashSet();
94+
95+
private ISet<UmbracoEntityReference> GetReferences(IDataEditor dataEditor, IEnumerable<object?> values, string propertyEditorAlias) =>
96+
GetReferencesEnumerable(dataEditor, values, propertyEditorAlias).ToHashSet();
97+
98+
private IEnumerable<UmbracoEntityReference> GetReferencesEnumerable(IDataEditor dataEditor, IEnumerable<object?> values, string? propertyEditorAlias)
7999
{
80100
// TODO: We will need to change this once we support tracking via variants/segments
81101
// for now, we are tracking values from ALL variants
82102
if (dataEditor.GetValueEditor() is IDataValueReference dataValueReference)
83103
{
84-
foreach (UmbracoEntityReference reference in values.SelectMany(dataValueReference.GetReferences))
104+
foreach (UmbracoEntityReference reference in GetReferencesFromPropertyValues(values, dataValueReference, propertyEditorAlias))
85105
{
86106
yield return reference;
87107
}
@@ -107,6 +127,38 @@ private IEnumerable<UmbracoEntityReference> GetReferencesEnumerable(IDataEditor
107127
}
108128
}
109129

130+
private IEnumerable<UmbracoEntityReference> GetReferencesFromPropertyValues(IEnumerable<object?> values, IDataValueReference dataValueReference, string? propertyEditorAlias)
131+
{
132+
var result = new List<UmbracoEntityReference>();
133+
foreach (var value in values)
134+
{
135+
// When property editors on data types are changed, we could have values that are incompatible with the new editor.
136+
// Leading to issues such as:
137+
// - https://github.com/umbraco/Umbraco-CMS/issues/17628
138+
// - https://github.com/umbraco/Umbraco-CMS/issues/17725
139+
// Although some changes like this are not intended to be compatible, we should handle them gracefully and not
140+
// error in retrieving references, which would prevent manipulating or deleting the content that uses the data type.
141+
try
142+
{
143+
IEnumerable<UmbracoEntityReference> references = dataValueReference.GetReferences(value);
144+
result.AddRange(references);
145+
}
146+
catch (Exception ex)
147+
{
148+
// Log the exception but don't throw, continue with the next value.
149+
_logger.LogError(
150+
ex,
151+
"Error getting references from value {Value} with data editor {DataEditor} and property editor alias {PropertyEditorAlias}.",
152+
value,
153+
dataValueReference.GetType().FullName,
154+
propertyEditorAlias ?? "n/a");
155+
throw;
156+
}
157+
}
158+
159+
return result;
160+
}
161+
110162
/// <summary>
111163
/// Gets all relation type aliases that are automatically tracked.
112164
/// </summary>
@@ -117,6 +169,7 @@ private IEnumerable<UmbracoEntityReference> GetReferencesEnumerable(IDataEditor
117169
[Obsolete("Use GetAllAutomaticRelationTypesAliases. This will be removed in Umbraco 15.")]
118170
public ISet<string> GetAutomaticRelationTypesAliases(PropertyEditorCollection propertyEditors) =>
119171
GetAllAutomaticRelationTypesAliases(propertyEditors);
172+
120173
public ISet<string> GetAllAutomaticRelationTypesAliases(PropertyEditorCollection propertyEditors)
121174
{
122175
// Always add default automatic relation types

src/Umbraco.Infrastructure/PropertyEditors/BlockValuePropertyValueEditorBase.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,17 +99,17 @@ protected IEnumerable<UmbracoEntityReference> GetBlockValueReferences(TValue blo
9999
continue;
100100
}
101101

102-
var districtValues = valuesByPropertyEditorAlias.Distinct().ToArray();
102+
var distinctValues = valuesByPropertyEditorAlias.Distinct().ToArray();
103103

104104
if (dataEditor.GetValueEditor() is IDataValueReference reference)
105105
{
106-
foreach (UmbracoEntityReference value in districtValues.SelectMany(reference.GetReferences))
106+
foreach (UmbracoEntityReference value in distinctValues.SelectMany(reference.GetReferences))
107107
{
108108
result.Add(value);
109109
}
110110
}
111111

112-
IEnumerable<UmbracoEntityReference> references = _dataValueReferenceFactoryCollection.GetReferences(dataEditor, districtValues);
112+
IEnumerable<UmbracoEntityReference> references = _dataValueReferenceFactoryCollection.GetReferences(dataEditor, distinctValues);
113113

114114
foreach (UmbracoEntityReference value in references)
115115
{

tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Collections.Generic;
55
using System.Linq;
66
using Microsoft.Extensions.Logging;
7+
using Microsoft.Extensions.Logging.Abstractions;
78
using Microsoft.Extensions.Options;
89
using Moq;
910
using NUnit.Framework;
@@ -129,7 +130,7 @@ private DocumentRepository CreateRepository(IScopeAccessor scopeAccessor, out Co
129130
var propertyEditors =
130131
new PropertyEditorCollection(new DataEditorCollection(() => Enumerable.Empty<IDataEditor>()));
131132
var dataValueReferences =
132-
new DataValueReferenceFactoryCollection(() => Enumerable.Empty<IDataValueReferenceFactory>());
133+
new DataValueReferenceFactoryCollection(() => Enumerable.Empty<IDataValueReferenceFactory>(), new NullLogger<DataValueReferenceFactoryCollection>());
133134
var repository = new DocumentRepository(
134135
scopeAccessor,
135136
appCaches,

tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaRepositoryTest.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Linq;
55
using Microsoft.Extensions.Logging;
6+
using Microsoft.Extensions.Logging.Abstractions;
67
using Moq;
78
using NUnit.Framework;
89
using Umbraco.Cms.Core;
@@ -64,7 +65,7 @@ private MediaRepository CreateRepository(IScopeProvider provider, out MediaTypeR
6465
new PropertyEditorCollection(new DataEditorCollection(() => Enumerable.Empty<IDataEditor>()));
6566
var mediaUrlGenerators = new MediaUrlGeneratorCollection(() => Enumerable.Empty<IMediaUrlGenerator>());
6667
var dataValueReferences =
67-
new DataValueReferenceFactoryCollection(() => Enumerable.Empty<IDataValueReferenceFactory>());
68+
new DataValueReferenceFactoryCollection(() => Enumerable.Empty<IDataValueReferenceFactory>(), new NullLogger<DataValueReferenceFactoryCollection>());
6869
var repository = new MediaRepository(
6970
scopeAccessor,
7071
appCaches,

tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MemberRepositoryTest.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Diagnostics;
55
using System.Linq;
66
using Microsoft.Extensions.Logging;
7+
using Microsoft.Extensions.Logging.Abstractions;
78
using Microsoft.Extensions.Options;
89
using Moq;
910
using NPoco;
@@ -53,7 +54,7 @@ private MemberRepository CreateRepository(IScopeProvider provider)
5354
var propertyEditors =
5455
new PropertyEditorCollection(new DataEditorCollection(() => Enumerable.Empty<IDataEditor>()));
5556
var dataValueReferences =
56-
new DataValueReferenceFactoryCollection(() => Enumerable.Empty<IDataValueReferenceFactory>());
57+
new DataValueReferenceFactoryCollection(() => Enumerable.Empty<IDataValueReferenceFactory>(), new NullLogger<DataValueReferenceFactoryCollection>());
5758
return new MemberRepository(
5859
accessor,
5960
AppCaches.Disabled,

tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Linq;
77
using System.Text;
88
using Microsoft.Extensions.Logging;
9+
using Microsoft.Extensions.Logging.Abstractions;
910
using Microsoft.Extensions.Options;
1011
using Moq;
1112
using NUnit.Framework;
@@ -272,7 +273,7 @@ public void Can_Perform_Delete_When_Assigned_To_Doc()
272273
var propertyEditors =
273274
new PropertyEditorCollection(new DataEditorCollection(() => Enumerable.Empty<IDataEditor>()));
274275
var dataValueReferences =
275-
new DataValueReferenceFactoryCollection(() => Enumerable.Empty<IDataValueReferenceFactory>());
276+
new DataValueReferenceFactoryCollection(() => Enumerable.Empty<IDataValueReferenceFactory>(), new NullLogger<DataValueReferenceFactoryCollection>());
276277
var contentRepo = new DocumentRepository(
277278
scopeAccessor,
278279
AppCaches.Disabled,

tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListEditorPropertyValueEditorTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Globalization;
22
using System.Text.Json.Nodes;
3+
using Microsoft.Extensions.Logging;
34
using Microsoft.Extensions.Logging.Abstractions;
45
using Moq;
56
using NUnit.Framework;
@@ -148,7 +149,7 @@ private static BlockListEditorPropertyValueEditor CreateValueEditor()
148149
new DataEditorAttribute("alias"),
149150
new BlockListEditorDataConverter(jsonSerializer),
150151
new(new DataEditorCollection(() => [])),
151-
new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>),
152+
new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>, Mock.Of<ILogger<DataValueReferenceFactoryCollection>>()),
152153
Mock.Of<IDataTypeConfigurationCache>(),
153154
Mock.Of<IBlockEditorElementTypeCache>(),
154155
localizedTextServiceMock.Object,

tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Microsoft.Extensions.Logging;
1+
using Microsoft.Extensions.Logging;
2+
using Microsoft.Extensions.Logging.Abstractions;
23
using Moq;
34
using NUnit.Framework;
45
using Umbraco.Cms.Core.Cache;
@@ -35,7 +36,7 @@ public void SetUp()
3536
Mock.Of<IIOHelper>()));
3637

3738
_propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>));
38-
_dataValueReferenceFactories = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>);
39+
_dataValueReferenceFactories = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>, new NullLogger<DataValueReferenceFactoryCollection>());
3940

4041
var blockVarianceHandler = new BlockEditorVarianceHandler(Mock.Of<ILanguageService>(), Mock.Of<IContentTypeService>());
4142
_dataValueEditorFactoryMock

tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueReferenceFactoryCollectionTests.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Umbraco.
22
// See LICENSE for more details.
33

4+
using Microsoft.Extensions.Logging.Abstractions;
45
using Moq;
56
using NUnit.Framework;
67
using Umbraco.Cms.Core;
@@ -49,7 +50,7 @@ public class DataValueReferenceFactoryCollectionTests
4950
[Test]
5051
public void GetAllReferences_All_Variants_With_IDataValueReferenceFactory()
5152
{
52-
var collection = new DataValueReferenceFactoryCollection(() => new TestDataValueReferenceFactory().Yield());
53+
var collection = new DataValueReferenceFactoryCollection(() => new TestDataValueReferenceFactory().Yield(), new NullLogger<DataValueReferenceFactoryCollection>());
5354

5455
// label does not implement IDataValueReference
5556
var labelEditor = new LabelPropertyEditor(
@@ -93,7 +94,7 @@ public void GetAllReferences_All_Variants_With_IDataValueReferenceFactory()
9394
[Test]
9495
public void GetAllReferences_All_Variants_With_IDataValueReference_Editor()
9596
{
96-
var collection = new DataValueReferenceFactoryCollection(() => Enumerable.Empty<IDataValueReferenceFactory>());
97+
var collection = new DataValueReferenceFactoryCollection(() => Enumerable.Empty<IDataValueReferenceFactory>(), new NullLogger<DataValueReferenceFactoryCollection>());
9798

9899
// mediaPicker does implement IDataValueReference
99100
var mediaPicker = new MediaPicker3PropertyEditor(
@@ -137,7 +138,7 @@ public void GetAllReferences_All_Variants_With_IDataValueReference_Editor()
137138
[Test]
138139
public void GetAllReferences_Invariant_With_IDataValueReference_Editor()
139140
{
140-
var collection = new DataValueReferenceFactoryCollection(() => Enumerable.Empty<IDataValueReferenceFactory>());
141+
var collection = new DataValueReferenceFactoryCollection(() => Enumerable.Empty<IDataValueReferenceFactory>(), new NullLogger<DataValueReferenceFactoryCollection>());
141142

142143
// mediaPicker does implement IDataValueReference
143144
var mediaPicker = new MediaPicker3PropertyEditor(
@@ -181,7 +182,7 @@ public void GetAllReferences_Invariant_With_IDataValueReference_Editor()
181182
[Test]
182183
public void GetAutomaticRelationTypesAliases_ContainsDefault()
183184
{
184-
var collection = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>);
185+
var collection = new DataValueReferenceFactoryCollection(Enumerable.Empty<IDataValueReferenceFactory>, new NullLogger<DataValueReferenceFactoryCollection>());
185186
var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<IDataEditor>));
186187

187188
var result = collection.GetAllAutomaticRelationTypesAliases(propertyEditors).ToArray();
@@ -193,7 +194,7 @@ public void GetAutomaticRelationTypesAliases_ContainsDefault()
193194
[Test]
194195
public void GetAutomaticRelationTypesAliases_ContainsCustom()
195196
{
196-
var collection = new DataValueReferenceFactoryCollection(() => new TestDataValueReferenceFactory().Yield());
197+
var collection = new DataValueReferenceFactoryCollection(() => new TestDataValueReferenceFactory().Yield(), new NullLogger<DataValueReferenceFactoryCollection>());
197198

198199
var labelPropertyEditor = new LabelPropertyEditor(DataValueEditorFactory, IOHelper);
199200
var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(() => labelPropertyEditor.Yield()));

0 commit comments

Comments
 (0)