UNPKG

search-client

Version:

Javascript library for executing searches in the Haive search-index via the SearchManager REST interface.

659 lines 29.6 kB
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