Merge remote-tracking branch 'origin/open-master' into open1482c

Merge latest into topic branch for WTD-1482
This commit is contained in:
Victor Woeltjen 2015-08-17 11:01:39 -07:00
commit bf417a14e0
39 changed files with 2091 additions and 94 deletions

View File

@ -67,7 +67,7 @@ as described above.
An example of this is expressed in `platform/framework`, which follows
bundle conventions.
### Regression Testing
### Functional Testing
The tests described above are all at the unit-level; an additional
test suite using [Protractor](https://angular.github.io/protractor/)
@ -76,9 +76,9 @@ us under development, in the `protractor` folder.
To run:
* Install protractor following the instructions above.
* `webdriver-manager start`
* `node app.js -p 1984 -x platform/persistence/elastic -i example/persistence
* `protractor protractor/conf.js`
* `cd protractor`
* `npm install`
* `npm run all`
## Build

View File

@ -21,6 +21,7 @@
"platform/persistence/queue",
"platform/policy",
"platform/entanglement",
"platform/search",
"example/imagery",
"example/persistence",

View File

@ -4,3 +4,7 @@ deployment:
commands:
- ./build-docs.sh
- git push git@heroku.com:openmctweb-demo.git $CIRCLE_SHA1:refs/heads/master
openmctweb-staging-un:
branch: search
heroku:
appname: openmctweb-staging-un

View File

@ -92,7 +92,7 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/* line 5, ../../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
/* line 5, ../../../../../../../../.gem/ruby/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
@ -113,38 +113,38 @@ time, mark, audio, video {
font-size: 100%;
vertical-align: baseline; }
/* line 22, ../../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
/* line 22, ../../../../../../../../.gem/ruby/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
html {
line-height: 1; }
/* line 24, ../../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
/* line 24, ../../../../../../../../.gem/ruby/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
ol, ul {
list-style: none; }
/* line 26, ../../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
/* line 26, ../../../../../../../../.gem/ruby/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
table {
border-collapse: collapse;
border-spacing: 0; }
/* line 28, ../../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
/* line 28, ../../../../../../../../.gem/ruby/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
caption, th, td {
text-align: left;
font-weight: normal;
vertical-align: middle; }
/* line 30, ../../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
/* line 30, ../../../../../../../../.gem/ruby/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
q, blockquote {
quotes: none; }
/* line 103, ../../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
/* line 103, ../../../../../../../../.gem/ruby/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
q:before, q:after, blockquote:before, blockquote:after {
content: "";
content: none; }
/* line 32, ../../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
/* line 32, ../../../../../../../../.gem/ruby/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
a img {
border: none; }
/* line 116, ../../../../../../../../../../Library/Ruby/Gems/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
/* line 116, ../../../../../../../../.gem/ruby/2.0.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
article, aside, details, figcaption, figure, footer, header, hgroup, main, menu, nav, section, summary {
display: block; }

View File

@ -0,0 +1,213 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
/**
* Module defining ElasticsearchSearchProvider. Created by shale on 07/16/2015.
* This is not currently included in the bundle definition.
*/
define(
[],
function () {
"use strict";
// JSLint doesn't like underscore-prefixed properties,
// so hide them here.
var ID = "_id",
SCORE = "_score",
DEFAULT_MAX_RESULTS = 100;
/**
* A search service which searches through domain objects in
* the filetree using ElasticSearch.
*
* @constructor
* @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
* interact with ElasticSearch.
*/
function ElasticsearchSearchProvider($http, objectService, ROOT) {
// 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) {
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
};
});
}
// 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};
}
}
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
};
}
return ElasticsearchSearchProvider;
}
);

View File

@ -0,0 +1,115 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,jasmine*/
/**
* SearchSpec. Created by shale on 07/31/2015.
*/
define(
["../src/ElasticsearchSearchProvider"],
function (ElasticsearchSearchProvider) {
"use strict";
// JSLint doesn't like underscore-prefixed properties,
// so hide them here.
var ID = "_id",
SCORE = "_score";
describe("The ElasticSearch search provider ", function () {
var mockHttp,
mockHttpPromise,
mockObjectPromise,
mockObjectService,
mockDomainObject,
provider,
mockProviderResults;
beforeEach(function () {
mockHttp = jasmine.createSpy("$http");
mockHttpPromise = jasmine.createSpyObj(
"promise",
[ "then" ]
);
mockHttp.andReturn(mockHttpPromise);
// allow chaining of promise.then().catch();
mockHttpPromise.then.andReturn(mockHttpPromise);
mockObjectService = jasmine.createSpyObj(
"objectService",
[ "getObjects" ]
);
mockObjectPromise = jasmine.createSpyObj(
"promise",
[ "then" ]
);
mockObjectService.getObjects.andReturn(mockObjectPromise);
mockDomainObject = jasmine.createSpyObj(
"domainObject",
[ "getId", "getModel" ]
);
provider = new ElasticsearchSearchProvider(mockHttp, mockObjectService, "");
provider.query(' test "query" ', 0, undefined, 1000);
});
it("sends a query to ElasticSearch", function () {
expect(mockHttp).toHaveBeenCalled();
});
it("gets data from ElasticSearch", function () {
var data = {
hits: {
hits: [
{},
{}
],
total: 0
},
timed_out: false
};
data.hits.hits[0][ID] = 1;
data.hits.hits[0][SCORE] = 1;
data.hits.hits[1][ID] = 2;
data.hits.hits[1][SCORE] = 2;
mockProviderResults = mockHttpPromise.then.mostRecentCall.args[0]({data: data});
expect(
mockObjectPromise.then.mostRecentCall.args[0]({
1: mockDomainObject,
2: mockDomainObject
}).hits.length
).toEqual(2);
});
it("returns nothing for an empty string query", function () {
expect(provider.query("").hits).toEqual([]);
});
it("returns something when there is an ElasticSearch error", function () {
mockProviderResults = mockHttpPromise.then.mostRecentCall.args[1]();
expect(mockProviderResults).toBeDefined();
});
});
}
);

View File

@ -1,4 +1,5 @@
[
"ElasticIndicator",
"ElasticPersistenceProvider"
"ElasticPersistenceProvider",
"ElasticsearchSearchProvider"
]

View File

@ -0,0 +1,33 @@
{
"name": "Search",
"description": "Allows the user to search through the file tree.",
"extensions": {
"constants": [
{
"key": "GENERIC_SEARCH_ROOTS",
"value": [ "ROOT" ],
"priority": "fallback"
}
],
"components": [
{
"provides": "searchService",
"type": "provider",
"implementation": "GenericSearchProvider.js",
"depends": [ "$q", "$timeout", "objectService", "workerService", "GENERIC_SEARCH_ROOTS" ]
},
{
"provides": "searchService",
"type": "aggregator",
"implementation": "SearchAggregator.js",
"depends": [ "$q" ]
}
],
"workers": [
{
"key": "genericSearchWorker",
"scriptUrl": "GenericSearchWorker.js"
}
]
}
}

View File

@ -0,0 +1,268 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
/**
* Module defining GenericSearchProvider. Created by shale on 07/16/2015.
*/
define(
[],
function () {
"use strict";
var DEFAULT_MAX_RESULTS = 100,
DEFAULT_TIMEOUT = 1000,
stopTime;
/**
* A search service which searches through domain objects in
* the filetree without using external search implementations.
*
* @constructor
* @param $q Angular's $q, for promise consolidation.
* @param $timeout Angular's $timeout, for delayed function execution.
* @param {ObjectService} objectService The service from which
* domain objects can be gotten.
* @param {WorkerService} workerService The service which allows
* more easy creation of web workers.
* @param {GENERIC_SEARCH_ROOTS} ROOTS An array of the root
* domain objects' IDs.
*/
function GenericSearchProvider($q, $timeout, objectService, workerService, ROOTS) {
var worker = workerService.run('genericSearchWorker'),
indexed = {},
pendingQueries = {};
// pendingQueries is a dictionary with the key value pairs st
// the key is the timestamp and the value is the promise
// Tell the web worker to add a domain object's model to its list of items.
function indexItem(domainObject) {
var message;
// undefined check
if (domainObject && domainObject.getModel) {
// Using model instead of whole domain object because
// it's a JSON object.
message = {
request: 'index',
model: domainObject.getModel(),
id: domainObject.getId()
};
worker.postMessage(message);
}
}
// Tell the worker to search for items it has that match this searchInput.
// Takes the searchInput, as well as a max number of results (will return
// less than that if there are fewer matches).
function workerSearch(searchInput, maxResults, timestamp, timeout) {
var message = {
request: 'search',
input: searchInput,
maxNumber: maxResults,
timestamp: timestamp,
timeout: timeout
};
worker.postMessage(message);
}
// Handles responses from the web worker. Namely, the results of
// a search request.
function handleResponse(event) {
var ids = [],
id;
// If we have the results from a search
if (event.data.request === 'search') {
// Convert the ids given from the web worker into domain objects
for (id in event.data.results) {
ids.push(id);
}
objectService.getObjects(ids).then(function (objects) {
var searchResults = [],
id;
// Create searchResult objects
for (id in objects) {
searchResults.push({
object: objects[id],
id: id,
score: event.data.results[id]
});
}
// Resove the promise corresponding to this
pendingQueries[event.data.timestamp].resolve({
hits: searchResults,
total: event.data.total,
timedOut: event.data.timedOut
});
});
}
}
worker.onmessage = handleResponse;
// Helper function for getItems(). Indexes the tree.
function indexItems(nodes) {
nodes.forEach(function (node) {
var id = node && node.getId && node.getId();
// If we have already indexed this item, stop here
if (indexed[id]) {
return;
}
// Index each item with the web worker
indexItem(node);
indexed[id] = true;
// If this node has children, index those
if (node && node.hasCapability && node.hasCapability('composition')) {
// Make sure that this is async, so doesn't block up page
$timeout(function () {
// Get the children...
node.useCapability('composition').then(function (children) {
$timeout(function () {
// ... then index the children
if (children.constructor === Array) {
indexItems(children);
} else {
indexItems([children]);
}
}, 0);
});
}, 0);
}
// Watch for changes to this item, in case it gets new children
if (node && node.hasCapability && node.hasCapability('mutation')) {
node.getCapability('mutation').listen(function (listener) {
if (listener && listener.composition) {
// If the node was mutated to have children, get the child domain objects
objectService.getObjects(listener.composition).then(function (objectsById) {
var objects = [],
id;
// Get each of the domain objects in objectsById
for (id in objectsById) {
objects.push(objectsById[id]);
}
indexItems(objects);
});
}
});
}
});
}
// Converts the filetree into a list
function getItems() {
// Aquire root objects
objectService.getObjects(ROOTS).then(function (objectsById) {
var objects = [],
id;
// Get each of the domain objects in objectsById
for (id in objectsById) {
objects.push(objectsById[id]);
}
// Index all of the roots' descendents
indexItems(objects);
});
}
// For documentation, see query below
function query(input, timestamp, maxResults, timeout) {
var terms = [],
searchResults = [],
defer = $q.defer();
// If the input is nonempty, do a search
if (input !== '' && input !== undefined) {
// Allow us to access this promise later to resolve it later
pendingQueries[timestamp] = defer;
// 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;
}
// Similarly, check if timeout was provided
if (!timeout) {
timeout = DEFAULT_TIMEOUT;
}
// Send the query to the worker
workerSearch(input, maxResults, timestamp, timeout);
return defer.promise;
} else {
// Otherwise return an empty result
return {hits: [], total: 0};
}
}
// Index the tree's contents once at the beginning
getItems();
return {
/**
* Searches through the filetree for domain objects which match
* the search term. This function is to be used as a fallback
* in the case where other search services are not avaliable.
* 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 not guarenteed.
* * A domain object qualifies as a match for a search input if
* the object's name property contains any of the search terms
* (which are generated by splitting the input at spaces).
* * Scores are higher for matches that have more of the terms
* as substrings.
*
* @param input 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.
*/
query: query
};
}
return GenericSearchProvider;
}
);

View File

@ -0,0 +1,185 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global self*/
/**
* Module defining GenericSearchWorker. Created by shale on 07/21/2015.
*/
(function () {
"use strict";
// An array of objects composed of domain object IDs and models
// {id: domainObject's ID, model: domainObject's model}
var indexedItems = [];
// Helper function for index()
// Checks whether an item with this ID is already indexed
function conainsItem(id) {
var i;
for (i = 0; i < indexedItems.length; i += 1) {
if (indexedItems[i].id === id) {
return true;
}
}
return false;
}
/**
* Indexes an item to indexedItems.
*
* @param data An object which contains:
* * model: The model of the domain object
* * id: The ID of the domain object
*/
function index(data) {
var message;
if (!conainsItem(data.id)) {
indexedItems.push({
id: data.id,
model: data.model
});
}
}
// Helper function for serach()
function convertToTerms(input) {
var terms = input;
// Shave any spaces off of the ends of the input
while (terms.substr(0, 1) === ' ') {
terms = terms.substring(1, terms.length);
}
while (terms.substr(terms.length - 1, 1) === ' ') {
terms = terms.substring(0, terms.length - 1);
}
// Then split it at spaces and asterisks
terms = terms.split(/ |\*/);
// Remove any empty strings from the terms
while (terms.indexOf('') !== -1) {
terms.splice(terms.indexOf(''), 1);
}
return terms;
}
// Helper function for search()
function scoreItem(item, input, terms) {
var name = item.model.name.toLocaleLowerCase(),
weight = 0.65,
score = 0.0,
i;
// Make the score really big if the item name and
// the original search input are the same
if (name === input) {
score = 42;
}
for (i = 0; i < terms.length; i += 1) {
// Increase the score if the term is in the item name
if (name.indexOf(terms[i]) !== -1) {
score += 1;
// Add extra to the score if the search term exists
// as its own term within the items
if (name.split(' ').indexOf(terms[i]) !== -1) {
score += 0.5;
}
}
}
return score * weight;
}
/**
* Gets search results from the indexedItems based on provided search
* input. Returns matching results from indexedItems, as well as the
* timestamp that was passed to it.
*
* @param data An object which contains:
* * input: The original string which we are searching with
* * maxNumber: The maximum number of search results desired
* * timestamp: The time identifier from when the query was made
*/
function search(data) {
// This results dictionary will have domain object ID keys which
// point to the value the domain object's score.
var results = {},
input = data.input.toLocaleLowerCase(),
terms = convertToTerms(input),
message = {
request: 'search',
results: {},
total: 0,
timestamp: data.timestamp,
timedOut: false
},
score,
i,
id;
// If the user input is empty, we want to have no search results.
if (input !== '') {
for (i = 0; i < indexedItems.length; i += 1) {
// If this is taking too long, then stop
if (Date.now() > data.timestamp + data.timeout) {
message.timedOut = true;
break;
}
// Score and add items
score = scoreItem(indexedItems[i], input, terms);
if (score > 0) {
results[indexedItems[i].id] = score;
message.total += 1;
}
}
}
// Truncate results if there are more than maxResults
if (message.total > data.maxResults) {
i = 0;
for (id in results) {
message.results[id] = results[id];
i += 1;
if (i >= data.maxResults) {
break;
}
}
// TODO: This seems inefficient.
} else {
message.results = results;
}
return message;
}
self.onmessage = function (event) {
if (event.data.request === 'index') {
index(event.data);
} else if (event.data.request === 'search') {
self.postMessage(search(event.data));
}
};
}());

View File

@ -0,0 +1,146 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
/**
* Module defining SearchAggregator. Created by shale on 07/16/2015.
*/
define(
[],
function () {
"use strict";
var DEFUALT_TIMEOUT = 1000,
DEFAULT_MAX_RESULTS = 100;
/**
* Allows multiple services which provide search functionality
* to be treated as one.
*
* @constructor
* @param $q Angular's $q, for promise consolidation.
* @param {SearchProvider[]} providers The search providers to be
* aggregated.
*/
function SearchAggregator($q, providers) {
// Remove duplicate objects that have the same ID. Modifies the passed
// array, and returns the number that were removed.
function filterDuplicates(results, total) {
var ids = {},
numRemoved = 0,
i;
for (i = 0; i < results.length; i += 1) {
if (ids[results[i].id]) {
// If this result's ID is already there, remove the object
results.splice(i, 1);
numRemoved += 1;
// Reduce loop index because we shortened the array
i -= 1;
} else {
// Otherwise add the ID to the list of the ones we have seen
ids[results[i].id] = true;
}
}
return numRemoved;
}
// Order the objects from highest to lowest score in the array.
// Modifies the passed array, as well as returns the modified array.
function orderByScore(results) {
results.sort(function (a, b) {
if (a.score > b.score) {
return -1;
} else if (b.score > a.score) {
return 1;
} else {
return 0;
}
});
return results;
}
// For documentation, see query below.
function queryAll(inputText, maxResults) {
var i,
timestamp = Date.now(),
resultPromises = [];
if (!maxResults) {
maxResults = DEFAULT_MAX_RESULTS;
}
// Send the query to all the providers
for (i = 0; i < providers.length; i += 1) {
resultPromises.push(
providers[i].query(inputText, timestamp, maxResults, DEFUALT_TIMEOUT)
);
}
// Get promises for results arrays
return $q.all(resultPromises).then(function (resultObjects) {
var results = [],
totalSum = 0,
i;
// Merge results
for (i = 0; i < resultObjects.length; i += 1) {
results = results.concat(resultObjects[i].hits);
totalSum += resultObjects[i].total;
}
// Order by score first, so that when removing repeats we keep the higher scored ones
orderByScore(results);
totalSum -= filterDuplicates(results, totalSum);
return {
hits: results,
total: totalSum,
timedOut: resultObjects.some(function (obj) {
return obj.timedOut;
})
};
});
}
return {
/**
* Sends a query to each of the providers. 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}
*
* @param inputText The text input that is the query.
* @param maxResults (optional) The maximum number of results
* that this function should return. If not provided, a
* default of 100 will be used.
*/
query: queryAll
};
}
return SearchAggregator;
}
);

View File

@ -0,0 +1,157 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,jasmine*/
/**
* SearchSpec. Created by shale on 07/31/2015.
*/
define(
["../src/GenericSearchProvider"],
function (GenericSearchProvider) {
"use strict";
describe("The generic search provider ", function () {
var mockQ,
mockTimeout,
mockDeferred,
mockObjectService,
mockObjectPromise,
mockDomainObjects,
mockCapability,
mockCapabilityPromise,
mockWorkerService,
mockWorker,
mockRoots = ['root1', 'root2'],
provider,
mockProviderResults;
beforeEach(function () {
var i;
mockQ = jasmine.createSpyObj(
"$q",
[ "defer" ]
);
mockDeferred = jasmine.createSpyObj(
"deferred",
[ "resolve", "reject"]
);
mockDeferred.promise = "mock promise";
mockQ.defer.andReturn(mockDeferred);
mockTimeout = jasmine.createSpy("$timeout");
mockObjectService = jasmine.createSpyObj(
"objectService",
[ "getObjects" ]
);
mockObjectPromise = jasmine.createSpyObj(
"promise",
[ "then", "catch" ]
);
mockObjectService.getObjects.andReturn(mockObjectPromise);
mockWorkerService = jasmine.createSpyObj(
"workerService",
[ "run" ]
);
mockWorker = jasmine.createSpyObj(
"worker",
[ "postMessage" ]
);
mockWorkerService.run.andReturn(mockWorker);
mockDomainObjects = {};
for (i = 0; i < 4; i += 1) {
mockDomainObjects[i] = (
jasmine.createSpyObj(
"domainObject",
[ "getId", "getModel", "hasCapability", "getCapability", "useCapability" ]
)
);
mockDomainObjects[i].getId.andReturn(i);
mockDomainObjects[i].getCapability.andReturn(mockCapability);
}
// Give the first object children
mockDomainObjects[0].hasCapability.andReturn(true);
mockCapability = jasmine.createSpyObj(
"capability",
[ "invoke", "listen" ]
);
mockCapabilityPromise = jasmine.createSpyObj(
"promise",
[ "then", "catch" ]
);
mockCapability.invoke.andReturn(mockCapabilityPromise);
mockDomainObjects[0].getCapability.andReturn(mockCapability);
provider = new GenericSearchProvider(mockQ, mockTimeout, mockObjectService, mockWorkerService, mockRoots);
});
it("indexes tree on initialization", function () {
expect(mockObjectService.getObjects).toHaveBeenCalled();
expect(mockObjectPromise.then).toHaveBeenCalled();
mockObjectPromise.then.mostRecentCall.args[0](mockDomainObjects);
//mockCapabilityPromise.then.mostRecentCall.args[0](mockDomainObjects[1]);
expect(mockWorker.postMessage).toHaveBeenCalled();
});
it("sends search queries to the worker", function () {
var timestamp = Date.now();
provider.query(' test "query" ', timestamp, 1, 2);
expect(mockWorker.postMessage).toHaveBeenCalledWith({
request: "search",
input: ' test "query" ',
timestamp: timestamp,
maxNumber: 1,
timeout: 2
});
});
it("handles responses from the worker", function () {
var timestamp = Date.now(),
event = {
data: {
request: "search",
results: {
1: 1,
2: 2
},
total: 2,
timedOut: false,
timestamp: timestamp
}
};
provider.query(' test "query" ', timestamp);
mockWorker.onmessage(event);
mockObjectPromise.then.mostRecentCall.args[0](mockDomainObjects);
expect(mockDeferred.resolve).toHaveBeenCalled();
});
});
}
);

View File

@ -0,0 +1,132 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,describe,it,expect,runs,waitsFor,beforeEach,jasmine,Worker,require*/
/**
* SearchSpec. Created by shale on 07/31/2015.
*/
define(
[],
function () {
"use strict";
describe("The generic search worker ", function () {
// If this test fails, make sure this path is correct
var worker = new Worker(require.toUrl('platform/search/src/GenericSearchWorker.js')),
numObjects = 5;
beforeEach(function () {
var i;
for (i = 0; i < numObjects; i += 1) {
worker.postMessage(
{
request: "index",
id: i,
model: {
name: "object " + i,
id: i,
type: "something"
}
}
);
}
});
it("searches can reach all objects", function () {
var flag = false,
workerOutput,
resultsLength = 0;
// Search something that should return all objects
runs(function () {
worker.postMessage(
{
request: "search",
input: "object",
maxNumber: 100,
timestamp: Date.now(),
timeout: 1000
}
);
});
worker.onmessage = function (event) {
var id;
workerOutput = event.data;
for (id in workerOutput.results) {
resultsLength += 1;
}
flag = true;
};
waitsFor(function () {
return flag;
}, "The worker should be searching", 1000);
runs(function () {
expect(workerOutput).toBeDefined();
expect(resultsLength).toEqual(numObjects);
});
});
it("searches return only matches", function () {
var flag = false,
workerOutput,
resultsLength = 0;
// Search something that should return 1 object
runs(function () {
worker.postMessage(
{
request: "search",
input: "2",
maxNumber: 100,
timestamp: Date.now(),
timeout: 1000
}
);
});
worker.onmessage = function (event) {
var id;
workerOutput = event.data;
for (id in workerOutput.results) {
resultsLength += 1;
}
flag = true;
};
waitsFor(function () {
return flag;
}, "The worker should be searching", 1000);
runs(function () {
expect(workerOutput).toBeDefined();
expect(resultsLength).toEqual(1);
expect(workerOutput.results[2]).toBeDefined();
});
});
});
}
);

View File

@ -0,0 +1,101 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,jasmine*/
/**
* SearchSpec. Created by shale on 07/31/2015.
*/
define(
["../src/SearchAggregator"],
function (SearchAggregator) {
"use strict";
describe("The search aggregator ", function () {
var mockQ,
mockPromise,
mockProviders = [],
aggregator,
mockProviderResults = [],
mockAggregatorResults,
i;
beforeEach(function () {
mockQ = jasmine.createSpyObj(
"$q",
[ "all" ]
);
mockPromise = jasmine.createSpyObj(
"promise",
[ "then" ]
);
for (i = 0; i < 3; i += 1) {
mockProviders.push(
jasmine.createSpyObj(
"mockProvider" + i,
[ "query" ]
)
);
mockProviders[i].query.andReturn(mockPromise);
}
mockQ.all.andReturn(mockPromise);
aggregator = new SearchAggregator(mockQ, mockProviders);
aggregator.query();
for (i = 0; i < mockProviders.length; i += 1) {
mockProviderResults.push({
hits: [
{
id: i,
score: 42 - i
},
{
id: i + 1,
score: 42 - (2 * i)
}
]
});
}
mockAggregatorResults = mockPromise.then.mostRecentCall.args[0](mockProviderResults);
});
it("sends queries to all providers", function () {
for (i = 0; i < mockProviders.length; i += 1) {
expect(mockProviders[i].query).toHaveBeenCalled();
}
});
it("filters out duplicate objects", function () {
expect(mockAggregatorResults.hits.length).toEqual(mockProviders.length + 1);
expect(mockAggregatorResults.total).not.toBeLessThan(mockAggregatorResults.hits.length);
});
it("orders results by score", function () {
for (i = 1; i < mockAggregatorResults.hits.length; i += 1) {
expect(mockAggregatorResults.hits[i].score)
.not.toBeGreaterThan(mockAggregatorResults.hits[i - 1].score);
}
});
});
}
);

View File

@ -0,0 +1,5 @@
[
"SearchAggregator",
"GenericSearchProvider",
"GenericSearchWorker"
]

View File

@ -6,7 +6,7 @@
<groupId>gov.nasa.arc.wtd</groupId>
<artifactId>open-mct-web</artifactId>
<name>Open MCT Web</name>
<version>0.8.0-SNAPSHOT</version>
<version>0.8.1-SNAPSHOT</version>
<packaging>war</packaging>
<properties>

69
protractor/README Normal file
View File

@ -0,0 +1,69 @@
E2e Protractor Tests.
1. Instructions:
1. 3 Control Scripts located in bin/.
run.js : node script used to start tests
start.js: node script used to setup test(starts node,localstorage and webdriver)
stop.js : node script, kills the 3 process started in start.js.
clean.js: node script used to remove the node_module directory.(clean up directory).
2. Use npm(Node Package Mangager) to Run Scripts.
a. cd protractor;
b. npm install;
c. To Run:
-npm start : will start processes need by protractor
-npm stop : will stop the processes need by protractor
-npm run-script run : will execute Protractor Script
-npm run-script all : will execute "start", "run", and "stop" script
2. Directory Hierachy:
-protractor: base directory
-common: contains prototype javascript functions that all other tests use.
-Buttons: common prototype functions related to enter fullscreen
-CreateItem: common prototype functions related to creating an item
-drag: common functions to test drag and drop.
-editItem: common functions used to test edit functionality.
-Launch: common script used to navigate the specified website.
-RightMenu: common functions for right click menu(remove).
-create
-e2e tests that creates the specified object.
-delete
-e2e tests that removes the specified object
-logs
-ctrl.sh redirects console output of MMAP, webdriver and elastic search and pipes them to log files.
-UI
-Contains tests that test the UI(drag drop, fullscreen, info bubble)
-conf.js:
-protractor config file. Explained below
-stressTest:
Tests that are used to test for memory leaks. You can use the new tab option on WARP and then open the
timeline in the new tab during the browser.sleep(). Once the test is do the browser will pause and you
can look a the timeline results in the new tab.
NOTE: Cannot open chrome dev tools on same tab as the test are run on. Protractor uses the dev tools to
exectute the tests.
-StressTest will create and delete folders.
-StressTestBubble.js: creates manny bubbles.
(Delay variable in InfoGesture.js was changed to 0)
3. Conf.js
Conf.js is used by protractor to setup and execute the tests.
-allScriptsTimeout: gives more time for protractor to synchronize with the page.
-jasmineNodeOpts: Protractor uses jasmine for the tests and jasmine has a default time out 30 seconds
per "it" test. Changed to maximume allowed time 360000 ms
-seleniumAddress: Protractor uses a Selenium server as a "proxy" between the test scripts and the browser
driver. A stand a lone version comes with protractor and to run use "webdriver-manager"
default address is: http://localhost:4444/wd/hub.
-specs[]: Is an array of files. Each File should have a "describe, it" test to be executed by protractor.
-capabilities: Tells protractor what browser to use and any browser arguments.
4. bundle.json
bundle.json is used by npm to determine dependencies and location of script files.
-Dependencies:
"protractor": Contains protractor and webdriver package.
"psnode": Window/Unix Command, used for list/kill process.(ps aux)
"shelljs": Window/Unix Common JS Commands. eg rm,ls,exec
"sleep": Window/Unix Commands used to sleep the script
"string": Window/Unix Commands for string manipulation.

View File

@ -22,7 +22,7 @@
//TODO Add filter for duplications/
var fullScreenFile = require("../common/Buttons");
describe('Test Fullscreen', function() {
describe('Enable Fullscreen', function() {
var fullScreenClass = new fullScreenFile();
beforeEach(require('../common/Launch'));

View File

@ -25,7 +25,7 @@ var itemEdit = require("../common/EditItem");
var rightMenu = require("../common/RightMenu");
var Drag = require("../common/drag");
describe('Test Info Bubble', function() {
describe('Info Bubble', function() {
var fullScreenClass = new fullScreenFile();
var createClass = new createItem();
var editItemClass = new itemEdit();

View File

@ -24,7 +24,7 @@ var createClassFile = require("../common/CreateItem")
var itemEdit = require("../common/EditItem");
var rightMenu = require("../common/RightMenu.js");
describe('Test New Window', function() {
describe('New Window', function() {
var fullScreenClass = new fullScreenFile();
var createClass = new createClassFile();
var editItemClass = new itemEdit();

View File

@ -21,28 +21,64 @@
*****************************************************************************/
var right_click = require("../common/RightMenu.js");
var Create = require("../common/CreateItem")
describe('Right Click Interations', function() {
var itemEdit = require("../common/EditItem");
describe('The Right Menu', function() {
var clickClass = new right_click();
var createClass = new Create();
var editItemClass = new itemEdit();
var ITEM_NAME = "Folder";
var ITEM_TYPE = "folder";
var ITEM_MENU_GLYPH = 'F\nFolder';
var ITEM_GRID_SELECT = 'P\nF\nFolder\n0 Items';
beforeEach(require('../common/Launch'));
it('should delete the specified object', function(){
createClass.createButton().click();
var folder = createClass.selectNewItem(ITEM_TYPE);
expect(folder.getText()).toEqual([ ITEM_MENU_GLYPH ]);
browser.sleep(1000);
folder.click()
browser.sleep(1000);
browser.wait(function () {
return element.all(by.model('ngModel[field]')).isDisplayed();
it('should Dissapear After Delete', function(){
browser.wait(function() {
createClass.createButton().click();
return true;
}).then(function (){
var folder = createClass.selectNewItem(ITEM_TYPE);
expect(folder.getText()).toEqual([ ITEM_MENU_GLYPH ]);
browser.sleep(1000);
folder.click()
}).then(function() {
browser.wait(function () {
return element.all(by.model('ngModel[field]')).isDisplayed();
})
createClass.fillFolderForum(ITEM_NAME, ITEM_TYPE).click();
browser.sleep(1000);
}).then(function (){
var item = editItemClass.SelectItem(ITEM_GRID_SELECT);
expect(item.count()).toBe(1);
browser.sleep(1000);
}).then(function () {
var MyItem = ">\nF\nMy Items"
element.all(by.repeater('child in composition')).filter(function (ele){
return ele.getText().then(function(text) {
return text === MyItem;
});
}).all(by.css('.ui-symbol.view-control.ng-binding.ng-scope')).click();
var object = element.all(by.repeater('child in composition')).filter(function (ele){
return ele.getText().then(function(text) {
return text === ">\nF\nFolder";
});
});
browser.sleep(1000)
browser.actions().mouseMove(object.get(0)).perform();
browser.actions().click(protractor.Button.RIGHT).perform();
browser.sleep(1000)
var menu = element.all(by.css('.ng-binding')).filter(function (ele){
return ele.getText().then(function (text) {
return text == "Z\nRemove";
})
})
menu.click();
browser.sleep(1000)
expect(menu.isDisplayed()).toBe(false);
})
createClass.fillFolderForum(ITEM_NAME, ITEM_TYPE).click();
clickClass.delete(ITEM_NAME);
browser.sleep(1000);
});
});

15
protractor/bin/clean.js Executable file
View File

@ -0,0 +1,15 @@
#! /usr/bin/env node
var shell = require("shelljs/global");
var startdir = process.cwd();
var command = "npm unlink";
console.log("Cleaning Directory")
exec(command, function(code, output) {
if(code != 0){
console.log('Exit code:', code);
console.log('Program output:', output);
}
});
console.log("rm -rf node_modules")
rm('-rf', __dirname + "/../node_modules")

90
protractor/bin/ctrl.sh Executable file
View File

@ -0,0 +1,90 @@
#! /bin/bash
ARGUMENT=$1;
if [ $# != 1 ]; then
echo "Expected 1 Aurgument. Received " $# 1>&2;
exit 1
fi
#Start webdrive and http-server
if [ $ARGUMENT == start ]; then
echo "Creating Log Directory ..."
mkdir logs;
cd ..
node app.js -p 1984 -x platform/persistence/elastic -i example/persistence > protractor/logs/nodeApp.log 2>&1 &
sleep 3;
if grep -iq "Error" protractor/logs/nodeApp.log; then
if grep -iq "minimist" protractor/logs/nodeApp.log; then
echo " Node Failed Because Minimist is not installed"
echo " Installng Minimist ..."
npm install minimist express > protractor/logs/minimist.log 2>&1 &
wait $!
if [ $? != 0 ]; then
echo " Error: minimist"
echo " Check Log file"
echo
else
echo " Started: Minimist"
echo
node app.js -p 1984 -x platform/persistence/elastic -i example/persistence > protractor/logs/nodeApp.log 2>&1 &
if grep -iq "Error" protractor/logs/nodeApp.log; then
echo " Error: node app failed"
echo " Check Log file"
echo
else
echo " Started: node app.js"
echo
fi
fi
else
echo " Error: node app failed"
echo " Check Log file"
echo
fi
else
echo " Started: node app.js"
echo
fi
echo "Starting webdriver ..."
cd protractor;
webdriver-manager start > logs/webdriver.log 2>&1 &
sleep 3;
if grep -iq "Exception" logs/webdriver.log; then
echo " Error: webdriver-manager"
echo " Check Log file"
echo
else
echo " Started: webdriver-manager"
fi
echo "Starting Elastic Search..."
elasticsearch > logs/elasticSearch.log 2>&1 &
sleep 3;
if grep -iq "Exception" logs/elasticSearch.log; then
echo " Error: ElasticSearch"
echo " Check Log file"
echo
else
echo " Started: ElasticSearch"
fi
#Runs Protractor tests
elif [ $ARGUMENT == run ]; then
protractor ./conf.js
#Kill Process
elif [ $ARGUMENT == stop ]; then
echo "Removing logs"
rm -rf logs
echo "Stopping Node"
kill $(ps aux | grep "[n]ode app.js"| awk '{print $2}');
echo "Stopping webdriver ..."
kill $(ps aux | grep "[p]rotractor" | awk '{print $2}');
kill $(ps aux | grep "[w]ebdriver-manager" | awk '{print $2}');
sleep 1;
echo "Stopping Elastic..."
kill $(ps aux | grep "[e]lastic" | awk '{print $2}');
sleep 1;
else
echo "Unkown: Command" $1;
fi

12
protractor/bin/run.js Executable file
View File

@ -0,0 +1,12 @@
#! /usr/bin/env node
var shell = require("shelljs/global");
var sleep = require('sleep');
var command = __dirname + "/../node_modules/protractor/bin/protractor " +__dirname + "/../conf.js";
console.log("Executing Protractor Test")
exec(command, function(code, output) {
if(code != 0){
console.log('Exit code:', code);
console.log('Program output:', output);
}
});

40
protractor/bin/start.js Executable file
View File

@ -0,0 +1,40 @@
#! /usr/bin/env node
var shell,sleep;
try {
shell = require("shelljs/global");
sleep = require('sleep');
}catch (e){
console.log("Dependencies Error");
console.log("Run npm install");
throw (e);
}
///Users/jsanderf/git/elastic/wtd/protractor/bin
var startdir = process.cwd();
var command;
mkdir(__dirname + '/../logs');
command = __dirname + "/../node_modules/protractor/bin/webdriver-manager update";
console.log("Installing Webdriver");
exec(command,{async:false});
sleep.sleep(1);
console.log();
cd(__dirname + '/../../');
console.log('Installing Dependencies');
exec("npm install minimist express", {async:false});
console.log('Starting Node');
sleep.sleep(1);
exec("node app.js -p 1984 -x example/persistence -x platform/persistence/elastic -i example/localstorage > protractor/logs/nodeApp.log 2>&1 &", {async:false});
console.log(' Started Node');
console.log();
console.log('Starting Webdriver');
sleep.sleep(1);
exec("protractor/node_modules/protractor/bin/webdriver-manager start --standalone> protractor/logs/webdriver.log 2>&1 &",{async:false});
if(error() == null){
console.log(" Webdriver Started");
}else{
console.log(" Error : ", error());
}
sleep.sleep(1);
cd(startdir);

44
protractor/bin/stop.js Executable file
View File

@ -0,0 +1,44 @@
#! /usr/bin/env node
var shell = require("shelljs/global");
var ps = require('psnode');
var S = require('string');
var sleep = require('sleep');
// A simple pid lookup
ps.list(function(err, results) {
results.forEach(function( process ){
//Killing Node
if(S(process.command).contains("node app.js")) {
console.log();
console.log( 'Killing Node: %s', process.command);
ps.kill(process.pid, function(err, stdout) {
if (err) {
throw new Error(err);
}
console.log(stdout);
});
}else if(S(process.command).contains("webdriver")) {
console.log();
console.log( 'Killing WebDriver: %s', process.command);
ps.kill(process.pid, function(err, stdout) {
if (err){
throw new Error(err);
}
console.log(stdout);
});
}else if(S(process.command).contains("protractor")) {
console.log();
console.log( 'Killing Chrome Drive: %s', process.command);
ps.kill(process.pid, function(err, stdout) {
if (err){
throw new Error(err);
}
console.log(stdout);
});
}
});
});

View File

@ -24,6 +24,6 @@
module.exports = function launch() {
'use strict';
browser.ignoreSynchronization = true;
browser.get('http://localhost:1984/');
browser.sleep(2000); // 20 seconds
browser.get('http://localhost:1984');
browser.sleep(2000); // 2 seconds
};

View File

@ -24,18 +24,25 @@ var RightMenu = (function () {
function RightMenu() {
}
function carrotMyItem(){
var MyItem = ">\nF\nMy Items"
element.all(by.repeater('child in composition')).filter(function (ele){
return ele.getText().then(function(text) {
return text === MyItem;
});
}).all(by.css('.ui-symbol.view-control.ng-binding.ng-scope')).click();
}
//RightMenu Click on Object
RightMenu.prototype.delete = function (name, flag) {
if(typeof flag === 'undefined'){
flag = true;
}
if(flag === true){
var carrot = element.all(by.css('.ui-symbol.view-control.ng-binding.ng-scope')).get(0).click();
carrotMyItem();
}
browser.sleep(1000)
var object = element.all(by.repeater('child in composition')).filter(function (ele){
return ele.getText().then(function(text) {
//expect(text).toEqual("3");
return text === name;
});
});
@ -43,7 +50,7 @@ var RightMenu = (function () {
browser.actions().mouseMove(object.get(0)).perform();
browser.actions().click(protractor.Button.RIGHT).perform();
browser.sleep(1000)
var remove = element.all(by.css('.ng-binding')).filter(function (ele){
element.all(by.css('.ng-binding')).filter(function (ele){
return ele.getText().then(function (text) {
return text == "Z\nRemove";
})
@ -58,11 +65,10 @@ var RightMenu = (function () {
});
};
RightMenu.prototype.reset = function (name) {
var carrot = element.all(by.css('.ui-symbol.view-control.ng-binding.ng-scope')).click();
carrotMyItem();
browser.sleep(1000)
var object = element.all(by.repeater('child in composition')).filter(function (ele){
return ele.getText().then(function(text) {
//expect(text).toEqual("3");
return text === name;
});
}).click();
@ -75,19 +81,19 @@ var RightMenu = (function () {
return text == "r\nRestart at 0";
})
}).click();
browser.sleep(1000)
};
//click '<', true==yes false==no
RightMenu.prototype.select = function(name, flag){
if(typeof flag == "undefined"){
flag = true;
}
//click '<', true==yes false==no
if(flag == true){
var carrot = element.all(by.css('.ui-symbol.view-control.ng-binding.ng-scope')).click();
carrotMyItem();
}
browser.sleep(1000)
return element.all(by.repeater('child in composition')).filter(function (ele){
return ele.getText().then(function(text) {
// expect(text).toEqual("3");
return text === name;
});
});
@ -96,7 +102,6 @@ var RightMenu = (function () {
RightMenu.prototype.dragDrop = function(name){
var object = element.all(by.repeater('child in composition')).filter(function (ele){
return ele.getText().then(function(text) {
//expect(text).toEqual("3");
return text === name;
});
});

View File

@ -24,34 +24,34 @@
// conf.js
exports.config = {
allScriptsTimeout: 500000,
defaultTimeoutInterval: 60000,
jasmineNodeOpts: {defaultTimeoutInterval: 360000},
seleniumAddress: 'http://localhost:4444/wd/hub',
//specs: ['StressTest.js'],
//specs: ['StressTestCarrot.js'],
specs: [
//'create/CreateActivity.js',
//'delete/DeleteActivity.js',
//'create/CreateActivityMode.js',
//'delete/DeleteActivityMode.js',
//'create/CreateActivityMode.js',
//'create/CreateClock.js',
//'delete/DeleteClock.js',
// 'create/CreateActivity.js',
// 'delete/DeleteActivity.js',
// 'create/CreateActivityMode.js',
// 'delete/DeleteActivityMode.js',
// 'create/CreateClock.js',
// 'delete/DeleteClock.js',
'create/CreateDisplay.js',
//'delete/DeleteDisplay.js',
'delete/DeleteDisplay.js',
'create/CreateFolder.js',
//'delete/DeleteFolder.js',
'create/CreateTelemetry.js',
//'delete/DeleteTelemetry.js',
//'create/CreateTimeline.js',
//'delete/DeleteTimeline.js',
//'create/CreateTimer.js',
//'delete/DeleteTimer.js',
'delete/DeleteFolder.js',
// 'create/CreateTelemetry.js',
// 'delete/DeleteTelemetry.js',
// 'create/CreateTimeline.js',
// 'delete/DeleteTimeline.js',
// 'create/CreateTimer.js',
// 'delete/DeleteTimer.js',
'create/CreateWebPage.js',
//'delete/DeleteWebPage.js',
'delete/DeleteWebPage.js',
'UI/Fullscreen.js',
'create/CreateButton.js',
//"UI/DragDrop.js",
//"UI/NewWindow.js",
'UI/InfoBubble.js'
"UI/NewWindow.js"
//'UI/InfoBubble.js',
//'UI/RightClick.js'
],
capabilities: {
'browserName': 'chrome', // or 'safari'
@ -61,7 +61,7 @@ exports.config = {
// Allow specifying binary location as an environment variable,
// for cases where Chrome is not installed in a usual location.
if (process.env.PROTRACTOR_CHROME_BINARY) {
if (process.env.CHROME_BIN) {
exports.config.capabilities.chromeOptions.binary =
process.env.PROTRACTOR_CHROME_BINARY;
process.env.CHROME_BIN;
}

View File

@ -22,7 +22,7 @@
var itemCreate = require("../common/CreateItem");
var itemEdit = require("../common/EditItem");
describe('Create Web Page', function() {
describe('Create Activity Mode', function() {
var createClass = new itemCreate();
var editItemClass = new itemEdit();
var ITEM_NAME = "Activity Mode";

View File

@ -57,7 +57,7 @@ describe('Create Clock', function() {
});
it('should check clock', function () {
function getTime() {
function getTime(flag) {
function addZero(time){
if(time < 10){
return '0' + time;
@ -66,7 +66,6 @@ describe('Create Clock', function() {
}
var currentdate = new Date();
var month = currentdate.getMonth() + 1;
month = addZero(month);
@ -77,6 +76,9 @@ describe('Create Clock', function() {
hour = addZero(hour);
var second = currentdate.getSeconds();
if(flag == true) {
second = second + 1;
}
second = addZero(second);
var minute = currentdate.getMinutes();
@ -85,17 +87,23 @@ describe('Create Clock', function() {
return ("UTC " + currentdate.getFullYear() + "/" + (month) + "/" +
day + " " + (hour) + ":" + minute + ":" + second + " PM");
}
var current,clock;
rightClickClass.select(ITEM_MENU_GLYPH, true).click().then(function () {
browser.sleep(1000);
current = browser.executeScript(getTime);
}).then(function () {
clock = element(by.css('.l-time-display.l-digital.l-clock.s-clock.ng-scope'));
clock.getText().then(function (time) {
expect(current).toEqual(time);
})
this.addMatchers({
toBeIn: function(expected){
var posibilities = Array.isArray(this.actual) ? this.actual : [this.actual];
return posibilities.indexOf(expected) > -1;
}
})
rightClickClass.select(ITEM_MENU_GLYPH, true).click().then(function () {
browser.sleep(1000);
browser.executeScript(getTime, false).then(function(current){
browser.executeScript(getTime, true).then(function(current1) {
var clock = element(by.css('.l-time-display.l-digital.l-clock.s-clock.ng-scope'));
clock.getText().then(function (ele) {
expect([current,current1]).toBeIn(ele);
})
});
});
})
});
});

View File

@ -63,7 +63,11 @@ describe('Create Timer', function() {
browser.sleep(1000)
var timer = element(by.css('.value.ng-binding.active'))
timer.getText().then(function (time) {
expect(time).toEqual("0D 00:00:01")
var timerChecker = false;
if(time == "0D 00:00:01" || time == "0D 00:00:02"){
timerChecker = true;
}
expect(timerChecker).toEqual(true)
})
});

20
protractor/package.json Normal file
View File

@ -0,0 +1,20 @@
{
"name": "ProtractorLauncher",
"version": "1.0.0",
"scripts" : {
"start" : "bin/start.js",
"protractor" : "bin/run.js",
"stop" : "bin/stop.js",
"all" : "bin/start.js; bin/run.js; bin/stop.js;",
"clean" : "bin/clean.js"
},
"dependencies": {
"protractor": "^2.1.0",
"psnode": "0.0.1",
"shelljs": "^0.5.2",
"sleep": "^3.0.0",
"string": "^3.3.1"
},
"description": "E2e Protractor Tests.",
"license": "ISC"
}

View File

@ -19,10 +19,9 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
//TODO Add filter for duplications/
var itemCreate = require("./common/CreateItem");
var itemEdit = require("./common/EditItem");
var right_click = require("./common/RightMenu.js");
var itemCreate = require("../common/CreateItem");
var itemEdit = require("../common/EditItem");
var right_click = require("../common/RightMenu.js");
describe('Create Folder', function() {
var clickClass = new right_click();
@ -41,31 +40,35 @@ describe('Create Folder', function() {
});
it('should Create new Folder', function(){
browser.sleep(5000);
for(var i=0; i < 50; i++){
for(var i=0; i < 25; i++){
browser.wait(function() {
createClass.createButton().click();
return true;
}).then(function (){
var folder = createClass.selectNewItem(ITEM_TYPE);
expect(folder.getText()).toEqual([ ITEM_MENU_GLYPH ]);
browser.sleep(1000);
browser.sleep(500);
folder.click()
}).then(function() {
browser.wait(function () {
return element.all(by.model('ngModel[field]')).isDisplayed();
})
createClass.fillFolderForum(ITEM_NAME, ITEM_TYPE).click();
browser.sleep(1000);
browser.sleep(500);
}).then(function (){
browser.sleep(1000);
// if(i === 1){
clickClass.delete(ITEM_SIDE_SELECT, true);
element.all(by.css('.ui-symbol.view-control.ng-binding.ng-scope')).click();
// }else{
browser.sleep(1000);
browser.sleep(500);
clickClass.delete(ITEM_SIDE_SELECT, true);
//element.all(by.css('.ui-symbol.view-control.ng-binding.ng-scope')).click();
var MyItem = ">\nF\nMy Items"
element.all(by.repeater('child in composition')).filter(function (ele){
return ele.getText().then(function(text) {
//expect(text).toEqual(MyItem);
return text === MyItem;
});
}).all(by.css('.ui-symbol.view-control.ng-binding.ng-scope')).click();
// clickClass.delete(ITEM_SIDE_SELECT, false);
// }
});
}
browser.pause();

View File

@ -0,0 +1,59 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/StressTestBubble.jsStressTestBubble.js
var itemCreate = require("../common/CreateItem");
var itemEdit = require("../common/EditItem");
var right_click = require("../common/RightMenu.js");
describe('Create Folder', function() {
var clickClass = new right_click();
var createClass = new itemCreate();
var editItemClass = new itemEdit();
var ITEM_NAME = "Folder";
var ITEM_TYPE = "folder";
var ITEM_MENU_GLYPH = 'F\nFolder';
var ITEM_GRID_SELECT = 'P\nF\nFolder\n0 Items';
var ITEM_SIDE_SELECT = ">\nF\nFolder"
beforeEach(function() {
browser.ignoreSynchronization = true;
browser.get('http://localhost:1984/warp/');
browser.sleep(2000); // 20 seconds
});
it('should Create new Folder', function(){
browser.sleep(10000);
for(var i=0; i < 1000; i++){
var object = element.all(by.repeater('child in composition')).filter(function (ele){
return ele.getText().then(function(text) {
return text === ">\nF\nMy Items";
});
});
//browser.sleep(1000)
browser.actions().mouseMove(object.get(0)).perform();
//browser.actions().click(protractor.Button.RIGHT).perform();
element.all(by.css('.items-holder.grid.abs.ng-scope')).click();
}
browser.pause();
});
});

View File

@ -0,0 +1,56 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
var itemCreate = require("../common/CreateItem");
var itemEdit = require("../common/EditItem");
var right_click = require("../common/RightMenu.js");
describe('Create Folder', function() {
var clickClass = new right_click();
var createClass = new itemCreate();
var editItemClass = new itemEdit();
var ITEM_NAME = "Folder";
var ITEM_TYPE = "folder";
var ITEM_MENU_GLYPH = 'F\nFolder';
var ITEM_GRID_SELECT = 'P\nF\nFolder\n0 Items';
var ITEM_SIDE_SELECT = ">\nF\nFolder"
beforeEach(function() {
browser.ignoreSynchronization = true;
browser.get('http://localhost:1984/warp/');
browser.sleep(2000); // 20 seconds
});
it('should Create new Folder', function(){
browser.sleep(10000);
for(var i=0; i < 1000; i++){
createClass.createButton().click();
//browser.sleep(1000)
//browser.actions().mouseMove(object.get(0)).perform();
//browser.actions().click(protractor.Button.RIGHT).perform();
element.all(by.css('.items-holder.grid.abs.ng-scope')).click();
}
browser.pause();
});
});

View File

@ -0,0 +1,55 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
var itemCreate = require("../common/CreateItem");
var itemEdit = require("../common/EditItem");
var right_click = require("../common/RightMenu.js");
describe('Create Folder', function() {
var clickClass = new right_click();
var createClass = new itemCreate();
var editItemClass = new itemEdit();
var ITEM_NAME = "Folder";
var ITEM_TYPE = "folder";
var ITEM_MENU_GLYPH = 'F\nFolder';
var ITEM_GRID_SELECT = 'P\nF\nFolder\n0 Items';
var ITEM_SIDE_SELECT = ">\nF\nFolder"
beforeEach(function() {
browser.ignoreSynchronization = true;
browser.get('http://localhost:1984/warp/');
browser.sleep(2000); // 20 seconds
});
it('should Create new Folder', function(){
browser.sleep(10000);
for(var i=0; i < 1000; i++){
browser.wait(function() {
createClass.createButton().click();
return true;
}).then(function (){
element.all(by.css('.items-holder.grid.abs.ng-scope')).click();
})
}
browser.pause();
});
});

View File

@ -0,0 +1,61 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
var itemCreate = require("../common/CreateItem");
var itemEdit = require("../common/EditItem");
var right_click = require("../common/RightMenu.js");
var fullScreenFile = require("../common/FullScreen");
describe('Create Folder', function() {
var clickClass = new right_click();
var createClass = new itemCreate();
var editItemClass = new itemEdit();
var fullScreenClass = new fullScreenFile();
var ITEM_NAME = "Folder";
var ITEM_TYPE = "folder";
var ITEM_MENU_GLYPH = 'F\nFolder';
var ITEM_GRID_SELECT = 'P\nF\nFolder\n0 Items';
var ITEM_SIDE_SELECT = ">\nF\nFolder"
beforeEach(function() {
browser.ignoreSynchronization = true;
browser.get('http://localhost:1984/warp/');
browser.sleep(2000); // 20 seconds
});
it('should Create new Folder', function(){
browser.sleep(15000);
for(var i=0; i < 1000; i++){
fullScreenClass.newWidnow().click();
browser.getAllWindowHandles().then(function (handles) {
//browser.driver.switchTo().window(handles[1]);
browser.sleep(1000);
browser.driver.close();
browser.sleep(1000);
// browser.driver.switchTo().window(handles[0]);
});
}
browser.pause();
});
});

View File

@ -0,0 +1,59 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
var itemCreate = require("../common/CreateItem");
var itemEdit = require("../common/EditItem");
var right_click = require("../common/RightMenu.js");
describe('Create Folder', function() {
var clickClass = new right_click();
var createClass = new itemCreate();
var editItemClass = new itemEdit();
var ITEM_NAME = "Folder";
var ITEM_TYPE = "folder";
var ITEM_MENU_GLYPH = 'F\nFolder';
var ITEM_GRID_SELECT = 'P\nF\nFolder\n0 Items';
var ITEM_SIDE_SELECT = ">\nF\nFolder"
beforeEach(function() {
browser.ignoreSynchronization = true;
browser.get('http://localhost:1984/warp/');
browser.sleep(2000); // 20 seconds
});
it('should Create new Folder', function(){
browser.sleep(8000);
for(var i=0; i < 1000; i++){
var object = element.all(by.repeater('child in composition')).filter(function (ele){
return ele.getText().then(function(text) {
return text === ">\nF\nMy Items";
});
});
//browser.sleep(1000)
browser.actions().mouseMove(object.get(0)).perform();
browser.actions().click(protractor.Button.RIGHT).perform();
element.all(by.css('.items-holder.grid.abs.ng-scope')).click();
}
browser.pause();
});
});