Skip to content
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

Add ability for the main Umbraco search to include media file names #6579

Merged
merged 12 commits into from Nov 14, 2019
Merged
37 changes: 34 additions & 3 deletions src/Umbraco.Examine/MediaValueSetBuilder.cs
@@ -1,9 +1,12 @@
using Examine;
using System;
using Examine;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.PropertyEditors.ValueConverters;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;

Expand All @@ -13,14 +16,16 @@ public class MediaValueSetBuilder : BaseValueSetBuilder<IMedia>
{
private readonly UrlSegmentProviderCollection _urlSegmentProviders;
private readonly IUserService _userService;
private readonly IRuntimeState _runtimeState;

public MediaValueSetBuilder(PropertyEditorCollection propertyEditors,
UrlSegmentProviderCollection urlSegmentProviders,
IUserService userService)
IUserService userService, IRuntimeState runtimeState)
: base(propertyEditors, false)
{
_urlSegmentProviders = urlSegmentProviders;
_userService = userService;
_runtimeState = runtimeState;
}

/// <inheritdoc />
Expand All @@ -29,6 +34,31 @@ public override IEnumerable<ValueSet> GetValueSets(params IMedia[] media)
foreach (var m in media)
{
var urlValue = m.GetUrlSegment(_urlSegmentProviders);

var umbracoFilePath = string.Empty;
var umbracoFile = string.Empty;

var umbracoFileSource = m.GetValue<string>(Constants.Conventions.Media.File);

if (umbracoFileSource.DetectIsJson())
{
var cropper = JsonConvert.DeserializeObject<ImageCropperValue>(m.GetValue<string>(Constants.Conventions.Media.File));
Shazwazza marked this conversation as resolved.
Show resolved Hide resolved
if (cropper != null)
{
umbracoFilePath = cropper.Src;
}
}
else
{
umbracoFilePath = umbracoFileSource;
}

if (!string.IsNullOrEmpty(umbracoFilePath))
{
var uri = new Uri(_runtimeState.ApplicationUrl.GetLeftPart(UriPartial.Authority) + umbracoFilePath);
Jeavon marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to use the application URL here? i mean, all this is really doing is constructing a Uri just so that it parses, you could just as easily have a static URI with a dummy hostname, etc... so you aren't allocating a new object for this each time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could have a dummy URI but that felt somehow wrong (I could also split the string), but can do that, what would you suggest for a dummy URI (is there a precedent)?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a dummy URL would be better and then we don't need to inject the IRuntimeState, at the end of the day it won't make any difference and we don't need to change as much

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Jeavon this will be the last thing we need to cleanup i think and then we can merge

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Shazwazza updated, thanks!

umbracoFile = uri.Segments.Last();
}

var values = new Dictionary<string, IEnumerable<object>>
{
{"icon", m.ContentType.Icon?.Yield() ?? Enumerable.Empty<string>()},
Expand All @@ -44,7 +74,8 @@ public override IEnumerable<ValueSet> GetValueSets(params IMedia[] media)
{"urlName", urlValue?.Yield() ?? Enumerable.Empty<string>()},
{"path", m.Path?.Yield() ?? Enumerable.Empty<string>()},
{"nodeType", m.ContentType.Id.ToString().Yield() },
{"creatorName", (m.GetCreatorProfile(_userService)?.Name ?? "??").Yield()}
{"creatorName", (m.GetCreatorProfile(_userService)?.Name ?? "??").Yield()},
{UmbracoExamineIndex.UmbracoFileFieldName, umbracoFile.Yield()}
};

foreach (var property in m.Properties)
Expand Down
1 change: 1 addition & 0 deletions src/Umbraco.Examine/UmbracoExamineIndex.cs
Expand Up @@ -32,6 +32,7 @@ public abstract class UmbracoExamineIndex : LuceneIndex, IUmbracoIndex, IIndexDi
/// </summary>
public const string IndexPathFieldName = SpecialFieldPrefix + "Path";
public const string NodeKeyFieldName = SpecialFieldPrefix + "Key";
public const string UmbracoFileFieldName = "umbracoFileSrc";
public const string IconFieldName = SpecialFieldPrefix + "Icon";
public const string PublishedFieldName = SpecialFieldPrefix + "Published";

Expand Down
13 changes: 12 additions & 1 deletion src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs
Expand Up @@ -7,6 +7,7 @@
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Store;
using Moq;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
Expand Down Expand Up @@ -44,11 +45,21 @@ public static ContentIndexPopulator GetContentIndexRebuilder(PropertyEditorColle

public static MediaIndexPopulator GetMediaIndexRebuilder(PropertyEditorCollection propertyEditors, IMediaService mediaService)
{
var mediaValueSetBuilder = new MediaValueSetBuilder(propertyEditors, new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }), GetMockUserService());
var mediaValueSetBuilder = new MediaValueSetBuilder(propertyEditors, new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }), GetMockUserService(), MockRuntimeState(RuntimeLevel.Run));
var mediaIndexDataSource = new MediaIndexPopulator(null, mediaService, mediaValueSetBuilder);
return mediaIndexDataSource;
}

