UNPKG

coveo-search-ui

Version:

Coveo JavaScript Search Framework

552 lines (515 loc) 22.5 kB
import * as _ from 'underscore'; import { ICategoryFacetRequest } from '../../rest/CategoryFacetRequest'; import { IFacetRequest } from '../../rest/Facet/FacetRequest'; import { IGroupByRequest } from '../../rest/GroupByRequest'; import { IFacetOptions, IQuery, IUserActionsRequest, ICommerceRequest } from '../../rest/Query'; import { IQueryFunction } from '../../rest/QueryFunction'; import { IRankingFunction } from '../../rest/RankingFunction'; import { Utils } from '../../utils/Utils'; import { ExpressionBuilder } from './ExpressionBuilder'; import { QueryBuilderExpression } from './QueryBuilderExpression'; /** * The QueryBuilder is used to build a {@link IQuery} that will be able to be executed using the Search API. * * The class exposes several members and methods that help components and external code to build up the final query that is sent to the Search API. * */ export class QueryBuilder { /** * Used to build the basic part of the query expression. * * This part typically consists of user-entered content such as query keywords, etc. * @type {Coveo.ExpressionBuilder} */ public expression: ExpressionBuilder = new ExpressionBuilder(); /** * Used to build the advanced part of the query expression. * * This part is typically formed of filter expressions generated by components such as facets, external code, etc. * @type {Coveo.ExpressionBuilder} */ public advancedExpression: ExpressionBuilder = new ExpressionBuilder(); /** * Used to build the advanced part of the query expression. * * This part is similar to `advancedExpression`, but its content is interpreted as a constant expression by the index and it takes advantage of special caching features. * @type {Coveo.ExpressionBuilder} */ public constantExpression: ExpressionBuilder = new ExpressionBuilder(); /** * The contextual text. * * This is the contextual text part of the query. It uses the Coveo Machine Learning service to pick key keywords from the text and add them to the basic expression. * This field is mainly used to pass context such a case description, long textual query or any other form of text that might help in * refining the query. */ public longQueryExpression: ExpressionBuilder = new ExpressionBuilder(); /** * Used to build the disjunctive part of the query expression. * * When present, this part is evaluated separately from the other expressions and the matching results are merged to those matching expressions, `advancedExpression` and `constantExpression`. * * The final boolean expression for the query is thus (basic advanced constant) OR (disjunction). * @type {Coveo.ExpressionBuilder} */ public disjunctionExpression: ExpressionBuilder = new ExpressionBuilder(); /** * The hub value set from the {@link Analytics} component. * * Used for analytics reporting in the Coveo platform. */ public searchHub: string; /** * The tab value set from the {@link Tab} component. */ public tab: string; public locale: string; /** * Name of the query pipeline to use. * * Specifies the name of the query pipeline to use for the query. If not specified, the default value is default, which means the default query pipeline will be used. */ public pipeline: string; /** * The maximum age for cached query results, in milliseconds. * * If results for the exact same request (including user identities) are available in the in-memory cache, they will be used if they are not older than the specified value. * * Otherwise, the query will be sent to the index. */ public maximumAge: number; /** * Whether to interpret wildcard characters (`*`) in the basic [`expression`]{@link QueryBuilder.expression} keywords. * * Setting this attribute to `true` enables the wildcards features of the index, effectively expanding keywords * containing wildcard characters (`*`) to the possible matching keywords in order to broaden the query (see * [Using Wildcards in Queries](https://docs.coveo.com/en/1580/)). * * See also [`enableQuestionMarks`]{@link QueryBuilder.enableQuestionMarks}. * * **Note:** * > Normally, the [`enableWildcards`]{@link Querybox.options.enableWildcards} option of the * > [`Querybox`]{@link Querybox} component determines the value of this attribute during the initialization of the * > search page. */ public enableWildcards: boolean; /** * Whether to interpret question mark characters (`?`) in the basic [`expression`]{@link QueryBuilder.expression} * keywords (see [Using Wildcards in Queries](https://docs.coveo.com/en/1580/). * * Setting this attribute to `true` has no effect unless [`enableWildcards`]{@link QueryBuilder.enableWildcards} is * also `true`. * * **Note:** * > Normally, the [`enableQuestionMarks`]{@link Querybox.options.enableQuestionMarks} option of the * > [`Querybox`]{@link Querybox} component determines the value of this attribute during the initialization of the * > search page. */ public enableQuestionMarks: boolean; /** * Whether to interpret special query syntax (e.g., `@objecttype=message`) in the basic * [`expression`]{@link QueryBuilder.expression} (see * [Coveo Query Syntax Reference](https://docs.coveo.com/en/1552/searching-with-coveo/coveo-cloud-query-syntax)). * * See also [`enableLowercaseOperators`]{@link QueryBuilder.enableLowercaseOperators}. * * **Note:** * > Normally, the [`enableQuerySyntax`]{@link Querybox.options.enableQuerySyntax} option of the * > [`Querybox`]{@link Querybox} component determines the value of this attribute during the initialization of the * search page. End user preferences can also modify the value of this attribute. * * Default value is `false` */ public enableQuerySyntax: boolean = false; /** * Whether to interpret the `AND`, `NOT`, `OR`, and `NEAR` keywords in the basic * [`expression`]{@link QueryBuilder.expression} as query operators, even if those keywords are in lowercase. * * Setting this attribute to `true` has no effect unless [`enableQuerySyntax`]{@link QueryBuilder.enableQuerySyntax} * is also `true`. * * **Note:** * > Normally, the [`enableLowercaseOperators`]{@link Querybox.options.enableLowercaseOperators} option of the * > [`Querybox`]{@link Querybox} component determines the value of this attribute during the initialization of the * > search page. */ public enableLowercaseOperators: boolean; /** * Whether to automatically convert the basic [`expression`]{@link QueryBuilder.expression} to a partial match * expression if it contains at least a certain number of keywords (see * [`partialMatchKeywords`]{@link QueryBuilder.partialMatchKeywords}), so that items containing at least a certain * subset of those keywords (see [`partialMatchThreshold`]{@link QueryBuilder.partialMatchThreshold}) will match the * query. * * **Note:** * > Normally, the [`enablePartialMatch`]{@link Querybox.options.enablePartialMatch} option of the * > [`Querybox`]{@link Querybox} component determines the value of this attribute during the initialization of the * > search page. */ public enablePartialMatch: boolean; /** * The minimum number of keywords that need to be present in the basic [`expression`]{@link QueryBuilder.expression} * to convert it to a partial match expression. * * The value of this attribute has no meaning unless [`enablePartialMatch`]{@link QueryBuilder.enablePartialMatch} is * `true`. * * See also [`partialMatchThreshold`]{@link QueryBuilder.partialMatchThreshold}. * * **Note:** * > Normally, the [`partialMatchKeywords`]{@link Querybox.options.partialMatchKeywords} option of the * > [`Querybox`]{@link Querybox} component determines the value of this attribute during the initialization of the * > search page. */ public partialMatchKeywords: number; /** * An absolute or relative (percentage) value indicating the minimum number of partial match expression keywords an * item must contain to match the query. * * The value of this attribute has no meaning unless [`enablePartialMatch`]{@link QueryBuilder.enablePartialMatch} is * `true`. * * See also [`partialMatchKeywords`]{@link QueryBuilder.partialMatchKeywords}. * * **Note:** * > Normally, the [`partialMatchThreshold`]{@link Querybox.options.partialMatchThreshold} option of the * > [`Querybox`]{@link Querybox} component determines the value of this attribute during the initialization of the * > search page. */ public partialMatchThreshold: string; /** * This is the 0-based index of the first result to return. * * If not specified, this parameter defaults to 0. */ public firstResult: number = 0; /** * This is the number of results to return, starting from {@link IQuery.firstResult}. * * If not specified, this parameter defaults to 10. */ public numberOfResults: number = 10; /** * Specifies the length (in number of characters) of the excerpts generated by the indexer based on the keywords present in the query. * * The index includes the top most interesting sentences (in the order they appear in the item) that fit in the specified number of characters. * * When not specified, the default value is 200. */ public excerptLength: number; /** * Specifies a field on which {@link Folding} should be performed. * * Folding is a kind of duplicate filtering where only the first result with any given value of the field is included in the result set. * * It's typically used to return only one result in a conversation, for example when forum posts in a thread are indexed as separate items. */ public filterField: string; /** * Number of results that should be folded, using the {@link IQuery.filterField}. */ public filterFieldRange: number; /** * Specifies the `parentField` when doing parent-child loading (See: {@link Folding}). */ public parentField: string; /** * Specifies the childField when doing parent-child loading (See: {@link Folding}). */ public childField: string; public fieldsToInclude: string[]; public requiredFields: string[] = []; public includeRequiredFields: boolean = false; public fieldsToExclude: string[]; /** * Whether to enable query corrections on this query (see {@link DidYouMean}). */ public enableDidYouMean: boolean = false; /** * Whether to enable debug info on the query. * * This will return additional information on the resulting JSON response from the Search API. * * Mostly: execution report (a detailed breakdown of the parsed and executed query). */ public enableDebug: boolean = false; /** * **Note:** * * > The Coveo Cloud V2 platform does not support collaborative rating. Therefore, this property is obsolete in Coveo Cloud V2. * * Whether the index should take collaborative rating in account when ranking result (See: {@link ResultRating}). */ public enableCollaborativeRating: boolean; /** * Specifies the sort criterion(s) to use to sort results. If not specified, this parameter defaults to relevancy. * * Possible values are : <br/> * -- relevancy : This uses all the configured ranking weights as well as any specified ranking expressions to rank results.<br/> * -- dateascending / datedescending Sort using the value of the `@date` field, which is typically the last modification date of an item in the index.<br/> * -- qre : Sort using only the weights applied through ranking expressions. This is much like using `relevancy` except that automatic weights based on keyword proximity etc, are not computed.<br/> * -- nosort : Do not sort the results. The order in which items are returned is essentially random.<br/> * -- @field ascending / @field descending Sort using the value of a custom field. */ public sortCriteria: string = 'relevancy'; /** * @deprecated */ public sortField: string; public retrieveFirstSentences: boolean = true; public timezone: string; public queryUid: string; /** * Specifies an array of Query Function operation that will be executed on the results. */ public queryFunctions: IQueryFunction[] = []; /** * Specifies an array of Ranking Function operations that will be executed on the results. */ public rankingFunctions: IRankingFunction[] = []; /** * Specifies an array of Group By operations that can be performed on the query results to extract facets. * Cannot be used alongside [`facetRequests`]{@link QueryBuilder.facetRequests} */ public groupByRequests: IGroupByRequest[] = []; /** * Specifies an array of request for the DynamicFacet component. * Cannot be used alongside [`groupByRequests`]{@link QueryBuilder.groupByRequests} */ public facetRequests: IFacetRequest[] = []; /** * The global configuration options to apply to the requests in the [facets]{@link QueryBuilder.facets} array. */ public facetOptions: IFacetOptions = {}; /** * Specifies an array of request for the CategoryFacet component. */ public categoryFacets: ICategoryFacetRequest[] = []; public enableDuplicateFiltering: boolean = false; /** * The custom context information to send along with the query. Each value should be a string or an array of strings. * * Custom context information can influence the output of Coveo Machine Learning models and can also be used in query pipeline conditions. */ public context: { [key: string]: any }; /** * The actions history represents the past actions a user made and is used by the Coveo Machine Learning service to suggest recommendations. * It is generated by the page view script (https://github.com/coveo/coveo.analytics.js). */ public actionsHistory: string; /** * This is the ID of the recommendation interface that generated the query. */ public recommendation: string; /** * Specifies if the Search API should perform queries even when no keywords were entered by the end user. * * End user keywords are present in either the {@link IQuery.q} or {@link IQuery.lq} part of the query. * * This parameter is normally controlled by {@link SearchInterface.options.allowEmptyQuery} option. */ public allowQueriesWithoutKeywords: boolean; /** * A request to retrieve user actions in the query response. */ public userActions: IUserActionsRequest; /** * A request for a commerce query. */ public commerce: ICommerceRequest; /** * Build the current content or state of the query builder and return a {@link IQuery}. * * build can be called multiple times on the same QueryBuilder. * @returns {IQuery} */ public build(): IQuery { const query: IQuery = { q: this.expression.build(), aq: this.advancedExpression.build(), cq: this.constantExpression.build(), lq: this.longQueryExpression.build(), dq: this.disjunctionExpression.build(), searchHub: this.searchHub, tab: this.tab, locale: this.locale, pipeline: this.pipeline, maximumAge: this.maximumAge, wildcards: this.enableWildcards, questionMark: this.enableQuestionMarks, lowercaseOperators: this.enableLowercaseOperators, partialMatch: this.enablePartialMatch, partialMatchKeywords: this.partialMatchKeywords, partialMatchThreshold: this.partialMatchThreshold, firstResult: this.firstResult, numberOfResults: this.numberOfResults, excerptLength: this.excerptLength, filterField: this.filterField, filterFieldRange: this.filterFieldRange, parentField: this.parentField, childField: this.childField, fieldsToInclude: this.computeFieldsToInclude(), fieldsToExclude: this.fieldsToExclude, enableDidYouMean: this.enableDidYouMean, sortCriteria: this.sortCriteria, sortField: this.sortField, queryFunctions: this.queryFunctions, rankingFunctions: this.rankingFunctions, groupBy: this.groupBy, facets: this.facets, facetOptions: this.facetOptions, categoryFacets: this.categoryFacets, retrieveFirstSentences: this.retrieveFirstSentences, timezone: this.timezone, enableQuerySyntax: this.enableQuerySyntax, enableDuplicateFiltering: this.enableDuplicateFiltering, enableCollaborativeRating: this.enableCollaborativeRating, debug: this.enableDebug, context: this.context, actionsHistory: this.actionsHistory, recommendation: this.recommendation, allowQueriesWithoutKeywords: this.allowQueriesWithoutKeywords, userActions: this.userActions, commerce: this.commerce }; return query; } /** * Return only the expression(s) part(s) of the query, as a string. * * This means the basic, advanced and constant part in a complete expression {@link IQuery.q}, {@link IQuery.aq}, {@link IQuery.cq}. * @returns {string} */ public computeCompleteExpression(): string { return this.computeCompleteExpressionParts().full; } /** * Return only the expression(s) part(s) of the query, as an object. * @returns {{full: string, withoutConstant: string, constant: string}} */ public computeCompleteExpressionParts(): QueryBuilderExpression { return new QueryBuilderExpression( this.expression.build(), this.advancedExpression.build(), this.constantExpression.build(), this.disjunctionExpression.build() ); } /** * Return only the expression(s) part(s) of the query, as a string, except the given expression. * * This is used by {@link Facet}, to build their group by request with query override. * @param except * @returns {string} */ public computeCompleteExpressionExcept(except: string): string { return this.computeCompleteExpressionPartsExcept(except).full; } /** * Return only the expression(s) part(s) of the query, as an object, except the given expression. * * This is used by {@link Facet}, to build their group by request with query override. * @param except * @returns {{full: string, withoutConstant: string, constant: string}} */ public computeCompleteExpressionPartsExcept(except: string): QueryBuilderExpression { const withoutConstantAndExcept = ExpressionBuilder.merge(this.expression, this.advancedExpression); withoutConstantAndExcept.remove(except); const basicWithoutException = new ExpressionBuilder(); basicWithoutException.fromExpressionBuilder(this.expression); basicWithoutException.remove(except); const advancedWithoutException = new ExpressionBuilder(); advancedWithoutException.fromExpressionBuilder(this.advancedExpression); advancedWithoutException.remove(except); const constantWithoutException = new ExpressionBuilder(); constantWithoutException.fromExpressionBuilder(this.constantExpression); constantWithoutException.remove(except); const disjunctionWithoutException = new ExpressionBuilder(); disjunctionWithoutException.fromExpressionBuilder(this.disjunctionExpression); disjunctionWithoutException.remove(except); return new QueryBuilderExpression( basicWithoutException.build(), advancedWithoutException.build(), constantWithoutException.build(), disjunctionWithoutException.build() ); } /** * Add fields to specifically include when the results return. * * This can be used to accelerate the execution time of every query, as there is much less data to process if you whitelist specific fields. * @param fields */ public addFieldsToInclude(fields: string[]) { this.fieldsToInclude = _.uniq((this.fieldsToInclude || []).concat(fields)); } public addRequiredFields(fields: string[]) { this.requiredFields = _.uniq(this.requiredFields.concat(fields)); } /** * Add fields to specifically exclude when the results return. * * This can be used to accelerate the execution time of every query, as there is much less data to process if you blacklist specific fields. * @param fields */ public addFieldsToExclude(fields: string[]) { this.fieldsToExclude = _.uniq((this.fieldsToInclude || []).concat(fields)); } public computeFieldsToInclude() { if (this.includeRequiredFields || this.fieldsToInclude != null) { return _.uniq(this.requiredFields.concat(this.fieldsToInclude || [])); } else { return null; } } /** * Adds or updates a single context key-value pair in the `context` object. * * @param key The context key. If this key is already present in the `context` object, its value is updated. * @param value The context value. This should be a string or an array of strings. */ public addContextValue(key: string, value: any) { if (this.context == null) { this.context = {}; } this.context[key] = value; } /** * Merges the specified `values` into the `context` object. * * @param values The object to merge into the `context` object. Can contain multiple key-value pairs, where each value should be a string or an array of strings. If some keys are already present in the `context` object, their values are updated. */ public addContext(values: { [key: string]: any }) { if (this.context == null) { this.context = {}; } _.extend(this.context, values); } /** * Returns true if the current query contains any expression that are considered "end user input". * * This usually means anything entered in the basic (see [q]{@link IQuery.options.q}) or long (see [lq]{@link IQuery.options.lq}) part of the query. */ public containsEndUserKeywords(): boolean { const query = this.build(); return Utils.isNonEmptyString(query.q) || Utils.isNonEmptyString(query.lq); } private get groupBy() { if (Utils.isEmptyArray(this.groupByRequests)) { return undefined; } return this.groupByRequests; } private set groupBy(groupBy: IGroupByRequest[]) { this.groupByRequests = groupBy; } private get facets() { if (Utils.isEmptyArray(this.facetRequests)) { return undefined; } return this.facetRequests; } private set facets(facets: IFacetRequest[]) { this.facetRequests = facets; } }