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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f0e7f81
init rollback ui prototype
madsrasmussen Nov 2, 2021
896dcf6
add busy state to button, deselect version, add pagination status
madsrasmussen Nov 2, 2021
77185c7
add localisation
madsrasmussen Nov 2, 2021
a0a0ea1
style current version
madsrasmussen Nov 2, 2021
1d874f3
disable rollback button when nothing is selected
madsrasmussen Nov 2, 2021
df27ea4
stop click event
madsrasmussen Nov 2, 2021
71fcb39
Endpoints for paginated content versions.
Nov 11, 2021
27cf328
Endpoints to "pin" content versions
Nov 15, 2021
de5bfe9
camel case json output.
Nov 15, 2021
7c14b10
wire up paging
madsrasmussen Nov 15, 2021
ec3bef8
wire up pin/unpin
madsrasmussen Nov 15, 2021
eb62dd1
rename getPagedRollbackVersions to getPagedContentVersions
madsrasmussen Nov 15, 2021
77fea29
prevent selection of current version and current draft
madsrasmussen Nov 15, 2021
558d1d1
add current draft and current version to UI
madsrasmussen Nov 15, 2021
1dc27fa
remove pointer if the row is not selectable
madsrasmussen Nov 15, 2021
e2b221a
Improve warning for globally disabled cleanup feature.
Nov 15, 2021
d50adf7
Fix current loses prevent cleanup state on publish.
Nov 15, 2021
5f44f87
Added umbracoLog audit entries for "pin" / "unpin"
Nov 15, 2021
5944188
Match v9 defaults for keepVersions settings
Nov 15, 2021
4a335f5
Fix - losing preventCleanup on save current with content changes
Nov 15, 2021
86b3fb6
update pin/unpin button labels
madsrasmussen Nov 15, 2021
87c2e5c
fix pagination bug
madsrasmussen Nov 15, 2021
6346762
add missing "
madsrasmussen Nov 15, 2021
669ecbb
always send culture when a doc type can vary
madsrasmussen Nov 15, 2021
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.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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,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 @@ -606,6 +610,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
Expand Up @@ -22,26 +22,33 @@ public DocumentVersionRepository(IScopeAccessor scopeAccessor)
/// 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();

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 />
Expand All @@ -55,6 +62,47 @@ public IReadOnlyCollection<ContentVersionCleanupPolicySettings> GetCleanupPolici
return _scopeAccessor.AmbientScope.Database.Fetch<ContentVersionCleanupPolicySettings>(query);
}

/// <inheritdoc />
public IEnumerable<ContentVersionMeta> GetPagedItemsByContentId(int contentId, long pageIndex, int pageSize, out long totalRecords, int? languageId = null)
{
var 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");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bergmania maybe I am missing something, but shouldn't the second line use Cms.Core.Constants.DatabaseSchema.Tables.ContentVersionCultureVariation?
https://github.com/umbraco/Umbraco-CMS/blob/v9/contrib/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs#L28

query.Where<ContentVersionCultureVariationDto>(x => x.LanguageId == null);

or

query.Where($"{Cms.Core.Constants.DatabaseSchema.Tables.ContentVersionCultureVariation}.languageId is null");

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

@p-m-j p-m-j Nov 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't do query.Where<ContentVersionCultureVariationDto>(x => x.LanguageId == null); as LanguageId is int (we are left joining)

Using constants sounds smart, applies to the column selection also.

We should probably just create a sql view and create a poco to represent that -- however sql ce

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh okay, the constants seems a bit nicer then 😁


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

var 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"/>
Expand Down Expand Up @@ -90,5 +138,45 @@ public void DeleteVersions(IEnumerable<int> versionIds)
_scopeAccessor.AmbientScope.Database.Execute(query);
}
}

/// <inheritdoc />
public void SetPreventCleanup(int versionId, bool preventCleanup)
{
var 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)
{
var 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.Id == versionId);

