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.
|
* Module defining ElasticSearchProvider. Created by shale on 07/16/2015.
|
||||||
*/
|
*/
|
||||||
define(
|
define([
|
||||||
[],
|
|
||||||
function () {
|
], function (
|
||||||
|
|
||||||
|
) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
// JSLint doesn't like underscore-prefixed properties,
|
var DEFAULT_MAX_RESULTS = 100;
|
||||||
// so hide them here.
|
|
||||||
var ID = "_id",
|
|
||||||
SCORE = "_score",
|
|
||||||
DEFAULT_MAX_RESULTS = 100;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A search service which searches through domain objects in
|
* A search service which searches through domain objects in
|
||||||
@ -53,161 +51,100 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Searches through the filetree for domain objects using a search
|
* Search for domain objects using elasticsearch as a search provider.
|
||||||
* 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}
|
|
||||||
*
|
*
|
||||||
* Notes:
|
* @param {String} searchTerm the term to search by.
|
||||||
* * The order of the results is from highest to lowest score,
|
* @param {Number} [maxResults] the max numer of results to return.
|
||||||
* as elsaticsearch determines them to be.
|
* @returns {Promise} promise for a modelResults object.
|
||||||
* * 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.
|
|
||||||
*/
|
*/
|
||||||
ElasticSearchProvider.prototype.query = function query(searchTerm, timestamp, maxResults, timeout) {
|
ElasticSearchProvider.prototype.query = function (searchTerm, maxResults) {
|
||||||
var $http = this.$http,
|
var searchUrl = this.root + '/_search/',
|
||||||
objectService = this.objectService,
|
params = {},
|
||||||
root = this.root,
|
provider = this;
|
||||||
esQuery;
|
|
||||||
|
|
||||||
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) {
|
if (!maxResults) {
|
||||||
// Else, we provide a default value.
|
|
||||||
maxResults = DEFAULT_MAX_RESULTS;
|
maxResults = DEFAULT_MAX_RESULTS;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the user input is empty, we want to have no search results.
|
searchTerm = this.cleanTerm(searchTerm);
|
||||||
if (searchTerm !== '' && searchTerm !== undefined) {
|
searchTerm = this.fuzzyMatchUnquotedTerms(searchTerm);
|
||||||
// Process the search term
|
|
||||||
searchTerm = processSearchTerm(searchTerm);
|
|
||||||
|
|
||||||
// Create the query to elasticsearch
|
params.q = searchTerm;
|
||||||
esQuery = root + "/_search/?q=" + searchTerm +
|
params.size = maxResults;
|
||||||
"&size=" + maxResults;
|
|
||||||
if (timeout) {
|
|
||||||
esQuery += "&timeout=" + timeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the data...
|
return this
|
||||||
return this.$http({
|
.$http({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: esQuery
|
url: searchUrl,
|
||||||
}).then(function (rawResults) {
|
params: params
|
||||||
// ...then process the data
|
})
|
||||||
return processResults(rawResults, timestamp);
|
.then(function success(succesResponse) {
|
||||||
}, function (err) {
|
return provider.parseResponse(succesResponse);
|
||||||
// In case of error, return nothing. (To prevent
|
}, function error(errorResponse) {
|
||||||
// infinite loading time.)
|
// Gracefully fail.
|
||||||
return {hits: [], total: 0};
|
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;
|
return ElasticSearchProvider;
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
Loading…
Reference in New Issue
Block a user