public void FormatResultSet_generates_special_output_for_tracked_searches() { // Arrange var item = new SearchResultItem(1, SearchResultType.Page, "page", 1, DateTime.Parse("2010/10/10").ToSafeUniversalTime()); var detail = new SearchResultDetail(Pairs( "id.page", 1, "path","path", "uri","http://uri", "title", "foo", "author", "bob", "preview", "preview" )); var set = new SearchResult("parsed", new[] { item }); var discriminator = new SetDiscriminator() { Ascending = true, Limit = 100, Offset = 0, SortField = "rank" }; var explain = false; TrackingInfo trackingInfo = new TrackingInfo() { QueryId = 123, PreviousQueryId = 456 }; _cache.Setup(x => x.TryGet(item.DetailKey, out detail)).Returns(true).AtMostOnce().Verifiable(); // Act var xml = Search.FormatResultSet(set, discriminator, explain, trackingInfo, new Result<XDoc>()).Wait(); // Assert _cache.VerifyAll(); var expectedXml = XDocFactory.From(@" <search querycount=""1"" ranking=""adaptive"" queryid=""123"" count=""1""> <parsedQuery>parsed</parsedQuery> <result> <id>1</id> <uri>http://uri</uri> <uri.track>mock://api/site/query/123?pageid=1&rank=1&type=page&position=1</uri.track> <rank>1</rank> <title>foo</title> <page> <title>foo</title> <path>path</path> </page> <author>bob</author> <date.modified>2010-10-10T00:00:00Z</date.modified> <content>preview</content> <type>page</type> </result> </search>", MimeType.TEXT_XML); Assert.AreEqual(expectedXml.ToCompactString(), xml.ToCompactString()); }
public Result<XDoc> FormatResultSet(SearchResult searchResultSet, SetDiscriminator discriminator, bool explain, TrackingInfo trackingInfo, Result<XDoc> result) { return _instance.FormatResultSet(searchResultSet, discriminator, explain, trackingInfo, result); }
public void FormatResults_hits_lucene_for_detail_records_on_cache_miss() { // Arrange var items = new[] { new SearchResultItem(1,SearchResultType.Page,"page",1,DateTime.UtcNow), new SearchResultItem(2,SearchResultType.File,"file",3,DateTime.UtcNow), new SearchResultItem(3,SearchResultType.Comment,"comment",2,DateTime.UtcNow), new SearchResultItem(4,SearchResultType.User,"user",4,DateTime.UtcNow), }; var set = new SearchResult("parsed", items); var discriminator = new SetDiscriminator() { Ascending = true, Limit = 100, Offset = 0, SortField = "rank" }; var explain = false; TrackingInfo trackingInfo = null; MockPlug.Setup(_searchPlug) .Verb("GET") .With("wikiid", "default") .With("q", v => v.Contains("page:(1")) .With("max", "all") .Returns(new XDoc("documents").Start("document").Elem("id.page", 1)) .ExpectCalls(DreamTimes.Once()); MockPlug.Setup(_searchPlug) .Verb("GET") .With("wikiid", "default") .With("q", v => v.Contains("file:(2")) .With("max", "all") .Returns(new XDoc("documents").Start("document").Elem("id.file", 2)) .ExpectCalls(DreamTimes.Once()); MockPlug.Setup(_searchPlug) .Verb("GET") .With("wikiid", "default") .With("q", v => v.Contains("comment:(3")) .With("max", "all") .Returns(new XDoc("documents").Start("document").Elem("id.comment", 3)) .ExpectCalls(DreamTimes.Once()); MockPlug.Setup(_searchPlug) .Verb("GET") .With("wikiid", "default") .With("q", v => v.Contains("user:(4")) .With("max", "all") .Returns(new XDoc("documents").Start("document").Elem("id.user", 4)) .ExpectCalls(DreamTimes.Once()); SearchResultDetail detail = null; _cache.Setup(x => x.TryGet(It.IsAny<string>(), out detail)).Returns(false).AtMost(4).Verifiable(); _cache.Setup(x => x.Set(items[0].DetailKey, It.Is<SearchResultDetail>(d => d["id.page"] == "1"), It.IsAny<TimeSpan>())) .AtMostOnce().Verifiable(); _cache.Setup(x => x.Set(items[1].DetailKey, It.Is<SearchResultDetail>(d => d["id.file"] == "2"), It.IsAny<TimeSpan>())) .AtMostOnce().Verifiable(); _cache.Setup(x => x.Set(items[2].DetailKey, It.Is<SearchResultDetail>(d => d["id.comment"] == "3"), It.IsAny<TimeSpan>())) .AtMostOnce().Verifiable(); _cache.Setup(x => x.Set(items[3].DetailKey, It.Is<SearchResultDetail>(d => d["id.user"] == "4"), It.IsAny<TimeSpan>())) .AtMostOnce().Verifiable(); // Act var xml = Search.FormatResultSet(set, discriminator, explain, trackingInfo, new Result<XDoc>()).Wait(); // Assert _cache.VerifyAll(); MockPlug.VerifyAll(); var expectedXml = XDocFactory.From(@" <search querycount=""4"" ranking=""adaptive"" count=""4""> <parsedQuery>parsed</parsedQuery> <document> <score>1</score> <id.page>1</id.page> </document> <document> <score>2</score> <id.comment>3</id.comment> </document> <document> <score>3</score> <id.file>2</id.file> </document> <document> <score>4</score> <id.user>4</id.user> </document> </search>", MimeType.TEXT_XML); Assert.AreEqual(expectedXml.ToCompactString(), xml.ToCompactString()); }
public void FormatResults_hits_cache_before_lucene_for_detail() { // Arrange var items = new[] { new SearchResultItem(1,SearchResultType.Page,"page",1,DateTime.UtcNow), new SearchResultItem(2,SearchResultType.File,"page",3,DateTime.UtcNow), new SearchResultItem(3,SearchResultType.Comment,"page",2,DateTime.UtcNow), new SearchResultItem(4,SearchResultType.User,"page",4,DateTime.UtcNow), }; var details = new[] { new SearchResultDetail(Pairs("id", items[0].TypeId)), new SearchResultDetail(Pairs("id", items[1].TypeId)), new SearchResultDetail(Pairs("id", items[2].TypeId)), new SearchResultDetail(Pairs("id", items[3].TypeId)), }; var set = new SearchResult("parsed", items); var discriminator = new SetDiscriminator() { Ascending = true, Limit = 100, Offset = 0, SortField = "rank" }; var explain = false; TrackingInfo trackingInfo = null; _cache.Setup(x => x.TryGet(items[0].DetailKey, out details[0])).Returns(true).AtMostOnce().Verifiable(); _cache.Setup(x => x.TryGet(items[1].DetailKey, out details[1])).Returns(true).AtMostOnce().Verifiable(); _cache.Setup(x => x.TryGet(items[2].DetailKey, out details[2])).Returns(true).AtMostOnce().Verifiable(); _cache.Setup(x => x.TryGet(items[3].DetailKey, out details[3])).Returns(true).AtMostOnce().Verifiable(); // Act var xml = Search.FormatResultSet(set, discriminator, explain, trackingInfo, new Result<XDoc>()).Wait(); // Assert _cache.VerifyAll(); var expectedXml = XDocFactory.From(@" <search querycount=""4"" ranking=""adaptive"" count=""4""> <parsedQuery>parsed</parsedQuery> <document> <score>1</score> <id>1</id> </document> <document> <score>2</score> <id>3</id> </document> <document> <score>3</score> <id>2</id> </document> <document> <score>4</score> <id>4</id> </document> </search>", MimeType.TEXT_XML); Assert.AreEqual(expectedXml.ToCompactString(), xml.ToCompactString()); }
public Result <XDoc> FormatResultSet(SearchResult searchResultSet, SetDiscriminator discriminator, bool explain, TrackingInfo trackingInfo, Result <XDoc> result) { return(_instance.FormatResultSet(searchResultSet, discriminator, explain, trackingInfo, result)); }
private Yield FormatResultSet_Helper(SearchResult searchResultSet, SetDiscriminator discriminator, bool explain, TrackingInfo trackingInfo, Result <XDoc> result) { _log.Debug("formatting result set"); var searchDoc = new XDoc("search") .Attr("querycount", searchResultSet.Count) .Attr("ranking", IsAdaptiveSearchEnabled ? "adaptive" : "simple") .Elem("parsedQuery", searchResultSet.ExecutedQuery); ulong queryId = 0; if (trackingInfo != null) { queryId = trackingInfo.QueryId.Value; searchDoc.Attr("queryid", queryId); // TODO (arnec): Keep or remove this? It does expose admin visibility data to non-admins if (explain) { searchDoc.Start("settings").Start("search") .Elem("rating-promote-boost", _settings.GetValue("search/rating-promote-boost", RATING_PROMOTE_BOOST)) .Elem("rating-demote-boost", _settings.GetValue("search/rating-demote-boost", RATING_DEMOTE_BOOST)) .Elem("rating-count-threshold", _settings.GetValue("search/rating-count-threshold", RATING_COUNT_THRESHOLD)) .Elem("rating-rank-midpoint", _settings.GetValue("search/rating-rank-midpoint", RATING_RANK_MIDPOINT)) .Elem("search-popularity-boost", _settings.GetValue("search/search-popularity-boost", SEARCH_POPULARITY_BOOST)) .Elem("search-popularity-threshold", _settings.GetValue("search/search-popularity-threshold", SEARCH_POPULARITY_THRESHOLD)) .End().End(); } } var query = searchResultSet as IEnumerable <SearchResultItem>; switch (discriminator.SortField) { case "title": query = OrderBy(query, item => item.Title, discriminator.Ascending); break; case "modified": query = OrderBy(query, item => item.Modified, discriminator.Ascending); break; default: query = OrderBy(query, item => item.Rank, discriminator.Ascending); break; } if (discriminator.Offset > 0) { query = query.Skip(discriminator.Offset.ToInt()); } if (discriminator.Limit > 0 && discriminator.Limit != uint.MaxValue) { query = query.Take(discriminator.Limit.ToInt()); } var items = query.ToList(); yield return(Coroutine.Invoke(PopulateDetail, items, new Result())); // position starts at 1 not 0, since 0 is the value used when position isn't tracked var position = discriminator.Offset + 1; var count = 0; foreach (var item in query) { if (item.Detail == null) { continue; } try { if (trackingInfo != null) { var detail = item.Detail; // Note (arnec): this assumes that any item in a tracked result has an id.page var trackUri = _apiUri.At("site", "query", queryId.ToString()) .With("pageid", item.Detail["id.page"] ?? "0") .With("rank", item.Rank.ToString()) .With("type", item.Type.ToString().ToLower()); if (discriminator.SortField.EqualsInvariantIgnoreCase("rank")) { trackUri = trackUri.With("position", position.ToString()); } var uri = new XUri(detail["uri"]); var path = detail["path"]; var title = detail["title"]; var pageTitle = title; try { switch (item.Type) { case SearchResultType.User: continue; case SearchResultType.File: trackUri = trackUri.With("typeid", item.TypeId.ToString()); uri = _apiUri.At("files", item.TypeId.ToString(), Title.AsApiParam(title)); pageTitle = detail["title.page"]; break; case SearchResultType.Comment: trackUri = trackUri.With("typeid", item.TypeId.ToString()); uri = new XUri(Utils.AsPublicUiUri(Title.FromUriPath(path))).WithFragment(uri.Fragment); pageTitle = detail["title.page"]; break; default: uri = new XUri(Utils.AsPublicUiUri(Title.FromUriPath(path))); break; } } catch (Exception e) { // Note (arnec): not being able to derive the Ui Uri is not enough reason to skip the item _log.Warn("unable to derive UI uri for item", e); } searchDoc.Start("result") .Elem("id", item.TypeId); if (explain) { var rankable = item as RankableSearchResultItem; if (rankable != null) { searchDoc.Start("explain") .Elem("rank.normalized", rankable.Rank) .Elem("rank.raw", rankable.RawRank) .Elem("lucene-score", rankable.LuceneScore) .Elem("lucene-position", rankable.Position) .Elem("normalized-rating", rankable.Rating) .Elem("rating-count", rankable.RatingCount) .Elem("rating-boost", rankable.RatingBoost) .Elem("search-popularity", rankable.SearchPopularity) .Elem("search-popularity-boost", rankable.SearchPopularityBoost) .End(); } } searchDoc.Elem("uri", uri) .Elem("uri.track", trackUri) .Elem("rank", item.Rank) .Elem("title", title) .Start("page") .Start("rating").Attr("score", detail["rating.score"]).Attr("count", detail["rating.count"]).End() .Elem("title", pageTitle) .Elem("path", path) .End() .Elem("author", detail["author"]) .Elem("date.modified", item.Modified) .Elem("content", detail["preview"]) .Elem("type", item.Type.ToString().ToLower()) .Elem("mime", detail["mime"]) .Elem("tag", detail["tag"]) .Elem("size", detail["size"]) .Elem("wordcount", detail["wordcount"]) .End(); position++; } else { searchDoc.Start("document").Elem("score", item.Rank); foreach (var kvp in item.Detail) { if (kvp.Key.EqualsInvariant("score")) { continue; } searchDoc.Elem(kvp.Key, kvp.Value); } searchDoc.End(); } } catch (Exception e) { // skip any item we cannot process _log.Warn("unable to process search data for item", e); // Note (arnec): skipping an item throws off total and querycount and messes with offset. It's an outlier in the first place // so probably not worth correcting for. } count++; } searchDoc.Attr("count", count); result.Return(searchDoc); yield break; }
public Result <XDoc> FormatResultSet(SearchResult searchResultSet, SetDiscriminator discriminator, bool explain, TrackingInfo trackingInfo, Result <XDoc> result) { return(Coroutine.Invoke(FormatResultSet_Helper, searchResultSet, discriminator, explain, trackingInfo, result)); }