Skip to content

Commit d89725b

Browse files
Paul Johnsonmadsrasmussen
andauthored
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 <[email protected]>
1 parent 1fbf02d commit d89725b

28 files changed

+805
-175
lines changed

src/Umbraco.Core/Models/AuditType.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,16 @@ public enum AuditType
113113
/// <summary>
114114
/// Custom audit message.
115115
/// </summary>
116-
Custom
116+
Custom,
117+
118+
/// <summary>
119+
/// Content version preventCleanup set to true
120+
/// </summary>
121+
ContentVersionPreventCleanup,
122+
123+
/// <summary>
124+
/// Content version preventCleanup set to false
125+
/// </summary>
126+
ContentVersionEnableCleanup
117127
}
118128
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using System;
2+
3+
namespace Umbraco.Core.Models
4+
{
5+
public class ContentVersionMeta
6+
{
7+
public int ContentId { get; }
8+
public int ContentTypeId { get; }
9+
public int VersionId { get; }
10+
public int UserId { get; }
11+
12+
public DateTime VersionDate { get; }
13+
public bool CurrentPublishedVersion { get; }
14+
public bool CurrentDraftVersion { get; }
15+
public bool PreventCleanup { get; }
16+
public string Username { get; }
17+
18+
public ContentVersionMeta() { }
19+
20+
public ContentVersionMeta(
21+
int versionId,
22+
int contentId,
23+
int contentTypeId,
24+
int userId,
25+
DateTime versionDate,
26+
bool currentPublishedVersion,
27+
bool currentDraftVersion,
28+
bool preventCleanup,
29+
string username)
30+
{
31+
VersionId = versionId;
32+
ContentId = contentId;
33+
ContentTypeId = contentTypeId;
34+
35+
UserId = userId;
36+
VersionDate = versionDate;
37+
CurrentPublishedVersion = currentPublishedVersion;
38+
CurrentDraftVersion = currentDraftVersion;
39+
PreventCleanup = preventCleanup;
40+
Username = username;
41+
}
42+
43+
public override string ToString() => $"ContentVersionMeta(versionId: {VersionId}, versionDate: {VersionDate:s}";
44+
}
45+
}

src/Umbraco.Core/Models/HistoricContentVersionMeta.cs

Lines changed: 0 additions & 24 deletions
This file was deleted.

src/Umbraco.Core/Persistence/Repositories/IDocumentVersionRepository.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,31 @@ public interface IDocumentVersionRepository : IRepository
88
/// <summary>
99
/// Gets a list of all historic content versions.
1010
/// </summary>
11-
public IReadOnlyCollection<HistoricContentVersionMeta> GetDocumentVersionsEligibleForCleanup();
11+
public IReadOnlyCollection<ContentVersionMeta> GetDocumentVersionsEligibleForCleanup();
1212

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

18+
/// <summary>
19+
/// Gets paginated content versions for given content id paginated.
20+
/// </summary>
21+
public IEnumerable<ContentVersionMeta> GetPagedItemsByContentId(int contentId, long pageIndex, int pageSize, out long totalRecords, int? languageId = null);
22+
1823
/// <summary>
1924
/// Deletes multiple content versions by ID.
2025
/// </summary>
2126
void DeleteVersions(IEnumerable<int> versionIds);
27+
28+
/// <summary>
29+
/// Updates the prevent cleanup flag on a content version.
30+
/// </summary>
31+
void SetPreventCleanup(int versionId, bool preventCleanup);
32+
33+
/// <summary>
34+
/// Gets the content version metadata for a specific version.
35+
/// </summary>
36+
ContentVersionMeta Get(int versionId);
2237
}
2338
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,10 @@ protected override void PersistUpdatedItem(IContent entity)
595595
documentVersionDto.Published = true; // now published
596596
contentVersionDto.Current = false; // no more current
597597
}
598+
599+
// Ensure existing version retains current preventCleanup flag (both saving and publishing).
600+
contentVersionDto.PreventCleanup = version.PreventCleanup;
601+
598602
Database.Update(contentVersionDto);
599603
Database.Update(documentVersionDto);
600604

