diff --git a/README.md b/README.md
index c36cbea653..42cd060282 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/bundles.json b/bundles.json
index 0b97f1abab..898ca3d738 100644
--- a/bundles.json
+++ b/bundles.json
@@ -21,6 +21,7 @@
"platform/persistence/queue",
"platform/policy",
"platform/entanglement",
+ "platform/search",
"example/imagery",
"example/persistence",
diff --git a/circle.yml b/circle.yml
index b8f367a604..2a79a5ed93 100644
--- a/circle.yml
+++ b/circle.yml
@@ -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
diff --git a/platform/commonUI/general/res/css/theme-espresso.css b/platform/commonUI/general/res/css/theme-espresso.css
index e3bbcd7448..26d9f9c142 100644
--- a/platform/commonUI/general/res/css/theme-espresso.css
+++ b/platform/commonUI/general/res/css/theme-espresso.css
@@ -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; }
diff --git a/platform/persistence/elastic/src/ElasticsearchSearchProvider.js b/platform/persistence/elastic/src/ElasticsearchSearchProvider.js
new file mode 100644
index 0000000000..af13628af9
--- /dev/null
+++ b/platform/persistence/elastic/src/ElasticsearchSearchProvider.js
@@ -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;
+ }
+);
\ No newline at end of file
diff --git a/platform/persistence/elastic/test/ElasticsearchSearchProviderSpec.js b/platform/persistence/elastic/test/ElasticsearchSearchProviderSpec.js
new file mode 100644
index 0000000000..1202ef77e6
--- /dev/null
+++ b/platform/persistence/elastic/test/ElasticsearchSearchProviderSpec.js
@@ -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();
+ });
+ });
+ }
+);
\ No newline at end of file
diff --git a/platform/persistence/elastic/test/suite.json b/platform/persistence/elastic/test/suite.json
index cc8dc2ce0c..939244c089 100644
--- a/platform/persistence/elastic/test/suite.json
+++ b/platform/persistence/elastic/test/suite.json
@@ -1,4 +1,5 @@
[
"ElasticIndicator",
- "ElasticPersistenceProvider"
+ "ElasticPersistenceProvider",
+ "ElasticsearchSearchProvider"
]
diff --git a/platform/search/bundle.json b/platform/search/bundle.json
new file mode 100644
index 0000000000..6668022939
--- /dev/null
+++ b/platform/search/bundle.json
@@ -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"
+ }
+ ]
+ }
+}
diff --git a/platform/search/src/GenericSearchProvider.js b/platform/search/src/GenericSearchProvider.js
new file mode 100644
index 0000000000..dae2cab9a9
--- /dev/null
+++ b/platform/search/src/GenericSearchProvider.js
@@ -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;
+ }
+);
\ No newline at end of file
diff --git a/platform/search/src/GenericSearchWorker.js b/platform/search/src/GenericSearchWorker.js
new file mode 100644
index 0000000000..69e4104602
--- /dev/null
+++ b/platform/search/src/GenericSearchWorker.js
@@ -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));
+ }
+ };
+}());
\ No newline at end of file
diff --git a/platform/search/src/SearchAggregator.js b/platform/search/src/SearchAggregator.js
new file mode 100644
index 0000000000..da267214bf
--- /dev/null
+++ b/platform/search/src/SearchAggregator.js
@@ -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;
+ }
+);
\ No newline at end of file
diff --git a/platform/search/test/GenericSearchProviderSpec.js b/platform/search/test/GenericSearchProviderSpec.js
new file mode 100644
index 0000000000..bec02653b8
--- /dev/null
+++ b/platform/search/test/GenericSearchProviderSpec.js
@@ -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();
+ });
+
+ });
+ }
+);
\ No newline at end of file
diff --git a/platform/search/test/GenericSearchWorkerSpec.js b/platform/search/test/GenericSearchWorkerSpec.js
new file mode 100644
index 0000000000..2e17858400
--- /dev/null
+++ b/platform/search/test/GenericSearchWorkerSpec.js
@@ -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();
+ });
+ });
+ });
+ }
+);
\ No newline at end of file
diff --git a/platform/search/test/SearchAggregatorSpec.js b/platform/search/test/SearchAggregatorSpec.js
new file mode 100644
index 0000000000..cf35e2928e
--- /dev/null
+++ b/platform/search/test/SearchAggregatorSpec.js
@@ -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);
+ }
+ });
+
+ });
+ }
+);
\ No newline at end of file
diff --git a/platform/search/test/suite.json b/platform/search/test/suite.json
new file mode 100644
index 0000000000..5097bde635
--- /dev/null
+++ b/platform/search/test/suite.json
@@ -0,0 +1,5 @@
+[
+ "SearchAggregator",
+ "GenericSearchProvider",
+ "GenericSearchWorker"
+]
diff --git a/pom.xml b/pom.xml
index 8ca3cd6edb..3acfe38fa1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
gov.nasa.arc.wtd
open-mct-web
Open MCT Web
- 0.8.0-SNAPSHOT
+ 0.8.1-SNAPSHOT
war
diff --git a/protractor/README b/protractor/README
new file mode 100644
index 0000000000..5734e5702d
--- /dev/null
+++ b/protractor/README
@@ -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.
\ No newline at end of file
diff --git a/protractor/UI/Fullscreen.js b/protractor/UI/Fullscreen.js
index 3c1c785228..532ad318d8 100644
--- a/protractor/UI/Fullscreen.js
+++ b/protractor/UI/Fullscreen.js
@@ -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'));
diff --git a/protractor/UI/InfoBubble.js b/protractor/UI/InfoBubble.js
index 274b7966b0..c88e9018d0 100644
--- a/protractor/UI/InfoBubble.js
+++ b/protractor/UI/InfoBubble.js
@@ -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();
diff --git a/protractor/UI/NewWindow.js b/protractor/UI/NewWindow.js
index 7a714d79a2..1e98bbc8b4 100644
--- a/protractor/UI/NewWindow.js
+++ b/protractor/UI/NewWindow.js
@@ -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();
diff --git a/protractor/UI/RightClick.js b/protractor/UI/RightClick.js
index 0ae4dd0708..5f7d389313 100644
--- a/protractor/UI/RightClick.js
+++ b/protractor/UI/RightClick.js
@@ -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);
});
});
diff --git a/protractor/bin/clean.js b/protractor/bin/clean.js
new file mode 100755
index 0000000000..82e776901f
--- /dev/null
+++ b/protractor/bin/clean.js
@@ -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")
diff --git a/protractor/bin/ctrl.sh b/protractor/bin/ctrl.sh
new file mode 100755
index 0000000000..faf385d2ad
--- /dev/null
+++ b/protractor/bin/ctrl.sh
@@ -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
diff --git a/protractor/bin/run.js b/protractor/bin/run.js
new file mode 100755
index 0000000000..316caa11d0
--- /dev/null
+++ b/protractor/bin/run.js
@@ -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);
+ }
+});
\ No newline at end of file
diff --git a/protractor/bin/start.js b/protractor/bin/start.js
new file mode 100755
index 0000000000..21aacc7efe
--- /dev/null
+++ b/protractor/bin/start.js
@@ -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);
diff --git a/protractor/bin/stop.js b/protractor/bin/stop.js
new file mode 100755
index 0000000000..ac2c3b4295
--- /dev/null
+++ b/protractor/bin/stop.js
@@ -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);
+ });
+ }
+ });
+});
+
+
diff --git a/protractor/common/Launch.js b/protractor/common/Launch.js
index 2b700672cc..fd745f94c3 100644
--- a/protractor/common/Launch.js
+++ b/protractor/common/Launch.js
@@ -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
};
diff --git a/protractor/common/RightMenu.js b/protractor/common/RightMenu.js
index 490d876d87..d1375d0533 100644
--- a/protractor/common/RightMenu.js
+++ b/protractor/common/RightMenu.js
@@ -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;
});
});
diff --git a/protractor/conf.js b/protractor/conf.js
index c33e196157..8b828fcbd7 100644
--- a/protractor/conf.js
+++ b/protractor/conf.js
@@ -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;
}
diff --git a/protractor/create/CreateActivityMode.js b/protractor/create/CreateActivityMode.js
index e9469749aa..17ed700ae6 100644
--- a/protractor/create/CreateActivityMode.js
+++ b/protractor/create/CreateActivityMode.js
@@ -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";
diff --git a/protractor/create/CreateClock.js b/protractor/create/CreateClock.js
index d1dc781b58..940db62af4 100644
--- a/protractor/create/CreateClock.js
+++ b/protractor/create/CreateClock.js
@@ -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);
+ })
+ });
+ });
+
+ })
});
});
diff --git a/protractor/create/CreateTimer.js b/protractor/create/CreateTimer.js
index d8059c1f59..e83fedfedf 100644
--- a/protractor/create/CreateTimer.js
+++ b/protractor/create/CreateTimer.js
@@ -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)
})
});
diff --git a/protractor/package.json b/protractor/package.json
new file mode 100644
index 0000000000..b43b9b1cdb
--- /dev/null
+++ b/protractor/package.json
@@ -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"
+}
diff --git a/protractor/StressTest.js b/protractor/stressTest/StressTest.js
similarity index 74%
rename from protractor/StressTest.js
rename to protractor/stressTest/StressTest.js
index 5e4a8b1507..108e431868 100644
--- a/protractor/StressTest.js
+++ b/protractor/stressTest/StressTest.js
@@ -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();
diff --git a/protractor/stressTest/StressTestBubble.js b/protractor/stressTest/StressTestBubble.js
new file mode 100644
index 0000000000..b06b29c1b9
--- /dev/null
+++ b/protractor/stressTest/StressTestBubble.js
@@ -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();
+
+ });
+
+});
diff --git a/protractor/stressTest/StressTestCreateButton.js b/protractor/stressTest/StressTestCreateButton.js
new file mode 100644
index 0000000000..25debf3bba
--- /dev/null
+++ b/protractor/stressTest/StressTestCreateButton.js
@@ -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();
+
+ });
+
+});
diff --git a/protractor/stressTest/StressTestMenu.js b/protractor/stressTest/StressTestMenu.js
new file mode 100644
index 0000000000..d6e30bc5b2
--- /dev/null
+++ b/protractor/stressTest/StressTestMenu.js
@@ -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();
+
+ });
+
+});
diff --git a/protractor/stressTest/StressTestNewPage.js b/protractor/stressTest/StressTestNewPage.js
new file mode 100644
index 0000000000..2b0e82fbb1
--- /dev/null
+++ b/protractor/stressTest/StressTestNewPage.js
@@ -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();
+
+ });
+
+});
diff --git a/protractor/stressTest/StressTestRightClick.js b/protractor/stressTest/StressTestRightClick.js
new file mode 100644
index 0000000000..f16f876a90
--- /dev/null
+++ b/protractor/stressTest/StressTestRightClick.js
@@ -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();
+
+ });
+
+});