mirror of
https://github.com/nasa/openmct.git
synced 2025-05-02 08:43:17 +00:00
Merge branch 'master' of https://github.com/nasa/openmctweb into searchservice
This commit is contained in:
commit
94531a39d0
3
.gitignore
vendored
3
.gitignore
vendored
@ -20,9 +20,6 @@ closed-lib
|
|||||||
# Node dependencies
|
# Node dependencies
|
||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
# Build documentation
|
|
||||||
docs
|
|
||||||
|
|
||||||
# Protractor logs
|
# Protractor logs
|
||||||
protractor/logs
|
protractor/logs
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ as described above.
|
|||||||
An example of this is expressed in `platform/framework`, which follows
|
An example of this is expressed in `platform/framework`, which follows
|
||||||
bundle conventions.
|
bundle conventions.
|
||||||
|
|
||||||
### Regression Testing
|
### Functional Testing
|
||||||
|
|
||||||
The tests described above are all at the unit-level; an additional
|
The tests described above are all at the unit-level; an additional
|
||||||
test suite using [Protractor](https://angular.github.io/protractor/)
|
test suite using [Protractor](https://angular.github.io/protractor/)
|
||||||
@ -76,9 +76,9 @@ us under development, in the `protractor` folder.
|
|||||||
To run:
|
To run:
|
||||||
|
|
||||||
* Install protractor following the instructions above.
|
* Install protractor following the instructions above.
|
||||||
* `webdriver-manager start`
|
* `cd protractor`
|
||||||
* `node app.js -p 1984 -x platform/persistence/elastic -i example/persistence
|
* `npm install`
|
||||||
* `protractor protractor/conf.js`
|
* `npm run all`
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
# Script to build and deploy docs to github pages.
|
# Script to build and deploy docs to github pages.
|
||||||
|
|
||||||
OUTPUT_DIRECTORY="docs"
|
OUTPUT_DIRECTORY="target/docs"
|
||||||
REPOSITORY_URL="git@github.com:nasa/openmctweb.git"
|
REPOSITORY_URL="git@github.com:nasa/openmctweb.git"
|
||||||
|
|
||||||
BUILD_SHA=`git rev-parse head`
|
BUILD_SHA=`git rev-parse head`
|
||||||
@ -39,7 +39,7 @@ if [ -d $OUTPUT_DIRECTORY ]; then
|
|||||||
rm -rf $OUTPUT_DIRECTORY || exit 1
|
rm -rf $OUTPUT_DIRECTORY || exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
npm run-script jsdoc
|
npm run docs
|
||||||
cd $OUTPUT_DIRECTORY || exit 1
|
cd $OUTPUT_DIRECTORY || exit 1
|
||||||
|
|
||||||
echo "git init"
|
echo "git init"
|
||||||
|
193
docs/gendocs.js
Normal file
193
docs/gendocs.js
Normal 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\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'
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}());
|
232
docs/src/architecture/Framework.md
Normal file
232
docs/src/architecture/Framework.md
Normal 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.
|
714
docs/src/architecture/Platform.md
Normal file
714
docs/src/architecture/Platform.md
Normal 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.
|
78
docs/src/architecture/index.md
Normal file
78
docs/src/architecture/index.md
Normal 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
3
docs/src/guide/index.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Developer Guide
|
||||||
|
|
||||||
|
This is a placeholder for the developer guide.
|
36
docs/src/index.html
Normal file
36
docs/src/index.html
Normal 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>
|
12
package.json
12
package.json
@ -16,13 +16,21 @@
|
|||||||
"karma-jasmine": "^0.1.5",
|
"karma-jasmine": "^0.1.5",
|
||||||
"karma-phantomjs-launcher": "^0.1.4",
|
"karma-phantomjs-launcher": "^0.1.4",
|
||||||
"karma-requirejs": "^0.2.2",
|
"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": {
|
"scripts": {
|
||||||
"start": "node app.js",
|
"start": "node app.js",
|
||||||
"test": "karma start --single-run",
|
"test": "karma start --single-run",
|
||||||
"jshint": "jshint platform example || exit 0",
|
"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": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -23,9 +23,8 @@
|
|||||||
<div class="pane left menu-items">
|
<div class="pane left menu-items">
|
||||||
<ul>
|
<ul>
|
||||||
|
|
||||||
<li ng-repeat="createAction in createActions">
|
<li ng-repeat="createAction in createActions" ng-click="createAction.perform()">
|
||||||
<a
|
<a
|
||||||
ng-click="createAction.perform()"
|
|
||||||
ng-mouseover="representation.activeMetadata = createAction.getMetadata()"
|
ng-mouseover="representation.activeMetadata = createAction.getMetadata()"
|
||||||
ng-mouseleave="representation.activeMetadata = undefined">
|
ng-mouseleave="representation.activeMetadata = undefined">
|
||||||
<span class="ui-symbol icon type-icon">
|
<span class="ui-symbol icon type-icon">
|
||||||
@ -48,4 +47,4 @@
|
|||||||
{{representation.activeMetadata.description}}
|
{{representation.activeMetadata.description}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -27,7 +27,8 @@ define(
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var DEFAULT_DIMENSIONS = [ 12, 8 ],
|
var DEFAULT_DIMENSIONS = [ 12, 8 ],
|
||||||
DEFAULT_GRID_SIZE = [32, 32];
|
DEFAULT_GRID_SIZE = [ 32, 32 ],
|
||||||
|
MINIMUM_FRAME_SIZE = [ 320, 180 ];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The LayoutController is responsible for supporting the
|
* The LayoutController is responsible for supporting the
|
||||||
@ -67,12 +68,22 @@ define(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate default positions for a new panel
|
||||||
|
function defaultDimensions() {
|
||||||
|
return MINIMUM_FRAME_SIZE.map(function (min, i) {
|
||||||
|
return Math.max(
|
||||||
|
Math.ceil(min / gridSize[i]),
|
||||||
|
DEFAULT_DIMENSIONS[i]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Generate a default position (in its raw format) for a frame.
|
// Generate a default position (in its raw format) for a frame.
|
||||||
// Use an index to ensure that default positions are unique.
|
// Use an index to ensure that default positions are unique.
|
||||||
function defaultPosition(index) {
|
function defaultPosition(index) {
|
||||||
return {
|
return {
|
||||||
position: [index, index],
|
position: [index, index],
|
||||||
dimensions: DEFAULT_DIMENSIONS
|
dimensions: defaultDimensions()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,6 +118,18 @@ define(
|
|||||||
ids.forEach(populatePosition);
|
ids.forEach(populatePosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update grid size when it changed
|
||||||
|
function updateGridSize(layoutGrid) {
|
||||||
|
var oldSize = gridSize;
|
||||||
|
|
||||||
|
gridSize = layoutGrid || DEFAULT_GRID_SIZE;
|
||||||
|
|
||||||
|
// Only update panel positions if this actually changed things
|
||||||
|
if (gridSize[0] !== oldSize[0] || gridSize[1] !== oldSize[1]) {
|
||||||
|
lookupPanels(Object.keys(positions));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Position a panel after a drop event
|
// Position a panel after a drop event
|
||||||
function handleDrop(e, id, position) {
|
function handleDrop(e, id, position) {
|
||||||
if (e.defaultPrevented) {
|
if (e.defaultPrevented) {
|
||||||
@ -138,6 +161,9 @@ define(
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Watch for changes to the grid size in the model
|
||||||
|
$scope.$watch("model.layoutGrid", updateGridSize);
|
||||||
|
|
||||||
// Position panes when the model field changes
|
// Position panes when the model field changes
|
||||||
$scope.$watch("model.composition", lookupPanels);
|
$scope.$watch("model.composition", lookupPanels);
|
||||||
|
|
||||||
|
@ -175,6 +175,23 @@ define(
|
|||||||
);
|
);
|
||||||
expect(testConfiguration.panels.d).not.toBeDefined();
|
expect(testConfiguration.panels.d).not.toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("ensures a minimum frame size", function () {
|
||||||
|
var styleB, styleC;
|
||||||
|
|
||||||
|
// Start with a very small frame size
|
||||||
|
testModel.layoutGrid = [ 1, 1 ];
|
||||||
|
|
||||||
|
// White-boxy; we know which watch is which
|
||||||
|
mockScope.$watch.calls[0].args[1](testModel.layoutGrid);
|
||||||
|
mockScope.$watch.calls[1].args[1](testModel.composition);
|
||||||
|
|
||||||
|
styleB = controller.getFrameStyle("b");
|
||||||
|
|
||||||
|
// Resulting size should still be reasonably large pixel-size
|
||||||
|
expect(parseInt(styleB.width, 10)).toBeGreaterThan(63);
|
||||||
|
expect(parseInt(styleB.width, 10)).toBeGreaterThan(31);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
69
protractor/README
Normal file
69
protractor/README
Normal 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.
|
@ -22,7 +22,7 @@
|
|||||||
//TODO Add filter for duplications/
|
//TODO Add filter for duplications/
|
||||||
var fullScreenFile = require("../common/Buttons");
|
var fullScreenFile = require("../common/Buttons");
|
||||||
|
|
||||||
describe('Test Fullscreen', function() {
|
describe('Enable Fullscreen', function() {
|
||||||
var fullScreenClass = new fullScreenFile();
|
var fullScreenClass = new fullScreenFile();
|
||||||
|
|
||||||
beforeEach(require('../common/Launch'));
|
beforeEach(require('../common/Launch'));
|
||||||
|
@ -25,7 +25,7 @@ var itemEdit = require("../common/EditItem");
|
|||||||
var rightMenu = require("../common/RightMenu");
|
var rightMenu = require("../common/RightMenu");
|
||||||
var Drag = require("../common/drag");
|
var Drag = require("../common/drag");
|
||||||
|
|
||||||
describe('Test Info Bubble', function() {
|
describe('Info Bubble', function() {
|
||||||
var fullScreenClass = new fullScreenFile();
|
var fullScreenClass = new fullScreenFile();
|
||||||
var createClass = new createItem();
|
var createClass = new createItem();
|
||||||
var editItemClass = new itemEdit();
|
var editItemClass = new itemEdit();
|
||||||
|
@ -24,7 +24,7 @@ var createClassFile = require("../common/CreateItem")
|
|||||||
var itemEdit = require("../common/EditItem");
|
var itemEdit = require("../common/EditItem");
|
||||||
var rightMenu = require("../common/RightMenu.js");
|
var rightMenu = require("../common/RightMenu.js");
|
||||||
|
|
||||||
describe('Test New Window', function() {
|
describe('New Window', function() {
|
||||||
var fullScreenClass = new fullScreenFile();
|
var fullScreenClass = new fullScreenFile();
|
||||||
var createClass = new createClassFile();
|
var createClass = new createClassFile();
|
||||||
var editItemClass = new itemEdit();
|
var editItemClass = new itemEdit();
|
||||||
|
@ -21,28 +21,64 @@
|
|||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
var right_click = require("../common/RightMenu.js");
|
var right_click = require("../common/RightMenu.js");
|
||||||
var Create = require("../common/CreateItem")
|
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 clickClass = new right_click();
|
||||||
var createClass = new Create();
|
var createClass = new Create();
|
||||||
|
var editItemClass = new itemEdit();
|
||||||
var ITEM_NAME = "Folder";
|
var ITEM_NAME = "Folder";
|
||||||
var ITEM_TYPE = "folder";
|
var ITEM_TYPE = "folder";
|
||||||
var ITEM_MENU_GLYPH = 'F\nFolder';
|
var ITEM_MENU_GLYPH = 'F\nFolder';
|
||||||
|
var ITEM_GRID_SELECT = 'P\nF\nFolder\n0 Items';
|
||||||
|
|
||||||
beforeEach(require('../common/Launch'));
|
beforeEach(require('../common/Launch'));
|
||||||
|
|
||||||
it('should delete the specified object', function(){
|
it('should Dissapear After Delete', function(){
|
||||||
createClass.createButton().click();
|
browser.wait(function() {
|
||||||
var folder = createClass.selectNewItem(ITEM_TYPE);
|
createClass.createButton().click();
|
||||||
expect(folder.getText()).toEqual([ ITEM_MENU_GLYPH ]);
|
return true;
|
||||||
browser.sleep(1000);
|
}).then(function (){
|
||||||
folder.click()
|
var folder = createClass.selectNewItem(ITEM_TYPE);
|
||||||
browser.sleep(1000);
|
expect(folder.getText()).toEqual([ ITEM_MENU_GLYPH ]);
|
||||||
browser.wait(function () {
|
browser.sleep(1000);
|
||||||
return element.all(by.model('ngModel[field]')).isDisplayed();
|
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
15
protractor/bin/clean.js
Executable 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
90
protractor/bin/ctrl.sh
Executable 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
12
protractor/bin/run.js
Executable 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
40
protractor/bin/start.js
Executable 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
44
protractor/bin/stop.js
Executable 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
@ -24,6 +24,6 @@
|
|||||||
module.exports = function launch() {
|
module.exports = function launch() {
|
||||||
'use strict';
|
'use strict';
|
||||||
browser.ignoreSynchronization = true;
|
browser.ignoreSynchronization = true;
|
||||||
browser.get('http://localhost:1984/');
|
browser.get('http://localhost:1984');
|
||||||
browser.sleep(2000); // 20 seconds
|
browser.sleep(2000); // 2 seconds
|
||||||
};
|
};
|
||||||
|
@ -24,18 +24,25 @@ var RightMenu = (function () {
|
|||||||
|
|
||||||
function RightMenu() {
|
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 Click on Object
|
||||||
RightMenu.prototype.delete = function (name, flag) {
|
RightMenu.prototype.delete = function (name, flag) {
|
||||||
if(typeof flag === 'undefined'){
|
if(typeof flag === 'undefined'){
|
||||||
flag = true;
|
flag = true;
|
||||||
}
|
}
|
||||||
if(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)
|
browser.sleep(1000)
|
||||||
var object = element.all(by.repeater('child in composition')).filter(function (ele){
|
var object = element.all(by.repeater('child in composition')).filter(function (ele){
|
||||||
return ele.getText().then(function(text) {
|
return ele.getText().then(function(text) {
|
||||||
//expect(text).toEqual("3");
|
|
||||||
return text === name;
|
return text === name;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -43,7 +50,7 @@ var RightMenu = (function () {
|
|||||||
browser.actions().mouseMove(object.get(0)).perform();
|
browser.actions().mouseMove(object.get(0)).perform();
|
||||||
browser.actions().click(protractor.Button.RIGHT).perform();
|
browser.actions().click(protractor.Button.RIGHT).perform();
|
||||||
browser.sleep(1000)
|
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 ele.getText().then(function (text) {
|
||||||
return text == "Z\nRemove";
|
return text == "Z\nRemove";
|
||||||
})
|
})
|
||||||
@ -58,11 +65,10 @@ var RightMenu = (function () {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
RightMenu.prototype.reset = function (name) {
|
RightMenu.prototype.reset = function (name) {
|
||||||
var carrot = element.all(by.css('.ui-symbol.view-control.ng-binding.ng-scope')).click();
|
carrotMyItem();
|
||||||
browser.sleep(1000)
|
browser.sleep(1000)
|
||||||
var object = element.all(by.repeater('child in composition')).filter(function (ele){
|
var object = element.all(by.repeater('child in composition')).filter(function (ele){
|
||||||
return ele.getText().then(function(text) {
|
return ele.getText().then(function(text) {
|
||||||
//expect(text).toEqual("3");
|
|
||||||
return text === name;
|
return text === name;
|
||||||
});
|
});
|
||||||
}).click();
|
}).click();
|
||||||
@ -75,19 +81,19 @@ var RightMenu = (function () {
|
|||||||
return text == "r\nRestart at 0";
|
return text == "r\nRestart at 0";
|
||||||
})
|
})
|
||||||
}).click();
|
}).click();
|
||||||
|
browser.sleep(1000)
|
||||||
};
|
};
|
||||||
|
//click '<', true==yes false==no
|
||||||
RightMenu.prototype.select = function(name, flag){
|
RightMenu.prototype.select = function(name, flag){
|
||||||
if(typeof flag == "undefined"){
|
if(typeof flag == "undefined"){
|
||||||
flag = true;
|
flag = true;
|
||||||
}
|
}
|
||||||
//click '<', true==yes false==no
|
|
||||||
if(flag == true){
|
if(flag == true){
|
||||||
var carrot = element.all(by.css('.ui-symbol.view-control.ng-binding.ng-scope')).click();
|
carrotMyItem();
|
||||||
}
|
}
|
||||||
browser.sleep(1000)
|
browser.sleep(1000)
|
||||||
return element.all(by.repeater('child in composition')).filter(function (ele){
|
return element.all(by.repeater('child in composition')).filter(function (ele){
|
||||||
return ele.getText().then(function(text) {
|
return ele.getText().then(function(text) {
|
||||||
// expect(text).toEqual("3");
|
|
||||||
return text === name;
|
return text === name;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -96,7 +102,6 @@ var RightMenu = (function () {
|
|||||||
RightMenu.prototype.dragDrop = function(name){
|
RightMenu.prototype.dragDrop = function(name){
|
||||||
var object = element.all(by.repeater('child in composition')).filter(function (ele){
|
var object = element.all(by.repeater('child in composition')).filter(function (ele){
|
||||||
return ele.getText().then(function(text) {
|
return ele.getText().then(function(text) {
|
||||||
//expect(text).toEqual("3");
|
|
||||||
return text === name;
|
return text === name;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -24,34 +24,34 @@
|
|||||||
// conf.js
|
// conf.js
|
||||||
exports.config = {
|
exports.config = {
|
||||||
allScriptsTimeout: 500000,
|
allScriptsTimeout: 500000,
|
||||||
defaultTimeoutInterval: 60000,
|
jasmineNodeOpts: {defaultTimeoutInterval: 360000},
|
||||||
seleniumAddress: 'http://localhost:4444/wd/hub',
|
seleniumAddress: 'http://localhost:4444/wd/hub',
|
||||||
//specs: ['StressTest.js'],
|
//specs: ['StressTestCarrot.js'],
|
||||||
specs: [
|
specs: [
|
||||||
//'create/CreateActivity.js',
|
// 'create/CreateActivity.js',
|
||||||
//'delete/DeleteActivity.js',
|
// 'delete/DeleteActivity.js',
|
||||||
//'create/CreateActivityMode.js',
|
// 'create/CreateActivityMode.js',
|
||||||
//'delete/DeleteActivityMode.js',
|
// 'delete/DeleteActivityMode.js',
|
||||||
//'create/CreateActivityMode.js',
|
// 'create/CreateClock.js',
|
||||||
//'create/CreateClock.js',
|
// 'delete/DeleteClock.js',
|
||||||
//'delete/DeleteClock.js',
|
|
||||||
'create/CreateDisplay.js',
|
'create/CreateDisplay.js',
|
||||||
//'delete/DeleteDisplay.js',
|
'delete/DeleteDisplay.js',
|
||||||
'create/CreateFolder.js',
|
'create/CreateFolder.js',
|
||||||
//'delete/DeleteFolder.js',
|
'delete/DeleteFolder.js',
|
||||||
'create/CreateTelemetry.js',
|
// 'create/CreateTelemetry.js',
|
||||||
//'delete/DeleteTelemetry.js',
|
// 'delete/DeleteTelemetry.js',
|
||||||
//'create/CreateTimeline.js',
|
// 'create/CreateTimeline.js',
|
||||||
//'delete/DeleteTimeline.js',
|
// 'delete/DeleteTimeline.js',
|
||||||
//'create/CreateTimer.js',
|
// 'create/CreateTimer.js',
|
||||||
//'delete/DeleteTimer.js',
|
// 'delete/DeleteTimer.js',
|
||||||
'create/CreateWebPage.js',
|
'create/CreateWebPage.js',
|
||||||
//'delete/DeleteWebPage.js',
|
'delete/DeleteWebPage.js',
|
||||||
'UI/Fullscreen.js',
|
'UI/Fullscreen.js',
|
||||||
'create/CreateButton.js',
|
'create/CreateButton.js',
|
||||||
//"UI/DragDrop.js",
|
//"UI/DragDrop.js",
|
||||||
//"UI/NewWindow.js",
|
"UI/NewWindow.js"
|
||||||
'UI/InfoBubble.js'
|
//'UI/InfoBubble.js',
|
||||||
|
//'UI/RightClick.js'
|
||||||
],
|
],
|
||||||
capabilities: {
|
capabilities: {
|
||||||
'browserName': 'chrome', // or 'safari'
|
'browserName': 'chrome', // or 'safari'
|
||||||
@ -61,7 +61,7 @@ exports.config = {
|
|||||||
|
|
||||||
// Allow specifying binary location as an environment variable,
|
// Allow specifying binary location as an environment variable,
|
||||||
// for cases where Chrome is not installed in a usual location.
|
// 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 =
|
exports.config.capabilities.chromeOptions.binary =
|
||||||
process.env.PROTRACTOR_CHROME_BINARY;
|
process.env.CHROME_BIN;
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
var itemCreate = require("../common/CreateItem");
|
var itemCreate = require("../common/CreateItem");
|
||||||
var itemEdit = require("../common/EditItem");
|
var itemEdit = require("../common/EditItem");
|
||||||
|
|
||||||
describe('Create Web Page', function() {
|
describe('Create Activity Mode', function() {
|
||||||
var createClass = new itemCreate();
|
var createClass = new itemCreate();
|
||||||
var editItemClass = new itemEdit();
|
var editItemClass = new itemEdit();
|
||||||
var ITEM_NAME = "Activity Mode";
|
var ITEM_NAME = "Activity Mode";
|
||||||
|
@ -57,7 +57,7 @@ describe('Create Clock', function() {
|
|||||||
});
|
});
|
||||||
it('should check clock', function () {
|
it('should check clock', function () {
|
||||||
|
|
||||||
function getTime() {
|
function getTime(flag) {
|
||||||
function addZero(time){
|
function addZero(time){
|
||||||
if(time < 10){
|
if(time < 10){
|
||||||
return '0' + time;
|
return '0' + time;
|
||||||
@ -66,7 +66,6 @@ describe('Create Clock', function() {
|
|||||||
}
|
}
|
||||||
var currentdate = new Date();
|
var currentdate = new Date();
|
||||||
|
|
||||||
|
|
||||||
var month = currentdate.getMonth() + 1;
|
var month = currentdate.getMonth() + 1;
|
||||||
month = addZero(month);
|
month = addZero(month);
|
||||||
|
|
||||||
@ -77,6 +76,9 @@ describe('Create Clock', function() {
|
|||||||
hour = addZero(hour);
|
hour = addZero(hour);
|
||||||
|
|
||||||
var second = currentdate.getSeconds();
|
var second = currentdate.getSeconds();
|
||||||
|
if(flag == true) {
|
||||||
|
second = second + 1;
|
||||||
|
}
|
||||||
second = addZero(second);
|
second = addZero(second);
|
||||||
|
|
||||||
var minute = currentdate.getMinutes();
|
var minute = currentdate.getMinutes();
|
||||||
@ -85,17 +87,23 @@ describe('Create Clock', function() {
|
|||||||
return ("UTC " + currentdate.getFullYear() + "/" + (month) + "/" +
|
return ("UTC " + currentdate.getFullYear() + "/" + (month) + "/" +
|
||||||
day + " " + (hour) + ":" + minute + ":" + second + " PM");
|
day + " " + (hour) + ":" + minute + ":" + second + " PM");
|
||||||
}
|
}
|
||||||
|
this.addMatchers({
|
||||||
var current,clock;
|
toBeIn: function(expected){
|
||||||
rightClickClass.select(ITEM_MENU_GLYPH, true).click().then(function () {
|
var posibilities = Array.isArray(this.actual) ? this.actual : [this.actual];
|
||||||
browser.sleep(1000);
|
return posibilities.indexOf(expected) > -1;
|
||||||
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);
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
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);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
})
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -63,7 +63,11 @@ describe('Create Timer', function() {
|
|||||||
browser.sleep(1000)
|
browser.sleep(1000)
|
||||||
var timer = element(by.css('.value.ng-binding.active'))
|
var timer = element(by.css('.value.ng-binding.active'))
|
||||||
timer.getText().then(function (time) {
|
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
20
protractor/package.json
Normal 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"
|
||||||
|
}
|
@ -19,10 +19,9 @@
|
|||||||
* this source code distribution or the Licensing information page available
|
* this source code distribution or the Licensing information page available
|
||||||
* at runtime from the About dialog for additional information.
|
* at runtime from the About dialog for additional information.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
//TODO Add filter for duplications/
|
var itemCreate = require("../common/CreateItem");
|
||||||
var itemCreate = require("./common/CreateItem");
|
var itemEdit = require("../common/EditItem");
|
||||||
var itemEdit = require("./common/EditItem");
|
var right_click = require("../common/RightMenu.js");
|
||||||
var right_click = require("./common/RightMenu.js");
|
|
||||||
|
|
||||||
describe('Create Folder', function() {
|
describe('Create Folder', function() {
|
||||||
var clickClass = new right_click();
|
var clickClass = new right_click();
|
||||||
@ -41,31 +40,35 @@ describe('Create Folder', function() {
|
|||||||
});
|
});
|
||||||
it('should Create new Folder', function(){
|
it('should Create new Folder', function(){
|
||||||
browser.sleep(5000);
|
browser.sleep(5000);
|
||||||
for(var i=0; i < 50; i++){
|
for(var i=0; i < 25; i++){
|
||||||
browser.wait(function() {
|
browser.wait(function() {
|
||||||
createClass.createButton().click();
|
createClass.createButton().click();
|
||||||
return true;
|
return true;
|
||||||
}).then(function (){
|
}).then(function (){
|
||||||
var folder = createClass.selectNewItem(ITEM_TYPE);
|
var folder = createClass.selectNewItem(ITEM_TYPE);
|
||||||
expect(folder.getText()).toEqual([ ITEM_MENU_GLYPH ]);
|
expect(folder.getText()).toEqual([ ITEM_MENU_GLYPH ]);
|
||||||
browser.sleep(1000);
|
browser.sleep(500);
|
||||||
folder.click()
|
folder.click()
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
browser.wait(function () {
|
browser.wait(function () {
|
||||||
return element.all(by.model('ngModel[field]')).isDisplayed();
|
return element.all(by.model('ngModel[field]')).isDisplayed();
|
||||||
})
|
})
|
||||||
createClass.fillFolderForum(ITEM_NAME, ITEM_TYPE).click();
|
createClass.fillFolderForum(ITEM_NAME, ITEM_TYPE).click();
|
||||||
browser.sleep(1000);
|
browser.sleep(500);
|
||||||
}).then(function (){
|
}).then(function (){
|
||||||
browser.sleep(1000);
|
browser.sleep(500);
|
||||||
// if(i === 1){
|
clickClass.delete(ITEM_SIDE_SELECT, true);
|
||||||
clickClass.delete(ITEM_SIDE_SELECT, true);
|
//element.all(by.css('.ui-symbol.view-control.ng-binding.ng-scope')).click();
|
||||||
element.all(by.css('.ui-symbol.view-control.ng-binding.ng-scope')).click();
|
|
||||||
// }else{
|
|
||||||
browser.sleep(1000);
|
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);
|
// clickClass.delete(ITEM_SIDE_SELECT, false);
|
||||||
// }
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
browser.pause();
|
browser.pause();
|
59
protractor/stressTest/StressTestBubble.js
Normal file
59
protractor/stressTest/StressTestBubble.js
Normal 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();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
56
protractor/stressTest/StressTestCreateButton.js
Normal file
56
protractor/stressTest/StressTestCreateButton.js
Normal 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();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
55
protractor/stressTest/StressTestMenu.js
Normal file
55
protractor/stressTest/StressTestMenu.js
Normal 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();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
61
protractor/stressTest/StressTestNewPage.js
Normal file
61
protractor/stressTest/StressTestNewPage.js
Normal 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();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
59
protractor/stressTest/StressTestRightClick.js
Normal file
59
protractor/stressTest/StressTestRightClick.js
Normal 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();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user