Skip to content

Commit 13a51d3

Browse files
Paul Johnsonronaldbarendse
andauthored
Support import/export for doc type history cleanup policy (#11660)
* Support import/export for doc type history cleanup policy * Support unset/null history cleanup value * Resolve issue when api endpoints called without cleanup policy. noop isn't good enough as map fails for response. * null conditional vs null coalesce assignment * Don't overwrite existing policy if omitted in import XML * Update history cleanup warning and translations * Change history cleanup alert to infomational styling * Remove margin around history cleanup config Co-authored-by: Ronald Barendse <[email protected]>
1 parent a86baa4 commit 13a51d3

File tree

14 files changed

+282
-35
lines changed

14 files changed

+282
-35
lines changed

src/Umbraco.Core/Models/ContentEditing/HistoryCleanup.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,9 @@ public class HistoryCleanup
99
public bool PreventCleanup { get; set; }
1010

1111
[DataMember(Name = "keepAllVersionsNewerThanDays")]
12-
public int? KeepAllVersionsNewerThanDays { get;set; }
12+
public int? KeepAllVersionsNewerThanDays { get; set; }
1313

1414
[DataMember(Name = "keepLatestVersionPerDayForDays")]
15-
public int? KeepLatestVersionPerDayForDays { get;set; }
15+
public int? KeepLatestVersionPerDayForDays { get; set; }
1616
}
1717
}
18-

src/Umbraco.Core/Models/IContentType.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ public interface IContentType : IContentTypeComposition
1919
IEnumerable<ITemplate> AllowedTemplates { get; set; }
2020

2121
/// <summary>
22-
/// Gets or Sets the history cleanup configuration
22+
/// Gets or sets the history cleanup configuration.
2323
/// </summary>
24+
/// <value>The history cleanup configuration.</value>
2425
HistoryCleanup HistoryCleanup { get; set; }
26+
2527
/// <summary>
2628
/// Determines if AllowedTemplates contains templateId
2729
/// </summary>

src/Umbraco.Core/Packaging/PackageDataInstallation.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Umbraco.Core.IO;
1010
using Umbraco.Core.Logging;
1111
using Umbraco.Core.Models;
12+
using Umbraco.Core.Models.ContentEditing;
1213
using Umbraco.Core.Models.Entities;
1314
using Umbraco.Core.Models.Packaging;
1415
using Umbraco.Core.Models.PublishedContent;
@@ -692,10 +693,44 @@ private IContentType UpdateContentTypeFromXml(XElement documentType, IContentTyp
692693
UpdateContentTypesAllowedTemplates(contentType, infoElement.Element("AllowedTemplates"), defaultTemplateElement);
693694
UpdateContentTypesPropertyGroups(contentType, documentType.Element("Tabs"));
694695
UpdateContentTypesProperties(contentType, documentType.Element("GenericProperties"));
696+
UpdateHistoryCleanupPolicy(contentType, documentType.Element("HistoryCleanupPolicy"));
695697

696698
return contentType;
697699
}
698700

