Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Shivam Dave 2015-08-17 10:50:19 -07:00
commit 0cee5ad380
47 changed files with 3358 additions and 100 deletions

3
.gitignore vendored
View File

@ -20,9 +20,6 @@ closed-lib
# Node dependencies
node_modules
# Build documentation
docs
# Protractor logs
protractor/logs

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

@ -24,7 +24,7 @@
# Script to build and deploy docs to github pages.
OUTPUT_DIRECTORY="docs"
OUTPUT_DIRECTORY="target/docs"
REPOSITORY_URL="git@github.com:nasa/openmctweb.git"
BUILD_SHA=`git rev-parse head`
@ -39,7 +39,7 @@ if [ -d $OUTPUT_DIRECTORY ]; then
rm -rf $OUTPUT_DIRECTORY || exit 1
fi
npm run-script jsdoc
npm run docs
cd $OUTPUT_DIRECTORY || exit 1
echo "git init"

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

193
docs/gendocs.js Normal file
View File

@ -0,0 +1,193 @@
/*****************************************************************************
* 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 require,process,GLOBAL*/
/*jslint nomen: false */
// Usage:
// node gendocs.js --in <source directory> --out <dest directory>
var CONSTANTS = {
DIAGRAM_WIDTH: 800,
DIAGRAM_HEIGHT: 500
};
GLOBAL.window = GLOBAL.window || GLOBAL; // nomnoml expects window to be defined
(function () {
"use strict";
var fs = require("fs"),
mkdirp = require("mkdirp"),
path = require("path"),
glob = require("glob"),
marked = require("marked"),
split = require("split"),
stream = require("stream"),
nomnoml = require('nomnoml'),
Canvas = require('canvas'),
options = require("minimist")(process.argv.slice(2));
// Convert from nomnoml source to a target PNG file.
function renderNomnoml(source, target) {
var canvas =
new Canvas(CONSTANTS.DIAGRAM_WIDTH, CONSTANTS.DIAGRAM_HEIGHT);
nomnoml.draw(canvas, source, 1.0);
canvas.pngStream().pipe(fs.createWriteStream(target));
}
// Stream transform.
// Pulls out nomnoml diagrams from fenced code blocks and renders them
// as PNG files in the output directory, prefixed with a provided name.
// The fenced code blocks will be replaced with Markdown in the
// output of this stream.
function nomnomlifier(outputDirectory, prefix) {
var transform = new stream.Transform({ objectMode: true }),
isBuilding = false,
counter = 1,
outputPath,
source = "";
transform._transform = function (chunk, encoding, done) {
if (!isBuilding) {
if (chunk.trim().indexOf("```nomnoml") === 0) {
var outputFilename = prefix + '-' + counter + '.png';
outputPath = path.join(outputDirectory, outputFilename);
this.push([
"\n![Diagram ",
counter,
"](",
outputFilename,
")\n\n"
].join(""));
isBuilding = true;
source = "";
counter += 1;
} else {
// Otherwise, pass through
this.push(chunk + '\n');
}
} else {
if (chunk.trim() === "```") {
// End nomnoml
renderNomnoml(source, outputPath);
isBuilding = false;
} else {
source += chunk + '\n';
}
}
done();
};
return transform;
}
// Convert from Github-flavored Markdown to HTML
function gfmifier() {
var transform = new stream.Transform({ objectMode: true }),
markdown = "";
transform._transform = function (chunk, encoding, done) {
markdown += chunk;
done();
};
transform._flush = function (done) {
this.push("<html><body>\n");
this.push(marked(markdown));
this.push("\n</body></html>\n");
done();
};
return transform;
}
// Custom renderer for marked; converts relative links from md to html,
// and makes headings linkable.
function CustomRenderer() {
var renderer = new marked.Renderer(),
customRenderer = Object.create(renderer);
customRenderer.heading = function (text, level) {
var escapedText = (text || "").trim().toLowerCase().replace(/\W/g, "-"),
aOpen = "<a name=\"" + escapedText + "\" href=\"#" + escapedText + "\">",
aClose = "</a>";
return aOpen + renderer.heading.apply(renderer, arguments) + aClose;
};
// Change links to .md files to .html
customRenderer.link = function (href, title, text) {
// ...but only if they look like relative paths
return (href || "").indexOf(":") === -1 && href[0] !== "/" ?
renderer.link(href.replace(/\.md/, ".html"), title, text) :
renderer.link.apply(renderer, arguments);
};
return customRenderer;
}
options['in'] = options['in'] || options.i;
options.out = options.out || options.o;
marked.setOptions({
renderer: new CustomRenderer(),
gfm: true,
tables: true,
breaks: false,
pedantic: false,
sanitize: true,
smartLists: true,
smartypants: false
});
// Convert all markdown files.
// First, pull out nomnoml diagrams.
// Then, convert remaining Markdown to HTML.
glob(options['in'] + "/**/*.md", {}, function (err, files) {
files.forEach(function (file) {
var destination = file.replace(options['in'], options.out)
.replace(/md$/, "html"),
destPath = path.dirname(destination),
prefix = path.basename(destination).replace(/\.html$/, "");
mkdirp(destPath, function (err) {
fs.createReadStream(file, { encoding: 'utf8' })
.pipe(split())
.pipe(nomnomlifier(destPath, prefix))
.pipe(gfmifier())
.pipe(fs.createWriteStream(destination, {
encoding: 'utf8'
}));
});
});
});
// Also copy over all HTML, CSS, or PNG files
glob(options['in'] + "/**/*.@(html|css|png)", {}, function (err, files) {
files.forEach(function (file) {
var destination = file.replace(options['in'], options.out),
destPath = path.dirname(destination);
mkdirp(destPath, function (err) {
fs.createReadStream(file, { encoding: 'utf8' })
.pipe(fs.createWriteStream(destination, {
encoding: 'utf8'
}));
});
});
});
}());