public static IRuntimeState MockRuntimeState(RuntimeLevel level)
{
var runtimeState = Mock.Of<IRuntimeState>();
Mock.Get(runtimeState).Setup(x => x.Level).Returns(level);
Mock.Get(runtimeState).SetupGet(m => m.ApplicationUrl).Returns(new Uri("https://localhost/umbraco"));

return runtimeState;
}


public static IContentService GetMockContentService()
{
long longTotalRecs;
Expand Down
9 changes: 9 additions & 0 deletions src/Umbraco.Web/Models/Mapping/EntityMapDefinition.cs
Expand Up @@ -177,6 +177,15 @@ private static void Map(ISearchResult source, SearchResultEntity target, MapperC

target.Name = source.Values.ContainsKey("nodeName") ? source.Values["nodeName"] : "[no name]";

if (source.Values.ContainsKey(UmbracoExamineIndex.UmbracoFileFieldName))
Shazwazza marked this conversation as resolved.
Show resolved Hide resolved
{
var umbracoFile = source.Values[UmbracoExamineIndex.UmbracoFileFieldName];
if (umbracoFile != null)
{
target.Name = $"{target.Name} ({umbracoFile})";
}
}

if (source.Values.ContainsKey(UmbracoExamineIndex.NodeKeyFieldName))
{
if (Guid.TryParse(source.Values[UmbracoExamineIndex.NodeKeyFieldName], out var key))
Expand Down
24 changes: 20 additions & 4 deletions src/Umbraco.Web/Search/UmbracoTreeSearcher.cs
Expand Up @@ -67,7 +67,7 @@ public class UmbracoTreeSearcher

string type;
var indexName = Constants.UmbracoIndexes.InternalIndexName;
var fields = new[] { "id", "__NodeId", "__Key" };
var fields = new List<string> { "id", "__NodeId", "__Key" };

// TODO: WE should try to allow passing in a lucene raw query, however we will still need to do some manual string
// manipulation for things like start paths, member types, etc...
Expand All @@ -87,7 +87,7 @@ public class UmbracoTreeSearcher
case UmbracoEntityTypes.Member:
indexName = Constants.UmbracoIndexes.MembersIndexName;
type = "member";
fields = new[] { "id", "__NodeId", "__Key", "email", "loginName" };
fields.AddRange(new[]{ "email", "loginName"});
if (searchFrom != null && searchFrom != Constants.Conventions.MemberTypes.AllMembersListId && searchFrom.Trim() != "-1")
{
sb.Append("+__NodeTypeAlias:");
Expand All @@ -97,6 +97,7 @@ public class UmbracoTreeSearcher
break;
case UmbracoEntityTypes.Media:
type = "media";
fields.AddRange(new[] { UmbracoExamineIndex.UmbracoFileFieldName });
var allMediaStartNodes = _umbracoContext.Security.CurrentUser.CalculateMediaStartNodeIds(_entityService);
AppendPath(sb, UmbracoObjectTypes.Media, allMediaStartNodes, searchFrom, ignoreUserStartNodes, _entityService);
break;
Expand Down Expand Up @@ -161,7 +162,7 @@ public IEnumerable<SearchResultEntity> EntitySearch(UmbracoObjectTypes objectTyp
return _mapper.MapEnumerable<IEntitySlim, SearchResultEntity>(results);
}

private bool BuildQuery(StringBuilder sb, string query, string searchFrom, string[] fields, string type)
private bool BuildQuery(StringBuilder sb, string query, string searchFrom, List<string> fields, string type)
{
//build a lucene query:
// the nodeName will be boosted 10x without wildcards
Expand Down Expand Up @@ -234,11 +235,26 @@ private bool BuildQuery(StringBuilder sb, string query, string searchFrom, strin

foreach (var f in fields)
{
var queryWordsReplaced = new string[querywords.Length];

// when searching file names containing hyphens we need to replace the hyphens with spaces
Shazwazza marked this conversation as resolved.
Show resolved Hide resolved
if (f.Equals(UmbracoExamineIndex.UmbracoFileFieldName))
{
for (var index = 0; index < querywords.Length; index++)
{
queryWordsReplaced[index] = querywords[index].Replace("\\-", " ").Replace("_", " ").Trim(" ");
}
}
else
{
queryWordsReplaced = querywords;
}

//additional fields normally
sb.Append(f);
sb.Append(":");
sb.Append("(");
foreach (var w in querywords)
foreach (var w in queryWordsReplaced)
{
sb.Append(w.ToLower());
sb.Append("* ");
Expand Down