@attivio/suit
Version:
Attivio SUIT, the Search UI Toolkit, is a library for creating search clients for searching the Attivio platform.
1,031 lines (877 loc) • 35.6 kB
JavaScript
var _class, _temp;
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
import React from 'react';
import { withRouter } from 'react-router-dom';
import PropTypes from 'prop-types';
import QueryString from 'query-string';
import Search from '../api/Search';
import SimpleQueryRequest from '../api/SimpleQueryRequest';
import QueryResponse from '../api/QueryResponse';
import FacetFilter from '../api/FacetFilter';
import FieldNames from '../api/FieldNames';
import SignalData from '../api/SignalData';
import Signals from '../api/Signals';
import AuthUtils from '../util/AuthUtils';
import ObjectUtils from '../util/ObjectUtils';
import Configurable from '../components/Configurable';
import Configuration from '../components/Configuration';
/*
A NOTE ABOUT THE SEARCHER, THE PAGE'S URL, AND WHEN QUERYING HAPPENS:
When the Searcher is first loaded, we check for query parameters and apply them if they exist.
In this case, we need to do a search right away.
When the Searcher is updated with a new query string, we parse it and possibly do a new search,
if it has changed.
When the user does a search manually, we need to calculate the query string and push the new
location onto the router's history if it has changed. (This happens in the method doSearch().)
When the user updates a property that affects existing searches but doesn't require resetting,
we update the state and then, if there has been a previous search, perform a new one (and, only
in this case, update the search string).
When the user updates a property that affects existing searches and does require resetting,
then we update the state including setting the offset to 0, and, if there has been a previous
search, we perform a new one (and, only in this case, update the search string). The following
properties require resetting when they're changed: geoFilters (adding or removing), resultsPerPage,
facetFilters (adding or removing), sort, relevancyModels, debug, and searchProfile.
*/
/*
* NOTE: If you add or remove anything from the Searcher's state, you'll
* need to update (at least) the following methods to accommodate the change:
* constructor()
* getQueryRequest()
* generateLocationQueryStringFromState()
* parseLocationQueryStringToState()
* reset()
* relevantStateDiffers()
* getDefaultState()
*/
/**
* A wrapper for an Attivio search. Child components can access this object using
* the searcher property that is inserted into their context object. This allows them
* to access the Searcher's state to see all of its input parameters aa well as the
* results of the most recent search and any errors. In addition, they can use the
* reference to the Searcher to call methods which allow them to update the Searcher's
* state or execute searches.
*
* The Searcher also provides a method, doCustomSearch(), that lets the callers
* query the index using the configured Search class but providing their own request
* object, without affecting the Searcher's state.
*
* See the SearchResults component for an example
* of how this is done using by defining "static contextTypes" in the component.
*
* Note that the Searcher will add query parameters to the URL for the page's location
* when the user executes a (non-custom) search. This allows the URL for the search to be
* used to repeat the same search, either when refreshing the browser window or when
* bookmarking the page, sharing it in an email, etc. The URL is updated whenever a search
* happens, whether caused by the user clicking the search button or by changing the
* parameters to an existing search (e.g., changing the sort order or paging through the
* results).
*
* IF
* Searcher is first loaded, we need to check for query parameters and apply them if they
* exist.In this case, we need to do the search.
*
* IF
* Searcher is updated with a new query string, then we need to parse it and possibly do
* a new search, if it has changed.
*
* IF
* User does a search manually, we need to calculate the query string and push the new
* location onto the history if it has changed.
* THIS HAPPENS IN THE doSearch() method
*
* IF
* User updates a property that affects existing searches but doesn't require resetting,
* we need to update the state and then, if there's a previous search, perform a new one
* (and, only in this case, update the search string
* THIS HAPPENS WHEN THESE PROPERTIES CHANGE:
* resultsOffset (i.e., paging)
*
* IF
* User updates a property that affects existing searches AND requires resetting, then we
* need to update the state including setting the offset to 0, and , if there's a previous
* search, perform a new one (and, only in this case, update the search string
* THIS HAPPENS WHEN THESE PROPERTIES CHANGE:
* geoFilters (adding or removing)
* resultsPerPage
* facetFilters (adding or removing)
* sort
* relevancyModels
* debug
* searchProfile
*/
var Searcher = (_temp = _class = function (_React$Component) {
_inherits(Searcher, _React$Component);
/**
* Convert an array of facet filters to an array of string representations thereof.
*/
Searcher.serializeFacetFilters = function serializeFacetFilters(facetFilters) {
return facetFilters.map(function (facetFilter) {
return facetFilter.facetName + ',+' + facetFilter.bucketLabel + ',+' + facetFilter.filter;
});
};
/**
* Convert an array of stringified facet filters to an array of actual FacetFilter objects.
*/
Searcher.deserializeFacetFilters = function deserializeFacetFilters(facetFilters) {
return facetFilters.map(function (facetFilterString) {
var parts = facetFilterString.split(',+');
var facetFilter = new FacetFilter();
facetFilter.facetName = parts[0];
facetFilter.bucketLabel = parts[1];
facetFilter.filter = parts[2];
return facetFilter;
});
};
function Searcher(props) {
_classCallCheck(this, Searcher);
var _this = _possibleConstructorReturn(this, _React$Component.call(this, props));
_this.getDefaultQuerySignal = function () {
var createNewIfNotExisting = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
var queryDocuments = _this.state.response ? _this.state.response.documents : null;
var querySignal = queryDocuments && queryDocuments.length >= 1 ? queryDocuments[0].signal : null;
if (querySignal) {
if (!querySignal.relevancyModelVersion) {
querySignal.relevancyModelVersion = 1;
}
return querySignal;
}
if (createNewIfNotExisting) {
var savedUser = AuthUtils.getSavedUser();
var defaultSignalData = new SignalData();
var _this$state = _this.state,
_query = _this$state.query,
_queryTimestamp = _this$state.queryTimestamp;
var _relevancyModels = _this.getRelevancyModels();
if (savedUser) {
defaultSignalData.locale = 'en';
defaultSignalData.principal = AuthUtils.config.ALL.defaultRealm + ':' + savedUser.fullName + ':' + savedUser.userId;
defaultSignalData.query = _query;
defaultSignalData.queryTimestamp = _queryTimestamp;
defaultSignalData.relevancyModelName = _relevancyModels[0] || '';
defaultSignalData.relevancyModelNames = _relevancyModels.length > 0 ? _relevancyModels : [];
defaultSignalData.relevancyModelVersion = 1;
}
return defaultSignalData;
}
return null;
};
_this.addPromotionSignal = function (signalDocID, signalDocOrdinal) {
var querySignal = _this.getDefaultQuerySignal(true);
if (!querySignal) {
return;
}
var signal = querySignal.clone();
signal.docId = signalDocID;
signal.docOrdinal = signalDocOrdinal;
signal.featureVector = '';
signal.signalTimestamp = Date.now();
signal.type = 'promotion';
signal.weight = 1;
new Signals(_this.props.baseUri).addRawSignal(signal);
};
_this.search = new Search(_this.props.baseUri, _this.props.searchEngineType, _this.props.customOptions);
_this.state = _this.getDefaultState();
_this.updateSearchResults = _this.updateSearchResults.bind(_this);
return _this;
}
Searcher.prototype.getChildContext = function getChildContext() {
return {
searcher: this
};
};
Searcher.prototype.componentDidMount = function componentDidMount() {
// When the searcher is first created, this is called.
// Pull a state object out of the location's query string
var location = this.props.location;
var newState = this.parseLocationQueryStringToState(location.search);
// We check to see if the state needs to be updated due to this
if (this.relevantStateDiffers(newState)) {
this.updateStateAndSearch(newState);
}
};
Searcher.prototype.componentWillReceiveProps = function componentWillReceiveProps(nextProps) {
// When the searcher gets updated to have a new set of props, then this is called.
var location = nextProps.location;
// Pull a state object out of the location's query string
var newState = this.parseLocationQueryStringToState(location.search);
// We check to see if the state needs to be updated due to this
if (this.relevantStateDiffers(newState)) {
this.updateStateAndSearch(newState);
}
};
/**
* Method to get the default state for the Searcher. This ism in a
* separate method since it needs to be done both in the constructor
* and in the reset method.
*/
Searcher.prototype.getDefaultState = function getDefaultState() {
return {
haveSearched: false,
response: undefined,
error: undefined,
query: Searcher.EVERYTHING,
queryLanguage: this.props.defaultQueryLanguage,
sort: ['.score:DESC'],
relevancyModels: this.props.relevancyModels,
facetFilters: [],
geoFilters: [],
resultsPerPage: parseInt(this.props.resultsPerPage, 10),
resultsOffset: 0,
debug: this.props.debug,
queryTimestamp: 0
};
};
Searcher.prototype.getRelevancyModels = function getRelevancyModels() {
if (this.state.relevancyModels && this.state.relevancyModels.length > 0) {
return this.state.relevancyModels;
} else if (this.props.relevancyModels && this.props.relevancyModels.length > 0) {
return this.props.relevancyModels;
}
return [];
};
/**
* Use the properties of the Searcher component and the values from its
* current state to generate a query request object that will be passed
* to the Search class to do the work.
*/
Searcher.prototype.getQueryRequest = function getQueryRequest() {
var qr = new SimpleQueryRequest();
qr.workflow = this.props.searchWorkflow;
qr.query = this.state.query;
qr.queryLanguage = this.state.queryLanguage;
qr.rows = this.state.resultsPerPage;
if (this.state.geoFilters) {
qr.filters = this.state.geoFilters;
} else {
qr.filters = [];
}
if (this.props.queryFilter) {
qr.filters.push(this.props.queryFilter);
}
if (this.props.locale) {
qr.locale = this.props.locale;
}
qr.facets = this.props.facets;
qr.sort = this.state.sort;
qr.fields = this.getFieldList();
qr.facetFilters = this.state.facetFilters;
// And now, the fields that don't have explicit counterparts
// in the simple query request, which need to be set using
// the restParams property.
var restParams = new Map();
restParams.set('offset', ['' + this.state.resultsOffset]);
var relevancyModels = this.getRelevancyModels();
restParams.set('relevancymodelnames', [relevancyModels.join(',')]);
restParams.set('includemetadatainresponse', ['true']);
if (this.props.highlightResults) {
restParams.set('highlight', ['true']);
restParams.set('highlight.mode', ['HTML']);
}
if (this.props.facetFinderCount > 0) {
restParams.set('facet.ff', ['RESULTS']);
restParams.set('facet.ffcount', [this.props.facetFinderCount.toString(10)]);
}
restParams.set('join.rollup', [this.props.joinRollupMode]);
if (this.props.businessCenterProfile) {
var profiles = [this.props.businessCenterProfile];
restParams.set('abc.enabled', ['true']);
restParams.set('searchProfile', profiles);
}
restParams.set('q.maxresubmits', ['' + this.props.maxResubmits]);
qr.restParams = restParams;
return qr;
};
/**
* Get the list of fields to use in the query request.
*/
Searcher.prototype.getFieldList = function getFieldList() {
// Start out with the fields the user specified
var result = [].concat(this.props.fields || []);
// Add the mapped fields that the search results will expect
result.push(this.props.title + ' as title');
result.push(this.props.uri + ' as uri');
result.push(this.props.table + ' as table');
result.push(this.props.teaser + ' as teaser');
result.push(this.props.text + ' as text');
result.push(this.props.previewImageUri + ' as previewImageUri');
result.push(this.props.thumbnailImageUri + ' as thumbnailImageUri');
result.push(this.props.latitude + ' as latitude');
result.push(this.props.longitude + ' as longitude');
result.push(this.props.moreLikeThisQuery + ' as morelikethisquery');
result.push(this.props.mimetype + ' as mimetype');
result.push(this.props.sourcePath + ' as sourcepath');
// Add the fields we always want
result.push('tags');
return result;
};
/**
* Set the query string to the passed-in value and trigger the
* query immediately, resetting parameters to the beginning.
* This is similar to performQueryImmediately() except that the
* current value of the queryLanguage is preserved.
*/
Searcher.prototype.setQueryAndSearch = function setQueryAndSearch(query) {
this.updateStateResetAndSearch({
haveSearched: true, // Force it to update right now
error: undefined,
response: undefined,
facetFilters: [],
geoFilters: [],
query: query
});
};
/**
* Fetches the signal data from the first document of the response.
* If the document or the signal does not exist, and createNewIfNotExisting = true,
* a new SignalData object is created with default values populated.
*/
/**
* Check to see if the old and new state differ, only comparing the
* properties we care about (non-transient ones).
*/
Searcher.prototype.relevantStateDiffers = function relevantStateDiffers(compareWith) {
// Get a copy of the state with transient values removed
var currentState = Object.assign({}, this.state);
delete currentState.error;
delete currentState.response;
delete currentState.haveSearched;
delete currentState.queryTimestamp;
var newState = Object.assign({}, compareWith);
delete newState.error;
delete newState.response;
delete newState.haveSearched;
delete newState.queryTimestamp;
return !ObjectUtils.deepEquals(currentState, newState);
};
/**
* Given a SearcherState object, generate the serialized URL query string parameters
* that represent that state. If the optional original queryString parameter is passed,
* then any non-SearcherState parameters encoded in it will be added to the resulting
* query string.
*/
Searcher.prototype.generateLocationQueryStringFromState = function generateLocationQueryStringFromState(state, originalQueryString) {
var basicState = {};
if (!(state.query === '*' || state.query === '*:*')) {
basicState.query = state.query;
}
if (state.queryLanguage !== this.props.defaultQueryLanguage) {
basicState.queryLanguage = state.queryLanguage;
}
if (state.geoFilters && state.geoFilters.length > 0) {
basicState.geoFilters = state.geoFilters;
}
if (state.resultsPerPage !== this.props.resultsPerPage) {
basicState.resultsPerPage = state.resultsPerPage;
}
if (state.resultsOffset !== 0) {
basicState.resultsOffset = state.resultsOffset;
}
if (state.facetFilters && state.facetFilters.length > 0) {
basicState.facetFilters = Searcher.serializeFacetFilters(state.facetFilters);
}
if (state.sort) {
// LJV TODO compare with default version
basicState.sort = state.sort;
}
if (state.relevancyModels && state.relevancyModels.length > 0) {
basicState.relevancyModels = state.relevancyModels;
}
if (state.debug !== this.props.debug) {
basicState.debug = state.debug;
}
// See if there are any query parameters other than those set by the Searcher. If so, we want to maintain them.
if (originalQueryString) {
var originalParsed = QueryString.parse(originalQueryString);
if (originalParsed) {
originalParsed.delete('query');
originalParsed.delete('queryLanguage');
originalParsed.delete('geoFilters');
originalParsed.delete('resultsPerPage');
originalParsed.delete('resultsOffset');
originalParsed.delete('facetFilters');
originalParsed.delete('sort');
originalParsed.delete('relevancyModels');
originalParsed.delete('debug');
}
// Add any leftover fields back in to the basic state
basicState = Object.assign({}, basicState, originalParsed);
}
return QueryString.stringify(basicState);
};
/**
* Given the query string from the location URL, parse it into the values of a SearcherState
* object. Values which are missing are set to their default values. Any values in the
* queryString which don't apply to the SearcherState are ignored.
*/
Searcher.prototype.parseLocationQueryStringToState = function parseLocationQueryStringToState(queryString) {
var parsed = QueryString.parse(queryString);
// Get the query string
// DEFAULT: *:*
var query = parsed.query ? parsed.query : '*:*';
// Get the query language (and validate that it's one of 'simple' or 'advanced')
// DEFAULT: this.props.defaultQueryLanguage
var queryLanguage = this.props.defaultQueryLanguage;
if (parsed.queryLanguage === 'simple' || parsed.queryLanguage === 'advanced') {
queryLanguage = parsed.queryLanguage;
}
// Get the geoFilters (normalized to an array of strings)
// DEFAULT: []
var geoFilters = parsed.geoFilters;
if (!geoFilters) {
geoFilters = [];
} else if (typeof geoFilters === 'string') {
geoFilters = [geoFilters];
}
// Get the number of results per page (as a positive integer)
// DEFAULT: this.props.resultsPerPage¸
var resultsPerPage = void 0;
if (parsed.resultsPerPage) {
resultsPerPage = parseInt(parsed.resultsPerPage, 10);
}
if (!resultsPerPage || resultsPerPage <= 0) {
resultsPerPage = this.props.resultsPerPage;
}
// Get the offset into the search results (as a positive integer or zero)
// DEFAULT: 0
var resultsOffset = void 0;
if (parsed.resultsOffset) {
resultsOffset = parseInt(parsed.resultsOffset, 10);
}
if (!resultsOffset || resultsOffset < 0) {
resultsOffset = 0;
}
// Get the facet filters (normalized to an array of FacetFilter objects
// DEFAULT: []
var facetFiltersStrings = [];
if (parsed.facetFilters) {
// Wrap single strings in an array
if (typeof parsed.facetFilters === 'string') {
facetFiltersStrings = [parsed.facetFilters];
} else {
facetFiltersStrings = parsed.facetFilters;
}
}
// Deserialize the strings to get FacetFilter objects
var facetFilters = Searcher.deserializeFacetFilters(facetFiltersStrings);
// Get the sort order
// DEFAULT: '.score:DESC'
var sort = void 0;
if (typeof parsed.sort === 'string') {
// LJV TODO Validate the sort column and direction
sort = parsed.sort;
}
if (!sort) {
sort = '.score:DESC';
}
// Get the relevancy models to use.
// DEFAULT: []
var relevancyModels = void 0;
if (parsed.relevancyModels) {
if (typeof parsed.relevancyModels === 'string') {
relevancyModels = [parsed.relevancyModels];
} else {
relevancyModels = parsed.relevancyModels;
}
}
if (!relevancyModels) {
relevancyModels = [];
}
// LJV TODO
// Get the business center profile to use.
// DEFAULT: none
// Determine if we're in debug mode.
// DEFAULT: this.props.format
var debug = this.props.debug;
if (Object.prototype.hasOwnProperty.call(parsed, 'debug')) {
debug = parsed.debug;
}
var result = {
query: query,
queryLanguage: queryLanguage,
geoFilters: geoFilters,
resultsPerPage: resultsPerPage,
resultsOffset: resultsOffset,
facetFilters: facetFilters,
sort: [sort],
relevancyModels: relevancyModels,
debug: debug,
haveSearched: this.state.haveSearched, // Make sure we don't change this
queryTimestamp: 0
};
return result;
};
/**
* Reset to the first page and then update the state, re-running the search if one had already been done.
*/
Searcher.prototype.updateStateResetAndSearch = function updateStateResetAndSearch(partialState) {
var newPartialState = Object.assign({}, partialState);
newPartialState.resultsOffset = 0;
this.updateStateAndSearch(newPartialState);
};
/**
* Update the state of the searcher and then re-run the search if one had already been done.
*/
Searcher.prototype.updateStateAndSearch = function updateStateAndSearch(partialState) {
var _this2 = this;
this.setState(partialState, function () {
if (_this2.state.haveSearched) {
_this2.doSearch();
}
});
};
/**
* Used to tell the search results component whether to use the debug
* format when rendering results.
*/
Searcher.prototype.updateDebug = function updateDebug(newDebug) {
if (this.state.debug !== newDebug) {
this.updateStateAndSearch({
debug: newDebug
});
}
};
/**
* Update the list of tags for the given document.
*/
Searcher.prototype.updateTags = function updateTags(tags, docId, onCompletion, onError) {
return this.search.updateRealtimeField(docId, FieldNames.TAGS, tags, onCompletion, onError);
};
/**
* Callback used when the search is completed. Will update the Searcher's state
* with the query response or the error string passed in.
*/
Searcher.prototype.updateSearchResults = function updateSearchResults(response, error) {
if (response) {
// Succeeded...
this.setState({
response: response,
error: undefined,
haveSearched: true,
queryTimestamp: Date.now()
});
} else if (error) {
// Failed!
this.setState({
response: undefined,
haveSearched: true,
error: error
});
}
};
/**
* Set whether the simple or advanced query language should be
* used to perform the search.
* This causes subsequent searches to be reset.
*/
Searcher.prototype.updateQueryLanguage = function updateQueryLanguage(queryLanguage) {
if (queryLanguage !== this.state.queryLanguage) {
this.updateStateResetAndSearch({
queryLanguage: queryLanguage
});
}
};
/**
* Update the query string to use for searching. Don't add this into the
* URL because we don't want the URL changing as the user types, only when
* the search button is clicked.
* This causes subsequent searches to be reset (see doSearch() for details).
*/
Searcher.prototype.updateQuery = function updateQuery(query) {
this.setState({
haveSearched: false,
query: query
});
};
/**
* Update the number of documents to show on a page.
* If there is a current search and the value has changed, the
* search will be repeated with the new value.
*/
Searcher.prototype.updateResultsPerPage = function updateResultsPerPage(newResultsPerPage) {
if (newResultsPerPage !== this.state.resultsPerPage) {
this.updateStateResetAndSearch({
resultsPerPage: newResultsPerPage
});
}
};
/**
* Call to change the relevancy model in use by the searcher.
* If there is a current search and the value has changed, the
* search will be repeated with the new value.
*/
Searcher.prototype.updateRelevancyModels = function updateRelevancyModels(newRelevancyModels) {
if (JSON.stringify(newRelevancyModels) !== JSON.stringify(this.state.relevancyModels)) {
this.updateStateResetAndSearch({
relevancyModels: newRelevancyModels
});
}
};
/**
* Update the searcher to use a new sort column. The value passed
* in should have the column name and sort direction, separated
* by a colon (direction is either ASC or DESC).
* If there is a current search and the value has changed, the
* search will be repeated with the new value.
* The search is reset to the first page when performed again.
*/
Searcher.prototype.updateSort = function updateSort(newSort) {
if (this.newSort !== this.state.sort) {
var _sort = this.state.sort;
if (_sort && _sort.length > 0) {
_sort[0] = newSort;
} else {
_sort = [newSort];
}
this.updateStateResetAndSearch({
sort: _sort
});
}
};
/**
* Trigger a new search.
*
* If the search has been reset by one of the other
* methods, then
* <ul>
* <li>The "haveSearched" flag is reset to false until the search completes</li>
* <li>The offset is reset to 0 to show the first page of results</li>
* <li>Any facet filters that were applied are cleared.</li>
* <li>Any response or error from a previous search are cleared.</li>
* </ul>
*/
Searcher.prototype.doSearch = function doSearch() {
var _this3 = this;
var qr = this.getQueryRequest();
this.search.search(qr, function (response, error) {
_this3.updateSearchResults(response, error);
// potentially do window.scrollTo(0, 0)?
// Update the URL if needed.
var oldQueryString = _this3.props.location.query;
var updatedQueryString = _this3.generateLocationQueryStringFromState(_this3.state, oldQueryString);
if (oldQueryString !== updatedQueryString) {
_this3.props.history.push('?' + updatedQueryString);
}
});
};
/**
* Add a query filter (in AQL) to the query request.
*/
Searcher.prototype.addGeoFilter = function addGeoFilter(filter) {
this.addGeoFilters([filter]);
};
/**
* Perform a custom search given a query request. Calls the updateResults callback
* and doesn't affect the state of the searcher itself.
*/
Searcher.prototype.doCustomSearch = function doCustomSearch(request, updateResults) {
this.search.search(request, updateResults);
};
/**
* Completely reset the searcher to its default state and call an
* optional callback when done.
*/
Searcher.prototype.reset = function reset() {
var _this4 = this;
var callback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : function () {};
this.setState(this.getDefaultState(), callback);
var callBackWrapper = function callBackWrapper() {
_this4.updateStateResetAndSearch(_this4.getDefaultState());
};
this.setState({
haveSearched: false,
response: undefined,
error: undefined
}, callBackWrapper);
};
/**
* Set the query string to the passed-in value and trigger the
* query immediately, resetting parameters to the beginning. The
* query is specified as either simple or advanced based on the
* value of the advanced flag (it's previous value is ignored).
*/
Searcher.prototype.performQueryImmediately = function performQueryImmediately(query) {
var advanced = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
this.updateStateResetAndSearch({
haveSearched: true, // Force it to update right now
error: undefined,
response: undefined,
queryLanguage: advanced ? 'advanced' : 'simple',
facetFilters: [],
geoFilters: [],
query: query
});
};
/**
* Add multiple query filters (in AQL) to the query request.
* When DrawControl (in MapFacetContents) is re-enabled,
* ensure signal of type 'facet' is created when applying
* geofilters to the search.
* See PLAT-44214 for details of signals of type 'facet'.
*/
Searcher.prototype.addGeoFilters = function addGeoFilters(filters) {
var geoFilters = this.state.geoFilters.slice();
geoFilters = geoFilters.concat(filters);
this.updateStateResetAndSearch({
geoFilters: geoFilters
});
};
/**
* Remove a query filter by name (in AQL) from the query request.
* When DrawControl (in MapFacetContents) is re-enabled,
* ensure signal of type 'facet' is created when removing
* geofilters from the search.
* See PLAT-44214 for details of signals of type 'facet'.
*/
Searcher.prototype.removeGeoFilter = function removeGeoFilter(filter) {
var geoFilters = this.state.geoFilters.slice();
var index = geoFilters.indexOf(filter);
if (index !== -1) {
geoFilters.splice(index, 1);
}
this.updateStateResetAndSearch({
geoFilters: geoFilters
});
};
/**
* Add a facet filter to the current search. Will repeat the search
* if it's already been performed. Note that if a filter for the
* same facet name already exists, it will add the new filter
* instead of replacing.
*/
Searcher.prototype.addFacetFilter = function addFacetFilter(facetName, bucketLabel, filter) {
var updatedFacetFilters = this.state.facetFilters ? this.state.facetFilters : [];
var newFF = new FacetFilter();
newFF.facetName = facetName;
newFF.bucketLabel = bucketLabel;
newFF.filter = filter;
this.addFacetFilterSignal(newFF, true);
updatedFacetFilters.push(newFF);
this.updateStateResetAndSearch({
facetFilters: updatedFacetFilters
});
};
/**
* Remove the specified facet filter from the current
* search. If a search has already been performed, it
* will be repeated with the updated set of facet filters,
* unless repeatSearch is set to false the search will not
* be repeated.
*/
Searcher.prototype.removeFacetFilter = function removeFacetFilter(removeFilter) {
var repeatSearch = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
var updatedFacetFilters = [];
var facetFilters = this.state.facetFilters;
this.addFacetFilterSignal(removeFilter, false);
facetFilters.forEach(function (facetFilter) {
if (facetFilter.filter !== removeFilter.filter) {
updatedFacetFilters.push(facetFilter);
}
});
if (repeatSearch) {
this.updateStateResetAndSearch({
facetFilters: updatedFacetFilters
});
} else {
this.setState({
facetFilters: updatedFacetFilters
});
}
};
/**
* Add signal each time a filter is added/removed.
* When a filter is added, signal with weight 1 is created.
* When a filter is removed, signal with weight 0 is created.
*/
Searcher.prototype.addFacetFilterSignal = function addFacetFilterSignal(facetFilter, facetAdded) {
var querySignal = this.getDefaultQuerySignal();
var facets = this.state.response ? this.state.response.facets : null;
if (!querySignal || !facets) {
return;
}
var facet = facets.find(function (searchFacet) {
return searchFacet.label === facetFilter.facetName;
});
if (!facet) {
return;
}
// The signals for adding and removing the facet have the same docId and
// thus same signal is created for them. Thus, we add a suffix '|add' or '|remove'
// to denote whether the signal is for adding or removing facets and
// also to differentiate both the signals on backend.
var docIDSuffix = facetAdded ? '|add' : '|remove';
var signal = querySignal.clone();
signal.docId = facetFilter.filter.concat(docIDSuffix);
signal.docOrdinal = facet.buckets.findIndex(function (bucket) {
return bucket.filter === facetFilter.filter;
}) + 1; // index starts at 1
signal.featureVector = '';
signal.signalTimestamp = Date.now();
signal.type = 'facet';
signal.weight = facetAdded ? 1 : 0;
new Signals(this.props.baseUri).addRawSignal(signal);
};
/**
* Add signal each time a promotion is clicked.
*/
/**
* Navigate to a new page of search results. (Should only actually be
* called if a search has been completed.) The search will be performed
* again with the new page's offset.
*/
Searcher.prototype.changePage = function changePage(newPage) {
var resultsPerPage = this.state.resultsPerPage;
var oldOffset = this.state.resultsOffset;
var newOffset = resultsPerPage * newPage;
if (newOffset !== oldOffset) {
this.updateStateAndSearch({
resultsOffset: newOffset
});
}
};
Searcher.prototype.render = function render() {
// Nothing special to do here. The children will all look at our state to decide what to render
return React.createElement(
'div',
null,
this.props.children
);
};
return Searcher;
}(React.Component), _class.defaultProps = {
searchEngineType: 'attivio',
customOptions: {},
baseUri: '',
searchWorkflow: 'search',
fields: ['*'],
facets: [],
relevancyModels: ['default'],
facetFinderCount: 0,
queryFilter: null,
highlightResults: 'on',
joinRollupMode: 'TREE',
locale: null,
title: FieldNames.TITLE,
uri: FieldNames.URI,
table: FieldNames.TABLE,
teaser: 'SCOPETEASER(text, fragment=true, numFragments=4, fragmentScope=sentence)',
text: 'SCOPETEASER(text, fragment=true, numFragments=1, fragmentScope=2147483647)',
previewImageUri: 'img.uri.preview',
thumbnailImageUri: 'img.uri.thumbnail',
latitude: FieldNames.LATITUDE,
longitude: FieldNames.LONGITUDE,
moreLikeThisQuery: 'morelikethisquery',
mimetype: FieldNames.MIME_TYPE,
sourcePath: FieldNames.SOURCEPATH,
debug: false,
resultsPerPage: 10,
businessCenterProfile: null,
defaultQueryLanguage: 'simple',
maxResubmits: 1
}, _class.contextTypes = {
configuration: PropTypes.instanceOf(Configuration)
}, _class.childContextTypes = {
searcher: PropTypes.any
}, _class.displayName = 'Searcher', _class.EVERYTHING = '*:*', _temp);
export default withRouter(Configurable(Searcher));