Merge branch 'u4-5003' of https://github.com/AndyButland/Umbraco-CMS into AndyButland-u4-5003

This commit is contained in:
Shannon
2014-08-18 11:38:09 -06:00
9 changed files with 344 additions and 48 deletions

View File

@@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Umbraco.Core.Persistence.Querying;
@@ -125,5 +126,10 @@ namespace Umbraco.Core.Persistence
SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName(rightColumnName));
return sql.On(onClause);
}
public static Sql OrderByDescending(this Sql sql, params object[] columns)
{
return sql.Append(new Sql("ORDER BY " + String.Join(", ", (from x in columns select x.ToString() + " DESC").ToArray())));
}
}
}

View File

@@ -3,8 +3,10 @@ using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Xml.Linq;
using Umbraco.Core.Configuration;
using Umbraco.Core.Dynamics;
using Umbraco.Core.IO;
using Umbraco.Core.Models;
using Umbraco.Core.Models.EntityBase;
@@ -642,7 +644,117 @@ namespace Umbraco.Core.Persistence.Repositories
_contentPreviewRepository.AddOrUpdate(new ContentPreviewEntity<IContent>(previewExists, content, xml));
}
/// <summary>
/// Gets paged content results
/// </summary>
/// <param name="query">Query to excute</param>
/// <param name="pageNumber">Page number</param>
/// <param name="pageSize">Page size</param>
/// <param name="totalRecords">Total records query would return without paging</param>
/// <param name="orderBy">Field to order by</param>
/// <param name="orderDirections">Direction to order by</param>
/// <param name="filter">Search text filter</param>
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
public IEnumerable<IContent> GetPagedResultsByQuery(IQuery<IContent> query, int pageNumber, int pageSize, out int totalRecords,
string orderBy, Direction orderDirection, string filter = "")
{
// Get base query
var sqlClause = GetBaseQuery(false);
var translator = new SqlTranslator<IContent>(sqlClause, query);
var sql = translator.Translate()
.Where<DocumentDto>(x => x.Newest);
// Apply filter
if (!string.IsNullOrEmpty(filter))
{
sql = sql.Where("cmsDocument.text LIKE @0", "%" + filter + "%");
}
// Apply order according to parameters
if (!string.IsNullOrEmpty(orderBy))
{
var orderByParams = new[] { GetDatabaseFieldNameForOrderBy(orderBy) };
if (orderDirection == Direction.Ascending)
{
sql = sql.OrderBy(orderByParams);
}
else
{
sql = sql.OrderByDescending(orderByParams);
}
}
// Note we can't do multi-page for several DTOs like we can multi-fetch and are doing in PerformGetByQuery,
// but actually given we are doing a Get on each one (again as in PerformGetByQuery), we only need the node Id.
// So we'll modify the SQL.
var modifiedSQL = sql.SQL.Replace("SELECT *", "SELECT cmsDocument.nodeId");
// Get page of results and total count
IEnumerable<IContent> result;
var pagedResult = Database.Page<DocumentDto>(pageNumber, pageSize, modifiedSQL, sql.Arguments);
totalRecords = Convert.ToInt32(pagedResult.TotalItems);
if (totalRecords > 0)
{
// Parse out node Ids and load content (we need the cast here in order to be able to call the IQueryable extension
// methods OrderBy or OrderByDescending)
var content = GetAll(pagedResult.Items
.DistinctBy(x => x.NodeId)
.Select(x => x.NodeId).ToArray())
.Cast<Content>()
.AsQueryable();
// Now we need to ensure this result is also ordered by the same order by clause
var orderByProperty = GetIContentPropertyNameForOrderBy(orderBy);
if (orderDirection == Direction.Ascending)
{
result = content.OrderBy(orderByProperty);
}
else
{
result = content.OrderByDescending(orderByProperty);
}
}
else
{
result = Enumerable.Empty<IContent>();
}
return result;
}
private string GetDatabaseFieldNameForOrderBy(string orderBy)
{
// Translate the passed order by field (which were originally defined for in-memory object sorting
// of ContentItemBasic instances) to the database field names.
switch (orderBy)
{
case "Name":
return "cmsDocument.text";
case "Owner":
return "umbracoNode.nodeUser";
case "Updator":
return "cmsDocument.documentUser";
default:
return orderBy;
}
}
private string GetIContentPropertyNameForOrderBy(string orderBy)
{
// Translate the passed order by field (which were originally defined for in-memory object sorting
// of ContentItemBasic instances) to the IContent property names.
switch (orderBy)
{
case "Owner":
return "CreatorId";
case "Updator":
return "WriterId";
default:
return orderBy;
}
}
#endregion
/// <summary>

