Compare commits

..

44 Commits

Author SHA1 Message Date
Deep Tailor
6da0044adf update description of resync action and reduce pixel contant for drag handler 2018-04-16 14:12:27 -07:00
Deep Tailor
76a88fc1e5 major performance update to import activities from csv 2018-04-12 15:20:24 -07:00
Deep Tailor
6901684124 add actionPolicy for import action, add resync button to timeline to resync duration of timespan with imported duration 2018-04-12 12:30:57 -07:00
Deep Tailor
dbb5b9bb4c change seconds to milliseconds when importing and creatin activities 2018-04-05 10:24:47 -07:00
Deep Tailor
dce20825ea pull most current master 2018-04-05 10:09:41 -07:00
Deep Tailor
5fa9925ea1 code cleanup activity import action 2018-03-12 15:10:05 -07:00
Deep Tailor
d774458b17 fixed issue of not linking right activity modes if user provides id 2018-03-12 11:50:16 -07:00
Deep Tailor
22f464e02b fix bug where activity modes arent being linked to their activities 2018-03-12 11:30:37 -07:00
Deep Tailor
ef8f52769e add imported id support for linked activity modes 2018-03-12 11:26:53 -07:00
Deep Tailor
f25987cfed add support for imported id's 2018-03-12 10:49:57 -07:00
Deep Tailor
684dd4ff8c fix persist issue with couch and make copies, make fragments 2018-02-22 10:44:36 -08:00
Deep Tailor
a36b86516f remove activity-modes folder to prevent couch error, for now activity modes dont have a location, but activities can still point to them 2018-02-21 15:54:30 -08:00
Deep Tailor
99e103fd9d allow multiple users to import their own activities based on new folders that they import into 2018-02-21 14:25:30 -08:00
Deep Tailor
2224fba618 clean code in activityModesImportAction 2018-02-12 15:34:10 -08:00
Deep Tailor
b3fcbddeb7 fix activity modes folder error for couch 2018-02-12 15:17:41 -08:00
Deep Tailor
b3ababd67e search for existing objects with same id, before instantiating, to prevent 409 error for couchDB 2018-02-12 13:58:00 -08:00
Deep Tailor
0e3c5e1199 store properties on parent (it can be either an activity or timeline) 2018-02-06 11:00:46 -08:00
Deep Tailor
d72db1ff8c fix finding parent timeline algorithm 2018-02-05 15:29:44 -08:00
Deep Tailor
bade2e1678 Merge branch 'master' of https://github.com/nasa/openmct into activity-import 2018-02-05 12:37:54 -08:00
Deep Tailor
4bd1a192ed fix checkstyle errors, and add d3-dsv to test-main 2018-02-01 15:41:10 -08:00
Deep Tailor
89ab47d835 fix duration bug by setting duration before start and end time 2018-02-01 13:24:50 -08:00
Deep Tailor
27dfb18991 fix older timelines in new model 2018-02-01 11:42:28 -08:00
Deep Tailor
9e57a661ea set duration on move 2018-01-29 12:46:10 -08:00
Deep Tailor
116e346aaf fix gantt move bug, frag number bug 2018-01-29 12:40:41 -08:00
Deep Tailor
78cfdd3942 fragment works need to add logic to remove start and end from timeline on destroy 2018-01-27 01:28:08 -08:00
Deep Tailor
a8ecdf98b5 fix checkstyle and lint errors 2018-01-25 15:37:50 -08:00
Deep Tailor
fee74883c6 add alias to vertical split pane to persist user width preference 2018-01-25 15:27:32 -08:00
Deep Tailor
2e8e6306ee change fragment to copy
copy button working as expected
2018-01-25 13:15:42 -08:00
Deep Tailor
7f2cc89f42 working action button - fragment 2018-01-25 10:54:34 -08:00
Deep Tailor
321271bd4d fix drag malfunction" 2018-01-23 15:18:09 -08:00
Deep Tailor
006e99fb07 keep durations specific to timelines 2018-01-11 13:15:27 -08:00
Deep Tailor
63dc4b6253 store start time on timeline and use from there if changed using drag component 2018-01-11 11:54:00 -08:00
Deep Tailor
85868f690e show blocking error if file type is not csv 2018-01-08 15:30:25 -08:00
Deep Tailor
b1f34f7cd7 fix checkstyle 2018-01-08 13:54:42 -08:00
Deep Tailor
8785d9a9d7 store imported activity-modes in a folder in the import location 2018-01-08 13:50:42 -08:00
Deep Tailor
3a6e1fd301 merge latest master 2017-12-19 13:48:59 -08:00
Deep Tailor
8161e4fc89 clean code 2017-12-18 15:16:30 -08:00
Deep Tailor
abf7654027 linking imported activity modes to activities working 2017-12-18 13:05:33 -08:00
Deep Tailor
8fba707321 link imported acitivity mode to correspoding activity 2017-12-18 12:44:17 -08:00
Deep Tailor
8ad5cca936 allow instantiation of activity modes from same csv file 2017-12-18 11:28:21 -08:00
Deep Tailor
754d484501 give imported actiivities predictable ids to prevent duplication when imported again 2017-12-18 10:31:42 -08:00
Deep Tailor
74717b59c3 working import, instantiate and provide propertiess to activities 2017-12-14 10:38:20 -08:00
Deep Tailor
ffdb19787b persist csv file objects to window local storage 2017-12-11 16:19:39 -08:00
Deep Tailor
5dc0d8c7f8 Create new Root Object, add provider and create new import action. Working import from CSV. CSV being parsed to array of objects 2017-12-11 14:29:29 -08:00
90 changed files with 902 additions and 2563 deletions

11
API.md
View File

