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&amp;rank=1&amp;type=page&amp;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());
        }
Exemple #5
0
 public Result <XDoc> FormatResultSet(SearchResult searchResultSet, SetDiscriminator discriminator, bool explain, TrackingInfo trackingInfo, Result <XDoc> result)
 {
     return(_instance.FormatResultSet(searchResultSet, discriminator, explain, trackingInfo, result));
 }
Exemple #6
0
        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;
        }
Exemple #7
0
 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));
 }