Compare commits

...

19 Commits

Author SHA1 Message Date
Pete Richards
12a4b2395a Spec fixes and minor tweaks 2018-11-07 22:32:44 -08:00
Pete Richards
c179098227 spec wip 2018-11-07 19:52:32 -08:00
Pete Richards
600d24e3af basic autoflow type for testing 2018-11-07 14:06:10 -08:00
Pete Richards
9e360fb226 WIP 2018-11-07 13:26:48 -08:00
Pete Richards
6687a83ca5 [Telemetry] latest value subscription
Add method for telemetry consumers to subscribe to the latest
value.  This method handles data ordering (when data may be
processed out of order) as well as ensuring that users will not
see data outside of the current time bounds when using fixed mode.

Intended to replace the current combination of
request(object, {strategy: 'latest'}) and subscribe(object) for
all telemetry views which display only the latest values of a
telemetry point.
2018-11-07 11:57:29 -08:00
Andrew Henry
e9643ad07f Added brief readmes for all plugins. (#2184) 2018-10-03 18:55:27 -07:00
Pete Richards
ca16892237 [cleanup] remove npm lodash (#2155)
remove npm lodash and unused scripts directory.  Removes usage of
lodash 3.10 in node files.
2018-08-31 12:04:16 -07:00
Pete Richards
a2b0d350d8 Merge pull request #2151 from nasa/update-d3-deps
Updated d3 dependency paths
2018-08-27 13:17:19 -07:00
Andrew Henry
534bdbae50 Updated d3 dependency paths 2018-08-27 10:19:39 -07:00
Andrew Henry
831873e7de Updated README to remove misleading references to instabilities in API. (#2150) 2018-08-21 10:38:12 -07:00
Pete Richards
622d246fdd Merge pull request #2129 from nasa/fixed-position-2125
[Fixed Position] Cannot add on objects when creating Fixed Position for the first time
2018-08-10 10:58:05 -07:00
Pete Richards
4a1ca9f299 Remove stray characters 2018-08-10 10:10:15 -07:00
Pete Richards
4e1de2678c Merge pull request #2134 from nasa/tables-2132
Change table row layout to use flex fixes #2132
2018-08-10 10:07:34 -07:00
Deep Tailor
33a4792531 do not show warning dialog when user moves an object from one location to another (#2142)
* dont show error dialog when user moves an object from one location to another

* add test for not showing blocking message on remove action

Fixes #2141
2018-08-10 10:06:40 -07:00
Pete Richards
37dd4856a6 Merge pull request #2145 from nasa/import-export-links
Fixes Import / Export to work correctly with links and contained objects.
2018-08-10 10:05:28 -07:00
Andrew Henry
6a9cf3389d Fixes #2144. Identifiers for objects and links now exported as identifier objects.
Use keystring as map key on import. Also removed redundant step to re-add imported objects to parent composition.
2018-08-10 07:09:59 -07:00
charlesh88
2da2395473 Change table row layout to use flex
Fixes #2132
- flex styles applied;
- CSS reorganized for better DRY;
- Set height on <tr> so that <td>'s won't collapse when
all cells of a row have empty values. This can occur when columns are
hidden in a Telemetry Table.
2018-07-27 18:04:06 -07:00
Deep Tailor
00ecd27bb3 fixes #2130
fix lingering event listener that was caused by function (setSelection) being stored in two different locations, causing pointer mis match
2018-07-26 12:21:10 -07:00
Deep Tailor
0fa4486dcf force a click on the fixed position element after initialize to initialise Fixed Position controller on the selection API 2018-07-25 16:31:31 -07:00
39 changed files with 1123 additions and 425 deletions

61
API.md
View File

@@ -52,7 +52,6 @@
- [The URL Status Indicator](#the-url-status-indicator)
- [Creating a Simple Indicator](#creating-a-simple-indicator)
- [Custom Indicators](#custom-indicators)
- [Included Plugins](#included-plugins)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
@@ -994,7 +993,7 @@ A common use case for indicators is to convey the state of some external system
persistence backend or HTTP server. So long as this system is accessible via HTTP request,
Open MCT provides a general purpose indicator to show whether the server is available and
returing a 2xx status code. The URL Status Indicator is made available as a default plugin. See
[Included Plugins](#included-plugins) below for details on how to install and configure the
the [documentation](./src/plugins/URLIndicatorPlugin) for details on how to install and configure the
URL Status Indicator.
### Creating a Simple Indicator
@@ -1045,60 +1044,4 @@ A completely custom indicator can be added by simple providing a DOM element to
openmct.indicators.add({
element: domNode
});
```
## Included Plugins
Open MCT is packaged along with a few general-purpose plugins:
* `openmct.plugins.Conductor` provides a user interface for working with time
within the application. If activated, configuration must be provided. This is
detailed in the section on [Time Conductor Configuration](#time-conductor-configuration).
* `openmct.plugins.CouchDB` is an adapter for using CouchDB for persistence
of user-created objects. This is a constructor that takes the URL for the
CouchDB database as a parameter, e.g.
```javascript
openmct.install(openmct.plugins.CouchDB('http://localhost:5984/openmct'))
```
* `openmct.plugins.Elasticsearch` is an adapter for using Elasticsearch for
persistence of user-created objects. This is a
constructor that takes the URL for the Elasticsearch instance as a
parameter. eg.
```javascript
openmct.install(openmct.plugins.CouchDB('http://localhost:9200'))
```
* `openmct.plugins.Espresso` and `openmct.plugins.Snow` are two different
themes (dark and light) available for Open MCT. Note that at least one
of these themes must be installed for Open MCT to appear correctly.
* `openmct.plugins.URLIndicator` adds an indicator which shows the
availability of a URL with the following options:
- `url` : URL to indicate the status of
- `iconClass`: Icon to show in the status bar, defaults to `icon-database`, [list of all icons](https://nasa.github.io/openmct/style-guide/#/browse/styleguide:home?view=items)
- `interval`: Interval between checking the connection, defaults to `10000`
- `label` Name showing up as text in the status bar, defaults to url
```javascript
openmct.install(openmct.plugins.URLIndicator({
url: 'http://localhost:8080',
iconClass: 'check',
interval: 10000,
label: 'Localhost'
})
);
```
* `openmct.plugins.LocalStorage` provides persistence of user-created
objects in browser-local storage. This is particularly useful in
development environments.
* `openmct.plugins.MyItems` adds a top-level folder named "My Items"
when the application is first started, providing a place for a
user to store created items.
* `openmct.plugins.UTCTimeSystem` provides a default time system for Open MCT.
Generally, you will want to either install these plugins, or install
different plugins that provide persistence and an initial folder
hierarchy.
eg.
```javascript
openmct.install(openmct.plugins.LocalStorage());
openmct.install(openmct.plugins.MyItems());
```
```

View File

@@ -20,14 +20,8 @@ API. Open MCT is also being refactored to minimize the dependencies that using
Open MCT imposes on developers, such as the current requirement to use
AngularJS.
This new API has not yet been heavily used and is likely to contain defects.
You can help by trying it out, and reporting any issues you encounter
using our GitHub issue tracker. Such issues may include bugs, suggestions,
missing documentation, or even just requests for help if you're having
trouble.
We want Open MCT to be as easy to use, install, run, and develop for as
possible, and your feedback will help us get there!
possible, and your feedback will help us get there! Feedback can be provided via [GitHub issues](https://github.com/nasa/openmct/issues), or by emailing us at [arc-dl-openmct@nasa.gov](mailto:arc-dl-openmct@nasa.gov).
## Building and Running Open MCT Locally

View File

@@ -31,7 +31,6 @@ var gulp = require('gulp'),
git = require('git-rev-sync'),
moment = require('moment'),
project = require('./package.json'),
_ = require('lodash'),
paths = {
main: 'openmct.js',
dist: 'dist',
@@ -140,7 +139,7 @@ gulp.task('checkstyle', function () {
var mkdirp = require('mkdirp');
var reportName = 'jscs-html-report.html';
var reportPath = path.resolve(paths.reports, 'checkstyle', reportName);
var moveReport = fs.rename.bind(fs, reportName, reportPath, _.noop);
var moveReport = fs.rename.bind(fs, reportName, reportPath, function () {});
mkdirp.sync(path.resolve(paths.reports, 'checkstyle'));
@@ -179,4 +178,4 @@ gulp.task('install', [ 'assets', 'scripts' ]);
gulp.task('verify', [ 'lint', 'test', 'checkstyle' ]);
gulp.task('build', [ 'verify', 'install' ]);
gulp.task('build', [ 'verify', 'install' ]);

View File

@@ -45,8 +45,17 @@
openmct.install(openmct.plugins.UTCTimeSystem());
openmct.install(openmct.plugins.ImportExport());
openmct.install(openmct.plugins.AutoflowView({
type: "telemetry.panel"
type: "view.autoflow"
}));
openmct.types.addType("view.autoflow", {
key: "view.autoflow",
name: "Autoflow",
creatable: true,
initialize: function (model) {
model.composition = [];
return model
}
});
openmct.install(openmct.plugins.Conductor({
menuOptions: [
{

View File

@@ -41,14 +41,14 @@ requirejs.config({
"lodash": "bower_components/lodash/lodash",
"d3-selection": "node_modules/d3-selection/dist/d3-selection.min",
"d3-scale": "node_modules/d3-scale/build/d3-scale.min",
"d3-axis": "node_modules/d3-axis/build/d3-axis.min",
"d3-array": "node_modules/d3-array/build/d3-array.min",
"d3-collection": "node_modules/d3-collection/build/d3-collection.min",
"d3-axis": "node_modules/d3-axis/dist/d3-axis.min",
"d3-array": "node_modules/d3-array/dist/d3-array.min",
"d3-collection": "node_modules/d3-collection/dist/d3-collection.min",
"d3-color": "node_modules/d3-color/build/d3-color.min",
"d3-format": "node_modules/d3-format/build/d3-format.min",
"d3-interpolate": "node_modules/d3-interpolate/build/d3-interpolate.min",
"d3-time": "node_modules/d3-time/build/d3-time.min",
"d3-time-format": "node_modules/d3-time-format/build/d3-time-format.min",
"d3-time": "node_modules/d3-time/dist/d3-time.min",
"d3-time-format": "node_modules/d3-time-format/dist/d3-time-format.min",
"html2canvas": "node_modules/html2canvas/dist/html2canvas.min",
"painterro": "node_modules/painterro/build/painterro.min",
"printj": "node_modules/printj/dist/printj.min"

View File

@@ -1,7 +1,7 @@
{
"name": "openmct",
"version": "0.14.0-SNAPSHOT",
"description": "The Open MCT core platform",
"description": "The Open MCT core platform.",
"dependencies": {
"d3-array": "1.2.x",
"d3-axis": "1.0.x",
@@ -43,7 +43,6 @@
"karma-html-reporter": "^0.2.7",
"karma-jasmine": "^1.1.2",
"karma-requirejs": "^1.1.0",
"lodash": "^3.10.1",
"markdown-toc": "^0.11.7",
"marked": "^0.3.5",
"merge-stream": "^1.0.0",

View File

@@ -51,7 +51,7 @@ define([
/**
* Perform this action.
*/
RemoveAction.prototype.perform = function () {
RemoveAction.prototype.perform = function (skipWarning) {
var dialog,
dialogService = this.dialogService,
domainObject = this.domainObject,
@@ -115,12 +115,18 @@ define([
return parent.useCapability('mutation', doMutate);
}
/*
* Pass in the function to remove the domain object so it can be
* associated with an 'OK' button press
*/
dialog = new RemoveDialog(dialogService, domainObject, removeFromContext);
dialog.show();
if (skipWarning) {
removeFromContext(domainObject);
} else {
/*
* Pass in the function to remove the domain object so it can be
* associated with an 'OK' button press
*/
dialog = new RemoveDialog(dialogService, domainObject, removeFromContext);
dialog.show();
}
};
// Object needs to have a parent for Remove to be applicable

View File

@@ -124,6 +124,17 @@ define(
expect(mockParent.useCapability).not.toHaveBeenCalledWith("mutation", jasmine.any(Function));
});
it("does not show a blocking message dialog when true is passed to perform", function () {
mockParent = jasmine.createSpyObj(
"parent",
["getModel", "getCapability", "useCapability"]
);
action.perform(true);
expect(mockDialogService.showBlockingMessage).not.toHaveBeenCalled();
});
describe("after the remove callback is triggered", function () {
var mockChildContext,
mockChildObject,

View File

@@ -0,0 +1,7 @@
# Espresso Theme
Dark theme for the Open MCT user interface.
## Installation
```js
openmct.install(openmct.plugins.Espresso());
```

View File

@@ -0,0 +1,7 @@
# Espresso Theme
A light colored theme for the Open MCT user interface.
## Installation
```js
openmct.install(openmct.plugins.Snow());
```

View File

@@ -102,14 +102,14 @@ define(
* @returns {Action[]} an array of matching actions
* @memberof platform/core.ActionCapability#
*/
ActionCapability.prototype.perform = function (context) {
ActionCapability.prototype.perform = function (context, flag) {
// Alias to getActions(context)[0].perform, with a
// check for empty arrays.
var actions = this.getActions(context);
return this.$q.when(
(actions && actions.length > 0) ?
actions[0].perform() :
actions[0].perform(flag) :
undefined
);
};

View File

@@ -92,7 +92,7 @@ define(
.then(function () {
return object
.getCapability('action')
.perform('remove');
.perform('remove', true);
});
};

View File

@@ -224,10 +224,11 @@ define(
locationPromise.resolve();
});
it("removes object from parent", function () {
it("removes object from parent without user warning dialog", function () {
expect(actionCapability.perform)
.toHaveBeenCalledWith('remove');
.toHaveBeenCalledWith('remove', true);
});
});
});
@@ -244,9 +245,9 @@ define(
.toHaveBeenCalled();
});
it("removes object from parent", function () {
it("removes object from parent without user warning dialog", function () {
expect(actionCapability.perform)
.toHaveBeenCalledWith('remove');
.toHaveBeenCalledWith('remove', true);
});
});

View File

@@ -87,7 +87,8 @@ define(
'setDisplayedValue',
'subscribeToObject',
'unsubscribe',
'updateView'
'updateView',
'setSelection'
].forEach(function (name) {
self[name] = self[name].bind(self);
});
@@ -224,7 +225,7 @@ define(
// Respond to external bounds changes
this.openmct.time.on("bounds", updateDisplayBounds);
this.openmct.selection.on('change', this.setSelection.bind(this));
this.openmct.selection.on('change', this.setSelection);
this.$element.on('click', this.bypassSelection.bind(this));
this.unlisten = this.openmct.objects.observe(this.newDomainObject, '*', function (obj) {
this.newDomainObject = JSON.parse(JSON.stringify(obj));
@@ -233,6 +234,9 @@ define(
this.updateElementPositions(this.newDomainObject.layoutGrid);
refreshElements();
//force a click, to initialize Fixed Position Controller on SelectionAPI
$element[0].click();
}
FixedController.prototype.updateElementPositions = function (layoutGrid) {

View File

@@ -0,0 +1,8 @@
# My Items plugin
Defines top-level folder named "My Items" to store user-created items. Enabled by default, this can be disabled in a
read-only deployment with no user-editable objects.
## Installation
```js
openmct.install(openmct.plugins.MyItems());
```

View File

@@ -35,28 +35,30 @@ mct-table {
}
.mct-table {
tr {
display: flex; // flex-flow defaults to row nowrap (which is what we want) so no need to define
height: 18px; // Needed when a row has empty values in its cells
align-items: stretch;
}
td, th {
box-sizing: border-box;
display: block;
flex: 1 0 auto;
white-space: nowrap;
}
thead {
display: block;
tr {
display: block;
white-space: nowrap;
th {
display: inline-block;
box-sizing: border-box;
}
}
}
tbody {
tr {
position: absolute;
white-space: nowrap;
display: block;
}
td {
white-space: nowrap;
overflow: hidden;
box-sizing: border-box;
display: inline-block;
}
}
}

View File

@@ -0,0 +1,14 @@
# Import / Export Plugin
The Import/Export plugin allows objects to be exported as JSON files. This allows for sharing of objects between users
who are not using a shared persistence store. It also allows object trees to be backed up. Additionally, object trees
exported using this tool can then be exposed as read-only static root trees using the
[Static Root Plugin](../../src/plugins/staticRootPlugin/README.md).
Upon installation it will add two new context menu actions to allow import and export of objects. Initiating the Export
action on an object will produce a JSON file that includes the object and all of its composed children. Selecting Import
on an object will allow the user to import a previously exported object tree as a child of the selected object.
## Installation
```js
openmct.install(openmct.plugins.ImportExport())
```

View File

@@ -133,7 +133,7 @@ define(['lodash'], function (_) {
copyOfChild.location = parentId;
parent.composition[index] = copyOfChild.identifier;
this.tree[newIdString] = copyOfChild;
this.tree[parentId].composition[index] = newIdString;
this.tree[parentId].composition[index] = copyOfChild.identifier;
return copyOfChild;
};

View File

@@ -79,16 +79,18 @@ define(['zepto'], function ($) {
var parentModel = parent.getModel();
var newObj;
seen.push(parent.getId());
seen.push(this.getKeyString(parent.getId()));
parentModel.composition.forEach(function (childId, index) {
if (!tree[childId] || seen.includes(childId)) {
var childIdString = this.getKeyString(childId);
if (!tree[childIdString] || seen.includes(childIdString)) {
return;
}
newObj = this.instantiate(tree[childId], childId);
parent.getCapability("composition").add(newObj);
newObj.getCapability("location")
.setPrimaryLocation(tree[childId].location);
newObj = this.instantiate(tree[childIdString], childIdString);
// New object has not been persisted yet so clear persisted
// timestamp from copied model.
delete newObj.getModel().persisted;
newObj.getCapability('persistence').persist();
this.deepInstantiate(newObj, tree, seen);
}, this);
}
@@ -103,6 +105,10 @@ define(['zepto'], function ($) {
return tree;
};
ImportAsJSONAction.prototype.getKeyString = function (identifier) {
return this.openmct.objects.makeKeyString(identifier);
};
/**
* Rewrites all instances of a given id in the tree with a newly generated
* replacement to prevent collision.

View File

@@ -47,7 +47,12 @@ define(
uniqueId = 0;
newObjects = [];
openmct = {
$injector: jasmine.createSpyObj('$injector', ['get'])
$injector: jasmine.createSpyObj('$injector', ['get']),
objects: {
makeKeyString: function (identifier) {
return identifier.key;
}
}
};
mockInstantiate = jasmine.createSpy('instantiate').and.callFake(
function (model, id) {
@@ -154,7 +159,7 @@ define(
body: JSON.stringify({
"openmct": {
"infiniteParent": {
"composition": ["infinteChild"],
"composition": [{key: "infinteChild", namespace: ""}],
"name": "1",
"type": "folder",
"modified": 1503598129176,
@@ -162,7 +167,7 @@ define(
"persisted": 1503598129176
},
"infinteChild": {
"composition": ["infiniteParent"],
"composition": [{key: "infinteParent", namespace: ""}],
"name": "2",
"type": "folder",
"modified": 1503598132428,

View File

@@ -1,2 +1,8 @@
This bundle implements a connection to an external CouchDB persistence
store in Open MCT.
# Couch DB Persistence Plugin
An adapter for using CouchDB for persistence of user-created objects. The plugin installation function takes the URL
for the CouchDB database as a parameter.
## Installation
```js
openmct.install(openmct.plugins.CouchDB('http://localhost:5984/openmct'))
```

View File

@@ -1,2 +1,8 @@
This bundle implements a connection to an external ElasticSearch persistence
store in Open MCT.
# Elasticsearch Persistence Provider
An adapter for using Elastic for persistence of user-created objects. The installation function takes the URL for an
Elasticsearch server as a parameter.
## Installation
```js
openmct.install(openmct.plugins.Elasticsearch('http://localhost:9200'))
```

View File

@@ -0,0 +1,9 @@
# Local Storage Plugin
Provides persistence of user-created objects in browser Local Storage. Objects persisted in this way will only be
available from the browser and machine on which they were persisted. For shared persistence, consider the
[Elasticsearch](../elastic/) and [CouchDB](../couch/) persistence plugins.
## Installation
```js
openmct.install(openmct.plugins.LocalStorage());
```

View File

@@ -1,49 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT 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 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.
*****************************************************************************/
// Converts all templateUrl references in bundle.js files to
// plain template references, loading said templates with the
// RequireJS text plugin.
var glob = require('glob'),
fs = require('fs');
function migrate(file) {
var sourceCode = fs.readFileSync(file, 'utf8'),
lines = sourceCode.split('\n')
.filter(function (line) {
return !(/^\W*['"]use strict['"];\W*$/.test(line));
})
.filter(function (line) {
return line.indexOf("/*global") !== 0;
});
fs.writeFileSync(file, lines.join('\n'));
}
glob('@(src|platform)/**/*.js', {}, function (err, files) {
if (err) {
console.log(err);
return;
}
files.forEach(migrate);
});

View File

@@ -1,106 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT 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 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.
*****************************************************************************/
// Converts all templateUrl references in bundle.js files to
// plain template references, loading said templates with the
// RequireJS text plugin.
var glob = require('glob'),
fs = require('fs'),
path = require('path'),
_ = require('lodash');
function toTemplateName(templateUrl) {
var parts = templateUrl.split('/');
return _.camelCase(parts[parts.length - 1].replace(".html", "")) +
"Template";
}
function getTemplateUrl(sourceLine) {
return _.trim(sourceLine.split(":")[1], "\", ");
}
function hasTemplateUrl(sourceLine) {
return sourceLine.indexOf("templateUrl") !== -1;
}
function findTemplateURLs(sourceCode) {
return sourceCode.split('\n')
.map(_.trim)
.filter(hasTemplateUrl)
.map(getTemplateUrl);
}
function injectRequireArgument(sourceCode, templateUrls) {
var lines = sourceCode.split('\n'),
index;
templateUrls = _.uniq(templateUrls);
// Add arguments for source paths...
index = lines.map(_.trim).indexOf("'legacyRegistry'");
lines = lines.slice(0, index).concat(templateUrls.map(function (url) {
return " \"text!./res/" + url + "\",";
}).concat(lines.slice(index)));
/// ...and for arguments
index = lines.map(_.trim).indexOf("legacyRegistry");
lines = lines.slice(0, index).concat(templateUrls.map(function (url) {
return " " + toTemplateName(url) + ",";
}).concat(lines.slice(index)));
return lines.join('\n');
}
function rewriteUrl(sourceLine) {
return [
sourceLine.substring(0, sourceLine.indexOf(sourceLine.trim())),
"\"template\": " + toTemplateName(getTemplateUrl(sourceLine)),
_.endsWith(sourceLine, ",") ? "," : ""
].join('');
}
function rewriteLine(sourceLine) {
return hasTemplateUrl(sourceLine) ?
rewriteUrl(sourceLine.replace("templateUrl", "template")) :
sourceLine;
}
function rewriteTemplateUrls(sourceCode) {
return sourceCode.split('\n').map(rewriteLine).join('\n');
}
function migrate(file) {
var sourceCode = fs.readFileSync(file, 'utf8');
fs.writeFileSync(file, rewriteTemplateUrls(
injectRequireArgument(sourceCode, findTemplateURLs(sourceCode))
), 'utf8');
}
glob('platform/**/bundle.js', {}, function (err, files) {
if (err) {
console.log(err);
return;
}
files.forEach(migrate);
});

View File

@@ -1,34 +0,0 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT 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 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([
<%= implPaths %>
'legacyRegistry'
], function (
<%= implNames %>
legacyRegistry
) {
"use strict";
legacyRegistry.register("<%= bundleName %>", <%= bundleContents %>);
});

View File

@@ -1,72 +0,0 @@
// Temporary utility script to rewrite bundle.json
// files as bundle.js files.
var glob = require('glob'),
fs = require('fs'),
path = require('path'),
_ = require('lodash'),
template = _.template(
fs.readFileSync(path.resolve(__dirname, 'rebundle-template.txt'), 'utf8')
);
function indent(str, depth) {
return _.trimLeft(str.split('\n').map(function (line) {
return _.repeat(' ', depth || 1) + line;
}).filter(function (line) {
return line.trim().length > 0;
}).join('\n'));
}
function findImpls(bundleContents) {
return _(bundleContents.extensions || {})
.map()
.flatten()
.pluck('implementation')
.filter()
.uniq()
.value();
}
function toIdentifier(impl) {
var parts = impl.replace(".js", "").split('/');
return parts[parts.length - 1];
}
function toPath(impl) {
return "\"./src/" + impl.replace(".js", "") + "\"";
}
function replaceImpls(bundleText) {
var rx = /"implementation": "([^"]*)"/;
return bundleText.split('\n').map(function (line) {
var m = line.match(rx);
return m !== null ?
line.replace(rx, '"implementation": ' + toIdentifier(m[1])) :
line;
}).join('\n');
}
function rebundle(file) {
var plainJson = fs.readFileSync(file, 'utf8'),
bundleContents = JSON.parse(plainJson),
impls = findImpls(bundleContents),
bundleName = file.replace("/bundle.json", ""),
outputFile = file.replace(".json", ".js"),
contents = template({
bundleName: bundleName,
implPaths: indent(impls.map(toPath).concat([""]).join(",\n")),
implNames: indent(impls.map(toIdentifier).concat([""]).join(",\n")),
bundleContents: indent(replaceImpls(JSON.stringify(bundleContents, null, 4)))
});
fs.writeFileSync(outputFile, contents, 'utf8');
}
glob('**/bundle.json', {}, function (err, files) {
if (err) {
console.log(err);
return;
}
files.forEach(rebundle);
});

View File

@@ -24,12 +24,14 @@ define([
'./TelemetryMetadataManager',
'./TelemetryValueFormatter',
'./DefaultMetadataProvider',
'./latestValueSubscription',
'../objects/object-utils',
'lodash'
], function (
TelemetryMetadataManager,
TelemetryValueFormatter,
DefaultMetadataProvider,
latestValueSubscription,
objectUtils,
_
) {
@@ -335,6 +337,41 @@ define([
}.bind(this);
};
/**
* Subscribe to receive the latest telemetry value for a given domain
* object. The callback will be called whenever newer data is received from
* a realtime provider. If a LAD provider is available, Open MCT will use
* it to provide an initial value for the latest data subscriber.
*
* Using this method will ensure that you only receive telemetry values in
* order, according to the current time system. If openmct receives a new
* telemetry value from a provider that occurs out of order, i.e. the
* timestamp is less than the last received timestamp, then it will discard
* the message instead of notifying the callback. In a telemetry system
* where data may be processed out of order, this guarantees that the end
* user is always viewing the latest data.
*
* If the user changes the time system, Open MCT will attempt to provide
* a new value from the LAD data provider and continue to provide new values
* via realtime providers.
*
* @param {module:openmct.DomainObject} domainObject the object
* which has associated telemetry
* @param {Function} callback the callback to invoke with new data, as
* it becomes available
* @returns {Function} a function which may be called to terminate
* the subscription
*/
TelemetryAPI.prototype.latest = function (domainObject, callback) {
return latestValueSubscription(
domainObject,
callback,
this,
this.openmct
);
};
/**
* Get telemetry metadata for a given domain object. Returns a telemetry
* metadata manager which provides methods for interrogating telemetry

View File

@@ -0,0 +1,177 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open openmct 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 openmct 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([
], function (
) {
/**
* Subscribe to receive the latest telemetry value for a given domain
* object. The callback will be called whenever newer data is received from
* a realtime provider. If a LAD provider is available, Open MCT will use
* it to provide an initial value for the latest data subscriber.
*
* Using this method will ensure that you only receive telemetry values in
* order, according to the current time system. If openmct receives a new
* telemetry value from a provider that occurs out of order, i.e. the
* timestamp is less than the last received timestamp, then it will discard
* the message instead of notifying the callback. In a telemetry system
* where data may be processed out of order, this guarantees that the end
* user is always viewing the latest data.
*
* If the user changes the time system, Open MCT will attempt to provide
* a new value from the LAD data provider and continue to provide new values
* via realtime providers.
*
* @param {module:openmct.DomainObject} domainObject the object
* which has associated telemetry
* @param {Function} callback the callback to invoke with new data, as
* it becomes available
* @returns {Function} a function which may be called to terminate
* the subscription
*/
function latestValueSubscription(
domainObject,
callback,
telemetryAPI,
openmct
) {
var latestDatum;
var pendingRealtimeDatum;
var currentRequest = 0;
var metadata = telemetryAPI.getMetadata(domainObject);
var formatters = telemetryAPI.getFormatMap(metadata);
var timeFormatter;
var active = true;
var restrictToBounds = false;
function isLater(a, b) {
return !timeFormatter || timeFormatter.parse(a) > timeFormatter.parse(b);
}
function applyBoundsFilter(datum) {
if (withinBounds(datum)) {
callback(datum);
}
}
function withinBounds(datum) {
if (!restrictToBounds || !timeFormatter) {
return true;
}
var timestamp = timeFormatter.parse(datum);
var bounds = openmct.time.bounds();
if (timestamp >= bounds.start && timestamp <= bounds.end) {
return true;
}
}
function updateClock(clock) {
// We restrict to bounds if there is no clock (aka fixed mode) to
// prevent users from seeing data they don't expect.
restrictToBounds = !clock;
}
function callbackIfLatest(datum) {
if (!active) {
return; // prevent LAD notify after unsubscribe.
}
// If we don't have latest data, store datum for later processing.
if (typeof latestDatum === 'undefined') {
if (typeof pendingRealtimeDatum === 'undefined' || (
isLater(datum, pendingRealtimeDatum) &&
withinBounds(datum)
)) {
pendingRealtimeDatum = datum;
}
return;
}
// If there is no latest data, or datum is latest, then notify
// subscriber.
if (latestDatum === false || isLater(datum, latestDatum)) {
latestDatum = datum;
applyBoundsFilter(datum);
}
}
function updateTimeSystem(timeSystem) {
// Reset subscription state, request new latest data and wait for
// response before filtering lad data.
latestDatum = undefined;
pendingRealtimeDatum = undefined;
timeFormatter = formatters[timeSystem.key];
currentRequest++;
var thisRequest = currentRequest;
telemetryAPI.request(domainObject, {strategy: 'latest', size: 1})
.then(function (results) {
return results[results.length - 1];
}, function (error) {
return undefined;
})
.then(function (datum) {
if (currentRequest !== thisRequest) {
return; // prevent race.
}
if (!datum || !withinBounds(datum)) {
latestDatum = false;
} else {
latestDatum = datum;
applyBoundsFilter(datum);
}
if (pendingRealtimeDatum) {
callbackIfLatest(pendingRealtimeDatum);
pendingRealtimeDatum = undefined;
}
});
}
function reloadInFixedMode(bounds, isTick) {
if (isTick || openmct.time.clock()) {
return;
}
updateTimeSystem(openmct.time.timeSystem());
}
openmct.time.on('clock', updateClock);
openmct.time.on('timeSystem', updateTimeSystem);
openmct.time.on('bounds', reloadInFixedMode);
var internalUnsubscribe = telemetryAPI.subscribe(domainObject, callbackIfLatest);
updateClock(openmct.time.clock());
updateTimeSystem(openmct.time.timeSystem());
return function unsubscribe() {
active = false;
internalUnsubscribe();
openmct.time.off('clock', updateClock);
openmct.time.off('timeSystem', updateTimeSystem);
openmct.time.off('bounds', reloadInFixedMode);
}
};
return latestValueSubscription
});

View File

@@ -0,0 +1,670 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open openmct 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 openmct 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([
'./latestValueSubscription'
], function (
latestValueSubscription
) {
function slowPromise() {
// Emprically, using setTimeout to resolve a promise results in a
// promise that will resolve after every other promise. This is a
// simple way to defer code.
return new Promise(function (resolve, reject) {
setTimeout(resolve);
});
}
describe("latestValueSubscription", function () {
var openmct;
var telemetryAPI;
var telemetryMetadata;
var formatMap;
var pendingRequests;
var subscriptions;
var domainObject;
var callback;
var unsubscribe;
var triggerTimeEvent;
var noLADTestcases = [
{
name: "empty LAD",
trigger: function () {
pendingRequests[0].resolve([]);
}
},
{
name: "rejected LAD",
trigger: function () {
pendingRequests[0].reject();
}
}
];
var outOfBoundsLADTestcases = [
{
name: "future LAD",
trigger: function () {
pendingRequests[0].resolve([{test: 1123}]);
}
},
{
name: "prehistoric LAD",
trigger: function () {
pendingRequests[0].resolve([{test: -150}]);
}
}
];
beforeEach(function () {
openmct = {
time: jasmine.createSpyObj('timeAPI', [
'clock',
'timeSystem',
'bounds',
'on',
'off'
])
};
openmct.time.timeSystem.and.returnValue({key: 'test'});
telemetryAPI = jasmine.createSpyObj('telemetryAPI', [
'getMetadata',
'getFormatMap',
'request',
'subscribe'
]);
telemetryMetadata = jasmine.createSpyObj('metadata', [
'values'
]);
telemetryAPI.getMetadata.and.returnValue(telemetryMetadata);
formatMap = {
test: jasmine.createSpyObj('testFormatter', ['parse']),
other: jasmine.createSpyObj('otherFormatter', ['parse'])
};
formatMap.test.parse.and.callFake(function (datum) {
return datum.test;
});
formatMap.other.parse.and.callFake(function (datum) {
return datum.other;
});
telemetryAPI.getFormatMap.and.returnValue(formatMap);
pendingRequests = [];
telemetryAPI.request.and.callFake(function (domainObject, options) {
var request = {
domainObject: domainObject,
options: options
};
request.promise = new Promise(function (resolve, reject) {
request.resolve = resolve;
request.reject = reject;
});
pendingRequests.push(request);
return request.promise;
});
subscriptions = [];
telemetryAPI.subscribe.and.callFake(function (domainObject, callback) {
var subscription = {
domainObject: domainObject,
callback: callback,
unsubscribe: jasmine.createSpy('unsubscribe')
};
subscriptions.push(subscription);
return subscription.unsubscribe;
});
callback = jasmine.createSpy('callback');
domainObject = {};
triggerTimeEvent = function (event, args) {
openmct.time.on.calls.allArgs().filter(function (callArgs) {
return callArgs[0] === event;
})[0][1].apply(null, args);
};
});
// A simple test case to make sure we have appropriate mocks.
it("requests, subscribes, and unsubscribes", function () {
var unsubscribe = latestValueSubscription(
domainObject,
callback,
telemetryAPI,
openmct
);
expect(unsubscribe).toEqual(jasmine.any(Function));
expect(telemetryAPI.request)
.toHaveBeenCalledWith(domainObject, jasmine.any(Object))
expect(telemetryAPI.subscribe)
.toHaveBeenCalledWith(domainObject, jasmine.any(Function))
expect(subscriptions[0].unsubscribe).not.toHaveBeenCalled();
unsubscribe();
expect(subscriptions[0].unsubscribe).toHaveBeenCalled();
});
/** TODO:
* test lad response inside bounds, outside bounds, no response.
* test realtime should wait until lad response (all cases);
* realtime should only notify if later than latest (or no latest).
*
* timesystem change should clear and re-request LAD.
* clock change should enable/disable bounds filtering.
* non-tick bounds change should clear and
*
*
* should receive lad response
* should receive realtime if later than lad.
* should receive lad response (unless outside)
* subscriptions should wait for lad response
*
*/
describe("no clock (AKA fixed)", function () {
var unsubscribe;
beforeEach(function () {
openmct.time.clock.and.returnValue(undefined);
openmct.time.timeSystem.and.returnValue({key: 'test'});
openmct.time.bounds.and.returnValue({start: 0, end: 1000});
unsubscribe = latestValueSubscription(
domainObject,
callback,
telemetryAPI,
openmct
);
});
describe("nominal LAD response", function () {
it("provides LAD datum on resolve", function (done) {
pendingRequests[0].resolve([{test: 123}]);
slowPromise().then(function () {
expect(callback).toHaveBeenCalledWith({test: 123});
done();
});
});
it("sends realtime values synchronously after resolve", function (done) {
pendingRequests[0].resolve([{test: 123}]);
slowPromise().then(function () {
expect(callback).toHaveBeenCalledWith({test: 123});
subscriptions[0].callback({test: 456})
expect(callback).toHaveBeenCalledWith({test: 456});
done();
});
});
it("holds realtime values until resolved", function (done) {
pendingRequests[0].resolve([{test: 123}]);
subscriptions[0].callback({test: 456})
expect(callback).not.toHaveBeenCalledWith({test: 456});
slowPromise().then(function () {
expect(callback).toHaveBeenCalledWith({test: 123});
expect(callback).toHaveBeenCalledWith({test: 456});
done();
});
});
it("only sends latest realtime value after resolve", function (done) {
pendingRequests[0].resolve([{test: 123}]);
subscriptions[0].callback({test: 456});
subscriptions[0].callback({test: 567});
expect(callback).not.toHaveBeenCalledWith({test: 456});
expect(callback).not.toHaveBeenCalledWith({test: 567});
slowPromise().then(function () {
expect(callback).toHaveBeenCalledWith({test: 123});
expect(callback).not.toHaveBeenCalledWith({test: 456});
expect(callback).toHaveBeenCalledWith({test: 567});
done();
});
});
it("filters realtime values before latest", function (done) {
pendingRequests[0].resolve([{test: 456}]);
slowPromise().then(function () {
expect(callback).toHaveBeenCalledWith({test: 456});
subscriptions[0].callback({test: 123})
expect(callback).not.toHaveBeenCalledWith({test: 123});
done();
});
});
it("filters realtime values outside bounds", function (done) {
pendingRequests[0].resolve([{test: 456}]);
slowPromise().then(function () {
expect(callback).toHaveBeenCalledWith({test: 456});
subscriptions[0].callback({test: 1123})
expect(callback).not.toHaveBeenCalledWith({test: 1123});
done();
});
});
it("doesn't override pending value with one outside bounds", function (done) {
pendingRequests[0].resolve([{test: 123}]);
subscriptions[0].callback({test: 456});
subscriptions[0].callback({test: 1123});
slowPromise().then(function () {
expect(callback).toHaveBeenCalledWith({test: 123});
expect(callback).toHaveBeenCalledWith({test: 456});
expect(callback).not.toHaveBeenCalledWith({test: 1123});
done();
});
});
it("doesn't send out of order realtime value", function (done) {
pendingRequests[0].resolve([{test: 123}]);
slowPromise().then(function () {
expect(callback).toHaveBeenCalledWith({test: 123});
subscriptions[0].callback({test: 567});
expect(callback).toHaveBeenCalledWith({test: 567});
subscriptions[0].callback({test: 456});
expect(callback).not.toHaveBeenCalledWith({test: 456});
done();
});
});
});
outOfBoundsLADTestcases.concat(noLADTestcases).forEach(function (testCase) {
describe(testCase.name, function () {
it("does not provide LAD datum", function (done) {
testCase.trigger();
slowPromise().then(function () {
expect(callback).not.toHaveBeenCalled();
done();
});
});
it("sends realtime values synchronously after resolve", function (done) {
testCase.trigger();
slowPromise().then(function () {
subscriptions[0].callback({test: 456})
expect(callback).toHaveBeenCalledWith({test: 456});
done();
});
});
it("holds realtime values until resolved", function (done) {
testCase.trigger();
subscriptions[0].callback({test: 456})
expect(callback).not.toHaveBeenCalledWith({test: 456});
slowPromise().then(function () {
expect(callback).toHaveBeenCalledWith({test: 456});
done();
});
});
it("only sends latest realtime value after resolve", function (done) {
testCase.trigger();
subscriptions[0].callback({test: 456});
subscriptions[0].callback({test: 567});
expect(callback).not.toHaveBeenCalledWith({test: 456});
expect(callback).not.toHaveBeenCalledWith({test: 567});
slowPromise().then(function () {
expect(callback).not.toHaveBeenCalledWith({test: 456});
expect(callback).toHaveBeenCalledWith({test: 567});
done();
});
});
it("filters realtime values outside bounds", function (done) {
testCase.trigger();
slowPromise().then(function () {
subscriptions[0].callback({test: 1123})
expect(callback).not.toHaveBeenCalledWith({test: 1123});
done();
});
});
it("doesn't override pending value with one outside bounds", function (done) {
testCase.trigger();
subscriptions[0].callback({test: 456});
subscriptions[0].callback({test: 1123});
slowPromise().then(function () {
expect(callback).toHaveBeenCalledWith({test: 456});
expect(callback).not.toHaveBeenCalledWith({test: 1123});
done();
});
});
it("doesn't send out of order realtime value", function (done) {
testCase.trigger();
slowPromise().then(function () {
subscriptions[0].callback({test: 567});
expect(callback).toHaveBeenCalledWith({test: 567});
subscriptions[0].callback({test: 456});
expect(callback).not.toHaveBeenCalledWith({test: 456});
done();
});
});
});
});
});
describe("with clock (AKA realtime)", function () {
beforeEach(function () {
openmct.time.clock.and.returnValue({});
openmct.time.timeSystem.and.returnValue({key: 'test'});
openmct.time.bounds.and.returnValue({start: 0, end: 1000});
unsubscribe = latestValueSubscription(
domainObject,
callback,
telemetryAPI,
openmct
);
});
describe("nominal LAD response", function () {
it("provides LAD datum on resolve", function (done) {
pendingRequests[0].resolve([{test: 123}]);
slowPromise().then(function () {
expect(callback).toHaveBeenCalledWith({test: 123});
done();
});
});
it("sends realtime values synchronously after resolve", function (done) {
pendingRequests[0].resolve([{test: 123}]);
slowPromise().then(function () {
expect(callback).toHaveBeenCalledWith({test: 123});
subscriptions[0].callback({test: 456})
expect(callback).toHaveBeenCalledWith({test: 456});
done();
});
});
it("holds realtime values until resolved", function (done) {
pendingRequests[0].resolve([{test: 123}]);
subscriptions[0].callback({test: 456})
expect(callback).not.toHaveBeenCalledWith({test: 456});
slowPromise().then(function () {
expect(callback).toHaveBeenCalledWith({test: 123});
expect(callback).toHaveBeenCalledWith({test: 456});
done();
});
});
it("only sends latest realtime value after resolve", function (done) {
pendingRequests[0].resolve([{test: 123}]);
subscriptions[0].callback({test: 456});
subscriptions[0].callback({test: 567});
expect(callback).not.toHaveBeenCalledWith({test: 456});
expect(callback).not.toHaveBeenCalledWith({test: 567});
slowPromise().then(function () {
expect(callback).toHaveBeenCalledWith({test: 123});
expect(callback).not.toHaveBeenCalledWith({test: 456});
expect(callback).toHaveBeenCalledWith({test: 567});
done();
});
});
it("filters realtime values before latest", function (done) {
pendingRequests[0].resolve([{test: 456}]);
slowPromise().then(function () {
expect(callback).toHaveBeenCalledWith({test: 456});
subscriptions[0].callback({test: 123})
expect(callback).not.toHaveBeenCalledWith({test: 123});
done();
});
});
it("does not filter realtime values outside bounds", function (done) {
pendingRequests[0].resolve([{test: 456}]);
slowPromise().then(function () {
expect(callback).toHaveBeenCalledWith({test: 456});
subscriptions[0].callback({test: 1123})
expect(callback).toHaveBeenCalledWith({test: 1123});
done();
});
});
it("overrides pending realtime value with one outside bounds", function (done) {
pendingRequests[0].resolve([{test: 123}]);
subscriptions[0].callback({test: 456});
subscriptions[0].callback({test: 1123});
slowPromise().then(function () {
expect(callback).toHaveBeenCalledWith({test: 123});
expect(callback).not.toHaveBeenCalledWith({test: 456});
expect(callback).toHaveBeenCalledWith({test: 1123});
done();
});
});
it("doesn't send out of order realtime value", function (done) {
pendingRequests[0].resolve([{test: 123}]);
slowPromise().then(function () {
expect(callback).toHaveBeenCalledWith({test: 123});
subscriptions[0].callback({test: 567});
expect(callback).toHaveBeenCalledWith({test: 567});
subscriptions[0].callback({test: 456});
expect(callback).not.toHaveBeenCalledWith({test: 456});
done();
});
});
});
noLADTestcases.forEach(function (testCase) {
describe(testCase.name, function () {
it("does not provide LAD datum", function (done) {
testCase.trigger();
slowPromise().then(function () {
expect(callback).not.toHaveBeenCalled();
done();
});
});
it("sends realtime values synchronously after resolve", function (done) {
testCase.trigger();
slowPromise().then(function () {
subscriptions[0].callback({test: 456})
expect(callback).toHaveBeenCalledWith({test: 456});
done();
});
});
it("holds realtime values until resolved", function (done) {
testCase.trigger();
subscriptions[0].callback({test: 456})
expect(callback).not.toHaveBeenCalledWith({test: 456});
slowPromise().then(function () {
expect(callback).toHaveBeenCalledWith({test: 456});
done();
});
});
it("only sends latest realtime value after resolve", function (done) {
testCase.trigger();
subscriptions[0].callback({test: 456});
subscriptions[0].callback({test: 567});
expect(callback).not.toHaveBeenCalledWith({test: 456});
expect(callback).not.toHaveBeenCalledWith({test: 567});
slowPromise().then(function () {
expect(callback).not.toHaveBeenCalledWith({test: 456});
expect(callback).toHaveBeenCalledWith({test: 567});
done();
});
});
it("doesn't filter realtime values outside bounds", function (done) {
testCase.trigger();
slowPromise().then(function () {
subscriptions[0].callback({test: 1123})
expect(callback).toHaveBeenCalledWith({test: 1123});
done();
});
});
it("doesn't filter pending realtime values outside bounds", function (done) {
testCase.trigger();
subscriptions[0].callback({test: 456});
subscriptions[0].callback({test: 1123});
slowPromise().then(function () {
expect(callback).not.toHaveBeenCalledWith({test: 456});
expect(callback).toHaveBeenCalledWith({test: 1123});
done();
});
});
it("doesn't send out of order realtime value", function (done) {
testCase.trigger();
slowPromise().then(function () {
subscriptions[0].callback({test: 567});
expect(callback).toHaveBeenCalledWith({test: 567});
subscriptions[0].callback({test: 456});
expect(callback).not.toHaveBeenCalledWith({test: 456});
done();
});
});
});
});
describe("out of bounds LAD", function () {
outOfBoundsLADTestcases.forEach(function (testCase) {
it(`provides ${testCase} datum`, function (done) {
testCase.trigger();
slowPromise().then(function () {
expect(callback).toHaveBeenCalled();
done();
});
});
});
});
});
describe("clock changes", function () {
beforeEach(function () {
openmct.time.timeSystem.and.returnValue({key: 'test'});
openmct.time.bounds.and.returnValue({start: 0, end: 1000});
});
it("starts bounds filtering when clock is cleared", function (done) {
openmct.time.clock.and.returnValue({});
unsubscribe = latestValueSubscription(
domainObject,
callback,
telemetryAPI,
openmct
);
pendingRequests[0].resolve([]);
slowPromise().then(function () {
subscriptions[0].callback({test: 1123});
expect(callback).toHaveBeenCalledWith({test: 1123});
triggerTimeEvent('clock', undefined);
subscriptions[0].callback({test: 1223});
expect(callback).not.toHaveBeenCalledWith({test: 1223});
done();
});
});
it("stops bounds filtering when clock is set", function (done) {
openmct.time.clock.and.returnValue(undefined);
unsubscribe = latestValueSubscription(
domainObject,
callback,
telemetryAPI,
openmct
);
pendingRequests[0].resolve([]);
slowPromise().then(function () {
subscriptions[0].callback({test: 1123});
expect(callback).not.toHaveBeenCalledWith({test: 1123});
triggerTimeEvent('clock', [{}]);
subscriptions[0].callback({test: 1223});
expect(callback).toHaveBeenCalledWith({test: 1223});
done();
});
});
});
describe("timesystem changes", function () {
it("requeries lad and uses new keys.", function (done) {
openmct.time.clock.and.returnValue(undefined);
openmct.time.timeSystem.and.returnValue({key: 'test'});
openmct.time.bounds.and.returnValue({start: 0, end: 1000});
unsubscribe = latestValueSubscription(
domainObject,
callback,
telemetryAPI,
openmct
);
expect(pendingRequests.length).toBe(1);
expect(subscriptions.length).toBe(1);
pendingRequests[0].resolve([{test: 234}]);
slowPromise().then(function () {
expect(callback).toHaveBeenCalledWith({test: 234});
triggerTimeEvent('timeSystem', [{key: 'other'}]);
expect(pendingRequests.length).toBe(2);
expect(subscriptions.length).toBe(1);
pendingRequests[1].resolve([{test: 123, other: 456}]);
return slowPromise(); // wait for new lad to resolve.
}).then(function() {
expect(callback).toHaveBeenCalledWith({test: 123, other: 456});
// should have synchronous callbacks when other is greater.
subscriptions[0].callback({test: 234, other: 567});
expect(callback).toHaveBeenCalledWith({test: 234, other:567});
// should filter out when other is less.
subscriptions[0].callback({test: 345, other: 345});
expect(callback).not.toHaveBeenCalledWith({test: 345, other: 345});
done();
});
});
});
it("does not filter when no value matches timesystem", function (done) {
openmct.time.clock.and.returnValue(undefined);
openmct.time.timeSystem.and.returnValue({key: 'blah'});
openmct.time.bounds.and.returnValue({start: 0, end: 1000});
unsubscribe = latestValueSubscription(
domainObject,
callback,
telemetryAPI,
openmct
);
pendingRequests[0].resolve([{test: 1234}]);
slowPromise().then(function () {
expect(callback).toHaveBeenCalledWith({test: 1234});
subscriptions[0].callback({test: 567});
expect(callback).toHaveBeenCalledWith({test: 567});
done();
});
});
describe("on bounds event", function () {
// TODO: test cases for what happens when bounds changes.
});
});
});

View File

@@ -0,0 +1,21 @@
# URL Indicator
Adds an indicator which shows the availability of a URL, with success based on receipt of a 200 HTTP code. Can be used
for monitoring the availability of web services.
## Installation
```js
openmct.install(openmct.plugins.URLIndicator({
url: 'http://localhost:8080',
iconClass: 'check',
interval: 10000,
label: 'Localhost'
})
);
```
## Options
* __url__: URL to indicate the status of
* __iconClass__: Icon to show in the status bar, defaults to icon-database. See the [Style Guide](https://nasa.github.io/openmct/style-guide/#/browse/styleguide:home/glyphs?view=styleguide.glyphs) for more icon options.
* __interval__: Interval between checking the connection, defaults to 10000
* __label__: Name showing up as text in the status bar, defaults to url

View File

@@ -25,15 +25,6 @@ define([
], function (
AutoflowTabularView
) {
/**
* This plugin provides an Autoflow Tabular View for domain objects
* in Open MCT.
*
* @param {Object} options
* @param {String} [options.type] the domain object type for which
* this view should be available; if omitted, this view will
* be available for all objects
*/
return function (options) {
return function (openmct) {
var views = (openmct.mainViews || openmct.objectViews);

View File

@@ -41,8 +41,7 @@ define([
spyOn(mockmct.telemetry, 'getMetadata');
spyOn(mockmct.telemetry, 'getValueFormatter');
spyOn(mockmct.telemetry, 'limitEvaluator');
spyOn(mockmct.telemetry, 'request');
spyOn(mockmct.telemetry, 'subscribe');
spyOn(mockmct.telemetry, 'latest');
var plugin = new AutoflowTabularPlugin({ type: testType });
plugin(mockmct);
@@ -140,15 +139,11 @@ define([
return mockFormatter;
});
mockmct.telemetry.limitEvaluator.and.returnValue(mockEvaluator);
mockmct.telemetry.subscribe.and.callFake(function (obj, callback) {
mockmct.telemetry.latest.and.callFake(function (obj, callback) {
var key = obj.identifier.key;
callbacks[key] = callback;
return mockUnsubscribes[key];
});
mockmct.telemetry.request.and.callFake(function (obj, request) {
var key = obj.identifier.key;
return Promise.resolve([testHistories[key]]);
});
mockMetadata.valuesForHints.and.callFake(function (hints) {
return [{ hint: hints[0] }];
});
@@ -232,19 +227,6 @@ define([
});
});
it("displays historical telemetry", function () {
function rowTextDefined() {
return $(testContainer).find(".l-autoflow-item").filter(".r").text() !== "";
}
return domObserver.when(rowTextDefined).then(function () {
testKeys.forEach(function (key, index) {
var datum = testHistories[key];
var $cell = $(testContainer).find(".l-autoflow-row").eq(index).find(".r");
expect($cell.text()).toEqual(String(datum.range));
});
});
});
it("displays incoming telemetry", function () {
var testData = testKeys.map(function (key, index) {
return { key: key, range: index * 100, domain: key + index };

View File

@@ -66,19 +66,10 @@ define([], function () {
* Activate this controller; begin listening for changes.
*/
AutoflowTabularRowController.prototype.activate = function () {
this.unsubscribe = this.openmct.telemetry.subscribe(
this.unsubscribe = this.openmct.telemetry.latest(
this.domainObject,
this.updateRowData.bind(this)
);
this.openmct.telemetry.request(
this.domainObject,
{ size: 1 }
).then(function (history) {
if (!this.initialized && history.length > 0) {
this.updateRowData(history[history.length - 1]);
}
}.bind(this));
};
/**

View File

@@ -0,0 +1,15 @@
# Autoflow View
This plugin provides the Autoflow View for domain objects in Open MCT. This view allows users to visualize the latest
values of a collection of telemetry points in a condensed list.
## Installation
``` js
openmct.install(openmct.plugins.AutoflowView({
type: "telemetry.fixed"
}));
```
## Options
* `type`: The object type to add the Autoflow View to. Currently supports a single value. If not provided, will make the
Autoflow view available for all objects (which is probably not what you want).

View File

@@ -0,0 +1,10 @@
# Plot Plugin
Enables plot visualization of telemetry data. This plugin adds a plot view that is available from the view switcher for
all telemetry objects. Two user createble objects are also added by this plugin, for Overlay and Stacked Plots.
Telemetry objects can be added to Overlay and Stacked Plots via drag and drop.
## Installation
``` js
openmct.install(openmct.plugins.Plot());
```

View File

@@ -0,0 +1,19 @@
# Static Root Plugin
This plugin takes an object tree as JSON and exposes it as a non-editable root level tree. This can be useful if you
have static non-editable content that you wish to expose, such as a standard set of displays that should not be edited
(but which can be copied and then modified if desired).
Any object tree in Open MCT can be exported as JSON after installing the
[Import/Export plugin](../../../platform/import-export/README.md).
## Installation
``` js
openmct.install(openmct.plugins.StaticRootPlugin('mission', 'data/static-objects.json'));
```
## Parameters
The StaticRootPlugin takes two parameters:
1. __namespace__: This should be a name that uniquely identifies this collection of objects.
2. __path__: The file that the static tree should be exposed from. This will need to be a path that is reachable by a web
browser, ie not a path on the local file system.

View File

@@ -0,0 +1,10 @@
# Summary Widget Plugin
Summary widgets can be used to provide visual indication of state based on telemetry data. They allow rules to be
defined that can then be used to change the appearance of the summary widget element based on data. For example, a
summary widget could be defined that is green when a temparature reading is between `0` and `100` centigrade, red when
it's above `100`, and orange when it's below `0`.
## Installation
```js
openmct.install(openmct.plugins.SummaryWidget());
```

View File

@@ -67,14 +67,14 @@ requirejs.config({
"lodash": "bower_components/lodash/lodash",
"d3-selection": "node_modules/d3-selection/dist/d3-selection.min",
"d3-scale": "node_modules/d3-scale/build/d3-scale.min",
"d3-axis": "node_modules/d3-axis/build/d3-axis.min",
"d3-array": "node_modules/d3-array/build/d3-array.min",
"d3-collection": "node_modules/d3-collection/build/d3-collection.min",
"d3-axis": "node_modules/d3-axis/dist/d3-axis.min",
"d3-array": "node_modules/d3-array/dist/d3-array.min",
"d3-collection": "node_modules/d3-collection/dist/d3-collection.min",
"d3-color": "node_modules/d3-color/build/d3-color.min",
"d3-format": "node_modules/d3-format/build/d3-format.min",
"d3-interpolate": "node_modules/d3-interpolate/build/d3-interpolate.min",
"d3-time": "node_modules/d3-time/build/d3-time.min",
"d3-time-format": "node_modules/d3-time-format/build/d3-time-format.min",
"d3-time": "node_modules/d3-time/dist/d3-time.min",
"d3-time-format": "node_modules/d3-time-format/dist/d3-time-format.min",
"html2canvas": "node_modules/html2canvas/dist/html2canvas.min",
"painterro": "node_modules/painterro/build/painterro.min",
"printj": "node_modules/printj/dist/printj.min"