Skip to content

Commit

Permalink
feat(queryRuleContext): allow to make refinements based on query (#4638)
Browse files Browse the repository at this point in the history
* feat(queryRuleContext): allow to make refinements based on query

this use case came up in a support ticket, they wanted to add a query rule when the query _did not_ match something. The engine only has positive matches, so negative matches need to be done frontend.

Right now I implemented it like this: https://codesandbox.io/s/dynamically-deactivate-rule-forked-wouf8?file=/src/app.js but it would be nicer using trackedFilters as they should be used: (link pending)

* refactor(getRefinements): rename clearsQuery to includesQuery
  • Loading branch information
Haroenv authored Feb 5, 2021
1 parent d3f18d1 commit dd033fc
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 10 deletions.
6 changes: 3 additions & 3 deletions src/connectors/clear-refinements/connectClearRefinements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ function getAttributesToClear({
excludedAttributes,
transformItems,
}): AttributesToClear {
const clearsQuery =
const includesQuery =
includedAttributes.indexOf('query') !== -1 ||
excludedAttributes.indexOf('query') === -1;

Expand All @@ -206,7 +206,7 @@ function getAttributesToClear({
getRefinements(
scopedResult.results,
scopedResult.helper.state,
clearsQuery
includesQuery
)
.map(refinement => refinement.attribute)
.filter(
Expand All @@ -219,7 +219,7 @@ function getAttributesToClear({
.filter(
attribute =>
// If the query is included, we ignore the default `excludedAttributes = ['query']`
(attribute === 'query' && clearsQuery) ||
(attribute === 'query' && includesQuery) ||
// Otherwise, ignore the excluded attributes
excludedAttributes.indexOf(attribute) === -1
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ function getRefinementsItems({
includedAttributes: CurrentRefinementsConnectorParams['includedAttributes'];
excludedAttributes: CurrentRefinementsConnectorParams['excludedAttributes'];
}): CurrentRefinementsConnectorParamsItem[] {
const clearsQuery =
const includesQuery =
(includedAttributes || []).indexOf('query') !== -1 ||
(excludedAttributes || []).indexOf('query') === -1;

Expand All @@ -259,7 +259,7 @@ function getRefinementsItems({
: (item: CurrentRefinementsConnectorParamsRefinement) =>
excludedAttributes!.indexOf(item.attribute) === -1;

const items = getRefinements(results, helper.state, clearsQuery)
const items = getRefinements(results, helper.state, includesQuery)
.map(normalizeRefinement)
.filter(filterFunction);

Expand Down
86 changes: 84 additions & 2 deletions src/connectors/query-rules/__tests__/connectQueryRules-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/query-rules
expect(priceFilterSpy).toHaveBeenCalledWith([500, 400, 100]);
});

test('can filter trackedFilters with facets refinements', () => {
test('can track filters from facets refinements', () => {
const helper = createFakeHelper({
disjunctiveFacets: ['brand'],
});
Expand Down Expand Up @@ -637,7 +637,7 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/query-rules
expect(brandFilterSpy).toHaveBeenCalledWith(['Samsung', 'Apple']);
});

test('can filter tracked filters from numeric refinements', () => {
test('can track filters from numeric refinements', () => {
const helper = createFakeHelper();
const priceFilterSpy = jest.fn(() => [500]);
const { makeWidget } = createWidget();
Expand Down Expand Up @@ -702,6 +702,88 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/query-rules
expect(priceFilterSpy).toHaveBeenCalledWith([500, 400, 100]);
});

test('can track filters from query', () => {
const helper = createFakeHelper();
const querySpy = jest.fn(filters => {
const [query] = filters as string[];
return query.includes('cat') ? [query] : [];
});
const { makeWidget } = createWidget();
const widget = makeWidget({
trackedFilters: {
query: querySpy,
},
});

widget.init!(
createInitOptions({
helper,
state: helper.state,
})
);

expect((helper.state as SearchParameters).ruleContexts).toEqual(
undefined
);
expect(querySpy).toHaveBeenCalledTimes(0);

widget.render!(
createRenderOptions({
helper,
state: helper.state,
results: new SearchResults(helper.state, [
createSingleSearchResponse(),
createSingleSearchResponse(),
]),
})
);

expect((helper.state as SearchParameters).ruleContexts).toEqual(
undefined
);
expect(querySpy).toHaveBeenCalledTimes(0);

helper.setState({
query: 'cats are cool',
});

widget.render!(
createRenderOptions({
helper,
state: helper.state,
results: new SearchResults(helper.state, [
createSingleSearchResponse(),
createSingleSearchResponse(),
]),
})
);

expect((helper.state as SearchParameters).ruleContexts).toEqual([
'ais-query-cats_are_cool',
]);
expect(querySpy).toHaveBeenCalledTimes(1);
expect(querySpy).toHaveBeenCalledWith(['cats are cool']);

helper.setState({
query: 'dogs are cool',
});

widget.render!(
createRenderOptions({
helper,
state: helper.state,
results: new SearchResults(helper.state, [
createSingleSearchResponse(),
createSingleSearchResponse(),
]),
})
);

expect((helper.state as SearchParameters).ruleContexts).toBeUndefined();
expect(querySpy).toHaveBeenCalledTimes(2);
expect(querySpy).toHaveBeenCalledWith(['dogs are cool']);
});

test('escapes all ruleContexts before passing them to search parameters', () => {
const helper = createFakeHelper({
disjunctiveFacets: ['brand'],
Expand Down
3 changes: 2 additions & 1 deletion src/connectors/query-rules/connectQueryRules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ function getRuleContextsFromTrackedFilters({
// An empty object is technically not a `SearchResults` but `getRefinements`
// only accesses properties, meaning it will not throw with an empty object.
helper.lastResults || ({} as SearchResults),
sharedHelperState
sharedHelperState,
true
)
.filter(
(refinement: InternalRefinement) => refinement.attribute === facetName
Expand Down
4 changes: 2 additions & 2 deletions src/lib/utils/getRefinements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ function getRefinement(
function getRefinements(
results: SearchResults,
state: SearchParameters,
clearsQuery: boolean = false
includesQuery: boolean = false
): Refinement[] {
const refinements: Refinement[] = [];
const {
Expand Down Expand Up @@ -188,7 +188,7 @@ function getRefinements(
refinements.push({ type: 'tag', attribute: '_tags', name: refinementName });
});

if (clearsQuery && state.query && state.query.trim()) {
if (includesQuery && state.query && state.query.trim()) {
refinements.push({
attribute: 'query',
type: 'query',
Expand Down

0 comments on commit dd033fc

Please sign in to comment.