labo-components
Version:
231 lines (205 loc) • 9.72 kB
JavaScript
import PropTypes from 'prop-types';
import IDUtil from '../util/IDUtil';
import TimeUtil from "../util/TimeUtil";
import RegexUtil from '../util/RegexUtil';
export default class Query {
constructor(id, searchId, term, collectionId, searchLayers, nestedSearchLayers, dateRange, fieldCategory, selectedFacets,
desiredFacets, entities, sort, offset, size, exclude, fragmentPath=null, fragmentFields=null, includeFragmentChildren=false,
includeMediaObjects=null, innerHitsOffset=0, innerHitsSize=5, highlightFields=null, storeAfterExecution=true) {
this.id = id;
this.searchId = searchId;
this.term = term;
this.collectionId = collectionId;
this.searchLayers = searchLayers; // deprecated, used for multi index search
this.nestedSearchLayers = nestedSearchLayers; // used for searching within nested documents
this.dateRange = dateRange;
this.fieldCategory = fieldCategory;
this.selectedFacets = selectedFacets;
this.desiredFacets = desiredFacets;
this.entities = entities;
this.sort = sort;
this.offset = offset;
this.size = size;
this.exclude = exclude;
//generated based on the search term
this.searchRegex = RegexUtil.generateRegexForSearchTerm(term); // store the search term regexp here for convenience/performance
//properties for fragment search (based on nested documents)
this.fragmentPath = fragmentPath;
this.fragmentFields = fragmentFields;
this.includeFragmentChildren = includeFragmentChildren;
this.includeMediaObjects = includeMediaObjects;
this.innerHitsOffset = innerHitsOffset;
this.innerHitsSize = innerHitsSize;
this.highlightFields = highlightFields;
this.storeAfterExecution = storeAfterExecution; //caches the results in localstorage (see SearchAPI)
}
//when a collectionConfig is provided, it means that defaults from this should be used to populate the query object
static construct(obj, collectionConfig=null, storeAfterExecution=true) {
obj = obj || {};
const term = obj.term || '';
return new Query(
obj.id || IDUtil.guid(), // used as a GUID
obj.searchId || null, // used to differentiate different executions of this query
term, // the search term entered by the user
(collectionConfig ? collectionConfig.getSearchIndex() : obj.collectionId) || null, // the collection being queried
Query.determineSearchLayers(obj, collectionConfig), // what layers to include
obj.nestedSearchLayers || (collectionConfig ? collectionConfig.getNestedSearchLayers() : null), // what nested search layers to include
obj.dateRange || null, // the selected date field and range (start/end dates)
obj.fieldCategory || null, // for field-specific search; can be defined by overriding CollectionConfig.getMetadataFieldCategories()
obj.selectedFacets || {}, // filters selected by the user (by selecting certain values from the desiredFacets)
obj.desiredFacets || Query.getInitialDesiredFacets(obj, collectionConfig), //which aggregations should be included next to the search results
obj.entities || [],
obj.sort && obj.sort.field ? obj.sort : { "field": "_score", "order": "desc"}, // direction & field to sort on
obj.offset || 0, // for paging
obj.size || 20, // for paging
obj.exclude || (collectionConfig ? collectionConfig.getFieldsToExclude() : null), // remove certain fields from the returned data
//for fragment search only
obj.fragmentPath || (collectionConfig ? collectionConfig.getFragmentPath() : null), // path to the desired nested document, e.g. document.page.paragraph
obj.fragmentFields || (collectionConfig ? collectionConfig.getFragmentTextFields() : null), // which fields of the indicated nested document to retrieve
typeof(obj.includeFragmentChildren) === 'boolean' ? obj.includeFragmentChildren : (collectionConfig && collectionConfig.includeFragmentChildren()), // return sub fragments y/n
typeof(obj.includeMediaObjects) === 'boolean' ? obj.includeMediaObjects : (collectionConfig && collectionConfig.includeMediaObjects(term)), // decide whether to search/retrieve the document level as well
//paging within inner hits is not really supported/reflected by the UI (yet)
obj.innerHitsOffset || 0,
obj.innerHitsSize || 5,
//new highlight feature TODO finish & test
obj.highlightFields || (collectionConfig ? collectionConfig.getHighlightFields() : null),
storeAfterExecution //whether to store the query results right after execution (in local storage)
);
}
static determineSearchLayers(query, config) {
let searchLayers = null;
let foundLayer = false;
if(config && config.getCollectionIndices()) {
searchLayers = {};
config.getCollectionIndices().forEach(layer => {
if(query && query.searchLayers) {
if(query.searchLayers[layer] !== undefined) {
searchLayers[layer] = query.searchLayers[layer];
foundLayer = true;
} else {
searchLayers[layer] = false;
}
} else { //include all default layers
searchLayers[layer] = true;
foundLayer = true;
}
});
}
//if for some shitty reason the search layer (entered in the URL) does not match the collection ID
//just set it manually (maybe this is being too nice)
if(!foundLayer) {
searchLayers = {};
searchLayers[config ? config.getCollectionId() : query.collectionId] = true;
}
return searchLayers;
}
static getInitialDesiredFacets(query, config) {
let df = config ? config.getFacets() : null || [];
if(query.dateRange && query.dateRange.field) {
df.push({
field: query.dateRange.field,
title : config.toPrettyFieldName(query.dateRange.field),
id : query.dateRange.field,
type : 'date_histogram'
});
}
return df;
}
toHumanReadableString() {
const strList = [];
if(this.term) {
strList.push('Search term: ' + this.term);
} else {
strList.push('No search term');
}
if(this.selectedFacets && Object.keys(this.selectedFacets).length > 0) {
strList.push('# filters: ' + Object.keys(this.selectedFacets).length);
}
if(this.entities && Object.keys(this.entities).length > 0) {
strList.push('# entities: ' + Object.keys(this.entities).length);
}
return strList.join('; ');
}
/* --------------------------------- FOR COPYING A QUERY TO THE CLIPBOARD ---------------------------------- */
queryDetailsToClipboard(queryName) {
const queryDetailsHeader = "Query details\r\r";
const namePart = "Name: " + queryName + "\r";
const dateFieldName = this.dateRange && this.dateRange.field
? " Name: " + this.dateRange.field + "\n" : "";
const startDate = this.dateRange && this.dateRange.start
? " Start: " + TimeUtil.UNIXTimeToPrettyDate(this.dateRange.start) + "\r" : "";
const endDate = this.dateRange && this.dateRange.end
? " End: " + TimeUtil.UNIXTimeToPrettyDate(this.dateRange.end) + "\r" : "";
const date = dateFieldName || startDate || endDate
? "Date Field: \r" + dateFieldName + startDate + endDate + "\r"
: "";
const searchTerm = this.term ? "Search Term: " + this.term + "\r" : "";
const selectedFacets = this.selectedFacets ? this.__getSelectedFacetsToClipboard() : "";
const selectedEntities = this.entities ? this.__getSelectedEntitiesToClipboard() : "";
const fieldCategory = this.fieldCategory && this.fieldCategory.length > 0 ? this.__getFieldsCategoryToClipboard() : "";
return queryDetailsHeader + namePart + searchTerm + date + fieldCategory + selectedFacets + selectedEntities;
}
__getSelectedFacetsToClipboard() {
if (this.selectedFacets && Object.keys(this.selectedFacets).length > 0) {
let text = "Selected facets\r";
const keys = Object.keys(this.selectedFacets);
keys.forEach(k => {
text += "Facet name: " + k + " \r";
this.selectedFacets[k].map(facet => {
text += " " + facet + "\r";
});
});
text += "\r";
return text;
}
return ''
}
__getFieldsCategoryToClipboard() {
if (this.fieldCategory) {
let text = "Selected field categories\r";
this.fieldCategory.forEach(item => text += " " + item.label + "\r")
text += "\r";
return text;
}
return ''
}
__getSelectedEntitiesToClipboard() {
if (this.entities && this.entities.length > 0) {
let text = "Selected entities:\r";
this.entities.forEach(entity => {
text += entity.label + " (" + entity.type + ")" + " \r";
});
text += "\r";
return text;
}
return ''
}
static getPropTypes(isRequired=true) {
const queryShape = PropTypes.shape({
id : PropTypes.string,
searchId : PropTypes.string,
term : PropTypes.string,
collectionId : PropTypes.string,
searchLayers : PropTypes.object,
nestedSearchLayers : PropTypes.arrayOf(PropTypes.object),
dateRange : PropTypes.object,
fieldCategory : PropTypes.arrayOf(PropTypes.object),
selectedFacets : PropTypes.object,
desiredFacets : PropTypes.arrayOf(PropTypes.object),
entities : PropTypes.arrayOf(PropTypes.object),
sort : PropTypes.object,
offset : PropTypes.number,
size : PropTypes.number,
exclude : PropTypes.arrayOf(PropTypes.string),
searchRegex : PropTypes.any,
fragmentPath : PropTypes.string,
fragmentFields : PropTypes.array,
includeFragmentChildren : PropTypes.bool,
includeMediaObjects : PropTypes.bool,
innerHitsOffset : PropTypes.number,
innerHitsSize : PropTypes.number,
highlightFields : PropTypes.arrayOf(PropTypes.string)
});
return isRequired ? queryShape.isRequired : queryShape;
}
}