View File

@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Xml.Linq;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Querying;
namespace Umbraco.Core.Persistence.Repositories
@@ -66,5 +68,18 @@ namespace Umbraco.Core.Persistence.Repositories
/// <param name="xml"></param>
void AddOrUpdatePreviewXml(IContent content, Func<IContent, XElement> xml);
/// <summary>
/// Gets paged content results
/// </summary>
/// <param name="query">Query to excute</param>
/// <param name="pageNumber">Page number</param>
/// <param name="pageSize">Page size</param>
/// <param name="totalRecords">Total records query would return without paging</param>
/// <param name="orderBy">Field to order by</param>
/// <param name="orderDirections">Direction to order by</param>
/// <param name="filter">Search text filter</param>
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
IEnumerable<IContent> GetPagedResultsByQuery(IQuery<IContent> query, int pageNumber, int pageSize, out int totalRecords,
string orderBy, Direction orderDirection, string filter = "");
}
}

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Xml.Linq;
using Umbraco.Core.Auditing;
@@ -13,6 +14,7 @@ using Umbraco.Core.Models.Membership;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Caching;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.SqlSyntax;
@@ -473,6 +475,29 @@ namespace Umbraco.Core.Services
}
}
/// <summary>
/// Gets a collection of <see cref="IContent"/> objects by Parent Id
/// </summary>
/// <param name="id">Id of the Parent to retrieve Children from</param>
/// <param name="pageNumber">Page number</param>
/// <param name="pageSize">Page size</param>
/// <param name="totalRecords">Total records query would return without paging</param>
/// <param name="orderBy">Field to order by</param>
/// <param name="orderDirections">Direction to order by</param>
/// <param name="filter">Search text filter</param>
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
public IEnumerable<IContent> GetPagedChildren(int id, int pageNumber, int pageSize, out int totalChildren,
string orderBy, Direction orderDirection, string filter = "")
{
using (var repository = _repositoryFactory.CreateContentRepository(_uowProvider.GetUnitOfWork()))
{
var query = Query<IContent>.Builder.Where(x => x.ParentId == id);
var contents = repository.GetPagedResultsByQuery(query, pageNumber, pageSize, out totalChildren, orderBy, orderDirection, filter);
return contents;
}
}
/// <summary>
/// Gets a collection of <see cref="IContent"/> objects by its name or partial name
/// </summary>

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Publishing;
namespace Umbraco.Core.Services
@@ -108,6 +109,20 @@ namespace Umbraco.Core.Services
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
IEnumerable<IContent> GetChildren(int id);
/// <summary>
/// Gets a collection of <see cref="IContent"/> objects by Parent Id
/// </summary>
/// <param name="id">Id of the Parent to retrieve Children from</param>
/// <param name="pageNumber">Page number</param>
/// <param name="pageSize">Page size</param>
/// <param name="totalRecords">Total records query would return without paging</param>
/// <param name="orderBy">Field to order by</param>
/// <param name="orderDirections">Direction to order by</param>
/// <param name="filter">Search text filter</param>
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
IEnumerable<IContent> GetPagedChildren(int id, int pageNumber, int pageSize, out int totalChildren,
string orderBy, Direction orderDirection, string filter = "");
/// <summary>
/// Gets a collection of an <see cref="IContent"/> objects versions by its Id
/// </summary>

View File

