* ContentVersion cleanup backoffice UI (#11637) * init rollback ui prototype * add busy state to button, deselect version, add pagination status * add localisation * style current version * disable rollback button when nothing is selected * stop click event * Endpoints for paginated content versions. Light on tests, tight on time. * Endpoints to "pin" content versions * camel case json output. Not sure why json formatter not set for controller, bit risky to add it now * wire up paging * wire up pin/unpin * rename getPagedRollbackVersions to getPagedContentVersions * prevent selection of current version and current draft * add current draft and current version to UI * remove pointer if the row is not selectable * Improve warning for globally disabled cleanup feature. * Fix current loses prevent cleanup state on publish. * Added umbracoLog audit entries for "pin" / "unpin" * Match v9 defaults for keepVersions settings * Fix - losing preventCleanup on save current with content changes * update pin/unpin button labels * fix pagination bug * add missing " * always send culture when a doc type can vary Co-authored-by: Mads Rasmussen <madsr@hey.com> # Conflicts: # src/Umbraco.Core/ContentEditing/ContentVersionMetaViewModel.cs # src/Umbraco.Core/Models/HistoricContentVersionMeta.cs # src/Umbraco.Infrastructure/Services/Implement/ContentService.cs # src/Umbraco.Tests/Persistence/Repositories/DocumentVersionRepository_Tests_Integration.cs # src/Umbraco.Tests/Services/ContentVersionCleanupService_Tests_UnitTests.cs # src/Umbraco.Web.BackOffice/Controllers/ContentController.cs # src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js # src/Umbraco.Web.UI/config/umbracoSettings.Release.config # src/Umbraco.Web.UI/umbraco/config/lang/en.xml # src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml # src/Umbraco.Web/Umbraco.Web.csproj * Added tests * Misc - missed translation update * Bugfix - DocumentVersionRepository.Get should not join culture variation * Bugfix - Missing write lock * Removed unnecessary view model * Misc - kill some warnings * Misc - Kill some more warnings * Fixed cypress rollback test * Bugfix - Policy returns items to delete not items to keep. Switch to inverse behavior. # Conflicts: # src/Umbraco.Tests/Services/DefaultContentVersionCleanupPolicy_Tests_UnitTests.cs Co-authored-by: Paul Johnson <pmj@umbraco.com>
275 lines
12 KiB
C#
275 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Moq;
|
|
using NUnit.Framework;
|
|
using Umbraco.Cms.Core.Actions;
|
|
using Umbraco.Cms.Core.Dictionary;
|
|
using Umbraco.Cms.Core.Events;
|
|
using Umbraco.Cms.Core.Mapping;
|
|
using Umbraco.Cms.Core.Models;
|
|
using Umbraco.Cms.Core.Models.ContentEditing;
|
|
using Umbraco.Cms.Core.PropertyEditors;
|
|
using Umbraco.Cms.Core.Routing;
|
|
using Umbraco.Cms.Core.Scoping;
|
|
using Umbraco.Cms.Core.Security;
|
|
using Umbraco.Cms.Core.Serialization;
|
|
using Umbraco.Cms.Core.Services;
|
|
using Umbraco.Cms.Core.Strings;
|
|
using Umbraco.Cms.Infrastructure.Persistence;
|
|
using Umbraco.Cms.Tests.Common.Builders;
|
|
using Umbraco.Cms.Tests.Common.Builders.Extensions;
|
|
using Umbraco.Cms.Web.BackOffice.Controllers;
|
|
|
|
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers
|
|
{
|
|
[TestFixture]
|
|
public class ContentControllerTests
|
|
{
|
|
[Test]
|
|
public void Root_Node_With_Domains_Causes_No_Warning()
|
|
{
|
|
// Setup domain service
|
|
var domainServiceMock = new Mock<IDomainService>();
|
|
domainServiceMock.Setup(x => x.GetAssignedDomains(1060, It.IsAny<bool>()))
|
|
.Returns(new []{new UmbracoDomain("/", "da-dk"), new UmbracoDomain("/en", "en-us")});
|
|
|
|
// Create content, we need to specify and ID in order to be able to configure domain service
|
|
Content rootNode = new ContentBuilder()
|
|
.WithContentType(CreateContentType())
|
|
.WithId(1060)
|
|
.AddContentCultureInfosCollection()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("da-dk")
|
|
.Done()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("en-us")
|
|
.Done()
|
|
.Done()
|
|
.Build();
|
|
|
|
var culturesPublished = new []{ "en-us", "da-dk" };
|
|
var notifications = new SimpleNotificationModel();
|
|
|
|
ContentController contentController = CreateContentController(domainServiceMock.Object);
|
|
contentController.AddDomainWarnings(rootNode, culturesPublished, notifications);
|
|
|
|
Assert.IsEmpty(notifications.Notifications);
|
|
}
|
|
|
|
[Test]
|
|
public void Node_With_Single_Published_Culture_Causes_No_Warning()
|
|
{
|
|
var domainServiceMock = new Mock<IDomainService>();
|
|
domainServiceMock.Setup(x => x.GetAssignedDomains(It.IsAny<int>(), It.IsAny<bool>()))
|
|
.Returns(Enumerable.Empty<IDomain>());
|
|
|
|
Content rootNode = new ContentBuilder()
|
|
.WithContentType(CreateContentType())
|
|
.WithId(1060)
|
|
.AddContentCultureInfosCollection()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("da-dk")
|
|
.Done()
|
|
.Done()
|
|
.Build();
|
|
|
|
var culturesPublished = new []{"da-dk" };
|
|
var notifications = new SimpleNotificationModel();
|
|
|
|
ContentController contentController = CreateContentController(domainServiceMock.Object);
|
|
contentController.AddDomainWarnings(rootNode, culturesPublished, notifications);
|
|
|
|
Assert.IsEmpty(notifications.Notifications);
|
|
}
|
|
|
|
[Test]
|
|
public void Root_Node_Without_Domains_Causes_SingleWarning()
|
|
{
|
|
var domainServiceMock = new Mock<IDomainService>();
|
|
domainServiceMock.Setup(x => x.GetAssignedDomains(It.IsAny<int>(), It.IsAny<bool>()))
|
|
.Returns(Enumerable.Empty<IDomain>());
|
|
|
|
Content rootNode = new ContentBuilder()
|
|
.WithContentType(CreateContentType())
|
|
.WithId(1060)
|
|
.AddContentCultureInfosCollection()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("da-dk")
|
|
.Done()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("en-us")
|
|
.Done()
|
|
.Done()
|
|
.Build();
|
|
|
|
var culturesPublished = new []{ "en-us", "da-dk" };
|
|
var notifications = new SimpleNotificationModel();
|
|
|
|
ContentController contentController = CreateContentController(domainServiceMock.Object);
|
|
contentController.AddDomainWarnings(rootNode, culturesPublished, notifications);
|
|
Assert.AreEqual(1, notifications.Notifications.Count(x => x.NotificationType == NotificationStyle.Warning));
|
|
}
|
|
|
|
[Test]
|
|
public void One_Warning_Per_Culture_Being_Published()
|
|
{
|
|
var domainServiceMock = new Mock<IDomainService>();
|
|
domainServiceMock.Setup(x => x.GetAssignedDomains(It.IsAny<int>(), It.IsAny<bool>()))
|
|
.Returns(new []{new UmbracoDomain("/", "da-dk")});
|
|
|
|
|
|
Content rootNode = new ContentBuilder()
|
|
.WithContentType(CreateContentType())
|
|
.WithId(1060)
|
|
.AddContentCultureInfosCollection()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("da-dk")
|
|
.Done()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("en-us")
|
|
.Done()
|
|
.Done()
|
|
.Build();
|
|
|
|
var culturesPublished = new []{ "en-us", "da-dk", "nl-bk", "se-sv" };
|
|
var notifications = new SimpleNotificationModel();
|
|
|
|
ContentController contentController = CreateContentController(domainServiceMock.Object);
|
|
contentController.AddDomainWarnings(rootNode, culturesPublished, notifications);
|
|
Assert.AreEqual(3, notifications.Notifications.Count(x => x.NotificationType == NotificationStyle.Warning));
|
|
}
|
|
|
|
[Test]
|
|
public void Ancestor_Domains_Counts()
|
|
{
|
|
var rootId = 1060;
|
|
var level1Id = 1061;
|
|
var level2Id = 1062;
|
|
var level3Id = 1063;
|
|
|
|
var domainServiceMock = new Mock<IDomainService>();
|
|
domainServiceMock.Setup(x => x.GetAssignedDomains(rootId, It.IsAny<bool>()))
|
|
.Returns(new[] { new UmbracoDomain("/", "da-dk") });
|
|
|
|
domainServiceMock.Setup(x => x.GetAssignedDomains(level1Id, It.IsAny<bool>()))
|
|
.Returns(new[] { new UmbracoDomain("/en", "en-us") });
|
|
|
|
domainServiceMock.Setup(x => x.GetAssignedDomains(level2Id, It.IsAny<bool>()))
|
|
.Returns(new[] { new UmbracoDomain("/se", "se-sv"), new UmbracoDomain("/nl", "nl-bk") });
|
|
|
|
Content level3Node = new ContentBuilder()
|
|
.WithContentType(CreateContentType())
|
|
.WithId(level3Id)
|
|
.WithPath($"-1,{rootId},{level1Id},{level2Id},{level3Id}")
|
|
.AddContentCultureInfosCollection()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("da-dk")
|
|
.Done()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("en-us")
|
|
.Done()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("se-sv")
|
|
.Done()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("nl-bk")
|
|
.Done()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("de-de")
|
|
.Done()
|
|
.Done()
|
|
.Build();
|
|
|
|
var culturesPublished = new []{ "en-us", "da-dk", "nl-bk", "se-sv", "de-de" };
|
|
|
|
ContentController contentController = CreateContentController(domainServiceMock.Object);
|
|
var notifications = new SimpleNotificationModel();
|
|
|
|
contentController.AddDomainWarnings(level3Node, culturesPublished, notifications);
|
|
// We expect one error because all domains except "de-de" is registered somewhere in the ancestor path
|
|
Assert.AreEqual(1, notifications.Notifications.Count(x => x.NotificationType == NotificationStyle.Warning));
|
|
}
|
|
|
|
[Test]
|
|
public void Only_Warns_About_Cultures_Being_Published()
|
|
{
|
|
var domainServiceMock = new Mock<IDomainService>();
|
|
domainServiceMock.Setup(x => x.GetAssignedDomains(It.IsAny<int>(), It.IsAny<bool>()))
|
|
.Returns(new []{new UmbracoDomain("/", "da-dk")});
|
|
|
|
Content rootNode = new ContentBuilder()
|
|
.WithContentType(CreateContentType())
|
|
.WithId(1060)
|
|
.AddContentCultureInfosCollection()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("da-dk")
|
|
.Done()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("en-us")
|
|
.Done()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("se-sv")
|
|
.Done()
|
|
.AddCultureInfos()
|
|
.WithCultureIso("de-de")
|
|
.Done()
|
|
.Done()
|
|
.Build();
|
|
|
|
var culturesPublished = new []{ "en-us", "se-sv" };
|
|
var notifications = new SimpleNotificationModel();
|
|
|
|
ContentController contentController = CreateContentController(domainServiceMock.Object);
|
|
contentController.AddDomainWarnings(rootNode, culturesPublished, notifications);
|
|
|
|
// We only get two errors, one for each culture being published, so no errors from previously published cultures.
|
|
Assert.AreEqual(2, notifications.Notifications.Count(x => x.NotificationType == NotificationStyle.Warning));
|
|
}
|
|
|
|
private ContentController CreateContentController(IDomainService domainService)
|
|
{
|
|
// We have to configure ILocalizedTextService to return a new string every time Localize is called
|
|
// Otherwise it won't add the notification because it skips dupes
|
|
var localizedTextServiceMock = new Mock<ILocalizedTextService>();
|
|
localizedTextServiceMock.Setup(x => x.Localize(It.IsAny<string>(),
|
|
It.IsAny<string>(), It.IsAny<CultureInfo>(), It.IsAny<IDictionary<string, string>>()))
|
|
.Returns(() => Guid.NewGuid().ToString());
|
|
|
|
var controller = new ContentController(
|
|
Mock.Of<ICultureDictionary>(),
|
|
NullLoggerFactory.Instance,
|
|
Mock.Of<IShortStringHelper>(),
|
|
Mock.Of<IEventMessagesFactory>(),
|
|
localizedTextServiceMock.Object,
|
|
new PropertyEditorCollection(new DataEditorCollection(() => null)),
|
|
Mock.Of<IContentService>(),
|
|
Mock.Of<IUserService>(),
|
|
Mock.Of<IBackOfficeSecurityAccessor>(),
|
|
Mock.Of<IContentTypeService>(),
|
|
Mock.Of<IUmbracoMapper>(),
|
|
Mock.Of<IPublishedUrlProvider>(),
|
|
domainService,
|
|
Mock.Of<IDataTypeService>(),
|
|
Mock.Of<ILocalizationService>(),
|
|
Mock.Of<IFileService>(),
|
|
Mock.Of<INotificationService>(),
|
|
new ActionCollection(() => null),
|
|
Mock.Of<ISqlContext>(),
|
|
Mock.Of<IJsonSerializer>(),
|
|
Mock.Of<IScopeProvider>(),
|
|
Mock.Of<IAuthorizationService>(),
|
|
Mock.Of<IContentVersionService>()
|
|
);
|
|
|
|
return controller;
|
|
}
|
|
|
|
private IContentType CreateContentType() =>
|
|
new ContentTypeBuilder().WithContentVariation(ContentVariation.Culture).Build();
|
|
}
|
|
}
|