'use strict';
var compact = require('../functions/compact');
var defaultsPure = require('../functions/defaultsPure');
var fv = require('../functions/escapeFacetValue');
var find = require('../functions/find');
var findIndex = require('../functions/findIndex');
var formatSort = require('../functions/formatSort');
var merge = require('../functions/merge');
var orderBy = require('../functions/orderBy');
var escapeFacetValue = fv.escapeFacetValue;
var unescapeFacetValue = fv.unescapeFacetValue;
var generateHierarchicalTree = require('./generate-hierarchical-tree');
function getIndices(attributes) {
var indices = {};
attributes.forEach(function (val, idx) {
indices[val] = idx;
});
return indices;
}
function assignFacetStats(dest, facetStats, key) {
if (facetStats && facetStats[key]) {
dest.stats = facetStats[key];
}
}
function findMatchingHierarchicalFacetFromAttributeName(
hierarchicalFacets,
hierarchicalAttributeName
) {
return find(
hierarchicalFacets,
function facetKeyMatchesAttribute(hierarchicalFacet) {
var facetNames = hierarchicalFacet.attributes || [];
return facetNames.indexOf(hierarchicalAttributeName) > -1;
}
);
}
function SearchResults(state, results, options) {
var mainSubResponse = results[0];
this._rawResults = results;
var self = this;
Object.keys(mainSubResponse).forEach(function (key) {
self[key] = mainSubResponse[key];
});
var opts = merge(
{
persistHierarchicalRootCount: false,
},
options
);
Object.keys(opts).forEach(function (key) {
self[key] = opts[key];
});
this.processingTimeMS = results.reduce(function (sum, result) {
return result.processingTimeMS === undefined
? sum
: sum + result.processingTimeMS;
}, 0);
this.disjunctiveFacets = [];
this.hierarchicalFacets = state.hierarchicalFacets.map(
function initFutureTree() {
return [];
}
);
this.facets = [];
var disjunctiveFacets = state.getRefinedDisjunctiveFacets();
var facetsIndices = getIndices(state.facets);
var disjunctiveFacetsIndices = getIndices(state.disjunctiveFacets);
var nextDisjunctiveResult = 1;
var mainFacets = mainSubResponse.facets || {};
Object.keys(mainFacets).forEach(function (facetKey) {
var facetValueObject = mainFacets[facetKey];
var hierarchicalFacet = findMatchingHierarchicalFacetFromAttributeName(
state.hierarchicalFacets,
facetKey
);
if (hierarchicalFacet) {
var facetIndex = hierarchicalFacet.attributes.indexOf(facetKey);
var idxAttributeName = findIndex(state.hierarchicalFacets, function (f) {
return f.name === hierarchicalFacet.name;
});
self.hierarchicalFacets[idxAttributeName][facetIndex] = {
attribute: facetKey,
data: facetValueObject,
exhaustive: mainSubResponse.exhaustiveFacetsCount,
};
} else {
var isFacetDisjunctive = state.disjunctiveFacets.indexOf(facetKey) !== -1;
var isFacetConjunctive = state.facets.indexOf(facetKey) !== -1;
var position;
if (isFacetDisjunctive) {
position = disjunctiveFacetsIndices[facetKey];
self.disjunctiveFacets[position] = {
name: facetKey,
data: facetValueObject,
exhaustive: mainSubResponse.exhaustiveFacetsCount,
};
assignFacetStats(
self.disjunctiveFacets[position],
mainSubResponse.facets_stats,
facetKey
);
}
if (isFacetConjunctive) {
position = facetsIndices[facetKey];
self.facets[position] = {
name: facetKey,
data: facetValueObject,
exhaustive: mainSubResponse.exhaustiveFacetsCount,
};
assignFacetStats(
self.facets[position],
mainSubResponse.facets_stats,
facetKey
);
}
}
});
this.hierarchicalFacets = compact(this.hierarchicalFacets);
disjunctiveFacets.forEach(function (disjunctiveFacet) {
var result = results[nextDisjunctiveResult];
var facets = result && result.facets ? result.facets : {};
var hierarchicalFacet = state.getHierarchicalFacetByName(disjunctiveFacet);
Object.keys(facets).forEach(function (dfacet) {
var facetResults = facets[dfacet];
var position;
if (hierarchicalFacet) {
position = findIndex(state.hierarchicalFacets, function (f) {
return f.name === hierarchicalFacet.name;
});
var attributeIndex = findIndex(
self.hierarchicalFacets[position],
function (f) {
return f.attribute === dfacet;
}
);
if (attributeIndex === -1) {
return;
}
self.hierarchicalFacets[position][attributeIndex].data = merge(
{},
self.hierarchicalFacets[position][attributeIndex].data,
facetResults
);
} else {
position = disjunctiveFacetsIndices[dfacet];
var dataFromMainRequest =
(mainSubResponse.facets && mainSubResponse.facets[dfacet]) || {};
self.disjunctiveFacets[position] = {
name: dfacet,
data: defaultsPure({}, facetResults, dataFromMainRequest),
exhaustive: result.exhaustiveFacetsCount,
};
assignFacetStats(
self.disjunctiveFacets[position],
result.facets_stats,
dfacet
);
if (state.disjunctiveFacetsRefinements[dfacet]) {
state.disjunctiveFacetsRefinements[dfacet].forEach(function (
refinementValue
) {
if (
!self.disjunctiveFacets[position].data[refinementValue] &&
state.disjunctiveFacetsRefinements[dfacet].indexOf(
unescapeFacetValue(refinementValue)
) > -1
) {
self.disjunctiveFacets[position].data[refinementValue] = 0;
}
});
}
}
});
nextDisjunctiveResult++;
});
state.getRefinedHierarchicalFacets().forEach(function (refinedFacet) {
var hierarchicalFacet = state.getHierarchicalFacetByName(refinedFacet);
var separator = state._getHierarchicalFacetSeparator(hierarchicalFacet);
var currentRefinement = state.getHierarchicalRefinement(refinedFacet);
if (
currentRefinement.length === 0 ||
currentRefinement[0].split(separator).length < 2
) {
return;
}
results.slice(nextDisjunctiveResult).forEach(function (result) {
var facets = result && result.facets ? result.facets : {};
Object.keys(facets).forEach(function (dfacet) {
var facetResults = facets[dfacet];
var position = findIndex(state.hierarchicalFacets, function (f) {
return f.name === hierarchicalFacet.name;
});
var attributeIndex = findIndex(
self.hierarchicalFacets[position],
function (f) {
return f.attribute === dfacet;
}
);
if (attributeIndex === -1) {
return;
}
var defaultData = {};
if (
currentRefinement.length > 0 &&
!self.persistHierarchicalRootCount
) {
var root = currentRefinement[0].split(separator)[0];
defaultData[root] =
self.hierarchicalFacets[position][attributeIndex].data[root];
}
self.hierarchicalFacets[position][attributeIndex].data = defaultsPure(
defaultData,
facetResults,
self.hierarchicalFacets[position][attributeIndex].data
);
});
nextDisjunctiveResult++;
});
});
Object.keys(state.facetsExcludes).forEach(function (facetName) {
var excludes = state.facetsExcludes[facetName];
var position = facetsIndices[facetName];
self.facets[position] = {
name: facetName,
data: mainFacets[facetName],
exhaustive: mainSubResponse.exhaustiveFacetsCount,
};
excludes.forEach(function (facetValue) {
self.facets[position] = self.facets[position] || { name: facetName };
self.facets[position].data = self.facets[position].data || {};
self.facets[position].data[facetValue] = 0;
});
});
this.hierarchicalFacets = this.hierarchicalFacets.map(
generateHierarchicalTree(state)
);
this.facets = compact(this.facets);
this.disjunctiveFacets = compact(this.disjunctiveFacets);
this._state = state;
}
SearchResults.prototype.getFacetByName = function (name) {
function predicate(facet) {
return facet.name === name;
}
return (
find(this.facets, predicate) ||
find(this.disjunctiveFacets, predicate) ||
find(this.hierarchicalFacets, predicate)
);
};
function extractNormalizedFacetValues(results, attribute) {
function predicate(facet) {
return facet.name === attribute;
}
if (results._state.isConjunctiveFacet(attribute)) {
var facet = find(results.facets, predicate);
if (!facet) return [];
return Object.keys(facet.data).map(function (name) {
var value = escapeFacetValue(name);
return {
name: name,
escapedValue: value,
count: facet.data[name],
isRefined: results._state.isFacetRefined(attribute, value),
isExcluded: results._state.isExcludeRefined(attribute, name),
};
});
} else if (results._state.isDisjunctiveFacet(attribute)) {
var disjunctiveFacet = find(results.disjunctiveFacets, predicate);
if (!disjunctiveFacet) return [];
return Object.keys(disjunctiveFacet.data).map(function (name) {
var value = escapeFacetValue(name);
return {
name: name,
escapedValue: value,
count: disjunctiveFacet.data[name],
isRefined: results._state.isDisjunctiveFacetRefined(attribute, value),
};
});
} else if (results._state.isHierarchicalFacet(attribute)) {
var hierarchicalFacetValues = find(results.hierarchicalFacets, predicate);
if (!hierarchicalFacetValues) return hierarchicalFacetValues;
var hierarchicalFacet =
results._state.getHierarchicalFacetByName(attribute);
var separator =
results._state._getHierarchicalFacetSeparator(hierarchicalFacet);
var currentRefinement = unescapeFacetValue(
results._state.getHierarchicalRefinement(attribute)[0] || ''
);
if (currentRefinement.indexOf(hierarchicalFacet.rootPath) === 0) {
currentRefinement = currentRefinement.replace(
hierarchicalFacet.rootPath + separator,
''
);
}
var currentRefinementSplit = currentRefinement.split(separator);
currentRefinementSplit.unshift(attribute);
setIsRefined(hierarchicalFacetValues, currentRefinementSplit, 0);
return hierarchicalFacetValues;
}
return undefined;
}
function setIsRefined(item, currentRefinement, depth) {
item.isRefined =
item.name === (currentRefinement[depth] && currentRefinement[depth].trim());
if (item.data) {
item.data.forEach(function (child) {
setIsRefined(child, currentRefinement, depth + 1);
});
}
}
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, names, level + 1);
});
var sortedChildren = sortFn(children, names[level]);
var newNode = defaultsPure({ data: sortedChildren }, node);
return newNode;
}
SearchResults.DEFAULT_SORT = ['isRefined:desc', 'count:desc', 'name:asc'];
function vanillaSortFn(order, data) {
return data.sort(order);
}
function sortViaFacetOrdering(facetValues, facetOrdering) {
var orderedFacets = [];
var remainingFacets = [];
var order = facetOrdering.order || [];
var reverseOrder = order.reduce(function (acc, name, i) {
acc[name] = i;
return acc;
}, {});
facetValues.forEach(function (item) {
var name = item.path || item.name;
if (reverseOrder[name] !== undefined) {
orderedFacets[reverseOrder[name]] = item;
} else {
remainingFacets.push(item);
}
});
orderedFacets = orderedFacets.filter(function (facet) {
return facet;
});
var sortRemainingBy = facetOrdering.sortRemainingBy;
var ordering;
if (sortRemainingBy === 'hidden') {
return orderedFacets;
} else if (sortRemainingBy === 'alpha') {
ordering = [
['path', 'name'],
['asc', 'asc'],
];
} else {
ordering = [['count'], ['desc']];
}
return orderedFacets.concat(
orderBy(remainingFacets, ordering[0], ordering[1])
);
}
function getFacetOrdering(results, attribute) {
return (
results.renderingContent &&
results.renderingContent.facetOrdering &&
results.renderingContent.facetOrdering.values &&
results.renderingContent.facetOrdering.values[attribute]
);
}
SearchResults.prototype.getFacetValues = function (attribute, opts) {
var facetValues = extractNormalizedFacetValues(this, attribute);
if (!facetValues) {
return undefined;
}
var options = defaultsPure({}, opts, {
sortBy: SearchResults.DEFAULT_SORT,
facetOrdering: !(opts && opts.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 (facetOrdering) {
return sortViaFacetOrdering(data, facetOrdering);
}
}
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);
}
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
);
};
SearchResults.prototype.getFacetStats = function (attribute) {
if (this._state.isConjunctiveFacet(attribute)) {
return getFacetStatsIfAvailable(this.facets, attribute);
} else if (this._state.isDisjunctiveFacet(attribute)) {
return getFacetStatsIfAvailable(this.disjunctiveFacets, attribute);
}
return undefined;
};
function getFacetStatsIfAvailable(facetList, facetName) {
var data = find(facetList, function (facet) {
return facet.name === facetName;
});
return data && data.stats;
}
SearchResults.prototype.getRefinements = function () {
var state = this._state;
var results = this;
var res = [];
Object.keys(state.facetsRefinements).forEach(function (attributeName) {
state.facetsRefinements[attributeName].forEach(function (name) {
res.push(
getRefinement(state, 'facet', attributeName, name, results.facets)
);
});
});
Object.keys(state.facetsExcludes).forEach(function (attributeName) {
state.facetsExcludes[attributeName].forEach(function (name) {
res.push(
getRefinement(state, 'exclude', attributeName, name, results.facets)
);
});
});
Object.keys(state.disjunctiveFacetsRefinements).forEach(function (
attributeName
) {
state.disjunctiveFacetsRefinements[attributeName].forEach(function (name) {
res.push(
getRefinement(
state,
'disjunctive',
attributeName,
name,
results.disjunctiveFacets
)
);
});
});
Object.keys(state.hierarchicalFacetsRefinements).forEach(function (
attributeName
) {
state.hierarchicalFacetsRefinements[attributeName].forEach(function (name) {
res.push(
getHierarchicalRefinement(
state,
attributeName,
name,
results.hierarchicalFacets
)
);
});
});
Object.keys(state.numericRefinements).forEach(function (attributeName) {
var operators = state.numericRefinements[attributeName];
Object.keys(operators).forEach(function (operator) {
operators[operator].forEach(function (value) {
res.push({
type: 'numeric',
attributeName: attributeName,
name: value,
numericValue: value,
operator: operator,
});
});
});
});
state.tagRefinements.forEach(function (name) {
res.push({ type: 'tag', attributeName: '_tags', name: name });
});
return res;
};
function getRefinement(state, type, attributeName, name, resultsFacets) {
var facet = find(resultsFacets, function (f) {
return f.name === attributeName;
});
var count = facet && facet.data && facet.data[name] ? facet.data[name] : 0;
var exhaustive = (facet && facet.exhaustive) || false;
return {
type: type,
attributeName: attributeName,
name: name,
count: count,
exhaustive: exhaustive,
};
}
function getHierarchicalRefinement(state, attributeName, name, resultsFacets) {
var facetDeclaration = state.getHierarchicalFacetByName(attributeName);
var separator = state._getHierarchicalFacetSeparator(facetDeclaration);
var split = name.split(separator);
var rootFacet = find(resultsFacets, function (facet) {
return facet.name === attributeName;
});
var facet = split.reduce(function (intermediateFacet, part) {
var newFacet =
intermediateFacet &&
find(intermediateFacet.data, function (f) {
return f.name === part;
});
return newFacet !== undefined ? newFacet : intermediateFacet;
}, rootFacet);
var count = (facet && facet.count) || 0;
var exhaustive = (facet && facet.exhaustive) || false;
var path = (facet && facet.path) || '';
return {
type: 'hierarchical',
attributeName: attributeName,
name: path,
count: count,
exhaustive: exhaustive,
};
}
module.exports = SearchResults;