701+
private void UpdateHistoryCleanupPolicy(IContentType contentType, XElement element)
702+
{
703+
if (element == null)
704+
{
705+
return;
706+
}
707+
708+
contentType.HistoryCleanup ??= new HistoryCleanup();
709+
710+
if (bool.TryParse(element.Attribute("preventCleanup")?.Value, out var preventCleanup))
711+
{
712+
contentType.HistoryCleanup.PreventCleanup = preventCleanup;
713+
}
714+
715+
if (int.TryParse(element.Attribute("keepAllVersionsNewerThanDays")?.Value, out var keepAll))
716+
{
717+
contentType.HistoryCleanup.KeepAllVersionsNewerThanDays = keepAll;
718+
}
719+
else
720+
{
721+
contentType.HistoryCleanup.KeepAllVersionsNewerThanDays = null;
722+
}
723+
724+
if (int.TryParse(element.Attribute("keepLatestVersionPerDayForDays")?.Value, out var keepLatest))
725+
{
726+
contentType.HistoryCleanup.KeepLatestVersionPerDayForDays = keepLatest;
727+
}
728+
else
729+
{
730+
contentType.HistoryCleanup.KeepLatestVersionPerDayForDays = null;
731+
}
732+
}
733+
699734
private void UpdateContentTypesAllowedTemplates(IContentType contentType,
700735
XElement allowedTemplatesElement, XElement defaultTemplateElement)
701736
{

src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepository.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Umbraco.Core.Exceptions;
77
using Umbraco.Core.Logging;
88
using Umbraco.Core.Models;
9+
using Umbraco.Core.Models.ContentEditing;
910
using Umbraco.Core.Models.Entities;
1011
using Umbraco.Core.Persistence.Dtos;
1112
using Umbraco.Core.Persistence.Querying;
@@ -297,14 +298,17 @@ protected override void PersistUpdatedItem(IContentType entity)
297298

298299
private void PersistHistoryCleanup(IContentType entity)
299300
{
301+
// historyCleanup property is not mandatory for api endpoint, handle the case where it's not present.
302+
// DocumentTypeSave doesn't handle this for us like ContentType constructors do.
300303
ContentVersionCleanupPolicyDto dto = new ContentVersionCleanupPolicyDto()
301304
{
302305
ContentTypeId = entity.Id,
303306
Updated = DateTime.Now,
304-
PreventCleanup = entity.HistoryCleanup.PreventCleanup,
305-
KeepAllVersionsNewerThanDays = entity.HistoryCleanup.KeepAllVersionsNewerThanDays,
306-
KeepLatestVersionPerDayForDays = entity.HistoryCleanup.KeepLatestVersionPerDayForDays,
307+
PreventCleanup = entity.HistoryCleanup?.PreventCleanup ?? false,
308+
KeepAllVersionsNewerThanDays = entity.HistoryCleanup?.KeepAllVersionsNewerThanDays,
309+
KeepLatestVersionPerDayForDays = entity.HistoryCleanup?.KeepLatestVersionPerDayForDays,
307310
};
311+
308312
Database.InsertOrUpdate(dto);
309313
}
310314
}

src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Newtonsoft.Json;
88
using Umbraco.Core.Composing;
99
using Umbraco.Core.Models;
10+
using Umbraco.Core.Models.ContentEditing;
1011
using Umbraco.Core.Strings;
1112

1213
namespace Umbraco.Core.Services.Implement
@@ -447,12 +448,19 @@ public XElement Serialize(IContentType contentType)
447448

448449
var tabs = new XElement("Tabs", SerializePropertyGroups(contentType.PropertyGroups)); // TODO Rename to PropertyGroups
449450

451+
452+
450453
var xml = new XElement("DocumentType",
451454
info,
452455
structure,
453456
genericProperties,
454457
tabs);
455458

459+
if (contentType.HistoryCleanup != null)
460+
{
461+
xml.Add(SerializeCleanupPolicy(contentType.HistoryCleanup));
462+
}
463+
456464
var folderNames = string.Empty;
457465
//don't add folders if this is a child doc type
458466
if (contentType.Level != 1 && masterContentType == null)
@@ -515,6 +523,29 @@ private IEnumerable<XElement> SerializePropertyGroups(IEnumerable<PropertyGroup>
515523
}
516524
}
517525