return _scopeAccessor.AmbientScope.Database.Single<ContentVersionMeta>(query);
}
}
}
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);
}
}
49 changes: 44 additions & 5 deletions src/Umbraco.Core/Services/Implement/ContentService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3227,25 +3227,64 @@ public OperationResult Rollback(int id, int versionId, string culture = "*", int
/// In v9 this can live in another class as we publish the notifications via IEventAggregator.
/// But for v8 must be here for access to the static events.
/// </remarks>
public IReadOnlyCollection<HistoricContentVersionMeta> PerformContentVersionCleanup(DateTime asAtDate)
public IReadOnlyCollection<ContentVersionMeta> PerformContentVersionCleanup(DateTime asAtDate)
{
return CleanupDocumentVersions(asAtDate);
// Media - ignored
// Members - ignored
}

/// <inheritdoc />
public IEnumerable<ContentVersionMeta> GetPagedContentVersions(int contentId, long pageIndex, int pageSize, out long totalRecords, string culture = null)
{
if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex));
if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize));

// NOTE: v9 - don't service locate
var documentVersionRepository = Composing.Current.Factory.GetInstance<IDocumentVersionRepository>();

using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
var languageId = _languageRepository.GetIdByIsoCode(culture, throwOnNotFound: true);
scope.ReadLock(Constants.Locks.ContentTree);
return documentVersionRepository.GetPagedItemsByContentId(contentId, pageIndex, pageSize, out totalRecords, languageId);
}
}

/// <inheritdoc />
public void SetPreventCleanup(int versionId, bool preventCleanup, int userId = -1)
{
// NOTE: v9 - don't service locate
var documentVersionRepository = Composing.Current.Factory.GetInstance<IDocumentVersionRepository>();

using (ScopeProvider.CreateScope(autoComplete: true))
{
documentVersionRepository.SetPreventCleanup(versionId, preventCleanup);

var version = documentVersionRepository.Get(versionId);

var auditType = preventCleanup
? AuditType.ContentVersionPreventCleanup
: AuditType.ContentVersionEnableCleanup;

var message = $"set preventCleanup = '{preventCleanup}' for version '{versionId}'";

Audit(auditType, userId, version.ContentId, message, $"{version.VersionDate}");
}
}

/// <remarks>
/// v9 - move to another class
/// </remarks>
private IReadOnlyCollection<HistoricContentVersionMeta> CleanupDocumentVersions(DateTime asAtDate)
private IReadOnlyCollection<ContentVersionMeta> CleanupDocumentVersions(DateTime asAtDate)
{
// NOTE: v9 - don't service locate
var documentVersionRepository = Composing.Current.Factory.GetInstance<IDocumentVersionRepository>();

// NOTE: v9 - don't service locate
var cleanupPolicy = Composing.Current.Factory.GetInstance<IContentVersionCleanupPolicy>();

List<HistoricContentVersionMeta> versionsToDelete;
List<ContentVersionMeta> versionsToDelete;

/* Why so many scopes?
*
Expand Down Expand Up @@ -3278,7 +3317,7 @@ private IReadOnlyCollection<HistoricContentVersionMeta> CleanupDocumentVersions(
var allHistoricVersions = documentVersionRepository.GetDocumentVersionsEligibleForCleanup();

Logger.Debug<ContentService>("Discovered {count} candidate(s) for ContentVersion cleanup.", allHistoricVersions.Count);
versionsToDelete = new List<HistoricContentVersionMeta>(allHistoricVersions.Count);
versionsToDelete = new List<ContentVersionMeta>(allHistoricVersions.Count);

var filteredContentVersions = cleanupPolicy.Apply(asAtDate, allHistoricVersions);

Expand All @@ -3299,7 +3338,7 @@ private IReadOnlyCollection<HistoricContentVersionMeta> CleanupDocumentVersions(
if (!versionsToDelete.Any())
{
Logger.Debug<ContentService>("No remaining ContentVersions for cleanup.", versionsToDelete.Count);
return Array.Empty<HistoricContentVersionMeta>();
return Array.Empty<ContentVersionMeta>();
}

Logger.Debug<ContentService>("Removing {count} ContentVersion(s).", versionsToDelete.Count);
Expand Down
Loading