@@ -15,6 +15,7 @@ using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
using umbraco.editorControls.tinyMCE3;
using umbraco.interfaces;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
namespace Umbraco.Tests.Persistence.Repositories
{
@@ -331,6 +332,132 @@ namespace Umbraco.Tests.Persistence.Repositories
}
}
[Test]
public void Can_Perform_GetPagedResultsByQuery_ForFirstPage_On_ContentRepository()
{
// Arrange
var provider = new PetaPocoUnitOfWorkProvider();
var unitOfWork = provider.GetUnitOfWork();
ContentTypeRepository contentTypeRepository;
using (var repository = CreateRepository(unitOfWork, out contentTypeRepository))
{
// Act
var query = Query<IContent>.Builder.Where(x => x.Level == 2);
int totalRecords;
var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "Name", Direction.Ascending);
// Assert
Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2));
Assert.That(result.Count(), Is.EqualTo(1));
Assert.That(result.First().Name, Is.EqualTo("Text Page 1"));
}
}
[Test]
public void Can_Perform_GetPagedResultsByQuery_ForSecondPage_On_ContentRepository()
{
// Arrange
var provider = new PetaPocoUnitOfWorkProvider();
var unitOfWork = provider.GetUnitOfWork();
ContentTypeRepository contentTypeRepository;
using (var repository = CreateRepository(unitOfWork, out contentTypeRepository))
{
// Act
var query = Query<IContent>.Builder.Where(x => x.Level == 2);
int totalRecords;
var result = repository.GetPagedResultsByQuery(query, 2, 1, out totalRecords, "Name", Direction.Ascending);
// Assert
Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2));
Assert.That(result.Count(), Is.EqualTo(1));
Assert.That(result.First().Name, Is.EqualTo("Text Page 2"));
}
}
[Test]
public void Can_Perform_GetPagedResultsByQuery_WithSinglePage_On_ContentRepository()
{
// Arrange
var provider = new PetaPocoUnitOfWorkProvider();
var unitOfWork = provider.GetUnitOfWork();
ContentTypeRepository contentTypeRepository;
using (var repository = CreateRepository(unitOfWork, out contentTypeRepository))
{
// Act
var query = Query<IContent>.Builder.Where(x => x.Level == 2);
int totalRecords;
var result = repository.GetPagedResultsByQuery(query, 1, 2, out totalRecords, "Name", Direction.Ascending);
// Assert
Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2));
Assert.That(result.Count(), Is.EqualTo(2));
Assert.That(result.First().Name, Is.EqualTo("Text Page 1"));
}
}
[Test]
public void Can_Perform_GetPagedResultsByQuery_WithDescendingOrder_On_ContentRepository()
{
// Arrange
var provider = new PetaPocoUnitOfWorkProvider();
var unitOfWork = provider.GetUnitOfWork();
ContentTypeRepository contentTypeRepository;
using (var repository = CreateRepository(unitOfWork, out contentTypeRepository))
{
// Act
var query = Query<IContent>.Builder.Where(x => x.Level == 2);
int totalRecords;
var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "Name", Direction.Descending);
// Assert
Assert.That(totalRecords, Is.GreaterThanOrEqualTo(2));
Assert.That(result.Count(), Is.EqualTo(1));
Assert.That(result.First().Name, Is.EqualTo("Text Page 2"));
}
}
[Test]
public void Can_Perform_GetPagedResultsByQuery_WithFilterMatchingSome_On_ContentRepository()
{
// Arrange
var provider = new PetaPocoUnitOfWorkProvider();
var unitOfWork = provider.GetUnitOfWork();
ContentTypeRepository contentTypeRepository;
using (var repository = CreateRepository(unitOfWork, out contentTypeRepository))
{
// Act
var query = Query<IContent>.Builder.Where(x => x.Level == 2);
int totalRecords;
var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "Name", Direction.Ascending, "Page 2");
// Assert
Assert.That(totalRecords, Is.EqualTo(1));
Assert.That(result.Count(), Is.EqualTo(1));
Assert.That(result.First().Name, Is.EqualTo("Text Page 2"));
}
}
[Test]
public void Can_Perform_GetPagedResultsByQuery_WithFilterMatchingAll_On_ContentRepository()
{
// Arrange
var provider = new PetaPocoUnitOfWorkProvider();
var unitOfWork = provider.GetUnitOfWork();
ContentTypeRepository contentTypeRepository;
using (var repository = CreateRepository(unitOfWork, out contentTypeRepository))
{
// Act
var query = Query<IContent>.Builder.Where(x => x.Level == 2);
int totalRecords;
var result = repository.GetPagedResultsByQuery(query, 1, 1, out totalRecords, "Name", Direction.Ascending, "Page");
// Assert
Assert.That(totalRecords, Is.EqualTo(2));
Assert.That(result.Count(), Is.EqualTo(1));
Assert.That(result.First().Name, Is.EqualTo("Text Page 1"));
}
}
[Test]
public void Can_Perform_GetAll_By_Param_Ids_On_ContentRepository()
{

View File

@@ -0,0 +1,18 @@
/**
* @ngdoc directive
* @name umbraco.directives.directive:noDirtyCheck
* @restrict A
* @description Can be attached to form inputs to prevent them from setting the form as dirty (http://stackoverflow.com/questions/17089090/prevent-input-from-setting-form-dirty-angularjs)
**/
function noDirtyCheck() {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
elm.focus(function () {
ctrl.$pristine = false;
});
}
};
}
angular.module('umbraco.directives').directive("noDirtyCheck", noDirtyCheck);

