search-client
Version:
Javascript library for executing searches in the Haive search-index via the SearchManager REST interface.
659 lines • 29.6 kB
JavaScript
import { __assign, __awaiter, __extends, __generator, __spreadArrays } from "tslib";
import { AuthToken } from '../Authentication';
import { BaseCall, Filter, MatchMode, Query, SortMethod, } from '../Common';
import { CategorizeQueryConverter } from './CategorizeQueryConverter';
import { CategorizeSettings } from './CategorizeSettings';
/**
* The Categorize service queries the search-engine for which categories that any
* search-matches for the same query will contain.
*
* It is normally used indirectly via the SearchClient class.
*/
var Categorize = /** @class */ (function (_super) {
__extends(Categorize, _super);
/**
* Creates a Categorize instance that handles fetching categories dependent on settings and query.
* Supports registering a callback in order to receive categories when they have been received.
* @param settings - The settings that define how the Categorize instance is to operate.
* @param auth - An object that handles the authentication.
*/
function Categorize(settings, auth, fetchMethod) {
var _this = _super.call(this) // dummy
|| this;
// prepare for super.init
settings = new CategorizeSettings(settings);
auth = auth || new AuthToken();
_super.prototype.init.call(_this, settings, auth, fetchMethod);
// Set own this props
_this.queryConverter = new CategorizeQueryConverter();
return _this;
}
Categorize.prototype.clientCategoriesUpdate = function (query) {
if (this.shouldUpdate()) {
this.cbSuccess(false, this.filterCategories(this.categories, query), null, null);
}
};
Categorize.prototype.categorizationTypeChanged = function (oldValue, query) {
if (!this.shouldUpdate('categorizationType', query)) {
return;
}
if (this.settings.triggers.categorizationTypeChanged) {
this.update(query);
}
};
Categorize.prototype.clientIdChanged = function (oldValue, query) {
if (!this.shouldUpdate('clientId', query)) {
return;
}
if (this.settings.triggers.clientIdChanged) {
this.update(query);
}
};
Categorize.prototype.dateFromChanged = function (oldValue, query) {
if (!this.shouldUpdate('dateFrom', query)) {
return;
}
if (this.settings.triggers.dateFromChanged) {
this.update(query);
}
};
Categorize.prototype.dateToChanged = function (oldValue, query) {
if (!this.shouldUpdate('dateTo', query)) {
return;
}
if (this.settings.triggers.dateToChanged) {
this.update(query);
}
};
Categorize.prototype.filtersChanged = function (oldValue, query) {
if (!this.shouldUpdate('filters', query)) {
return;
}
if (this.settings.triggers.filtersChanged) {
this.update(query);
}
};
Categorize.prototype.queryTextChanged = function (oldValue, query) {
if (!this.shouldUpdate('queryText', query)) {
return;
}
if (this.settings.triggers.queryChange) {
if (query.queryText.trim().length >
this.settings.triggers.queryChangeMinLength) {
if (this.settings.triggers.queryChangeInstantRegex &&
this.settings.triggers.queryChangeInstantRegex.test(query.queryText)) {
this.update(query);
return;
}
else {
if (this.settings.triggers.queryChangeDelay > -1) {
this.update(query, this.settings.triggers.queryChangeDelay);
return;
}
}
}
}
clearTimeout(this.delay);
};
Categorize.prototype.searchTypeChanged = function (oldValue, query) {
if (!this.shouldUpdate('searchType', query)) {
return;
}
if (this.settings.triggers.searchTypeChanged) {
this.update(query);
}
};
Categorize.prototype.uiLanguageCodeChanged = function (oldValue, query) {
if (!this.shouldUpdate('uiLanguageCode', query)) {
return;
}
if (this.settings.triggers.uiLanguageCodeChanged) {
this.update(query);
}
};
/**
* Creates a Filter object based on the input id (string [] or Category).
*
* NB! This method does NOT apply the filter in the filters collection.
* It is used behind the scenes by the filter* methods in SearchClient.
* To apply a filter you need to use the filter* properties/methods in
* SearchClient.
*
* If the category doesn't exist then the filter
* will not be created.
*
* If passing in a string[] then the value is expected to match the categoryName
* property of a listed category.
*
* @param categoryName A string array or a Category that denotes the category to create a filter for.
*/
Categorize.prototype.createCategoryFilter = function (categoryName) {
var catName = Array.isArray(categoryName)
? categoryName
: categoryName.categoryName;
var result = [];
var path = catName.slice(0);
var groupId = path.splice(0, 1)[0].toLowerCase();
if (!this.categories ||
!this.categories.groups ||
this.categories.groups.length === 0) {
return null;
}
var group = this.categories.groups.find(function (g) { return g.name.toLowerCase() === groupId; });
if (!group) {
return null;
}
result.push(group.displayName);
if (group.categories.length > 0) {
var _a = this.getCategoryPathDisplayNameFromCategories(path, group.categories), displayName = _a.displayName, ref = _a.ref;
if (displayName && displayName.length > 0) {
result = result.concat(displayName);
return new Filter(result, ref);
}
}
return null;
};
/**
* Find the category based on the category-name array.
*
* @param categoryName The category array that identifies the category.
* @returns The Category object if found or null.
*/
Categorize.prototype.findCategory = function (categoryName, categories) {
categories = categories || this.categories;
if (!categories) {
return null;
}
var groupIndex = categories.groups.findIndex(function (g) { return g.name === categoryName[0]; });
if (groupIndex < 0) {
return null;
}
var group = categories.groups[groupIndex];
if (categoryName.length === 1) {
return group;
}
var category = this.getCategoryPathDisplayNameFromCategories(categoryName.slice(1), group.categories);
return category ? category.ref : null;
};
/**
* Fetches the search-result categories from the server.
* @param query - The query-object that controls which results that are to be returned.
* @param suppressCallbacks - Set to true if you have defined callbacks, but somehow don't want them to be called.
* @returns a promise that when resolved returns a Categories object.
*/
Categorize.prototype.fetchInternal = function (query, suppressCallbacks) {
if (query === void 0) { query = new Query(); }
if (suppressCallbacks === void 0) { suppressCallbacks = false; }
return __awaiter(this, void 0, void 0, function () {
var reqInit, url, err, response, categories, warning, error_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
reqInit = this.requestObject();
this.fetchQuery = new Query(query);
url = this.queryConverter.getUrl(this.settings.url, this.fetchQuery);
_a.label = 1;
case 1:
_a.trys.push([1, 4, , 5]);
if (!this.cbRequest(suppressCallbacks, url, reqInit)) {
err = new Error();
err.name = 'cbRequestCancelled';
throw err;
}
return [4 /*yield*/, this.fetchMethod(url, reqInit)];
case 2:
response = _a.sent();
if (!response.ok) {
throw Error(response.status + " " + response.statusText + " for request url '" + url + "'");
}
return [4 /*yield*/, response.json()];
case 3:
categories = _a.sent();
this.categories = categories;
categories = this.filterCategories(categories, query);
// Handle situations where parsing was ok, but we have an error in the returned message from the server
if (!categories ||
categories.errorMessage ||
categories.statusCode !== 0) {
warning = {
message: (categories === null || categories === void 0 ? void 0 : categories.errorMessage) || 'Unspecified issue',
statusCode: categories === null || categories === void 0 ? void 0 : categories.statusCode,
};
console.warn('search-client/Categorize.fetchInternal()> ', warning);
this.cbWarning(suppressCallbacks, warning, url, reqInit);
}
this.cbSuccess(suppressCallbacks, categories, url, reqInit);
return [2 /*return*/, categories];
case 4:
error_1 = _a.sent();
if (error_1.name !== 'AbortError') {
this.cbError(suppressCallbacks, error_1, url, reqInit);
}
throw error_1;
case 5: return [2 /*return*/];
}
});
});
};
/**
* Adds missing filters as category-tree-nodes.
*/
Categorize.prototype.addFiltersInTreeIfMissing = function (filters, cats) {
var _this = this;
filters.forEach(function (f) {
if (f.hidden) {
return;
}
var depth = f.category.categoryName.length;
for (var i = 0; i < depth; i++) {
var categoryNames = f.category.categoryName.slice(0, i + 1);
if (!_this.findCategory(categoryNames, cats)) {
var displayName = f.displayName
? f.displayName[i]
: categoryNames[categoryNames.length - 1];
var parentCategoryNames = categoryNames.slice(0, -1);
if (i === 0) {
// Need to add group
var group = {
displayName: displayName,
categories: [],
expanded: false,
name: categoryNames[0],
};
cats.groups.push(group);
}
else {
// Need to add child category
var parent_1 = _this.findCategory(parentCategoryNames, cats);
if (!parent_1) {
throw Error('Since we are iterating from groups and outwards this should not happen.');
}
var category = i === depth - 1
? // Since we are on the last element we can add the category within the filter directly
__assign(__assign({ children: [], displayName: displayName }, f.category), { count: 0, expanded: false }) : // Not at the leaf-node yet.
{
categoryName: categoryNames,
children: [],
count: 0,
expanded: false,
displayName: displayName,
name: categoryNames[i],
};
// Since the parent has a child, set the node to be expanded.
parent_1.expanded = true;
// Add the child-category to the parent-node
if (i === 1) {
// Parent is a group
;
parent_1.categories.push(category);
}
else {
// Parent is a category
;
parent_1.children.push(category);
}
}
}
}
});
};
Categorize.prototype.filterCategories = function (categories, query) {
var _this = this;
if (query === void 0) { query = new Query(); }
// ROOT level adjustments
var cats = __assign({}, categories);
var rootOverride = this.settings.presentations.__ROOT__;
if (rootOverride) {
if (rootOverride.group &&
rootOverride.group.enabled &&
cats.groups.length >= rootOverride.group.minCount) {
// Add level of categories to group according to pattern
cats.groups = this.grouping(rootOverride, cats.groups);
}
if (rootOverride.filter && rootOverride.filter.enabled) {
cats.groups = this.filtering(rootOverride, cats.groups);
}
if (rootOverride.sort && rootOverride.sort.enabled) {
// Reorder level
cats.groups = this.sorting(rootOverride, cats.groups);
}
if (rootOverride.limit && rootOverride.limit.enabled) {
// Limit which categories to show
cats.groups = this.limiting(cats.groups, rootOverride.limit);
}
// Skipping expansion, as root is always expanded
}
var hiddenFilters = query.filters
? query.filters.filter(function (f) { return f.hidden; })
: [];
// GROUP-level adjustments
var groups = cats.groups.map(function (inGroup) {
var group = __assign({}, inGroup);
// Iterate filters that have only the group-level set
var hiddenFiltersInGroup = hiddenFilters.filter(function (f) {
return f.category.categoryName.length > 0 &&
f.category.categoryName[0] === group.name;
});
var match = hiddenFiltersInGroup.find(function (f) { return f.category.categoryName.length === 1; });
if (match) {
// The hidden filter is for this group exactly. So, remove the group
return null;
}
var groupOverride = _this.settings.presentations[group.name];
if (groupOverride) {
if (groupOverride.group &&
groupOverride.group.enabled &&
group.categories.length >= groupOverride.group.minCount) {
// Add level of categories to group according to pattern
group.categories = _this.grouping(groupOverride, group.categories);
}
if (groupOverride.filter && groupOverride.filter.enabled) {
group.categories = _this.filtering(groupOverride, group.categories);
}
if (groupOverride.sort && groupOverride.sort.enabled) {
// Reorder level
group.categories = _this.sorting(groupOverride, group.categories);
}
if (groupOverride.limit && groupOverride.limit.enabled) {
// Limit which categories to show
group.categories = _this.limiting(group.categories, groupOverride.limit);
}
if (groupOverride.expanded !== null) {
// Override whether the group is to be expanded or not
group.expanded = groupOverride.expanded;
}
}
if (group.categories && group.categories.length > 0) {
group.categories = _this.mapCategories(group.categories, hiddenFiltersInGroup, 1); // .filter(c => c !== undefined && c !== null);
}
if ((!group.categories || group.categories.length === 0) &&
hiddenFiltersInGroup.length > 0) {
// If the group has no categories, due to hidden filters, then remove the group itself
return null;
}
return group;
});
cats.groups = groups.filter(function (g) { return g !== undefined && g !== null; });
this.addFiltersInTreeIfMissing(query.filters, cats);
return cats;
};
Categorize.prototype.mapCategories = function (categories, hiddenFilters, depth) {
var _this = this;
// CATEGORY_level adjustments
var cats = __spreadArrays(categories);
cats = cats.map(function (inCategory) {
var category = __assign({}, inCategory);
// Iterate filters that have only the group-level set
var hiddenFiltersInCategory = hiddenFilters.filter(function (f) {
return f.category.categoryName.length > depth &&
f.category.categoryName[depth] === category.name;
});
if (hiddenFiltersInCategory.find(function (f) { return f.category.categoryName.length === depth + 1; })) {
// The hidden filter is for this category exactly. So, remove the category
return null;
}
if (category.categoryName == null) {
console.warn("HAIVE/search-client: Illegal category-object received. The categoryName array cannot be null. The category was not added to the category-tree.", category);
return null;
}
var categoryOverride = _this.settings.presentations[category.categoryName.join('|')];
if (categoryOverride) {
if (categoryOverride.group &&
categoryOverride.group.enabled &&
category.children.length >= categoryOverride.group.minCount) {
category.children = _this.grouping(categoryOverride, category.children);
}
if (categoryOverride.filter &&
categoryOverride.filter.enabled) {
category.children = _this.filtering(categoryOverride, category.children);
}
if (categoryOverride.sort && categoryOverride.sort.enabled) {
category.children = _this.sorting(categoryOverride, category.children);
}
if (categoryOverride.limit && categoryOverride.limit.enabled) {
category.children = _this.limiting(category.children, categoryOverride.limit);
}
if (categoryOverride.expanded !== null) {
category.expanded = categoryOverride.expanded;
}
}
if (category.children && category.children.length > 0) {
category.children = _this.mapCategories(category.children, hiddenFilters, depth++);
}
if ((!category.children || category.children.length === 0) &&
hiddenFiltersInCategory.length > 0) {
// If the category has no children, due to hidden filters, then remove the category itself
return null;
}
return category;
});
cats = cats.filter(function (c) { return c !== undefined && c !== null; });
return cats;
};
Categorize.prototype.grouping = function (categoryOverride, categories) {
var matchCategories = new Map();
var category2MatchCategory = new Map();
// Iterate, map and count to check whether or not to group results.
var isCategory;
for (var _i = 0, categories_1 = categories; _i < categories_1.length; _i++) {
var c = categories_1[_i];
var groupName = categoryOverride.group.getMatch(c.displayName);
if (!groupName) {
continue;
}
isCategory = c.hasOwnProperty('count');
var newNode = isCategory
? c
: {
categoryName: [c.name],
children: c.categories,
// We are really not sure what the real count is, as the category-hits may or may not be referring to the same items
// -1 should indicate "do not show"
count: -1,
displayName: c.displayName,
expanded: c.expanded,
name: c.name,
};
if (!matchCategories.has(groupName)) {
matchCategories.set(groupName, [newNode]);
}
else {
var collection = matchCategories.get(groupName);
collection.push(newNode);
matchCategories.set(groupName, collection);
}
category2MatchCategory.set(c.displayName, groupName);
}
// Do actual re-mapping, if any
return categories
.map(function (c) {
var displayName = category2MatchCategory.get(c.displayName);
if (!displayName) {
// Done before
return undefined;
}
var matchCategory = matchCategories.get(displayName);
if (!matchCategory) {
return c;
}
if (matchCategory.length >=
categoryOverride.group.minCountPerGroup) {
var newCategory = {
name: "__" + displayName + "__",
children: matchCategory,
displayName: displayName,
expanded: true,
categoryName: ["__" + displayName + "__"],
};
if (isCategory) {
;
newCategory.count = -1;
}
matchCategory.forEach(function (i) {
category2MatchCategory.delete(i.displayName);
});
return newCategory;
}
else {
return c;
}
})
.filter(function (c) { return c !== undefined && c !== null; });
};
Categorize.prototype.filtering = function (override, groups) {
return groups.filter(function (g) {
if (override.filter.match) {
var matchName = override.filter.matchMode === MatchMode.DisplayName
? g.displayName
: g.name;
return override.filter.match.test(matchName);
}
});
};
Categorize.prototype.sorting = function (categoryOverride, categories) {
// 1. Create parts2group-map
var part2cats = new Map();
for (var _i = 0, _a = categoryOverride.sort.parts; _i < _a.length; _i++) {
var p = _a[_i];
part2cats.set(p, []);
}
var other = new Array();
var stringMatches = categoryOverride.sort.parts.filter(function (p) { return typeof p.match === 'string'; });
var regexMatches = categoryOverride.sort.parts.filter(function (p) { return typeof p.match === 'object'; });
for (var _b = 0, categories_2 = categories; _b < categories_2.length; _b++) {
var c = categories_2[_b];
var found = false;
for (var _c = 0, stringMatches_1 = stringMatches; _c < stringMatches_1.length; _c++) {
var stringPart = stringMatches_1[_c];
if (stringPart.match ===
(stringPart.matchMode === MatchMode.DisplayName
? c.displayName
: c.name)) {
var collection = part2cats.get(stringPart);
collection.push(c);
part2cats.set(stringPart, collection);
found = true;
break;
}
}
if (found) {
continue;
}
for (var _d = 0, regexMatches_1 = regexMatches; _d < regexMatches_1.length; _d++) {
var regexPart = regexMatches_1[_d];
if (regexPart.match.test(regexPart.matchMode === MatchMode.DisplayName
? c.displayName
: c.name)) {
var collection = part2cats.get(regexPart);
collection.push(c);
part2cats.set(regexPart, collection);
found = true;
break;
}
}
if (!found) {
other.push(c);
}
}
// 2. Sort each part
var sortedCats = new Array();
part2cats.forEach(function (cs, p) {
if (cs.length === 1) {
sortedCats = sortedCats.concat(cs);
return;
}
var res = new Array();
switch (p.sortMethod) {
case SortMethod.AlphaAsc:
res = cs.sort(function (a, b) {
var aVal = p.matchMode === MatchMode.DisplayName
? a.displayName
: a.name;
var bVal = p.matchMode === MatchMode.DisplayName
? b.displayName
: b.name;
return aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
});
break;
case SortMethod.AlphaDesc:
res = cs.sort(function (a, b) {
var aVal = p.matchMode === MatchMode.DisplayName
? a.displayName
: a.name;
var bVal = p.matchMode === MatchMode.DisplayName
? b.displayName
: b.name;
return aVal > bVal ? -1 : aVal < bVal ? 1 : 0;
});
break;
case SortMethod.CountAsc:
if (cs[0].hasOwnProperty('count')) {
res = cs.sort(function (a, b) {
return a.count < b.count
? -1
: a.count > b.count
? 1
: 0;
});
}
else {
res = cs;
}
break;
case SortMethod.CountDesc:
if (cs[0].hasOwnProperty('count')) {
res = cs.sort(function (a, b) {
return a.count > b.count
? -1
: a.count < b.count
? 1
: 0;
});
}
else {
res = cs;
}
break;
case SortMethod.Original:
default:
// Keep order unchanged
res = cs;
}
sortedCats = sortedCats.concat(res);
});
// Finally add any leftovers at the bottom
return sortedCats.concat(other);
};
Categorize.prototype.limiting = function (array, limit) {
var from = (limit.page - 1) * limit.pageSize;
var to = from + limit.pageSize;
return array.slice(from, to);
};
Categorize.prototype.getCategoryPathDisplayNameFromCategories = function (categoryName, categories) {
var result = [];
var path = categoryName.slice(0);
var catId = path.splice(0, 1)[0].toLowerCase();
var category = categories.find(function (c) { return c.name.toLowerCase() === catId; });
if (!category) {
return null;
}
result.push(category.displayName);
var res;
if (path.length > 0 && category.children.length === 0) {
return null;
}
if (category.children.length > 0 && path.length > 0) {
res = this.getCategoryPathDisplayNameFromCategories(path, category.children);
if (!res) {
return;
}
if (res.displayName && res.displayName.length > 0) {
result = result.concat(res.displayName);
}
}
return { displayName: result, ref: res ? res.ref : category };
};
return Categorize;
}(BaseCall));
export { Categorize };
//# sourceMappingURL=Categorize.js.map