-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
V8 - Add Media Tracking of items added via Macro Parameters in RTE + Grid - issue #8131 #8614
Closed
marcemarc
wants to merge
9
commits into
umbraco:v8/contrib
from
marcemarc:v8/feature/media-tracking-macro-parameters
Closed
Changes from 4 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
4eb54e4
Add Parser to find Udi's in Macro Parameters in Macros in RTE + Grid
marcemarc bf5b356
Register HtmlMacroParameterParser with DI
marcemarc 5a0bc3d
Some Tests to cover the parsing of text/json for Macros and their par…
marcemarc 3b38bc6
Inject the HtmlMacroParameterParser into GridPropertyEditor + RichTex…
marcemarc b7c937b
Add GetAll implementation to MacroService for multiple Macro Aliases
marcemarc ed0180f
Remove previous tests based on scraping the embedded parameter udis
marcemarc e398926
Update Macro Parameter Parser, to use the Macro Configuration, to fin…
marcemarc 7ab1028
Update Grid + RichTextEditor Property Editors to use the MacroParamet…
marcemarc 27007ce
Update MultiMediaPicker Parameter editor to have it's own ValueEditor…
marcemarc File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
154 changes: 154 additions & 0 deletions
154
src/Umbraco.Tests/Templates/HtmlMacroParameterParserTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
using Umbraco.Core.Logging; | ||
using Moq; | ||
using NUnit.Framework; | ||
using Umbraco.Tests.Testing.Objects.Accessors; | ||
using Umbraco.Web.Templates; | ||
using Umbraco.Web; | ||
using Umbraco.Core.Models.PublishedContent; | ||
using Umbraco.Web.Routing; | ||
using Umbraco.Tests.Testing.Objects; | ||
using System.Web; | ||
using System; | ||
using System.Linq; | ||
using Umbraco.Core.Models; | ||
using Umbraco.Core; | ||
using System.Diagnostics; | ||
using Newtonsoft.Json.Linq; | ||
using System.Collections.Generic; | ||
|
||
namespace Umbraco.Tests.Templates | ||
{ | ||
[TestFixture] | ||
public class HtmlMacroParameterParserTests | ||
{ | ||
[Test] | ||
public void Returns_Udis_From_Single_MediaPicker_Macro_Parameters_In_Macros_In_Html() | ||
{ | ||
//Two macros, one single parameter, single Media Picker, one multiple paramters of single media picker | ||
var input = @"<p>This is some normal text before the macro</p> | ||
<?UMBRACO_MACRO macroAlias=""ThreeSingleMediaPickers"" singleMediaPicker1=""umb://media/eee91c05b2e84031a056dcd7f28eff89"" singleMediaPicker2=""umb://media/fa763e0d0ceb408c8720365d57e06e32"" singleMediaPicker3="""" /> | ||
<p>This is a paragraph after the macro and before the next</p> | ||
<?UMBRACO_MACRO macroAlias=""SingleMediaPicker"" singleMediaPicker=""umb://media/90ba0d3dba6e4c9fa1953db78352ba73"" /> | ||
<p>some more text</p>"; | ||
|
||
var macroParameterParser = new HtmlMacroParameterParser(); | ||
|
||
var result = macroParameterParser.FindUdisFromMacroParameters(input).ToList(); | ||
Assert.AreEqual(3, result.Count); | ||
Assert.AreEqual(Udi.Parse("umb://media/eee91c05b2e84031a056dcd7f28eff89"), result[0]); | ||
Assert.AreEqual(Udi.Parse("umb://media/fa763e0d0ceb408c8720365d57e06e32"), result[1]); | ||
Assert.AreEqual(Udi.Parse("umb://media/90ba0d3dba6e4c9fa1953db78352ba73"), result[2]); | ||
} | ||
[Test] | ||
public void Returns_Empty_With_From_Single_MediaPicker_With_No_Macro_Parameters_In_Macros_In_Html() | ||
{ | ||
//Two macros, one single parameter, single Media Picker, one multiple paramters of single media picker | ||
var input = @"<p>This is some normal text before the macro</p> | ||
<?UMBRACO_MACRO macroAlias=""ThreeSingleMediaPickers"" /> | ||
<p>This is a paragraph after the macro and before the next</p> | ||
<?UMBRACO_MACRO macroAlias=""AnotherParameter"" singleMediaPicker=""<p>Some other value</p>"" /> | ||
<p>some more text</p>"; | ||
|
||
var macroParameterParser = new HtmlMacroParameterParser(); | ||
|
||
var result = macroParameterParser.FindUdisFromMacroParameters(input).ToList(); | ||
Assert.AreEqual(0, result.Count); | ||
} | ||
//NB: When multiple media pickers store udis instead of ints! - see https://github.com/umbraco/Umbraco-CMS/pull/8388 | ||
[Test] | ||
public void Returns_Empty_When_No_Macros_In_Html() | ||
{ | ||
//Two macros, one single parameter, single Media Picker, one multiple paramters of single media picker | ||
var input = @"<p>This is some normal text before the macro</p> | ||
<p>This is a paragraph after the macro and before the next</p> | ||
<p>some more text</p>"; | ||
|
||
var macroParameterParser = new HtmlMacroParameterParser(); | ||
|
||
var result = macroParameterParser.FindUdisFromMacroParameters(input).ToList(); | ||
Assert.AreEqual(0, result.Count); | ||
} | ||
[Test] | ||
public void Returns_Udis_From_Multiple_MediaPicker_Macro_Parameters_In_Macros_In_Html() | ||
{ | ||
//Two macros, one single parameter, single Media Picker, one multiple paramters of single media picker | ||
var input = @"<p>This is some normal text before the macro</p> | ||
<?UMBRACO_MACRO macroAlias=""MultipleMediaPickers"" multipleMediaPicker1=""umb://media/eee91c05b2e84031a056dcd7f28eff89,umb://media/fa763e0d0ceb408c8720365d57e06e32,umb://media/bb763e0d0ceb408c8720365d57e06444"" /> | ||
<p>This is a paragraph after the macro</p>"; | ||
|
||
var macroParameterParser = new HtmlMacroParameterParser(); | ||
|
||
var result = macroParameterParser.FindUdisFromMacroParameters(input).ToList(); | ||
Assert.AreEqual(3, result.Count); | ||
Assert.AreEqual(Udi.Parse("umb://media/eee91c05b2e84031a056dcd7f28eff89"), result[0]); | ||
Assert.AreEqual(Udi.Parse("umb://media/fa763e0d0ceb408c8720365d57e06e32"), result[1]); | ||
Assert.AreEqual(Udi.Parse("umb://media/bb763e0d0ceb408c8720365d57e06444"), result[2]); | ||
} | ||
[Test] | ||
public void Returns_Udis_From_Single_MediaPicker_Macro_Parameters_In_Grid_Macros() | ||
{ | ||
// create a list of GridValue.GridControls with Editor GridEditor alias macro | ||
List<GridValue.GridControl> gridControls = new List<GridValue.GridControl>(); | ||
|
||
// single media picker macro parameter | ||
var macroGridControl = GetMacroGridControl(@"{ ""macroAlias"": ""SingleMediaPicker"", ""macroParamsDictionary"": { ""singleMediaPicker"": ""umb://media/90ba0d3dba6e4c9fa1953db78352ba73"" }}"); | ||
gridControls.Add(macroGridControl); | ||
|
||
var macroParameterParser = new HtmlMacroParameterParser(); | ||
|
||
var result = macroParameterParser.FindUdisFromGridControlMacroParameters(gridControls).ToList(); | ||
Assert.AreEqual(1, result.Count); | ||
Assert.AreEqual(Udi.Parse("umb://media/90ba0d3dba6e4c9fa1953db78352ba73"), result[0]); | ||
|
||
} | ||
[Test] | ||
public void Returns_Empty_From_Single_MediaPicker_With_No_Macro_Parameters_In_Grid_Macros() | ||
{ | ||
// create a list of GridValue.GridControls with Editor GridEditor alias macro | ||
List<GridValue.GridControl> gridControls = new List<GridValue.GridControl>(); | ||
|
||
// single media picker macro parameter | ||
var macroGridControl = GetMacroGridControl(@"{ ""macroAlias"": ""SingleMediaPicker"", ""macroParamsDictionary"": {}}"); | ||
gridControls.Add(macroGridControl); | ||
|
||
var macroParameterParser = new HtmlMacroParameterParser(); | ||
|
||
var result = macroParameterParser.FindUdisFromGridControlMacroParameters(gridControls).ToList(); | ||
Assert.AreEqual(0, result.Count); | ||
|
||
} | ||
//NB: When multiple media pickers store udis instead of ints! - see https://github.com/umbraco/Umbraco-CMS/pull/8388 | ||
[Test] | ||
public void Returns_Udis_From_Multiple_MediaPicker_Macro_Parameters_In_Grid_Macros() | ||
{ | ||
// create a list of GridValue.GridControls with Editor GridEditor alias macro | ||
List<GridValue.GridControl> gridControls = new List<GridValue.GridControl>(); | ||
|
||
// multiple media picker macro parameter | ||
var macroGridControl = GetMacroGridControl(@"{ ""macroAlias"": ""SingleMediaPicker"", ""macroParamsDictionary"": { ""multipleMediaPicker"": ""umb://media/eee91c05b2e84031a056dcd7f28eff89,umb://media/fa763e0d0ceb408c8720365d57e06e32,umb://media/bb763e0d0ceb408c8720365d57e06444"" }}"); | ||
gridControls.Add(macroGridControl); | ||
|
||
var macroParameterParser = new HtmlMacroParameterParser(); | ||
|
||
var result = macroParameterParser.FindUdisFromGridControlMacroParameters(gridControls).ToList(); | ||
Assert.AreEqual(3, result.Count); | ||
Assert.AreEqual(Udi.Parse("umb://media/eee91c05b2e84031a056dcd7f28eff89"), result[0]); | ||
Assert.AreEqual(Udi.Parse("umb://media/fa763e0d0ceb408c8720365d57e06e32"), result[1]); | ||
Assert.AreEqual(Udi.Parse("umb://media/bb763e0d0ceb408c8720365d57e06444"), result[2]); | ||
|
||
} | ||
|
||
//setup a Macro Grid Control based on Json of the Macro | ||
private GridValue.GridControl GetMacroGridControl(string macroJson) | ||
{ | ||
var macroGridEditor = new GridValue.GridEditor(); | ||
macroGridEditor.Alias = "macro"; | ||
macroGridEditor.View = "macro"; | ||
var macroGridControl = new GridValue.GridControl(); | ||
macroGridControl.Editor = macroGridEditor; | ||
macroGridControl.Value = JToken.Parse(macroJson); | ||
return macroGridControl; | ||
} | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes but eventually we want to track all sorts of things... basically anything that can have a UDI reference. Could be content -> content, etc... Macros do have a property editor, they are the same. They are just property editors flagged with also being a macro parameter editor. This is exposed by
IDataEditor.EditorType
enum. But you can 'just' resolve all macro parameter editors from the collectionParameterEditorCollection
. Don't think that would be expensive get the editor based on the editor alias ... if you have it. IIRC that is stored as part of the embedded macro parameters?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I'm trying to say is that the Grid, has a property editor - the class is called GridPropertyEditor, it is responsible for the references to udis in the Grid - The Rich Text Editor has a property editor - the class is called RichTextPropertyEditor - but the Macro isn't a 'property editor' in the 'same way' it doesn't have a MacroPropertyEditor class (or maybe I can't find it! :-)) so it doesn't have it's own GetReferences implementation (which following the pattern would 'call' all the 'GetReferences' for each of the Macro Parameters, which of course are tied to their own PropertyEditor implementations...
Maybe we should have a 'Macro Picker' property editor that people can use for Doc Type properties? (like the old Macro Container)
So what I'm trying to say, is a Macro is a bit different, as it isn't an editor in its own right - and I'm not sure how to make it follow the same pattern of responsibility (a property editor is responsible for finding the references!) - which is why I created the HtmlMacroParameterParser to take on this task...
Having created that, and with the warnings about performance:
it occurred to me that pragmatically having the values in udi format means you 'know' what type they are - even if you want to add content item tracking at a later date - it will have a udi//document format ... you will know it's type without needing to know how it was picked... (the argument against this approach is what about complex custom macro property editors that store Json... but ignore that for now)
... but I'm aware this doesn't follow the pattern and the notion of property editor responsibility (which is good, but I don't think Macros follow this pattern which is the problem I'm encountering or misunderstanding).
The embedded Macro markup looks like this:
<?UMBRACO_MACRO macroAlias=""InsertImageCarousel"" carouselImages=""umb://media/eee91c05b2e84031a056dcd7f28eff89,umb://media/fa763e0d0ceb408c8720365d57e06e32,umb://media/bb763e0d0ceb408c8720365d57e06444"" />
and in the case of the grid:
{ ""macroAlias"": ""InsertImageCarousel"", ""macroParamsDictionary"": { ""carouselImages"": ""umb://media/eee91c05b2e84031a056dcd7f28eff89,umb://media/fa763e0d0ceb408c8720365d57e06e32,umb://media/bb763e0d0ceb408c8720365d57e06444"" }}
So you DO not have the alias of the property editor stored as part of the embedded macro parameters - just the alias of the Macro, and the name given by the developer to the parameter...
... so 'including' the property editor alias in the embedded markup would be another way of making this much easier!! :-)
but in the absence of this, my thinking here is we'd need to lookup the configured Macro, by Macro Alias, read the parameter configuration, establish the editor type for each parameter, get the editor from the ParameterEditorCollection, call it's GetReferences method - passing in the value of that parameter from the embedded macro...
... I don't think the ParameterEditorCollection look up is the expensive bit... it's the querying to get the Macro configuration? which will happen for every macro - even when it doesn't have a media picker, just to find out, it doesn't have a media picker - unless there is a better way?
whereas the pragmatic thing here is we know from the Macro Parameter Value what 'type' of udi it is, and it is all we need to add the tracking! Macros are special!
But can rework the PR to try and lookup the macro, establish parameter editor type, and call getreferences if you confirm that is 'just' the preferred approach for the longer term, but if there is a better way to do the lookup?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the gist of what it would be looking up the macro each time:
it's certainly neater, but does that seem ok, with using the MacroService?
I can update along these lines if it make sense (and I can work out how to inject the ParameterEditorCollection properly).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes the grid is it's own beast as well since 'grid editors' aren't property editors either. I think there's probably another issue with the grid, references and macros too since the GridPropertyEditor.GetReferences is not taking into account the macro picker grid editor either.
I do much prefer your 2nd approach above using the macro service. We are most likely going to have to do this in a few places moving forward like for the grid + rte, the RTE itself (there's already a TODO there:
//TODO: Detect Macros too ... but we can save that for a later date, right now need to do media refs
) , nested content + rte, block editor + rte (perhaps the block editor has a macro picker too, can't remember) so doing this the right way and having a central bit of code to do this all will be good.Regarding performance, yes that will cause quite a few queries to execute since we are doing N+1 all over the place. But we can deal with this, there's a couple options and perhaps they are both things we might want to do:
IEnumerable<IMacro> GetAll(params string[] aliases)
which will query for all macros by those aliases. This wouldn't require any changes to the macro repository because we can construct a query to pass to the repo for that. Then instead of doing N+1 queries, we can just get all macros by all aliases at once. This would greatly reduce the queries but still means one query to this new macro service per RTE and macro editor. I think that would still be ok though.1 ) above is pretty easy. I'd start with that. 2 ) is probably good to do anyways because we use the
GetByAlias
call in quite a few places in the core and we already have hacked in a cache for it inside ofMacroRenderer.Render
and it's definitely less than ideal to have this hacked cache in there when it should definitely be part of the macro repository.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great, thanks @Shazwazza for the direction, I'll rework the PR to take this approach! I was just wary of the n+1, but as you say, there are ways around reducing the impact of that - and this is much neater, the responsibility of finding the Udis is with the property editor implementation which feels right.
The PR also handled the Grid, and the Macro Grid Controls and their parameters, which were parsed in the same 'pragmatic' way, this can remain but instead call this new common method to do the entity reference finding.
Let's see how far I get!