From 6c9fe2ebd0210fd8c3c8adca07a319fe69fa188e Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 25 Sep 2013 13:54:25 +1000 Subject: [PATCH] Adds dynamic filtering to EntityService (post filtering for GetAll) - updated the insert macro dialog to ensure that it only shows macros flagged for the rte when in rte mode, otherwise shows all of them. adds unit tests for dynamic queryable stuff. --- .../Dynamics/QueryableExtensions.cs | 2 + .../Dynamics/QueryableExtensionTests.cs | 162 ++++++++++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + .../src/common/resources/entity.resource.js | 17 +- .../src/common/services/tinymce.service.js | 11 +- .../common/dialogs/insertmacro.controller.js | 4 +- src/Umbraco.Web/Dynamics/DynamicQueryable.cs | 5 + src/Umbraco.Web/Dynamics/ExpressionParser.cs | 2 + src/Umbraco.Web/Editors/EntityController.cs | 42 ++++- src/Umbraco.Web/Editors/MacroController.cs | 2 + 10 files changed, 237 insertions(+), 11 deletions(-) create mode 100644 src/Umbraco.Tests/Dynamics/QueryableExtensionTests.cs diff --git a/src/Umbraco.Core/Dynamics/QueryableExtensions.cs b/src/Umbraco.Core/Dynamics/QueryableExtensions.cs index f90f08cfa2..b7fea7f68b 100644 --- a/src/Umbraco.Core/Dynamics/QueryableExtensions.cs +++ b/src/Umbraco.Core/Dynamics/QueryableExtensions.cs @@ -57,5 +57,7 @@ namespace Umbraco.Core.Dynamics .Invoke(null, new object[] { source, lambda }); return (IOrderedQueryable)result; } + + } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Dynamics/QueryableExtensionTests.cs b/src/Umbraco.Tests/Dynamics/QueryableExtensionTests.cs new file mode 100644 index 0000000000..e93b7dc50a --- /dev/null +++ b/src/Umbraco.Tests/Dynamics/QueryableExtensionTests.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NUnit.Framework; +using Umbraco.Core.Dynamics; +using Umbraco.Web.Dynamics; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Dynamics +{ + //NOTE: there's libraries in both Umbraco.Core.Dynamics and Umbraco.Web.Dynamics - the reason for this is that the Web.Dynamics + // started with the razor macro implementation and is modified with hard coded references to dynamic node and dynamic null, though it seems + // to still work for other regular classes I don't want to move it to the core without removing these references but that would require a lot of work. + + [TestFixture] + public class QueryableExtensionTests + { + + [Test] + public void Order_By_Test_Int() + { + var items = new List + { + new TestModel {Age = 10, Name = "test1", Female = false}, + new TestModel {Age = 31, Name = "someguy", Female = true}, + new TestModel {Age = 11, Name = "test2", Female = true}, + new TestModel {Age = 20, Name = "anothertest", Female = false}, + new TestModel {Age = 55, Name = "blah", Female = false}, + new TestModel {Age = 12, Name = "test3", Female = false} + }; + + var result = items.AsQueryable().OrderBy("Age").ToArray(); + + Assert.AreEqual(10, result.ElementAt(0).Age); + Assert.AreEqual(11, result.ElementAt(1).Age); + Assert.AreEqual(12, result.ElementAt(2).Age); + Assert.AreEqual(20, result.ElementAt(3).Age); + Assert.AreEqual(31, result.ElementAt(4).Age); + Assert.AreEqual(55, result.ElementAt(5).Age); + + } + + [Test] + public void Order_By_Test_String() + { + var items = new List + { + new TestModel {Age = 10, Name = "test1", Female = false}, + new TestModel {Age = 31, Name = "someguy", Female = true}, + new TestModel {Age = 11, Name = "test2", Female = true}, + new TestModel {Age = 20, Name = "anothertest", Female = false}, + new TestModel {Age = 55, Name = "blah", Female = false}, + new TestModel {Age = 12, Name = "test3", Female = false} + }; + + var result = items.AsQueryable().OrderBy("Name").ToArray(); + + Assert.AreEqual("anothertest", result.ElementAt(0).Name); + Assert.AreEqual("blah", result.ElementAt(1).Name); + Assert.AreEqual("someguy", result.ElementAt(2).Name); + Assert.AreEqual("test1", result.ElementAt(3).Name); + Assert.AreEqual("test2", result.ElementAt(4).Name); + Assert.AreEqual("test3", result.ElementAt(5).Name); + + } + + [Test] + public void Where_Test_String() + { + var items = new List + { + new TestModel {Age = 10, Name = "test1", Female = false}, + new TestModel {Age = 31, Name = "someguy", Female = true}, + new TestModel {Age = 11, Name = "test2", Female = true}, + new TestModel {Age = 20, Name = "anothertest", Female = false}, + new TestModel {Age = 55, Name = "blah", Female = false}, + new TestModel {Age = 12, Name = "test3", Female = false} + }; + + var result = items.AsQueryable().Where("Name = \"test1\"").ToArray(); + + Assert.AreEqual(1, result.Count()); + Assert.AreEqual("test1", result.ElementAt(0).Name); + + + } + + [Test] + public void Where_Test_String_With_Params() + { + var items = new List + { + new TestModel {Age = 10, Name = "test1", Female = false}, + new TestModel {Age = 31, Name = "someguy", Female = true}, + new TestModel {Age = 11, Name = "test2", Female = true}, + new TestModel {Age = 20, Name = "anothertest", Female = false}, + new TestModel {Age = 55, Name = "blah", Female = false}, + new TestModel {Age = 12, Name = "test3", Female = false} + }; + + //NOTE: Currently the object query structure is not supported + //var result = items.AsQueryable().Where("Name = @name", new {name = "test1"}).ToArray(); + var result = items.AsQueryable().Where("Name = @Name", new Dictionary { { "Name", "test1" } }).ToArray(); + + Assert.AreEqual(1, result.Count()); + Assert.AreEqual("test1", result.ElementAt(0).Name); + + + } + + [Test] + public void Where_Test_Int_With_Params() + { + var items = new List + { + new TestModel {Age = 10, Name = "test1", Female = false}, + new TestModel {Age = 31, Name = "someguy", Female = true}, + new TestModel {Age = 11, Name = "test2", Female = true}, + new TestModel {Age = 20, Name = "anothertest", Female = false}, + new TestModel {Age = 55, Name = "blah", Female = false}, + new TestModel {Age = 12, Name = "test3", Female = false} + }; + + var result = items.AsQueryable().Where("Age = @Age", new Dictionary { { "Age", 10 } }).ToArray(); + + Assert.AreEqual(1, result.Count()); + Assert.AreEqual("test1", result.ElementAt(0).Name); + + + } + + [Test] + public void Where_Test_Bool_With_Params() + { + var items = new List + { + new TestModel {Age = 10, Name = "test1", Female = false}, + new TestModel {Age = 31, Name = "someguy", Female = true}, + new TestModel {Age = 11, Name = "test2", Female = true}, + new TestModel {Age = 20, Name = "anothertest", Female = false}, + new TestModel {Age = 55, Name = "blah", Female = false}, + new TestModel {Age = 12, Name = "test3", Female = false} + }; + + var result = items.AsQueryable().Where("Female = @Female", new Dictionary { { "Female", true } }).ToArray(); + + Assert.AreEqual(2, result.Count()); + + + } + + private class TestModel + { + public string Name { get; set; } + public int Age { get; set; } + public bool Female { get; set; } + } + + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 38f3f87c8c..20d9ed5113 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -237,6 +237,7 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 164699eb05..ed4a539cba 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -128,16 +128,29 @@ function entityResource($q, $http, umbRequestHelper) { * * * @param {string} type Object type name + * @param {string} postFilter optional filter expression which will execute a dynamic where clause on the server + * @param {string} postFilterParams optional parameters for the postFilter expression * @returns {Promise} resourcePromise object containing the entity. * */ - getAll: function (type) { + getAll: function (type, postFilter, postFilterParams) { + + //need to build the query string manually + var query = "type=" + type + "&postFilter=" + (postFilter ? postFilter : ""); + if (postFilter && postFilterParams) { + var counter = 0; + _.each(postFilterParams, function(val, key) { + query += "&postFilterParams[" + counter + "].key=" + key + "&postFilterParams[" + counter + "].value=" + val; + counter++; + }); + } + return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( "entityApiBaseUrl", "GetAll", - [{type: type }])), + query)), 'Failed to retreive entity data for type ' + type); }, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index 532fe06bb5..6a87ea8a06 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -216,6 +216,12 @@ function tinyMceService(dialogService, $log, imageHelper, assetsService, $timeou } + /** when the contents load we need to find any macros declared and load in their content */ + editor.on("LoadContent", function(o) { + var asdf = editor.getContent(); + alert(asdf); + }); + /** This prevents any other commands from executing when the current element is the macro so the content cannot be edited */ editor.on('BeforeExecCommand', function (o) { if (isOnMacroElement) { @@ -310,7 +316,10 @@ function tinyMceService(dialogService, $log, imageHelper, assetsService, $timeou /** The insert macro button click event handler */ onclick: function () { - var dialogData; + var dialogData = { + //flag for use in rte so we only show macros flagged for the editor + richTextEditor: true + }; //when we click we could have a macro already selected and in that case we'll want to edit the current parameters //so we'll need to extract them and submit them to the dialog. diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/insertmacro.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/insertmacro.controller.js index 437e5a2472..c2d77eb5c3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/insertmacro.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/insertmacro.controller.js @@ -104,8 +104,8 @@ function InsertMacroController($scope, entityResource, macroResource, umbPropEdi $scope.wizardStep = "paramSelect"; } - //get the macro list - entityResource.getAll("Macro") + //get the macro list - pass in a filter if it is only for rte + entityResource.getAll("Macro", ($scope.dialogData && $scope.dialogData.richTextEditor && $scope.dialogData.richTextEditor === true) ? "UseInEditor=true" : null) .then(function (data) { $scope.macros = data; diff --git a/src/Umbraco.Web/Dynamics/DynamicQueryable.cs b/src/Umbraco.Web/Dynamics/DynamicQueryable.cs index f4650af6d9..122fa0cd29 100644 --- a/src/Umbraco.Web/Dynamics/DynamicQueryable.cs +++ b/src/Umbraco.Web/Dynamics/DynamicQueryable.cs @@ -11,6 +11,11 @@ using Umbraco.Web.Models; namespace Umbraco.Web.Dynamics { + //TODO: Much of this can move to Umbraco.Core.Dynamics - but somehow need to remove all the hard coded references to things like + // dynamicnull, etc... + //NOTE: The OrderBy stuff here seems to be a bit hacked with hard references to umbraco node objects so don't think it can be + // re-used which is why we have the OrderBy stuff that hasn't been hacked in teh Umbraco.Core.Dynamics + internal static class DynamicQueryable { public static IQueryable Where(this IQueryable source, string predicate, params object[] values) diff --git a/src/Umbraco.Web/Dynamics/ExpressionParser.cs b/src/Umbraco.Web/Dynamics/ExpressionParser.cs index 1ab7b28716..d348682428 100644 --- a/src/Umbraco.Web/Dynamics/ExpressionParser.cs +++ b/src/Umbraco.Web/Dynamics/ExpressionParser.cs @@ -12,6 +12,8 @@ namespace Umbraco.Web.Dynamics { //SD: I wish all of this wasn't hacked and was just the original dynamic linq from MS... sigh. Just // means we can't really use it for anything other than dynamic node (i think) + // I'm fairly sure it's just hte convert to dynamic null stuff... still seems to work for normal linq operations would love to make it + // properly one day. internal class ExpressionParser { diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 05eabdfa86..772f5ecbf3 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -20,6 +20,7 @@ using Constants = Umbraco.Core.Constants; using Examine; using Examine.LuceneEngine.SearchCriteria; using Examine.SearchCriteria; +using Umbraco.Web.Dynamics; namespace Umbraco.Web.Editors { @@ -69,9 +70,9 @@ namespace Umbraco.Web.Editors return GetResultForAncestors(id, type); } - public IEnumerable GetAll(UmbracoEntityTypes type) + public IEnumerable GetAll(UmbracoEntityTypes type, string postFilter, [FromUri]IDictionary postFilterParams) { - return GetResultForAll(type); + return GetResultForAll(type, postFilter, postFilterParams); } private IEnumerable ExamineSearch(string query, bool isContent) @@ -147,20 +148,49 @@ namespace Umbraco.Web.Editors } } - private IEnumerable GetResultForAll(UmbracoEntityTypes entityType) + /// + /// Gets the result for the entity list based on the type + /// + /// + /// A string where filter that will filter the results dynamically with linq - optional + /// the parameters to fill in the string where filter - optional + /// + private IEnumerable GetResultForAll(UmbracoEntityTypes entityType, string postFilter = null, IDictionary postFilterParams = null) { var objectType = ConvertToObjectType(entityType); if (objectType.HasValue) { - return Services.EntityService.GetAll(objectType.Value).Select(Mapper.Map) - .WhereNotNull(); + //TODO: Should we order this by something ? + var entities = Services.EntityService.GetAll(objectType.Value).WhereNotNull().Select(Mapper.Map); + + //if a post filter is assigned then try to execute it + if (postFilter.IsNullOrWhiteSpace() == false) + { + return postFilterParams == null + ? entities.AsQueryable().Where(postFilter).ToArray() + : entities.AsQueryable().Where(postFilter, postFilterParams).ToArray(); + + } + return entities; } //now we need to convert the unknown ones switch (entityType) { case UmbracoEntityTypes.Macro: //Get all macros from the macro service - return Services.MacroService.GetAll().OrderBy(x => x.Name).Select(Mapper.Map); + var result = Services.MacroService.GetAll().WhereNotNull().OrderBy(x => x.Name).AsQueryable(); + + //if a post filter is assigned then try to execute it + if (postFilter.IsNullOrWhiteSpace() == false) + { + result = postFilterParams == null + ? result.Where(postFilter) + : result.Where(postFilter, postFilterParams); + + } + + return result.Select(Mapper.Map); + case UmbracoEntityTypes.Domain: case UmbracoEntityTypes.Language: diff --git a/src/Umbraco.Web/Editors/MacroController.cs b/src/Umbraco.Web/Editors/MacroController.cs index a3fee00c75..aac9798d23 100644 --- a/src/Umbraco.Web/Editors/MacroController.cs +++ b/src/Umbraco.Web/Editors/MacroController.cs @@ -23,6 +23,8 @@ namespace Umbraco.Web.Editors [PluginController("UmbracoApi")] public class MacroController : UmbracoAuthorizedJsonController { + + /// /// Gets the macro parameters to be filled in for a particular macro ///