View File

@ -0,0 +1,232 @@
# Overview
The framework layer's most basic responsibility is allowing individual
software components to communicate. The software components it recognizes
are:
* _Extensions_: Individual units of functionality that can be added to
or removed from Open MCT Web. _Extension categories_ distinguish what
type of functionality is being added/removed.
* _Bundles_: A grouping of related extensions
(named after an analogous concept from [OSGi](http://www.osgi.org/))
that may be added or removed as a group.
The framework layer operates by taking a set of active bundles, and
exposing extensions to one another as-needed, using
[dependency injection](https://en.wikipedia.org/wiki/Dependency_injection).
Extensions are responsible for declaring their dependencies in a
manner which the framework layer can understand.
```nomnoml
#direction: down
[Open MCT Web|
[Dependency injection framework]-->[Platform bundle #1]
[Dependency injection framework]-->[Platform bundle #2]
[Dependency injection framework]-->[Plugin bundle #1]
[Dependency injection framework]-->[Plugin bundle #2]
[Platform bundle #1|[Extensions]]
[Platform bundle #2|[Extensions]]
[Plugin bundle #1|[Extensions]]
[Plugin bundle #2|[Extensions]]
[Platform bundle #1]<->[Platform bundle #2]
[Plugin bundle #1]<->[Platform bundle #2]
[Plugin bundle #1]<->[Plugin bundle #2]
]
```
The "dependency injection framework" in this case is
[AngularJS](https://angularjs.org/). Open MCT Web's framework layer
is really just a thin wrapper over Angular that recognizes the
concepts of bundles and extensions (as declared in JSON files) and
registering extensions with Angular. It additionally acts as a
mediator between Angular and [RequireJS](http://requirejs.org/),
which is used to load JavaScript sources which implement
extensions.
```nomnoml
[Framework layer|
[AngularJS]<-[Framework Component]
[RequireJS]<-[Framework Component]
[Framework Component]1o-*[Bundles]
]
```
It is worth noting that _no other components_ are "aware" of the
framework component directly; Angular and Require are _used by_ the
framework components, and extensions in various bundles will have
their dependencies satisfied by Angular as a consequence of registration
activities which were performed by the framework component.
## Application Initialization
The framework component initializes an Open MCT Web application following
a simple sequence of steps.
```nomnoml
[<start> Start]->[<state> Load bundles.json]
[Load bundles.json]->[<state> Load bundle.json files]
[Load bundle.json files]->[<state> Resolve implementations]
[Resolve implementations]->[<state> Register with Angular]
[Register with Angular]->[<state> Bootstrap application]
[Bootstrap application]->[<end> End]
```
1. __Loading bundles.json.__ A file named `bundles.json` is loaded to determine
which bundles to load. Bundles are given in this file as relative paths
which point to bundle directories.
2. __Load bundle.json files.__ Individual bundle definitions are loaded; a
`bundle.json` file is expected in each bundle directory.
2. __Resolving implementations.__ Any scripts which provide implementations for
extensions exposed by bundles are loaded, using RequireJS.
3. __Register with Angular.__ Resolved extensions are registered with Angular,
such that they can be used by the application at run-time. This stage
includes both registration of Angular built-ins (directives, controllers,
routes, constants, and services) as well as registration of non-Angular
extensions.
4. __Bootstrap application.__ Once all extensions have been registered,
the Angular application
[is bootstrapped](https://docs.angularjs.org/guide/bootstrap).
## Architectural Paradigm
```nomnoml
[Extension]
[Extension]o->[Dependency #1]
[Extension]o->[Dependency #2]
[Extension]o->[Dependency #3]
```
Open MCT Web's architecture relies on a simple premise: Individual units
(extensions) only have access to the dependencies they declare that they
need, and they acquire references to these dependencies via dependency
injection. This has several desirable traits:
* Programming to an interface is enforced. Any given dependency can be
swapped out for something which exposes an equivalent interface. This
improves flexibility against refactoring, simplifies testing, and
provides a common mechanism for extension and reconfiguration.
* The dependencies of a unit must be explicitly defined. This means that
it can be easily determined what a given unit's role is within the
larger system, in terms of what other components it will interact with.
It also helps to enforce good separation of concerns: When a set of
declared dependencies becomes long it is obvious, and this is usually
a sign that a given unit is involved in too many concerns and should
be refactored into smaller pieces.
* Individual units do not need to be aware of the framework; they need
only be aware of the interfaces to the components they specifically
use. This avoids introducing a ubiquitous dependency upon the framework
layer itself; it is plausible to modify or replace the framework
without making changes to individual software components which run upon
the framework.
A drawback to this approach is that it makes it difficult to define
"the architecture" of Open MCT Web, in terms of describing the specific
units that interact at run-time. The run-time architecture is determined
by the framework as the consequence of wiring together dependencies.
As such, the specific architecture of any given application built on
Open MCT Web can look very different.
Keeping that in mind, there are a few useful patterns supported by the
framework that are useful to keep in mind.
The specific service infrastructure provided by the platform is described
in the [Platform Architecture](Platform.md).
## Extension Categories
One of the capabilities that the framework component layers on top of
AngularJS is support for many-to-one dependencies. That is, a specific
extension may declare a dependency to _all extensions of a specific
category_, instead of being limited to declaring specific dependencies.
```nomnoml
#direction: right
[Specific Extension] 1 o-> * [Extension of Some Category]
```
This is useful for introducing specific extension points to an application.
Some unit of software will depend upon all extensions of a given category
and integrate their behavior into the system in some fashion; plugin authors
can then add new extensions of that category to augment existing behaviors.
Some developers may be familiar with the use of registries to achieve
similar characteristics. This approach is similar, except that the registry
is effectively implicit whenever a new extension category is used or
depended-upon. This has some advantages over a more straightforward
registry-based approach:
* These many-to-one relationships are expressed as dependencies; an
extension category is registered as having dependencies on all individual
extensions of this category. This avoids ordering issues that may occur
with more conventional registries, which may be observed before all
dependencies are resolved.
* The need for service registries of specific types is removed, reducing
the number of interfaces to manage within the system. Groups of
extensions are provided as arrays.
## Composite Services
Composite services (registered via extension category `components`) are
a pattern supported by the framework. These allow service instances to
be built from multiple components at run-time; support for this pattern
allows additional bundles to introduce or modify behavior associated
with these services without modifying or replacing original service
instances.
```nomnoml
#direction: down
[<abstract> FooService]
[FooDecorator #1]--:>[FooService]
[FooDecorator #n]--:>[FooService]
[FooAggregator]--:>[FooService]
[FooProvider #1]--:>[FooService]
[FooProvider #n]--:>[FooService]
[FooDecorator #1]o->[<state> ...decorators...]
[...decorators...]o->[FooDecorator #n]
[FooDecorator #n]o->[FooAggregator]
[FooAggregator]o->[FooProvider #1]
[FooAggregator]o->[<state> ...providers...]
[FooAggregator]o->[FooProvider #n]
[FooDecorator #1]--[<note> Exposed as fooService]
```
In this pattern, components all implement an interface which is
standardized for that service. Components additionally declare
that they belong to one of three types:
* __Providers.__ A provider actually implements the behavior
(satisfies the contract) for that kind of service. For instance,
if a service is responsible for looking up documents by an identifier,
one provider may do so by querying a database, while another may
do so by reading a static JSON document. From the outside, either
provider would look the same (they expose the same interface) and
they could be swapped out easily.
* __Aggregator.__ An aggregator takes many providers and makes them
behave as one. Again, this implements the same interface as an
individual provider, so users of the service do not need to be
concerned about the difference between consulting many providers
and consulting one. Continuing with the example of a service that
looks up documents by identifiers, an aggregator here might consult
all providers, and return any document is found (perhaps picking one
over the other or merging documents if there are multiple matches.)
* __Decorators.__ A decorator exposes the same interface as other
components, but instead of fully implementing the behavior associated
with that kind of service, it only acts as an intermediary, delegating
the actual behavior to a different component. Decorators may transform
inputs or outputs, or initiate some side effects associated with a
service. This is useful if certain common behavior associated with a
service (caching, for instance) may be useful across many different
implementations of that same service.
The framework will register extensions in this category such that an
aggregator will depend on all of its providers, and decorators will
depend upon on one another in a chain. The result of this compositing step
(the last decorator, if any; otherwise the aggregator, if any;
otherwise a single provider) will be exposed as a single service that
other extensions can acquire through dependency injection. Because all
components of the same type of service expose the same interface, users
of that service do not need to be aware that they are talking to an
aggregator or a provider, for instance.

View File

@ -0,0 +1,714 @@
# Overview
The Open MCT Web platform utilizes the [framework layer](Framework.md)
to provide an extensible baseline for applications which includes:
* A common user interface (and user interface paradigm) for dealing with
domain objects of various sorts.
* A variety of extension points for introducing new functionality
of various kinds within the context of the common user interface.
* A service infrastructure to support building additional components.
## Platform Architecture
While the framework provides a more general architectural paradigm for
building application, the platform adds more specificity by defining
additional extension types and allowing for integration with back end
components.
The run-time architecture of an Open MCT Web application can be categorized
into certain high-level tiers:
```nomnoml
[DOM]->[<state> AngularJS]
[AngularJS]->[Presentation Layer]
[Presentation Layer]->[Information Model]
[Presentation Layer]->[Service Infrastructure]
[Information Model]->[Service Infrastructure]
[Service Infrastructure]->[<state> Browser APIs]
[Browser APIs]->[Back-end]
```
Applications built using Open MCT Web may add or configure functionality
in __any of these tiers__.
* _DOM_: The rendered HTML document, composed from HTML templates which
have been processed by AngularJS and will be updated by AngularJS
to reflect changes from the presentation layer. User interactions
are initiated from here and invoke behavior in the presentation layer.
* [_Presentation layer_](#presentation-layer): The presentation layer
is responsible for updating (and providing information to update)
the displayed state of the application. The presentation layer consists
primarily of _controllers_ and _directives_. The presentation layer is
concerned with inspecting the information model and preparing it for
display.
* [_Information model_](#information-model): The information model
describes the state and behavior of the objects with which the user
interacts.
* [_Service infrastructure_](#service-infrastructure): The service
infrastructure is responsible for providing the underlying general
functionality needed to support the information model. This includes
exposing underlying sets of extensions and mediating with the
back-end.
* _Back-end_: The back-end is out of the scope of Open MCT Web, except
for the interfaces which are utilized by adapters participating in the
service infrastructure.
## Application Start-up
Once the
[application has been initialized](Framework.md#application-initialization)
Open MCT Web primarily operates in an event-driven paradigm; various
events (mouse clicks, timers firing, receiving responses to XHRs) trigger
the invocation of functions, typically in the presentation layer for
user actions or in the service infrastructure for server responses.
The "main point of entry" into an initialized Open MCT Web application
is effectively the
[route](https://docs.angularjs.org/api/ngRoute/service/$route#example)
which is associated with the URL used to access Open MCT Web (or a
default route.) This route will be associated with a template which
will be displayed; this template will include references to directives
and controllers which will be interpreted by Angular and used to
initialize the state of the display in a manner which is backed by
both the information model and the service infrastructure.
```nomnoml
[<start> Start]->[<state> page load]
[page load]->[<state> route selection]
[route selection]->[<state> compile, display template]
[compile, display template]->[Template]
[Template]->[<state> use Controllers]
[Template]->[<state> use Directives]
[use Controllers]->[Controllers]
[use Directives]->[Directives]
[Controllers]->[<state> consult information model]
[consult information model]->[<state> expose data]
[expose data]->[Angular]
[Angular]->[<state> update display]
[Directives]->[<state> add event listeners]
[Directives]->[<state> update display]
[add event listeners]->[<end> End]
[update display]->[<end> End]
```
# Presentation Layer
The presentation layer of Open MCT Web is responsible for providing
information to display within templates, and for handling interactions
which are initiated from templated DOM elements. AngularJS acts as
an intermediary between the web page as the user sees it, and the
presentation layer implemented as Open MCT Web extensions.
```nomnoml
[Presentation Layer|
[Angular built-ins|
[routes]
[controllers]
[directives]
[templates]
]
[Domain object representation|
[views]
[representations]
[representers]
[gestures]
]
]
```
## Angular built-ins
Several extension categories in the presentation layer map directly
to primitives from AngularJS:
* [_Controllers_](https://docs.angularjs.org/guide/controller) provide
data to templates, and expose functionality that can be called from
templates.
* [_Directives_](https://docs.angularjs.org/guide/directive) effectively
extend HTML to provide custom behavior associated with specific
attributes and tags.
* [_Routes_](https://docs.angularjs.org/api/ngRoute/service/$route#example)
are used to associate specific URLs (including the fragment identifier)
with specific application states. (In Open MCT Web, these are used to
describe the mode of usage - e.g. browse or edit - as well as to
identify the object being used.)
* [_Templates_](https://docs.angularjs.org/guide/templates) are partial
HTML documents that will be rendered and kept up-to-date by AngularJS.
Open MCT Web introduces a custom `mct-include` directive which acts
as a wrapper around `ng-include` to allow templates to be referred
to by symbolic names.
## Domain object representation
The remaining extension categories in the presentation layer are specific
to displaying domain objects.
* _Representations_ are templates that will be used to display
domain objects in specific ways (e.g. "as a tree node.")
* _Views_ are representations which are exposed to the user as options
for displaying domain objects.
* _Representers_ are extensions which modify or augment the process
of representing domain objects generally (e.g. by attaching
gestures to them.)
* _Gestures_ provide associations between specific user actions
(expressed as DOM events) and resulting behavior upon domain objects
(typically expressed as members of the `actions` extension category)
that can be reused across domain objects. For instance, `drag` and
`drop` are both gestures associated with using drag-and-drop to
modify the composition of domain objects by interacting with their
representations.
# Information Model
```nomnoml
#direction: right
[Information Model|
[DomainObject|
getId() : string
getModel() : object
getCapability(key : string) : Capability
hasCapability(key : string) : boolean
useCapability(key : string, args...) : *
]
[DomainObject] 1 +- 1 [Model]
[DomainObject] 1 o- * [Capability]
]
```
Domain objects are the most fundamental component of Open MCT Web's
information model. A domain object is some distinct thing relevant to a
user's work flow, such as a telemetry channel, display, or similar.
Open MCT Web is a tool for viewing, browsing, manipulating, and otherwise
interacting with a graph of domain objects.
A domain object should be conceived of as the union of the following:
* _Identifier_: A machine-readable string that uniquely identifies the
domain object within this application instance.
* _Model_: The persistent state of the domain object. A domain object's
model is a JavaScript object that can be losslessly converted to JSON.
* _Capabilities_: Dynamic behavior associated with the domain object.
Capabilities are JavaScript objects which provide additional methods
for interacting with the domain objects which expose those capabilities.
Not all domain objects expose all capabilities. The interface exposed
by any given capability will depend on its type (as identified
by the `key` argument.) For instance, a `persistence` capability
has a different interface from a `telemetry` capability. Using
capabilities requires some prior knowledge of their interface.
## Capabilities and Services
```nomnoml
#direction: right
[DomainObject]o-[FooCapability]
[FooCapability]o-[FooService]
[FooService]o-[foos]
```
At run-time, the user is primarily concerned with interacting with
domain objects. These interactions are ultimately supported via back-end
services, but to allow customization per-object, these are often mediated
by capabilities.
A common pattern that emerges in the Open MCT Platform is as follows:
* A `DomainObject` has some particular behavior that will be supported
by a service.
* A `Capability` of that domain object will define that behavior,
_for that domain object_, supported by a service.
* A `Service` utilized by that capability will perform the actual behavior.
* An extension category will be utilized by that capability to determine
the set of possible behaviors.
Concrete examples of capabilities which follow this pattern
(or a subset of this pattern) include:
```nomnoml
#direction: right
[DomainObject]1 o- *[Capability]
[Capability]<:--[TypeCapability]
[Capability]<:--[ActionCapability]
[Capability]<:--[PersistenceCapability]
[Capability]<:--[TelemetryCapability]
[TypeCapability]o-[TypeService]
[TypeService]o-[types]
[ActionCapability]o-[ActionService]
[ActionService]o-[actions]
[PersistenceCapability]o-[PersistenceService]
[TelemetryCapability]o-[TelemetryService]
```
# Service Infrastructure
Most services exposed by the Open MCT Web platform follow the
[composite services](Framework.md#composite-services) to permit
a higher degree of flexibility in how a service can be modified
or customized for specific applications.
To simplify usage for plugin developers, the platform also usually
includes a provider implementation for these service type that consumes
some extension category. For instance, an `ActionService` provider is
included which depends upon extension category `actions`, and exposes
all actions declared as such to the system. As such, plugin developers
can simply implement the new actions they wish to be made available without
worrying about the details of composite services or implementing a new
`ActionService` provider; however, the ability to implement a new provider
remains useful when the expressive power of individual extensions is
insufficient.
```nomnoml
[ Service Infrastructure |
[ObjectService]->[ModelService]
[ModelService]->[PersistenceService]
[ObjectService]->[CapabilityService]
[CapabilityService]->[capabilities]
[capabilities]->[TelemetryService]
[capabilities]->[PersistenceService]
[capabilities]->[TypeService]
[capabilities]->[ActionService]
[capabilities]->[ViewService]
[PersistenceService]->[<database> Document store]
[TelemetryService]->[<database> Telemetry source]
[ActionService]->[actions]
[ActionService]->[PolicyService]
[ViewService]->[PolicyService]
[ViewService]->[views]
[PolicyService]->[policies]
[TypeService]->[types]
]
```
A short summary of the roles of these services:
* _[ObjectService](#object-service)_: Allows retrieval of domain objects by
their identifiers; in practice, often the main point of entry into the
[information model](#information-model).
* _[ModelService](#model-service)_: Provides domain object models, retrieved
by their identifier.
* _[CapabilityService](#capability-service)_: Provides capabilities, as they
apply to specific domain objects (as judged from their model.)
* _[TelemetryService](#telemetry-service)_: Provides access to historical
and real-time telemetry data.
* _[PersistenceService](#persistence-service)_: Provides the ability to
store and retrieve documents (such as domain object models.)
* _[ActionService](#action-service)_: Provides distinct user actions that
can take place within the system (typically, upon or using domain objects.)
* _[ViewService](#view-service)_: Provides views for domain objects. A view
is a user-selectable representation of a domain object (in practice, an
HTML template.)
* _[PolicyService](#policy-service)_: Handles decisions about which
behavior are allowed within certain specific contexts.
* _[TypeService](#type-service)_: Provides information to distinguish
different types of domain objects from one another within the system.
## Object Service
```nomnoml
#direction: right
[<abstract> ObjectService|
getObjects(ids : Array.<string>) : Promise.<object.<string, DomainObject>>
]
[DomainObjectProvider]--:>[ObjectService]
[DomainObjectProvider]o-[ModelService]
[DomainObjectProvider]o-[CapabilityService]
```
As domain objects are central to Open MCT Web's information model,
acquiring domain objects is equally important.
```nomnoml
#direction: right
[<start> Start]->[<state> Look up models]
[<state> Look up models]->[<state> Look up capabilities]
[<state> Look up capabilities]->[<state> Instantiate DomainObject]
[<state> Instantiate DomainObject]->[<end> End]
```
Open MCT Web includes an implementation of an `ObjectService` which
satisfies this capability by:
* Consulting the [Model Service](#model-service) to acquire domain object
models by identifier.
* Passing these models to a [Capability Service](#capability-service) to
determine which capabilities are applicable.
* Combining these results together as [DomainObject](#information-model)
instances.
## Model Service
```nomnoml
#direction: down
[<abstract> ModelService|
getModels(ids : Array.<string>) : Promise.<object.<string, object>>
]
[StaticModelProvider]--:>[ModelService]
[RootModelProvider]--:>[ModelService]
[PersistedModelProvider]--:>[ModelService]
[ModelAggregator]--:>[ModelService]
[CachingModelDecorator]--:>[ModelService]
[MissingModelDecorator]--:>[ModelService]
[MissingModelDecorator]o-[CachingModelDecorator]
[CachingModelDecorator]o-[ModelAggregator]
[ModelAggregator]o-[StaticModelProvider]
[ModelAggregator]o-[RootModelProvider]
[ModelAggregator]o-[PersistedModelProvider]
[PersistedModelProvider]o-[PersistenceService]
[RootModelProvider]o-[roots]
[StaticModelProvider]o-[models]
```
The platform's model service is responsible for providing domain object
models (effectively, JSON documents describing the persistent state
associated with domain objects.) These are retrieved by identifier.
The platform includes multiple components of this variety:
* `PersistedModelProvider` looks up domain object models from
a persistence store (the [`PersistenceService`](#persistence-service));
this is how user-created and user-modified
domain object models are retrieved.
* `RootModelProvider` provides domain object models that have been
declared via the `roots` extension category. These will appear at the
top level of the tree hierarchy in the user interface.
* `StaticModelProvider` provides domain object models that have been
declared via the `models` extension category. This is useful for
allowing plugins to expose new domain objects declaratively.
* `ModelAggregator` merges together the results from multiple providers.
If multiple providers return models for the same domain object,
the most recently modified version (as determined by the `modified`
property of the model) is chosen.
* `CachingModelDecorator` caches model instances in memory. This
ensures that only a single instance of a domain object model is
present at any given time within the application, and prevent
redundant retrievals.
* `MissingModelDecorator` adds in placeholders when no providers
have returned domain object models for a specific identifier. This
allows the user to easily see that something was expected to be
present, but wasn't.
## Capability Service
```nomnoml
#direction: down
[<abstract> CapabilityService|
getCapabilities(model : object) : object.<string, Function>
]
[CoreCapabilityProvider]--:>[CapabilityService]
[QueuingPersistenceCapabilityDecorator]--:>[CapabilityService]
[CoreCapabilityProvider]o-[capabilities]
[QueuingPersistenceCapabilityDecorator]o-[CoreCapabilityProvider]
```
The capability service is responsible for determining which capabilities
are applicable for a given domain object, based on its model. Primarily,
this is handled by the `CoreCapabilityProvider`, which examines
capabilities exposed via the `capabilities` extension category.
Additionally, `platform/persistence/queue` decorates the persistence
capability specifically to batch persistence attempts among multiple
objects (this allows failures to be recognized and handled in groups.)
## Telemetry Service
```nomnoml
[<abstract> TelemetryService|
requestData(requests : Array.<TelemetryRequest>) : Promise.<object>
subscribe(requests : Array.<TelemetryRequest>) : Function
]<--:[TelemetryAggregator]
```
The telemetry service is responsible for acquiring telemetry data.
Notably, the platform does not include any providers for
`TelemetryService`; applications built on Open MCT Web will need to
implement a provider for this service if they wish to expose telemetry
data. This is usually the most important step for integrating Open MCT Web
into an existing telemetry system.
Requests for telemetry data are usually initiated in the
[presentation layer](#presentation-layer) by some `Controller` referenced
from a view. The `telemetryHandler` service is most commonly used (although
one could also use an object's `telemetry` capability directly) as this
handles capability delegation, by which a domain object such as a Telemetry
Panel can declare that its `telemetry` capability should be handled by the
objects it contains. Ultimately, the request for historical data and the
new subscriptions will reach the `TelemetryService`, and, by way of the
provider(s) which are present for that `TelemetryService`, will pass the
same requests to the back-end.
```nomnoml
[<start> Start]->[Controller]
[Controller]->[<state> declares object of interest]
[declares object of interest]->[TelemetryHandler]
[TelemetryHandler]->[<state> requests telemetry from capabilities]
[TelemetryHandler]->[<state> subscribes to telemetry using capabilities]
[requests telemetry from capabilities]->[TelemetryCapability]
[subscribes to telemetry using capabilities]->[TelemetryCapability]
[TelemetryCapability]->[<state> requests telemetry]
[TelemetryCapability]->[<state> subscribes to telemetry]
[requests telemetry]->[TelemetryService]
[subscribes to telemetry]->[TelemetryService]
[TelemetryService]->[<state> issues request]
[TelemetryService]->[<state> updates subscriptions]
[TelemetryService]->[<state> listens for real-time data]
[issues request]->[<database> Telemetry Back-end]
[updates subscriptions]->[Telemetry Back-end]
[listens for real-time data]->[Telemetry Back-end]
[Telemetry Back-end]->[<end> End]
```
The back-end, in turn, is expected to provide whatever historical
telemetry is available to satisfy the request that has been issue.
```nomnoml
[<start> Start]->[<database> Telemetry Back-end]
[Telemetry Back-end]->[<state> transmits historical telemetry]
[transmits historical telemetry]->[TelemetryService]
[TelemetryService]->[<state> packages telemetry, fulfills requests]
[packages telemetry, fulfills requests]->[TelemetryCapability]
[TelemetryCapability]->[<state> unpacks telemetry per-object, fulfills request]
[unpacks telemetry per-object, fulfills request]->[TelemetryHandler]
[TelemetryHandler]->[<state> exposes data]
[TelemetryHandler]->[<state> notifies controller]
[exposes data]->[Controller]
[notifies controller]->[Controller]
[Controller]->[<state> prepares data for template]
[prepares data for template]->[Template]
[Template]->[<state> displays data]
[displays data]->[<end> End]
```
One peculiarity of this approach is that we package many responses
together at once in the `TelemetryService`, then unpack these in the
`TelemetryCapability`, then repackage these in the `TelemetryHandler`.
The rationale for this is as follows:
* In the `TelemetryService`, we want to have the ability to combine
multiple requests into one call to the back-end, as many back-ends
will support this. It follows that we give the response as a single
object, packages in a manner that allows responses to individual
requests to be easily identified.
* In the `TelemetryCapability`, we want to provide telemetry for a
_single object_, so the telemetry data gets unpacked. This allows
for the unpacking of data to be handled in a single place, and
also permits a flexible substitution method; domain objects may have
implementations of the `telemetry` capability that do not use the
`TelemetryService` at all, while still maintaining compatibility
with any presentation layer code written to utilize this capability.
(This is true of capabilities generally.)
* In the `TelemetryHandler`, we want to group multiple responses back
together again to make it easy for the presentation layer to consume.
In this case, the grouping is different from what may have occurred
in the `TelemetryService`; this grouping is based on what is expected
to be useful _in a specific view_. The `TelemetryService`
may be receiving requests from multiple views.
```nomnoml
[<start> Start]->[<database> Telemetry Back-end]
[Telemetry Back-end]->[<state> notifies client of new data]
[notifies client of new data]->[TelemetryService]
[TelemetryService]->[<choice> relevant subscribers?]
[relevant subscribers?] yes ->[<state> notify subscribers]
[relevant subscribers?] no ->[<state> ignore]
[ignore]->[<end> Ignored]
[notify subscribers]->[TelemetryCapability]
[TelemetryCapability]->[<state> notify listener]
[notify listener]->[TelemetryHandler]
[TelemetryHandler]->[<state> exposes data]
[TelemetryHandler]->[<state> notifies controller]
[exposes data]->[Controller]
[notifies controller]->[Controller]
[Controller]->[<state> prepares data for template]
[prepares data for template]->[Template]
[Template]->[<state> displays data]
[displays data]->[<end> End]
```
The flow of real-time data is similar, and is handled by a sequence
of callbacks between the presentation layer component which is
interested in data and the telemetry service. Providers in the
telemetry service listen to the back-end for new data (via whatever
mechanism their specific back-end supports), package this data in
the same manner as historical data, and pass that to the callbacks
which are associated with relevant requests.
## Persistence Service
```nomnoml
#direction: right
[<abstract> PersistenceService|
listSpaces() : Promise.<Array.<string>>
listObjects() : Promise.<Array.<string>>
createObject(space : string, key : string, document : object) : Promise.<boolean>
readObject(space : string, key : string, document : object) : Promise.<object>
updateObject(space : string, key : string, document : object) : Promise.<boolean>
deleteObject(space : string, key : string, document : object) : Promise.<boolean>
]
[ElasticPersistenceProvider]--:>[PersistenceService]
[ElasticPersistenceProvider]->[<database> ElasticSearch]
[CouchPersistenceProvider]--:>[PersistenceService]
[CouchPersistenceProvider]->[<database> CouchDB]
```
Closely related to the notion of domain objects models is their
persistence. The `PersistenceService` allows these to be saved
and loaded. (Currently, this capability is only used for domain
object models, but the interface has been designed without this idea
in mind; other kinds of documents could be saved and loaded in the
same manner.)
There is no single definitive implementation of a `PersistenceService` in
the platform. Optional adapters are provided to store and load documents
from CouchDB and ElasticSearch, respectively; plugin authors may also
write additional adapters to utilize different back end technologies.
## Action Service
```nomnoml
[ActionService|
getActions(context : ActionContext) : Array.<Action>
]
[ActionProvider]--:>[ActionService]
[CreateActionProvider]--:>[ActionService]
[ActionAggregator]--:>[ActionService]
[LoggingActionDecorator]--:>[ActionService]
[PolicyActionDecorator]--:>[ActionService]
[LoggingActionDecorator]o-[PolicyActionDecorator]
[PolicyActionDecorator]o-[ActionAggregator]
[ActionAggregator]o-[ActionProvider]
[ActionAggregator]o-[CreateActionProvider]
[ActionProvider]o-[actions]
[CreateActionProvider]o-[TypeService]
[PolicyActionDecorator]o-[PolicyService]
```
Actions are discrete tasks or behaviors that can be initiated by a user
upon or using a domain object. Actions may appear as menu items or
buttons in the user interface, or may be triggered by certain gestures.
Responsibilities of platform components of the action service are as
follows:
* `ActionProvider` exposes actions registered via extension category
`actions`, supporting simple addition of new actions. Actions are
filtered down to match action contexts based on criteria defined as
part of an action's extension definition.
* `CreateActionProvider` provides the various Create actions which
populate the Create menu. These are driven by the available types,
so do not map easily ot extension category `actions`; instead, these
are generated after looking up which actions are available from the
[`TypeService`](#type-service).
* `ActionAggregator` merges together actions from multiple providers.
* `PolicyActionDecorator` enforces the `action` policy category by
filtering out actions which violate this policy, as determined by
consulting the [`PolicyService`](#policy-service).
* `LoggingActionDecorator` wraps exposed actions and writes to the
console when they are performed.
## View Service
```nomnoml
[ViewService|
getViews(domainObject : DomainObject) : Array.<View>
]
[ViewProvider]--:>[ViewService]
[PolicyViewDecorator]--:>[ViewService]
[ViewProvider]o-[views]
[PolicyViewDecorator]o-[ViewProvider]
```
The view service provides views that are relevant to a specified domain
object. A "view" is a user-selectable visualization of a domain object.
The responsibilities of components of the view service are as follows:
* `ViewProvider` exposes views registered via extension category
`views`, supporting simple addition of new views. Views are
filtered down to match domain objects based on criteria defined as
part of a view's extension definition.
* `PolicyViewDecorator` enforces the `view` policy category by
filtering out views which violate this policy, as determined by
consulting the [`PolicyService`](#policy-service).
## Policy Service
```nomnoml
[PolicyService|
allow(category : string, candidate : object, context : object, callback? : Function) : boolean
]
[PolicyProvider]--:>[PolicyService]
[PolicyProvider]o-[policies]
```
The policy service provides a general-purpose extensible decision-making
mechanism; plugins can add new extensions of category `policies` to
modify decisions of a known category.
Often, the policy service is referenced from a decorator for another
service, to filter down the results of using that service based on some
appropriate policy category.
The policy provider works by looking up all registered policy extensions
which are relevant to a particular _category_, then consulting each in
order to see if they allow a particular _candidate_ in a particular
_context_; the types for the `candidate` and `context` arguments will
vary depending on the `category`. Any one policy may disallow the
decision as a whole.
```nomnoml
[<start> Start]->[<state> is something allowed?]
[is something allowed?]->[PolicyService]
[PolicyService]->[<state> look up relevant policies by category]
[look up relevant policies by category]->[<state> consult policy #1]
[consult policy #1]->[Policy #1]
[Policy #1]->[<choice> policy #1 allows?]
[policy #1 allows?] no ->[<state> decision disallowed]
[policy #1 allows?] yes ->[<state> consult policy #2]
[consult policy #2]->[Policy #2]
[Policy #2]->[<choice> policy #2 allows?]
[policy #2 allows?] no ->[<state> decision disallowed]
[policy #2 allows?] yes ->[<state> consult policy #3]
[consult policy #3]->[<state> ...]
[...]->[<state> consult policy #n]
[consult policy #n]->[Policy #n]
[Policy #n]->[<choice> policy #n allows?]
[policy #n allows?] no ->[<state> decision disallowed]
[policy #n allows?] yes ->[<state> decision allowed]
[decision disallowed]->[<end> Disallowed]
[decision allowed]->[<end> Allowed]
```
The policy decision is effectively an "and" operation over the individual
policy decisions: That is, all policies must agree to allow a particular
policy decision, and the first policy to disallow a decision will cause
the entire decision to be disallowed. As a consequence of this, policies
should generally be written with a default behavior of "allow", and
should only disallow the specific circumstances they are intended to
disallow.
## Type Service
```nomnoml
[TypeService|
listTypes() : Array.<Type>
getType(key : string) : Type
]
[TypeProvider]--:>[TypeService]
[TypeProvider]o-[types]
```
The type service provides metadata about the different types of domain
objects that exist within an Open MCT Web application. The platform
implementation reads these types in from extension category `types`
and wraps them in a JavaScript interface.

View File

@ -0,0 +1,78 @@
# Introduction
The purpose of this document is to familiarize developers with the
overall architecture of Open MCT Web.
The target audience includes:
* _Platform maintainers_: Individuals involved in developing,
extending, and maintaing capabilities of the platform.
* _Integration developers_: Individuals tasked with integrated
Open MCT Web into a larger system, who need to understand
its inner workings sufficiently to complete this integration.
As the focus of this document is on architecture, whenever possible
implementation details (such as relevant API or JSON syntax) have been
omitted. These details may be found in the developer guide.
# Overview
Open MCT Web is client software: It runs in a web browser and
provides a user interface, while communicating with various
server-side resources through browser APIs.
```nomnoml
#direction: right
[Client|[Browser|[Open MCT Web]->[Browser APIs]]]
[Server|[Web services]]
[Client]<->[Server]
```
While Open MCT Web can be configured to run as a standalone client,
this is rarely very useful. Instead, it is intended to be used as a
display and interaction layer for information obtained from a
variety of back-end services. Doing so requires authoring or utilizing
adapter plugins which allow Open MCT Web to interact with these services.
Typically, the pattern here is to provide a known interface that
Open MCT Web can utilize, and implement it such that it interacts with
whatever back-end provides the relevant information.
Examples of back-ends that can be utilized in this fashion include
databases for the persistence of user-created objects, or sources of
telemetry data.
## Software Architecture
The simplest overview of Open MCT Web is to look at it as a "layered"
architecture, where each layer more clearly specifies the behavior
of the software.
```nomnoml
#direction: down
[Open MCT Web|
[Platform]<->[Application]
[Framework]->[Application]
[Framework]->[Platform]
]
```
These layers are:
* [_Framework_](Framework.md): The framework layer is responsible for
managing the interactions between application components. It has no
application-specific knowledge; at this layer, we have only
established an abstraction by which different software components
may communicate and/or interact.
* [_Platform_](Platform.md): The platform layer defines the general look, feel, and
behavior of Open MCT Web. This includes user-facing components like
Browse mode and Edit mode, as well as underlying elements of the
information model and the general service infrastructure.
* _Application_: The application layer defines specific features of
an application built on Open MCT Web. This includes adapters to
specific back-ends, new types of things for users to create, and
new ways of visualizing objects within the system. This layer
typically consists of a mix of custom plug-ins to Open MCT Web,
as well as optional features (such as Plot view) included alongside
the platform.

3
docs/src/guide/index.md Normal file
View File

@ -0,0 +1,3 @@
# Developer Guide
This is a placeholder for the developer guide.

36
docs/src/index.html Normal file
View File

@ -0,0 +1,36 @@
<!--
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.
-->
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>Open MCT Web Documentation</title>
</head>
<body class="user-environ" ng-view>
Sections:
<ul>
<li><a href="api/">API</a></li>
<li><a href="guide/">Developer Guide</a></li>
<li><a href="architecture/">Architecture Overview</a></li>
</ul>
</body>
</html>

View File

@ -16,13 +16,21 @@
"karma-jasmine": "^0.1.5",
"karma-phantomjs-launcher": "^0.1.4",
"karma-requirejs": "^0.2.2",
"requirejs": "^2.1.17"
"requirejs": "^2.1.17",
"marked": "^0.3.5",
"glob": ">= 3.0.0",
"split": "^1.0.0",
"mkdirp": "^0.5.1",
"nomnoml": "^0.0.3",
"canvas": "^1.2.7"
},
"scripts": {
"start": "node app.js",
"test": "karma start --single-run",
"jshint": "jshint platform example || exit 0",
"jsdoc": "jsdoc -c jsdoc.json -r -d docs"
"jsdoc": "jsdoc -c jsdoc.json -r -d target/docs/api",
"otherdoc": "node docs/gendocs.js --in docs/src --out target/docs",
"docs": "npm run jsdoc ; npm run otherdoc"
},
"repository": {
"type": "git",

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"
]

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();
});
});