526+
private XElement SerializeCleanupPolicy(HistoryCleanup cleanupPolicy)
527+
{
528+
if (cleanupPolicy == null)
529+
{
530+
throw new ArgumentNullException(nameof(cleanupPolicy));
531+
}
532+
533+
var element = new XElement("HistoryCleanupPolicy",
534+
new XAttribute("preventCleanup", cleanupPolicy.PreventCleanup));
535+
536+
if (cleanupPolicy.KeepAllVersionsNewerThanDays.HasValue)
537+
{
538+
element.Add(new XAttribute("keepAllVersionsNewerThanDays", cleanupPolicy.KeepAllVersionsNewerThanDays));
539+
}
540+
541+
if (cleanupPolicy.KeepLatestVersionPerDayForDays.HasValue)
542+
{
543+
element.Add(new XAttribute("keepLatestVersionPerDayForDays", cleanupPolicy.KeepLatestVersionPerDayForDays));
544+
}
545+
546+
return element;
547+
}
548+
518549
// exports an IContentBase (IContent, IMedia or IMember) as an XElement.
519550
private XElement SerializeContentBase(IContentBase contentBase, string urlValue, string nodeName, bool published)
520551
{

src/Umbraco.Tests/Packaging/PackageDataInstallationTests.cs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -757,5 +757,118 @@ private void AddExistingEnglishAndNorwegianParentDictionaryItem(string expectedE
757757
}
758758
);
759759
}
760+
761+
[Test]
762+
public void ImportDocumentType_NewTypeWithOmittedHistoryCleanupPolicy_InsertsDefaultPolicy()
763+
{
764+
// Arrange
765+
var withoutCleanupPolicy = XElement.Parse(ImportResources.SingleDocType);
766+
767+
// Act
768+
var contentTypes = PackageDataInstallation.ImportDocumentType(withoutCleanupPolicy, 0);
769+
770+
// Assert
771+
Assert.Multiple(() =>
772+
{
773+
Assert.NotNull(contentTypes.Single().HistoryCleanup);
774+
Assert.IsFalse(contentTypes.Single().HistoryCleanup.PreventCleanup);
775+
});
776+
}
777+
778+
[Test]
779+
public void ImportDocumentType_WithHistoryCleanupPolicyElement_ImportsWithCorrectValues()
780+
{
781+
// Arrange
782+
var docTypeElement = XElement.Parse(ImportResources.SingleDocType_WithCleanupPolicy);
783+
784+
// Act
785+
var contentTypes = PackageDataInstallation.ImportDocumentType(docTypeElement, 0);
786+
787+
// Assert
788+
Assert.Multiple(() =>
789+
{
790+
Assert.NotNull(contentTypes.Single().HistoryCleanup);
791+
Assert.IsTrue(contentTypes.Single().HistoryCleanup.PreventCleanup);
792+
Assert.AreEqual(1, contentTypes.Single().HistoryCleanup.KeepAllVersionsNewerThanDays);
793+
Assert.AreEqual(2, contentTypes.Single().HistoryCleanup.KeepLatestVersionPerDayForDays);
794+
});
795+
}
796+
797+
798+
[Test]
799+
public void ImportDocumentType_ExistingTypeWithOmittedHistoryCleanupPolicy_DoesNotOverwriteDatabaseContent()
800+
{
801+
// Arrange
802+
var withoutCleanupPolicy = XElement.Parse(ImportResources.SingleDocType);
803+
var withCleanupPolicy = XElement.Parse(ImportResources.SingleDocType_WithCleanupPolicy);
804+
805+
// Act
806+
var contentTypes = PackageDataInstallation.ImportDocumentType(withCleanupPolicy, 0);
807+
var contentTypesUpdated = PackageDataInstallation.ImportDocumentType(withoutCleanupPolicy, 0);
808+
809+
// Assert
810+
Assert.Multiple(() =>
811+
{
812+
Assert.NotNull(contentTypes.Single().HistoryCleanup);
813+
Assert.IsTrue(contentTypes.Single().HistoryCleanup.PreventCleanup);
814+
Assert.AreEqual(1, contentTypes.Single().HistoryCleanup.KeepAllVersionsNewerThanDays);
815+
Assert.AreEqual(2, contentTypes.Single().HistoryCleanup.KeepLatestVersionPerDayForDays);
816+
817+
Assert.NotNull(contentTypesUpdated.Single().HistoryCleanup);
818+
Assert.IsTrue(contentTypesUpdated.Single().HistoryCleanup.PreventCleanup);
819+
Assert.AreEqual(1, contentTypes.Single().HistoryCleanup.KeepAllVersionsNewerThanDays);
820+
Assert.AreEqual(2, contentTypes.Single().HistoryCleanup.KeepLatestVersionPerDayForDays);
821+
});
822+
}
823+
824+
// This test covers EntityXmlSerializer, it's in an odd place, much like Can_Export_Single_DocType
825+
[Test]
826+
public void Serialize_ForContentTypeWithHistoryCleanupPolicy_OutputsSerializedHistoryCleanupPolicy()
827+
{
828+
// Arrange
829+
var docTypeElement = XElement.Parse(ImportResources.SingleDocType_WithCleanupPolicy);
830+
831+
var serializer = Factory.GetInstance<IEntityXmlSerializer>();
832+
833+
// Act
834+
var contentTypes = PackageDataInstallation.ImportDocumentType(docTypeElement, 0);
835+
var contentType = contentTypes.FirstOrDefault();
836+
var element = serializer.Serialize(contentType);
837+
838+
// Assert
839+
Assert.Multiple(() =>
840+
{
841+
Assert.That(element.Element("HistoryCleanupPolicy")!.Attribute("preventCleanup")!.Value, Is.EqualTo("true"));
842+
Assert.That(element.Element("HistoryCleanupPolicy")!.Attribute("keepAllVersionsNewerThanDays")!.Value, Is.EqualTo("1"));
843+
Assert.That(element.Element("HistoryCleanupPolicy")!.Attribute("keepLatestVersionPerDayForDays")!.Value, Is.EqualTo("2"));
844+
});
845+
}
846+
847+
848+
// This test covers EntityXmlSerializer, it's in an odd place, much like Can_Export_Single_DocType
849+
[Test]
850+
public void Serialize_ForContentTypeWithNullHistoryCleanupPolicy_DoesNotOutputSerializedDefaultPolicy()
851+
{
852+
// Arrange
853+
var docTypeElement = XElement.Parse(ImportResources.SingleDocType);
854+
855+
var serializer = Factory.GetInstance<IEntityXmlSerializer>();
856+
857+
// Act
858+
var contentTypes = PackageDataInstallation.ImportDocumentType(docTypeElement, 0);
859+
var contentType = contentTypes.First();
860+
861+
// Import results in this being created even if not present in XML
862+
// These tests are all a bit confused as to what is under test, do better in v9.
863+
contentType.HistoryCleanup = null;
864+
865+
var element = serializer.Serialize(contentType);
866+
867+
// Assert
868+
Assert.Multiple(() =>
869+
{
870+
Assert.That(element.Element("HistoryCleanupPolicy"), Is.Null);
871+
});
872+
}
760873
}
761874
}

