Merge branch 'master' of https://github.com/nasa/openmctweb into search

Conflicts:
	platform/commonUI/general/src/controllers/TreeNodeController.js
	platform/persistence/elastic/src/ElasticSearchProvider.js
This commit is contained in:
slhale
2015-08-24 13:14:43 -07:00
277 changed files with 10693 additions and 9066 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "Couch Persistence",
"description": "Adapter to read and write objects using a CouchDB instance.",
"name": "ElasticSearch Persistence",
"description": "Adapter to read and write objects using an ElasticSearch instance.",
"extensions": {
"components": [
{

View File

@@ -46,71 +46,56 @@ define(
};
/**
* Indicator for the current CouchDB connection. Polls CouchDB
* at a regular interval (defined by bundle constants) to ensure
* that the database is available.
* Indicator for the current ElasticSearch connection. Polls
* ElasticSearch at a regular interval (defined by bundle constants)
* to ensure that the database is available.
* @constructor
* @memberof platform/persistence/elastic
* @implements {Indicator}
* @param $http Angular's $http service
* @param $interval Angular's $interval service
* @param {string} path the URL to poll for elasticsearch availability
* @param {number} interval the interval, in milliseconds, to poll at
*/
function ElasticIndicator($http, $interval, PATH, INTERVAL) {
function ElasticIndicator($http, $interval, path, interval) {
// Track the current connection state
var state = PENDING;
var self = this;
// Callback if the HTTP request to Couch fails
function handleError(err) {
state = DISCONNECTED;
this.state = PENDING;
// Callback if the HTTP request to ElasticSearch fails
function handleError() {
self.state = DISCONNECTED;
}
// Callback if the HTTP request succeeds.
function handleResponse(response) {
state = CONNECTED;
function handleResponse() {
self.state = CONNECTED;
}
// Try to connect to CouchDB, and update the indicator.
// Try to connect to ElasticSearch, and update the indicator.
function updateIndicator() {
$http.get(PATH).then(handleResponse, handleError);
$http.get(path).then(handleResponse, handleError);
}
// Update the indicator initially, and start polling.
updateIndicator();
$interval(updateIndicator, INTERVAL, false);
return {
/**
* Get the glyph (single character used as an icon)
* to display in this indicator. This will return "D",
* which should appear as a database icon.
* @returns {string} the character of the database icon
*/
getGlyph: function () {
return "D";
},
/**
* Get the name of the CSS class to apply to the glyph.
* This is used to color the glyph to match its
* state (one of ok, caution or err)
* @returns {string} the CSS class to apply to this glyph
*/
getGlyphClass: function () {
return state.glyphClass;
},
/**
* Get the text that should appear in the indicator.
* @returns {string} brief summary of connection status
*/
getText: function () {
return state.text;
},
/**
* Get a longer-form description of the current connection
* space, suitable for display in a tooltip
* @returns {string} longer summary of connection status
*/
getDescription: function () {
return state.description;
}
};
$interval(updateIndicator, interval, false);
}
ElasticIndicator.prototype.getGlyph = function () {
return "D";
};
ElasticIndicator.prototype.getGlyphClass = function () {
return this.state.glyphClass;
};
ElasticIndicator.prototype.getText = function () {
return this.state.text;
};
ElasticIndicator.prototype.getDescription = function () {
return this.state.description;
};
return ElasticIndicator;
}
);
);

View File

@@ -21,6 +21,11 @@
*****************************************************************************/
/*global define*/
/**
* This bundle implements a persistence service which uses ElasticSearch to
* store documents.
* @namespace platform/persistence/elastic
*/
define(
[],
function () {
@@ -37,164 +42,126 @@ define(
* The ElasticPersistenceProvider reads and writes JSON documents
* (more specifically, domain object models) to/from an ElasticSearch
* instance.
* @memberof platform/persistence/elastic
* @constructor
* @implements {PersistenceService}
* @param $http Angular's $http service
* @param $interval Angular's $interval service
* @param {string} space the name of the persistence space being served
* @param {string} root the root of the path to ElasticSearch
* @param {stirng} path the path to domain objects within ElasticSearch
*/
function ElasticPersistenceProvider($http, $q, SPACE, ROOT, PATH) {
var spaces = [ SPACE ],
revs = {};
function ElasticPersistenceProvider($http, $q, space, root, path) {
this.spaces = [ space ];
this.revs = {};
this.$http = $http;
this.$q = $q;
this.root = root;
this.path = path;
}
// Convert a subpath to a full path, suitable to pass
// to $http.
function url(subpath) {
return ROOT + '/' + PATH + '/' + subpath;
}
function bind(fn, thisArg) {
return function () {
return fn.apply(thisArg, arguments);
};
}
// Issue a request using $http; get back the plain JS object
// from the expected JSON response
function request(subpath, method, value, params) {
return $http({
method: method,
url: url(subpath),
params: params,
data: value
}).then(function (response) {
return response.data;
}, function (response) {
return (response || {}).data;
// Issue a request using $http; get back the plain JS object
// from the expected JSON response
ElasticPersistenceProvider.prototype.request = function (subpath, method, value, params) {
return this.$http({
method: method,
url: this.root + '/' + this.path + '/' + subpath,
params: params,
data: value
}).then(function (response) {
return response.data;
}, function (response) {
return (response || {}).data;
});
};
// Shorthand methods for GET/PUT methods
ElasticPersistenceProvider.prototype.get = function (subpath) {
return this.request(subpath, "GET");
};
ElasticPersistenceProvider.prototype.put = function (subpath, value, params) {
return this.request(subpath, "PUT", value, params);
};
ElasticPersistenceProvider.prototype.del = function (subpath) {
return this.request(subpath, "DELETE");
};
// Handle an update error
ElasticPersistenceProvider.prototype.handleError = function (response, key) {
var error = new Error("Persistence error."),
$q = this.$q;
if ((response || {}).status === CONFLICT) {
error.key = "revision";
// Load the updated model, then reject the promise
return this.get(key).then(function (response) {
error.model = response[SRC];
return $q.reject(error);
});
}
// Reject the promise
return this.$q.reject(error);
};
// Shorthand methods for GET/PUT methods
function get(subpath) {
return request(subpath, "GET");
// Get a domain object model out of ElasticSearch's response
function getModel(response) {
if (response && response[SRC]) {
this.revs[response[ID]] = response[REV];
return response[SRC];
} else {
return undefined;
}
function put(subpath, value, params) {
return request(subpath, "PUT", value, params);
}
function del(subpath) {
return request(subpath, "DELETE");
}
// Get a domain object model out of CouchDB's response
function getModel(response) {
if (response && response[SRC]) {
revs[response[ID]] = response[REV];
return response[SRC];
} else {
return undefined;
}
}
// Handle an update error
function handleError(response, key) {
var error = new Error("Persistence error.");
if ((response || {}).status === CONFLICT) {
error.key = "revision";
// Load the updated model, then reject the promise
return get(key).then(function (response) {
error.model = response[SRC];
return $q.reject(error);
});
}
// Reject the promise
return $q.reject(error);
}
// Check the response to a create/update/delete request;
// track the rev if it's valid, otherwise return false to
// indicate that the request failed.
function checkResponse(response, key) {
var error;
if (response && !response.error) {
revs[key] = response[REV];
return response;
} else {
return handleError(response, key);
}
}
return {
/**
* List all persistence spaces which this provider
* recognizes.
*
* @returns {Promise.<string[]>} a promise for a list of
* spaces supported by this provider
*/
listSpaces: function () {
return $q.when(spaces);
},
/**
* List all objects (by their identifiers) that are stored
* in the given persistence space, per this provider.
* @param {string} space the space to check
* @returns {Promise.<string[]>} a promise for the list of
* identifiers
*/
listObjects: function (space) {
return $q.when([]);
},
/**
* Create a new object in the specified persistence space.
* @param {string} space the space in which to store the object
* @param {string} key the identifier for the persisted object
* @param {object} value a JSONifiable object that should be
* stored and associated with the provided identifier
* @returns {Promise.<boolean>} a promise for an indication
* of the success (true) or failure (false) of this
* operation
*/
createObject: function (space, key, value) {
return put(key, value).then(checkResponse);
},
/**
* Read an existing object back from persistence.
* @param {string} space the space in which to look for
* the object
* @param {string} key the identifier for the persisted object
* @returns {Promise.<object>} a promise for the stored
* object; this will resolve to undefined if no such
* object is found.
*/
readObject: function (space, key) {
return get(key).then(getModel);
},
/**
* Update an existing object in the specified persistence space.
* @param {string} space the space in which to store the object
* @param {string} key the identifier for the persisted object
* @param {object} value a JSONifiable object that should be
* stored and associated with the provided identifier
* @returns {Promise.<boolean>} a promise for an indication
* of the success (true) or failure (false) of this
* operation
*/
updateObject: function (space, key, value) {
function checkUpdate(response) {
return checkResponse(response, key);
}
return put(key, value, { version: revs[key] })
.then(checkUpdate);
},
/**
* Delete an object in the specified persistence space.
* @param {string} space the space from which to delete this
* object
* @param {string} key the identifier of the persisted object
* @param {object} value a JSONifiable object that should be
* deleted
* @returns {Promise.<boolean>} a promise for an indication
* of the success (true) or failure (false) of this
* operation
*/
deleteObject: function (space, key, value) {
return del(key).then(checkResponse);
}
};
}
// Check the response to a create/update/delete request;
// track the rev if it's valid, otherwise return false to
// indicate that the request failed.
ElasticPersistenceProvider.prototype.checkResponse = function (response, key) {
if (response && !response.error) {
this.revs[key] = response[REV];
return response;
} else {
return this.handleError(response, key);
}
};
// Public API
ElasticPersistenceProvider.prototype.listSpaces = function () {
return this.$q.when(this.spaces);
};
ElasticPersistenceProvider.prototype.listObjects = function () {
// Not yet implemented
return this.$q.when([]);
};
ElasticPersistenceProvider.prototype.createObject = function (space, key, value) {
return this.put(key, value).then(bind(this.checkResponse, this));
};
ElasticPersistenceProvider.prototype.readObject = function (space, key) {
return this.get(key).then(bind(getModel, this));
};
ElasticPersistenceProvider.prototype.updateObject = function (space, key, value) {
function checkUpdate(response) {
return this.checkResponse(response, key);
}
return this.put(key, value, { version: this.revs[key] })
.then(bind(checkUpdate, this));
};
ElasticPersistenceProvider.prototype.deleteObject = function (space, key, value) {
return this.del(key).then(bind(this.checkResponse, this));
};
return ElasticPersistenceProvider;
}
);
);

View File

@@ -43,17 +43,51 @@ define(
* @param $http Angular's $http service, for working with urls.
* @param {ObjectService} objectService the service from which
* domain objects can be gotten.
* @param ROOT the constant ELASTIC_ROOT which allows us to
* @param ROOT the constant `ELASTIC_ROOT` which allows us to
* interact with ElasticSearch.
*/
function ElasticSearchProvider($http, objectService, ROOT) {
this.$http = $http;
this.objectService = objectService;
this.root = ROOT;
}
/**
* 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}
*
* 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.
*/
ElasticSearchProvider.prototype.query = function query(searchTerm, timestamp, maxResults, timeout) {
var $http = this.$http,
objectService = this.objectService,
root = this.root,
esQuery;
// Add the fuzziness operator to the search term
function addFuzziness(searchTerm, editDistance) {
if (!editDistance) {
editDistance = '';
}
return searchTerm.split(' ').map(function (s) {
// Don't add fuzziness for quoted strings
if (s.indexOf('"') !== -1) {
@@ -63,11 +97,11 @@ define(
}
}).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);
@@ -78,18 +112,18 @@ define(
spaceIndex = searchTerm.indexOf(' ');
while (spaceIndex !== -1) {
searchTerm = searchTerm.substring(0, spaceIndex) +
searchTerm.substring(spaceIndex + 1, searchTerm.length);
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
// 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,
@@ -98,25 +132,25 @@ define(
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
@@ -127,7 +161,7 @@ define(
});
}
}
return {
hits: searchResults,
total: rawResults.data.hits.total,
@@ -135,76 +169,43 @@ define(
};
});
}
// For documentation, see query below.
function query(searchTerm, timestamp, maxResults, timeout) {
var esQuery;
// 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);
// Create the query to elasticsearch
esQuery = ROOT + "/_search/?q=" + searchTerm +
"&size=" + maxResults;
if (timeout) {
esQuery += "&timeout=" + timeout;
}
// Get the data...
return $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};
});
} else {
return {hits: [], total: 0};
}
// 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;
}
return {
/**
* 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}
*
* 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.
*/
query: query
};
}
// If the user input is empty, we want to have no search results.
if (searchTerm !== '' && searchTerm !== undefined) {
// Process the search term
searchTerm = processSearchTerm(searchTerm);
// Create the query to elasticsearch
esQuery = root + "/_search/?q=" + searchTerm +
"&size=" + maxResults;
if (timeout) {
esQuery += "&timeout=" + timeout;
}
// Get the data...
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};
});
} else {
return {hits: [], total: 0};
}
};
return ElasticSearchProvider;