@@ -606,6 +610,7 @@ protected override void PersistUpdatedItem(IContent entity)
606610
contentVersionDto.Id = 0; // want a new id
607611
contentVersionDto.Current = true; // current version
608612
contentVersionDto.Text = entity.Name;
613+
contentVersionDto.PreventCleanup = false; // new draft version disregards prevent cleanup flag
609614
Database.Insert(contentVersionDto);
610615
entity.VersionId = documentVersionDto.Id = contentVersionDto.Id; // get the new id
611616

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

Lines changed: 91 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,26 +22,33 @@ public DocumentVersionRepository(IScopeAccessor scopeAccessor)
2222
/// Never includes current published version.<br/>
2323
/// Never includes versions marked as "preventCleanup".<br/>
2424
/// </remarks>
25-
public IReadOnlyCollection<HistoricContentVersionMeta> GetDocumentVersionsEligibleForCleanup()
25+
public IReadOnlyCollection<ContentVersionMeta> GetDocumentVersionsEligibleForCleanup()
2626
{
2727
var query = _scopeAccessor.AmbientScope.SqlContext.Sql();
2828

2929
query.Select(@"umbracoDocument.nodeId as contentId,
3030
umbracoContent.contentTypeId as contentTypeId,
3131
umbracoContentVersion.id as versionId,
32-
umbracoContentVersion.versionDate as versionDate")
32+
umbracoContentVersion.userId as userId,
33+
umbracoContentVersion.versionDate as versionDate,
34+
umbracoDocumentVersion.published as currentPublishedVersion,
35+
umbracoContentVersion.[current] as currentDraftVersion,
36+
umbracoContentVersion.preventCleanup as preventCleanup,
37+
umbracoUser.userName as username")
3338
.From<DocumentDto>()
3439
.InnerJoin<ContentDto>()
3540
.On<DocumentDto, ContentDto>(left => left.NodeId, right => right.NodeId)
3641
.InnerJoin<ContentVersionDto>()
3742
.On<ContentDto, ContentVersionDto>(left => left.NodeId, right => right.NodeId)
3843
.InnerJoin<DocumentVersionDto>()
3944
.On<ContentVersionDto, DocumentVersionDto>(left => left.Id, right => right.Id)
45+
.LeftJoin<UserDto>()
46+
.On<UserDto, ContentVersionDto>(left => left.Id, right => right.UserId)
4047
.Where<ContentVersionDto>(x => !x.Current) // Never delete current draft version
4148
.Where<ContentVersionDto>(x => !x.PreventCleanup) // Never delete "pinned" versions
4249
.Where<DocumentVersionDto>(x => !x.Published); // Never delete published version
4350

44-
return _scopeAccessor.AmbientScope.Database.Fetch<HistoricContentVersionMeta>(query);
51+
return _scopeAccessor.AmbientScope.Database.Fetch<ContentVersionMeta>(query);
4552
}
4653

4754
/// <inheritdoc />
@@ -55,6 +62,47 @@ public IReadOnlyCollection<ContentVersionCleanupPolicySettings> GetCleanupPolici
5562
return _scopeAccessor.AmbientScope.Database.Fetch<ContentVersionCleanupPolicySettings>(query);
5663
}
5764

