Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ContentVersion cleanup backoffice UI (#11637) #11644

Merged
merged 10 commits into from
Nov 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/Umbraco.Core/Models/AuditType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@ public enum AuditType
/// <summary>
/// Custom audit message.
/// </summary>
Custom
Custom,

/// <summary>
/// Content version preventCleanup set to true
/// </summary>
ContentVersionPreventCleanup,

/// <summary>
/// Content version preventCleanup set to false
/// </summary>
ContentVersionEnableCleanup
}
}
45 changes: 45 additions & 0 deletions src/Umbraco.Core/Models/ContentVersionMeta.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System;

namespace Umbraco.Cms.Core.Models
{
public class ContentVersionMeta
{
public int ContentId { get; }
public int ContentTypeId { get; }
public int VersionId { get; }
public int UserId { get; }

public DateTime VersionDate { get; }
public bool CurrentPublishedVersion { get; }
public bool CurrentDraftVersion { get; }
public bool PreventCleanup { get; }
public string Username { get; }

public ContentVersionMeta() { }

public ContentVersionMeta(
int versionId,
int contentId,
int contentTypeId,
int userId,
DateTime versionDate,
bool currentPublishedVersion,
bool currentDraftVersion,
bool preventCleanup,
string username)
{
VersionId = versionId;
ContentId = contentId;
ContentTypeId = contentTypeId;

UserId = userId;
VersionDate = versionDate;
CurrentPublishedVersion = currentPublishedVersion;
CurrentDraftVersion = currentDraftVersion;
PreventCleanup = preventCleanup;
Username = username;
}

public override string ToString() => $"ContentVersionMeta(versionId: {VersionId}, versionDate: {VersionDate:s}";
}
}
24 changes: 0 additions & 24 deletions src/Umbraco.Core/Models/HistoricContentVersionMeta.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,31 @@ public interface IDocumentVersionRepository : IRepository
/// <summary>
/// Gets a list of all historic content versions.
/// </summary>
public IReadOnlyCollection<HistoricContentVersionMeta> GetDocumentVersionsEligibleForCleanup();
public IReadOnlyCollection<ContentVersionMeta> GetDocumentVersionsEligibleForCleanup();

/// <summary>
/// Gets cleanup policy override settings per content type.
/// </summary>
public IReadOnlyCollection<ContentVersionCleanupPolicySettings> GetCleanupPolicies();

/// <summary>
/// Gets paginated content versions for given content id paginated.
/// </summary>
public IEnumerable<ContentVersionMeta> GetPagedItemsByContentId(int contentId, long pageIndex, int pageSize, out long totalRecords, int? languageId = null);

/// <summary>
/// Deletes multiple content versions by ID.
/// </summary>
void DeleteVersions(IEnumerable<int> versionIds);

/// <summary>
/// Updates the prevent cleanup flag on a content version.
/// </summary>
void SetPreventCleanup(int versionId, bool preventCleanup);

/// <summary>
/// Gets the content version metadata for a specific version.
/// </summary>
ContentVersionMeta Get(int versionId);
}
}
2 changes: 1 addition & 1 deletion src/Umbraco.Core/Services/IContentVersionCleanupPolicy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ public interface IContentVersionCleanupPolicy
/// <summary>
/// Filters a set of candidates historic content versions for cleanup according to policy settings.
/// </summary>
IEnumerable<HistoricContentVersionMeta> Apply(DateTime asAtDate, IEnumerable<HistoricContentVersionMeta> items);
IEnumerable<ContentVersionMeta> Apply(DateTime asAtDate, IEnumerable<ContentVersionMeta> items);
}
}
13 changes: 12 additions & 1 deletion src/Umbraco.Core/Services/IContentVersionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ public interface IContentVersionService
/// <summary>
/// Removes historic content versions according to a policy.
/// </summary>
IReadOnlyCollection<HistoricContentVersionMeta> PerformContentVersionCleanup(DateTime asAtDate);
IReadOnlyCollection<ContentVersionMeta> PerformContentVersionCleanup(DateTime asAtDate);

/// <summary>
/// Gets paginated content versions for given content id paginated.
/// </summary>
/// <exception cref="ArgumentException">Thrown when <paramref name="culture"/> is invalid.</exception>
IEnumerable<ContentVersionMeta> GetPagedContentVersions(int contentId, long pageIndex, int pageSize, out long totalRecords, string culture = null);

/// <summary>
/// Updates preventCleanup value for given content version.
/// </summary>
void SetPreventCleanup(int versionId, bool preventCleanup, int userId = -1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,10 @@ protected override void PersistUpdatedItem(IContent entity)
documentVersionDto.Published = true; // now published
contentVersionDto.Current = false; // no more current
}

