V8: Improve nested content optimization in the backoffice (#10885)

* Get all nested content scaffolds in a single post request

* Only load alias array and initialize once

* Remove old nested content load code

* Get the content types within a scope

This will drastically reduce the amount of SQL queries, since the same read lock will be used for all operations.

* Add comments

* Fix typo

* Fix typos

Co-authored-by: Elitsa Marinovska <21998037+elit0451@users.noreply.github.com>
This commit is contained in:
Mole
2021-08-18 15:51:16 +02:00
committed by GitHub
parent f62fbe3327
commit ac53b89fe1
5 changed files with 112 additions and 55 deletions

View File

@@ -642,6 +642,24 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
return $q.when(umbDataFormatter.formatContentGetData(result));
});
},
getScaffolds: function(parentId, aliases){
return umbRequestHelper.resourcePromise(
$http.post(
umbRequestHelper.getApiUrl(
"contentApiBaseUrl",
"GetEmptyByAliases"),
{ parentId: parentId, contentTypeAliases: aliases }
),
'Failed to retrieve data for empty content item aliases ' + aliases.join(", ")
).then(function(result) {
Object.keys(result).map(function(key){
result[key] = umbDataFormatter.formatContentGetData(result[key]);
});
return $q.when(result);
});
},
/**
* @ngdoc method
* @name umbraco.resources.contentResource#getScaffoldByKey

View File

@@ -522,10 +522,14 @@
];
// Initialize
var scaffoldsLoaded = 0;
vm.scaffolds = [];
_.each(model.config.contentTypes, function (contentType) {
contentResource.getScaffold(-20, contentType.ncAlias).then(function (scaffold) {
contentResource.getScaffolds(-20, contentTypeAliases).then(function (scaffolds){
// Loop through all the content types
_.each(model.config.contentTypes, function (contentType){
// Get the scaffold from the result
var scaffold = scaffolds[contentType.ncAlias];
// make sure it's an element type before allowing the user to create new ones
if (scaffold.isElement) {
// remove all tabs except the specified tab
@@ -554,13 +558,10 @@
// Store the scaffold object
vm.scaffolds.push(scaffold);
}
scaffoldsLoaded++;
initIfAllScaffoldsHaveLoaded();
}, function (error) {
scaffoldsLoaded++;
initIfAllScaffoldsHaveLoaded();
});
// Initialize once all scaffolds have been loaded
initNestedContent();
});
/**
@@ -586,57 +587,50 @@
});
}
var initIfAllScaffoldsHaveLoaded = function () {
var initNestedContent = function () {
// Initialize when all scaffolds have loaded
if (model.config.contentTypes.length === scaffoldsLoaded) {
// Because we're loading the scaffolds async one at a time, we need to
// sort them explicitly according to the sort order defined by the data type.
contentTypeAliases = [];
_.each(model.config.contentTypes, function (contentType) {
contentTypeAliases.push(contentType.ncAlias);
});
vm.scaffolds = $filter("orderBy")(vm.scaffolds, function (s) {
return contentTypeAliases.indexOf(s.contentTypeAlias);
});
// Sort the scaffold explicitly according to the sort order defined by the data type.
vm.scaffolds = $filter("orderBy")(vm.scaffolds, function (s) {
return contentTypeAliases.indexOf(s.contentTypeAlias);
});
// Convert stored nodes
if (model.value) {
for (var i = 0; i < model.value.length; i++) {
var item = model.value[i];
var scaffold = getScaffold(item.ncContentTypeAlias);
if (scaffold == null) {
// No such scaffold - the content type might have been deleted. We need to skip it.
continue;
}
createNode(scaffold, item);
// Convert stored nodes
if (model.value) {
for (var i = 0; i < model.value.length; i++) {
var item = model.value[i];
var scaffold = getScaffold(item.ncContentTypeAlias);
if (scaffold == null) {
// No such scaffold - the content type might have been deleted. We need to skip it.
continue;
}
createNode(scaffold, item);
}
// Enforce min items if we only have one scaffold type
var modelWasChanged = false;
if (vm.nodes.length < vm.minItems && vm.scaffolds.length === 1) {
for (var i = vm.nodes.length; i < model.config.minItems; i++) {
addNode(vm.scaffolds[0].contentTypeAlias);
}
modelWasChanged = true;
}
// If there is only one item, set it as current node
if (vm.singleMode || (vm.nodes.length === 1 && vm.maxItems === 1)) {
setCurrentNode(vm.nodes[0], false);
}
validate();
vm.inited = true;
if (modelWasChanged) {
updateModel();
}
updatePropertyActionStates();
checkAbilityToPasteContent();
}
// Enforce min items if we only have one scaffold type
var modelWasChanged = false;
if (vm.nodes.length < vm.minItems && vm.scaffolds.length === 1) {
for (var i = vm.nodes.length; i < model.config.minItems; i++) {
addNode(vm.scaffolds[0].contentTypeAlias);
}
modelWasChanged = true;
}
// If there is only one item, set it as current node
if (vm.singleMode || (vm.nodes.length === 1 && vm.maxItems === 1)) {
setCurrentNode(vm.nodes[0], false);
}
validate();
vm.inited = true;
if (modelWasChanged) {
updateModel();
}
updatePropertyActionStates();
checkAbilityToPasteContent();
}
function extendPropertyWithNCData(prop) {

View File

@@ -364,6 +364,24 @@ namespace Umbraco.Web.Editors
return GetEmpty(contentType, parentId);
}
/// <summary>
/// Gets a dictionary containing empty content items for every alias specified in the contentTypeAliases array in the body of the request.
/// </summary>
/// <remarks>
/// This is a post request in order to support a large amount of aliases without hitting the URL length limit.
/// </remarks>
/// <param name="contentTypesByAliases"></param>
/// <returns></returns>
[OutgoingEditorModelEvent]
[HttpPost]
public IDictionary<string, ContentItemDisplay> GetEmptyByAliases(ContentTypesByAliases contentTypesByAliases)
{
// It's important to do this operation within a scope to reduce the amount of readlock queries.
using var scope = _scopeProvider.CreateScope(autoComplete: true);
var contentTypes = contentTypesByAliases.ContentTypeAliases.Select(alias => Services.ContentTypeService.Get(alias));
return GetEmpties(contentTypes, contentTypesByAliases.ParentId).ToDictionary(x => x.ContentTypeAlias);
}
/// <summary>
/// Gets an empty content item for the document type.

View File

@@ -0,0 +1,26 @@
using System.ComponentModel.DataAnnotations;
using System.Runtime.Serialization;
namespace Umbraco.Web.Models.ContentEditing
{
/// <summary>
/// A model for retrieving multiple content types based on their aliases.
/// </summary>
[DataContract(Name = "contentTypes", Namespace = "")]
public class ContentTypesByAliases
{
/// <summary>
/// Id of the parent of the content type.
/// </summary>
[DataMember(Name = "parentId")]
[Required]
public int ParentId { get; set; }
/// <summary>
/// The alias of every content type to get.
/// </summary>
[DataMember(Name = "contentTypeAliases")]
[Required]
public string[] ContentTypeAliases { get; set; }
}
}

View File

@@ -253,6 +253,7 @@
<Compile Include="Media\UploadAutoFillProperties.cs" />
<Compile Include="Migrations\PostMigrations\PublishedSnapshotRebuilder.cs" />
<Compile Include="Models\AnchorsModel.cs" />
<Compile Include="Models\ContentEditing\ContentTypesByAliases.cs" />
<Compile Include="Models\ContentEditing\ContentTypesByKeys.cs" />
<Compile Include="Models\ContentEditing\DataTypeReferences.cs" />
<Compile Include="Models\ContentEditing\LinkDisplay.cs" />