UNPKG

coveo-search-ui-extensions

Version:

Small generic components to extend the functionality of Coveo's Search UI framework.

175 lines 7.84 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { Model, QueryBuilder, Assert, Component, SearchInterface, } from 'coveo-search-ui'; import { UserActionEvents } from '../components/UserActions/Events'; import { UserProfilingEndpoint, UserActionType } from '../rest/UserProfilingEndpoint'; export class UserActionSession { constructor(timestamp, actions, expanded = false) { this.timestamp = timestamp; this.actions = actions; this.expanded = expanded; } } /** * Represent an action that a user has made. */ export class UserAction { constructor(type, timestamp, raw, document, query) { this.type = type; this.timestamp = timestamp; this.raw = raw; this.document = document; this.query = query; } } /** * Model that store each user profile informations such as actions made by them, */ export class UserProfileModel extends Model { /** * Create a `UserProfileModel` and bound it to `element`. * Also create a `UserProfilingEndpoint` that will be use to fetch actions made by a user. * * @param element An element on which the model will be bound on. * @param options A set of options necessary for the component creation. */ constructor(element, options) { super(element, UserProfileModel.ID, {}); this.options = options; Assert.isNotUndefined(this.options.restUri); Assert.isNotUndefined(this.options.organizationId); Assert.isNotUndefined(this.options.searchEndpoint); this.getOrFetchCache = {}; this.endpoint = new UserProfilingEndpoint({ uri: this.options.restUri, accessToken: this.options.accessToken || this.options.searchEndpoint.accessToken, organization: this.options.organizationId, }); } /** * Get all actions related to a user. * * @param userId The identifier of a user. */ getActions(userId) { return __awaiter(this, void 0, void 0, function* () { let actions = this.get(userId); actions = Array.isArray(actions) ? actions : yield this.fetchActions(userId); this.set(userId, actions, UserProfileModel.MODEL_CONFIG); return actions; }); } /** * Delete all actions related to a user from the model. * * @param userId The identifier of a user. */ deleteActions(userId) { this.set(userId, undefined, UserProfileModel.MODEL_CONFIG); this.getOrFetchCache[userId] = undefined; } fetchActions(userId) { const pendingFetch = this.getOrFetchCache[userId]; const doFetch = () => { this.getOrFetchCache[userId] = this.endpoint.getActions(userId).then((actions) => this.parseGetActionsResponse(userId, actions)); return this.getOrFetchCache[userId]; }; return pendingFetch || doFetch(); } parseGetActionsResponse(userId, actions) { const userActions = this.buildUserActions(actions); this.registerNewAttribute(userId, userActions); return userActions; } fetchDocuments(urihashes) { return __awaiter(this, void 0, void 0, function* () { if (urihashes.length === 0) { return Promise.resolve({}); } const builder = new QueryBuilder(); builder.advancedExpression.addFieldExpression('@urihash', '==', urihashes.filter((x) => x)); // Ensure we fetch the good amount of document. builder.numberOfResults = urihashes.length; // Here we directly use the Search Endpoint to query without side effects. const query = builder.build(); const searchRequest = yield this.options.searchEndpoint.search(query); // Here we directly send the event using the Analytics Endpoint to prevent any unwanted side effects. this.sendUserActionLoad(query, searchRequest); const documentsDict = searchRequest.results.reduce((acc, result) => (Object.assign(Object.assign({}, acc), { [result.raw.urihash]: result })), {}); return documentsDict; }); } buildUserActions(actions) { return __awaiter(this, void 0, void 0, function* () { let documents = {}; const urihashes = actions .filter(this.isClick) .map((action) => action.value.uri_hash) // Remove duplicates. .filter((value, index, list) => list.indexOf(value) === index); try { documents = yield this.fetchDocuments(urihashes); } catch (error) { console.log(error); this.logger.error(UserProfileModel.ERROR_MESSAGE.FETCH_CLICKED_DOCUMENT_FAIL, error); } return actions.map((action) => { return new UserAction(action.name, new Date(action.time), action.value, this.isClickOrView(action) ? documents[action.value.uri_hash] : undefined, this.isSearch(action) ? action.value.query_expression : undefined); }); }); } isClick(action) { return action.name === UserActionType.Click; } isClickOrView(action) { return this.isClick(action) || action.name === UserActionType.PageView; } isSearch(action) { return action.name === UserActionType.Search; } sendUserActionLoad(query, result) { var _a, _b, _c; const uaClient = (_a = Component.get(this.element, SearchInterface, true)) === null || _a === void 0 ? void 0 : _a.usageAnalytics; uaClient === null || uaClient === void 0 ? void 0 : uaClient.logSearchEvent(UserActionEvents.load, {}); uaClient === null || uaClient === void 0 ? void 0 : uaClient.endpoint.sendSearchEvents([ Object.assign(Object.assign({}, uaClient.getPendingSearchEvent().templateSearchEvent), { queryPipeline: result.pipeline, splitTestRunName: result.splitTestRun, splitTestRunVersion: result.splitTestRun ? result.pipeline : undefined, queryText: (_b = query.q) !== null && _b !== void 0 ? _b : '', advancedQuery: (_c = query.aq) !== null && _c !== void 0 ? _c : '', didYouMean: query.enableDidYouMean, numberOfResults: result.totalCount, responseTime: result.duration, pageNumber: query.firstResult / query.numberOfResults, resultsPerPage: query.numberOfResults, searchQueryUid: result.searchUid, contextual: false, }), ]); } } /** * Identifier of the Search-UI component. */ UserProfileModel.ID = 'UserProfileModel'; UserProfileModel.ERROR_MESSAGE = Object.freeze({ FETCH_CLICKED_DOCUMENT_FAIL: 'Fetching clicked documents details failed', }); UserProfileModel.MODEL_CONFIG = { customAttribute: true, silent: true, }; /** * Expose the UserProfileModel so a user action implementation can use it. */ window['Coveo'] && (window['Coveo']['UserProfileModel'] = UserProfileModel); //# sourceMappingURL=UserProfileModel.js.map