65+
/// <inheritdoc />
66+
public IEnumerable<ContentVersionMeta> GetPagedItemsByContentId(int contentId, long pageIndex, int pageSize, out long totalRecords, int? languageId = null)
67+
{
68+
var query = _scopeAccessor.AmbientScope.SqlContext.Sql();
69+
70+
query.Select(@"umbracoDocument.nodeId as contentId,
71+
umbracoContent.contentTypeId as contentTypeId,
72+
umbracoContentVersion.id as versionId,
73+
umbracoContentVersion.userId as userId,
74+
umbracoContentVersion.versionDate as versionDate,
75+
umbracoDocumentVersion.published as currentPublishedVersion,
76+
umbracoContentVersion.[current] as currentDraftVersion,
77+
umbracoContentVersion.preventCleanup as preventCleanup,
78+
umbracoUser.userName as username")
79+
.From<DocumentDto>()
80+
.InnerJoin<ContentDto>()
81+
.On<DocumentDto, ContentDto>(left => left.NodeId, right => right.NodeId)
82+
.InnerJoin<ContentVersionDto>()
83+
.On<ContentDto, ContentVersionDto>(left => left.NodeId, right => right.NodeId)
84+
.InnerJoin<DocumentVersionDto>()
85+
.On<ContentVersionDto, DocumentVersionDto>(left => left.Id, right => right.Id)
86+
.LeftJoin<UserDto>()
87+
.On<UserDto, ContentVersionDto>(left => left.Id, right => right.UserId)
88+
.LeftJoin<ContentVersionCultureVariationDto>()
89+
.On<ContentVersionCultureVariationDto, ContentVersionDto>(left => left.VersionId, right => right.Id)
90+
.Where<ContentVersionDto>(x => x.NodeId == contentId);
91+
92+
// TODO: If there's not a better way to write this then we need a better way to write this.
93+
query = languageId.HasValue
94+
? query.Where<ContentVersionCultureVariationDto>(x => x.LanguageId == languageId.Value)
95+
: query.Where("umbracoContentVersionCultureVariation.languageId is null");
96+
97+
query = query.OrderByDescending<ContentVersionDto>(x => x.Id);
98+
99+
var page = _scopeAccessor.AmbientScope.Database.Page<ContentVersionMeta>(pageIndex + 1, pageSize, query);
100+
101+
totalRecords = page.TotalItems;
102+
103+
return page.Items;
104+
}
105+
58106
/// <inheritdoc />
59107
/// <remarks>
60108
/// Deletes in batches of <see cref="Constants.Sql.MaxParameterCount"/>
@@ -90,5 +138,45 @@ public void DeleteVersions(IEnumerable<int> versionIds)
90138
_scopeAccessor.AmbientScope.Database.Execute(query);
91139
}
92140
}
141+
142+
/// <inheritdoc />
143+
public void SetPreventCleanup(int versionId, bool preventCleanup)
144+
{
145+
var query = _scopeAccessor.AmbientScope.SqlContext.Sql()
146+
.Update<ContentVersionDto>(x => x.Set(y => y.PreventCleanup, preventCleanup))
147+
.Where<ContentVersionDto>(x => x.Id == versionId);
148+
149+
_scopeAccessor.AmbientScope.Database.Execute(query);
150+
}
151+
152+
/// <inheritdoc />
153+
public ContentVersionMeta Get(int versionId)
154+
{
155+
var query = _scopeAccessor.AmbientScope.SqlContext.Sql();
156+
157+
query.Select(@"umbracoDocument.nodeId as contentId,
158+
umbracoContent.contentTypeId as contentTypeId,
159+
umbracoContentVersion.id as versionId,
160+
umbracoContentVersion.userId as userId,
161+
umbracoContentVersion.versionDate as versionDate,
162+
umbracoDocumentVersion.published as currentPublishedVersion,
163+
umbracoContentVersion.[current] as currentDraftVersion,
164+
umbracoContentVersion.preventCleanup as preventCleanup,
165+
umbracoUser.userName as username")
166+
.From<DocumentDto>()
167+
.InnerJoin<ContentDto>()
168+
.On<DocumentDto, ContentDto>(left => left.NodeId, right => right.NodeId)
169+
.InnerJoin<ContentVersionDto>()
170+
.On<ContentDto, ContentVersionDto>(left => left.NodeId, right => right.NodeId)
171+
.InnerJoin<DocumentVersionDto>()
172+
.On<ContentVersionDto, DocumentVersionDto>(left => left.Id, right => right.Id)
173+
.LeftJoin<UserDto>()
174+
.On<UserDto, ContentVersionDto>(left => left.Id, right => right.UserId)
175+
.LeftJoin<ContentVersionCultureVariationDto>()
176+
.On<ContentVersionCultureVariationDto, ContentVersionDto>(left => left.VersionId, right => right.Id)
177+
.Where<ContentVersionDto>(x => x.Id == versionId);
178+
179+
return _scopeAccessor.AmbientScope.Database.Single<ContentVersionMeta>(query);
180+
}
93181
}
94182
}

src/Umbraco.Core/Services/IContentVersionCleanupPolicy.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ public interface IContentVersionCleanupPolicy
1212
/// <summary>
1313
/// Filters a set of candidates historic content versions for cleanup according to policy settings.
1414
/// </summary>
15-
IEnumerable<HistoricContentVersionMeta> Apply(DateTime asAtDate, IEnumerable<HistoricContentVersionMeta> items);
15+
IEnumerable<ContentVersionMeta> Apply(DateTime asAtDate, IEnumerable<ContentVersionMeta> items);
1616
}
1717
}

