mirror of
https://github.com/nasa/openmct.git
synced 2025-01-21 03:55:31 +00:00
[Search] Rewrite elasticsearch provider with prototype
Rewrite the elasticsearch provider to use prototypes and clean up the implementation. Now returns a modelResults object to keep it in line with the general search provider.
This commit is contained in:
parent
78e5c0143b
commit
a2fce8e56c
@ -24,16 +24,14 @@
|
||||
/**
|
||||
* Module defining ElasticSearchProvider. Created by shale on 07/16/2015.
|
||||
*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
define([
|
||||
|
||||
], function (
|
||||
|
||||
) {
|
||||
"use strict";
|
||||
|
||||
// JSLint doesn't like underscore-prefixed properties,
|
||||
// so hide them here.
|
||||
var ID = "_id",
|
||||
SCORE = "_score",
|
||||
DEFAULT_MAX_RESULTS = 100;
|
||||
var DEFAULT_MAX_RESULTS = 100;
|
||||
|
||||
/**
|
||||
* A search service which searches through domain objects in
|
||||
@ -53,161 +51,100 @@ define(
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches through the filetree for domain objects using a search
|
||||
* term. This is done through querying elasticsearch. Returns a
|
||||
* promise for a result object that has the format
|
||||
* {hits: searchResult[], total: number, timedOut: boolean}
|
||||
* where a searchResult has the format
|
||||
* {id: string, object: domainObject, score: number}
|
||||
* Search for domain objects using elasticsearch as a search provider.
|
||||
*
|
||||
* Notes:
|
||||
* * The order of the results is from highest to lowest score,
|
||||
* as elsaticsearch determines them to be.
|
||||
* * Uses the fuzziness operator to get more results.
|
||||
* * More about this search's behavior at
|
||||
* https://www.elastic.co/guide/en/elasticsearch/reference/current/search-uri-request.html
|
||||
*
|
||||
* @param searchTerm The text input that is the query.
|
||||
* @param timestamp The time at which this function was called.
|
||||
* This timestamp is used as a unique identifier for this
|
||||
* query and the corresponding results.
|
||||
* @param maxResults (optional) The maximum number of results
|
||||
* that this function should return.
|
||||
* @param timeout (optional) The time after which the search should
|
||||
* stop calculations and return partial results. Elasticsearch
|
||||
* does not guarentee that this timeout will be strictly followed.
|
||||
* @param {String} searchTerm the term to search by.
|
||||
* @param {Number} [maxResults] the max numer of results to return.
|
||||
* @returns {Promise} promise for a modelResults object.
|
||||
*/
|
||||
ElasticSearchProvider.prototype.query = function query(searchTerm, timestamp, maxResults, timeout) {
|
||||
var $http = this.$http,
|
||||
objectService = this.objectService,
|
||||
root = this.root,
|
||||
esQuery;
|
||||
ElasticSearchProvider.prototype.query = function (searchTerm, maxResults) {
|
||||
var searchUrl = this.root + '/_search/',
|
||||
params = {},
|
||||
provider = this;
|
||||
|
||||
function addFuzziness(searchTerm, editDistance) {
|
||||
if (!editDistance) {
|
||||
editDistance = '';
|
||||
}
|
||||
|
||||
return searchTerm.split(' ').map(function (s) {
|
||||
// Don't add fuzziness for quoted strings
|
||||
if (s.indexOf('"') !== -1) {
|
||||
return s;
|
||||
} else {
|
||||
return s + '~' + editDistance;
|
||||
}
|
||||
}).join(' ');
|
||||
}
|
||||
|
||||
// Currently specific to elasticsearch
|
||||
function processSearchTerm(searchTerm) {
|
||||
var spaceIndex;
|
||||
|
||||
// Cut out any extra spaces
|
||||
while (searchTerm.substr(0, 1) === ' ') {
|
||||
searchTerm = searchTerm.substring(1, searchTerm.length);
|
||||
}
|
||||
while (searchTerm.substr(searchTerm.length - 1, 1) === ' ') {
|
||||
searchTerm = searchTerm.substring(0, searchTerm.length - 1);
|
||||
}
|
||||
spaceIndex = searchTerm.indexOf(' ');
|
||||
while (spaceIndex !== -1) {
|
||||
searchTerm = searchTerm.substring(0, spaceIndex) +
|
||||
searchTerm.substring(spaceIndex + 1, searchTerm.length);
|
||||
spaceIndex = searchTerm.indexOf(' ');
|
||||
}
|
||||
|
||||
// Add fuzziness for completeness
|
||||
searchTerm = addFuzziness(searchTerm);
|
||||
|
||||
return searchTerm;
|
||||
}
|
||||
|
||||
// Processes results from the format that elasticsearch returns to
|
||||
// a list of searchResult objects, then returns a result object
|
||||
// (See documentation for query for object descriptions)
|
||||
function processResults(rawResults, timestamp) {
|
||||
var results = rawResults.data.hits.hits,
|
||||
resultsLength = results.length,
|
||||
ids = [],
|
||||
scores = {},
|
||||
searchResults = [],
|
||||
i;
|
||||
|
||||
// Get the result objects' IDs
|
||||
for (i = 0; i < resultsLength; i += 1) {
|
||||
ids.push(results[i][ID]);
|
||||
}
|
||||
|
||||
// Get the result objects' scores
|
||||
for (i = 0; i < resultsLength; i += 1) {
|
||||
scores[ids[i]] = results[i][SCORE];
|
||||
}
|
||||
|
||||
// Get the domain objects from their IDs
|
||||
return objectService.getObjects(ids).then(function (objects) {
|
||||
var j,
|
||||
id;
|
||||
|
||||
for (j = 0; j < resultsLength; j += 1) {
|
||||
id = ids[j];
|
||||
|
||||
// Include items we can get models for
|
||||
if (objects[id].getModel) {
|
||||
// Format the results as searchResult objects
|
||||
searchResults.push({
|
||||
id: id,
|
||||
object: objects[id],
|
||||
score: scores[id]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
hits: searchResults,
|
||||
total: rawResults.data.hits.total,
|
||||
timedOut: rawResults.data.timed_out
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Check to see if the user provided a maximum
|
||||
// number of results to display
|
||||
if (!maxResults) {
|
||||
// Else, we provide a default value.
|
||||
maxResults = DEFAULT_MAX_RESULTS;
|
||||
}
|
||||
|
||||
// If the user input is empty, we want to have no search results.
|
||||
if (searchTerm !== '' && searchTerm !== undefined) {
|
||||
// Process the search term
|
||||
searchTerm = processSearchTerm(searchTerm);
|
||||
searchTerm = this.cleanTerm(searchTerm);
|
||||
searchTerm = this.fuzzyMatchUnquotedTerms(searchTerm);
|
||||
|
||||
// Create the query to elasticsearch
|
||||
esQuery = root + "/_search/?q=" + searchTerm +
|
||||
"&size=" + maxResults;
|
||||
if (timeout) {
|
||||
esQuery += "&timeout=" + timeout;
|
||||
}
|
||||
params.q = searchTerm;
|
||||
params.size = maxResults;
|
||||
|
||||
// Get the data...
|
||||
return this.$http({
|
||||
return this
|
||||
.$http({
|
||||
method: "GET",
|
||||
url: esQuery
|
||||
}).then(function (rawResults) {
|
||||
// ...then process the data
|
||||
return processResults(rawResults, timestamp);
|
||||
}, function (err) {
|
||||
// In case of error, return nothing. (To prevent
|
||||
// infinite loading time.)
|
||||
return {hits: [], total: 0};
|
||||
url: searchUrl,
|
||||
params: params
|
||||
})
|
||||
.then(function success(succesResponse) {
|
||||
return provider.parseResponse(succesResponse);
|
||||
}, function error(errorResponse) {
|
||||
// Gracefully fail.
|
||||
return {
|
||||
hits: [],
|
||||
total: 0
|
||||
};
|
||||
});
|
||||
} else {
|
||||
return {hits: [], total: 0};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clean excess whitespace from a search term and return the cleaned
|
||||
* version.
|
||||
*
|
||||
* @private
|
||||
* @param {string} the search term to clean.
|
||||
* @returns {string} search terms cleaned of excess whitespace.
|
||||
*/
|
||||
ElasticSearchProvider.prototype.cleanTerm = function (term) {
|
||||
return term.trim().replace(/ +/, ' ');
|
||||
};
|
||||
|
||||
/**
|
||||
* Add fuzzy matching markup to search terms that are not quoted.
|
||||
*
|
||||
* The following:
|
||||
* hello welcome "to quoted village" have fun
|
||||
* will become
|
||||
* hello~ welcome~ "to quoted village" have~ fun~
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
ElasticSearchProvider.prototype.fuzzyMatchUnquotedTerms = function (query) {
|
||||
var matchUnquotedSpaces = '\\s+(?=([^"]*"[^"]*")*[^"]*$)',
|
||||
matcher = new RegExp(matchUnquotedSpaces, 'g');
|
||||
|
||||
return query
|
||||
.replace(matcher, '~ ')
|
||||
.replace('"~', '"');
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse the response from ElasticSearch and convert it to a
|
||||
* modelResults object.
|
||||
*
|
||||
* @private
|
||||
* @param response a ES response object from $http
|
||||
* @returns modelResults
|
||||
*/
|
||||
ElasticSearchProvider.prototype.parseResponse = function (response) {
|
||||
var results = response.data.hits.hits,
|
||||
searchResults = results.map(function (result) {
|
||||
return {
|
||||
id: result['_id'],
|
||||
model: result['_source'],
|
||||
score: result['_score']
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
hits: searchResults,
|
||||
total: response.data.hits.total,
|
||||
timedOut: response.data.timed_out
|
||||
};
|
||||
};
|
||||
|
||||
return ElasticSearchProvider;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user