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>
This commit is contained in:
Paul Johnson
2021-11-16 07:24:12 +00:00
committed by GitHub
parent 1fbf02d61e
commit d89725bd48
28 changed files with 801 additions and 171 deletions

View File

@@ -1343,6 +1343,77 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
),
"Failed to remove public access for content item with id " + contentId
);
},
/**
* @ngdoc method
* @name umbraco.resources.contentResource#getPagedContentVersions
* @methodOf umbraco.resources.contentResource
*
* @description
* Returns a paged array of previous version id's, given a node id, pageNumber, pageSize and a culture
*
* ##usage
* <pre>
* contentResource.getPagedContentVersions(id, pageNumber, pageSize, culture)
* .then(function(versions) {
* alert('its here!');
* });
* </pre>
*
* @param {Int} id Id of node
* @param {Int} pageNumber page number
* @param {Int} pageSize page size
* @param {Int} culture if provided, the results will be for this specific culture/variant
* @returns {Promise} resourcePromise object containing the versions
*
*/
getPagedContentVersions: function (contentId, pageNumber, pageSize, culture) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl("contentApiBaseUrl", "GetPagedContentVersions", {
contentId: contentId,
pageNumber: pageNumber,
pageSize: pageSize,
culture: culture
})
),
"Failed to get versions for content item with id " + contentId
);
},
/**
* @ngdoc method
* @name umbraco.resources.contentResource#contentVersionPreventCleanup
* @methodOf umbraco.resources.contentResource
*
* @description
* Enables or disabled clean up of a version
*
* ##usage
* <pre>
* contentResource.contentVersionPreventCleanup(contentId, versionId, preventCleanup)
* .then(function() {
* // do your thing
* });
* </pre>
*
* @param {Int} contentId Id of node
* @param {Int} versionId Id of version
* @param {Int} preventCleanup Boolean to toggle clean up prevention
*
*/
contentVersionPreventCleanup: function (contentId, versionId, preventCleanup) {
return umbRequestHelper.resourcePromise(
$http.post(
umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostSetContentVersionPreventCleanup", {
contentId: contentId,
versionId: versionId,
preventCleanup: preventCleanup
})
),
"Failed to toggle prevent cleanup of version with id " + versionId
);
}
};
}

View File

@@ -527,7 +527,7 @@ When building a custom infinite editor view you can use the same components as a
*/
function rollback(editor) {
editor.view = "views/common/infiniteeditors/rollback/rollback.html";
if (!editor.size) editor.size = "medium";
if (!editor.size) editor.size = "";
open(editor);
}

View File

@@ -203,6 +203,8 @@
@import "components/umbemailmarketing.less";
// Editors
@import "../views/common/infiniteeditors/rollback/rollback.less";
// Property Editors
@import "../views/components/blockcard/umb-block-card-grid.less";

View File

