diff --git a/src/Constructorio_NET.Tests/client/modules/AutocompleteTests.cs b/src/Constructorio_NET.Tests/client/modules/AutocompleteTests.cs index e8edd593..90107231 100644 --- a/src/Constructorio_NET.Tests/client/modules/AutocompleteTests.cs +++ b/src/Constructorio_NET.Tests/client/modules/AutocompleteTests.cs @@ -229,6 +229,92 @@ public async Task GetAutocompleteResultsShouldReturnResultWithMultipleFilters() Assert.GreaterOrEqual(res.Sections["Products"].Count, 1, "Results exist"); } + [Test] + public async Task GetAutocompleteResultsShouldReturnResultWithPreFilterExpression() + { + ValuePreFilterExpression filterByGroupId = new ValuePreFilterExpression("group_id", "All"); + AutocompleteRequest req = new AutocompleteRequest("item") + { + UserInfo = UserInfo, + PreFilterExpression = filterByGroupId, + }; + ConstructorIO constructorio = new ConstructorIO(this.Config); + AutocompleteResponse res = await constructorio.Autocomplete.GetAutocompleteResults(req); + + Assert.NotNull(res.ResultId, "Result id exists"); + res.Request.TryGetValue("pre_filter_expression", out object reqPreFilterExpression); + Assert.AreEqual( + JObject.Parse(filterByGroupId.GetExpression()), + reqPreFilterExpression, + "Pre Filter Expression is sent in request" + ); + } + + [Test] + public async Task GetAutocompleteResultsShouldReturnResultWithPreFilterExpressionPerSection() + { + ValuePreFilterExpression filterByGroupId = new ValuePreFilterExpression("group_id", "All"); + AutocompleteRequest req = new AutocompleteRequest("item") + { + UserInfo = UserInfo, + PreFilterExpressionPerSection = new List + { + new PreFilterExpressionPerSection("Products", filterByGroupId), + }, + }; + ConstructorIO constructorio = new ConstructorIO(this.Config); + AutocompleteResponse res = await constructorio.Autocomplete.GetAutocompleteResults(req); + + Assert.NotNull(res.ResultId, "Result id exists"); + res.Request.TryGetValue("pre_filter_expression", out object reqPreFilterExpression); + JObject parsedPreFilterExpression = (JObject)reqPreFilterExpression; + Assert.NotNull(parsedPreFilterExpression, "Pre filter expression exists in response"); + Assert.AreEqual( + JObject.Parse(filterByGroupId.GetExpression()), + parsedPreFilterExpression["Products"], + "Per-section pre filter expression is sent in request" + ); + } + + [Test] + public async Task GetAutocompleteResultsShouldReturnResultWithMultiplePreFilterExpressionsPerSection() + { + ValuePreFilterExpression filterByGroupId = new ValuePreFilterExpression("group_id", "All"); + AndPreFilterExpression filterByPriceAndBrand = new AndPreFilterExpression( + new List + { + new RangePreFilterExpression("price", new List { "10", "100" }), + new ValuePreFilterExpression("brand", "Nike"), + } + ); + AutocompleteRequest req = new AutocompleteRequest("item") + { + UserInfo = UserInfo, + PreFilterExpressionPerSection = new List + { + new PreFilterExpressionPerSection("Products", filterByGroupId), + new PreFilterExpressionPerSection("Search Suggestions", filterByPriceAndBrand), + }, + }; + ConstructorIO constructorio = new ConstructorIO(this.Config); + AutocompleteResponse res = await constructorio.Autocomplete.GetAutocompleteResults(req); + + Assert.NotNull(res.ResultId, "Result id exists"); + res.Request.TryGetValue("pre_filter_expression", out object reqPreFilterExpression); + JObject parsedPreFilterExpression = (JObject)reqPreFilterExpression; + Assert.NotNull(parsedPreFilterExpression, "Pre filter expression exists in response"); + Assert.AreEqual( + JObject.Parse(filterByGroupId.GetExpression()), + parsedPreFilterExpression["Products"], + "Value per-section pre filter expression is sent in request" + ); + Assert.AreEqual( + JObject.Parse(filterByPriceAndBrand.GetExpression()), + parsedPreFilterExpression["Search Suggestions"], + "Composite per-section pre filter expression is sent in request" + ); + } + [Test] public async Task GetAutocompleteResultsShouldReturnResultWithFiltersPerSection() { diff --git a/src/Constructorio_NET.Tests/models/Autocomplete/AutocompleteRequestTest.cs b/src/Constructorio_NET.Tests/models/Autocomplete/AutocompleteRequestTest.cs index 587fb9ab..9c6b58b4 100644 --- a/src/Constructorio_NET.Tests/models/Autocomplete/AutocompleteRequestTest.cs +++ b/src/Constructorio_NET.Tests/models/Autocomplete/AutocompleteRequestTest.cs @@ -62,6 +62,53 @@ public void GetRequestParameters() Assert.AreEqual(this.FiltersPerSection, requestParameters[Constants.FILTERS_PER_SECTION]); } + [Test] + public void GetRequestParametersWithPreFilterExpression() + { + ValuePreFilterExpression filterByBrand = new ValuePreFilterExpression("Brand", "XYZ"); + AutocompleteRequest req = new AutocompleteRequest(this.Query) + { + PreFilterExpression = filterByBrand, + }; + + Hashtable requestParameters = req.GetRequestParameters(); + Assert.IsNotNull(requestParameters[Constants.PRE_FILTER_EXPRESSION]); + Assert.IsTrue(requestParameters[Constants.PRE_FILTER_EXPRESSION].ToString().Contains("Brand")); + Assert.IsTrue(requestParameters[Constants.PRE_FILTER_EXPRESSION].ToString().Contains("XYZ")); + } + + [Test] + public void GetRequestParametersWithPreFilterExpressionPerSection() + { + ValuePreFilterExpression filterProducts = new ValuePreFilterExpression("Brand", "XYZ"); + ValuePreFilterExpression filterSuggestions = new ValuePreFilterExpression("group_id", "All"); + AutocompleteRequest req = new AutocompleteRequest(this.Query) + { + PreFilterExpressionPerSection = new List + { + new PreFilterExpressionPerSection("Products", filterProducts), + new PreFilterExpressionPerSection("Search Suggestions", filterSuggestions), + }, + }; + + Hashtable requestParameters = req.GetRequestParameters(); + List perSection = (List)requestParameters[Constants.PRE_FILTER_EXPRESSION_PER_SECTION]; + Assert.IsNotNull(perSection); + Assert.AreEqual(2, perSection.Count); + Assert.AreEqual("Products", perSection[0].Section); + Assert.AreEqual("Search Suggestions", perSection[1].Section); + } + + [Test] + public void GetRequestParametersWithoutPreFilterExpression() + { + AutocompleteRequest req = new AutocompleteRequest(this.Query); + + Hashtable requestParameters = req.GetRequestParameters(); + Assert.IsFalse(requestParameters.ContainsKey(Constants.PRE_FILTER_EXPRESSION)); + Assert.IsFalse(requestParameters.ContainsKey(Constants.PRE_FILTER_EXPRESSION_PER_SECTION)); + } + [Test] public void GetRequestHeaders() { diff --git a/src/Constructorio_NET.Tests/utils/HelpersTest.cs b/src/Constructorio_NET.Tests/utils/HelpersTest.cs index 0ad04df9..139cb40b 100644 --- a/src/Constructorio_NET.Tests/utils/HelpersTest.cs +++ b/src/Constructorio_NET.Tests/utils/HelpersTest.cs @@ -360,6 +360,28 @@ public void MakeUrlSearchWithFiltersPerSection() Assert.That(hasColorBlueFilter && hasColorGreenFilter, "url is properly formed and has all filters applied"); } + [Test] + public void MakeUrlAutocompleteWithPreFilterExpressionPerSection() + { + List paths = new List { "autocomplete", this.Query }; + List preFilterExpressionPerSection = new List + { + new PreFilterExpressionPerSection("Products", new ValuePreFilterExpression("Brand", "XYZ")), + new PreFilterExpressionPerSection("Search Suggestions", new ValuePreFilterExpression("group_id", "All")), + }; + Hashtable queryParams = new Hashtable() + { + { Constants.PRE_FILTER_EXPRESSION_PER_SECTION, preFilterExpressionPerSection }, + }; + + string url = MakeUrl(this.Options, paths, queryParams); + bool hasProductsKey = Regex.Match(url, "&pre_filter_expression%5BProducts%5D=").Success; + bool hasSuggestionsKey = Regex.Match(url, "&pre_filter_expression%5BSearch%20Suggestions%5D=").Success; + bool hasBrandExpression = url.Contains(OurEscapeDataString("XYZ")); + bool hasGroupExpression = url.Contains(OurEscapeDataString("All")); + Assert.That(hasProductsKey && hasSuggestionsKey && hasBrandExpression && hasGroupExpression, "url should have bracketed per-section pre_filter_expression"); + } + [Test] public async Task TestCreateRequest() { diff --git a/src/constructor.io/models/Autocomplete/AutocompleteRequest.cs b/src/constructor.io/models/Autocomplete/AutocompleteRequest.cs index a10f00f9..bdbbcd47 100644 --- a/src/constructor.io/models/Autocomplete/AutocompleteRequest.cs +++ b/src/constructor.io/models/Autocomplete/AutocompleteRequest.cs @@ -31,6 +31,17 @@ public class AutocompleteRequest : IFilterable, IUserDetails /// public Dictionary>> FiltersPerSection { get; set; } + /// + /// Gets or sets the filter expression used to scope results across all sections. + /// + public PreFilterExpression PreFilterExpression { get; set; } + + /// + /// Gets or sets per-section filter expressions used to scope results for specific sections. + /// Serialized as pre_filter_expression[Section]={...}. + /// + public List PreFilterExpressionPerSection { get; set; } + /// /// Gets or sets the format options used to refine result groups. /// @@ -105,6 +116,16 @@ public Hashtable GetRequestParameters() parameters.Add(Constants.FILTERS_PER_SECTION, this.FiltersPerSection); } + if (this.PreFilterExpression != null) + { + parameters.Add(Constants.PRE_FILTER_EXPRESSION, this.PreFilterExpression.GetExpression()); + } + + if (this.PreFilterExpressionPerSection != null && this.PreFilterExpressionPerSection.Count > 0) + { + parameters.Add(Constants.PRE_FILTER_EXPRESSION_PER_SECTION, this.PreFilterExpressionPerSection); + } + if (this.TestCells != null) { parameters.Add(Constants.TEST_CELLS, this.TestCells); diff --git a/src/constructor.io/models/common/PreFilterExpressionPerSection.cs b/src/constructor.io/models/common/PreFilterExpressionPerSection.cs new file mode 100644 index 00000000..ec68ae20 --- /dev/null +++ b/src/constructor.io/models/common/PreFilterExpressionPerSection.cs @@ -0,0 +1,37 @@ +namespace Constructorio_NET.Models +{ + /// + /// Associates a with a specific autocomplete section. + /// Serialized to the documented bracket-notation shape (e.g. pre_filter_expression[Products]={...}). + /// + public class PreFilterExpressionPerSection + { + /// + /// Gets or sets the section the expression applies to (e.g. "Products", "Search Suggestions"). + /// + public string Section { get; set; } + + /// + /// Gets or sets the pre-filter expression to scope results for the section. + /// + public PreFilterExpression Expression { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// Section the expression applies to. + /// Pre-filter expression for the section. + public PreFilterExpressionPerSection(string section, PreFilterExpression expression) + { + this.Section = section; + this.Expression = expression; + } + + /// + /// Initializes a new instance of the class. + /// + public PreFilterExpressionPerSection() + { + } + } +} diff --git a/src/constructor.io/utils/Constants.cs b/src/constructor.io/utils/Constants.cs index eeaf54a0..2455de05 100644 --- a/src/constructor.io/utils/Constants.cs +++ b/src/constructor.io/utils/Constants.cs @@ -52,6 +52,7 @@ public static class Constants public const string QUIZ_VERSION_ID = "quiz_version_id"; public const string QUIZ_SESSION_ID = "quiz_session_id"; public const string PRE_FILTER_EXPRESSION = "pre_filter_expression"; + public const string PRE_FILTER_EXPRESSION_PER_SECTION = "pre_filter_expression_per_section"; public const string QS_PARAM = "qs"; public const string ON_MISSING = "on_missing"; public const string FORMAT = "format"; diff --git a/src/constructor.io/utils/Helpers.cs b/src/constructor.io/utils/Helpers.cs index 56f85bb8..5f3c7b7e 100644 --- a/src/constructor.io/utils/Helpers.cs +++ b/src/constructor.io/utils/Helpers.cs @@ -162,6 +162,25 @@ protected static string MakeUrl(Hashtable options, List paths, Hashtable } } + if (queryParams.Contains(Constants.PRE_FILTER_EXPRESSION_PER_SECTION)) + { + List preFilterExpressionPerSection = (List)queryParams[Constants.PRE_FILTER_EXPRESSION_PER_SECTION]; + queryParams.Remove(Constants.PRE_FILTER_EXPRESSION_PER_SECTION); + + foreach (var sectionExpression in preFilterExpressionPerSection) + { + if (sectionExpression?.Section == null || sectionExpression.Expression == null) + { + continue; + } + + url.Append("&" + Constants.PRE_FILTER_EXPRESSION + UrlEscapedStartSquareBracket) + .Append(OurEscapeDataString(sectionExpression.Section)) + .Append(UrlEscapedEndSquareBracket + "=") + .Append(OurEscapeDataString(sectionExpression.Expression.GetExpression())); + } + } + // Add test cells to query string if (queryParams.Contains(Constants.TEST_CELLS)) {