src/Umbraco.Core/Services/IContentVersionService.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,17 @@ public interface IContentVersionService
99
/// <summary>
1010
/// Removes historic content versions according to a policy.
1111
/// </summary>
12-
IReadOnlyCollection<HistoricContentVersionMeta> PerformContentVersionCleanup(DateTime asAtDate);
12+
IReadOnlyCollection<ContentVersionMeta> PerformContentVersionCleanup(DateTime asAtDate);
13+
14+
/// <summary>
15+
/// Gets paginated content versions for given content id paginated.
16+
/// </summary>
17+
/// <exception cref="ArgumentException">Thrown when <paramref name="culture"/> is invalid.</exception>
18+
IEnumerable<ContentVersionMeta> GetPagedContentVersions(int contentId, long pageIndex, int pageSize, out long totalRecords, string culture = null);
19+
20+
/// <summary>
21+
/// Updates preventCleanup value for given content version.
22+
/// </summary>
23+
void SetPreventCleanup(int versionId, bool preventCleanup, int userId = -1);
1324
}
1425
}

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

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3227,25 +3227,64 @@ public OperationResult Rollback(int id, int versionId, string culture = "*", int
32273227
/// In v9 this can live in another class as we publish the notifications via IEventAggregator.
32283228
/// But for v8 must be here for access to the static events.
32293229
/// </remarks>
3230-
public IReadOnlyCollection<HistoricContentVersionMeta> PerformContentVersionCleanup(DateTime asAtDate)
3230+
public IReadOnlyCollection<ContentVersionMeta> PerformContentVersionCleanup(DateTime asAtDate)
32313231
{
32323232
return CleanupDocumentVersions(asAtDate);
32333233
// Media - ignored
32343234
// Members - ignored
32353235
}
32363236

3237+
/// <inheritdoc />
3238+
public IEnumerable<ContentVersionMeta> GetPagedContentVersions(int contentId, long pageIndex, int pageSize, out long totalRecords, string culture = null)
3239+
{
3240+
if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex));
3241+
if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize));
3242+
3243+
// NOTE: v9 - don't service locate
3244+
var documentVersionRepository = Composing.Current.Factory.GetInstance<IDocumentVersionRepository>();
3245+
3246+
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
3247+
{
3248+
var languageId = _languageRepository.GetIdByIsoCode(culture, throwOnNotFound: true);
3249+
scope.ReadLock(Constants.Locks.ContentTree);
3250+
return documentVersionRepository.GetPagedItemsByContentId(contentId, pageIndex, pageSize, out totalRecords, languageId);
3251+
}
3252+
}
3253+
3254+
/// <inheritdoc />
3255+
public void SetPreventCleanup(int versionId, bool preventCleanup, int userId = -1)
3256+
{
3257+
// NOTE: v9 - don't service locate
3258+
var documentVersionRepository = Composing.Current.Factory.GetInstance<IDocumentVersionRepository>();
3259+
3260+
using (ScopeProvider.CreateScope(autoComplete: true))
3261+
{
3262+
documentVersionRepository.SetPreventCleanup(versionId, preventCleanup);
3263+
3264+
var version = documentVersionRepository.Get(versionId);
3265+
3266+
var auditType = preventCleanup
3267+
? AuditType.ContentVersionPreventCleanup
3268+
: AuditType.ContentVersionEnableCleanup;
3269+
3270+
var message = $"set preventCleanup = '{preventCleanup}' for version '{versionId}'";
3271+
3272+
Audit(auditType, userId, version.ContentId, message, $"{version.VersionDate}");
3273+
}
3274+
}
3275+
32373276
/// <remarks>
32383277
/// v9 - move to another class
32393278
/// </remarks>
3240-
private IReadOnlyCollection<HistoricContentVersionMeta> CleanupDocumentVersions(DateTime asAtDate)
3279+
private IReadOnlyCollection<ContentVersionMeta> CleanupDocumentVersions(DateTime asAtDate)
32413280
{
32423281
// NOTE: v9 - don't service locate
32433282
var documentVersionRepository = Composing.Current.Factory.GetInstance<IDocumentVersionRepository>();
32443283

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

3248-
List<HistoricContentVersionMeta> versionsToDelete;
3287+
List<ContentVersionMeta> versionsToDelete;
32493288

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

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

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

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

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

0 commit comments

Comments
 (0)