mirror of
https://github.com/nasa/openmct.git
synced 2024-12-20 13:43:09 +00:00
Merge branch 'master' of https://github.com/nasa/openmctweb into search
This commit is contained in:
commit
89cb6867bd
3
.gitignore
vendored
3
.gitignore
vendored
@ -20,9 +20,6 @@ closed-lib
|
||||
# Node dependencies
|
||||
node_modules
|
||||
|
||||
# Build documentation
|
||||
docs
|
||||
|
||||
# Protractor logs
|
||||
protractor/logs
|
||||
|
||||
|
@ -77,7 +77,7 @@ To run:
|
||||
|
||||
* Install protractor following the instructions above.
|
||||
* `webdriver-manager start`
|
||||
* `node app.js -p 1984 -x platform/persistence/elastic -i example/persistence
|
||||
* `node app.js -p 1984 -x platform/persistence/elastic -i example/persistence`
|
||||
* `protractor protractor/conf.js`
|
||||
|
||||
## Build
|
||||
|
@ -24,7 +24,7 @@
|
||||
|
||||
# Script to build and deploy docs to github pages.
|
||||
|
||||
OUTPUT_DIRECTORY="docs"
|
||||
OUTPUT_DIRECTORY="target/docs"
|
||||
REPOSITORY_URL="git@github.com:nasa/openmctweb.git"
|
||||
|
||||
BUILD_SHA=`git rev-parse head`
|
||||
@ -39,7 +39,7 @@ if [ -d $OUTPUT_DIRECTORY ]; then
|
||||
rm -rf $OUTPUT_DIRECTORY || exit 1
|
||||
fi
|
||||
|
||||
npm run-script jsdoc
|
||||
npm run docs
|
||||
cd $OUTPUT_DIRECTORY || exit 1
|
||||
|
||||
echo "git init"
|
||||
|
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![Diagram ",
|
||||
counter,
|
||||
"](",
|
||||
outputFilename,
|
||||
")\n\n"
|
||||
].join(""));
|
||||
isBuilding = true;
|
||||
source = "";
|
||||
counter += 1;
|
||||
} else {
|
||||
// Otherwise, pass through
|
||||
this.push(chunk + '\n');
|
||||
}
|
||||
} else {
|
||||
if (chunk.trim() === "```") {
|
||||
// End nomnoml
|
||||
renderNomnoml(source, outputPath);
|
||||
isBuilding = false;
|
||||
} else {
|
||||
source += chunk + '\n';
|
||||
}
|
||||
}
|
||||
done();
|
||||
};
|
||||
|
||||
return transform;
|
||||
}
|
||||
|
||||
// Convert from Github-flavored Markdown to HTML
|
||||
function gfmifier() {
|
||||
var transform = new stream.Transform({ objectMode: true }),
|
||||
markdown = "";
|
||||
transform._transform = function (chunk, encoding, done) {
|
||||
markdown += chunk;
|
||||
done();
|
||||
};
|
||||
transform._flush = function (done) {
|
||||
this.push("<html><body>\n");
|
||||
this.push(marked(markdown));
|
||||
this.push("\n</body></html>\n");
|
||||
done();
|
||||
};
|
||||
return transform;
|
||||
}
|
||||
|
||||
// Custom renderer for marked; converts relative links from md to html,
|
||||
// and makes headings linkable.
|
||||
function CustomRenderer() {
|
||||
var renderer = new marked.Renderer(),
|
||||
customRenderer = Object.create(renderer);
|
||||
customRenderer.heading = function (text, level) {
|
||||
var escapedText = (text || "").trim().toLowerCase().replace(/\W/g, "-"),
|
||||
aOpen = "<a name=\"" + escapedText + "\" href=\"#" + escapedText + "\">",
|
||||
aClose = "</a>";
|
||||
return aOpen + renderer.heading.apply(renderer, arguments) + aClose;
|
||||
};
|
||||
// Change links to .md files to .html
|
||||
customRenderer.link = function (href, title, text) {
|
||||
// ...but only if they look like relative paths
|
||||
return (href || "").indexOf(":") === -1 && href[0] !== "/" ?
|
||||
renderer.link(href.replace(/\.md/, ".html"), title, text) :
|
||||
renderer.link.apply(renderer, arguments);
|
||||
};
|
||||
return customRenderer;
|
||||
}
|
||||
|
||||
options['in'] = options['in'] || options.i;
|
||||
options.out = options.out || options.o;
|
||||
|
||||
marked.setOptions({
|
||||
renderer: new CustomRenderer(),
|
||||
gfm: true,
|
||||
tables: true,
|
||||
breaks: false,
|
||||
pedantic: false,
|
||||
sanitize: true,
|
||||
smartLists: true,
|
||||
smartypants: false
|
||||
});
|
||||
|
||||
// Convert all markdown files.
|
||||
// First, pull out nomnoml diagrams.
|
||||
// Then, convert remaining Markdown to HTML.
|
||||
glob(options['in'] + "/**/*.md", {}, function (err, files) {
|
||||
files.forEach(function (file) {
|
||||
var destination = file.replace(options['in'], options.out)
|
||||
.replace(/md$/, "html"),
|
||||
destPath = path.dirname(destination),
|
||||
prefix = path.basename(destination).replace(/\.html$/, "");
|
||||
|
||||
mkdirp(destPath, function (err) {
|
||||
fs.createReadStream(file, { encoding: 'utf8' })
|
||||
.pipe(split())
|
||||
.pipe(nomnomlifier(destPath, prefix))
|
||||
.pipe(gfmifier())
|
||||
.pipe(fs.createWriteStream(destination, {
|
||||
encoding: 'utf8'
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Also copy over all HTML, CSS, or PNG files
|
||||
glob(options['in'] + "/**/*.@(html|css|png)", {}, function (err, files) {
|
||||
files.forEach(function (file) {
|
||||
var destination = file.replace(options['in'], options.out),
|
||||
destPath = path.dirname(destination);
|
||||
|
||||
mkdirp(destPath, function (err) {
|
||||
fs.createReadStream(file, { encoding: 'utf8' })
|
||||
.pipe(fs.createWriteStream(destination, {
|
||||
encoding: 'utf8'
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
}());
|
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-phantomjs-launcher": "^0.1.4",
|
||||
"karma-requirejs": "^0.2.2",
|
||||
"requirejs": "^2.1.17"
|
||||
"requirejs": "^2.1.17",
|
||||
"marked": "^0.3.5",
|
||||
"glob": ">= 3.0.0",
|
||||
"split": "^1.0.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"nomnoml": "^0.0.3",
|
||||
"canvas": "^1.2.7"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node app.js",
|
||||
"test": "karma start --single-run",
|
||||
"jshint": "jshint platform example || exit 0",
|
||||
"jsdoc": "jsdoc -c jsdoc.json -r -d docs"
|
||||
"jsdoc": "jsdoc -c jsdoc.json -r -d target/docs/api",
|
||||
"otherdoc": "node docs/gendocs.js --in docs/src --out target/docs",
|
||||
"docs": "npm run jsdoc ; npm run otherdoc"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -23,9 +23,8 @@
|
||||
<div class="pane left menu-items">
|
||||
<ul>
|
||||
|
||||
<li ng-repeat="createAction in createActions">
|
||||
<li ng-repeat="createAction in createActions" ng-click="createAction.perform()">
|
||||
<a
|
||||
ng-click="createAction.perform()"
|
||||
ng-mouseover="representation.activeMetadata = createAction.getMetadata()"
|
||||
ng-mouseleave="representation.activeMetadata = undefined">
|
||||
<span class="ui-symbol icon type-icon">
|
||||
@ -48,4 +47,4 @@
|
||||
{{representation.activeMetadata.description}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -27,7 +27,8 @@ define(
|
||||
"use strict";
|
||||
|
||||
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
|
||||
@ -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.
|
||||
// Use an index to ensure that default positions are unique.
|
||||
function defaultPosition(index) {
|
||||
return {
|
||||
position: [index, index],
|
||||
dimensions: DEFAULT_DIMENSIONS
|
||||
dimensions: defaultDimensions()
|
||||
};
|
||||
}
|
||||
|
||||
@ -107,6 +118,18 @@ define(
|
||||
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
|
||||
function handleDrop(e, id, position) {
|
||||
if (e.defaultPrevented) {
|
||||
@ -138,6 +161,9 @@ define(
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
// Watch for changes to the grid size in the model
|
||||
$scope.$watch("model.layoutGrid", updateGridSize);
|
||||
|
||||
// Position panes when the model field changes
|
||||
$scope.$watch("model.composition", lookupPanels);
|
||||
|
||||
|
@ -175,6 +175,23 @@ define(
|
||||
);
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user