Add (un)publishing details to TreeChange notifications (#17757)
This commit is contained in:
@@ -142,7 +142,9 @@ public static class DistributedCacheExtensions
|
||||
Id = x.Item.Id,
|
||||
Key = x.Item.Key,
|
||||
ChangeTypes = x.ChangeTypes,
|
||||
Blueprint = x.Item.Blueprint
|
||||
Blueprint = x.Item.Blueprint,
|
||||
PublishedCultures = x.PublishedCultures?.ToArray(),
|
||||
UnpublishedCultures = x.UnpublishedCultures?.ToArray()
|
||||
});
|
||||
|
||||
dc.RefreshByPayload(ContentCacheRefresher.UniqueId, payloads);
|
||||
|
||||
@@ -182,6 +182,10 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
|
||||
public TreeChangeTypes ChangeTypes { get; init; }
|
||||
|
||||
public bool Blueprint { get; init; }
|
||||
|
||||
public string[]? PublishedCultures { get; init; }
|
||||
|
||||
public string[]? UnpublishedCultures { get; init; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -32,4 +32,14 @@ public class ContentTreeChangeNotification : TreeChangeNotification<IContent>
|
||||
: base(new TreeChange<IContent>(target, changeTypes), messages)
|
||||
{
|
||||
}
|
||||
|
||||
public ContentTreeChangeNotification(
|
||||
IContent target,
|
||||
TreeChangeTypes changeTypes,
|
||||
IEnumerable<string>? publishedCultures,
|
||||
IEnumerable<string>? unpublishedCultures,
|
||||
EventMessages messages)
|
||||
: base(new TreeChange<IContent>(target, changeTypes, publishedCultures, unpublishedCultures), messages)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,22 @@ public class TreeChange<TItem>
|
||||
ChangeTypes = changeTypes;
|
||||
}
|
||||
|
||||
public TreeChange(TItem changedItem, TreeChangeTypes changeTypes, IEnumerable<string>? publishedCultures, IEnumerable<string>? unpublishedCultures)
|
||||
{
|
||||
Item = changedItem;
|
||||
ChangeTypes = changeTypes;
|
||||
PublishedCultures = publishedCultures;
|
||||
UnpublishedCultures = unpublishedCultures;
|
||||
}
|
||||
|
||||
public TItem Item { get; }
|
||||
|
||||
public TreeChangeTypes ChangeTypes { get; }
|
||||
|
||||
public IEnumerable<string>? PublishedCultures { get; }
|
||||
|
||||
public IEnumerable<string>? UnpublishedCultures { get; }
|
||||
|
||||
public EventArgs ToEventArgs() => new EventArgs(this);
|
||||
|
||||
public class EventArgs : System.EventArgs
|
||||
|
||||
@@ -1595,7 +1595,12 @@ public class ContentService : RepositoryService, IContentService
|
||||
// events and audit
|
||||
scope.Notifications.Publish(
|
||||
new ContentUnpublishedNotification(content, eventMessages).WithState(notificationState));
|
||||
scope.Notifications.Publish(new ContentTreeChangeNotification(content, TreeChangeTypes.RefreshBranch, eventMessages));
|
||||
scope.Notifications.Publish(new ContentTreeChangeNotification(
|
||||
content,
|
||||
TreeChangeTypes.RefreshBranch,
|
||||
variesByCulture ? culturesPublishing.IsCollectionEmpty() ? null : culturesPublishing : null,
|
||||
variesByCulture ? culturesUnpublishing.IsCollectionEmpty() ? null : culturesUnpublishing : ["*"],
|
||||
eventMessages));
|
||||
|
||||
if (culturesUnpublishing != null)
|
||||
{
|
||||
@@ -1654,7 +1659,12 @@ public class ContentService : RepositoryService, IContentService
|
||||
if (!branchOne)
|
||||
{
|
||||
scope.Notifications.Publish(
|
||||
new ContentTreeChangeNotification(content, changeType, eventMessages));
|
||||
new ContentTreeChangeNotification(
|
||||
content,
|
||||
changeType,
|
||||
variesByCulture ? culturesPublishing.IsCollectionEmpty() ? null : culturesPublishing : ["*"],
|
||||
variesByCulture ? culturesUnpublishing.IsCollectionEmpty() ? null : culturesUnpublishing : null,
|
||||
eventMessages));
|
||||
scope.Notifications.Publish(
|
||||
new ContentPublishedNotification(content, eventMessages).WithState(notificationState));
|
||||
}
|
||||
@@ -2118,7 +2128,8 @@ public class ContentService : RepositoryService, IContentService
|
||||
}
|
||||
|
||||
// deal with the branch root - if it fails, abort
|
||||
PublishResult? result = SaveAndPublishBranchItem(scope, document, shouldPublish, publishCultures, true, publishedDocuments, eventMessages, userId, allLangs, out IDictionary<string, object?> notificationState);
|
||||
HashSet<string>? culturesToPublish = shouldPublish(document);
|
||||
PublishResult? result = SaveAndPublishBranchItem(scope, document, culturesToPublish, publishCultures, true, publishedDocuments, eventMessages, userId, allLangs, out IDictionary<string, object?> notificationState);
|
||||
if (result != null)
|
||||
{
|
||||
results.Add(result);
|
||||
@@ -2128,6 +2139,8 @@ public class ContentService : RepositoryService, IContentService
|
||||
}
|
||||
}
|
||||
|
||||
HashSet<string> culturesPublished = culturesToPublish ?? [];
|
||||
|
||||
// deal with descendants
|
||||
// if one fails, abort its branch
|
||||
var exclude = new HashSet<int>();
|
||||
@@ -2153,12 +2166,14 @@ public class ContentService : RepositoryService, IContentService
|
||||
}
|
||||
|
||||
// no need to check path here, parent has to be published here
|
||||
result = SaveAndPublishBranchItem(scope, d, shouldPublish, publishCultures, false, publishedDocuments, eventMessages, userId, allLangs, out _);
|
||||
culturesToPublish = shouldPublish(d);
|
||||
result = SaveAndPublishBranchItem(scope, d, culturesToPublish, publishCultures, false, publishedDocuments, eventMessages, userId, allLangs, out _);
|
||||
if (result != null)
|
||||
{
|
||||
results.Add(result);
|
||||
if (result.Success)
|
||||
{
|
||||
culturesPublished.UnionWith(culturesToPublish ?? []);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -2175,8 +2190,14 @@ public class ContentService : RepositoryService, IContentService
|
||||
|
||||
// trigger events for the entire branch
|
||||
// (SaveAndPublishBranchOne does *not* do it)
|
||||
var variesByCulture = document.ContentType.VariesByCulture();
|
||||
scope.Notifications.Publish(
|
||||
new ContentTreeChangeNotification(document, TreeChangeTypes.RefreshBranch, eventMessages));
|
||||
new ContentTreeChangeNotification(
|
||||
document,
|
||||
TreeChangeTypes.RefreshBranch,
|
||||
variesByCulture ? culturesPublished.IsCollectionEmpty() ? null : culturesPublished : ["*"],
|
||||
null,
|
||||
eventMessages));
|
||||
scope.Notifications.Publish(new ContentPublishedNotification(publishedDocuments, eventMessages).WithState(notificationState));
|
||||
|
||||
scope.Complete();
|
||||
@@ -2191,7 +2212,7 @@ public class ContentService : RepositoryService, IContentService
|
||||
private PublishResult? SaveAndPublishBranchItem(
|
||||
ICoreScope scope,
|
||||
IContent document,
|
||||
Func<IContent, HashSet<string>?> shouldPublish,
|
||||
HashSet<string>? culturesToPublish,
|
||||
Func<IContent, HashSet<string>, IReadOnlyCollection<ILanguage>,
|
||||
bool> publishCultures,
|
||||
bool isRoot,
|
||||
@@ -2202,7 +2223,6 @@ public class ContentService : RepositoryService, IContentService
|
||||
out IDictionary<string, object?> notificationState)
|
||||
{
|
||||
notificationState = new Dictionary<string, object?>();
|
||||
HashSet<string>? culturesToPublish = shouldPublish(document);
|
||||
|
||||
// null = do not include
|
||||
if (culturesToPublish == null)
|
||||
|
||||
@@ -55,7 +55,8 @@ public class ContentServiceNotificationTests : UmbracoIntegrationTest
|
||||
.AddNotificationHandler<ContentPublishingNotification, ContentNotificationHandler>()
|
||||
.AddNotificationHandler<ContentPublishedNotification, ContentNotificationHandler>()
|
||||
.AddNotificationHandler<ContentUnpublishingNotification, ContentNotificationHandler>()
|
||||
.AddNotificationHandler<ContentUnpublishedNotification, ContentNotificationHandler>();
|
||||
.AddNotificationHandler<ContentUnpublishedNotification, ContentNotificationHandler>()
|
||||
.AddNotificationHandler<ContentTreeChangeNotification, ContentNotificationHandler>();
|
||||
|
||||
private void CreateTestData()
|
||||
{
|
||||
@@ -177,6 +178,67 @@ public class ContentServiceNotificationTests : UmbracoIntegrationTest
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Publishing_Invariant()
|
||||
{
|
||||
IContent document = new Content("content", -1, _contentType);
|
||||
|
||||
var treeChangeWasCalled = false;
|
||||
|
||||
ContentNotificationHandler.TreeChange += notification =>
|
||||
{
|
||||
var change = notification.Changes.FirstOrDefault();
|
||||
var publishedCultures = change?.PublishedCultures?.ToArray();
|
||||
Assert.IsNotNull(publishedCultures);
|
||||
Assert.AreEqual(1, publishedCultures.Length);
|
||||
Assert.IsTrue(publishedCultures.InvariantContains("*"));
|
||||
Assert.IsNull(change.UnpublishedCultures);
|
||||
|
||||
treeChangeWasCalled = true;
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
ContentService.SaveAndPublish(document);
|
||||
Assert.IsTrue(treeChangeWasCalled);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ContentNotificationHandler.TreeChange = null;
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Unpublishing_Invariant()
|
||||
{
|
||||
IContent document = new Content("content", -1, _contentType);
|
||||
ContentService.SaveAndPublish(document);
|
||||
|
||||
var treeChangeWasCalled = false;
|
||||
|
||||
ContentNotificationHandler.TreeChange += notification =>
|
||||
{
|
||||
var change = notification.Changes.FirstOrDefault();
|
||||
Assert.IsNull(change?.PublishedCultures);
|
||||
var unpublishedCultures = change?.UnpublishedCultures?.ToArray();
|
||||
Assert.IsNotNull(unpublishedCultures);
|
||||
Assert.AreEqual(1, unpublishedCultures.Length);
|
||||
Assert.IsTrue(unpublishedCultures.InvariantContains("*"));
|
||||
|
||||
treeChangeWasCalled = true;
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
ContentService.Unpublish(document);
|
||||
Assert.IsTrue(treeChangeWasCalled);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ContentNotificationHandler.TreeChange = null;
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Publishing_Culture()
|
||||
{
|
||||
@@ -203,6 +265,7 @@ public class ContentServiceNotificationTests : UmbracoIntegrationTest
|
||||
|
||||
var publishingWasCalled = false;
|
||||
var publishedWasCalled = false;
|
||||
var treeChangeWasCalled = false;
|
||||
|
||||
ContentNotificationHandler.PublishingContent += notification =>
|
||||
{
|
||||
@@ -228,16 +291,30 @@ public class ContentServiceNotificationTests : UmbracoIntegrationTest
|
||||
publishedWasCalled = true;
|
||||
};
|
||||
|
||||
ContentNotificationHandler.TreeChange += notification =>
|
||||
{
|
||||
var change = notification.Changes.FirstOrDefault();
|
||||
var publishedCultures = change?.PublishedCultures?.ToArray();
|
||||
Assert.IsNotNull(publishedCultures);
|
||||
Assert.AreEqual(1, publishedCultures.Length);
|
||||
Assert.IsTrue(publishedCultures.InvariantContains("fr-FR"));
|
||||
Assert.IsNull(change.UnpublishedCultures);
|
||||
|
||||
treeChangeWasCalled = true;
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
ContentService.SaveAndPublish(document, "fr-FR");
|
||||
Assert.IsTrue(publishingWasCalled);
|
||||
Assert.IsTrue(publishedWasCalled);
|
||||
Assert.IsTrue(treeChangeWasCalled);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ContentNotificationHandler.PublishingContent = null;
|
||||
ContentNotificationHandler.PublishedContent = null;
|
||||
ContentNotificationHandler.TreeChange = null;
|
||||
}
|
||||
|
||||
document = ContentService.GetById(document.Id);
|
||||
@@ -366,6 +443,7 @@ public class ContentServiceNotificationTests : UmbracoIntegrationTest
|
||||
|
||||
var publishingWasCalled = false;
|
||||
var publishedWasCalled = false;
|
||||
var treeChangeWasCalled = false;
|
||||
|
||||
// TODO: revisit this - it was migrated when removing static events, but the expected result seems illogic - why does this test bind to Published and not Unpublished?
|
||||
|
||||
@@ -399,16 +477,30 @@ public class ContentServiceNotificationTests : UmbracoIntegrationTest
|
||||
publishedWasCalled = true;
|
||||
};
|
||||
|
||||
ContentNotificationHandler.TreeChange += notification =>
|
||||
{
|
||||
var change = notification.Changes.FirstOrDefault();
|
||||
var unpublishedCultures = change?.UnpublishedCultures?.ToArray();
|
||||
Assert.IsNotNull(unpublishedCultures);
|
||||
Assert.AreEqual(1, unpublishedCultures.Length);
|
||||
Assert.IsTrue(unpublishedCultures.InvariantContains("fr-FR"));
|
||||
Assert.IsNull(change.PublishedCultures);
|
||||
|
||||
treeChangeWasCalled = true;
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
ContentService.CommitDocumentChanges(document);
|
||||
Assert.IsTrue(publishingWasCalled);
|
||||
Assert.IsTrue(publishedWasCalled);
|
||||
Assert.IsTrue(treeChangeWasCalled);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ContentNotificationHandler.PublishingContent = null;
|
||||
ContentNotificationHandler.PublishedContent = null;
|
||||
ContentNotificationHandler.TreeChange = null;
|
||||
}
|
||||
|
||||
document = ContentService.GetById(document.Id);
|
||||
@@ -423,7 +515,8 @@ public class ContentServiceNotificationTests : UmbracoIntegrationTest
|
||||
INotificationHandler<ContentPublishingNotification>,
|
||||
INotificationHandler<ContentPublishedNotification>,
|
||||
INotificationHandler<ContentUnpublishingNotification>,
|
||||
INotificationHandler<ContentUnpublishedNotification>
|
||||
INotificationHandler<ContentUnpublishedNotification>,
|
||||
INotificationHandler<ContentTreeChangeNotification>
|
||||
{
|
||||
public static Action<ContentSavingNotification> SavingContent { get; set; }
|
||||
|
||||
@@ -437,6 +530,8 @@ public class ContentServiceNotificationTests : UmbracoIntegrationTest
|
||||
|
||||
public static Action<ContentUnpublishedNotification> UnpublishedContent { get; set; }
|
||||
|
||||
public static Action<ContentTreeChangeNotification> TreeChange { get; set; }
|
||||
|
||||
public void Handle(ContentPublishedNotification notification) => PublishedContent?.Invoke(notification);
|
||||
|
||||
public void Handle(ContentPublishingNotification notification) => PublishingContent?.Invoke(notification);
|
||||
@@ -447,5 +542,7 @@ public class ContentServiceNotificationTests : UmbracoIntegrationTest
|
||||
public void Handle(ContentUnpublishedNotification notification) => UnpublishedContent?.Invoke(notification);
|
||||
|
||||
public void Handle(ContentUnpublishingNotification notification) => UnpublishingContent?.Invoke(notification);
|
||||
|
||||
public void Handle(ContentTreeChangeNotification notification) => TreeChange?.Invoke(notification);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,26 +27,68 @@ public class RefresherTests
|
||||
Assert.AreEqual(source[0].ChangeTypes, payload[0].ChangeTypes);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ContentCacheRefresherCanDeserializeJsonPayload()
|
||||
[TestCase(TreeChangeTypes.None, false)]
|
||||
[TestCase(TreeChangeTypes.RefreshAll, true)]
|
||||
[TestCase(TreeChangeTypes.RefreshBranch, false)]
|
||||
[TestCase(TreeChangeTypes.Remove, true)]
|
||||
[TestCase(TreeChangeTypes.RefreshNode, false)]
|
||||
public void ContentCacheRefresherCanDeserializeJsonPayload(TreeChangeTypes changeTypes, bool blueprint)
|
||||
{
|
||||
var key = Guid.NewGuid();
|
||||
ContentCacheRefresher.JsonPayload[] source =
|
||||
{
|
||||
new ContentCacheRefresher.JsonPayload()
|
||||
{
|
||||
Id = 1234,
|
||||
Key = Guid.NewGuid(),
|
||||
ChangeTypes = TreeChangeTypes.None
|
||||
Key = key,
|
||||
ChangeTypes = changeTypes,
|
||||
Blueprint = blueprint
|
||||
}
|
||||
};
|
||||
|
||||
var json = JsonConvert.SerializeObject(source);
|
||||
var payload = JsonConvert.DeserializeObject<ContentCacheRefresher.JsonPayload[]>(json);
|
||||
|
||||
Assert.AreEqual(source[0].Id, payload[0].Id);
|
||||
Assert.AreEqual(source[0].Key, payload[0].Key);
|
||||
Assert.AreEqual(source[0].ChangeTypes, payload[0].ChangeTypes);
|
||||
Assert.AreEqual(source[0].Blueprint, payload[0].Blueprint);
|
||||
Assert.AreEqual(1234, payload[0].Id);
|
||||
Assert.AreEqual(key, payload[0].Key);
|
||||
Assert.AreEqual(changeTypes, payload[0].ChangeTypes);
|
||||
Assert.AreEqual(blueprint, payload[0].Blueprint);
|
||||
Assert.IsNull(payload[0].PublishedCultures);
|
||||
Assert.IsNull(payload[0].UnpublishedCultures);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ContentCacheRefresherCanDeserializeJsonPayloadWithCultures()
|
||||
{
|
||||
var key = Guid.NewGuid();
|
||||
ContentCacheRefresher.JsonPayload[] source =
|
||||
{
|
||||
new ContentCacheRefresher.JsonPayload()
|
||||
{
|
||||
Id = 1234,
|
||||
Key = key,
|
||||
PublishedCultures = ["en-US", "da-DK"],
|
||||
UnpublishedCultures = ["de-DE"]
|
||||
}
|
||||
};
|
||||
|
||||
var json = JsonConvert.SerializeObject(source);
|
||||
var payload = JsonConvert.DeserializeObject<ContentCacheRefresher.JsonPayload[]>(json);
|
||||
|
||||
Assert.IsNotNull(payload[0].PublishedCultures);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(2, payload[0].PublishedCultures.Length);
|
||||
Assert.AreEqual("en-US", payload[0].PublishedCultures.First());
|
||||
Assert.AreEqual("da-DK", payload[0].PublishedCultures.Last());
|
||||
});
|
||||
|
||||
Assert.IsNotNull(payload[0].UnpublishedCultures);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(1, payload[0].UnpublishedCultures.Length);
|
||||
Assert.AreEqual("de-DE", payload[0].UnpublishedCultures.First());
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
Reference in New Issue
Block a user