Skip to content
This repository has been archived by the owner on Jul 11, 2023. It is now read-only.

feat(getFacetValues): process facetOrdering #822

Merged
merged 5 commits into from Jun 14, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
138 changes: 111 additions & 27 deletions src/SearchResults/index.js
Expand Up @@ -659,20 +659,30 @@ function extractNormalizedFacetValues(results, attribute) {
}

/**
* Sort nodes of a hierarchical facet results
* Sort nodes of a hierarchical or disjunctive facet results
* @private
* @param {HierarchicalFacet} node node to upon which we want to apply the sort
* @param {function} sortFn
* @param {HierarchicalFacet|Array} node node to upon which we want to apply the sort
Haroenv marked this conversation as resolved.
Show resolved Hide resolved
* @param {string[]} names attribute names
* @param {number} [level=0] current index in the names array
*/
function recSort(sortFn, node) {
function recSort(sortFn, node, names, level) {
level = level || 0;

if (Array.isArray(node)) {
return sortFn(node, names[level]);
}

if (!node.data || node.data.length === 0) {
return node;
}

var children = node.data.map(function(childNode) {
return recSort(sortFn, childNode);
return recSort(sortFn, childNode, names, level + 1);
});
var sortedChildren = sortFn(children);
var newNode = merge({}, node, {data: sortedChildren});
var sortedChildren = sortFn(children, names[level]);
var newNode = merge({}, node);
newNode.data = sortedChildren;
Haroenv marked this conversation as resolved.
Show resolved Hide resolved
return newNode;
}

Expand All @@ -682,6 +692,66 @@ function vanillaSortFn(order, data) {
return data.sort(order);
}

/**
* @typedef FacetOrdering
* @type {Object}
* @property {string[]} [order]
* @property {'count' | 'alpha' | 'hidden'} [sortRemainingBy]
*/

/**
* Sorts facet arrays via their facet ordering
* @param {Array} facetValues the values
* @param {FacetOrdering} facetOrdering the ordering
* @returns {Array}
*/
function sortViaFacetOrdering(facetValues, facetOrdering) {
var pinnedFacets = [];
var unpinnedFacets = [];
Haroenv marked this conversation as resolved.
Show resolved Hide resolved

var pinned = facetOrdering.order || [];
var reversePinned = pinned.reduce(function(acc, name, i) {
acc[name] = i;
return acc;
}, {});

facetValues.forEach(function(item) {
if (reversePinned[item.name] !== undefined) {
pinnedFacets[reversePinned[item.name]] = item;
} else {
unpinnedFacets.push(item);
}
});

var sortRemainingBy = facetOrdering.sortRemainingBy;
var ordering;
if (sortRemainingBy === 'hidden') {
return pinnedFacets;
} else if (sortRemainingBy === 'alpha') {
ordering = [['name'], ['asc']];
} else {
ordering = [['count'], ['desc']];
}
Comment on lines +735 to +737
Copy link
Member

Choose a reason for hiding this comment

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

Making sure: we decide to fallback to sorting by count and not throwing if the provided order is wrong?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it's something that's a bit blurry at the moment, the spec mentions falling back to sortFacetValuesBy, but I can't retrieve that in the result-side, so I think falling back to just count by default is reasonable.

Alternatively we could make sortRemainingBy required in the dashboard possibly. What did you pick @VladislavFitz?

Choose a reason for hiding this comment

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

required in the dashboard

What do you mean? Make the engine always return renderingContent even if it's not set?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is about the key sortRemainingBy, what do you sort by if the key isn't given?

Choose a reason for hiding this comment

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

Sort by count by default sounds good to me 👍


return pinnedFacets.concat(
orderBy(unpinnedFacets, ordering[0], ordering[1])
);
}

/**
* @param {SearchResults} results a results class (this)
Haroenv marked this conversation as resolved.
Show resolved Hide resolved
* @param {string} attribute the attribute to retrieve ordering of
* @returns {FacetOrdering=}
francoischalifour marked this conversation as resolved.
Show resolved Hide resolved
*/
function getFacetOrdering(results, attribute) {
return (
results.renderingContent &&
results.renderingContent.facetOrdering &&
results.renderingContent.facetOrdering.values &&
results.renderingContent.facetOrdering.values[attribute]
);
}

/**
* Get a the list of values for a given facet attribute. Those values are sorted
* refinement first, descending count (bigger value on top), and name ascending
Expand All @@ -694,6 +764,9 @@ function vanillaSortFn(order, data) {
* might not be respected if you have facet values that are already refined.
* @param {string} attribute attribute name
* @param {object} opts configuration options.
* @param {boolean} [opts.facetOrdering=false]
* Force the use of facetOrdering from the result if a sortBy is present. If
* sortBy isn't present, facetOrdering will be used automatically.
Haroenv marked this conversation as resolved.
Show resolved Hide resolved
* @param {Array.<string> | function} opts.sortBy
* When using strings, it consists of
* the name of the [FacetValue](#SearchResults.FacetValue) or the
Expand Down Expand Up @@ -733,30 +806,41 @@ SearchResults.prototype.getFacetValues = function(attribute, opts) {
return undefined;
}

var options = defaultsPure({}, opts, {sortBy: SearchResults.DEFAULT_SORT});
var options = defaultsPure({}, opts, {
sortBy: SearchResults.DEFAULT_SORT,
// if no sortBy is given, attempt to sort based on facetOrdering
// if it is given, we still allow to sort via facet ordering first
facetOrdering: !(opts && opts.sortBy)
francoischalifour marked this conversation as resolved.
Show resolved Hide resolved
});

if (Array.isArray(options.sortBy)) {
var order = formatSort(options.sortBy, SearchResults.DEFAULT_SORT);
if (Array.isArray(facetValues)) {
return orderBy(facetValues, order[0], order[1]);
}
// If facetValues is not an array, it's an object thus a hierarchical facet object
return recSort(function(hierarchicalFacetValues) {
return orderBy(hierarchicalFacetValues, order[0], order[1]);
}, facetValues);
} else if (typeof options.sortBy === 'function') {
if (Array.isArray(facetValues)) {
return facetValues.sort(options.sortBy);
var results = this;
var attributes;
if (Array.isArray(facetValues)) {
attributes = [attribute];
} else {
var config = results._state.getHierarchicalFacetByName(facetValues.name);
attributes = config.attributes;
}

return recSort(function(data, facetName) {
if (options.facetOrdering) {
var facetOrdering = getFacetOrdering(results, facetName);
if (Boolean(facetOrdering)) {
return sortViaFacetOrdering(data, facetOrdering);
}
}
// If facetValues is not an array, it's an object thus a hierarchical facet object
return recSort(function(data) {

if (Array.isArray(options.sortBy)) {
var order = formatSort(options.sortBy, SearchResults.DEFAULT_SORT);
return orderBy(data, order[0], order[1]);
} else if (typeof options.sortBy === 'function') {
return vanillaSortFn(options.sortBy, data);
}, facetValues);
}
throw new Error(
'options.sortBy is optional but if defined it must be ' +
'either an array of string (predicates) or a sorting function'
);
}
throw new Error(
'options.sortBy is optional but if defined it must be ' +
'either an array of string (predicates) or a sorting function'
);
}, facetValues, attributes);
};

/**
Expand Down