src/Umbraco.Tests/Services/Importing/ImportResources.Designer.cs

Lines changed: 31 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Umbraco.Tests/Services/Importing/ImportResources.resx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,4 +154,7 @@
154154
<data name="Fanoe_Package" type="System.Resources.ResXFileRef, System.Windows.Forms">
155155
<value>fanoe-package.xml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
156156
</data>
157-
</root>
157+
<data name="SingleDocType_WithCleanupPolicy" type="System.Resources.ResXFileRef, System.Windows.Forms">
158+
<value>SingleDocType-WithCleanupPolicy.xml;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
159+
</data>
160+
</root>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<DocumentType>
3+
<Info>
4+
<Name>test</Name>
5+
<Alias>test</Alias>
6+
<Icon>folder.gif</Icon>
7+
<Thumbnail>folder.png</Thumbnail>
8+
<Description>
9+
</Description>
10+
<AllowAtRoot>False</AllowAtRoot>
11+
<AllowedTemplates>
12+
<Template>test</Template>
13+
</AllowedTemplates>
14+
<DefaultTemplate>test</DefaultTemplate>
15+
</Info>
16+
<Structure>
17+
<DocumentType>test</DocumentType>
18+
</Structure>
19+
<GenericProperties>
20+
<GenericProperty>
21+
<Name>test</Name>
22+
<Alias>test</Alias>
23+
<Type>b4471851-82b6-4c75-afa4-39fa9c6a75e9</Type>
24+
<Definition>fbaf13a8-4036-41f2-93a3-974f678c312a</Definition>
25+
<Tab>
26+
</Tab>
27+
<Mandatory>False</Mandatory>
28+
<Validation>
29+
</Validation>
30+
<Description><![CDATA[]]></Description>
31+
</GenericProperty>
32+
</GenericProperties>
33+
<Tabs />
34+
<HistoryCleanupPolicy preventCleanup="true" keepAllVersionsNewerThanDays="1" keepLatestVersionPerDayForDays="2" />
35+
</DocumentType>

0 commit comments

Comments
 (0)