@@ -91,6 +91,11 @@
color: @white;
}
.umb-button__success.-black,
.umb-button__error.-black {
color: @black;
}
.umb-button__overlay {
position: absolute;
width: 100%;

View File

@@ -1,7 +1,7 @@
(function () {
"use strict";
function RollbackController($scope, contentResource, localizationService, assetsService, dateHelper, userService) {
function RollbackController($scope, contentResource, localizationService, assetsService, dateHelper, userService, notificationsService) {
var vm = this;
@@ -10,6 +10,9 @@
vm.changeVersion = changeVersion;
vm.submit = submit;
vm.close = close;
vm.pinVersion = pinVersion;
vm.goToPage = goToPage;
vm.paginationCount = { from: 0, to: 0, total: 0 };
//////////
@@ -22,6 +25,11 @@
vm.rollbackButtonDisabled = true;
vm.labels = {};
vm.pageSize = 15;
vm.pageNumber = 1;
vm.totalPages = 1;
vm.totalItems = 0;
// find the current version for invariant nodes
if($scope.model.node.variants.length === 1) {
vm.currentVersion = $scope.model.node.variants[0];
@@ -61,27 +69,40 @@
function changeLanguage(language) {
vm.currentVersion = language;
vm.pageNumber = 1;
getVersions();
}
function changeVersion(version) {
if(version && version.versionId) {
const canRollback = !version.currentDraftVersion && !version.currentPublishedVersion;
vm.loading = true;
if (canRollback === false) {
return;
}
if (vm.previousVersion && version && vm.previousVersion.versionId === version.versionId) {
vm.previousVersion = null;
vm.diff = null;
vm.rollbackButtonDisabled = true;
return;
}
if (version && version.versionId) {
vm.loadingDiff = true;
const culture = $scope.model.node.variants.length > 1 ? vm.currentVersion.language.culture : null;
contentResource.getRollbackVersion(version.versionId, culture)
.then(function(data) {
vm.previousVersion = data;
vm.previousVersion.versionId = version.versionId;
vm.previousVersion.displayValue = version.displayValue + ' - ' + version.username;
createDiff(vm.currentVersion, vm.previousVersion);
vm.loading = false;
vm.loadingDiff = false;
vm.rollbackButtonDisabled = false;
}, function () {
vm.loading = false;
vm.loadingDiff = false;
});
} else {
@@ -93,15 +114,26 @@
function getVersions() {
const nodeId = $scope.model.node.id;
const culture = $scope.model.node.variants.length > 1 ? vm.currentVersion.language.culture : null;
const culture = vm.currentVersion.language ? vm.currentVersion.language.culture : null;
return contentResource.getRollbackVersions(nodeId, culture)
return contentResource.getPagedContentVersions(nodeId, vm.pageNumber, vm.pageSize, culture)
.then(function (data) {
vm.totalPages = data.totalPages;
vm.totalItems = data.totalItems;
const possibleTotalItems = vm.pageNumber * vm.pageSize;
vm.paginationCount = {
from: (vm.pageNumber * vm.pageSize - vm.pageSize) + 1,
to: vm.totalItems < possibleTotalItems ? vm.totalItems : possibleTotalItems,
total: vm.totalItems
};
// get current backoffice user and format dates
userService.getCurrentUser().then(function (currentUser) {
vm.previousVersions = data.map(version => {
vm.previousVersions = data.items.map(version => {
var timestampFormatted = dateHelper.getLocalDate(version.versionDate, currentUser.locale, 'LLL');
version.displayValue = timestampFormatted + ' - ' + version.versionAuthorName;
version.displayValue = timestampFormatted;
return version;
});
});
@@ -200,6 +232,39 @@
}
}
function pinVersion (version, event) {
if (!version) {
return;
}
version.pinningState = 'busy';
const nodeId = $scope.model.node.id;
const versionId = version.versionId;
const preventCleanup = !version.preventCleanup;
contentResource.contentVersionPreventCleanup(nodeId, versionId, preventCleanup)
.then(() => {
version.pinningState = 'success';
version.preventCleanup = preventCleanup;
}, () => {
version.pinningState = 'error';
const localizationKey = preventCleanup ? 'speechBubbles_preventCleanupEnableError' : 'speechBubbles_preventCleanupDisableError';
localizationService.localize(localizationKey).then(value => {
notificationsService.error(value);
});
});
event.stopPropagation();
}
function goToPage (pageNumber) {
vm.pageNumber = pageNumber;
getVersions();
}
onInit();
}

View File

@@ -1,4 +1,4 @@
<div ng-controller="Umbraco.Editors.RollbackController as vm">
<div class="editor-rollback" ng-controller="Umbraco.Editors.RollbackController as vm">
<umb-editor-view>
@@ -16,75 +16,138 @@
ng-if="vm.loading">
</umb-load-indicator>
<umb-box>
<umb-box-content>
<div class="main-content">
<div ng-if="model.node.variants.length > 1">
<h5><localize key="general_language">Language</localize></h5>
<select
class="input-block-level"
ng-model="vm.selectedLanguage"
ng-options="variant as variant.displayName for variant in model.node.variants track by variant.language.culture"
ng-change="vm.changeLanguage(vm.selectedLanguage)">
</select>
</div>
<div>
<h5><localize key="rollback_currentVersion">Current version</localize></h5>
<p>{{vm.currentVersion.name}} (<localize key="rollback_created">Created</localize>: {{vm.currentVersion.createDate}})</p>
<h5><localize key="rollback_rollbackTo">Rollback to</localize></h5>
<select
class="input-block-level"
ng-model="vm.selectedVersion"
ng-options="version.displayValue for version in vm.previousVersions track by version.versionId"
ng-change="vm.changeVersion(vm.selectedVersion)">
<option value="">{{vm.labels.choose}}...</option>
</select>
</div>
<div ng-if="vm.diff && !vm.loading" class="diff" style="border-top: 1px solid #e9e9eb; margin-top: 30px;">
<h5><localize key="rollback_changes">Changes</localize></h5>
<small style="margin-bottom: 15px; display: block;">
<localize key="rollback_diffHelp">
This shows the differences between the current version and the selected version<br /><del>Red text</del> will be
removed in the selected version, <ins>green text</ins> will be added
<umb-box class="side-panel">
<umb-box-header title-key="rollback_versions">
<small>
<localize
key="rollback_pagination"
tokens="[vm.paginationCount.from, vm.paginationCount.to, vm.paginationCount.total]"
watch-tokens="true">
</localize>
</small>
</umb-box-header>
<table class="table table-condensed table-bordered">
<tbody>
<tr>
<td class="bold">
<localize key="general_name">Name</localize>
</td>
<td>
<span ng-repeat="part in vm.diff.name track by $id(part)">
<ins ng-if="part.added">{{part.value}}</ins>
<del ng-if="part.removed">{{part.value}}</del>
<span ng-if="!part.added && !part.removed">{{part.value}}</span>
</span>
</td>
</tr>
<tr ng-repeat="property in vm.diff.properties track by property.alias">
<td class="bold">{{property.label}}</td>
<td ng-class="{'pre-line': property.isObject, 'word-wrap': !property.isObject}">
<span ng-repeat="part in property.diff track by $id(part)">
<ins ng-if="part.added">{{part.value}}</ins>
<del ng-if="part.removed">{{part.value}}</del>
<span ng-if="!part.added && !part.removed">{{part.value}}</span>
</span>
</td>
</tr>
</tbody>
</table>
<umb-box-content>
</div>
<div ng-if="model.node.variants.length > 1">
<h5 class="mt0"><localize key="general_language">Language</localize></h5>
<select
class="culture-select input-block-level"
ng-model="vm.selectedLanguage"
ng-options="variant as variant.displayName for variant in model.node.variants track by variant.language.culture"
ng-change="vm.changeLanguage(vm.selectedLanguage)">
</select>
</div>
</umb-box-content>
</umb-box>
<div>
<div class="current-version">
<span class="bold"><localize key="rollback_currentVersion">Current version</localize>: </span>
<span>{{vm.currentVersion.name}} (<localize key="rollback_created">Created</localize>: {{vm.currentVersion.createDate}})</span>
</div>
<div class="umb-table umb-table--condensed">
<div class="umb-table-body">
<div class="umb-table-row umb-outline"
ng-repeat="version in vm.previousVersions track by version.versionId"
ng-class="{'-selectable cursor-pointer': !version.currentDraftVersion && !version.currentPublishedVersion,'-selected': version.versionId === vm.previousVersion.versionId }"
ng-click="vm.changeVersion(version)">
<div class="umb-table-cell dn"></div>
<div class="umb-table-cell black">
<div class="flex flex-column">
<div>{{ version.displayValue }}</div>
<div class="version-details">{{version.username}}</div>
<div class="version-details">
<localize ng-if="version.currentPublishedVersion" key="rollback_currentPublishedVersion">Current version</localize>
<localize ng-if="version.currentDraftVersion" key="rollback_currentDraftVersion">Current version</localize>
</div>
</div>
</div>
<div class="umb-table-cell umb-table-cell--auto-width">
<umb-button
style="margin-left: auto;"
button-style="outline"
type="button"
size="xxs"
state="version.pinningState"
action="vm.pinVersion(version, $event)"
label="{{ version.preventCleanup ? 'Enable cleanup' : 'Prevent cleanup' }}">
</umb-button>
</div>
</div>
</div>
</div>
</div>
<umb-pagination
ng-if="vm.totalPages > 0 && !vm.loading"
page-number="vm.pageNumber"
total-pages="vm.totalPages"
on-change="vm.goToPage(pageNumber)">
</umb-pagination>
</umb-box-content>
</umb-box>
<umb-box class="compare-panel">
<umb-box-header ng-if="vm.previousVersion" title="{{vm.previousVersion.displayValue}}"></umb-box-header>
<umb-box-content>
<umb-load-indicator ng-if="vm.loadingDiff"></umb-load-indicator>
<umb-empty-state
ng-if="!vm.diff"
position="center">
<localize key="rollback_headline"></localize>
</umb-empty-state>
<div ng-if="vm.diff && !vm.loadingDiff" class="diff">
<small class="db" style="margin-bottom: 15px;">
<localize key="rollback_diffHelp">
This shows the differences between the current version and the selected version<br /><del>Red text</del> will be
removed in the selected version, <ins>green text</ins> will be added
</localize>
</small>
<table class="table table-condensed table-bordered">
<tbody>
<tr>
<td class="bold">
<localize key="general_name">Name</localize>
</td>
<td>
<span ng-repeat="part in vm.diff.name track by $id(part)">
<ins ng-if="part.added">{{part.value}}</ins>
<del ng-if="part.removed">{{part.value}}</del>
<span ng-if="!part.added && !part.removed">{{part.value}}</span>
</span>
</td>
</tr>
<tr ng-repeat="property in vm.diff.properties track by property.alias">
<td class="bold">{{property.label}}</td>
<td ng-class="{'pre-line': property.isObject, 'word-wrap': !property.isObject}">
<span ng-repeat="part in property.diff track by $id(part)">
<ins ng-if="part.added">{{part.value}}</ins>
<del ng-if="part.removed">{{part.value}}</del>
<span ng-if="!part.added && !part.removed">{{part.value}}</span>
</span>
</td>
</tr>
</tbody>
</table>
</div>
</umb-box-content>
</umb-box>
</div>
</umb-editor-container>
<umb-editor-footer>

View File

@@ -0,0 +1,32 @@
.editor-rollback {
.main-content {
display: flex;
gap: 20px;
}
.side-panel {
flex: 0 0 33%;
}
.compare-panel {
flex: 1 1 auto;
position: relative;
}
.current-version {
background: @gray-10;
padding: 15px;
margin-bottom: 12px;
}
.culture-select {
margin-bottom: 12px;
}
.version-details {
color: @gray-5;
font-size: 13px;
}
}

View File

@@ -1,9 +1,9 @@
<div class="umb-button" ng-class="{'ml0': vm.generalActions, 'umb-button--block': vm.blockElement}" data-element="{{ vm.alias ? 'button-' + vm.alias : '' }}">
<div ng-if="vm.innerState">
<umb-icon icon="icon-check" class="umb-button__success" ng-class="{'-hidden': vm.innerState !== 'success', '-white': vm.isPrimaryButtonStyle}"></umb-icon>
<umb-icon icon="icon-delete" class="umb-button__error" ng-class="{'-hidden': vm.innerState !== 'error', '-white': vm.isPrimaryButtonStyle}"></umb-icon>
<div class="umb-button__progress" ng-class="{'-hidden': vm.innerState !== 'busy', '-white': vm.isPrimaryButtonStyle}"></div>
<umb-icon icon="icon-check" class="umb-button__success" ng-class="{'-hidden': vm.innerState !== 'success', '-white': vm.isPrimaryButtonStyle, '-black': vm.buttonStyle === 'outline'}"></umb-icon>
<umb-icon icon="icon-delete" class="umb-button__error" ng-class="{'-hidden': vm.innerState !== 'error', '-white': vm.isPrimaryButtonStyle, '-black': vm.buttonStyle === 'outline'}"></umb-icon>
<div class="umb-button__progress" ng-class="{'-hidden': vm.innerState !== 'busy', '-white': vm.isPrimaryButtonStyle, '-black': vm.buttonStyle === 'outline'}"></div>
<div ng-if="vm.innerState !== 'init'" class="umb-button__overlay"></div>
</div>