// Ensure existing version retains current preventCleanup flag (both saving and publishing).
contentVersionDto.PreventCleanup = version.PreventCleanup;

Database.Update(contentVersionDto);
Database.Update(documentVersionDto);

Expand All @@ -641,6 +645,7 @@ protected override void PersistUpdatedItem(IContent entity)
contentVersionDto.Id = 0; // want a new id
contentVersionDto.Current = true; // current version
contentVersionDto.Text = entity.Name;
contentVersionDto.PreventCleanup = false; // new draft version disregards prevent cleanup flag
Database.Insert(contentVersionDto);
entity.VersionId = documentVersionDto.Id = contentVersionDto.Id; // get the new id

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NPoco;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Persistence.Repositories;
Expand All @@ -14,65 +15,112 @@ internal class DocumentVersionRepository : IDocumentVersionRepository
{
private readonly IScopeAccessor _scopeAccessor;

public DocumentVersionRepository(IScopeAccessor scopeAccessor)
{
public DocumentVersionRepository(IScopeAccessor scopeAccessor) =>
_scopeAccessor = scopeAccessor ?? throw new ArgumentNullException(nameof(scopeAccessor));
}

/// <inheritdoc />
/// <remarks>
/// Never includes current draft version. <br/>
/// Never includes current published version.<br/>
/// Never includes versions marked as "preventCleanup".<br/>
/// </remarks>
public IReadOnlyCollection<HistoricContentVersionMeta> GetDocumentVersionsEligibleForCleanup()
public IReadOnlyCollection<ContentVersionMeta> GetDocumentVersionsEligibleForCleanup()
{
var query = _scopeAccessor.AmbientScope.SqlContext.Sql();
Sql<ISqlContext> query = _scopeAccessor.AmbientScope.SqlContext.Sql();

query.Select(@"umbracoDocument.nodeId as contentId,
umbracoContent.contentTypeId as contentTypeId,
umbracoContentVersion.id as versionId,
umbracoContentVersion.versionDate as versionDate")
umbracoContentVersion.userId as userId,
umbracoContentVersion.versionDate as versionDate,
umbracoDocumentVersion.published as currentPublishedVersion,
umbracoContentVersion.[current] as currentDraftVersion,
umbracoContentVersion.preventCleanup as preventCleanup,
umbracoUser.userName as username")
.From<DocumentDto>()
.InnerJoin<ContentDto>()
.On<DocumentDto, ContentDto>(left => left.NodeId, right => right.NodeId)
.InnerJoin<ContentVersionDto>()
.On<ContentDto, ContentVersionDto>(left => left.NodeId, right => right.NodeId)
.InnerJoin<DocumentVersionDto>()
.On<ContentVersionDto, DocumentVersionDto>(left => left.Id, right => right.Id)
.LeftJoin<UserDto>()
.On<UserDto, ContentVersionDto>(left => left.Id, right => right.UserId)
.Where<ContentVersionDto>(x => !x.Current) // Never delete current draft version
.Where<ContentVersionDto>(x => !x.PreventCleanup) // Never delete "pinned" versions
.Where<DocumentVersionDto>(x => !x.Published); // Never delete published version

return _scopeAccessor.AmbientScope.Database.Fetch<HistoricContentVersionMeta>(query);
return _scopeAccessor.AmbientScope.Database.Fetch<ContentVersionMeta>(query);
}

/// <inheritdoc />
public IReadOnlyCollection<ContentVersionCleanupPolicySettings> GetCleanupPolicies()
{
var query = _scopeAccessor.AmbientScope.SqlContext.Sql();
Sql<ISqlContext> query = _scopeAccessor.AmbientScope.SqlContext.Sql();

query.Select<ContentVersionCleanupPolicyDto>()
.From<ContentVersionCleanupPolicyDto>();

return _scopeAccessor.AmbientScope.Database.Fetch<ContentVersionCleanupPolicySettings>(query);
}

/// <inheritdoc />
public IEnumerable<ContentVersionMeta> GetPagedItemsByContentId(int contentId, long pageIndex, int pageSize, out long totalRecords, int? languageId = null)
{
Sql<ISqlContext> query = _scopeAccessor.AmbientScope.SqlContext.Sql();

query.Select(@"umbracoDocument.nodeId as contentId,
umbracoContent.contentTypeId as contentTypeId,
umbracoContentVersion.id as versionId,
umbracoContentVersion.userId as userId,
umbracoContentVersion.versionDate as versionDate,
umbracoDocumentVersion.published as currentPublishedVersion,
umbracoContentVersion.[current] as currentDraftVersion,
umbracoContentVersion.preventCleanup as preventCleanup,
umbracoUser.userName as username")
.From<DocumentDto>()
.InnerJoin<ContentDto>()
.On<DocumentDto, ContentDto>(left => left.NodeId, right => right.NodeId)
.InnerJoin<ContentVersionDto>()
.On<ContentDto, ContentVersionDto>(left => left.NodeId, right => right.NodeId)
.InnerJoin<DocumentVersionDto>()
.On<ContentVersionDto, DocumentVersionDto>(left => left.Id, right => right.Id)
.LeftJoin<UserDto>()
.On<UserDto, ContentVersionDto>(left => left.Id, right => right.UserId)
.LeftJoin<ContentVersionCultureVariationDto>()
.On<ContentVersionCultureVariationDto, ContentVersionDto>(left => left.VersionId, right => right.Id)
.Where<ContentVersionDto>(x => x.NodeId == contentId);

// TODO: If there's not a better way to write this then we need a better way to write this.
query = languageId.HasValue
? query.Where<ContentVersionCultureVariationDto>(x => x.LanguageId == languageId.Value)
: query.Where("umbracoContentVersionCultureVariation.languageId is null");

query = query.OrderByDescending<ContentVersionDto>(x => x.Id);

Page<ContentVersionMeta> page = _scopeAccessor.AmbientScope.Database.Page<ContentVersionMeta>(pageIndex + 1, pageSize, query);

totalRecords = page.TotalItems;

return page.Items;
}

/// <inheritdoc />
/// <remarks>
/// Deletes in batches of <see cref="Constants.Sql.MaxParameterCount"/>
/// </remarks>
public void DeleteVersions(IEnumerable<int> versionIds)
{
foreach (var group in versionIds.InGroupsOf(Constants.Sql.MaxParameterCount))
foreach (IEnumerable<int> group in versionIds.InGroupsOf(Constants.Sql.MaxParameterCount))
{
var groupedVersionIds = group.ToList();

// Note: We had discussed doing this in a single SQL Command.
// If you can work out how to make that work with SQL CE, let me know!
// Can use test PerformContentVersionCleanup_WithNoKeepPeriods_DeletesEverythingExceptActive to try things out.
/* Note: We had discussed doing this in a single SQL Command.
* If you can work out how to make that work with SQL CE, let me know!
* Can use test PerformContentVersionCleanup_WithNoKeepPeriods_DeletesEverythingExceptActive to try things out.
*/

var query = _scopeAccessor.AmbientScope.SqlContext.Sql()
Sql<ISqlContext> query = _scopeAccessor.AmbientScope.SqlContext.Sql()
.Delete<PropertyDataDto>()
.WhereIn<PropertyDataDto>(x => x.VersionId, groupedVersionIds);
_scopeAccessor.AmbientScope.Database.Execute(query);
Expand All @@ -93,5 +141,43 @@ public void DeleteVersions(IEnumerable<int> versionIds)
_scopeAccessor.AmbientScope.Database.Execute(query);
}
}

/// <inheritdoc />
public void SetPreventCleanup(int versionId, bool preventCleanup)
{
Sql<ISqlContext> query = _scopeAccessor.AmbientScope.SqlContext.Sql()
.Update<ContentVersionDto>(x => x.Set(y => y.PreventCleanup, preventCleanup))
.Where<ContentVersionDto>(x => x.Id == versionId);

_scopeAccessor.AmbientScope.Database.Execute(query);
}

/// <inheritdoc />
public ContentVersionMeta Get(int versionId)
{
Sql<ISqlContext> query = _scopeAccessor.AmbientScope.SqlContext.Sql();

query.Select(@"umbracoDocument.nodeId as contentId,
umbracoContent.contentTypeId as contentTypeId,
umbracoContentVersion.id as versionId,
umbracoContentVersion.userId as userId,
umbracoContentVersion.versionDate as versionDate,
umbracoDocumentVersion.published as currentPublishedVersion,
umbracoContentVersion.[current] as currentDraftVersion,
umbracoContentVersion.preventCleanup as preventCleanup,
umbracoUser.userName as username")
.From<DocumentDto>()
.InnerJoin<ContentDto>()
.On<DocumentDto, ContentDto>(left => left.NodeId, right => right.NodeId)
.InnerJoin<ContentVersionDto>()
.On<ContentDto, ContentVersionDto>(left => left.NodeId, right => right.NodeId)
.InnerJoin<DocumentVersionDto>()
.On<ContentVersionDto, DocumentVersionDto>(left => left.Id, right => right.Id)
.LeftJoin<UserDto>()
.On<UserDto, ContentVersionDto>(left => left.Id, right => right.UserId)
.Where<ContentVersionDto>(x => x.Id == versionId);

return _scopeAccessor.AmbientScope.Database.Single<ContentVersionMeta>(query);
}
}
}
Loading