coveo-search-ui-extensions
Version:
Small generic components to extend the functionality of Coveo's Search UI framework.
175 lines • 7.84 kB
JavaScript
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