View File

@@ -41,11 +41,7 @@
<span ng-bind="bulkStatus" ng-show="isAnythingSelected()"></span>
</div>
<p ng-show="listViewResultSet.totalItems === 0">
<localize key="content_listViewNoItems">There are no items show in the list.</localize>
</p>
<table class="table table-striped" ng-show="listViewResultSet.totalItems > 0">
<table class="table table-striped">
<thead>
<tr>
<td style="width: 35px">
@@ -62,26 +58,38 @@
<td>
<form class="pull-right" novalidate>
<i class="icon-search"></i>
<input type="text" ng-model="options.filter" on-keyup="search()">
<input type="text" ng-model="options.filter" on-keyup="search()" no-dirty-check>
</form>
</td>
</tr>
</thead>
<tbody>
<tbody ng-show="listViewResultSet.totalItems === 0">
<tr>
<td colspan="5">
<p><localize key="content_listViewNoItems">There are no items show in the list.</localize></p>
</td>
</tr>
</tbody>
<tbody ng-show="listViewResultSet.totalItems > 0">
<tr ng-repeat="result in listViewResultSet.items"
ng-class="{selected:result.selected}">
<td>
<i class="icon {{result.icon}}" ng-class="getIcon(result)"></i>
<input type="checkbox" ng-model="result.selected"></td>
<input type="checkbox" ng-model="result.selected">
</td>
<td>
<a ng-class="{inactive: (entityType === 'content' && !result.published) || isTrashed}" href="#/{{entityType}}/{{entityType}}/edit/{{result.id}}">{{result.name}}</a></td>
<td>{{result.updateDate|date:'medium'}}
<a ng-class="{inactive: (entityType === 'content' && !result.published) || isTrashed}" href="#/{{entityType}}/{{entityType}}/edit/{{result.id}}">{{result.name}}</a>
</td>
<td>
{{result.updateDate|date:'medium'}}
<!--<<span class="label label-success">Publish</span>-->
</td>
<td>{{result.owner.name}}
<td>
{{result.owner.name}}
<!--<span class="label">Admin</span>-->
</td>
<td></td>

View File

@@ -157,47 +157,17 @@ namespace Umbraco.Web.Editors
Direction orderDirection = Direction.Ascending,
string filter = "")
{
//TODO: This will be horribly inefficient for paging! This is because our datasource/repository
// doesn't support paging at the SQL level... and it'll be pretty interesting to try to make that work.
//PP: could we in 7.0.1+ migrate this to the internal examine index instead of using the content service?
var children = Services.ContentService.GetChildren(id).ToArray();
var totalChildren = children.Length;
int totalChildren;
var children = Services.ContentService.GetPagedChildren(id, pageNumber, pageSize, out totalChildren, orderBy, orderDirection, filter).ToArray();
if (totalChildren == 0)
{
return new PagedResult<ContentItemBasic<ContentPropertyBasic, IContent>>(0, 0, 0);
var result = children
.Select(Mapper.Map<IContent, ContentItemBasic<ContentPropertyBasic, IContent>>)
.AsQueryable();
//TODO: This is a rudimentry filter - should use the logic found in the EntityService filter (dynamic linq) instead
if (!string.IsNullOrEmpty(filter))
{
filter = filter.ToLower();
result = result.Where(x => x.Name.InvariantContains(filter));
}
var orderedResult = orderDirection == Direction.Ascending
? result.OrderBy(orderBy)
: result.OrderByDescending(orderBy);
var pagedResult = new PagedResult<ContentItemBasic<ContentPropertyBasic, IContent>>(
totalChildren,
pageNumber,
pageSize);
if (pageNumber > 0 && pageSize > 0)
{
pagedResult.Items = orderedResult
.Skip(pagedResult.SkipSize)
.Take(pageSize);
}
else
{
pagedResult.Items = orderedResult;
}
var pagedResult = new PagedResult<ContentItemBasic<ContentPropertyBasic, IContent>>(totalChildren, pageNumber, pageSize);
pagedResult.Items = children
.Select(Mapper.Map<IContent, ContentItemBasic<ContentPropertyBasic, IContent>>);
return pagedResult;
}