mirror of
https://github.com/nasa/openmct.git
synced 2025-06-25 18:50:11 +00:00
Compare commits
16 Commits
vue-3-migr
...
api-draft
Author | SHA1 | Date | |
---|---|---|---|
a2bcc828eb | |||
7c6fa305ad | |||
7b68b2971e | |||
f43cc60720 | |||
f68ce2fc80 | |||
7b2762e034 | |||
66c68e44bd | |||
04f59750b7 | |||
58a2ec6d94 | |||
04c11612c9 | |||
030fc18c38 | |||
c5ec132484 | |||
c3e5d0d155 | |||
d8e14a457b | |||
8ca366a009 | |||
761fbfe665 |
20
api/README.md
Normal file
20
api/README.md
Normal file
@ -0,0 +1,20 @@
|
||||
# API
|
||||
|
||||
This directory is for draft API documentation and design. The API is organized into a few major components, which are documented in their own READMEs. See the following:
|
||||
|
||||
* Domain Objects
|
||||
Capabilities
|
||||
Events
|
||||
Mutation
|
||||
Etc
|
||||
|
||||
* [Object API](object-api/README.md) (encapsulates persistence), should include roots
|
||||
* [Region API](region-api/README.md)
|
||||
* [Telemetry API](telemetry-api/README.md)
|
||||
* [Type API](type-api/README.md)
|
||||
|
||||
Not yet started:
|
||||
|
||||
* [Action API](action-api/README.md)
|
||||
* [Indicators API](indicators-api/README.md) -- potentially compress into regions?
|
||||
* [Plugin API](plugin-api/README.md)
|
93
api/composition-api/CompositionAPI.js
Normal file
93
api/composition-api/CompositionAPI.js
Normal file
@ -0,0 +1,93 @@
|
||||
define([
|
||||
|
||||
], function (
|
||||
|
||||
) {
|
||||
|
||||
|
||||
var PROVIDER_REGISTRY = [];
|
||||
|
||||
function getProvider (object) {
|
||||
return PROVIDER_REGISTRY.filter(function (p) {
|
||||
return p.appliesTo(object);
|
||||
})[0];
|
||||
};
|
||||
|
||||
function composition(object) {
|
||||
var provider = getProvider(object);
|
||||
|
||||
if (!provider) {
|
||||
return;
|
||||
}
|
||||
|
||||
return new CompositionCollection(object, provider);
|
||||
};
|
||||
|
||||
composition.addProvider = function (provider) {
|
||||
PROVIDER_REGISTRY.unshift(provider);
|
||||
};
|
||||
|
||||
window.MCT = window.MCT || {};
|
||||
window.MCT.composition = composition;
|
||||
|
||||
function CompositionCollection(domainObject, provider) {
|
||||
this.domainObject = domainObject;
|
||||
this.provider = provider;
|
||||
};
|
||||
|
||||
CompositionCollection.prototype.add = function (child, skipMutate) {
|
||||
if (!this._children) {
|
||||
throw new Error("Must load composition before you can add!");
|
||||
}
|
||||
// we probably should not add until we have loaded.
|
||||
// todo: should we modify parent?
|
||||
if (!skipMutate) {
|
||||
this.provider.add(this.domainObject, child);
|
||||
}
|
||||
this.children.push(child);
|
||||
this.emit('add', child);
|
||||
};
|
||||
|
||||
CompositionCollection.prototype.load = function () {
|
||||
return this.provider.load(this.domainObject)
|
||||
.then(function (children) {
|
||||
this._children = [];
|
||||
children.map(function (c) {
|
||||
this.add(c, true);
|
||||
}, this);
|
||||
this.emit('load');
|
||||
// Todo: set up listener for changes via provider?
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
CompositionCollection.prototype.remove = function (child) {
|
||||
var index = this.children.indexOf(child);
|
||||
if (index === -1) {
|
||||
throw new Error("Unable to remove child: not found in composition");
|
||||
}
|
||||
this.provider.remove(this.domainObject, child);
|
||||
this.children.splice(index, 1);
|
||||
this.emit('remove', index, child);
|
||||
};
|
||||
|
||||
var DefaultCompositionProvider = {
|
||||
appliesTo: function (domainObject) {
|
||||
return !!domainObject.composition;
|
||||
},
|
||||
load: function (domainObject) {
|
||||
return Promise.all(domainObject.composition.map(MCT.objects.get));
|
||||
},
|
||||
add: function (domainObject, child) {
|
||||
domainObject.composition.push(child.key);
|
||||
}
|
||||
};
|
||||
|
||||
composition.addProvider(DefaultCompositionProvider);
|
||||
|
||||
function Injector() {
|
||||
console.log('composition api injected!');
|
||||
}
|
||||
|
||||
return Injector;
|
||||
|
||||
});
|
35
api/composition-api/README.md
Normal file
35
api/composition-api/README.md
Normal file
@ -0,0 +1,35 @@
|
||||
# Composition API - Overview
|
||||
|
||||
The composition API is straightforward:
|
||||
|
||||
MCT.composition(object) -- returns a `CompositionCollection` if the object has
|
||||
composition, returns undefined if it doesn't.
|
||||
|
||||
## CompositionCollection
|
||||
|
||||
Has three events:
|
||||
* `load`: when the collection has completed loading.
|
||||
* `add`: when a new object has been added to the collection.
|
||||
* `remove` when an object has been removed from the collection.
|
||||
|
||||
Has three methods:
|
||||
|
||||
`Collection.load()` -- returns a promise that is fulfilled when the composition
|
||||
has loaded.
|
||||
`Collection.add(object)` -- add a domain object to the composition.
|
||||
`Collection.remove(object)` -- remove the object from the composition.
|
||||
|
||||
## Composition providers
|
||||
composition providers are anything that meets the following interface:
|
||||
|
||||
* `provider.appliesTo(domainObject)` -> return true if this provider can provide
|
||||
composition for a given domain object.
|
||||
* `provider.add(domainObject, childObject)` -> adds object
|
||||
* `provider.remove(domainObject, childObject)` -> immediately removes objects
|
||||
* `provider.load(domainObject)` -> returns promise for array of children
|
||||
|
||||
There is a default composition provider which handles loading composition for
|
||||
any object with a `composition` property. If you want specialized composition
|
||||
loading behavior, implement your own composition provider and register it with
|
||||
|
||||
`MCT.composition.addProvider(myProvider)`
|
47
api/composition-api/bundle.js
Normal file
47
api/composition-api/bundle.js
Normal file
@ -0,0 +1,47 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
define([
|
||||
'./CompositionAPI',
|
||||
'legacyRegistry'
|
||||
], function (
|
||||
CompositionAPI,
|
||||
legacyRegistry
|
||||
) {
|
||||
legacyRegistry.register('api/composition-api', {
|
||||
name: 'Composition API',
|
||||
description: 'The public Composition API',
|
||||
extensions: {
|
||||
runs: [
|
||||
{
|
||||
key: "CompositionAPI",
|
||||
priority: "mandatory",
|
||||
implementation: CompositionAPI,
|
||||
depends: [
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
});
|
200
api/object-api/ObjectAPI.js
Normal file
200
api/object-api/ObjectAPI.js
Normal file
@ -0,0 +1,200 @@
|
||||
define([
|
||||
'lodash'
|
||||
], function (
|
||||
_
|
||||
) {
|
||||
|
||||
/**
|
||||
Object API. Intercepts the existing object API while also exposing
|
||||
A new Object API.
|
||||
|
||||
MCT.objects.get('mine')
|
||||
.then(function (root) {
|
||||
console.log(root);
|
||||
MCT.objects.getComposition(root)
|
||||
.then(function (composition) {
|
||||
console.log(composition)
|
||||
})
|
||||
});
|
||||
*/
|
||||
|
||||
var Objects = {},
|
||||
ROOT_REGISTRY = [],
|
||||
PROVIDER_REGISTRY = {},
|
||||
FALLBACK_PROVIDER;
|
||||
|
||||
window.MCT = window.MCT || {};
|
||||
window.MCT.objects = Objects;
|
||||
|
||||
// take a key string and turn it into a key object
|
||||
// 'scratch:root' ==> {namespace: 'scratch', identifier: 'root'}
|
||||
function parseKeyString(key) {
|
||||
if (typeof key === 'object') {
|
||||
return key;
|
||||
}
|
||||
var namespace = '',
|
||||
identifier = key;
|
||||
for (var i = 0, escaped = false, len=key.length; i < len; i++) {
|
||||
if (key[i] === ":" && !escaped) {
|
||||
namespace = key.slice(0, i);
|
||||
identifier = key.slice(i + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {
|
||||
namespace: namespace,
|
||||
identifier: identifier
|
||||
};
|
||||
};
|
||||
|
||||
// take a key and turn it into a key string
|
||||
// {namespace: 'scratch', identifier: 'root'} ==> 'scratch:root'
|
||||
function makeKeyString(key) {
|
||||
if (typeof key === 'string') {
|
||||
return key;
|
||||
}
|
||||
if (!key.namespace) {
|
||||
return key.identifier;
|
||||
}
|
||||
return [
|
||||
key.namespace.replace(':', '\\:'),
|
||||
key.identifier.replace(':', '\\:')
|
||||
].join(':');
|
||||
};
|
||||
|
||||
// Converts composition to use key strings instead of keys
|
||||
function toOldFormat(model) {
|
||||
delete model.key;
|
||||
if (model.composition) {
|
||||
model.composition = model.composition.map(makeKeyString);
|
||||
}
|
||||
return model;
|
||||
};
|
||||
|
||||
// converts composition to use keys instead of key strings
|
||||
function toNewFormat(model, key) {
|
||||
model.key = key;
|
||||
if (model.composition) {
|
||||
model.composition = model.composition.map(parseKeyString);
|
||||
}
|
||||
return model;
|
||||
};
|
||||
|
||||
// Root provider is hardcoded in; can't be skipped.
|
||||
var RootProvider = {
|
||||
'get': function () {
|
||||
return Promise.resolve({
|
||||
name: 'The root object',
|
||||
type: 'root',
|
||||
composition: ROOT_REGISTRY
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Retrieve the provider for a given key.
|
||||
function getProvider(key) {
|
||||
if (key.identifier === 'ROOT') {
|
||||
return RootProvider;
|
||||
}
|
||||
return PROVIDER_REGISTRY[key.namespace] || FALLBACK_PROVIDER;
|
||||
};
|
||||
|
||||
Objects.addProvider = function (namespace, provider) {
|
||||
PROVIDER_REGISTRY[namespace] = provider;
|
||||
};
|
||||
|
||||
[
|
||||
'save',
|
||||
'delete',
|
||||
'get'
|
||||
].forEach(function (method) {
|
||||
Objects[method] = function () {
|
||||
var key = arguments[0],
|
||||
provider = getProvider(key);
|
||||
|
||||
if (!provider) {
|
||||
throw new Error('No Provider Matched');
|
||||
}
|
||||
|
||||
if (!provider[method]) {
|
||||
throw new Error('Provider does not support [' + method + '].');
|
||||
}
|
||||
|
||||
return provider[method].apply(provider, arguments);
|
||||
};
|
||||
});
|
||||
|
||||
Objects.addRoot = function (key) {
|
||||
ROOT_REGISTRY.unshift(key);
|
||||
};
|
||||
|
||||
Objects.removeRoot = function (key) {
|
||||
ROOT_REGISTRY = ROOT_REGISTRY.filter(function (k) {
|
||||
return (
|
||||
k.identifier !== key.identifier ||
|
||||
k.namespace !== key.namespace
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
function ObjectServiceProvider(objectService, instantiate) {
|
||||
this.objectService = objectService;
|
||||
this.instantiate = instantiate;
|
||||
}
|
||||
|
||||
ObjectServiceProvider.prototype.save = function (object) {
|
||||
var key = object.key,
|
||||
keyString = makeKeyString(key),
|
||||
newObject = this.instantiate(toOldFormat(object), keyString);
|
||||
|
||||
return object.getCapability('persistence')
|
||||
.persist()
|
||||
.then(function () {
|
||||
return toNewFormat(object, key);
|
||||
});
|
||||
};
|
||||
|
||||
ObjectServiceProvider.prototype.delete = function (object) {
|
||||
// TODO!
|
||||
};
|
||||
|
||||
ObjectServiceProvider.prototype.get = function (key) {
|
||||
var keyString = makeKeyString(key);
|
||||
return this.objectService.getObjects([keyString])
|
||||
.then(function (results) {
|
||||
var model = JSON.parse(JSON.stringify(results[keyString].getModel()));
|
||||
return toNewFormat(model, key);
|
||||
});
|
||||
};
|
||||
|
||||
// Injects new object API as a decorator so that it hijacks all requests.
|
||||
// Object providers implemented on new API should just work, old API should just work, many things may break.
|
||||
function ObjectAPIInjector(ROOTS, instantiate, objectService) {
|
||||
this.getObjects = function (keys) {
|
||||
var results = {},
|
||||
promises = keys.map(function (keyString) {
|
||||
var key = parseKeyString(keyString);
|
||||
return Objects.get(key)
|
||||
.then(function (object) {
|
||||
object = toOldFormat(object)
|
||||
results[keyString] = instantiate(object, keyString);
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(function () {
|
||||
return results;
|
||||
});
|
||||
};
|
||||
|
||||
FALLBACK_PROVIDER = new ObjectServiceProvider(objectService, instantiate);
|
||||
|
||||
ROOTS.forEach(function (r) {
|
||||
ROOT_REGISTRY.push(parseKeyString(r.id));
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
return ObjectAPIInjector;
|
||||
});
|
101
api/object-api/README.md
Normal file
101
api/object-api/README.md
Normal file
@ -0,0 +1,101 @@
|
||||
# Object API - Overview
|
||||
|
||||
The object API provides methods for fetching domain objects.
|
||||
|
||||
# Keys
|
||||
Keys are a composite identifier that is used to create and persist objects. Ex:
|
||||
```javascript
|
||||
{
|
||||
namespace: 'elastic',
|
||||
identifier: 'myIdentifier'
|
||||
}
|
||||
```
|
||||
|
||||
In old MCT days, we called this an "id", and we encoded it in a single string.
|
||||
The above key would encode into the identifier, `elastic:myIdentifier`.
|
||||
|
||||
When interacting with the API you will be dealing with key objects.
|
||||
|
||||
# Configuring the Object API
|
||||
|
||||
The following methods should be used before calling run. They allow you to
|
||||
configure the persistence space of MCT.
|
||||
|
||||
* `MCT.objects.addRoot(key)` -- add a "ROOT" to Open MCT by specifying it's
|
||||
key.
|
||||
* `MCT.objects.removeRoot(key)` -- Remove a "ROOT" from Open MCT by key.
|
||||
* `MCT.objects.addProvider(namespace, provider)` -- register an object provider
|
||||
for a specific namespace. See below for documentation on the provider
|
||||
interface.
|
||||
|
||||
# Using the object API
|
||||
|
||||
The object API provides methods for getting, saving, and deleting objects.
|
||||
|
||||
* MCT.objects.get(key) -> returns promise for an object
|
||||
* MCT.objects.save(object) -> returns promise that is resolved when object
|
||||
has been saved
|
||||
* MCT.objects.delete(object) -> returns promise that is resolved when object has
|
||||
been deleted
|
||||
|
||||
## Configuration Example: Adding a groot
|
||||
|
||||
The following example adds a new root object for groot and populates it with
|
||||
some pieces of groot.
|
||||
|
||||
```javascript
|
||||
|
||||
var ROOT_KEY = {
|
||||
namespace: 'groot',
|
||||
identifier: 'groot'
|
||||
};
|
||||
|
||||
var GROOT_ROOT = {
|
||||
name: 'I am groot',
|
||||
type: 'folder',
|
||||
composition: [
|
||||
{
|
||||
namespace: 'groot',
|
||||
identifier: 'arms'
|
||||
},
|
||||
{
|
||||
namespace: 'groot',
|
||||
identifier: 'legs'
|
||||
},
|
||||
{
|
||||
namespace: 'groot',
|
||||
identifier: 'torso'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var GrootProvider = {
|
||||
get: function (key) {
|
||||
if (key.identifier === 'groot') {
|
||||
return Promise.resolve(GROOT_ROOT);
|
||||
}
|
||||
return Promise.resolve({
|
||||
name: 'Groot\'s ' + key.identifier
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
MCT.objects.addRoot(ROOT_KEY);
|
||||
|
||||
MCT.objects.addProvider('groot', GrootProvider);
|
||||
|
||||
MCT.run();
|
||||
```
|
||||
|
||||
### Making a custom provider:
|
||||
|
||||
All methods on the provider interface are optional, so you do not need
|
||||
to modify them.
|
||||
|
||||
* `provider.get(key)` -> promise for a domain object.
|
||||
* `provider.save(domainObject)` -> returns promise that is fulfilled when object
|
||||
has been saved.
|
||||
* `provider.delete(domainObject)` -> returns promise that is fulfilled when
|
||||
object has been deleted.
|
||||
|
||||
|
50
api/object-api/bundle.js
Normal file
50
api/object-api/bundle.js
Normal file
@ -0,0 +1,50 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
define([
|
||||
'./ObjectAPI',
|
||||
'legacyRegistry'
|
||||
], function (
|
||||
ObjectAPI,
|
||||
legacyRegistry
|
||||
) {
|
||||
legacyRegistry.register('api/object-api', {
|
||||
name: 'Object API',
|
||||
description: 'The public Objects API',
|
||||
extensions: {
|
||||
components: [
|
||||
{
|
||||
provides: "objectService",
|
||||
type: "decorator",
|
||||
priority: "mandatory",
|
||||
implementation: ObjectAPI,
|
||||
depends: [
|
||||
"roots[]",
|
||||
"instantiate"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
});
|
64
api/region-api/README.md
Normal file
64
api/region-api/README.md
Normal file
@ -0,0 +1,64 @@
|
||||
# Region API - Overview
|
||||
|
||||
The region API provides a method for specifying which views should display in a given region for a given domain object. As such, they also define the basic view interface that components must define.
|
||||
|
||||
### MCT.region.Region
|
||||
|
||||
The base region type, all regions implement this interface.
|
||||
|
||||
`register(view)`
|
||||
|
||||
`getViews(domainObject)`
|
||||
|
||||
Additionally, Regions may have subregions for different modes of the application. Specifying a view for a region
|
||||
|
||||
### MCT.region.View
|
||||
|
||||
The basic type for views. You can extend this to implement your own functionality, or you can create your own object so long as it meets the interface.
|
||||
|
||||
`attribute` | `type` | `note`
|
||||
--- | --- | ---
|
||||
`label` | `string` or `Function` | The name of the view. Used in the view selector when more than one view exists for an object.
|
||||
`glyph` | `string` or `Function` | The glyph to associate with this view. Used in the view selector when more than one view exists for an object.
|
||||
`instantiate` | `Function` | constructor for a view. Will be invoked with two arguments, `container` and `domainObject`. It should return an object with a `destroy` method that is called when the view is removed.
|
||||
`appliesTo` | `Function` | Determines if a view applies to a specific domain object. Will be invoked with a domainObject. Should return a number, `priority` if the view applies to a given object. If multiple views return a truthy value for a given object, they will be ordered by priority, and the largest priority value will be the default view for the object. Return `false` if a view should not apply to an object.
|
||||
|
||||
Basic Hello World:
|
||||
|
||||
```javascript
|
||||
|
||||
function HelloWorldView(container, domainObject) {
|
||||
container.innerHTML = 'Hello World!';
|
||||
}
|
||||
|
||||
HelloWorldView.label = 'Hello World';
|
||||
HelloWorldView.glyph = 'whatever';
|
||||
|
||||
HelloWorldView.appliesTo = function (domainObject) {
|
||||
return 10;
|
||||
};
|
||||
|
||||
HelloWorldView.prototype.destroy = function () {
|
||||
// clean up outstanding handlers;
|
||||
};
|
||||
|
||||
MCT.regions.Main.register(HelloWorldView);
|
||||
|
||||
```
|
||||
|
||||
## Region Hierarchy
|
||||
|
||||
Regions are organized in a hierarchy, with the most specific region taking precedence over less specific regions.
|
||||
|
||||
If you specify a view for the Main Region, it will be used for both Edit and View modes. You can override the Main Region view for a specific mode by registering the view with that specific mode.
|
||||
|
||||
### MCT.regions.Tree
|
||||
### MCT.regions.Main
|
||||
### MCT.regions.Main.View
|
||||
### MCT.regions.Main.Edit
|
||||
### MCT.regions.Inspector
|
||||
### MCT.regions.Inspector.View
|
||||
### MCT.regions.Inspector.Edit
|
||||
### MCT.regions.Toolbar
|
||||
### MCT.regions.Toolbar.View
|
||||
### MCT.regions.Toolbar.Edit
|
11
api/region-api/RegionAPI.js
Normal file
11
api/region-api/RegionAPI.js
Normal file
@ -0,0 +1,11 @@
|
||||
define([
|
||||
|
||||
], function () {
|
||||
|
||||
function RegionAPI() {
|
||||
window.MCT = window.MCT || {};
|
||||
window.MCT.regions = {};
|
||||
}
|
||||
|
||||
return RegionAPI;
|
||||
})
|
45
api/region-api/bundle.js
Normal file
45
api/region-api/bundle.js
Normal file
@ -0,0 +1,45 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./RegionAPI',
|
||||
'legacyRegistry'
|
||||
], function (
|
||||
RegionAPI,
|
||||
legacyRegistry
|
||||
) {
|
||||
legacyRegistry.register('api/region-api', {
|
||||
name: 'Region API',
|
||||
description: 'The public Region API',
|
||||
extensions: {
|
||||
runs: [
|
||||
{
|
||||
key: "RegionAPI",
|
||||
implementation: RegionAPI,
|
||||
depends: [
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
});
|
71
api/telemetry-api/README.md
Normal file
71
api/telemetry-api/README.md
Normal file
@ -0,0 +1,71 @@
|
||||
# Telemetry API - Overview
|
||||
|
||||
The Telemetry API provides basic methods for retrieving historical and realtime telemetry data, retrieving telemetry metadata, and registering additional telemetry providers.
|
||||
|
||||
The Telemetry API also provides a set of helpers built upon these basics-- TelemetryFormatters help you format telemetry values for display purposes, LimitEvaluators help you display evaluate and display alarm states, while TelemetryCollections provide a method for seamlessly combining historical and realtime data, while supporting more advanced client side filtering and interactivity.
|
||||
|
||||
|
||||
## Getting Telemetry Data
|
||||
|
||||
|
||||
### `MCT.telemetry.request(domainObject, options)`
|
||||
|
||||
Request historical telemetry for a domain object. Options allows you to specify filters (start, end, etc.), sort order, and strategies for retrieving telemetry (aggregation, latest available, etc.).
|
||||
|
||||
Returns a `Promise` for an array of telemetry values.
|
||||
|
||||
### `MCT.telemetry.subscribe(domainObject, callback, options)`
|
||||
|
||||
Subscribe to realtime telemetry for a specific domain object. callback will be called whenever data is received from a realtime provider. Options allows you to specify ???
|
||||
|
||||
## Understanding Telemetry
|
||||
|
||||
### `MCT.telemetry.getMetadata(domainObject)`
|
||||
|
||||
Retrieve telemetry metadata for a domain object. Telemetry metadata helps you understand the sort of telemetry data a domain object can provide-- for instances, the possible enumerations or states, the units, and more.
|
||||
|
||||
### `MCT.telemetry.Formatter`
|
||||
|
||||
Telemetry formatters help you format telemetry values for display. Under the covers, they use telemetry metadata to interpret your telemetry data, and then they use the format API to format that data for display.
|
||||
|
||||
|
||||
### `MCT.telemetry.LimitEvaluator`
|
||||
|
||||
Limit Evaluators help you evaluate limit and alarm status of individual telemetry datums for display purposes without having to interact directly with the Limit API.
|
||||
|
||||
## Adding new telemetry sources
|
||||
|
||||
### `MCT.telemetry.registerProvider(telemetryProvider)`
|
||||
|
||||
Register a telemetry provider with the telemetry service. This allows you to connect alternative telemetry sources to For more information, see the `MCT.telemetry.BaseProvider`
|
||||
|
||||
### `MCT.telemetry.BaseProvider`
|
||||
|
||||
The base provider is a great starting point for developers who would like to implement their own telemetry provider. At the same time, you can implement your own telemetry provider as long as it meets the TelemetryProvider (see other docs).
|
||||
|
||||
## Other tools
|
||||
|
||||
### `MCT.telemetry.TelemetryCollection`
|
||||
|
||||
The TelemetryCollection is a useful tool for building advanced displays. It helps you seamlessly handle both historical and realtime telemetry data, while making it easier to deal with large data sets and interactive displays that need to frequently requery data.
|
||||
|
||||
|
||||
|
||||
# API Reference (TODO)
|
||||
|
||||
* Telemetry Metadata
|
||||
* Request Options
|
||||
-- start
|
||||
-- end
|
||||
-- sort
|
||||
-- ???
|
||||
-- strategies -- specify which strategies you want. an array provides for fallback strategies without needing decoration. Design fallbacks into API.
|
||||
|
||||
### `MCT.telemetry.request(domainObject, options)`
|
||||
### `MCT.telemetry.subscribe(domainObject, callback, options)`
|
||||
### `MCT.telemetry.getMetadata(domainObject)`
|
||||
### `MCT.telemetry.Formatter`
|
||||
### `MCT.telemetry.LimitEvaluator`
|
||||
### `MCT.telemetry.registerProvider(telemetryProvider)`
|
||||
### `MCT.telemetry.BaseProvider`
|
||||
### `MCT.telemetry.TelemetryCollection`
|
239
api/telemetry-api/TelemetryAPI.js
Normal file
239
api/telemetry-api/TelemetryAPI.js
Normal file
@ -0,0 +1,239 @@
|
||||
/*global define,window,console,MCT*/
|
||||
|
||||
/**
|
||||
|
||||
var key = '114ced6c-deb7-4169-ae71-68c571665514';
|
||||
MCT.objects.getObject([key])
|
||||
.then(function (results) {
|
||||
console.log('got results');
|
||||
return results[key];
|
||||
})
|
||||
.then(function (domainObject) {
|
||||
console.log('got object');
|
||||
MCT.telemetry.subscribe(domainObject, function (datum) {
|
||||
console.log('gotData!', datum);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
*/
|
||||
|
||||
define([
|
||||
'lodash',
|
||||
'eventemitter2'
|
||||
], function (
|
||||
_,
|
||||
EventEmitter
|
||||
) {
|
||||
|
||||
// format map is a placeholder until we figure out format service.
|
||||
var FORMAT_MAP = {
|
||||
generic: function (range) {
|
||||
return function (datum) {
|
||||
return datum[range.key];
|
||||
};
|
||||
},
|
||||
enum: function (range) {
|
||||
var enumMap = _.indexBy(range.enumerations, 'value');
|
||||
return function (datum) {
|
||||
try {
|
||||
return enumMap[datum[range.valueKey]].text;
|
||||
} catch (e) {
|
||||
return datum[range.valueKey];
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
FORMAT_MAP.number =
|
||||
FORMAT_MAP.float =
|
||||
FORMAT_MAP.integer =
|
||||
FORMAT_MAP.ascii =
|
||||
FORMAT_MAP.generic;
|
||||
|
||||
|
||||
|
||||
function TelemetryAPI(
|
||||
formatService
|
||||
) {
|
||||
|
||||
var FORMATTER_CACHE = new WeakMap(),
|
||||
EVALUATOR_CACHE = new WeakMap();
|
||||
|
||||
function testAPI() {
|
||||
var key = '114ced6c-deb7-4169-ae71-68c571665514';
|
||||
window.MCT.objects.getObjects([key])
|
||||
.then(function (results) {
|
||||
console.log('got results');
|
||||
return results[key];
|
||||
})
|
||||
.then(function (domainObject) {
|
||||
var formatter = new MCT.telemetry.Formatter(domainObject);
|
||||
console.log('got object');
|
||||
window.MCT.telemetry.subscribe(domainObject, function (datum) {
|
||||
var formattedValues = {};
|
||||
Object.keys(datum).forEach(function (key) {
|
||||
formattedValues[key] = formatter.format(datum, key);
|
||||
});
|
||||
console.log(
|
||||
'datum:',
|
||||
datum,
|
||||
'formatted:',
|
||||
formattedValues
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getFormatter(range) {
|
||||
if (FORMAT_MAP[range.type]) {
|
||||
return FORMAT_MAP[range.type](range);
|
||||
}
|
||||
try {
|
||||
var format = formatService.getFormat(range.type).format.bind(
|
||||
formatService.getFormat(range.type)
|
||||
),
|
||||
formatter = function (datum) {
|
||||
return format(datum[range.key]);
|
||||
};
|
||||
return formatter;
|
||||
} catch (e) {
|
||||
console.log('could not retrieve format', range, e, e.message);
|
||||
return FORMAT_MAP.generic(range);
|
||||
}
|
||||
}
|
||||
|
||||
function TelemetryFormatter(domainObject) {
|
||||
this.metadata = domainObject.getCapability('telemetry').getMetadata();
|
||||
this.formats = {};
|
||||
var ranges = this.metadata.ranges.concat(this.metadata.domains);
|
||||
|
||||
ranges.forEach(function (range) {
|
||||
this.formats[range.key] = getFormatter(range);
|
||||
}, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the 'key' from the datum and format it accordingly to
|
||||
* telemetry metadata in domain object.
|
||||
*/
|
||||
TelemetryFormatter.prototype.format = function (datum, key) {
|
||||
return this.formats[key](datum);
|
||||
};
|
||||
|
||||
function LimitEvaluator(domainObject) {
|
||||
this.domainObject = domainObject;
|
||||
this.evaluator = domainObject.getCapability('limit');
|
||||
if (!this.evaluator) {
|
||||
this.evalute = function () {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** TODO: Do we need a telemetry parser, or do we assume telemetry
|
||||
is numeric by default? */
|
||||
|
||||
LimitEvaluator.prototype.evaluate = function (datum, key) {
|
||||
return this.evaluator.evaluate(datum, key);
|
||||
};
|
||||
|
||||
/** Basic telemetry collection, needs more magic. **/
|
||||
function TelemetryCollection(domainObject) {
|
||||
this.domainObject = domainObject;
|
||||
this.data = [];
|
||||
}
|
||||
|
||||
_.extend(TelemetryCollection.prototype, EventEmitter.prototype);
|
||||
|
||||
TelemetryCollection.prototype.request = function (options) {
|
||||
request(this.domainObject, options).then(function (data) {
|
||||
data.forEach(function (datum) {
|
||||
this.addDatum(datum);
|
||||
}, this);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
TelemetryCollection.prototype.addDatum = function (datum) {
|
||||
this.data.push(datum);
|
||||
this.emit('add', datum);
|
||||
};
|
||||
|
||||
TelemetryCollection.prototype.subscribe = function (options) {
|
||||
if (this.unsubscribe) {
|
||||
this.unsubscribe();
|
||||
delete this.unsubscribe;
|
||||
}
|
||||
|
||||
this.unsubscribe = subscribe(
|
||||
this.domainObject,
|
||||
function (telemetrySeries) {
|
||||
telemetrySeries.getData().forEach(this.addDatum, this);
|
||||
}.bind(this),
|
||||
options
|
||||
);
|
||||
};
|
||||
|
||||
function registerProvider(provider) {
|
||||
// Not yet implemented.
|
||||
console.log('registering provider', provider);
|
||||
}
|
||||
|
||||
function registerEvaluator(evaluator) {
|
||||
// not yet implemented.
|
||||
console.log('registering evaluator', evaluator);
|
||||
}
|
||||
|
||||
function request(domainObject, options) {
|
||||
return domainObject.getCapability('telemetry')
|
||||
.requestData(options)
|
||||
.then(function (telemetrySeries) {
|
||||
return telemetrySeries.getData();
|
||||
});
|
||||
}
|
||||
|
||||
function subscribe(domainObject, callback, options) {
|
||||
return domainObject.getCapability('telemetry')
|
||||
.subscribe(function (series) {
|
||||
series.getData().forEach(callback);
|
||||
}, options);
|
||||
}
|
||||
|
||||
var Telemetry = {
|
||||
registerProvider: registerProvider,
|
||||
registerEvaluator: registerEvaluator,
|
||||
request: request,
|
||||
subscribe: subscribe,
|
||||
getMetadata: function (domainObject) {
|
||||
return domainObject.getCapability('telemetry').getMetadata();
|
||||
},
|
||||
Formatter: function (domainObject) {
|
||||
if (!FORMATTER_CACHE.has(domainObject)) {
|
||||
FORMATTER_CACHE.set(
|
||||
domainObject,
|
||||
new TelemetryFormatter(domainObject)
|
||||
);
|
||||
}
|
||||
return FORMATTER_CACHE.get(domainObject);
|
||||
},
|
||||
LimitEvaluator: function (domainObject) {
|
||||
if (!EVALUATOR_CACHE.has(domainObject)) {
|
||||
EVALUATOR_CACHE.set(
|
||||
domainObject,
|
||||
new LimitEvaluator(domainObject)
|
||||
);
|
||||
}
|
||||
return EVALUATOR_CACHE.get(domainObject);
|
||||
}
|
||||
};
|
||||
|
||||
window.MCT = window.MCT || {};
|
||||
window.MCT.telemetry = Telemetry;
|
||||
window.testAPI = testAPI;
|
||||
|
||||
return Telemetry;
|
||||
}
|
||||
|
||||
return TelemetryAPI;
|
||||
});
|
46
api/telemetry-api/bundle.js
Normal file
46
api/telemetry-api/bundle.js
Normal file
@ -0,0 +1,46 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
define([
|
||||
'./TelemetryAPI',
|
||||
'legacyRegistry'
|
||||
], function (
|
||||
TelemetryAPI,
|
||||
legacyRegistry
|
||||
) {
|
||||
legacyRegistry.register('api/telemetry-api', {
|
||||
name: 'Telemetry API',
|
||||
description: 'The public Telemetry API',
|
||||
extensions: {
|
||||
runs: [
|
||||
{
|
||||
key: "TelemetryAPI",
|
||||
implementation: TelemetryAPI,
|
||||
depends: [
|
||||
'formatService'
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
31
api/type-api/README.md
Normal file
31
api/type-api/README.md
Normal file
@ -0,0 +1,31 @@
|
||||
# Type API - Overview
|
||||
|
||||
The Type API allows you to register type information for domain objects, and allows you to retrieve type information about a given domain object. Crucially, type information allows you to add new creatible object types.
|
||||
|
||||
### MCT.types.Type
|
||||
|
||||
The basic interface for a type. You can extend this to implement your own type,
|
||||
or you can provide an object that implements the same interface.
|
||||
|
||||
`attribute` | `type` | `note`
|
||||
--- | --- | ---
|
||||
`label` | `String` | The human readible name of the type.
|
||||
`key` | `String` | The unique identifier for this type.
|
||||
`glyph` | `String` | The glyph identifier for the type. Displayed in trees, labels, and other locations.
|
||||
`description` | `String` | A basic description of the type visible in the create menu.
|
||||
`isCreatible` | `Boolean`, `Number`, or `Function` | If truthy, this type will be visible in the create menu. Note that objects not in the create menu can be instantiated via other means.
|
||||
`namespace` | `String` | The object namespace that provides instances of this type. This allows you to implement custom object providers for specific types while still utilizing other namespaces for persistence.
|
||||
`properties` | `Object` | Object defining properties of an instance of this class. Properties are used for automatic form generation and automated metadata display. For more information on the definition of this object, look at (some resource-- jsonschema?)
|
||||
`canContain` | `Function` | determins whether objects of this type can contain other objects. Will be invoked with a domain object. Return true to allow composition, return false to disallow composition.
|
||||
|
||||
### MCT.types.register(type)
|
||||
|
||||
Register a type with the type API. Registering a type with the same key as another type will replace the original type definition.
|
||||
|
||||
### MCT.types.getType(typeKey)
|
||||
|
||||
Returns the type definition for a given typeKey. returns undefined if type does not exist.
|
||||
|
||||
### MCT.types.getType(domainObject)
|
||||
|
||||
Return the type definition for a given domain object.
|
10
api/type-api/TypeAPI.js
Normal file
10
api/type-api/TypeAPI.js
Normal file
@ -0,0 +1,10 @@
|
||||
define([
|
||||
|
||||
], function () {
|
||||
function TypeAPI() {
|
||||
window.MCT = window.MCT || {};
|
||||
window.MCT.types = {};
|
||||
}
|
||||
|
||||
return TypeAPI;
|
||||
});
|
47
api/type-api/bundle.js
Normal file
47
api/type-api/bundle.js
Normal file
@ -0,0 +1,47 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
define([
|
||||
'./TypeAPI',
|
||||
'legacyRegistry'
|
||||
], function (
|
||||
TypeAPI,
|
||||
legacyRegistry
|
||||
) {
|
||||
legacyRegistry.register('api/type-api', {
|
||||
name: 'Type API',
|
||||
description: 'The public Type API',
|
||||
extensions: {
|
||||
runs: [
|
||||
{
|
||||
key: "TypeAPI",
|
||||
implementation: TypeAPI,
|
||||
depends: [
|
||||
'typeService'
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
});
|
@ -18,6 +18,8 @@
|
||||
"node-uuid": "^1.4.7",
|
||||
"comma-separated-values": "^3.6.4",
|
||||
"FileSaver.js": "^0.0.2",
|
||||
"zepto": "^1.1.6"
|
||||
"zepto": "^1.1.6",
|
||||
"eventemitter2": "^1.0.0",
|
||||
"lodash": "3.10.1"
|
||||
}
|
||||
}
|
||||
|
@ -100,26 +100,46 @@ define([
|
||||
"domains": [
|
||||
{
|
||||
"key": "time",
|
||||
"name": "Time"
|
||||
"name": "Time",
|
||||
"type": "utc"
|
||||
},
|
||||
{
|
||||
"key": "yesterday",
|
||||
"name": "Yesterday"
|
||||
"name": "Yesterday",
|
||||
"type": "utc"
|
||||
},
|
||||
{
|
||||
"key": "delta",
|
||||
"name": "Delta",
|
||||
"format": "example.delta"
|
||||
"type": "example.delta"
|
||||
}
|
||||
],
|
||||
"ranges": [
|
||||
{
|
||||
"key": "sin",
|
||||
"name": "Sine"
|
||||
"name": "Sine",
|
||||
"type": "generic"
|
||||
},
|
||||
{
|
||||
"key": "cos",
|
||||
"name": "Cosine"
|
||||
"name": "Cosine",
|
||||
"type": "generic"
|
||||
},
|
||||
{
|
||||
"key": "positive",
|
||||
"name": "Positive Sine?",
|
||||
"type": "enum",
|
||||
"valueKey": "positive",
|
||||
"enumerations": [
|
||||
{
|
||||
"value": 0,
|
||||
"text": "FALSE"
|
||||
},
|
||||
{
|
||||
"value": 1,
|
||||
"text": "TRUE"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -62,6 +62,9 @@ define(
|
||||
},
|
||||
evaluate: function (datum, range) {
|
||||
range = range || 'sin';
|
||||
if (['sin', 'cos'].indexOf(range) === -1) {
|
||||
return '';
|
||||
}
|
||||
if (datum[range] > RED) {
|
||||
return LIMITS.rh;
|
||||
}
|
||||
@ -84,4 +87,4 @@ define(
|
||||
|
||||
return SinewaveLimitCapability;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -19,10 +19,11 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define,Promise*/
|
||||
/*global define,setInterval,clearInterval*/
|
||||
|
||||
/**
|
||||
* Module defining SinewaveTelemetryProvider. Created by vwoeltje on 11/12/14.
|
||||
* Rewritten by larkin on 05/06/2016.
|
||||
*/
|
||||
define(
|
||||
["./SinewaveTelemetrySeries"],
|
||||
@ -34,86 +35,109 @@ define(
|
||||
* @constructor
|
||||
*/
|
||||
function SinewaveTelemetryProvider($q, $timeout) {
|
||||
var subscriptions = [],
|
||||
generating = false;
|
||||
|
||||
//
|
||||
function matchesSource(request) {
|
||||
return request.source === "generator";
|
||||
}
|
||||
|
||||
// Used internally; this will be repacked by doPackage
|
||||
function generateData(request) {
|
||||
return {
|
||||
key: request.key,
|
||||
telemetry: new SinewaveTelemetrySeries(request)
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
function doPackage(results) {
|
||||
var packaged = {};
|
||||
results.forEach(function (result) {
|
||||
packaged[result.key] = result.telemetry;
|
||||
});
|
||||
// Format as expected (sources -> keys -> telemetry)
|
||||
return { generator: packaged };
|
||||
}
|
||||
|
||||
function requestTelemetry(requests) {
|
||||
return $timeout(function () {
|
||||
return doPackage(requests.filter(matchesSource).map(generateData));
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function handleSubscriptions() {
|
||||
subscriptions.forEach(function (subscription) {
|
||||
var requests = subscription.requests;
|
||||
subscription.callback(doPackage(
|
||||
requests.filter(matchesSource).map(generateData)
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
function startGenerating() {
|
||||
generating = true;
|
||||
$timeout(function () {
|
||||
handleSubscriptions();
|
||||
if (generating && subscriptions.length > 0) {
|
||||
startGenerating();
|
||||
} else {
|
||||
generating = false;
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function subscribe(callback, requests) {
|
||||
var subscription = {
|
||||
callback: callback,
|
||||
requests: requests
|
||||
};
|
||||
|
||||
function unsubscribe() {
|
||||
subscriptions = subscriptions.filter(function (s) {
|
||||
return s !== subscription;
|
||||
});
|
||||
}
|
||||
|
||||
subscriptions.push(subscription);
|
||||
|
||||
if (!generating) {
|
||||
startGenerating();
|
||||
}
|
||||
|
||||
return unsubscribe;
|
||||
}
|
||||
|
||||
return {
|
||||
requestTelemetry: requestTelemetry,
|
||||
subscribe: subscribe
|
||||
};
|
||||
this.$q = $q;
|
||||
this.$timeout = $timeout;
|
||||
}
|
||||
|
||||
SinewaveTelemetryProvider.prototype.canHandleRequest = function (request) {
|
||||
return request.source === 'generator';
|
||||
};
|
||||
|
||||
SinewaveTelemetryProvider.prototype.requestTelemetry = function (requests) {
|
||||
var sinewaveRequests = requests.filter(this.canHandleRequest, this),
|
||||
response = {
|
||||
generator: {}
|
||||
};
|
||||
|
||||
sinewaveRequests.forEach(function (request) {
|
||||
response.generator[request.key] = this.singleRequest(request);
|
||||
}, this);
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
SinewaveTelemetryProvider.prototype.subscribe = function (callback, requests) {
|
||||
var sinewaveRequests = requests.filter(this.canHandleRequest, this),
|
||||
unsubscribers = sinewaveRequests.map(function (request) {
|
||||
return this.singleSubscribe(
|
||||
function (series) {
|
||||
var response = {
|
||||
generator: {}
|
||||
};
|
||||
response.generator[request.key] = series;
|
||||
callback(response);
|
||||
},
|
||||
request
|
||||
);
|
||||
}, this);
|
||||
|
||||
return function () {
|
||||
unsubscribers.forEach(function (unsubscribe) {
|
||||
unsubscribe();
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
SinewaveTelemetryProvider.prototype.singleRequest = function (request) {
|
||||
var start = Math.floor(request.start / 1000) * 1000,
|
||||
end = Math.floor(request.end / 1000) * 1000,
|
||||
period = request.period || 30,
|
||||
data = [],
|
||||
current,
|
||||
i;
|
||||
|
||||
for (current = start; current <= end; current += 2000) {
|
||||
i = Math.floor((current - start) / 1000);
|
||||
data.push({
|
||||
sin: Math.sin(i * Math.PI * 2 / period),
|
||||
cos: Math.cos(i * Math.PI * 2 / period),
|
||||
positive: Math.sin(i * Math.PI * 2 / period) >= 0,
|
||||
time: current,
|
||||
yesterday: current - (60 * 60 * 24 * 1000),
|
||||
delta: current
|
||||
});
|
||||
}
|
||||
return new SinewaveTelemetrySeries(data);
|
||||
};
|
||||
|
||||
|
||||
SinewaveTelemetryProvider.prototype.singleSubscribe = function (callback, options) {
|
||||
// calculate interval position based on start - end; such that this data will line up with data generated by singleRequest.
|
||||
var start = Math.floor((options.start || Date.now()) / 1000) * 1000,
|
||||
currentTime = Math.floor((options.end || Date.now()) / 1000) * 1000,
|
||||
period = options.period || 30,
|
||||
unsubscribe,
|
||||
generatePoint,
|
||||
interval;
|
||||
|
||||
generatePoint = function () {
|
||||
var i = Math.floor((currentTime - start) / 1000),
|
||||
point = {
|
||||
sin: Math.sin(i * Math.PI * 2 / period),
|
||||
cos: Math.cos(i * Math.PI * 2 / period),
|
||||
positive: Math.sin(i * Math.PI * 2 / period) >= 0,
|
||||
time: currentTime,
|
||||
yesterday: currentTime - (60 * 60 * 24 * 1000),
|
||||
delta: currentTime
|
||||
};
|
||||
currentTime += 1000;
|
||||
return point;
|
||||
};
|
||||
|
||||
interval = setInterval(function () {
|
||||
var series = new SinewaveTelemetrySeries(generatePoint());
|
||||
callback(series);
|
||||
}, 1000);
|
||||
|
||||
unsubscribe = function () {
|
||||
clearInterval(interval);
|
||||
};
|
||||
|
||||
return unsubscribe;
|
||||
};
|
||||
|
||||
|
||||
|
||||
return SinewaveTelemetryProvider;
|
||||
}
|
||||
);
|
||||
|
@ -32,47 +32,37 @@ define(
|
||||
var ONE_DAY = 60 * 60 * 24,
|
||||
firstObservedTime = Math.floor(SinewaveConstants.START_TIME / 1000);
|
||||
|
||||
/**
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function SinewaveTelemetrySeries(request) {
|
||||
var timeOffset = (request.domain === 'yesterday') ? ONE_DAY : 0,
|
||||
latestTime = Math.floor(Date.now() / 1000) - timeOffset,
|
||||
firstTime = firstObservedTime - timeOffset,
|
||||
endTime = (request.end !== undefined) ?
|
||||
Math.floor(request.end / 1000) : latestTime,
|
||||
count = Math.min(endTime, latestTime) - firstTime,
|
||||
period = +request.period || 30,
|
||||
generatorData = {},
|
||||
requestStart = (request.start === undefined) ? firstTime :
|
||||
Math.max(Math.floor(request.start / 1000), firstTime),
|
||||
offset = requestStart - firstTime;
|
||||
|
||||
if (request.size !== undefined) {
|
||||
offset = Math.max(offset, count - request.size);
|
||||
function SinewaveTelemetrySeries(data) {
|
||||
if (!Array.isArray(data)) {
|
||||
data = [data];
|
||||
}
|
||||
|
||||
generatorData.getPointCount = function () {
|
||||
return count - offset;
|
||||
};
|
||||
|
||||
generatorData.getDomainValue = function (i, domain) {
|
||||
// delta uses the same numeric values as the default domain,
|
||||
// so it's not checked for here, just formatted for display
|
||||
// differently.
|
||||
return (i + offset) * 1000 + firstTime * 1000 -
|
||||
(domain === 'yesterday' ? (ONE_DAY * 1000) : 0);
|
||||
};
|
||||
|
||||
generatorData.getRangeValue = function (i, range) {
|
||||
range = range || "sin";
|
||||
return Math[range]((i + offset) * Math.PI * 2 / period);
|
||||
};
|
||||
|
||||
return generatorData;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
SinewaveTelemetrySeries.prototype.getPointCount = function () {
|
||||
return this.data.length;
|
||||
};
|
||||
|
||||
SinewaveTelemetrySeries.prototype.getDomainValue = function (i, domain) {
|
||||
return this.getDatum(i)[domain];
|
||||
};
|
||||
|
||||
SinewaveTelemetrySeries.prototype.getRangeValue = function (i, range) {
|
||||
return this.getDatum(i)[range];
|
||||
};
|
||||
|
||||
SinewaveTelemetrySeries.prototype.getDatum = function (i) {
|
||||
if (i >= this.data.length || i < 0) {
|
||||
throw new Error('IndexOutOfRange: index not available in series.');
|
||||
}
|
||||
return this.data[i];
|
||||
};
|
||||
|
||||
SinewaveTelemetrySeries.prototype.getData = function () {
|
||||
return this.data;
|
||||
};
|
||||
|
||||
return SinewaveTelemetrySeries;
|
||||
|
||||
}
|
||||
);
|
||||
|
10
main.js
10
main.js
@ -34,7 +34,9 @@ requirejs.config({
|
||||
"screenfull": "bower_components/screenfull/dist/screenfull.min",
|
||||
"text": "bower_components/text/text",
|
||||
"uuid": "bower_components/node-uuid/uuid",
|
||||
"zepto": "bower_components/zepto/zepto.min"
|
||||
"zepto": "bower_components/zepto/zepto.min",
|
||||
"eventemitter2": "bower_components/eventemitter2/lib/eventemitter2",
|
||||
"lodash": "bower_components/lodash/lodash"
|
||||
},
|
||||
"shim": {
|
||||
"angular": {
|
||||
@ -90,6 +92,10 @@ define([
|
||||
'./platform/search/bundle',
|
||||
'./platform/status/bundle',
|
||||
'./platform/commonUI/regions/bundle',
|
||||
'./api/telemetry-api/bundle',
|
||||
'./api/object-api/bundle',
|
||||
'./api/composition-api/bundle',
|
||||
'./example/scratchpad/bundle',
|
||||
|
||||
'./example/imagery/bundle',
|
||||
'./example/eventGenerator/bundle',
|
||||
@ -103,4 +109,4 @@ define([
|
||||
return new Main().run(legacyRegistry);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
@ -1,64 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define,moment*/
|
||||
|
||||
/**
|
||||
* Module defining DomainColumn.
|
||||
*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* A column which will report telemetry domain values
|
||||
* (typically, timestamps.) Used by the ScrollingListController.
|
||||
*
|
||||
* @memberof platform/features/table
|
||||
* @constructor
|
||||
* @param domainMetadata an object with the machine- and human-
|
||||
* readable names for this domain (in `key` and `name`
|
||||
* fields, respectively.)
|
||||
* @param {TelemetryFormatter} telemetryFormatter the telemetry
|
||||
* formatting service, for making values human-readable.
|
||||
*/
|
||||
function DomainColumn(domainMetadata, telemetryFormatter) {
|
||||
this.domainMetadata = domainMetadata;
|
||||
this.telemetryFormatter = telemetryFormatter;
|
||||
}
|
||||
|
||||
DomainColumn.prototype.getTitle = function () {
|
||||
return this.domainMetadata.name;
|
||||
};
|
||||
|
||||
DomainColumn.prototype.getValue = function (domainObject, datum) {
|
||||
return {
|
||||
text: this.telemetryFormatter.formatDomainValue(
|
||||
datum[this.domainMetadata.key],
|
||||
this.domainMetadata.format
|
||||
)
|
||||
};
|
||||
};
|
||||
|
||||
return DomainColumn;
|
||||
}
|
||||
);
|
@ -19,7 +19,7 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define,Promise*/
|
||||
/*global define*/
|
||||
|
||||
/**
|
||||
* Module defining DomainColumn. Created by vwoeltje on 11/18/14.
|
||||
@ -41,24 +41,23 @@ define(
|
||||
* @param {TelemetryFormatter} telemetryFormatter the telemetry
|
||||
* formatting service, for making values human-readable.
|
||||
*/
|
||||
function RangeColumn(rangeMetadata, telemetryFormatter) {
|
||||
this.rangeMetadata = rangeMetadata;
|
||||
this.telemetryFormatter = telemetryFormatter;
|
||||
function RangeColumn(columnMetadata) {
|
||||
this.title = columnMetadata.name || '';
|
||||
this.key = columnMetadata.key;
|
||||
}
|
||||
|
||||
RangeColumn.prototype.getTitle = function () {
|
||||
return this.rangeMetadata.name;
|
||||
return this.title;
|
||||
};
|
||||
|
||||
RangeColumn.prototype.getValue = function (domainObject, datum) {
|
||||
var range = this.rangeMetadata.key,
|
||||
limit = domainObject.getCapability('limit'),
|
||||
value = isNaN(datum[range]) ? datum[range] : parseFloat(datum[range]),
|
||||
alarm = limit && limit.evaluate(datum, range);
|
||||
var formatter = MCT.telemetry.Formatter(domainObject),
|
||||
evaluator = MCT.telemetry.LimitEvaluator(domainObject),
|
||||
alarm = evaluator.evaluate(datum, this.key);
|
||||
|
||||
return {
|
||||
cssClass: alarm && alarm.cssClass,
|
||||
text: typeof(value) === 'undefined' ? undefined : this.telemetryFormatter.formatRangeValue(value)
|
||||
text: formatter.format(datum, this.key)
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -19,15 +19,14 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define,moment*/
|
||||
/*global define*/
|
||||
|
||||
define(
|
||||
[
|
||||
'./DomainColumn',
|
||||
'./RangeColumn',
|
||||
'./NameColumn'
|
||||
],
|
||||
function (DomainColumn, RangeColumn, NameColumn) {
|
||||
function (RangeColumn, NameColumn) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
@ -36,10 +35,9 @@ define(
|
||||
* @param domainObject
|
||||
* @constructor
|
||||
*/
|
||||
function TableConfiguration(domainObject, telemetryFormatter) {
|
||||
function TableConfiguration(domainObject) {
|
||||
this.domainObject = domainObject;
|
||||
this.columns = [];
|
||||
this.telemetryFormatter = telemetryFormatter;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -57,7 +55,7 @@ define(
|
||||
metadata.forEach(function (metadatum) {
|
||||
//Push domains first
|
||||
(metadatum.domains || []).forEach(function (domainMetadata) {
|
||||
self.addColumn(new DomainColumn(domainMetadata,
|
||||
self.addColumn(new RangeColumn(domainMetadata,
|
||||
self.telemetryFormatter));
|
||||
});
|
||||
(metadatum.ranges || []).forEach(function (rangeMetadata) {
|
||||
@ -75,7 +73,7 @@ define(
|
||||
|
||||
/**
|
||||
* Add a column definition to this Table
|
||||
* @param {RangeColumn | DomainColumn | NameColumn} column
|
||||
* @param {RangeColumn | NameColumn} column
|
||||
* @param {Number} [index] Where the column should appear (will be
|
||||
* affected by column filtering)
|
||||
*/
|
||||
@ -87,26 +85,25 @@ define(
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param column
|
||||
* @returns {*|string}
|
||||
*/
|
||||
TableConfiguration.prototype.getColumnTitle = function (column) {
|
||||
return column.getTitle();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a simple list of column titles
|
||||
* @returns {Array} The titles of the columns
|
||||
*/
|
||||
TableConfiguration.prototype.getHeaders = function () {
|
||||
var self = this;
|
||||
return this.columns.map(function (column, i){
|
||||
return self.getColumnTitle(column) || 'Column ' + (i + 1);
|
||||
});
|
||||
return this.columns.map(this.getColumnTitle, this);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param column
|
||||
* @param i column index
|
||||
* @returns {*|string}
|
||||
*/
|
||||
TableConfiguration.prototype.getColumnTitle = function (column, i) {
|
||||
return column.getTitle() || 'Column ' + (i + 1);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve and format values for a given telemetry datum.
|
||||
* @param telemetryObject The object that the telemetry data is
|
||||
@ -116,24 +113,13 @@ define(
|
||||
* title, and the value is the formatted value from the provided datum.
|
||||
*/
|
||||
TableConfiguration.prototype.getRowValues = function (telemetryObject, datum) {
|
||||
var self = this;
|
||||
return this.columns.reduce(function (rowObject, column, i){
|
||||
var columnTitle = self.getColumnTitle(column) || 'Column ' + (i + 1),
|
||||
return this.columns.reduce(function (row, column, i){
|
||||
var columnTitle = this.getColumnTitle(column, i),
|
||||
columnValue = column.getValue(telemetryObject, datum);
|
||||
|
||||
if (columnValue !== undefined && columnValue.text === undefined){
|
||||
columnValue.text = '';
|
||||
}
|
||||
// Don't replace something with nothing.
|
||||
// This occurs when there are multiple columns with the
|
||||
// column title
|
||||
if (rowObject[columnTitle] === undefined ||
|
||||
rowObject[columnTitle].text === undefined ||
|
||||
rowObject[columnTitle].text.length === 0) {
|
||||
rowObject[columnTitle] = columnValue;
|
||||
}
|
||||
return rowObject;
|
||||
}, {});
|
||||
row[columnTitle] = columnValue;
|
||||
return row;
|
||||
}.bind(this), {});
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1,84 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine,xit*/
|
||||
|
||||
/**
|
||||
* MergeModelsSpec. Created by vwoeltje on 11/6/14.
|
||||
*/
|
||||
define(
|
||||
["../src/DomainColumn"],
|
||||
function (DomainColumn) {
|
||||
"use strict";
|
||||
|
||||
var TEST_DOMAIN_VALUE = "some formatted domain value";
|
||||
|
||||
describe("A domain column", function () {
|
||||
var mockDataSet,
|
||||
testMetadata,
|
||||
mockFormatter,
|
||||
column;
|
||||
|
||||
beforeEach(function () {
|
||||
mockDataSet = jasmine.createSpyObj(
|
||||
"data",
|
||||
[ "getDomainValue" ]
|
||||
);
|
||||
mockFormatter = jasmine.createSpyObj(
|
||||
"formatter",
|
||||
[ "formatDomainValue", "formatRangeValue" ]
|
||||
);
|
||||
testMetadata = {
|
||||
key: "testKey",
|
||||
name: "Test Name"
|
||||
};
|
||||
mockFormatter.formatDomainValue.andReturn(TEST_DOMAIN_VALUE);
|
||||
|
||||
column = new DomainColumn(testMetadata, mockFormatter);
|
||||
});
|
||||
|
||||
it("reports a column header from domain metadata", function () {
|
||||
expect(column.getTitle()).toEqual("Test Name");
|
||||
});
|
||||
|
||||
xit("looks up data from a data set", function () {
|
||||
column.getValue(undefined, mockDataSet, 42);
|
||||
expect(mockDataSet.getDomainValue)
|
||||
.toHaveBeenCalledWith(42, "testKey");
|
||||
});
|
||||
|
||||
xit("formats domain values as time", function () {
|
||||
mockDataSet.getDomainValue.andReturn(402513731000);
|
||||
|
||||
// Should have just given the value the formatter gave
|
||||
expect(column.getValue(undefined, mockDataSet, 42).text)
|
||||
.toEqual(TEST_DOMAIN_VALUE);
|
||||
|
||||
// Make sure that service interactions were as expected
|
||||
expect(mockFormatter.formatDomainValue)
|
||||
.toHaveBeenCalledWith(402513731000);
|
||||
expect(mockFormatter.formatRangeValue)
|
||||
.not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
Reference in New Issue
Block a user