@@ -23,7 +23,7 @@
- [Value Hints](#value-hints)
- [The Time Conductor and Telemetry](#the-time-conductor-and-telemetry)
- [Telemetry Providers](#telemetry-providers)
- [Telemetry Requests and Responses.](#telemetry-requests-and-responses)
- [Telemetry Requests](#telemetry-requests)
- [Request Strategies **draft**](#request-strategies-draft)
- [`latest` request strategy](#latest-request-strategy)
- [`minmax` request strategy](#minmax-request-strategy)
@@ -32,7 +32,7 @@
- [Telemetry Data](#telemetry-data)
- [Telemetry Datums](#telemetry-datums)
- [Limit Evaluators **draft**](#limit-evaluators-draft)
- [Telemetry Consumer APIs **draft**](#telemetry-consumer-apis-draft)
- [Telemetry Visualization APIs **draft**](#telemetry-visualization-apis-draft)
- [Time API](#time-api)
- [Time Systems and Bounds](#time-systems-and-bounds)
- [Defining and Registering Time Systems](#defining-and-registering-time-systems)
@@ -449,7 +449,7 @@ A telemetry provider is a javascript object with up to four methods:
* `supportsSubscribe(domainObject, callback, options)` optional. Must be implemented to provide realtime telemetry. Should return `true` if the provider supports subscriptions for the given domain object (and request options).
* `subscribe(domainObject, callback, options)` required if `supportsSubscribe` is implemented. Establish a subscription for realtime data for the given domain object. Should invoke `callback` with a single telemetry datum every time data is received. Must return an unsubscribe function. Multiple views can subscribe to the same telemetry object, so it should always return a new unsubscribe function.
* `supportsRequest(domainObject, options)` optional. Must be implemented to provide historical telemetry. Should return `true` if the provider supports historical requests for the given domain object.
* `request(domainObject, options)` required if `supportsRequest` is implemented. Must return a promise for an array of telemetry datums that fulfills the request. The `options` argument will include a `start`, `end`, and `domain` attribute representing the query bounds. See [Telemetry Requests and Responses](#telemetry-requests-and-responses) for more info on how to respond to requests.
* `request(domainObject, options)` required if `supportsRequest` is implemented. Must return a promise for an array of telemetry datums that fulfills the request. The `options` argument will include a `start`, `end`, and `domain` attribute representing the query bounds. For more request properties, see Request Properties below.
* `supportsMetadata(domainObject)` optional. Implement and return `true` for objects that you want to provide dynamic metadata for.
* `getMetadata(domainObject)` required if `supportsMetadata` is implemented. Must return a valid telemetry metadata definition that includes at least one valueMetadata definition.
* `supportsLimits(domainObject)` optional. Implement and return `true` for domain objects that you want to provide a limit evaluator for.
@@ -466,7 +466,7 @@ openmct.telemetry.addProvider({
Note: it is not required to implement all of the methods on every provider. Depending on the complexity of your implementation, it may be helpful to instantiate and register your realtime, historical, and metadata providers separately.
#### Telemetry Requests and Responses.
#### Telemetry Requests
Telemetry requests support time bounded queries. A call to a _Telemetry Provider_'s `request` function will include an `options` argument. These are simply javascript objects with attributes for the request parameters. An example of a telemetry request object with a start and end time is included below:
@@ -480,7 +480,8 @@ Telemetry requests support time bounded queries. A call to a _Telemetry Provider
In this case, the `domain` is the currently selected time-system, and the start and end dates are valid dates in that time system.
A telemetry provider's `request` method should return a promise for an array of telemetry datums. These datums must be sorted by `domain` in ascending order.
The response to a telemetry request is an array of telemetry datums.
These datums must be sorted by `domain` in ascending order.
#### Request Strategies **draft**

View File

@@ -1,6 +1,6 @@
machine:
node:
version: 8.11.0
version: 4.7.0
dependencies:
pre:
@@ -24,4 +24,4 @@ test:
general:
branches:
ignore:
- gh-pages
- gh-pages

View File

@@ -47,9 +47,7 @@ define([
var interval = setInterval(function () {
var now = Date.now();
var datum = pointForTimestamp(now, duration, domainObject.name);
datum.value += "";
callback(datum);
callback(pointForTimestamp(now, duration, domainObject.name));
}, duration);
return function () {

View File

@@ -101,11 +101,11 @@
padding-top: 1em;
.cols {
display: flex;
flex-direction: row;
@include display(flex);
@include flex-direction(row);
.col {
flex: 1 1 auto;
@include flex(1 1 auto);
&:not(:last-child) {
$v: $interiorMargin * 4;
border-right: 1px solid $colorInteriorBorder;
@@ -199,7 +199,7 @@
border-radius: 15%;
position: absolute;
left: 50%;
transform: translateX(-50%);
@include transform(translateX(-50%));
}
}
}

View File

@@ -37,7 +37,8 @@
openmct.legacyRegistry.enable.bind(openmct.legacyRegistry)
);
openmct.install(openmct.plugins.MyItems());
openmct.install(openmct.plugins.LocalStorage());
// openmct.install(openmct.plugins.LocalStorage());
openmct.install(openmct.plugins.CouchDB('http://127.0.0.1:5984/openmct'));
openmct.install(openmct.plugins.Espresso());
openmct.install(openmct.plugins.Generator());
openmct.install(openmct.plugins.ExampleImagery());
@@ -68,6 +69,7 @@
]
}));
openmct.install(openmct.plugins.SummaryWidget());
openmct.install(openmct.plugins.ActivityModes());
openmct.time.clock('local', {start: -THIRTY_MINUTES, end: 0});
openmct.time.timeSystem('utc');
openmct.start();

View File

@@ -49,7 +49,8 @@ requirejs.config({
"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-format": "node_modules/d3-time-format/build/d3-time-format.min",
"d3-dsv": "node_modules/d3-dsv/build/d3-dsv.min"
},
"shim": {
"angular": {

View File

@@ -1,6 +1,6 @@
{
"name": "openmct",
"version": "0.14.0-SNAPSHOT",
"version": "0.13.3-SNAPSHOT",
"description": "The Open MCT core platform",
"dependencies": {
"d3-array": "1.2.x",
@@ -8,6 +8,7 @@
"d3-collection": "1.0.x",
"d3-color": "1.0.x",
"d3-format": "1.2.x",
"d3-dsv": "^1.0.8",
"d3-interpolate": "1.1.x",
"d3-scale": "1.0.x",
"d3-selection": "1.3.x",

View File

@@ -38,7 +38,6 @@ define([
"./src/policies/EditableLinkPolicy",
"./src/policies/EditableMovePolicy",
"./src/policies/EditContextualActionPolicy",
"./src/policies/RemoveActionPolicy",
"./src/representers/EditRepresenter",
"./src/representers/EditToolbarRepresenter",
"./src/capabilities/EditorCapability",
@@ -78,7 +77,6 @@ define([
EditableLinkPolicy,
EditableMovePolicy,
EditContextualActionPolicy,
RemoveActionPolicy,
EditRepresenter,
EditToolbarRepresenter,
EditorCapability,
@@ -270,10 +268,6 @@ define([
"category": "action",
"implementation": EditableLinkPolicy
},
{
"category": "action",
"implementation": RemoveActionPolicy
},
{
"implementation": CreationPolicy,
"category": "creation"

View File

@@ -88,6 +88,12 @@ define(
commit("Changes from toolbar.");
}
}
// Avoid attaching scope to this;
// http://errors.angularjs.org/1.2.26/ng/cpws
this.setSelection = function (s) {
scope.selection = s;
};
this.clearExposedToolbar = function () {
// Clear exposed toolbar state (if any)
if (attrs.toolbar) {
@@ -104,7 +110,6 @@ define(
this.toolbar = undefined;
this.toolbarObject = {};
this.openmct = openmct;
this.scope = scope;
// If this representation exposes a toolbar, set up watches
// to synchronize with it.
@@ -125,23 +130,26 @@ define(
// Represent a domain object using this definition
EditToolbarRepresenter.prototype.represent = function (representation) {
// Get the newest toolbar definition from the view
var definition = (representation || {}).toolbar || {};
var definition = (representation || {}).toolbar || {},
self = this;
// If we have been asked to expose toolbar state...
if (this.attrs.toolbar) {
// Initialize toolbar object
this.toolbar = new EditToolbar(definition, this.commit);
// Ensure toolbar state is exposed
this.exposeToolbar();
// Initialize toolbar (expose object to parent scope)
function initialize(def) {
// If we have been asked to expose toolbar state...
if (self.attrs.toolbar) {
// Initialize toolbar object
self.toolbar = new EditToolbar(def, self.commit);
// Ensure toolbar state is exposed
self.exposeToolbar();
}
}
// Add toolbar selection to scope.
this.scope.selection = new EditToolbarSelection(
this.scope,
this.openmct
);
// Initialize toolbar to current selection
this.updateSelection(this.scope.selection.all());
// Expose the toolbar object to the parent scope
initialize(definition);
// Create a selection scope
this.setSelection(new EditToolbarSelection(this.openmct));
// Initialize toolbar to an empty selection
this.updateSelection([]);
};
// Destroy; remove toolbar object from parent scope

View File

@@ -38,37 +38,24 @@ define(
* @memberof platform/commonUI/edit
* @constructor
*/
function EditToolbarSelection($scope, openmct) {
function EditToolbarSelection(openmct) {
this.selection = [{}];
this.selecting = false;
this.selectedObj = undefined;
this.openmct = openmct;
var self = this;
function setSelection(selection) {
openmct.selection.on('change', function (selection) {
var selected = selection[0];
if (selected && selected.context.toolbar) {
self.select(selected.context.toolbar);
this.select(selected.context.toolbar);
} else {
self.deselect();
this.deselect();
}
if (selected && selected.context.viewProxy) {
self.proxy(selected.context.viewProxy);
this.proxy(selected.context.viewProxy);
}
setTimeout(function () {
$scope.$apply();
});
}
$scope.$on("$destroy", function () {
self.openmct.selection.off('change', setSelection);
});
this.openmct.selection.on('change', setSelection);
setSelection(this.openmct.selection.get());
}.bind(this));
}
/**

View File

@@ -36,7 +36,7 @@ define(
beforeEach(function () {
mockScope = jasmine.createSpyObj(
'$scope',
['$on', '$watch', '$watchCollection', "commit", "$apply"]
['$on', '$watch', '$watchCollection', "commit"]
);
mockElement = {};
testAttrs = { toolbar: 'testToolbar' };

View File

@@ -30,8 +30,7 @@ define(
otherElement,
selection,
mockSelection,
mockOpenMCT,
mockScope;
mockOpenMCT;
beforeEach(function () {
testProxy = { someKey: "some value" };
@@ -47,12 +46,7 @@ define(
mockOpenMCT = {
selection: mockSelection
};
mockScope = jasmine.createSpyObj('$scope', [
'$on',
'$apply'
]);
selection = new EditToolbarSelection(mockScope, mockOpenMCT);
selection = new EditToolbarSelection(mockOpenMCT);
selection.proxy(testProxy);
});
@@ -109,20 +103,6 @@ define(
expect(selection.all()).toEqual([testProxy]);
});
it("cleans up selection on scope destroy", function () {
expect(mockScope.$on).toHaveBeenCalledWith(
'$destroy',
jasmine.any(Function)
);
mockScope.$on.mostRecentCall.args[1]();
expect(mockOpenMCT.selection.off).toHaveBeenCalledWith(
'change',
jasmine.any(Function)
);
});
});
}
);

View File

@@ -1,91 +1,91 @@
@include keyframes(rotation) {
100% { transform: rotate(360deg); }
100% { @include transform(rotate(360deg)); }
}
@include keyframes(rotation-centered) {
0% { transform: translate(-50%, -50%) rotate(0deg); }
100% { transform: translate(-50%, -50%) rotate(360deg); }
0% { @include transform(translate(-50%, -50%) rotate(0deg)); }
100% { @include transform(translate(-50%, -50%) rotate(360deg)); }
}
@include keyframes(clock-hands) {
0% { transform: translate(-50%, -50%) rotate(0deg); }
100% { transform: translate(-50%, -50%) rotate(360deg); }
0% { @include transform(translate(-50%, -50%) rotate(0deg)); }
100% { @include transform(translate(-50%, -50%) rotate(360deg)); }
}
@include keyframes(clock-hands-sticky) {
0% {
transform: translate(-50%, -50%) rotate(0deg);
@include transform(translate(-50%, -50%) rotate(0deg));
}
7% {
transform: translate(-50%, -50%) rotate(0deg);
@include transform(translate(-50%, -50%) rotate(0deg));
}
8% {
transform: translate(-50%, -50%) rotate(30deg);
@include transform(translate(-50%, -50%) rotate(30deg));
}
15% {
transform: translate(-50%, -50%) rotate(30deg);
@include transform(translate(-50%, -50%) rotate(30deg));
}
16% {
transform: translate(-50%, -50%) rotate(60deg);
@include transform(translate(-50%, -50%) rotate(60deg));
}
24% {
transform: translate(-50%, -50%) rotate(60deg);
@include transform(translate(-50%, -50%) rotate(60deg));
}
25% {
transform: translate(-50%, -50%) rotate(90deg);
@include transform(translate(-50%, -50%) rotate(90deg));
}
32% {
transform: translate(-50%, -50%) rotate(90deg);
@include transform(translate(-50%, -50%) rotate(90deg));
}
33% {
transform: translate(-50%, -50%) rotate(120deg);
@include transform(translate(-50%, -50%) rotate(120deg));
}
40% {
transform: translate(-50%, -50%) rotate(120deg);
@include transform(translate(-50%, -50%) rotate(120deg));
}
41% {
transform: translate(-50%, -50%) rotate(150deg);
@include transform(translate(-50%, -50%) rotate(150deg));
}
49% {
transform: translate(-50%, -50%) rotate(150deg);
@include transform(translate(-50%, -50%) rotate(150deg));
}
50% {
transform: translate(-50%, -50%) rotate(180deg);
@include transform(translate(-50%, -50%) rotate(180deg));
}
57% {
transform: translate(-50%, -50%) rotate(180deg);
@include transform(translate(-50%, -50%) rotate(180deg));
}
58% {
transform: translate(-50%, -50%) rotate(210deg);
@include transform(translate(-50%, -50%) rotate(210deg));
}
65% {
transform: translate(-50%, -50%) rotate(210deg);
@include transform(translate(-50%, -50%) rotate(210deg));
}
66% {
transform: translate(-50%, -50%) rotate(240deg);
@include transform(translate(-50%, -50%) rotate(240deg));
}
74% {
transform: translate(-50%, -50%) rotate(240deg);
@include transform(translate(-50%, -50%) rotate(240deg));
}
75% {
transform: translate(-50%, -50%) rotate(270deg);
@include transform(translate(-50%, -50%) rotate(270deg));
}
82% {
transform: translate(-50%, -50%) rotate(270deg);
@include transform(translate(-50%, -50%) rotate(270deg));
}
83% {
transform: translate(-50%, -50%) rotate(300deg);
@include transform(translate(-50%, -50%) rotate(300deg));
}
90% {
transform: translate(-50%, -50%) rotate(300deg);
@include transform(translate(-50%, -50%) rotate(300deg));
}
91% {
transform: translate(-50%, -50%) rotate(330deg);
@include transform(translate(-50%, -50%) rotate(330deg));
}
99% {
transform: translate(-50%, -50%) rotate(330deg);
@include transform(translate(-50%, -50%) rotate(330deg));
}
100% {
transform: translate(-50%, -50%) rotate(360deg);
@include transform(translate(-50%, -50%) rotate(360deg));
}
}

View File

@@ -1,11 +0,0 @@
// At the last, hide .l-splash-holder and show .holder-all
.l-splash-holder.fadeout {
@include trans-prop-nice($props: opacity, $dur: 1000ms);
opacity: 0;
pointer-events: none;
}
.user-environ .holder-all {
opacity: 1;
pointer-events: inherit;
}

View File

@@ -94,19 +94,19 @@
/********************************************* FLEX STYLES */
.l-flex-row,
.l-flex-col {
display: flex;
flex-wrap: nowrap;
@include display(flex);
@include flex-wrap(nowrap);
.flex-elem {
min-height: 0; // Needed to allow element to shrink within parent
position: relative;
&:not(.grows) {
flex: 0 0 auto;
@include flex(0 0 auto);
&.flex-can-shrink {
flex: 0 1 auto;
@include flex(0 1 auto);
}
}
&.grows {
flex: 1 1 auto;
@include flex(1 1 auto);
}
&.contents-align-right {
text-align: right;
@@ -114,25 +114,25 @@
}
.flex-container {
// Apply to wrapping elements, mct-includes, etc.
display: flex;
flex-wrap: nowrap;
flex: 1 1 auto;
@include display(flex);
@include flex-wrap(nowrap);
@include flex(1 1 auto);
min-height:0;
}
}
.l-flex-row {
flex-direction: row;
&.flex-elem { flex: 1 1 auto; }
@include flex-direction(row);
&.flex-elem { @include flex(1 1 auto); }
> .flex-elem {
min-width: 0;
&.holder:not(:last-child) { margin-right: $interiorMargin; }
}
.flex-container { flex-direction: row; }
.flex-container { @include flex-direction(row); }
}
.l-flex-col {
flex-direction: column;
@include flex-direction(column);
> .flex-elem {
min-height: 0;
&.holder:not(:last-child) { margin-bottom: $interiorMarginLg; }
@@ -142,15 +142,15 @@
flex-direction: column;
//overflow: hidden !important;
}
.flex-container { flex-direction: column; }
.flex-container { @include flex-direction(column); }
}
.flex-fixed {
flex: 0 0 auto;
@include flex(0 0 auto);
}
.flex-justify-end {
justify-content: flex-end;
@include justify-content(flex-end);
}
/********************************************* POPUPS */

View File

@@ -83,8 +83,8 @@
height: auto; width: auto;
position: absolute;
left: 0; top: 0; right: 0; bottom: 20%;
transform-origin: bottom left;
transform: scale(0.3);
@include transform-origin(bottom left);
@include transform(scale(0.3));
z-index: 2;
}
}

View File

@@ -53,6 +53,7 @@
.l-inspector-part {
box-sizing: border-box;
padding-right: $interiorMargin;
.tree .form {
margin-left: $treeVCW + $interiorMarginLg;
}
@@ -78,7 +79,8 @@
}
}
.form-row {
align-items: center;
// To be replaced with .inspector-config, see below.
@include align-items(center);
border: none !important;
margin-bottom: 0 !important;
padding: $interiorMarginSm 0;
@@ -99,15 +101,12 @@
position: relative;
}
ul li {
margin-bottom: $interiorMarginLg;
}
em.t-inspector-part-header {
border-radius: $basicCr;
background-color: $colorInspectorSectionHeaderBg;
color: $colorInspectorSectionHeaderFg;
margin-bottom: $interiorMargin;
margin-top: $interiorMarginLg;
//margin-bottom: $interiorMargin;
padding: floor($formTBPad * .75) $formLRPad;
text-transform: uppercase;
}
@@ -201,3 +200,102 @@ mct-representation:not(.s-status-editing) .l-inspect {
pointer-events: inherit;
}
}
// NEW COMPACT FORM, FOR USE IN INSPECTOR
// ul > li > label, control
// Make a new UL for each form section
// Allow control-first, controls-below
.l-inspect .tree ul li,
.inspector-config ul li {
padding: 2px 0;
}
.inspector-config {
$labelW: 40%;
$minW: $labelW;
ul {
margin-bottom: $interiorMarginLg;
li {
@include display(flex);
@include flex-wrap(wrap);
@include align-items(center);
label,
.control {
@include display(flex);
min-width: $minW;
}
label {
line-height: inherit;
padding: $interiorMarginSm 0;
width: $labelW;
}
.control {
@include flex-grow(1);
}
&:not(.section-header) {
&:not(.connects-to-previous) {
//border-top: 1px solid $colorFormLines;
}
}
&.connects-to-previous {
padding-top: 0 !important;
}
&.section-header {
margin-top: $interiorMarginLg;
border-top: 1px solid $colorFormLines;
}
&.controls-first {
.control {
@include flex-grow(0);
margin-right: $interiorMargin;
min-width: 0;
order: 1;
width: auto;
}
label {
@include flex-grow(1);
order: 2;
width: auto;
}
}
&.controls-under {
display: block;
.control, label {
display: block;
width: auto;
}
ul li {
border-top: none !important;
padding: 0;
}
}
}
}
.form-error {
// Block element that visually flags an error and contains a message
background-color: $colorFormFieldErrorBg;
color: $colorFormFieldErrorFg;
border-radius: $basicCr;
display: block;
padding: 1px 6px;
&:before {
content: $glyph-icon-alert-triangle;
display: inline;
font-family: symbolsfont;
margin-right: $interiorMarginSm;
}
}
}
.tree .inspector-config {
margin-left: $treeVCW + $interiorMarginLg;
}

View File

@@ -76,11 +76,9 @@
@import "items/item";
@import "mobile/item";
/********************************* TO BE MOVED */
@import "autoflow";
@import "features/imagery";
@import "features/time-display";
@import "widgets";
/********************************* APP STARTUP */
@import "app-start";

View File

@@ -382,7 +382,7 @@
/* This doesn't work on an element inside an element with absolute positioning that has height: auto */
//position: relative;
top: 50%;
transform: translateY(-50%);
@include transform(translateY(-50%));
}
@mixin verticalCenterBlock($holderH, $itemH) {

View File

@@ -22,8 +22,8 @@
// mct-representation surrounding an object-label key="'label'"
.rep-object-label {
flex-direction: row;
flex: 1 1 auto;
@include flex-direction(row);
@include flex(1 1 auto);
height: inherit;
line-height: inherit;
min-width: 0;

View File

@@ -55,7 +55,7 @@
.widget-rule-header {
@extend .l-flex-row;
align-items: center;
@include align-items(center);
margin-bottom: $interiorMargin;
> .flex-elem {
&:not(:first-child) {
@@ -103,7 +103,7 @@
.l-compact-form label {
$ruleLabelW: 40%;
$ruleLabelMaxW: 150px;
display: flex;
@include display(flex);
max-width: $ruleLabelMaxW;
width: $ruleLabelW;
}
@@ -177,8 +177,8 @@
ul {
&:last-child { margin: 0; }
li {
align-items: flex-start;
flex-wrap: nowrap;
@include align-items(flex-start);
@include flex-wrap(nowrap);
line-height: 230%; // Provide enough space when controls wrap
padding: 2px 0;
&:not(.widget-rule-header) {
@@ -233,7 +233,7 @@
.l-widget-thumb-wrapper {
@extend .l-flex-row;
align-items: center;
@include align-items(center);
> span { display: block; }
.grippy-holder,
.view-control {
@@ -243,18 +243,18 @@
}
.widget-thumb {
flex: 1 1 auto;
@include flex(1 1 auto);
width: 100%;
}
}
.rule-title {
flex: 0 1 auto;
@include flex(0 1 auto);
color: pullForward($colorBodyFg, 50%);
}
.rule-description {
flex: 1 1 auto;
@include flex(1 1 auto);
@include ellipsize();
color: pushBack($colorBodyFg, 20%);
}

View File

@@ -356,7 +356,7 @@ input[type="text"].s-input-inline,
}
}
&:before {
transform: translateY(-50%);
@include transform(translateY(-50%));
color: rgba($colorInvokeMenu, percentToDecimal($contrastInvokeMenuPercent));
display: block;
pointer-events: none;
@@ -434,7 +434,7 @@ input[type="text"].s-input-inline,
.context-available {
font-size: 0.7em;
flex: 0 0 1;
@include flex(0 0 1);
}
.t-object-alert {
@@ -675,14 +675,14 @@ input[type="range"] {
.l-calendar {
$colorMuted: pushBack($colorMenuFg, 30%);
ul.l-cal-row {
display: flex;
flex-flow: row nowrap;
@include display(flex);
@include flex-flow(row nowrap);
margin-top: 1px;
&:first-child {
margin-top: 0;
}
li {
flex: 1 0;
@include flex(1 0);
margin-left: 1px;
padding: $interiorMargin;
text-align: center;
@@ -763,10 +763,10 @@ textarea {
&:before {
position: absolute;
@include trans-prop-nice(transform, 100ms);
transform-origin: center;
@include transform-origin(center);
}
&.expanded:before {
transform: rotate(90deg);
@include transform(rotate(90deg));
}
}

View File

@@ -157,16 +157,16 @@
$lh: $ueFooterH - ($m*2) - 1;
box-sizing: border-box;
@include ellipsize();
display: flex;
flex-direction: row;
align-items: center;
@include display(flex);
@include flex-direction(row);
@include align-items(center);
position: absolute;
top: $m; right: auto; bottom: $m; left: 50%;
height: auto; width: auto;
line-height: $lh;
max-width: 300px;
padding: 0 $interiorMargin 0 $interiorMargin;
transform: translateX(-50%);
@include transform(translateX(-50%));
&.minimized {
@include transition-property(left, opacity);
@@ -185,7 +185,7 @@
}
.banner-elem {
flex: 0 1 auto;
@include flex(0 1 auto);
margin-left: $interiorMargin;
}
a {
@@ -250,14 +250,14 @@
// Archetypal message
.l-message {
$iconW: 32px;
display: flex;
flex-direction: row;
align-items: stretch;
@include display(flex);
@include flex-direction(row);
@include align-items(stretch);
padding: $interiorMarginLg;
&:before {
// Icon
flex: 0 1 auto;
@include flex(0 1 auto);
@include txtShdw($shdwStatusIc);
@extend .icon-bell;
color: $colorStatusDefault;
@@ -283,9 +283,9 @@
.w-message-contents {
flex: 1 1 auto;
display: flex;
flex-direction: column;
@include flex(1 1 auto);
@include display(flex);
@include flex-direction(column);
> div,
> span {
@@ -294,7 +294,7 @@
}
.message-body {
flex: 1 1 100%;
@include flex(1 1 100%);
}
}
@@ -331,8 +331,8 @@
// In a list
.t-message-list {
@include absPosDefault();
display: flex;
flex-direction: column;
@include display(flex);
@include flex-direction(column);
> div,
> span {
@@ -340,7 +340,7 @@
}
.w-messages {
flex: 1 1 100%;
@include flex(1 1 100%);
overflow-y: auto;
padding-right: $interiorMargin;
}
@@ -360,7 +360,7 @@
@include phonePortrait {
.t-message-single .l-message,
.t-message-single.l-message {
flex-direction: column;
@include flex-direction(column);
&:before {
margin-right: 0;
margin-bottom: $interiorMarginLg;

View File

@@ -15,7 +15,7 @@
margin-bottom: $interiorMargin;
}
.l-image-main-controlbar {
&.l-flex-row { align-items: center; }
&.l-flex-row { @include align-items(center); }
}
}

View File

@@ -34,10 +34,13 @@
}
.btns-add-remove {
// background: rgba(#ff0000, 0.3);;
margin-top: 150px;
.s-button {
display: block;
//font-size: 1.5em;
margin-bottom: $interiorMargin;
//padding: 10px;
text-align: center;
}
}

View File

@@ -45,7 +45,7 @@
&.grows {
.l-section-body,
.form-row {
flex: 1 1 auto;
@include flex(1 1 auto);
.wrapper {
height: 100%;
}
@@ -87,7 +87,7 @@
.controls {
order: 2;
position: relative;
flex: 1 1 auto;
@include flex(1 1 auto);
.l-composite-control {
&.l-checkbox {
@@ -124,16 +124,16 @@
>.label,
>.controls {
line-height: inherit;
min-height: inherit;
min-height: inherit;;
}
>.label {
flex: 1 1 auto;
@include flex(1 1 auto);
min-width: 0;
width: auto;
order: 2;
}
>.controls {
flex: 0 0 auto;
@include flex(0 0 auto);
margin-right: $interiorMargin;
order: 1;
}
@@ -141,7 +141,7 @@
.l-controls-under.l-flex-row {
// Change to use column layout
flex-direction: column;
@include flex-direction(column);
.flex-elem {
margin-bottom: $interiorMarginLg;
}
@@ -190,19 +190,19 @@
ul {
margin-bottom: $interiorMarginLg;
li {
display: flex;
flex-wrap: wrap;
align-items: center;
@include display(flex);
@include flex-wrap(wrap);
@include align-items(center);
label,
.control {
display: flex;
@include display(flex);
}
label {
line-height: inherit;
width: $labelW;
}
.controls {
flex-grow: 1;
@include flex-grow(1);
margin-left: $interiorMargin;
input[type="text"],
input[type="search"],
@@ -232,14 +232,14 @@
&.controls-first {
.control {
flex-grow: 0;
@include flex-grow(0);
margin-right: $interiorMargin;
min-width: 0;
order: 1;
width: auto;
}
label {
flex-grow: 1;
@include flex-grow(1);
order: 2;
width: auto;
}

View File

@@ -101,7 +101,7 @@
line-height: inherit;
position: absolute;
top: 50%;
transform: translateY(-50%);
@include transform(translateY(-50%));
z-index: 1;
}

View File

@@ -20,7 +20,7 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
@mixin spinner($b: 5px, $c: $colorKey) {
transform-origin: center;
@include transform-origin(center);
@include animation-name(rotation-centered);
@include animation-duration(0.5s);
@include animation-iteration-count(infinite);

View File

@@ -85,14 +85,14 @@
z-index: 1;
.item-type,
.t-item-icon {
transform: translateX(-50%) translateY(-55%);
@include transform(translateX(-50%) translateY(-55%));
position: absolute;
top: 50%; left: 50%;
font-size: $iconD * 0.95;
&.l-icon-link {
.t-item-icon-glyph {
&:after {
transform: scale(0.25);
@include transform(scale(0.25));
}
}
}

View File

@@ -138,7 +138,7 @@ body.phone.portrait {
}
.pane.right.items {
left: 0 !important;
transform: translateX($proporMenuOnly);
@include transform(translateX($proporMenuOnly));
.holder-object-and-inspector {
opacity: 0;
}

View File

@@ -34,18 +34,7 @@ body.touch {
line-height: $mobileTreeItemH !important;
.view-control {
font-size: 1em;
margin-right: $interiorMargin;
width: ceil($mobileTreeItemH * 0.75);
&.has-children {
&:before {
content: $glyph-icon-arrow-down;
left: 50%;
transform: translateX(-50%) rotate(-90deg);
}
&.expanded:before {
transform: translateX(-50%) rotate(0deg);
}
}
width: ceil($mobileTreeItemH * 0.5);
}
.t-object-label {
line-height: inherit;

View File

@@ -89,7 +89,7 @@
> .abs.outer-holder {
@include desktopandtablet {
$max: 1280px;
transform: translate(-50%, -50%);
@include transform(translateX(-50%) translateY(-50%));
border-radius: $overlayCr;
top: 50%; right: auto; bottom: auto; left: 50%;
width: 70%; height: 70%;
@@ -101,7 +101,7 @@
.editor .form .form-row.l-flex-row {
// Display elements in a columnar view
flex-direction: column;
@include flex-direction(column);
> .flex-elem {
&:not(:first-child) {
margin-top: $interiorMargin;

View File

@@ -152,8 +152,8 @@
&.l-plot-y-label {
$x: -50%;
$r: -90deg;
transform-origin: 50% 0;
transform: translateX($x) rotate($r);
@include transform-origin(50% 0);
@include transform(translateX($x) rotate($r));
display: inline-block;
margin-left: $interiorMargin; // Kick off the left edge
left: 0;
@@ -172,13 +172,13 @@
}
.gl-plot-x-options {
transform: translateX(-50%);
@include transform(translateX(-50%));
bottom: 0;
left: 50%;
}
.gl-plot-y-options {
transform: translateY(-50%);
@include transform(translateY(-50%));
min-width: 150px; // Need this due to enclosure of .select
top: 50%;
left: $plotYLabelW + $interiorMargin * 2;

View File

@@ -152,7 +152,7 @@
opacity: 1;
}
.load-more-button {
transform: translateX(-50%);
@include transform(translateX(-50%));
display: inline-block;
margin-top: $interiorMargin;
padding: 0 $interiorMarginLg;

View File

@@ -32,7 +32,11 @@ body, html {
}
.l-splash-holder {
// Main outer holder for splash.
// Main outer holder.
@include transition-property(opacity);
@include transition-duration(500ms);
@include transition-timing-function(ease-in-out);
@include transition-delay(1s);
position: absolute;
top: 0;
right: 0;
@@ -40,18 +44,16 @@ body, html {
left: 0;
z-index: 10000;
opacity: 1;
&.fadeout {
opacity: 0;
pointer-events: none;
}
.l-splash {
// The splash element.
@include splashElem();
}
}
.user-environ .holder-all {
// Gets shown again by main CSS, once loaded
opacity: 0;
pointer-events: none;
}
@media only screen and (max-device-width: 767px) {
.l-splash-holder .l-splash {
@include splashElem(0);

View File

@@ -142,7 +142,7 @@
.l-hyperlink.s-button {
.label {
@include ellipsize();
transform: translateY(-50%);
@include transform(translateY(-50%));
padding: 0 $interiorMargin;
position: absolute;
min-width: 0;

View File

@@ -159,20 +159,20 @@ body.desktop .pane .mini-tab-icon.toggle-pane {
&.toggle-tree.anchor-left {
left: 0;
transform: translateX(-1 * $paneExpandedOffset);
@include transform(translateX(-1 * $paneExpandedOffset));
&.collapsed {
transform: translateX(-1 * $ueCollapsedPaneEdgeM);
@include transform(translateX(-1 * $ueCollapsedPaneEdgeM));
}
}
&.toggle-inspect.anchor-right {
right: 0;
transform: translateX($paneExpandedOffset);
@include transform(translateX($paneExpandedOffset));
&.flush-right {
transform: translateX(($uePaneMiniTabW + ceil($splitterD / 2)));
@include transform(translateX(($uePaneMiniTabW + ceil($splitterD / 2))));
}
&.collapsed {
transform: translateX($ueCollapsedPaneEdgeM);
@include transform(translateX($ueCollapsedPaneEdgeM));
}
}
}

View File

@@ -40,18 +40,10 @@ define([
return type.getCssClass();
}
function removePreviousIconClass(el) {
$(el).removeClass(function (index, className) {
return (className.match (/\bicon-\S+/g) || []).join(' ');
});
}
TreeLabelView.prototype.updateView = function (domainObject) {
var titleEl = this.el.find('.t-title-label'),
iconEl = this.el.find('.t-item-icon');
removePreviousIconClass(iconEl);
titleEl.text(domainObject ? domainObject.getModel().name : "");
iconEl.addClass(domainObject ? getClass(domainObject) : "");

View File

@@ -55,7 +55,7 @@
div[class*="hand"] {
$handW: 2px;
$handH: $d * 0.4;
transform: translate(-50%, -50%);
@include transform(translate(-50%, -50%));
@include animation-iteration-count(infinite);
@include animation-timing-function(linear);
position: absolute;
@@ -258,7 +258,7 @@
align-items: center;
margin-top: $interiorMargin;
.l-time-conductor-zoom-w {
justify-content: flex-end;
@include justify-content(flex-end);
.time-conductor-zoom {
height: $r3H;
min-width: 100px;
@@ -327,7 +327,7 @@
$i: $glyph-icon-calendar;
.time-conductor-icon div[class*="hand"] {
&.hand-little {
transform: rotate(120deg);
@include transform(rotate(120deg));
}
}
.mode-selector .s-menu-button:before {

View File

@@ -29,7 +29,7 @@ mct-include.l-toi-holder,
mct-include.l-toi-holder {
$blockerFadeW: $toiBlockerFadeW;
transform: translateX(-50%);
@include transform(translateX(-50%));
color: $toiColorBg;
position: absolute;
top: 0;
@@ -66,7 +66,7 @@ mct-include.l-toi-holder {
box-sizing: content-box;
height: $toiH;
left: $toiPad * -2;
transform: translateY(-50%); top: 50%;
@include transform(translateY(-50%)); top: 50%;
padding: $toiPad;
z-index: 1;
@@ -207,7 +207,7 @@ table {
border-radius: 20%;
height: auto;
padding: $toiPad;
transform: translate(-50%, -50%);
@include transform(translate(-50%, -50%));
left: 50%; right: auto; top: 0;
.l-toi-buttons {
padding: 1px;
@@ -233,7 +233,7 @@ table {
z-index: 3;
.l-toi {
transform: translateY(100%);
@include transform(translateY(100%));
}
@@ -257,13 +257,13 @@ table {
}
&:before {
transform: translate(-50%, $linesVOffset * -1);
@include transform(translate(-50%, $linesVOffset * -1));
top: 0px;
bottom: auto;
}
&:after {
transform: translate(-50%, $linesVOffset);
@include transform(translate(-50%, $linesVOffset));
top: auto;
bottom: 0px;
}

View File

@@ -92,7 +92,7 @@ define(
this.updateHistory(datum);
this.updateValues(datum);
}.bind(this));
this.requestLad(false);
this.requestHistory(this.openmct.time.bounds());
}.bind(this));
};
@@ -107,15 +107,36 @@ define(
if (this.requestCount > requestId) {
return Promise.resolve('Stale request');
}
values.forEach(function (datum) {
this.updateHistory(datum);
}, this);
this.updateValues(values[values.length - 1]);
this.requestLad(true);
}.bind(this));
};
/**
* Makes a request for the most recent datum in the
* telelmetry store. Optional addToHistory argument
* determines whether the requested telemetry should
* be added to history or only used to update the current
* image url and timestamp.
* @private
* @param {boolean} [addToHistory] if true, adds to history
*/
ImageryController.prototype.requestLad = function (addToHistory) {
this.openmct.telemetry
.request(this.domainObject, {
strategy: 'latest',
size: 1
})
.then(function (values) {
this.updateValues(values[0]);
if (addToHistory !== false) {
this.updateHistory(values[0]);
}
}.bind(this));
};
ImageryController.prototype.stopListening = function () {
this.openmct.time.off('bounds', this.onBoundsChange);
this.scrollable.off('scroll', this.onScroll);

View File

@@ -59,8 +59,7 @@ define(
'timeSystem',
'clock',
'on',
'off',
'bounds'
'off'
]),
telemetry: jasmine.createSpyObj('telemetryAPI', [
'subscribe',
@@ -119,8 +118,7 @@ define(
describe("when loaded", function () {
var callback,
boundsListener,
bounds;
boundsListener;
beforeEach(function () {
waitsFor(function () {
@@ -139,9 +137,13 @@ define(
});
});
it("requests history", function () {
it("uses LAD telemetry", function () {
expect(openmct.telemetry.request).toHaveBeenCalledWith(
newDomainObject, bounds
newDomainObject,
{
strategy: 'latest',
size: 1
}
);
expect(controller.getTime()).toEqual(prefix + 1434600258123);
expect(controller.getImageUrl()).toEqual('some/url');
@@ -202,7 +204,7 @@ define(
it("requests telemetry", function () {
expect(openmct.telemetry.request).toHaveBeenCalledWith(
newDomainObject,
bounds
jasmine.any(Object)
);
});

View File

@@ -315,8 +315,6 @@ define(
this.openmct.time.on("bounds", updateDisplayBounds);
this.openmct.selection.on('change', setSelection);
this.$element.on('click', this.bypassSelection.bind(this));
setSelection(this.openmct.selection.get());
}
/**

View File

@@ -201,7 +201,7 @@ define(
'off',
'get'
]);
mockSelection.get.andReturn([]);
mockSelection.get.andCallThrough();
mockOpenMCT = {
time: mockConductor,
@@ -596,7 +596,7 @@ define(
expect(controller.getSelectedElementStyle()).not.toEqual(oldStyle);
});
it("cleans up selection on scope destroy", function () {
it("cleans up slection on scope destroy", function () {
expect(mockScope.$on).toHaveBeenCalledWith(
'$destroy',
jasmine.any(Function)

View File

@@ -204,7 +204,10 @@ define([
"composition": [],
"start": {
"timestamp": 0
}
},
"activityStart": {},
"activityDuration": {},
"activityEnd": {}
}
},
{
@@ -341,7 +344,7 @@ define([
"cssClass": "icon-plot-resource",
"description": "Graph Resource Utilization",
"control": "button",
"method": "toggleGraph"
"method": "test"
},
{
"cssClass": "icon-activity-mode",
@@ -374,7 +377,36 @@ define([
"description": "Edit Properties...",
"control": "button",
"method": "properties"
},
{
"cssClass": "icon-duplicate",
"description": "Make copies of activities",
"control": "dialog-button",
"dialog": {
"control": "textfield",
"name": "Number of copies (1 to 5)",
"cssClass": "l-input-sm numeric"
},
"property": "makeCopies"
},
{
"cssClass": "icon-layers",
"description": "Fragment Activity",
"control": "dialog-button",
"dialog": {
"control": "textfield",
"name": "Number of fragments (2 to 5)",
"cssClass": "l-input-sm numeric"
},
"property": "fragment"
},
{
"cssClass": "icon-resync",
"description": "Update Duration from imported activity",
"control": "button",
"method": "updateDuration"
}
]
},
{
@@ -464,7 +496,8 @@ define([
"$scope",
"$q",
"objectLoader",
"TIMELINE_MINIMUM_DURATION"
"TIMELINE_MINIMUM_DURATION",
"openmct"
]
},
{

View File

@@ -53,7 +53,7 @@
width: $d;
position: absolute;
top: 0;
transform: translateX(-50%);
@include transform(translateX(-50%));
}
&:before {
// Icon blocker

View File

@@ -177,7 +177,7 @@
top: 20px; bottom: 5px;
.l-labels-holder {
@include absPosDefault();
justify-content: space-between;
@include justify-content(space-between);
left: $m;
.t-resource-graph-tick-label {
font-size: 0.9em;

View File

@@ -23,7 +23,7 @@
ng-click="$event.stopPropagation()"
ng-controller="TimelineController as timelineController">
<mct-split-pane anchor="left" class="abs" position="pane.x">
<mct-split-pane anchor="left" class="abs" position="pane.x" alias="timelineCenter">
<!-- LEFT PANE: TABULAR AND RESOURCE LEGEND AREAS -->
<mct-split-pane anchor="bottom"
position="pane.y"

View File

@@ -25,7 +25,7 @@
*/
define({
// Pixel width of start/end handles
HANDLE_WIDTH: 32,
HANDLE_WIDTH: 16,
// Pixel tolerance for snapping behavior
SNAP_WIDTH: 16
});

View File

@@ -21,27 +21,56 @@
*****************************************************************************/
define(
[],
function () {
['EventEmitter'],
function (EventEmitter) {
/**
* Describes the time span of an activity object.
* @param model the activity's object model
*/
function ActivityTimespan(model, mutation) {
function ActivityTimespan(model, mutation, parent) {
var parentTimelineModel = parent.getModel(),
parentMutation = parent.getCapability('mutation');
function getTimelineActivityStart(domainObjectModel) {
if (domainObjectModel.activityStart && domainObjectModel.activityStart[model.id]) {
return domainObjectModel.activityStart[model.id];
} else {
return model.start.timestamp;
}
}
function getTimelineActivityDuration(domainObjectModel) {
if (domainObjectModel.activityDuration && domainObjectModel.activityDuration[model.id]) {
return domainObjectModel.activityDuration[model.id];
} else {
return model.duration.timestamp;
}
}
function getTimelineActivityEnd(domainObjectModel) {
if (domainObjectModel.activityEnd && domainObjectModel.activityEnd[model.id]) {
return domainObjectModel.activityEnd[model.id];
} else {
return getTimelineActivityStart(parentTimelineModel) + getTimelineActivityDuration(parentTimelineModel);
}
}
// Get the start time for this timeline
function getStart() {
return model.start.timestamp;
return getTimelineActivityStart(parentTimelineModel);
}
// Get the end time for this timeline
function getEnd() {
return model.start.timestamp + model.duration.timestamp;
return getTimelineActivityEnd(parentTimelineModel);
}
// Get the duration of this timeline
function getDuration() {
return model.duration.timestamp;
function getDuration(flag) {
if (flag === 'model') {
return model.duration.timestamp;
}
return getTimelineActivityDuration(parentTimelineModel);
}
// Get the epoch used by this timeline
@@ -51,27 +80,36 @@ define(
// Set the start time associated with this object
function setStart(value) {
var end = getEnd();
mutation.mutate(function (m) {
m.start.timestamp = Math.max(value, 0);
// Update duration to keep end time
m.duration.timestamp = Math.max(end - value, 0);
}, model.modified);
parentMutation.mutate(function (m) {
if (!m.activityStart) {
m.activityStart = {};
}
m.activityStart[model.id] = Math.max(value, 0);
});
}
// Set the duration associated with this object
function setDuration(value) {
mutation.mutate(function (m) {
m.duration.timestamp = Math.max(value, 0);
}, model.modified);
parentMutation.mutate(function (m) {
if (!m.activityDuration) {
m.activityDuration = {};
}
m.activityDuration[model.id] = Math.max(value, 0);
});
}
// Set the end time associated with this object
function setEnd(value) {
var start = getStart();
mutation.mutate(function (m) {
m.duration.timestamp = Math.max(value - start, 0);
}, model.modified);
parentMutation.mutate(function (m) {
if (!m.activityEnd) {
m.activityEnd = {};
}
m.activityEnd[model.id] = Math.max(value, 0);
});
}
return {
@@ -110,7 +148,15 @@ define(
* start and end times.
* @returns {string} the epoch
*/
getEpoch: getEpoch
getEpoch: getEpoch,
getModel: function () {
return model;
},
getParent: function () {
return parent;
}
};
}

View File

@@ -32,11 +32,15 @@ define(
* @param {DomainObject} domainObject the Activity
*/
function ActivityTimespanCapability($q, domainObject) {
var parent = domainObject.getCapability('context').parentObject;
// Promise time span
function promiseTimeSpan() {
return $q.when(new ActivityTimespan(
domainObject.getModel(),
domainObject.getCapability('mutation')
domainObject.getCapability('mutation'),
parent
));
}

View File

@@ -36,11 +36,12 @@ define(
* Controller for the Timeline view.
* @constructor
*/
function TimelineController($scope, $q, objectLoader, MINIMUM_DURATION) {
function TimelineController($scope, $q, objectLoader, MINIMUM_DURATION, openmct) {
var swimlanePopulator = new TimelineSwimlanePopulator(
objectLoader,
$scope.configuration || {},
$scope.selection
$scope.selection,
openmct
),
graphPopulator = new TimelineGraphPopulator($q),
dragPopulator = new TimelineDragPopulator(objectLoader);

View File

@@ -51,7 +51,6 @@ define(
handles: function (domainObject) {
var type = domainObject.getCapability('type'),
id = domainObject.getId();
// Instantiate a handle
function instantiate(Handle) {
return new Handle(

View File

@@ -115,11 +115,13 @@ define(
var timespan = timespans[toId(id)];
// Use as setter if argument is present
if ((typeof value === 'number') && timespan) {
// Set the start (ensuring that it's non-negative,
// Set the start and duration(ensuring that it's non-negative,
// and not after the end time.)
timespan.setStart(
Math.min(Math.max(value, 0), timespan.getEnd())
);
timespan.setDuration(timespan.getEnd() - Math.min(Math.max(value, 0)));
// Mark as dirty for subsequent persistence
dirty[toId(id)] = true;
}
@@ -140,10 +142,12 @@ define(
var timespan = timespans[toId(id)];
// Use as setter if argument is present
if ((typeof value === 'number') && timespan) {
// Set the end (ensuring it doesn't precede start)
// Set the end and duration (ensuring it doesn't precede start)
timespan.setEnd(
Math.max(value, timespan.getStart())
);
timespan.setDuration(Math.max(value, timespan.getStart()) - timespan.getStart());
// Mark as dirty for subsequent persistence
dirty[toId(id)] = true;
}
@@ -198,9 +202,12 @@ define(
// own adjustments
start = timespan.getStart();
end = timespan.getEnd();
// Update start, then end
// Update duration, start, then end
timespan.setDuration(end - start);
timespan.setStart(start + delta);
timespan.setEnd(end + delta);
// Mark as dirty for subsequent persistence
dirty[toId(spanId)] = true;
}

View File

@@ -68,7 +68,6 @@ define(
chooseEnd = diffEnd > 0;
}
// Start is chosen if diffEnd didn't snap, or nothing snapped
// Our delta is relative to our initial state, but
// dragHandler.move is relative to current state, so whichever
// end we're snapping to, we need to compute a delta

View File

@@ -39,7 +39,7 @@ define(
* @param configuration the view's configuration object
* @param {TimelineSwimlane} parent the parent swim lane (if any)
*/
function TimelineSwimlane(domainObject, assigner, configuration, parent, index) {
function TimelineSwimlane(domainObject, assigner, configuration, parent, index, openmct) {
var id = domainObject.getId(),
highlight = false, // Drop highlight (middle)
highlightBottom = false, // Drop highlight (lower)
@@ -47,13 +47,93 @@ define(
depth = parent ? (parent.depth + 1) : 0,
timespan,
path = (!parent || !parent.parent) ? "" : parent.path +
parent.domainObject.getModel().name + " > ";
parent.domainObject.getModel().name + " > ",
instantiate = openmct.$injector.get("instantiate"),
copy = 1;
// Look up timespan for this object
domainObject.useCapability('timespan').then(function (t) {
timespan = t;
});
function makeCopies(input) {
if (input !== undefined) {
var number = Number(input);
if (!isNaN(number)) {
if (number >= 1 && number <= 5) {
var parentComposition = timespan.getParent().getCapability('composition'),
timespanModel = timespan.getModel();
for (var i = 1; i <= number; i++) {
var activityId = timespanModel.id + '-copy-' + copy,
activityModel = {
name: timespanModel.name + ' Copy ' + copy,
start: {timestamp: timespan.getStart(), epoch: "SET"},
duration: {timestamp: timespan.getDuration(), epoch: "SET"},
type: 'activity',
relationships: timespanModel.relationships,
id: activityId
},
activityInstance = instantiate(activityModel);
activityInstance.getCapability('location').setPrimaryLocation(timespan.getParent().model.id);
parentComposition.add(activityInstance);
copy++;
}
} else {
window.alert("Please enter a Number between 1 and 5");
}
} else {
window.alert("Please enter a Number");
}
}
}
function fragment(input) {
if (input !== undefined) {
var number = Number(input),
frag = 1;
if (!isNaN(number)) {
if (number >= 2 && number <= 5) {
var parentComposition = timespan.getParent().getCapability('composition'),
timespanModel = timespan.getModel(),
duration = (timespan.getEnd() - timespan.getStart()) / number;
timespan.setDuration(duration);
timespan.setEnd(timespan.getStart() + duration);
for (var i = 1; i < number; i++) {
var activityId = timespanModel.id + '-fragment-' + frag,
activityModel = {
name: timespanModel.name + ' Fragment ' + frag,
start: {timestamp: timespan.getStart(), epoch: "SET"},
duration: {timestamp: duration, epoch: "SET"},
type: 'activity',
relationships: timespanModel.relationships,
id: activityId
},
activityInstance = instantiate(activityModel);
activityInstance.getCapability('location').setPrimaryLocation(timespan.getParent().model.id);
parentComposition.add(activityInstance);
frag++;
}
} else {
window.alert("Please enter a Number between 2 and 5");
}
} else {
window.alert("Please enter a Number");
}
}
}
return {
/**
* Check if this swimlane is currently visible. (That is,
@@ -155,6 +235,15 @@ define(
timespan: function () {
return timespan;
},
updateDuration: function () {
var duration = timespan.getDuration('model'),
start = timespan.getStart();
timespan.setDuration(duration);
timespan.setEnd(start + duration);
},
makeCopies: makeCopies,
fragment: fragment,
// Expose domain object, expansion state, indentation depth
domainObject: domainObject,
expanded: true,

View File

@@ -39,7 +39,7 @@ define(
* timeline view.
* @constructor
*/
function TimelineSwimlanePopulator(objectLoader, configuration, selection) {
function TimelineSwimlanePopulator(objectLoader, configuration, selection, openmct) {
var swimlanes = [],
start = Number.POSITIVE_INFINITY,
end = Number.NEGATIVE_INFINITY,
@@ -72,7 +72,8 @@ define(
assigner,
configuration,
parent,
index || 0
index || 0,
openmct
), selection);
// Track start & end times of this domain object
domainObject.useCapability('timespan').then(trackStartEnd);

View File

@@ -57,8 +57,8 @@ define([
return vm;
}, {byValue: {}, byString: {}});
this.formatter.format = function (value) {
if (this.enumerations.byValue.hasOwnProperty(value)) {
return this.enumerations.byValue[value];
if (typeof value === "number") {
return this.enumerations.byValue[value] || value;
}
return value;
}.bind(this);

View File

@@ -0,0 +1,31 @@
define([
'./src/actions/activityModesImportAction',
'./src/policies/ActionPolicy'
],
function (ActivityModes, ActionPolicy) {
function plugin() {
return function install(openmct) {
openmct.legacyExtension('actions', {
key: "import-csv",
category: ["contextual"],
implementation: ActivityModes,
cssClass: "major icon-import",
name: "Import Activity Definitions from CSV",
description: "Import activities from a CSV file",
depends: [
"dialogService",
"openmct"
]
});
openmct.legacyExtension('policies', {
category: 'action',
implementation: ActionPolicy
});
};
}
return plugin;
});

View File

@@ -0,0 +1,172 @@
define(['d3-dsv'], function (d3Dsv) {
function ActivityModesImportAction(dialogService, openmct, context) {
this.dialogService = dialogService;
this.openmct = openmct;
this.context = context;
this.parent = this.context.domainObject;
this.instantiate = this.openmct.$injector.get("instantiate");
this.objectService = this.openmct.$injector.get("objectService").objectService;
this.populateActivities = this.populateActivities.bind(this);
}
ActivityModesImportAction.prototype.perform = function () {
this.parentId = this.parent.getId();
this.dialogService.getUserInput(this.getFormModel(), function () {})
.then(function (form) {
if (form.selectFile.name.slice(-3) !== 'csv') {
this.displayError();
}
this.csvParse(form.selectFile.body).then(this.populateActivities);
}.bind(this));
};
ActivityModesImportAction.prototype.csvParse = function (csvString) {
return new Promise(function (resolve, reject) {
var parsedObject = d3Dsv.csvParse(csvString);
return parsedObject ? resolve(parsedObject) : reject('Could not parse provided file');
});
};
ActivityModesImportAction.prototype.populateActivities = function (csvObjects) {
this.parentComposition = this.parent.getCapability("composition");
this.blockingDialog = this.showBlockingMessage();
var activitiesObjects = {},
activityModesObjects = {};
csvObjects.forEach(function (activity, index) {
var newActivity = {},
newActivityMode = {},
duration = !isNaN(Number(activity.duration)) ? 1000 * Number(activity.duration) : 0;
newActivity.name = activity.name;
newActivity.id = activity.id ? ('activity-' + activity.id) : ('activity-' + index + '-' + this.parentId);
newActivity.start = {timestamp: 0, epoch: "SET"};
newActivity.duration = {timestamp: duration, epoch: "SET"};
newActivity.type = "activity";
newActivity.composition = [];
newActivity.relationships = {modes: []};
newActivityMode.name = activity.name + ' Resources';
newActivityMode.id = activity.id ? ('activity-mode-' + activity.id) : ('activity-mode-' + index + '-' + this.parentId);
newActivityMode.resources = {comms: Number(activity.comms) || 0, power: Number(activity.power) || 0};
newActivityMode.type = 'mode';
newActivity.relationships.modes.push(newActivityMode.id);
activitiesObjects[newActivity.id] = newActivity;
activityModesObjects[newActivityMode.id] = newActivityMode;
}.bind(this));
this.instantiateActivityModes(activityModesObjects);
this.instantiateActivities(activitiesObjects);
};
ActivityModesImportAction.prototype.instantiateActivityModes = function (activityModesObjects) {
var activityModesArray = Object.keys(activityModesObjects);
this.objectService.getObjects(activityModesArray).then(
function (previousActivityModes) {
activityModesArray.forEach(function (activityModeId) {
previousActivityModes[activityModeId].getCapability('mutation').mutate(function (prev) {
var activityMode = activityModesObjects[activityModeId];
prev.name = activityMode.name;
prev.resources = activityMode.resources;
prev.type = activityMode.type;
prev.id = activityMode.id;
});
});
}
);
};
ActivityModesImportAction.prototype.instantiateActivities = function (activitiesObjects) {
var activityObjectArray = Object.keys(activitiesObjects);
this.objectService.getObjects(activityObjectArray).then(
function (objects) {
activityObjectArray.forEach(function (activityId, index) {
var activity = activitiesObjects[activityId];
objects[activityId].getCapability('mutation').mutate(function (prevActivity) {
prevActivity.name = activity.name;
prevActivity.start = activity.start;
prevActivity.duration = activity.duration;
prevActivity.type = activity.type;
prevActivity.composition = activity.composition;
prevActivity.relationships = activity.relationships;
prevActivity.id = activity.id;
});
objects[activityId].getCapability('location').setPrimaryLocation(this.parentId);
if ((index === (activityObjectArray.length - 1)) && this.blockingDialog) {
this.blockingDialog.dismiss();
}
}.bind(this));
this.parentComposition.domainObject.getCapability('mutation').mutate(function (parentComposition) {
parentComposition.composition = activityObjectArray;
});
}.bind(this)
);
};
ActivityModesImportAction.prototype.showBlockingMessage = function () {
var model = {
title: "Importing",
actionText: "Importing Activities from CSV",
severity: "info",
unknownProgress: true
};
return this.dialogService.showBlockingMessage(model);
};
ActivityModesImportAction.prototype.displayError = function () {
var dialog,
perform = this.perform.bind(this),
model = {
title: "Invalid File",
actionText: "The selected file was not a valid CSV file",
severity: "error",
options: [
{
label: "Ok",
callback: function () {
dialog.dismiss();
perform();
}
}
]
};
dialog = this.dialogService.showBlockingMessage(model);
};
ActivityModesImportAction.prototype.getFormModel = function () {
return {
name: 'Import activities from CSV',
sections: [
{
name: 'Import A File',
rows: [
{
name: 'Select File',
key: 'selectFile',
control: 'file-input',
required: true,
text: 'Select File'
}
]
}
]
};
};
return ActivityModesImportAction;
});

View File

@@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, United States Government
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@@ -19,44 +19,28 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
[],
function () {
function RemoveActionPolicy() {
function isAnyViewEditable(views) {
var isEditable = false;
views.forEach(function (view) {
if (view.editable) {
isEditable = true;
return;
}
});
return isEditable;
}
return {
/**
* Disallow the Remove action on objects that are not editable
*/
allow: function (action, context) {
var domainObject = (context || {}).domainObject,
views = domainObject.useCapability('view'),
metadata = action.getMetadata() || {};
if (metadata.key === 'remove') {
return isAnyViewEditable(views);
}
return true;
}
};
function ActionPolicy() {
}
return RemoveActionPolicy;
ActionPolicy.prototype.allow = function (action, context) {
var key = action.getMetadata().key,
domainObjectType =
context.domainObject ? context.domainObject.getModel().type : '';
if (key === 'import-csv') {
if (domainObjectType === 'folder') {
return true;
}
return false;
}
return true;
};
return ActionPolicy;
}
);

View File

@@ -61,7 +61,7 @@ function (
this.alarmSets = [];
this.offset = {};
this.config = $scope.config;
this.listenTo(this.$scope, '$destroy', this.destroy, this);
this.listenTo(this.$scope, '$destoy', this.destroy, this);
this.draw = this.draw.bind(this);
this.scheduleDraw = this.scheduleDraw.bind(this);
this.seriesElements = new WeakMap();
@@ -197,29 +197,9 @@ function (
this.canvas = canvas;
this.overlay = overlay;
this.drawAPI = DrawLoader.getDrawAPI(canvas, overlay);
if (this.drawAPI) {
this.listenTo(this.drawAPI, 'error', this.fallbackToCanvas, this);
}
return !!this.drawAPI;
};
MCTChartController.prototype.fallbackToCanvas = function () {
this.stopListening(this.drawAPI);
DrawLoader.releaseDrawAPI(this.drawAPI);
// Have to throw away the old canvas elements and replace with new
// canvas elements in order to get new drawing contexts.
var div = document.createElement('div');
div.innerHTML = this.TEMPLATE;
var mainCanvas = div.querySelectorAll("canvas")[1];
var overlayCanvas = div.querySelectorAll("canvas")[0];
this.canvas.parentNode.replaceChild(mainCanvas, this.canvas);
this.canvas = mainCanvas;
this.overlay.parentNode.replaceChild(overlayCanvas, this.overlay);
this.overlay = overlayCanvas;
this.drawAPI = DrawLoader.getFallbackDrawAPI(this.canvas, this.overlay);
this.$scope.$emit('plot:reinitializeCanvas');
};
MCTChartController.prototype.removeChartElement = function (series) {
var elements = this.seriesElements.get(series);
@@ -231,10 +211,6 @@ function (
this.pointSets.splice(this.pointSets.indexOf(pointSet), 1);
pointSet.destroy();
}, this);
if (elements.alarmSet) {
elements.alarmSet.destroy();
this.alarmSets.splice(this.alarmSets.indexOf(elements.alarmSet), 1);
}
this.seriesElements.delete(series);
};

View File

@@ -43,7 +43,6 @@ define([
restrict: "E",
template: TEMPLATE,
link: function ($scope, $element, attrs, ctrl) {
ctrl.TEMPLATE = TEMPLATE;
var mainCanvas = $element.find("canvas")[1];
var overlayCanvas = $element.find("canvas")[0];

View File

@@ -75,14 +75,11 @@ define([
openmct: options.openmct
});
if (this.get('domainObject').type === 'telemetry.plot.overlay') {
this.removeMutationListener = this.openmct.objects.observe(
this.get('domainObject'),
'*',
this.updateDomainObject.bind(this)
);
}
this.removeMutationListener = this.openmct.objects.observe(
this.get('domainObject'),
'*',
this.updateDomainObject.bind(this)
);
this.yAxis.listenToSeriesCollection(this.series);
this.legend.listenToSeriesCollection(this.series);
@@ -115,9 +112,7 @@ define([
this.yAxis.destroy();
this.series.destroy();
this.legend.destroy();
if (this.removeMutationListener) {
this.removeMutationListener();
}
this.removeMutationListener();
},
/**
* Return defaults, which are extracted from the passed in domain

View File

@@ -22,13 +22,9 @@
define([
'lodash',
'EventEmitter',
'../lib/eventHelpers'
], function (
_,
EventEmitter,
eventHelpers
) {
/**
@@ -51,9 +47,6 @@ define([
}
}
_.extend(Draw2D.prototype, EventEmitter.prototype);
eventHelpers.extend(Draw2D.prototype);
// Convert from logical to physical x coordinates
Draw2D.prototype.x = function (v) {
return ((v - this.origin[0]) / this.dimensions[0]) * this.width;

View File

@@ -35,7 +35,7 @@ define(
ALLOCATIONS: []
},
{
MAX_INSTANCES: Number.POSITIVE_INFINITY,
MAX_INSTANCES: Number.MAX_INFINITY,
API: Draw2D,
ALLOCATIONS: []
}
@@ -83,24 +83,12 @@ define(
return api;
},
/**
* Returns a fallback draw api.
*/
getFallbackDrawAPI: function (canvas, overlay) {
var api = new CHARTS[1].API(canvas, overlay);
CHARTS[1].ALLOCATIONS.push(api);
return api;
},
releaseDrawAPI: function (api) {
CHARTS.forEach(function (CHART_TYPE) {
if (api instanceof CHART_TYPE.API) {
CHART_TYPE.ALLOCATIONS.splice(CHART_TYPE.ALLOCATIONS.indexOf(api), 1);
}
});
if (api.destroy) {
api.destroy();
}
}
};
}

View File

@@ -22,13 +22,9 @@
define([
'lodash',
'EventEmitter',
'../lib/eventHelpers'
], function (
_,
EventEmitter,
eventHelpers
) {
// WebGL shader sources (for drawing plain colors)
@@ -73,21 +69,6 @@ define([
throw new Error("WebGL unavailable.");
}
this.initContext();
this.listenTo(this.canvas, "webglcontextlost", this.onContextLost, this);
}
_.extend(DrawWebGL.prototype, EventEmitter.prototype);
eventHelpers.extend(DrawWebGL.prototype);
DrawWebGL.prototype.onContextLost = function (event) {
this.emit('error');
this.isContextLost = true;
this.destroy();
};
DrawWebGL.prototype.initContext = function () {
// Initialize shaders
this.vertexShader = this.gl.createShader(this.gl.VERTEX_SHADER);
this.gl.shaderSource(this.vertexShader, VERTEX_SHADER);
@@ -122,12 +103,7 @@ define([
// Enable blending, for smoothness
this.gl.enable(this.gl.BLEND);
this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA);
};
DrawWebGL.prototype.destroy = function () {
this.stopListening();
};
}
// Convert from logical to physical x coordinates
DrawWebGL.prototype.x = function (v) {
@@ -141,9 +117,6 @@ define([
};
DrawWebGL.prototype.doDraw = function (drawType, buf, color, points) {
if (this.isContextLost) {
return;
}
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer);
this.gl.bufferData(this.gl.ARRAY_BUFFER, buf, this.gl.DYNAMIC_DRAW);
this.gl.vertexAttribPointer(this.aVertexPosition, 2, this.gl.FLOAT, false, 0, 0);
@@ -152,9 +125,6 @@ define([
};
DrawWebGL.prototype.clear = function () {
if (this.isContextLost) {
return;
}
this.height = this.canvas.height = this.canvas.offsetHeight;
this.width = this.canvas.width = this.canvas.offsetWidth;
this.overlay.height = this.overlay.offsetHeight;
@@ -181,9 +151,6 @@ define([
DrawWebGL.prototype.setDimensions = function (dimensions, origin) {
this.dimensions = dimensions;
this.origin = origin;
if (this.isContextLost) {
return;
}
if (dimensions && dimensions.length > 0 &&
origin && origin.length > 0) {
this.gl.uniform2fv(this.uDimensions, dimensions);
@@ -202,9 +169,6 @@ define([
* @param {number} points the number of points to draw
*/
DrawWebGL.prototype.drawLine = function (buf, color, points) {
if (this.isContextLost) {
return;
}
this.doDraw(this.gl.LINE_STRIP, buf, color, points);
};
@@ -213,9 +177,6 @@ define([
*
*/
DrawWebGL.prototype.drawPoints = function (buf, color, points, pointSize) {
if (this.isContextLost) {
return;
}
this.gl.uniform1f(this.uPointSize, pointSize);
this.doDraw(this.gl.POINTS, buf, color, points);
};
@@ -230,9 +191,6 @@ define([
* is in the range of 0.0-1.0
*/
DrawWebGL.prototype.drawSquare = function (min, max, color) {
if (this.isContextLost) {
return;
}
this.doDraw(this.gl.TRIANGLE_FAN, new Float32Array(
min.concat([min[0], max[1]]).concat(max).concat([max[0], min[1]])
), color, 4);

View File

@@ -59,19 +59,6 @@ define([
eventHelpers.extend(MCTPlotController.prototype);
MCTPlotController.prototype.initCanvas = function () {
if (this.$canvas) {
this.stopListening(this.$canvas);
}
this.$canvas = this.$element.find('canvas');
this.listenTo(this.$canvas, 'mousemove', this.trackMousePosition, this);
this.listenTo(this.$canvas, 'mouseleave', this.untrackMousePosition, this);
this.listenTo(this.$canvas, 'mousedown', this.onMouseDown, this);
this.watchForMarquee();
};
MCTPlotController.prototype.initialize = function () {
this.$canvas = this.$element.find('canvas');
@@ -95,7 +82,6 @@ define([
this.listenTo(this.$scope, '$destroy', this.destroy, this);
this.listenTo(this.$scope, 'plot:tickWidth', this.onTickWidthChange, this);
this.listenTo(this.$scope, 'plot:highlight:set', this.onPlotHighlightSet, this);
this.listenTo(this.$scope, 'plot:reinitializeCanvas', this.initCanvas, this);
this.listenTo(this.config.xAxis, 'change:displayRange', this.onXAxisChange, this);
this.listenTo(this.config.yAxis, 'change:displayRange', this.onYAxisChange, this);
@@ -220,7 +206,6 @@ define([
return;
}
this.marquee.end = this.positionOverPlot;
this.marquee.endPixels = this.positionOverElement;
};
MCTPlotController.prototype.startMarquee = function ($event) {
@@ -228,8 +213,6 @@ define([
if (this.positionOverPlot) {
this.freeze();
this.marquee = {
startPixels: this.positionOverElement,
endPixels: this.positionOverElement,
start: this.positionOverPlot,
end: this.positionOverPlot,
color: [1, 1, 1, 0.5]
@@ -240,14 +223,8 @@ define([
};
MCTPlotController.prototype.endMarquee = function () {
var startPixels = this.marquee.startPixels;
var endPixels = this.marquee.endPixels;
var marqueeDistance = Math.sqrt(
Math.pow(startPixels.x - endPixels.x, 2) +
Math.pow(startPixels.y - endPixels.y, 2)
);
// Don't zoom if mouse moved less than 7.5 pixels.
if (marqueeDistance > 7.5) {
if (this.marquee.start.x !== this.marquee.end.x &&
this.marquee.start.y !== this.marquee.end.y) {
this.$scope.xAxis.set('displayRange', {
min: Math.min(this.marquee.start.x, this.marquee.end.x),
max: Math.max(this.marquee.start.x, this.marquee.end.x)
@@ -257,10 +234,6 @@ define([
max: Math.max(this.marquee.start.y, this.marquee.end.y)
});
this.$scope.$emit('user:viewport:change:end');
} else {
// A history entry is created by startMarquee, need to remove
// if marquee zoom doesn't occur.
this.back();
}
this.$scope.rectangles = [];
this.marquee = undefined;

View File

@@ -30,6 +30,7 @@ define([
'../../platform/import-export/bundle',
'./summaryWidget/plugin',
'./URLIndicatorPlugin/URLIndicatorPlugin',
'./activityModes/plugin',
'./telemetryMean/plugin',
'./plot/plugin',
'./staticRootPlugin/plugin'
@@ -43,6 +44,7 @@ define([
ImportExport,
SummaryWidget,
URLIndicatorPlugin,
ActivityModes,
TelemetryMean,
PlotPlugin,
StaticRootPlugin
@@ -137,6 +139,7 @@ define([
plugins.SummaryWidget = SummaryWidget;
plugins.TelemetryMean = TelemetryMean;
plugins.URLIndicatorPlugin = URLIndicatorPlugin;
plugins.ActivityModes = ActivityModes;
return plugins;
});

View File

@@ -1,14 +1,4 @@
define([
'./SummaryWidgetsCompositionPolicy',
'./src/telemetry/SummaryWidgetMetadataProvider',
'./src/telemetry/SummaryWidgetTelemetryProvider',
'./src/views/SummaryWidgetViewProvider'
], function (
SummaryWidgetsCompositionPolicy,
SummaryWidgetMetadataProvider,
SummaryWidgetTelemetryProvider,
SummaryWidgetViewProvider
) {
define(['./src/SummaryWidget', './SummaryWidgetsCompositionPolicy'], function (SummaryWidget, SummaryWidgetsCompositionPolicy) {
function plugin() {
@@ -19,40 +9,8 @@ define([
cssClass: 'icon-summary-widget',
initialize: function (domainObject) {
domainObject.composition = [];
domainObject.configuration = {
ruleOrder: ['default'],
ruleConfigById: {
default: {
name: 'Default',
label: 'Unnamed Rule',
message: '',
id: 'default',
icon: ' ',
style: {
'color': '#ffffff',
'background-color': '#38761d',
'border-color': 'rgba(0,0,0,0)'
},
description: 'Default appearance for the widget',
conditions: [{
object: '',
key: '',
operation: '',
values: []
}],
jsCondition: '',
trigger: 'any',
expanded: 'true'
}
},
testDataConfig: [{
object: '',
key: '',
value: ''
}]
};
domainObject.configuration = {};
domainObject.openNewTab = 'thisTab';
domainObject.telemetry = {};
},
form: [
{
@@ -82,14 +40,26 @@ define([
]
};
function initViewProvider(openmct) {
return {
name: 'Widget View',
view: function (domainObject) {
return new SummaryWidget(domainObject, openmct);
},
canView: function (domainObject) {
return (domainObject.type === 'summary-widget');
},
editable: true,
key: 'summaryWidgets'
};
}
return function install(openmct) {
openmct.types.addType('summary-widget', widgetType);
openmct.objectViews.addProvider(initViewProvider(openmct));
openmct.legacyExtension('policies', {category: 'composition',
implementation: SummaryWidgetsCompositionPolicy, depends: ['openmct']
});
openmct.telemetry.addProvider(new SummaryWidgetMetadataProvider(openmct));
openmct.telemetry.addProvider(new SummaryWidgetTelemetryProvider(openmct));
openmct.objectViews.addProvider(new SummaryWidgetViewProvider(openmct));
};
}

View File

@@ -1,12 +1,10 @@
define ([
'./ConditionEvaluator',
'../../../api/objects/object-utils',
'EventEmitter',
'zepto',
'lodash'
], function (
ConditionEvaluator,
objectUtils,
EventEmitter,
$,
_
@@ -125,23 +123,21 @@ define ([
* has completed and types have been parsed
*/
ConditionManager.prototype.parsePropertyTypes = function (object) {
var objectId = objectUtils.makeKeyString(object.identifier);
var telemetryAPI = this.openmct.telemetry,
key,
type,
self = this;
this.telemetryTypesById[objectId] = {};
Object.values(this.telemetryMetadataById[objectId]).forEach(function (valueMetadata) {
var type;
if (valueMetadata.hints.hasOwnProperty('range')) {
type = 'number';
} else if (valueMetadata.hints.hasOwnProperty('domain')) {
type = 'number';
} else if (valueMetadata.key === 'name') {
type = 'string';
} else {
type = 'string';
}
this.telemetryTypesById[objectId][valueMetadata.key] = type;
this.addGlobalPropertyType(valueMetadata.key, type);
}, this);
self.telemetryTypesById[object.identifier.key] = {};
return telemetryAPI.request(object, {size: 1, strategy: 'latest'}).then(function (telemetry) {
Object.entries(telemetry[telemetry.length - 1]).forEach(function (telem) {
key = telem[0];
type = typeof telem[1];
self.telemetryTypesById[object.identifier.key][key] = type;
self.subscriptionCache[object.identifier.key][key] = telem[1];
self.addGlobalPropertyType(key, type);
});
});
};
/**
@@ -151,9 +147,23 @@ define ([
* and property types parsed
*/
ConditionManager.prototype.parseAllPropertyTypes = function () {
Object.values(this.compositionObjs).forEach(this.parsePropertyTypes, this);
this.metadataLoadComplete = true;
this.eventEmitter.emit('metadata');
var self = this,
index = 0,
objs = Object.values(self.compositionObjs),
promise = new Promise(function (resolve, reject) {
if (objs.length === 0) {
resolve();
}
objs.forEach(function (obj) {
self.parsePropertyTypes(obj).then(function () {
if (index === objs.length - 1) {
resolve();
}
index += 1;
});
});
});
return promise;
};
/**
@@ -177,7 +187,7 @@ define ([
ConditionManager.prototype.onCompositionAdd = function (obj) {
var compositionKeys,
telemetryAPI = this.openmct.telemetry,
objId = objectUtils.makeKeyString(obj.identifier),
objId = obj.identifier.key,
telemetryMetadata,
self = this;
@@ -185,9 +195,10 @@ define ([
self.compositionObjs[objId] = obj;
self.telemetryMetadataById[objId] = {};
// FIXME: this should just update based on listener.
compositionKeys = self.domainObject.composition.map(objectUtils.makeKeyString);
if (!compositionKeys.includes(objId)) {
compositionKeys = self.domainObject.composition.map(function (object) {
return object.key;
});
if (!compositionKeys.includes(obj.identifier.key)) {
self.domainObject.composition.push(obj.identifier);
}
@@ -201,12 +212,6 @@ define ([
self.subscriptions[objId] = telemetryAPI.subscribe(obj, function (datum) {
self.handleSubscriptionCallback(objId, datum);
}, {});
telemetryAPI.request(obj, {strategy: 'latest', size: 1})
.then(function (results) {
if (results && results.length) {
self.handleSubscriptionCallback(objId, results[results.length - 1]);
}
});
/**
* if this is the initial load, parsing property types will be postponed
@@ -229,14 +234,11 @@ define ([
* @private
*/
ConditionManager.prototype.onCompositionRemove = function (identifier) {
var objectId = objectUtils.makeKeyString(identifier);
// FIXME: this should just update by listener.
_.remove(this.domainObject.composition, function (id) {
return id.key === identifier.key &&
id.namespace === identifier.namespace;
return id.key === identifier.key;
});
delete this.compositionObjs[objectId];
this.subscriptions[objectId](); //unsubscribe from telemetry source
delete this.compositionObjs[identifier.key];
this.subscriptions[identifier.key](); //unsubscribe from telemetry source
this.eventEmitter.emit('remove', identifier);
if (_.isEmpty(this.compositionObjs)) {
@@ -251,9 +253,13 @@ define ([
* @private
*/
ConditionManager.prototype.onCompositionLoad = function () {
this.loadComplete = true;
this.eventEmitter.emit('load');
this.parseAllPropertyTypes();
var self = this;
self.loadComplete = true;
self.eventEmitter.emit('load');
self.parseAllPropertyTypes().then(function () {
self.metadataLoadComplete = true;
self.eventEmitter.emit('metadata');
});
};
/**

View File

@@ -126,13 +126,6 @@ define([
}
};
/**
* Implement "off" to complete event emitter interface.
*/
TestDataItem.prototype.off = function (event, callback, context) {
this.eventEmitter.off(event, callback, context);
};
/**
* Hide the appropriate inputs when this is the only item
*/

View File

@@ -131,20 +131,15 @@ define([
*/
TestDataManager.prototype.refreshItems = function () {
var self = this;
if (this.items) {
this.items.forEach(function (item) {
this.stopListening(item);
}, this);
}
self.items = [];
$('.t-test-data-item', this.domElement).remove();
this.config.forEach(function (item, index) {
var newItem = new TestDataItem(item, index, self.manager);
self.listenTo(newItem, 'remove', self.removeItem, self);
self.listenTo(newItem, 'duplicate', self.initItem, self);
self.listenTo(newItem, 'change', self.onItemChange, self);
newItem.on('remove', self.removeItem, self);
newItem.on('duplicate', self.initItem, self);
newItem.on('change', self.onItemChange, self);
self.items.push(newItem);
});
@@ -195,10 +190,10 @@ define([
};
TestDataManager.prototype.destroy = function () {
this.stopListening();
this.items.forEach(function (item) {
item.remove();
});
this.stopListening();
};
return TestDataManager;

View File

@@ -1,10 +1,4 @@
define([
'./Select',
'../../../../api/objects/object-utils'
], function (
Select,
objectUtils
) {
define(['./Select'], function (Select) {
/**
* Create a {Select} element whose composition is dynamically updated with
@@ -43,7 +37,7 @@ define([
* @private
*/
function onCompositionAdd(obj) {
self.select.addOption(objectUtils.makeKeyString(obj.identifier), obj.name);
self.select.addOption(obj.identifier.key, obj.name);
}
/**
@@ -81,7 +75,7 @@ define([
*/
ObjectSelect.prototype.generateOptions = function () {
var items = Object.values(this.compositionObjs).map(function (obj) {
return [objectUtils.makeKeyString(obj.identifier), obj.name];
return [obj.identifier.key, obj.name];
});
this.baseOptions.forEach(function (option, index) {
items.splice(index, 0, option);

View File

@@ -1,64 +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.
*****************************************************************************/
define([
'./SummaryWidgetEvaluator',
'../../../../api/objects/object-utils'
], function (
SummaryWidgetEvaluator,
objectUtils
) {
function EvaluatorPool(openmct) {
this.openmct = openmct;
this.byObjectId = {};
this.byEvaluator = new WeakMap();
}
EvaluatorPool.prototype.get = function (domainObject) {
var objectId = objectUtils.makeKeyString(domainObject.identifier);
var poolEntry = this.byObjectId[objectId];
if (!poolEntry) {
poolEntry = {
leases: 0,
objectId: objectId,
evaluator: new SummaryWidgetEvaluator(domainObject, this.openmct)
};
this.byEvaluator.set(poolEntry.evaluator, poolEntry);
this.byObjectId[objectId] = poolEntry;
}
poolEntry.leases += 1;
return poolEntry.evaluator;
};
EvaluatorPool.prototype.release = function (evaluator) {
var poolEntry = this.byEvaluator.get(evaluator);
poolEntry.leases -= 1;
if (poolEntry.leases === 0) {
evaluator.destroy();
this.byEvaluator.delete(evaluator);
delete this.byObjectId[poolEntry.objectId];
}
};
return EvaluatorPool;
});

View File

@@ -1,102 +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.
*****************************************************************************/
define([
'./EvaluatorPool',
'./SummaryWidgetEvaluator'
], function (
EvaluatorPool,
SummaryWidgetEvaluator
) {
describe('EvaluatorPool', function () {
var pool;
var openmct;
var objectA;
var objectB;
beforeEach(function () {
openmct = {
composition: jasmine.createSpyObj('compositionAPI', ['get']),
objects: jasmine.createSpyObj('objectAPI', ['observe'])
};
openmct.composition.get.andCallFake(function () {
var compositionCollection = jasmine.createSpyObj(
'compositionCollection',
[
'load',
'on',
'off'
]
);
compositionCollection.load.andReturn(Promise.resolve());
return compositionCollection;
});
openmct.objects.observe.andCallFake(function () {
return function () {};
});
pool = new EvaluatorPool(openmct);
objectA = {
identifier: {
namespace: 'someNamespace',
key: 'someKey'
},
configuration: {
ruleOrder: []
}
};
objectB = {
identifier: {
namespace: 'otherNamespace',
key: 'otherKey'
},
configuration: {
ruleOrder: []
}
};
});
it('returns new evaluators for different objects', function () {
var evaluatorA = pool.get(objectA);
var evaluatorB = pool.get(objectB);
expect(evaluatorA).not.toBe(evaluatorB);
});
it('returns the same evaluator for the same object', function () {
var evaluatorA = pool.get(objectA);
var evaluatorB = pool.get(objectA);
expect(evaluatorA).toBe(evaluatorB);
var evaluatorC = pool.get(JSON.parse(JSON.stringify(objectA)));
expect(evaluatorA).toBe(evaluatorC);
});
it('returns new evaluator when old is released', function () {
var evaluatorA = pool.get(objectA);
var evaluatorB = pool.get(objectA);
expect(evaluatorA).toBe(evaluatorB);
pool.release(evaluatorA);
pool.release(evaluatorB);
var evaluatorC = pool.get(objectA);
expect(evaluatorA).not.toBe(evaluatorC);
});
});
});

View File

@@ -1,80 +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.
*****************************************************************************/
define([
'./operations'
], function (
OPERATIONS
) {
function SummaryWidgetCondition(definition) {
this.object = definition.object;
this.key = definition.key;
this.values = definition.values;
if (!definition.operation) {
// TODO: better handling for default rule.
this.evaluate = function () {
return true;
};
} else {
this.comparator = OPERATIONS[definition.operation].operation;
}
}
SummaryWidgetCondition.prototype.evaluate = function (telemetryState) {
var stateKeys = Object.keys(telemetryState);
var state;
var result;
var i;
if (this.object === 'any') {
for (i = 0; i < stateKeys.length; i++) {
state = telemetryState[stateKeys[i]];
result = this.evaluateState(state);
if (result) {
return true;
}
}
return false;
} else if (this.object === 'all') {
for (i = 0; i < stateKeys.length; i++) {
state = telemetryState[stateKeys[i]];
result = this.evaluateState(state);
if (!result) {
return false;
}
}
return true;
} else {
return this.evaluateState(telemetryState[this.object]);
}
};
SummaryWidgetCondition.prototype.evaluateState = function (state) {
var testValues = [
state.formats[this.key].parse(state.lastDatum)
].concat(this.values);
return this.comparator(testValues);
};
return SummaryWidgetCondition;
});

View File

@@ -1,142 +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.
*****************************************************************************/
define([
'./SummaryWidgetCondition'
], function (
SummaryWidgetCondition
) {
describe('SummaryWidgetCondition', function () {
var condition;
var telemetryState;
beforeEach(function () {
// Format map intentionally uses different keys than those present
// in datum, which serves to verify conditions use format map to get
// data.
var formatMap = {
adjusted: {
parse: function (datum) {
return datum.value + 10;
}
},
raw: {
parse: function (datum) {
return datum.value;
}
}
};
telemetryState = {
objectId: {
formats: formatMap,
lastDatum: {
}
},
otherObjectId: {
formats: formatMap,
lastDatum: {
}
}
};
});
it('can evaluate if a single object matches', function () {
condition = new SummaryWidgetCondition({
object: 'objectId',
key: 'raw',
operation: 'greaterThan',
values: [
10
]
});
telemetryState.objectId.lastDatum.value = 5;
expect(condition.evaluate(telemetryState)).toBe(false);
telemetryState.objectId.lastDatum.value = 15;
expect(condition.evaluate(telemetryState)).toBe(true);
});
it('can evaluate if a single object matches (alternate keys)', function () {
condition = new SummaryWidgetCondition({
object: 'objectId',
key: 'adjusted',
operation: 'greaterThan',
values: [
10
]
});
telemetryState.objectId.lastDatum.value = -5;
expect(condition.evaluate(telemetryState)).toBe(false);
telemetryState.objectId.lastDatum.value = 5;
expect(condition.evaluate(telemetryState)).toBe(true);
});
it('can evaluate "if all objects match"', function () {
condition = new SummaryWidgetCondition({
object: 'all',
key: 'raw',
operation: 'greaterThan',
values: [
10
]
});
telemetryState.objectId.lastDatum.value = 0;
telemetryState.otherObjectId.lastDatum.value = 0;
expect(condition.evaluate(telemetryState)).toBe(false);
telemetryState.objectId.lastDatum.value = 0;
telemetryState.otherObjectId.lastDatum.value = 15;
expect(condition.evaluate(telemetryState)).toBe(false);
telemetryState.objectId.lastDatum.value = 15;
telemetryState.otherObjectId.lastDatum.value = 0;
expect(condition.evaluate(telemetryState)).toBe(false);
telemetryState.objectId.lastDatum.value = 15;
telemetryState.otherObjectId.lastDatum.value = 15;
expect(condition.evaluate(telemetryState)).toBe(true);
});
it('can evalute "if any object matches"', function () {
condition = new SummaryWidgetCondition({
object: 'any',
key: 'raw',
operation: 'greaterThan',
values: [
10
]
});
telemetryState.objectId.lastDatum.value = 0;
telemetryState.otherObjectId.lastDatum.value = 0;
expect(condition.evaluate(telemetryState)).toBe(false);
telemetryState.objectId.lastDatum.value = 0;
telemetryState.otherObjectId.lastDatum.value = 15;
expect(condition.evaluate(telemetryState)).toBe(true);
telemetryState.objectId.lastDatum.value = 15;
telemetryState.otherObjectId.lastDatum.value = 0;
expect(condition.evaluate(telemetryState)).toBe(true);
telemetryState.objectId.lastDatum.value = 15;
telemetryState.otherObjectId.lastDatum.value = 15;
expect(condition.evaluate(telemetryState)).toBe(true);
});
});
});

View File

@@ -1,281 +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.
*****************************************************************************/
define([
'./SummaryWidgetRule',
'../eventHelpers',
'../../../../api/objects/object-utils',
'lodash'
], function (
SummaryWidgetRule,
eventHelpers,
objectUtils,
_
) {
/**
* evaluates rules defined in a summary widget against either lad or
* realtime state.
*
*/
function SummaryWidgetEvaluator(domainObject, openmct) {
this.openmct = openmct;
this.baseState = {};
this.updateRules(domainObject);
this.removeObserver = openmct.objects.observe(
domainObject,
'*',
this.updateRules.bind(this)
);
var composition = openmct.composition.get(domainObject);
this.listenTo(composition, 'add', this.addChild, this);
this.listenTo(composition, 'remove', this.removeChild, this);
this.loadPromise = composition.load();
}
eventHelpers.extend(SummaryWidgetEvaluator.prototype);
/**
* Subscribes to realtime telemetry for the given summary widget.
*/
SummaryWidgetEvaluator.prototype.subscribe = function (callback) {
var active = true;
var unsubscribes = [];
this.getBaseStateClone()
.then(function (realtimeStates) {
if (!active) {
return;
}
var updateCallback = function () {
var datum = this.evaluateState(
realtimeStates,
this.openmct.time.timeSystem().key
);
if (datum) {
callback(datum);
}
}.bind(this);
unsubscribes = _.map(
realtimeStates,
this.subscribeToObjectState.bind(this, updateCallback)
);
}.bind(this));
return function () {
active = false;
unsubscribes.forEach(function (unsubscribe) {
unsubscribe();
});
};
};
/**
* Returns a promise for a telemetry datum obtained by evaluating the
* current lad data.
*/
SummaryWidgetEvaluator.prototype.requestLatest = function (options) {
return this.getBaseStateClone()
.then(function (ladState) {
var promises = Object.values(ladState)
.map(this.updateObjectStateFromLAD.bind(this, options));
return Promise.all(promises)
.then(function () {
return ladState;
});
}.bind(this))
.then(function (ladStates) {
return this.evaluateState(ladStates, options.domain);
}.bind(this));
};
SummaryWidgetEvaluator.prototype.updateRules = function (domainObject) {
this.rules = domainObject.configuration.ruleOrder.map(function (ruleId) {
return new SummaryWidgetRule(domainObject.configuration.ruleConfigById[ruleId]);
});
};
SummaryWidgetEvaluator.prototype.addChild = function (childObject) {
var childId = objectUtils.makeKeyString(childObject.identifier);
var metadata = this.openmct.telemetry.getMetadata(childObject);
var formats = this.openmct.telemetry.getFormatMap(metadata);
this.baseState[childId] = {
id: childId,
domainObject: childObject,
metadata: metadata,
formats: formats
};
};
SummaryWidgetEvaluator.prototype.removeChild = function (childObject) {
var childId = objectUtils.makeKeyString(childObject.identifier);
delete this.baseState[childId];
};
SummaryWidgetEvaluator.prototype.load = function () {
return this.loadPromise;
};
/**
* Return a promise for a 2-deep clone of the base state object: object
* states are shallow cloned, and then assembled and returned as a new base
* state. Allows object states to be mutated while sharing telemetry
* metadata and formats.
*/
SummaryWidgetEvaluator.prototype.getBaseStateClone = function () {
return this.load()
.then(function () {
return _(this.baseState)
.values()
.map(_.clone)
.indexBy('id')
.value();
}.bind(this));
};
/**
* Subscribes to realtime updates for a given objectState, and invokes
* the supplied callback when objectState has been updated. Returns
* a function to unsubscribe.
* @private.
*/
SummaryWidgetEvaluator.prototype.subscribeToObjectState = function (callback, objectState) {
return this.openmct.telemetry.subscribe(
objectState.domainObject,
function (datum) {
objectState.lastDatum = datum;
objectState.timestamps = this.getTimestamps(objectState.id, datum);
callback();
}.bind(this)
);
};
/**
* Given an object state, will return a promise that is resolved when the
* object state has been updated from the LAD.
* @private.
*/
SummaryWidgetEvaluator.prototype.updateObjectStateFromLAD = function (options, objectState) {
options = _.extend({}, options, {
strategy: 'latest',
size: 1
});
return this.openmct
.telemetry
.request(
objectState.domainObject,
options
)
.then(function (results) {
objectState.lastDatum = results[results.length - 1];
objectState.timestamps = this.getTimestamps(
objectState.id,
objectState.lastDatum
);
}.bind(this));
};
/**
* Returns an object containing all domain values in a datum.
* @private.
*/
SummaryWidgetEvaluator.prototype.getTimestamps = function (childId, datum) {
var timestampedDatum = {};
this.openmct.time.getAllTimeSystems().forEach(function (timeSystem) {
timestampedDatum[timeSystem.key] =
this.baseState[childId].formats[timeSystem.key].parse(datum);
}, this);
return timestampedDatum;
};
/**
* Given a base datum(containing timestamps) and rule index, adds values
* from the matching rule.
* @private
*/
SummaryWidgetEvaluator.prototype.makeDatumFromRule = function (ruleIndex, baseDatum) {
var rule = this.rules[ruleIndex];
baseDatum.ruleLabel = rule.label;
baseDatum.ruleName = rule.name;
baseDatum.message = rule.message;
baseDatum.ruleIndex = ruleIndex;
baseDatum.backgroundColor = rule.style['background-color'];
baseDatum.textColor = rule.style.color;
baseDatum.borderColor = rule.style['border-color'];
baseDatum.icon = rule.icon;
return baseDatum;
};
/**
* Evaluate a `state` object and return a summary widget telemetry datum.
* Datum timestamps will be taken from the "latest" datum in the `state`
* where "latest" is the datum with the largest value for the given
* `timestampKey`.
* @private.
*/
SummaryWidgetEvaluator.prototype.evaluateState = function (state, timestampKey) {
var hasRequiredData = Object.keys(state).reduce(function (itDoes, k) {
return itDoes && state[k].lastDatum;
}, true);
if (!hasRequiredData) {
return;
}
for (var i = this.rules.length - 1; i > 0; i--) {
if (this.rules[i].evaluate(state, false)) {
break;
}
}
var latestTimestamp = _(state)
.map('timestamps')
.sortBy(timestampKey)
.last();
if (!latestTimestamp) {
latestTimestamp = {};
}
var baseDatum = _.clone(latestTimestamp);
return this.makeDatumFromRule(i, baseDatum);
};
/**
* remove all listeners and clean up any resources.
*/
SummaryWidgetEvaluator.prototype.destroy = function () {
this.stopListening();
this.removeObserver();
};
return SummaryWidgetEvaluator;
});

View File

@@ -1,119 +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.
*****************************************************************************/
define([
], function (
) {
function SummaryWidgetMetadataProvider(openmct) {
this.openmct = openmct;
}
SummaryWidgetMetadataProvider.prototype.supportsMetadata = function (domainObject) {
return domainObject.type === 'summary-widget';
};
SummaryWidgetMetadataProvider.prototype.getDomains = function (domainObject) {
return this.openmct.time.getAllTimeSystems().map(function (ts, i) {
return {
key: ts.key,
name: 'UTC',
format: ts.timeFormat,
hints: {
domain: i
}
};
});
};
SummaryWidgetMetadataProvider.prototype.getMetadata = function (domainObject) {
var ruleOrder = domainObject.configuration.ruleOrder || [];
var enumerations = ruleOrder
.filter(function (ruleId) {
return !!domainObject.configuration.ruleConfigById[ruleId];
})
.map(function (ruleId, ruleIndex) {
return {
string: domainObject.configuration.ruleConfigById[ruleId].label,
value: ruleIndex
};
});
var metadata = {
// Generally safe assumption is that we have one domain per timeSystem.
values: this.getDomains().concat([
{
name: 'state',
key: 'state',
source: 'ruleIndex',
format: 'enum',
enumerations: enumerations,
hints: {
range: 1
}
},
{
name: 'Rule Label',
key: 'ruleLabel',
format: 'string'
},
{
name: 'Rule Name',
key: 'ruleName',
format: 'string'
},
{
name: 'Message',
key: 'message',
format: 'string'
},
{
name: 'Background Color',
key: 'backgroundColor',
format: 'string'
},
{
name: 'Text Color',
key: 'textColor',
format: 'string'
},
{
name: 'Border Color',
key: 'borderColor',
format: 'string'
},
{
name: 'Display Icon',
key: 'icon',
format: 'string'
}
])
};
return metadata;
};
return SummaryWidgetMetadataProvider;
});

View File

@@ -1,73 +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.
*****************************************************************************/
define([
'./SummaryWidgetCondition'
], function (
SummaryWidgetCondition
) {
function SummaryWidgetRule(definition) {
this.name = definition.name;
this.label = definition.label;
this.id = definition.id;
this.icon = definition.icon;
this.style = definition.style;
this.message = definition.message;
this.description = definition.description;
this.conditions = definition.conditions.map(function (cDefinition) {
return new SummaryWidgetCondition(cDefinition);
});
this.trigger = definition.trigger;
}
/**
* Evaluate the given rule against a telemetryState and return true if it
* matches.
*/
SummaryWidgetRule.prototype.evaluate = function (telemetryState) {
var i;
var result;
if (this.trigger === 'all') {
for (i = 0; i < this.conditions.length; i++) {
result = this.conditions[i].evaluate(telemetryState);
if (!result) {
return false;
}
}
return true;
} else if (this.trigger === 'any') {
for (i = 0; i < this.conditions.length; i++) {
result = this.conditions[i].evaluate(telemetryState);
if (result) {
return true;
}
}
return false;
} else {
throw new Error('Invalid rule trigger: ' + this.trigger);
}
};
return SummaryWidgetRule;
});

View File

@@ -1,163 +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.
*****************************************************************************/
define([
'./SummaryWidgetRule'
], function (
SummaryWidgetRule
) {
describe('SummaryWidgetRule', function () {
var rule;
var telemetryState;
beforeEach(function () {
var formatMap = {
raw: {
parse: function (datum) {
return datum.value;
}
}
};
telemetryState = {
objectId: {
formats: formatMap,
lastDatum: {
}
},
otherObjectId: {
formats: formatMap,
lastDatum: {
}
}
};
});
it('allows single condition rules with any', function () {
rule = new SummaryWidgetRule({
trigger: 'any',
conditions: [{
object: 'objectId',
key: 'raw',
operation: 'greaterThan',
values: [
10
]
}]
});
telemetryState.objectId.lastDatum.value = 5;
expect(rule.evaluate(telemetryState)).toBe(false);
telemetryState.objectId.lastDatum.value = 15;
expect(rule.evaluate(telemetryState)).toBe(true);
});
it('allows single condition rules with all', function () {
rule = new SummaryWidgetRule({
trigger: 'all',
conditions: [{
object: 'objectId',
key: 'raw',
operation: 'greaterThan',
values: [
10
]
}]
});
telemetryState.objectId.lastDatum.value = 5;
expect(rule.evaluate(telemetryState)).toBe(false);
telemetryState.objectId.lastDatum.value = 15;
expect(rule.evaluate(telemetryState)).toBe(true);
});
it('can combine multiple conditions with all', function () {
rule = new SummaryWidgetRule({
trigger: 'all',
conditions: [{
object: 'objectId',
key: 'raw',
operation: 'greaterThan',
values: [
10
]
}, {
object: 'otherObjectId',
key: 'raw',
operation: 'greaterThan',
values: [
20
]
}]
});
telemetryState.objectId.lastDatum.value = 5;
telemetryState.otherObjectId.lastDatum.value = 5;
expect(rule.evaluate(telemetryState)).toBe(false);
telemetryState.objectId.lastDatum.value = 5;
telemetryState.otherObjectId.lastDatum.value = 25;
expect(rule.evaluate(telemetryState)).toBe(false);
telemetryState.objectId.lastDatum.value = 15;
telemetryState.otherObjectId.lastDatum.value = 5;
expect(rule.evaluate(telemetryState)).toBe(false);
telemetryState.objectId.lastDatum.value = 15;
telemetryState.otherObjectId.lastDatum.value = 25;
expect(rule.evaluate(telemetryState)).toBe(true);
});
it('can combine multiple conditions with any', function () {
rule = new SummaryWidgetRule({
trigger: 'any',
conditions: [{
object: 'objectId',
key: 'raw',
operation: 'greaterThan',
values: [
10
]
}, {
object: 'otherObjectId',
key: 'raw',
operation: 'greaterThan',
values: [
20
]
}]
});
telemetryState.objectId.lastDatum.value = 5;
telemetryState.otherObjectId.lastDatum.value = 5;
expect(rule.evaluate(telemetryState)).toBe(false);
telemetryState.objectId.lastDatum.value = 5;
telemetryState.otherObjectId.lastDatum.value = 25;
expect(rule.evaluate(telemetryState)).toBe(true);
telemetryState.objectId.lastDatum.value = 15;
telemetryState.otherObjectId.lastDatum.value = 5;
expect(rule.evaluate(telemetryState)).toBe(true);
telemetryState.objectId.lastDatum.value = 15;
telemetryState.otherObjectId.lastDatum.value = 25;
expect(rule.evaluate(telemetryState)).toBe(true);
});
});
});

View File

@@ -1,64 +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.
*****************************************************************************/
define([
'./EvaluatorPool'
], function (
EvaluatorPool
) {
function SummaryWidgetTelemetryProvider(openmct) {
this.pool = new EvaluatorPool(openmct);
}
SummaryWidgetTelemetryProvider.prototype.supportsRequest = function (domainObject, options) {
return domainObject.type === 'summary-widget';
};
SummaryWidgetTelemetryProvider.prototype.request = function (domainObject, options) {
if (options.strategy !== 'latest' && options.size !== 1) {
return Promise.resolve([]);
}
var evaluator = this.pool.get(domainObject);
return evaluator.requestLatest(options)
.then(function (latestDatum) {
this.pool.release(evaluator);
return [latestDatum];
}.bind(this));
};
SummaryWidgetTelemetryProvider.prototype.supportsSubscribe = function (domainObject) {
return domainObject.type === 'summary-widget';
};
SummaryWidgetTelemetryProvider.prototype.subscribe = function (domainObject, callback) {
var evaluator = this.pool.get(domainObject);
var unsubscribe = evaluator.subscribe(callback);
return function () {
this.pool.release(evaluator);
unsubscribe();
}.bind(this);
};
return SummaryWidgetTelemetryProvider;
});

View File

@@ -1,475 +0,0 @@
define([
'./SummaryWidgetTelemetryProvider'
], function (
SummaryWidgetTelemetryProvider
) {
describe('SummaryWidgetTelemetryProvider', function () {
var telemObjectA;
var telemObjectB;
var summaryWidgetObject;
var openmct;
var telemUnsubscribes;
var unobserver;
var composition;
var telemetryProvider;
var loader;
beforeEach(function () {
telemObjectA = {
identifier: {
namespace: 'a',
key: 'telem'
}
};
telemObjectB = {
identifier: {
namespace: 'b',
key: 'telem'
}
};
summaryWidgetObject = {
name: "Summary Widget",
type: "summary-widget",
identifier: {
namespace: 'base',
key: 'widgetId'
},
composition: [
'a:telem',
'b:telem'
],
configuration: {
ruleOrder: [
"default",
"rule0",
"rule1"
],
ruleConfigById: {
"default": {
name: "safe",
label: "Don't Worry",
message: "It's Ok",
id: "default",
icon: "a-ok",
style: {
"color": "#ffffff",
"background-color": "#38761d",
"border-color": "rgba(0,0,0,0)"
},
conditions: [
{
object: "",
key: "",
operation: "",
values: []
}
],
trigger: "any"
},
"rule0": {
name: "A High",
label: "Start Worrying",
message: "A is a little high...",
id: "rule0",
icon: "a-high",
style: {
"color": "#000000",
"background-color": "#ffff00",
"border-color": "rgba(1,1,0,0)"
},
conditions: [
{
object: "a:telem",
key: "measurement",
operation: "greaterThan",
values: [
50
]
}
],
trigger: "any"
},
rule1: {
name: "B Low",
label: "WORRY!",
message: "B is Low",
id: "rule1",
icon: "b-low",
style: {
"color": "#ff00ff",
"background-color": "#ff0000",
"border-color": "rgba(1,0,0,0)"
},
conditions: [
{
object: "b:telem",
key: "measurement",
operation: "lessThan",
values: [
10
]
}
],
trigger: "any"
}
}
}
};
openmct = {
objects: jasmine.createSpyObj('objectAPI', [
'get',
'observe'
]),
telemetry: jasmine.createSpyObj('telemetryAPI', [
'getMetadata',
'getFormatMap',
'request',
'subscribe',
'addProvider'
]),
composition: jasmine.createSpyObj('compositionAPI', [
'get'
]),
time: jasmine.createSpyObj('timeAPI', [
'getAllTimeSystems',
'timeSystem'
])
};
openmct.time.getAllTimeSystems.andReturn([{key: 'timestamp'}]);
openmct.time.timeSystem.andReturn({key: 'timestamp'});
unobserver = jasmine.createSpy('unobserver');
openmct.objects.observe.andReturn(unobserver);
composition = jasmine.createSpyObj('compositionCollection', [
'on',
'off',
'load'
]);
function notify(eventName, a, b) {
composition.on.calls.filter(function (c) {
return c.args[0] === eventName;
}).forEach(function (c) {
if (c.args[2]) { // listener w/ context.
c.args[1].call(c.args[2], a, b);
} else { // listener w/o context.
c.args[1](a, b);
}
});
}
loader = {};
loader.promise = new Promise(function (resolve, reject) {
loader.resolve = resolve;
loader.reject = reject;
});
composition.load.andCallFake(function () {
setTimeout(function () {
notify('add', telemObjectA);
setTimeout(function () {
notify('add', telemObjectB);
setTimeout(function () {
loader.resolve();
setTimeout(function () {
loader.loaded = true;
});
});
});
});
return loader.promise;
});
openmct.composition.get.andReturn(composition);
telemUnsubscribes = [];
openmct.telemetry.subscribe.andCallFake(function () {
var unsubscriber = jasmine.createSpy('unsubscriber' + telemUnsubscribes.length);
telemUnsubscribes.push(unsubscriber);
return unsubscriber;
});
openmct.telemetry.getMetadata.andCallFake(function (object) {
return {
name: 'fake metadata manager',
object: object,
keys: ['timestamp', 'measurement']
};
});
openmct.telemetry.getFormatMap.andCallFake(function (metadata) {
expect(metadata.name).toBe('fake metadata manager');
return {
metadata: metadata,
timestamp: {
parse: function (datum) {
return datum.t;
}
},
measurement: {
parse: function (datum) {
return datum.m;
}
}
};
});
telemetryProvider = new SummaryWidgetTelemetryProvider(openmct);
});
it("supports subscription for summary widgets", function () {
expect(telemetryProvider.supportsSubscribe(summaryWidgetObject))
.toBe(true);
});
it("supports requests for summary widgets", function () {
expect(telemetryProvider.supportsRequest(summaryWidgetObject))
.toBe(true);
});
it("does not support other requests or subscriptions", function () {
expect(telemetryProvider.supportsSubscribe(telemObjectA))
.toBe(false);
expect(telemetryProvider.supportsRequest(telemObjectA))
.toBe(false);
});
it("Returns no results for basic requests", function () {
var result;
telemetryProvider.request(summaryWidgetObject, {})
.then(function (r) {
result = r;
});
waitsFor(function () {
return !!result;
});
runs(function () {
expect(result).toEqual([]);
});
});
it('provides realtime telemetry', function () {
var callback = jasmine.createSpy('callback');
telemetryProvider.subscribe(summaryWidgetObject, callback);
waitsFor(function () {
return loader.loaded;
});
runs(function () {
expect(openmct.telemetry.subscribe.calls.length).toBe(2);
expect(openmct.telemetry.subscribe)
.toHaveBeenCalledWith(telemObjectA, jasmine.any(Function));
expect(openmct.telemetry.subscribe)
.toHaveBeenCalledWith(telemObjectB, jasmine.any(Function));
var aCallback = openmct.telemetry.subscribe.calls[0].args[1];
var bCallback = openmct.telemetry.subscribe.calls[1].args[1];
aCallback({
t: 123,
m: 25
});
expect(callback).not.toHaveBeenCalled();
bCallback({
t: 123,
m: 25
});
expect(callback).toHaveBeenCalledWith({
timestamp: 123,
ruleLabel: "Don't Worry",
ruleName: "safe",
message: "It's Ok",
ruleIndex: 0,
backgroundColor: '#38761d',
textColor: '#ffffff',
borderColor: 'rgba(0,0,0,0)',
icon: 'a-ok'
});
callback.reset();
aCallback({
t: 140,
m: 55
});
expect(callback).toHaveBeenCalledWith({
timestamp: 140,
ruleLabel: "Start Worrying",
ruleName: "A High",
message: "A is a little high...",
ruleIndex: 1,
backgroundColor: '#ffff00',
textColor: '#000000',
borderColor: 'rgba(1,1,0,0)',
icon: 'a-high'
});
callback.reset();
bCallback({
t: 140,
m: -10
});
expect(callback).toHaveBeenCalledWith({
timestamp: 140,
ruleLabel: "WORRY!",
ruleName: "B Low",
message: "B is Low",
ruleIndex: 2,
backgroundColor: '#ff0000',
textColor: '#ff00ff',
borderColor: 'rgba(1,0,0,0)',
icon: 'b-low'
});
callback.reset();
aCallback({
t: 160,
m: 25
});
expect(callback).toHaveBeenCalledWith({
timestamp: 160,
ruleLabel: "WORRY!",
ruleName: "B Low",
message: "B is Low",
ruleIndex: 2,
backgroundColor: '#ff0000',
textColor: '#ff00ff',
borderColor: 'rgba(1,0,0,0)',
icon: 'b-low'
});
callback.reset();
bCallback({
t: 160,
m: 25
});
expect(callback).toHaveBeenCalledWith({
timestamp: 160,
ruleLabel: "Don't Worry",
ruleName: "safe",
message: "It's Ok",
ruleIndex: 0,
backgroundColor: '#38761d',
textColor: '#ffffff',
borderColor: 'rgba(0,0,0,0)',
icon: 'a-ok'
});
});
});
describe('providing lad telemetry', function () {
var isResolved;
var resolver;
var responseDatums;
var resultsShouldBe;
beforeEach(function () {
isResolved = false;
resolver = jasmine.createSpy('resolved')
.andCallFake(function () {
isResolved = true;
});
openmct.telemetry.request.andCallFake(function (rObj, options) {
expect(rObj).toEqual(jasmine.any(Object));
expect(options).toEqual({size: 1, strategy: 'latest', domain: 'timestamp'});
expect(responseDatums[rObj.identifier.namespace]).toBeDefined();
return Promise.resolve([responseDatums[rObj.identifier.namespace]]);
});
responseDatums = {};
resultsShouldBe = function (results) {
telemetryProvider
.request(summaryWidgetObject, {size: 1, strategy: 'latest', domain: 'timestamp'})
.then(resolver);
waitsFor(function () {
return isResolved;
});
runs(function () {
expect(resolver).toHaveBeenCalledWith(results);
});
};
});
it("returns default when no rule matches", function () {
responseDatums = {
a: {
t: 122,
m: 25
},
b: {
t: 111,
m: 25
}
};
resultsShouldBe([{
timestamp: 122,
ruleLabel: "Don't Worry",
ruleName: "safe",
message: "It's Ok",
ruleIndex: 0,
backgroundColor: '#38761d',
textColor: '#ffffff',
borderColor: 'rgba(0,0,0,0)',
icon: 'a-ok'
}]);
});
it("returns highest priority when multiple match", function () {
responseDatums = {
a: {
t: 131,
m: 55
},
b: {
t: 139,
m: 5
}
};
resultsShouldBe([{
timestamp: 139,
ruleLabel: "WORRY!",
ruleName: "B Low",
message: "B is Low",
ruleIndex: 2,
backgroundColor: '#ff0000',
textColor: '#ff00ff',
borderColor: 'rgba(1,0,0,0)',
icon: 'b-low'
}]);
});
it("returns matching rule", function () {
responseDatums = {
a: {
t: 144,
m: 55
},
b: {
t: 141,
m: 15
}
};
resultsShouldBe([{
timestamp: 144,
ruleLabel: "Start Worrying",
ruleName: "A High",
message: "A is a little high...",
ruleIndex: 1,
backgroundColor: '#ffff00',
textColor: '#000000',
borderColor: 'rgba(1,1,0,0)',
icon: 'a-high'
}]);
});
});
});
});

View File

@@ -1,197 +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.
*****************************************************************************/
define([
], function (
) {
var OPERATIONS = {
equalTo: {
operation: function (input) {
return input[0] === input[1];
},
text: 'is equal to',
appliesTo: ['number'],
inputCount: 1,
getDescription: function (values) {
return ' == ' + values[0];
}
},
notEqualTo: {
operation: function (input) {
return input[0] !== input[1];
},
text: 'is not equal to',
appliesTo: ['number'],
inputCount: 1,
getDescription: function (values) {
return ' != ' + values[0];
}
},
greaterThan: {
operation: function (input) {
return input[0] > input[1];
},
text: 'is greater than',
appliesTo: ['number'],
inputCount: 1,
getDescription: function (values) {
return ' > ' + values[0];
}
},
lessThan: {
operation: function (input) {
return input[0] < input[1];
},
text: 'is less than',
appliesTo: ['number'],
inputCount: 1,
getDescription: function (values) {
return ' < ' + values[0];
}
},
greaterThanOrEq: {
operation: function (input) {
return input[0] >= input[1];
},
text: 'is greater than or equal to',
appliesTo: ['number'],
inputCount: 1,
getDescription: function (values) {
return ' >= ' + values[0];
}
},
lessThanOrEq: {
operation: function (input) {
return input[0] <= input[1];
},
text: 'is less than or equal to',
appliesTo: ['number'],
inputCount: 1,
getDescription: function (values) {
return ' <= ' + values[0];
}
},
between: {
operation: function (input) {
return input[0] > input[1] && input[0] < input[2];
},
text: 'is between',
appliesTo: ['number'],
inputCount: 2,
getDescription: function (values) {
return ' between ' + values[0] + ' and ' + values[1];
}
},
notBetween: {
operation: function (input) {
return input[0] < input[1] || input[0] > input[2];
},
text: 'is not between',
appliesTo: ['number'],
inputCount: 2,
getDescription: function (values) {
return ' not between ' + values[0] + ' and ' + values[1];
}
},
textContains: {
operation: function (input) {
return input[0] && input[1] && input[0].includes(input[1]);
},
text: 'text contains',
appliesTo: ['string'],
inputCount: 1,
getDescription: function (values) {
return ' contains ' + values[0];
}
},
textDoesNotContain: {
operation: function (input) {
return input[0] && input[1] && !input[0].includes(input[1]);
},
text: 'text does not contain',
appliesTo: ['string'],
inputCount: 1,
getDescription: function (values) {
return ' does not contain ' + values[0];
}
},
textStartsWith: {
operation: function (input) {
return input[0].startsWith(input[1]);
},
text: 'text starts with',
appliesTo: ['string'],
inputCount: 1,
getDescription: function (values) {
return ' starts with ' + values[0];
}
},
textEndsWith: {
operation: function (input) {
return input[0].endsWith(input[1]);
},
text: 'text ends with',
appliesTo: ['string'],
inputCount: 1,
getDescription: function (values) {
return ' ends with ' + values[0];
}
},
textIsExactly: {
operation: function (input) {
return input[0] === input[1];
},
text: 'text is exactly',
appliesTo: ['string'],
inputCount: 1,
getDescription: function (values) {
return ' is exactly ' + values[0];
}
},
isUndefined: {
operation: function (input) {
return typeof input[0] === 'undefined';
},
text: 'is undefined',
appliesTo: ['string', 'number'],
inputCount: 0,
getDescription: function () {
return ' is undefined';
}
},
isDefined: {
operation: function (input) {
return typeof input[0] !== 'undefined';
},
text: 'is defined',
appliesTo: ['string', 'number'],
inputCount: 0,
getDescription: function () {
return ' is defined';
}
}
};
return OPERATIONS;
});

View File

@@ -1,92 +0,0 @@
define([
'text!./summary-widget.html'
], function (
summaryWidgetTemplate
) {
function SummaryWidgetView(domainObject, openmct) {
this.openmct = openmct;
this.domainObject = domainObject;
this.hasUpdated = false;
this.render = this.render.bind(this);
}
SummaryWidgetView.prototype.updateState = function (datum) {
this.hasUpdated = true;
this.widget.style.color = datum.textColor;
this.widget.style.backgroundColor = datum.backgroundColor;
this.widget.style.borderColor = datum.borderColor;
this.widget.title = datum.message;
this.label.title = datum.message;
this.label.innerHTML = datum.ruleLabel;
this.label.className = 'label widget-label ' + datum.icon;
};
SummaryWidgetView.prototype.render = function () {
if (this.unsubscribe) {
this.unsubscribe();
}
this.hasUpdated = false;
this.container.innerHTML = summaryWidgetTemplate;
this.widget = this.container.querySelector('a');
this.label = this.container.querySelector('.widget-label');
if (this.domainObject.url) {
this.widget.setAttribute('href', this.domainObject.url);
} else {
this.widget.removeAttribute('href');
}
if (this.domainObject.openNewTab === 'newTab') {
this.widget.setAttribute('target', '_blank');
} else {
this.widget.removeAttribute('target');
}
var renderTracker = {};
this.renderTracker = renderTracker;
this.openmct.telemetry.request(this.domainObject, {
strategy: 'latest',
size: 1
}).then(function (results) {
if (this.destroyed || this.hasUpdated || this.renderTracker !== renderTracker) {
return;
}
this.updateState(results[results.length - 1]);
}.bind(this));
this.unsubscribe = this.openmct
.telemetry
.subscribe(this.domainObject, this.updateState.bind(this));
};
SummaryWidgetView.prototype.show = function (container) {
this.container = container;
this.render();
this.removeMutationListener = this.openmct.objects.observe(
this.domainObject,
'*',
this.onMutation.bind(this)
);
this.openmct.time.on('timeSystem', this.render);
};
SummaryWidgetView.prototype.onMutation = function (domainObject) {
this.domainObject = domainObject;
this.render();
};
SummaryWidgetView.prototype.destroy = function (container) {
this.unsubscribe();
this.removeMutationListener();
this.openmct.time.off('timeSystem', this.render);
this.destroyed = true;
delete this.widget;
delete this.label;
delete this.openmct;
delete this.domainObject;
};
return SummaryWidgetView;
});

View File

@@ -1,42 +0,0 @@
define([
'../SummaryWidget',
'./SummaryWidgetView',
'../../../../api/objects/object-utils'
], function (
SummaryWidgetEditView,
SummaryWidgetView,
objectUtils
) {
/**
*
*/
function SummaryWidgetViewProvider(openmct) {
return {
key: 'summary-widget-viewer',
name: 'Widget View',
canView: function (domainObject) {
return domainObject.type === 'summary-widget';
},
view: function (domainObject) {
var statusService = openmct.$injector.get('statusService');
var objectId = objectUtils.makeKeyString(domainObject.identifier);
var statuses = statusService.listStatuses(objectId);
var isEditing = statuses.indexOf('editing') !== -1;
if (isEditing) {
return new SummaryWidgetEditView(domainObject, openmct);
} else {
return new SummaryWidgetView(domainObject, openmct);
}
},
editable: true,
priority: function (domainObject) {
return 1;
}
};
}
return SummaryWidgetViewProvider;
});

View File

@@ -1,5 +0,0 @@
<div class="w-summary-widget s-status-no-data">
<a class="t-summary-widget l-summary-widget s-summary-widget labeled">
<span class="label widget-label">Loading...</span>
</a>
</div>

View File

@@ -19,7 +19,6 @@ define(['../src/ConditionManager'], function (ConditionManager) {
removeCallbackSpy,
telemetryCallbackSpy,
metadataCallbackSpy,
telemetryRequests,
mockTelemetryValues,
mockTelemetryValues2,
mockConditionEvaluator;
@@ -62,43 +61,31 @@ define(['../src/ConditionManager'], function (ConditionManager) {
mockCompObject1: {
property1: {
key: 'property1',
name: 'Property 1',
format: 'string',
hints: {}
name: 'Property 1'
},
property2: {
key: 'property2',
name: 'Property 2',
hints: {
domain: 1
}
name: 'Property 2'
}
},
mockCompObject2: {
property3: {
key: 'property3',
name: 'Property 3',
format: 'string',
hints: {}
name: 'Property 3'
},
property4: {
key: 'property4',
name: 'Property 4',
hints: {
range: 1
}
name: 'Property 4'
}
},
mockCompObject3: {
property1: {
key: 'property1',
name: 'Property 1',
hints: {}
name: 'Property 1'
},
property2: {
key: 'property2',
name: 'Property 2',
hints: {}
name: 'Property 2'
}
}
};
@@ -173,20 +160,22 @@ define(['../src/ConditionManager'], function (ConditionManager) {
unregisterSpies[event]();
});
mockComposition.load.andCallFake(function () {
mockComposition.triggerCallback('add', mockCompObject1);
mockComposition.triggerCallback('add', mockCompObject2);
mockComposition.triggerCallback('load');
mockEventCallbacks.add(mockCompObject1);
mockEventCallbacks.add(mockCompObject2);
mockEventCallbacks.load();
});
mockComposition.triggerCallback.andCallFake(function (event, obj) {
mockComposition.triggerCallback.andCallFake(function (event) {
if (event === 'add') {
mockEventCallbacks.add(obj);
mockEventCallbacks.add(mockCompObject3);
} else if (event === 'remove') {
mockEventCallbacks.remove(obj.identifier);
mockEventCallbacks.remove({
key: 'mockCompObject2'
});
} else {
mockEventCallbacks[event]();
}
});
telemetryRequests = [];
mockTelemetryAPI = jasmine.createSpyObj('telemetryAPI', [
'request',
'isTelemetryObject',
@@ -195,15 +184,9 @@ define(['../src/ConditionManager'], function (ConditionManager) {
'triggerTelemetryCallback'
]);
mockTelemetryAPI.request.andCallFake(function (obj) {
var req = {
object: obj
};
req.promise = new Promise(function (resolve, reject) {
req.resolve = resolve;
req.reject = reject;
return new Promise(function (resolve, reject) {
resolve(mockTelemetryValues[obj.identifer.key]);
});
telemetryRequests.push(req);
return req.promise;
});
mockTelemetryAPI.isTelemetryObject.andReturn(true);
mockTelemetryAPI.getMetadata.andCallFake(function (obj) {
@@ -262,50 +245,41 @@ define(['../src/ConditionManager'], function (ConditionManager) {
var allKeys = {
property1: {
key: 'property1',
name: 'Property 1',
format: 'string',
hints: {}
name: 'Property 1'
},
property2: {
key: 'property2',
name: 'Property 2',
hints: {
domain: 1
}
name: 'Property 2'
},
property3: {
key: 'property3',
name: 'Property 3',
format: 'string',
hints: {}
name: 'Property 3'
},
property4: {
key: 'property4',
name: 'Property 4',
hints: {
range: 1
}
name: 'Property 4'
}
};
expect(conditionManager.getTelemetryMetadata('all')).toEqual(allKeys);
expect(conditionManager.getTelemetryMetadata('any')).toEqual(allKeys);
mockComposition.triggerCallback('add', mockCompObject3);
mockComposition.triggerCallback('add');
expect(conditionManager.getTelemetryMetadata('all')).toEqual(allKeys);
expect(conditionManager.getTelemetryMetadata('any')).toEqual(allKeys);
});
it('loads and gets telemetry property types', function () {
conditionManager.parseAllPropertyTypes();
expect(conditionManager.getTelemetryPropertyType('mockCompObject1', 'property1'))
.toEqual('string');
expect(conditionManager.getTelemetryPropertyType('mockCompObject2', 'property4'))
.toEqual('number');
expect(conditionManager.metadataLoadCompleted()).toEqual(true);
expect(metadataCallbackSpy).toHaveBeenCalled();
conditionManager.parseAllPropertyTypes().then(function () {
expect(conditionManager.getTelemetryPropertyType('mockCompObject1', 'property1'))
.toEqual('string');
expect(conditionManager.getTelemetryPropertyType('mockCompObject2', 'property4'))
.toEqual('number');
expect(conditionManager.metadataLoadComplete()).toEqual(true);
expect(metadataCallbackSpy).toHaveBeenCalled();
});
});
it('responds to a composition add event and invokes the appropriate handlers', function () {
mockComposition.triggerCallback('add', mockCompObject3);
mockComposition.triggerCallback('add');
expect(addCallbackSpy).toHaveBeenCalledWith(mockCompObject3);
expect(conditionManager.getComposition()).toEqual({
mockCompObject1: mockCompObject1,
@@ -315,7 +289,7 @@ define(['../src/ConditionManager'], function (ConditionManager) {
});
it('responds to a composition remove event and invokes the appropriate handlers', function () {
mockComposition.triggerCallback('remove', mockCompObject2);
mockComposition.triggerCallback('remove');
expect(removeCallbackSpy).toHaveBeenCalledWith({
key: 'mockCompObject2'
});
@@ -326,7 +300,7 @@ define(['../src/ConditionManager'], function (ConditionManager) {
});
it('unregisters telemetry subscriptions and composition listeners on destroy', function () {
mockComposition.triggerCallback('add', mockCompObject3);
mockComposition.triggerCallback('add');
conditionManager.destroy();
Object.values(unsubscribeSpies).forEach(function (spy) {
expect(spy).toHaveBeenCalled();
@@ -337,19 +311,7 @@ define(['../src/ConditionManager'], function (ConditionManager) {
});
it('populates its LAD cache with historial data on load, if available', function () {
expect(telemetryRequests.length).toBe(2);
expect(telemetryRequests[0].object).toBe(mockCompObject1);
expect(telemetryRequests[1].object).toBe(mockCompObject2);
expect(telemetryCallbackSpy).not.toHaveBeenCalled();
telemetryRequests[0].resolve([mockTelemetryValues.mockCompObject1]);
telemetryRequests[1].resolve([mockTelemetryValues.mockCompObject2]);
waitsFor(function () {
return telemetryCallbackSpy.calls.length === 2;
});
runs(function () {
conditionManager.parseAllPropertyTypes().then(function () {
expect(conditionManager.subscriptionCache.mockCompObject1.property1).toEqual('Its a string');
expect(conditionManager.subscriptionCache.mockCompObject2.property4).toEqual(66);
});
@@ -390,10 +352,12 @@ define(['../src/ConditionManager'], function (ConditionManager) {
});
it('gets the human-readable name of a telemetry field', function () {
expect(conditionManager.getTelemetryPropertyName('mockCompObject1', 'property1'))
.toEqual('Property 1');
expect(conditionManager.getTelemetryPropertyName('mockCompObject2', 'property4'))
.toEqual('Property 4');
conditionManager.parseAllPropertyTypes().then(function () {
expect(conditionManager.getTelemetryPropertyName('mockCompObject1', 'property1'))
.toEqual('Property 1');
expect(conditionManager.getTelemetryPropertyName('mockCompObject2', 'property4'))
.toEqual('Property 4');
});
});
it('gets its associated ConditionEvaluator', function () {

View File

@@ -75,7 +75,8 @@ requirejs.config({
"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-format": "node_modules/d3-time-format/build/d3-time-format.min",
"d3-dsv": "node_modules/d3-dsv/build/d3-dsv.min"
},
"shim": {