mirror of
https://github.com/nasa/openmct.git
synced 2025-06-25 18:50:11 +00:00
Compare commits
21 Commits
notebook-i
...
object-del
Author | SHA1 | Date | |
---|---|---|---|
b4fbf0a187 | |||
241d75e393 | |||
f4271c96a3 | |||
8c739e9fd9 | |||
75ae5ab3bb | |||
78a5ace18d | |||
ba0077498c | |||
909a21ee8a | |||
94c35a67c5 | |||
19fc05b76d | |||
1326dae27a | |||
7d8dc00996 | |||
a958055f50 | |||
1f86a5a131 | |||
8ad07fa8de | |||
fa7298a752 | |||
ae42298d08 | |||
1a23f2b390 | |||
4df6d6141b | |||
1c97138607 | |||
8db75bf41e |
171
API.md
171
API.md
@ -1,3 +1,57 @@
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
**Table of Contents**
|
||||
|
||||
- [Building Applications With Open MCT](#building-applications-with-open-mct)
|
||||
- [Scope and purpose of this document](#scope-and-purpose-of-this-document)
|
||||
- [Building From Source](#building-from-source)
|
||||
- [Starting an Open MCT application](#starting-an-open-mct-application)
|
||||
- [Plugins](#plugins)
|
||||
- [Defining and Installing a New Plugin](#defining-and-installing-a-new-plugin)
|
||||
- [Domain Objects and Identifiers](#domain-objects-and-identifiers)
|
||||
- [Object Attributes](#object-attributes)
|
||||
- [Domain Object Types](#domain-object-types)
|
||||
- [Root Objects](#root-objects)
|
||||
- [Object Providers](#object-providers)
|
||||
- [Composition Providers](#composition-providers)
|
||||
- [Adding Composition Providers](#adding-composition-providers)
|
||||
- [Default Composition Provider](#default-composition-provider)
|
||||
- [Telemetry API](#telemetry-api)
|
||||
- [Integrating Telemetry Sources](#integrating-telemetry-sources)
|
||||
- [Telemetry Metadata](#telemetry-metadata)
|
||||
- [Values](#values)
|
||||
- [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)
|
||||
- [Request Strategies **draft**](#request-strategies-draft)
|
||||
- [`latest` request strategy](#latest-request-strategy)
|
||||
- [`minmax` request strategy](#minmax-request-strategy)
|
||||
- [Telemetry Formats **draft**](#telemetry-formats-draft)
|
||||
- [Registering Formats](#registering-formats)
|
||||
- [Telemetry Data](#telemetry-data)
|
||||
- [Telemetry Datums](#telemetry-datums)
|
||||
- [Limit Evaluators **draft**](#limit-evaluators-draft)
|
||||
- [Telemetry Consumer APIs **draft**](#telemetry-consumer-apis-draft)
|
||||
- [Time API](#time-api)
|
||||
- [Time Systems and Bounds](#time-systems-and-bounds)
|
||||
- [Defining and Registering Time Systems](#defining-and-registering-time-systems)
|
||||
- [Getting and Setting the Active Time System](#getting-and-setting-the-active-time-system)
|
||||
- [Time Bounds](#time-bounds)
|
||||
- [Clocks](#clocks)
|
||||
- [Defining and registering clocks](#defining-and-registering-clocks)
|
||||
- [Getting and setting active clock](#getting-and-setting-active-clock)
|
||||
- [Stopping an active clock](#stopping-an-active-clock)
|
||||
- [Clock Offsets](#clock-offsets)
|
||||
- [Time Events](#time-events)
|
||||
- [List of Time Events](#list-of-time-events)
|
||||
- [The Time Conductor](#the-time-conductor)
|
||||
- [Time Conductor Configuration](#time-conductor-configuration)
|
||||
- [Example conductor configuration](#example-conductor-configuration)
|
||||
- [Included Plugins](#included-plugins)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
# Building Applications With Open MCT
|
||||
|
||||
## Scope and purpose of this document
|
||||
@ -154,7 +208,7 @@ registry.
|
||||
|
||||
eg.
|
||||
```javascript
|
||||
openmct.types.addType('my-type', {
|
||||
openmct.types.addType('example.my-type', {
|
||||
name: "My Type",
|
||||
description: "This is a type that I added!",
|
||||
creatable: true
|
||||
@ -162,8 +216,9 @@ openmct.types.addType('my-type', {
|
||||
```
|
||||
|
||||
The `addType` function accepts two arguments:
|
||||
* A `string` key identifying the type. This key is used when specifying a type
|
||||
for an object.
|
||||
* A `string` key identifying the type. This key is used when specifying a type
|
||||
for an object. We recommend prefixing your types with a namespace to avoid
|
||||
conflicts with other plugins.
|
||||
* An object type specification. An object type definition supports the following
|
||||
attributes
|
||||
* `name`: a `string` naming this object type
|
||||
@ -194,7 +249,7 @@ To do so, use the `addRoot` method of the object API.
|
||||
eg.
|
||||
```javascript
|
||||
openmct.objects.addRoot({
|
||||
namespace: "my-namespace",
|
||||
namespace: "example.namespace",
|
||||
key: "my-key"
|
||||
});
|
||||
```
|
||||
@ -235,13 +290,12 @@ It is expected that the `get` function will return a
|
||||
[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
|
||||
that resolves with the object being requested.
|
||||
|
||||
In future, object providers will support other methods to enable other operations
|
||||
with persistence stores, such as creating, updating, and deleting objects.
|
||||
In future, object providers will support other methods to enable other operations with persistence stores, such as creating, updating, and deleting objects.
|
||||
|
||||
## Composition Providers
|
||||
|
||||
The _composition_ of a domain object is the list of objects it contains, as shown
|
||||
(for example) in the tree for browsing. Open MCT provides a
|
||||
The _composition_ of a domain object is the list of objects it contains, as
|
||||
shown (for example) in the tree for browsing. Open MCT provides a
|
||||
[default solution](#default-composition-provider) for composition, but there
|
||||
may be cases where you want to provide the composition of a certain object
|
||||
(or type of object) dynamically.
|
||||
@ -255,7 +309,7 @@ Composition Provider:
|
||||
```javascript
|
||||
openmct.composition.addProvider({
|
||||
appliesTo: function (domainObject) {
|
||||
return domainObject.type === 'my-type';
|
||||
return domainObject.type === 'example.my-type';
|
||||
},
|
||||
load: function (domainObject) {
|
||||
return Promise.resolve(myDomainObjects);
|
||||
@ -273,8 +327,9 @@ These identifiers will be used to fetch Domain Objects from an [Object Provider]
|
||||
|
||||
### Default Composition Provider
|
||||
|
||||
The default composition provider applies to any domain object with a `composition`
|
||||
property. The value of `composition` should be an array of identifiers, e.g.:
|
||||
The default composition provider applies to any domain object with a
|
||||
`composition` property. The value of `composition` should be an array of
|
||||
identifiers, e.g.:
|
||||
|
||||
```javascript
|
||||
var domainObject = {
|
||||
@ -295,13 +350,17 @@ var domainObject = {
|
||||
|
||||
## Telemetry API
|
||||
|
||||
The Open MCT telemetry API provides two main sets of interfaces-- one for integrating telemetry data into Open MCT, and another for developing Open MCT visualization plugins utilizing telemetry API.
|
||||
The Open MCT telemetry API provides two main sets of interfaces-- one for
|
||||
integrating telemetry data into Open MCT, and another for developing Open MCT
|
||||
visualization plugins utilizing the telemetry API.
|
||||
|
||||
The APIs for visualization plugins are still a work in progress and docs may change at any time. However, the APIs for integrating telemetry metadata into Open MCT are stable and documentation is included below.
|
||||
The APIs for visualization plugins are still a work in progress and docs may
|
||||
change at any time. However, the APIs for integrating telemetry metadata into
|
||||
Open MCT are stable and documentation is included below.
|
||||
|
||||
### Integrating Telemetry Sources
|
||||
|
||||
There are two main tasks for integrating telemetry sources-- describing telemetry objects with relevant metadata, and then providing telemetry data for those objects. You'll use an [Object Provider](#object-providers) to provide objects with the necessary [Telemetry Metadata](#telemetry-metadata), and then register a [Telemetry Provider](#telemetry-providers) to retrieve telemetry data for those objects.
|
||||
There are two main tasks for integrating telemetry sources-- describing telemetry objects with relevant metadata, and then providing telemetry data for those objects. You'll use an [Object Provider](#object-providers) to provide objects with the necessary [Telemetry Metadata](#telemetry-metadata), and then register a [Telemetry Provider](#telemetry-providers) to retrieve telemetry data for those objects. Alternatively, you can register a telemetry metadata provider to provide the necessary telemetry metadata.
|
||||
|
||||
For a step-by-step guide to building a telemetry adapter, please see the
|
||||
[Open MCT Tutorials](https://github.com/nasa/openmct-tutorial).
|
||||
@ -355,7 +414,7 @@ attribute | type | flags | notes
|
||||
--- | --- | --- | ---
|
||||
`key` | string | required | unique identifier for this field.
|
||||
`hints` | object | required | Hints allow views to intelligently select relevant attributes for display, and are required for most views to function. See section on "Value Hints" below.
|
||||
`name` | string | optional | a human readible label for this field. If omitted, defaults to `key`.
|
||||
`name` | string | optional | a human readable label for this field. If omitted, defaults to `key`.
|
||||
`source` | string | optional | identifies the property of a datum where this value is stored. If omitted, defaults to `key`.
|
||||
`format` | string | optional | a specific format identifier, mapping to a formatter. If omitted, uses a default formatter. For enumerations, use `enum`. For timestamps, use `utc` if you are using utc dates, otherwise use a key mapping to your custom date format.
|
||||
`units` | string | optional | the units of this value, e.g. `km`, `seconds`, `parsecs`
|
||||
@ -383,14 +442,18 @@ In order for the time conductor to work, there will always be an active "time sy
|
||||
|
||||
#### Telemetry Providers
|
||||
|
||||
Telemetry providers are responsible for providing historical and real time telemetry data for telemetry objects. Each telemetry provider determines which objects it can provide telemetry for, and then must implement methods to provide telemetry for those objects.
|
||||
Telemetry providers are responsible for providing historical and real-time telemetry data for telemetry objects. Each telemetry provider determines which objects it can provide telemetry for, and then must implement methods to provide telemetry for those objects.
|
||||
|
||||
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. For more request properties, see Request Properties below.
|
||||
* `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.
|
||||
* `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.
|
||||
* `getLimitEvaluator(domainObject)` required if `supportsLimits` is implemented. Must return a valid LimitEvaluator for a given domain object.
|
||||
|
||||
Telemetry providers are registered by calling `openmct.telemetry.addProvider(provider)`, e.g.
|
||||
|
||||
@ -398,14 +461,15 @@ Telemetry providers are registered by calling `openmct.telemetry.addProvider(pro
|
||||
openmct.telemetry.addProvider({
|
||||
supportsRequest: function (domainObject, options) { /*...*/ },
|
||||
request: function (domainObject, options) { /*...*/ },
|
||||
supportsSubscribe: function (domainObject, callback, options) { /*...*/ },
|
||||
subscribe: function (domainObject, callback, options) { /*...*/ }
|
||||
})
|
||||
```
|
||||
|
||||
#### Telemetry Requests
|
||||
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 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:
|
||||
|
||||
```javascript
|
||||
{
|
||||
start: 1487981997240,
|
||||
@ -414,7 +478,53 @@ Telemetry requests support time bounded queries. A call to a _Telemetry Provider
|
||||
}
|
||||
```
|
||||
|
||||
#### Telemetry Formats
|
||||
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.
|
||||
|
||||
#### Request Strategies **draft**
|
||||
|
||||
To improve performance views may request a certain strategy for data reduction. These are intended to improve visualization performance by reducing the amount of data needed to be sent to the client. These strategies will be indicated by additional parameters in the request options. You may choose to handle them or ignore them.
|
||||
|
||||
Note: these strategies are currently being tested in core plugins and may change based on developer feedback.
|
||||
|
||||
##### `latest` request strategy
|
||||
|
||||
This request is a "depth based" strategy. When a view is only capable of
|
||||
displaying a single value (or perhaps the last ten values), then it can
|
||||
use the `latest` request strategy with a `size` parameter that specifies
|
||||
the number of results it desires. The `size` parameter is a hint; views
|
||||
must not assume the response will have the exact number of results requested.
|
||||
|
||||
example:
|
||||
```javascript
|
||||
{
|
||||
start: 1487981997240,
|
||||
end: 1487982897240,
|
||||
domain: 'utc',
|
||||
strategy: 'latest',
|
||||
size: 1
|
||||
}
|
||||
```
|
||||
|
||||
This strategy says "I want the lastest data point in this time range". A provider which recognizes this request should return only one value-- the latest-- in the requested time range. Depending on your back-end implementation, performing these queries in bulk can be a large performance increase. These are generally issued by views that are only capable of displaying a single value and only need to show the latest value.
|
||||
|
||||
##### `minmax` request strategy
|
||||
|
||||
example:
|
||||
```javascript
|
||||
{
|
||||
start: 1487981997240,
|
||||
end: 1487982897240,
|
||||
domain: 'utc',
|
||||
strategy: 'minmax',
|
||||
size: 720
|
||||
}
|
||||
```
|
||||
|
||||
MinMax queries are issued by plots, and may be issued by other types as well. The aim is to reduce the amount of data returned but still faithfully represent the full extent of the data. In order to do this, the view calculates the maximum data resolution it can display (i.e. the number of horizontal pixels in a plot) and sends that as the `size`. The response should include at least one minimum and one maximum value per point of resolution.
|
||||
|
||||
#### Telemetry Formats **draft**
|
||||
|
||||
Telemetry format objects define how to interpret and display telemetry data.
|
||||
They have a simple structure:
|
||||
@ -484,6 +594,17 @@ The key-value pairs of this object are described by the telemetry metadata of
|
||||
a domain object, as discussed in the [Telemetry Metadata](#telemetry-metadata)
|
||||
section.
|
||||
|
||||
#### Limit Evaluators **draft**
|
||||
|
||||
Limit evaluators allow a telemetry integrator to define how limits should be
|
||||
applied to telemetry from a given domain object. For an example of a limit
|
||||
evaluator, take a look at `examples/generator/SinewaveLimitProvider.js`.
|
||||
|
||||
### Telemetry Consumer APIs **draft**
|
||||
|
||||
The APIs for requesting telemetry from Open MCT -- e.g. for use in custom views -- are currently in draft state and are being revised. If you'd like to experiement with them before they are finalized, please contact the team via the contact-us link on our website.
|
||||
|
||||
|
||||
## Time API
|
||||
|
||||
Open MCT provides API for managing the temporal state of the application.
|
||||
@ -591,7 +712,7 @@ openmct.time.bounds({start: now - ONE_HOUR, now);
|
||||
To respond to bounds change events, listen for the [`'bounds'`](#time-events)
|
||||
event.
|
||||
|
||||
## Clocks
|
||||
### Clocks
|
||||
|
||||
The Time API can be set to follow a clock source which will cause the bounds
|
||||
to be updated automatically whenever the clock source "ticks". A clock is simply
|
||||
@ -610,7 +731,7 @@ be defined to tick on some remote timing source.
|
||||
The values provided by clocks are simple `number`s, which are interpreted in the
|
||||
context of the active [Time System](#defining-and-registering-time-systems).
|
||||
|
||||
### Defining and registering clocks
|
||||
#### Defining and registering clocks
|
||||
|
||||
A clock is an object that defines certain required metadata and functions:
|
||||
|
||||
@ -724,7 +845,7 @@ __Note:__ Setting the clock offsets will trigger an immediate bounds change, as
|
||||
new bounds will be calculated based on the `currentValue()` of the active clock.
|
||||
Clock offsets are only relevant when a clock source is active.
|
||||
|
||||
## Time Events
|
||||
### Time Events
|
||||
|
||||
The Time API is a standard event emitter; you can register callbacks for events using the `on` method and remove callbacks for events with the `off` method.
|
||||
|
||||
@ -766,7 +887,7 @@ The events emitted by the Time API are:
|
||||
* `clockOffsets`: The new [clock offsets](#clock-offsets).
|
||||
|
||||
|
||||
## The Time Conductor
|
||||
### The Time Conductor
|
||||
|
||||
The Time Conductor provides a user interface for managing time bounds in Open
|
||||
MCT. It allows a user to select from configured time systems and clocks, and to set bounds and clock offsets.
|
||||
|
108
example/generator/GeneratorMetadataProvider.js
Normal file
108
example/generator/GeneratorMetadataProvider.js
Normal file
@ -0,0 +1,108 @@
|
||||
define([
|
||||
'lodash'
|
||||
], function (
|
||||
_
|
||||
) {
|
||||
|
||||
var METADATA_BY_TYPE = {
|
||||
'generator': {
|
||||
values: [
|
||||
{
|
||||
key: "name",
|
||||
name: "Name"
|
||||
},
|
||||
{
|
||||
key: "utc",
|
||||
name: "Time",
|
||||
format: "utc",
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "yesterday",
|
||||
name: "Yesterday",
|
||||
format: "utc",
|
||||
hints: {
|
||||
domain: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "sin",
|
||||
name: "Sine",
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "cos",
|
||||
name: "Cosine",
|
||||
hints: {
|
||||
range: 2
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
'example.state-generator': {
|
||||
values: [
|
||||
{
|
||||
key: "name",
|
||||
name: "Name"
|
||||
},
|
||||
{
|
||||
key: "utc",
|
||||
name: "Time",
|
||||
format: "utc",
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "state",
|
||||
source: "value",
|
||||
name: "State",
|
||||
format: "enum",
|
||||
enumerations: [
|
||||
{
|
||||
value: 0,
|
||||
string: "OFF"
|
||||
},
|
||||
{
|
||||
value: 1,
|
||||
string: "ON"
|
||||
}
|
||||
],
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "value",
|
||||
name: "Value",
|
||||
hints: {
|
||||
range: 2
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
function GeneratorMetadataProvider() {
|
||||
|
||||
}
|
||||
|
||||
GeneratorMetadataProvider.prototype.supportsMetadata = function (domainObject) {
|
||||
return METADATA_BY_TYPE.hasOwnProperty(domainObject.type);
|
||||
};
|
||||
|
||||
GeneratorMetadataProvider.prototype.getMetadata = function (domainObject) {
|
||||
return _.extend(
|
||||
{},
|
||||
domainObject.telemetry,
|
||||
METADATA_BY_TYPE[domainObject.type]
|
||||
);
|
||||
};
|
||||
|
||||
return GeneratorMetadataProvider;
|
||||
|
||||
});
|
@ -66,7 +66,7 @@ define([
|
||||
if (request && request.hasOwnProperty(prop)) {
|
||||
workerRequest[prop] = request[prop];
|
||||
}
|
||||
if (!workerRequest[prop]) {
|
||||
if (!workerRequest.hasOwnProperty(prop)) {
|
||||
workerRequest[prop] = REQUEST_DEFAULTS[prop];
|
||||
}
|
||||
workerRequest[prop] = Number(workerRequest[prop]);
|
||||
|
@ -1,87 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
var RED = 0.9,
|
||||
YELLOW = 0.5,
|
||||
LIMITS = {
|
||||
rh: {
|
||||
cssClass: "s-limit-upr s-limit-red",
|
||||
low: RED,
|
||||
high: Number.POSITIVE_INFINITY,
|
||||
name: "Red High"
|
||||
},
|
||||
rl: {
|
||||
cssClass: "s-limit-lwr s-limit-red",
|
||||
high: -RED,
|
||||
low: Number.NEGATIVE_INFINITY,
|
||||
name: "Red Low"
|
||||
},
|
||||
yh: {
|
||||
cssClass: "s-limit-upr s-limit-yellow",
|
||||
low: YELLOW,
|
||||
high: RED,
|
||||
name: "Yellow High"
|
||||
},
|
||||
yl: {
|
||||
cssClass: "s-limit-lwr s-limit-yellow",
|
||||
low: -RED,
|
||||
high: -YELLOW,
|
||||
name: "Yellow Low"
|
||||
}
|
||||
};
|
||||
|
||||
function SinewaveLimitCapability(domainObject) {
|
||||
return {
|
||||
limits: function (range) {
|
||||
return LIMITS;
|
||||
},
|
||||
evaluate: function (datum, range) {
|
||||
range = range || 'sin';
|
||||
if (datum[range] > RED) {
|
||||
return LIMITS.rh;
|
||||
}
|
||||
if (datum[range] < -RED) {
|
||||
return LIMITS.rl;
|
||||
}
|
||||
if (datum[range] > YELLOW) {
|
||||
return LIMITS.yh;
|
||||
}
|
||||
if (datum[range] < -YELLOW) {
|
||||
return LIMITS.yl;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
SinewaveLimitCapability.appliesTo = function (model) {
|
||||
return model.type === 'generator';
|
||||
};
|
||||
|
||||
return SinewaveLimitCapability;
|
||||
}
|
||||
);
|
88
example/generator/SinewaveLimitProvider.js
Normal file
88
example/generator/SinewaveLimitProvider.js
Normal file
@ -0,0 +1,88 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
define([
|
||||
|
||||
], function (
|
||||
|
||||
) {
|
||||
|
||||
var RED = 0.9,
|
||||
YELLOW = 0.5,
|
||||
LIMITS = {
|
||||
rh: {
|
||||
cssClass: "s-limit-upr s-limit-red",
|
||||
low: RED,
|
||||
high: Number.POSITIVE_INFINITY,
|
||||
name: "Red High"
|
||||
},
|
||||
rl: {
|
||||
cssClass: "s-limit-lwr s-limit-red",
|
||||
high: -RED,
|
||||
low: Number.NEGATIVE_INFINITY,
|
||||
name: "Red Low"
|
||||
},
|
||||
yh: {
|
||||
cssClass: "s-limit-upr s-limit-yellow",
|
||||
low: YELLOW,
|
||||
high: RED,
|
||||
name: "Yellow High"
|
||||
},
|
||||
yl: {
|
||||
cssClass: "s-limit-lwr s-limit-yellow",
|
||||
low: -RED,
|
||||
high: -YELLOW,
|
||||
name: "Yellow Low"
|
||||
}
|
||||
};
|
||||
|
||||
function SinewaveLimitProvider() {
|
||||
|
||||
}
|
||||
|
||||
SinewaveLimitProvider.prototype.supportsLimits = function (domainObject) {
|
||||
return domainObject.type === 'generator';
|
||||
};
|
||||
|
||||
SinewaveLimitProvider.prototype.getLimitEvaluator = function (domainObject) {
|
||||
return {
|
||||
evaluate: function (datum, valueMetadata) {
|
||||
var range = valueMetadata ? valueMetadata.key : 'sin'
|
||||
if (datum[range] > RED) {
|
||||
return LIMITS.rh;
|
||||
}
|
||||
if (datum[range] < -RED) {
|
||||
return LIMITS.rl;
|
||||
}
|
||||
if (datum[range] > YELLOW) {
|
||||
return LIMITS.yh;
|
||||
}
|
||||
if (datum[range] < -YELLOW) {
|
||||
return LIMITS.yl;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
return SinewaveLimitProvider;
|
||||
});
|
@ -23,31 +23,17 @@
|
||||
|
||||
define([
|
||||
"./GeneratorProvider",
|
||||
"./SinewaveLimitCapability",
|
||||
"./StateGeneratorProvider"
|
||||
"./SinewaveLimitProvider",
|
||||
"./StateGeneratorProvider",
|
||||
"./GeneratorMetadataProvider"
|
||||
], function (
|
||||
GeneratorProvider,
|
||||
SinewaveLimitCapability,
|
||||
StateGeneratorProvider
|
||||
SinewaveLimitProvider,
|
||||
StateGeneratorProvider,
|
||||
GeneratorMetadataProvider
|
||||
) {
|
||||
|
||||
var legacyExtensions = {
|
||||
"capabilities": [
|
||||
{
|
||||
"key": "limit",
|
||||
"implementation": SinewaveLimitCapability
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
return function(openmct){
|
||||
//Register legacy extensions for things not yet supported by the new API
|
||||
Object.keys(legacyExtensions).forEach(function (type){
|
||||
var extensionsOfType = legacyExtensions[type];
|
||||
extensionsOfType.forEach(function (extension) {
|
||||
openmct.legacyExtension(type, extension)
|
||||
})
|
||||
});
|
||||
|
||||
openmct.types.addType("example.state-generator", {
|
||||
name: "State Generator",
|
||||
@ -70,47 +56,7 @@ define([
|
||||
],
|
||||
initialize: function (object) {
|
||||
object.telemetry = {
|
||||
duration: 5,
|
||||
values: [
|
||||
{
|
||||
key: "name",
|
||||
name: "Name"
|
||||
},
|
||||
{
|
||||
key: "utc",
|
||||
name: "Time",
|
||||
format: "utc",
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "state",
|
||||
source: "value",
|
||||
name: "State",
|
||||
format: "enum",
|
||||
enumerations: [
|
||||
{
|
||||
value: 0,
|
||||
string: "OFF"
|
||||
},
|
||||
{
|
||||
value: 1,
|
||||
string: "ON"
|
||||
}
|
||||
],
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "value",
|
||||
name: "Value",
|
||||
hints: {
|
||||
range: 2
|
||||
}
|
||||
}
|
||||
]
|
||||
duration: 5
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -125,63 +71,58 @@ define([
|
||||
form: [
|
||||
{
|
||||
name: "Period",
|
||||
control: "textfield",
|
||||
control: "numberfield",
|
||||
cssClass: "l-input-sm l-numeric",
|
||||
key: "period",
|
||||
required: true,
|
||||
property: [
|
||||
"telemetry",
|
||||
"period"
|
||||
],
|
||||
pattern: "^\\d*(\\.\\d*)?$"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Amplitude",
|
||||
control: "textfield",
|
||||
control: "numberfield",
|
||||
cssClass: "l-input-sm l-numeric",
|
||||
key: "amplitude",
|
||||
required: true,
|
||||
property: [
|
||||
"telemetry",
|
||||
"amplitude"
|
||||
],
|
||||
pattern: "^\\d*(\\.\\d*)?$"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Offset",
|
||||
control: "textfield",
|
||||
control: "numberfield",
|
||||
cssClass: "l-input-sm l-numeric",
|
||||
key: "offset",
|
||||
required: true,
|
||||
property: [
|
||||
"telemetry",
|
||||
"offset"
|
||||
],
|
||||
pattern: "^\\d*(\\.\\d*)?$"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Data Rate (hz)",
|
||||
control: "textfield",
|
||||
control: "numberfield",
|
||||
cssClass: "l-input-sm l-numeric",
|
||||
key: "dataRateInHz",
|
||||
required: true,
|
||||
property: [
|
||||
"telemetry",
|
||||
"dataRateInHz"
|
||||
],
|
||||
pattern: "^\\d*(\\.\\d*)?$"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Phase (radians)",
|
||||
control: "textfield",
|
||||
control: "numberfield",
|
||||
cssClass: "l-input-sm l-numeric",
|
||||
key: "phase",
|
||||
required: true,
|
||||
property: [
|
||||
"telemetry",
|
||||
"phase"
|
||||
],
|
||||
pattern: "^\\d*(\\.\\d*)?$"
|
||||
]
|
||||
}
|
||||
],
|
||||
initialize: function (object) {
|
||||
@ -190,48 +131,14 @@ define([
|
||||
amplitude: 1,
|
||||
offset: 0,
|
||||
dataRateInHz: 1,
|
||||
phase: 0,
|
||||
values: [
|
||||
{
|
||||
key: "name",
|
||||
name: "Name"
|
||||
},
|
||||
{
|
||||
key: "utc",
|
||||
name: "Time",
|
||||
format: "utc",
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "yesterday",
|
||||
name: "Yesterday",
|
||||
format: "utc",
|
||||
hints: {
|
||||
domain: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "sin",
|
||||
name: "Sine",
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "cos",
|
||||
name: "Cosine",
|
||||
hints: {
|
||||
range: 2
|
||||
}
|
||||
}
|
||||
]
|
||||
phase: 0
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
openmct.telemetry.addProvider(new GeneratorProvider());
|
||||
openmct.telemetry.addProvider(new GeneratorMetadataProvider());
|
||||
openmct.telemetry.addProvider(new SinewaveLimitProvider());
|
||||
};
|
||||
|
||||
});
|
||||
|
@ -101,11 +101,11 @@
|
||||
padding-top: 1em;
|
||||
|
||||
.cols {
|
||||
@include display(flex);
|
||||
@include flex-direction(row);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.col {
|
||||
@include flex(1 1 auto);
|
||||
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%;
|
||||
@include transform(translateX(-50%));
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
22
package.json
22
package.json
@ -1,18 +1,18 @@
|
||||
{
|
||||
"name": "openmct",
|
||||
"version": "0.13.3-SNAPSHOT",
|
||||
"version": "0.14.0-SNAPSHOT",
|
||||
"description": "The Open MCT core platform",
|
||||
"dependencies": {
|
||||
"d3-array": "^1.0.2",
|
||||
"d3-axis": "^1.0.4",
|
||||
"d3-collection": "^1.0.2",
|
||||
"d3-color": "^1.0.2",
|
||||
"d3-format": "^1.0.2",
|
||||
"d3-interpolate": "^1.1.3",
|
||||
"d3-scale": "^1.0.4",
|
||||
"d3-selection": "^1.0.3",
|
||||
"d3-time": "^1.0.4",
|
||||
"d3-time-format": "^2.0.3",
|
||||
"d3-array": "1.2.x",
|
||||
"d3-axis": "1.0.x",
|
||||
"d3-collection": "1.0.x",
|
||||
"d3-color": "1.0.x",
|
||||
"d3-format": "1.2.x",
|
||||
"d3-interpolate": "1.1.x",
|
||||
"d3-scale": "1.0.x",
|
||||
"d3-selection": "1.3.x",
|
||||
"d3-time": "1.0.x",
|
||||
"d3-time-format": "2.1.x",
|
||||
"express": "^4.13.1",
|
||||
"minimist": "^1.1.1",
|
||||
"request": "^2.69.0",
|
||||
|
@ -186,9 +186,8 @@ define([
|
||||
"key": "remove",
|
||||
"category": "contextual",
|
||||
"implementation": RemoveAction,
|
||||
"cssClass": "icon-trash",
|
||||
"name": "Remove",
|
||||
"description": "Remove this object from its containing object.",
|
||||
"name": "Delete...",
|
||||
"description": "Delete this object.",
|
||||
"depends": [
|
||||
"navigationService"
|
||||
]
|
||||
|
@ -88,12 +88,6 @@ 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) {
|
||||
@ -110,6 +104,7 @@ 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.
|
||||
@ -130,26 +125,23 @@ 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 || {},
|
||||
self = this;
|
||||
var definition = (representation || {}).toolbar || {};
|
||||
|
||||
// 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();
|
||||
}
|
||||
// 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();
|
||||
}
|
||||
|
||||
// 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([]);
|
||||
// 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());
|
||||
};
|
||||
|
||||
// Destroy; remove toolbar object from parent scope
|
||||
|
@ -38,24 +38,37 @@ define(
|
||||
* @memberof platform/commonUI/edit
|
||||
* @constructor
|
||||
*/
|
||||
function EditToolbarSelection(openmct) {
|
||||
function EditToolbarSelection($scope, openmct) {
|
||||
this.selection = [{}];
|
||||
this.selecting = false;
|
||||
this.selectedObj = undefined;
|
||||
this.openmct = openmct;
|
||||
var self = this;
|
||||
|
||||
openmct.selection.on('change', function (selection) {
|
||||
function setSelection(selection) {
|
||||
var selected = selection[0];
|
||||
|
||||
if (selected && selected.context.toolbar) {
|
||||
this.select(selected.context.toolbar);
|
||||
self.select(selected.context.toolbar);
|
||||
} else {
|
||||
this.deselect();
|
||||
self.deselect();
|
||||
}
|
||||
|
||||
if (selected && selected.context.viewProxy) {
|
||||
this.proxy(selected.context.viewProxy);
|
||||
self.proxy(selected.context.viewProxy);
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -36,7 +36,7 @@ define(
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpyObj(
|
||||
'$scope',
|
||||
['$on', '$watch', '$watchCollection', "commit"]
|
||||
['$on', '$watch', '$watchCollection', "commit", "$apply"]
|
||||
);
|
||||
mockElement = {};
|
||||
testAttrs = { toolbar: 'testToolbar' };
|
||||
|
@ -30,7 +30,8 @@ define(
|
||||
otherElement,
|
||||
selection,
|
||||
mockSelection,
|
||||
mockOpenMCT;
|
||||
mockOpenMCT,
|
||||
mockScope;
|
||||
|
||||
beforeEach(function () {
|
||||
testProxy = { someKey: "some value" };
|
||||
@ -46,7 +47,12 @@ define(
|
||||
mockOpenMCT = {
|
||||
selection: mockSelection
|
||||
};
|
||||
selection = new EditToolbarSelection(mockOpenMCT);
|
||||
mockScope = jasmine.createSpyObj('$scope', [
|
||||
'$on',
|
||||
'$apply'
|
||||
]);
|
||||
|
||||
selection = new EditToolbarSelection(mockScope, mockOpenMCT);
|
||||
selection.proxy(testProxy);
|
||||
});
|
||||
|
||||
@ -103,6 +109,20 @@ 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)
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -1,91 +1,91 @@
|
||||
@include keyframes(rotation) {
|
||||
100% { @include transform(rotate(360deg)); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@include keyframes(rotation-centered) {
|
||||
0% { @include transform(translate(-50%, -50%) rotate(0deg)); }
|
||||
100% { @include transform(translate(-50%, -50%) rotate(360deg)); }
|
||||
0% { transform: translate(-50%, -50%) rotate(0deg); }
|
||||
100% { transform: translate(-50%, -50%) rotate(360deg); }
|
||||
}
|
||||
|
||||
@include keyframes(clock-hands) {
|
||||
0% { @include transform(translate(-50%, -50%) rotate(0deg)); }
|
||||
100% { @include transform(translate(-50%, -50%) rotate(360deg)); }
|
||||
0% { transform: translate(-50%, -50%) rotate(0deg); }
|
||||
100% { transform: translate(-50%, -50%) rotate(360deg); }
|
||||
}
|
||||
|
||||
@include keyframes(clock-hands-sticky) {
|
||||
0% {
|
||||
@include transform(translate(-50%, -50%) rotate(0deg));
|
||||
transform: translate(-50%, -50%) rotate(0deg);
|
||||
}
|
||||
7% {
|
||||
@include transform(translate(-50%, -50%) rotate(0deg));
|
||||
transform: translate(-50%, -50%) rotate(0deg);
|
||||
}
|
||||
8% {
|
||||
@include transform(translate(-50%, -50%) rotate(30deg));
|
||||
transform: translate(-50%, -50%) rotate(30deg);
|
||||
}
|
||||
15% {
|
||||
@include transform(translate(-50%, -50%) rotate(30deg));
|
||||
transform: translate(-50%, -50%) rotate(30deg);
|
||||
}
|
||||
16% {
|
||||
@include transform(translate(-50%, -50%) rotate(60deg));
|
||||
transform: translate(-50%, -50%) rotate(60deg);
|
||||
}
|
||||
24% {
|
||||
@include transform(translate(-50%, -50%) rotate(60deg));
|
||||
transform: translate(-50%, -50%) rotate(60deg);
|
||||
}
|
||||
25% {
|
||||
@include transform(translate(-50%, -50%) rotate(90deg));
|
||||
transform: translate(-50%, -50%) rotate(90deg);
|
||||
}
|
||||
32% {
|
||||
@include transform(translate(-50%, -50%) rotate(90deg));
|
||||
transform: translate(-50%, -50%) rotate(90deg);
|
||||
}
|
||||
33% {
|
||||
@include transform(translate(-50%, -50%) rotate(120deg));
|
||||
transform: translate(-50%, -50%) rotate(120deg);
|
||||
}
|
||||
40% {
|
||||
@include transform(translate(-50%, -50%) rotate(120deg));
|
||||
transform: translate(-50%, -50%) rotate(120deg);
|
||||
}
|
||||
41% {
|
||||
@include transform(translate(-50%, -50%) rotate(150deg));
|
||||
transform: translate(-50%, -50%) rotate(150deg);
|
||||
}
|
||||
49% {
|
||||
@include transform(translate(-50%, -50%) rotate(150deg));
|
||||
transform: translate(-50%, -50%) rotate(150deg);
|
||||
}
|
||||
50% {
|
||||
@include transform(translate(-50%, -50%) rotate(180deg));
|
||||
transform: translate(-50%, -50%) rotate(180deg);
|
||||
}
|
||||
57% {
|
||||
@include transform(translate(-50%, -50%) rotate(180deg));
|
||||
transform: translate(-50%, -50%) rotate(180deg);
|
||||
}
|
||||
58% {
|
||||
@include transform(translate(-50%, -50%) rotate(210deg));
|
||||
transform: translate(-50%, -50%) rotate(210deg);
|
||||
}
|
||||
65% {
|
||||
@include transform(translate(-50%, -50%) rotate(210deg));
|
||||
transform: translate(-50%, -50%) rotate(210deg);
|
||||
}
|
||||
66% {
|
||||
@include transform(translate(-50%, -50%) rotate(240deg));
|
||||
transform: translate(-50%, -50%) rotate(240deg);
|
||||
}
|
||||
74% {
|
||||
@include transform(translate(-50%, -50%) rotate(240deg));
|
||||
transform: translate(-50%, -50%) rotate(240deg);
|
||||
}
|
||||
75% {
|
||||
@include transform(translate(-50%, -50%) rotate(270deg));
|
||||
transform: translate(-50%, -50%) rotate(270deg);
|
||||
}
|
||||
82% {
|
||||
@include transform(translate(-50%, -50%) rotate(270deg));
|
||||
transform: translate(-50%, -50%) rotate(270deg);
|
||||
}
|
||||
83% {
|
||||
@include transform(translate(-50%, -50%) rotate(300deg));
|
||||
transform: translate(-50%, -50%) rotate(300deg);
|
||||
}
|
||||
90% {
|
||||
@include transform(translate(-50%, -50%) rotate(300deg));
|
||||
transform: translate(-50%, -50%) rotate(300deg);
|
||||
}
|
||||
91% {
|
||||
@include transform(translate(-50%, -50%) rotate(330deg));
|
||||
transform: translate(-50%, -50%) rotate(330deg);
|
||||
}
|
||||
99% {
|
||||
@include transform(translate(-50%, -50%) rotate(330deg));
|
||||
transform: translate(-50%, -50%) rotate(330deg);
|
||||
}
|
||||
100% {
|
||||
@include transform(translate(-50%, -50%) rotate(360deg));
|
||||
transform: translate(-50%, -50%) rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
11
platform/commonUI/general/res/sass/_app-start.scss
Normal file
11
platform/commonUI/general/res/sass/_app-start.scss
Normal file
@ -0,0 +1,11 @@
|
||||
// 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;
|
||||
}
|
@ -94,19 +94,19 @@
|
||||
/********************************************* FLEX STYLES */
|
||||
.l-flex-row,
|
||||
.l-flex-col {
|
||||
@include display(flex);
|
||||
@include flex-wrap(nowrap);
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
.flex-elem {
|
||||
min-height: 0; // Needed to allow element to shrink within parent
|
||||
position: relative;
|
||||
&:not(.grows) {
|
||||
@include flex(0 0 auto);
|
||||
flex: 0 0 auto;
|
||||
&.flex-can-shrink {
|
||||
@include flex(0 1 auto);
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
}
|
||||
&.grows {
|
||||
@include flex(1 1 auto);
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
&.contents-align-right {
|
||||
text-align: right;
|
||||
@ -114,25 +114,25 @@
|
||||
}
|
||||
.flex-container {
|
||||
// Apply to wrapping elements, mct-includes, etc.
|
||||
@include display(flex);
|
||||
@include flex-wrap(nowrap);
|
||||
@include flex(1 1 auto);
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
flex: 1 1 auto;
|
||||
min-height:0;
|
||||
}
|
||||
}
|
||||
|
||||
.l-flex-row {
|
||||
@include flex-direction(row);
|
||||
&.flex-elem { @include flex(1 1 auto); }
|
||||
flex-direction: row;
|
||||
&.flex-elem { flex: 1 1 auto; }
|
||||
> .flex-elem {
|
||||
min-width: 0;
|
||||
&.holder:not(:last-child) { margin-right: $interiorMargin; }
|
||||
}
|
||||
.flex-container { @include flex-direction(row); }
|
||||
.flex-container { flex-direction: row; }
|
||||
}
|
||||
|
||||
.l-flex-col {
|
||||
@include flex-direction(column);
|
||||
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 { @include flex-direction(column); }
|
||||
.flex-container { flex-direction: column; }
|
||||
}
|
||||
|
||||
.flex-fixed {
|
||||
@include flex(0 0 auto);
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.flex-justify-end {
|
||||
@include justify-content(flex-end);
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
/********************************************* POPUPS */
|
||||
|
@ -83,8 +83,8 @@
|
||||
height: auto; width: auto;
|
||||
position: absolute;
|
||||
left: 0; top: 0; right: 0; bottom: 20%;
|
||||
@include transform-origin(bottom left);
|
||||
@include transform(scale(0.3));
|
||||
transform-origin: bottom left;
|
||||
transform: scale(0.3);
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +53,6 @@
|
||||
.l-inspector-part {
|
||||
box-sizing: border-box;
|
||||
padding-right: $interiorMargin;
|
||||
|
||||
.tree .form {
|
||||
margin-left: $treeVCW + $interiorMarginLg;
|
||||
}
|
||||
@ -79,8 +78,7 @@
|
||||
}
|
||||
}
|
||||
.form-row {
|
||||
// To be replaced with .inspector-config, see below.
|
||||
@include align-items(center);
|
||||
align-items: center;
|
||||
border: none !important;
|
||||
margin-bottom: 0 !important;
|
||||
padding: $interiorMarginSm 0;
|
||||
@ -101,12 +99,15 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
ul li {
|
||||
margin-bottom: $interiorMarginLg;
|
||||
}
|
||||
|
||||
em.t-inspector-part-header {
|
||||
border-radius: $basicCr;
|
||||
background-color: $colorInspectorSectionHeaderBg;
|
||||
color: $colorInspectorSectionHeaderFg;
|
||||
margin-top: $interiorMarginLg;
|
||||
//margin-bottom: $interiorMargin;
|
||||
margin-bottom: $interiorMargin;
|
||||
padding: floor($formTBPad * .75) $formLRPad;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
@ -200,102 +201,3 @@ 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;
|
||||
}
|
||||
|
||||
|
@ -76,9 +76,11 @@
|
||||
@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";
|
||||
|
@ -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%;
|
||||
@include transform(translateY(-50%));
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
@mixin verticalCenterBlock($holderH, $itemH) {
|
||||
|
@ -22,8 +22,8 @@
|
||||
|
||||
// mct-representation surrounding an object-label key="'label'"
|
||||
.rep-object-label {
|
||||
@include flex-direction(row);
|
||||
@include flex(1 1 auto);
|
||||
flex-direction: row;
|
||||
flex: 1 1 auto;
|
||||
height: inherit;
|
||||
line-height: inherit;
|
||||
min-width: 0;
|
||||
|
@ -55,7 +55,7 @@
|
||||
|
||||
.widget-rule-header {
|
||||
@extend .l-flex-row;
|
||||
@include align-items(center);
|
||||
align-items: center;
|
||||
margin-bottom: $interiorMargin;
|
||||
> .flex-elem {
|
||||
&:not(:first-child) {
|
||||
@ -103,7 +103,7 @@
|
||||
.l-compact-form label {
|
||||
$ruleLabelW: 40%;
|
||||
$ruleLabelMaxW: 150px;
|
||||
@include display(flex);
|
||||
display: flex;
|
||||
max-width: $ruleLabelMaxW;
|
||||
width: $ruleLabelW;
|
||||
}
|
||||
@ -177,8 +177,8 @@
|
||||
ul {
|
||||
&:last-child { margin: 0; }
|
||||
li {
|
||||
@include align-items(flex-start);
|
||||
@include flex-wrap(nowrap);
|
||||
align-items: flex-start;
|
||||
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;
|
||||
@include align-items(center);
|
||||
align-items: center;
|
||||
> span { display: block; }
|
||||
.grippy-holder,
|
||||
.view-control {
|
||||
@ -243,18 +243,18 @@
|
||||
}
|
||||
|
||||
.widget-thumb {
|
||||
@include flex(1 1 auto);
|
||||
flex: 1 1 auto;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.rule-title {
|
||||
@include flex(0 1 auto);
|
||||
flex: 0 1 auto;
|
||||
color: pullForward($colorBodyFg, 50%);
|
||||
}
|
||||
|
||||
.rule-description {
|
||||
@include flex(1 1 auto);
|
||||
flex: 1 1 auto;
|
||||
@include ellipsize();
|
||||
color: pushBack($colorBodyFg, 20%);
|
||||
}
|
||||
|
@ -356,7 +356,7 @@ input[type="text"].s-input-inline,
|
||||
}
|
||||
}
|
||||
&:before {
|
||||
@include transform(translateY(-50%));
|
||||
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;
|
||||
@include flex(0 0 1);
|
||||
flex: 0 0 1;
|
||||
}
|
||||
|
||||
.t-object-alert {
|
||||
@ -675,14 +675,14 @@ input[type="range"] {
|
||||
.l-calendar {
|
||||
$colorMuted: pushBack($colorMenuFg, 30%);
|
||||
ul.l-cal-row {
|
||||
@include display(flex);
|
||||
@include flex-flow(row nowrap);
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
margin-top: 1px;
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
li {
|
||||
@include flex(1 0);
|
||||
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);
|
||||
@include transform-origin(center);
|
||||
transform-origin: center;
|
||||
}
|
||||
&.expanded:before {
|
||||
@include transform(rotate(90deg));
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -157,16 +157,16 @@
|
||||
$lh: $ueFooterH - ($m*2) - 1;
|
||||
box-sizing: border-box;
|
||||
@include ellipsize();
|
||||
@include display(flex);
|
||||
@include flex-direction(row);
|
||||
@include align-items(center);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
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;
|
||||
@include transform(translateX(-50%));
|
||||
transform: translateX(-50%);
|
||||
|
||||
&.minimized {
|
||||
@include transition-property(left, opacity);
|
||||
@ -185,7 +185,7 @@
|
||||
}
|
||||
|
||||
.banner-elem {
|
||||
@include flex(0 1 auto);
|
||||
flex: 0 1 auto;
|
||||
margin-left: $interiorMargin;
|
||||
}
|
||||
a {
|
||||
@ -250,14 +250,14 @@
|
||||
// Archetypal message
|
||||
.l-message {
|
||||
$iconW: 32px;
|
||||
@include display(flex);
|
||||
@include flex-direction(row);
|
||||
@include align-items(stretch);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
padding: $interiorMarginLg;
|
||||
|
||||
&:before {
|
||||
// Icon
|
||||
@include flex(0 1 auto);
|
||||
flex: 0 1 auto;
|
||||
@include txtShdw($shdwStatusIc);
|
||||
@extend .icon-bell;
|
||||
color: $colorStatusDefault;
|
||||
@ -283,9 +283,9 @@
|
||||
|
||||
|
||||
.w-message-contents {
|
||||
@include flex(1 1 auto);
|
||||
@include display(flex);
|
||||
@include flex-direction(column);
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> div,
|
||||
> span {
|
||||
@ -294,7 +294,7 @@
|
||||
}
|
||||
|
||||
.message-body {
|
||||
@include flex(1 1 100%);
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@ -331,8 +331,8 @@
|
||||
// In a list
|
||||
.t-message-list {
|
||||
@include absPosDefault();
|
||||
@include display(flex);
|
||||
@include flex-direction(column);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> div,
|
||||
> span {
|
||||
@ -340,7 +340,7 @@
|
||||
}
|
||||
|
||||
.w-messages {
|
||||
@include flex(1 1 100%);
|
||||
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 {
|
||||
@include flex-direction(column);
|
||||
flex-direction: column;
|
||||
&:before {
|
||||
margin-right: 0;
|
||||
margin-bottom: $interiorMarginLg;
|
||||
|
@ -15,7 +15,7 @@
|
||||
margin-bottom: $interiorMargin;
|
||||
}
|
||||
.l-image-main-controlbar {
|
||||
&.l-flex-row { @include align-items(center); }
|
||||
&.l-flex-row { align-items: center; }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,13 +34,10 @@
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@
|
||||
&.grows {
|
||||
.l-section-body,
|
||||
.form-row {
|
||||
@include flex(1 1 auto);
|
||||
flex: 1 1 auto;
|
||||
.wrapper {
|
||||
height: 100%;
|
||||
}
|
||||
@ -87,7 +87,7 @@
|
||||
.controls {
|
||||
order: 2;
|
||||
position: relative;
|
||||
@include flex(1 1 auto);
|
||||
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 {
|
||||
@include flex(1 1 auto);
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
width: auto;
|
||||
order: 2;
|
||||
}
|
||||
>.controls {
|
||||
@include flex(0 0 auto);
|
||||
flex: 0 0 auto;
|
||||
margin-right: $interiorMargin;
|
||||
order: 1;
|
||||
}
|
||||
@ -141,7 +141,7 @@
|
||||
|
||||
.l-controls-under.l-flex-row {
|
||||
// Change to use column layout
|
||||
@include flex-direction(column);
|
||||
flex-direction: column;
|
||||
.flex-elem {
|
||||
margin-bottom: $interiorMarginLg;
|
||||
}
|
||||
@ -190,19 +190,19 @@
|
||||
ul {
|
||||
margin-bottom: $interiorMarginLg;
|
||||
li {
|
||||
@include display(flex);
|
||||
@include flex-wrap(wrap);
|
||||
@include align-items(center);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
label,
|
||||
.control {
|
||||
@include display(flex);
|
||||
display: flex;
|
||||
}
|
||||
label {
|
||||
line-height: inherit;
|
||||
width: $labelW;
|
||||
}
|
||||
.controls {
|
||||
@include flex-grow(1);
|
||||
flex-grow: 1;
|
||||
margin-left: $interiorMargin;
|
||||
input[type="text"],
|
||||
input[type="search"],
|
||||
@ -232,14 +232,14 @@
|
||||
|
||||
&.controls-first {
|
||||
.control {
|
||||
@include flex-grow(0);
|
||||
flex-grow: 0;
|
||||
margin-right: $interiorMargin;
|
||||
min-width: 0;
|
||||
order: 1;
|
||||
width: auto;
|
||||
}
|
||||
label {
|
||||
@include flex-grow(1);
|
||||
flex-grow: 1;
|
||||
order: 2;
|
||||
width: auto;
|
||||
}
|
||||
|
@ -101,7 +101,7 @@
|
||||
line-height: inherit;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
@include transform(translateY(-50%));
|
||||
transform: translateY(-50%);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
@mixin spinner($b: 5px, $c: $colorKey) {
|
||||
@include transform-origin(center);
|
||||
transform-origin: center;
|
||||
@include animation-name(rotation-centered);
|
||||
@include animation-duration(0.5s);
|
||||
@include animation-iteration-count(infinite);
|
||||
|
@ -85,14 +85,14 @@
|
||||
z-index: 1;
|
||||
.item-type,
|
||||
.t-item-icon {
|
||||
@include transform(translateX(-50%) translateY(-55%));
|
||||
transform: translateX(-50%) translateY(-55%);
|
||||
position: absolute;
|
||||
top: 50%; left: 50%;
|
||||
font-size: $iconD * 0.95;
|
||||
&.l-icon-link {
|
||||
.t-item-icon-glyph {
|
||||
&:after {
|
||||
@include transform(scale(0.25));
|
||||
transform: scale(0.25);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -138,7 +138,7 @@ body.phone.portrait {
|
||||
}
|
||||
.pane.right.items {
|
||||
left: 0 !important;
|
||||
@include transform(translateX($proporMenuOnly));
|
||||
transform: translateX($proporMenuOnly);
|
||||
.holder-object-and-inspector {
|
||||
opacity: 0;
|
||||
}
|
||||
|
@ -34,7 +34,18 @@ body.touch {
|
||||
line-height: $mobileTreeItemH !important;
|
||||
.view-control {
|
||||
font-size: 1em;
|
||||
width: ceil($mobileTreeItemH * 0.5);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
.t-object-label {
|
||||
line-height: inherit;
|
||||
|
@ -89,7 +89,7 @@
|
||||
> .abs.outer-holder {
|
||||
@include desktopandtablet {
|
||||
$max: 1280px;
|
||||
@include transform(translateX(-50%) translateY(-50%));
|
||||
transform: translate(-50%, -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
|
||||
@include flex-direction(column);
|
||||
flex-direction: column;
|
||||
> .flex-elem {
|
||||
&:not(:first-child) {
|
||||
margin-top: $interiorMargin;
|
||||
|
@ -152,8 +152,8 @@
|
||||
&.l-plot-y-label {
|
||||
$x: -50%;
|
||||
$r: -90deg;
|
||||
@include transform-origin(50% 0);
|
||||
@include transform(translateX($x) rotate($r));
|
||||
transform-origin: 50% 0;
|
||||
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 {
|
||||
@include transform(translateX(-50%));
|
||||
transform: translateX(-50%);
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
.gl-plot-y-options {
|
||||
@include transform(translateY(-50%));
|
||||
transform: translateY(-50%);
|
||||
min-width: 150px; // Need this due to enclosure of .select
|
||||
top: 50%;
|
||||
left: $plotYLabelW + $interiorMargin * 2;
|
||||
|
@ -152,7 +152,7 @@
|
||||
opacity: 1;
|
||||
}
|
||||
.load-more-button {
|
||||
@include transform(translateX(-50%));
|
||||
transform: translateX(-50%);
|
||||
display: inline-block;
|
||||
margin-top: $interiorMargin;
|
||||
padding: 0 $interiorMarginLg;
|
||||
|
@ -32,11 +32,7 @@ body, html {
|
||||
}
|
||||
|
||||
.l-splash-holder {
|
||||
// Main outer holder.
|
||||
@include transition-property(opacity);
|
||||
@include transition-duration(500ms);
|
||||
@include transition-timing-function(ease-in-out);
|
||||
@include transition-delay(1s);
|
||||
// Main outer holder for splash.
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
@ -44,16 +40,18 @@ 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);
|
||||
|
@ -142,7 +142,7 @@
|
||||
.l-hyperlink.s-button {
|
||||
.label {
|
||||
@include ellipsize();
|
||||
@include transform(translateY(-50%));
|
||||
transform: translateY(-50%);
|
||||
padding: 0 $interiorMargin;
|
||||
position: absolute;
|
||||
min-width: 0;
|
||||
|
@ -159,20 +159,20 @@ body.desktop .pane .mini-tab-icon.toggle-pane {
|
||||
|
||||
&.toggle-tree.anchor-left {
|
||||
left: 0;
|
||||
@include transform(translateX(-1 * $paneExpandedOffset));
|
||||
transform: translateX(-1 * $paneExpandedOffset);
|
||||
&.collapsed {
|
||||
@include transform(translateX(-1 * $ueCollapsedPaneEdgeM));
|
||||
transform: translateX(-1 * $ueCollapsedPaneEdgeM);
|
||||
}
|
||||
}
|
||||
|
||||
&.toggle-inspect.anchor-right {
|
||||
right: 0;
|
||||
@include transform(translateX($paneExpandedOffset));
|
||||
transform: translateX($paneExpandedOffset);
|
||||
&.flush-right {
|
||||
@include transform(translateX(($uePaneMiniTabW + ceil($splitterD / 2))));
|
||||
transform: translateX(($uePaneMiniTabW + ceil($splitterD / 2)));
|
||||
}
|
||||
&.collapsed {
|
||||
@include transform(translateX($ueCollapsedPaneEdgeM));
|
||||
transform: translateX($ueCollapsedPaneEdgeM);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,10 +40,18 @@ 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) : "");
|
||||
|
||||
|
@ -55,7 +55,7 @@
|
||||
div[class*="hand"] {
|
||||
$handW: 2px;
|
||||
$handH: $d * 0.4;
|
||||
@include transform(translate(-50%, -50%));
|
||||
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 {
|
||||
@include justify-content(flex-end);
|
||||
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 {
|
||||
@include transform(rotate(120deg));
|
||||
transform: rotate(120deg);
|
||||
}
|
||||
}
|
||||
.mode-selector .s-menu-button:before {
|
||||
|
@ -29,7 +29,7 @@ mct-include.l-toi-holder,
|
||||
|
||||
mct-include.l-toi-holder {
|
||||
$blockerFadeW: $toiBlockerFadeW;
|
||||
@include transform(translateX(-50%));
|
||||
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;
|
||||
@include transform(translateY(-50%)); top: 50%;
|
||||
transform: translateY(-50%); top: 50%;
|
||||
padding: $toiPad;
|
||||
z-index: 1;
|
||||
|
||||
@ -207,7 +207,7 @@ table {
|
||||
border-radius: 20%;
|
||||
height: auto;
|
||||
padding: $toiPad;
|
||||
@include transform(translate(-50%, -50%));
|
||||
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 {
|
||||
@include transform(translateY(100%));
|
||||
transform: translateY(100%);
|
||||
|
||||
}
|
||||
|
||||
@ -257,13 +257,13 @@ table {
|
||||
}
|
||||
|
||||
&:before {
|
||||
@include transform(translate(-50%, $linesVOffset * -1));
|
||||
transform: translate(-50%, $linesVOffset * -1);
|
||||
top: 0px;
|
||||
bottom: auto;
|
||||
}
|
||||
|
||||
&:after {
|
||||
@include transform(translate(-50%, $linesVOffset));
|
||||
transform: translate(-50%, $linesVOffset);
|
||||
top: auto;
|
||||
bottom: 0px;
|
||||
}
|
||||
|
@ -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,34 +107,13 @@ define(
|
||||
if (this.requestCount > requestId) {
|
||||
return Promise.resolve('Stale request');
|
||||
}
|
||||
|
||||
values.forEach(function (datum) {
|
||||
this.updateHistory(datum);
|
||||
}, this);
|
||||
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));
|
||||
this.updateValues(values[values.length - 1]);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
ImageryController.prototype.stopListening = function () {
|
||||
|
@ -59,7 +59,8 @@ define(
|
||||
'timeSystem',
|
||||
'clock',
|
||||
'on',
|
||||
'off'
|
||||
'off',
|
||||
'bounds'
|
||||
]),
|
||||
telemetry: jasmine.createSpyObj('telemetryAPI', [
|
||||
'subscribe',
|
||||
@ -118,7 +119,8 @@ define(
|
||||
|
||||
describe("when loaded", function () {
|
||||
var callback,
|
||||
boundsListener;
|
||||
boundsListener,
|
||||
bounds;
|
||||
|
||||
beforeEach(function () {
|
||||
waitsFor(function () {
|
||||
@ -137,13 +139,9 @@ define(
|
||||
});
|
||||
});
|
||||
|
||||
it("uses LAD telemetry", function () {
|
||||
it("requests history", function () {
|
||||
expect(openmct.telemetry.request).toHaveBeenCalledWith(
|
||||
newDomainObject,
|
||||
{
|
||||
strategy: 'latest',
|
||||
size: 1
|
||||
}
|
||||
newDomainObject, bounds
|
||||
);
|
||||
expect(controller.getTime()).toEqual(prefix + 1434600258123);
|
||||
expect(controller.getImageUrl()).toEqual('some/url');
|
||||
@ -204,7 +202,7 @@ define(
|
||||
it("requests telemetry", function () {
|
||||
expect(openmct.telemetry.request).toHaveBeenCalledWith(
|
||||
newDomainObject,
|
||||
jasmine.any(Object)
|
||||
bounds
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -315,6 +315,8 @@ 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());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -464,7 +466,7 @@ define(
|
||||
|
||||
function filterForTelemetryObjects(objects) {
|
||||
return objects.filter(function (object) {
|
||||
return self.openmct.telemetry.canProvideTelemetry(object);
|
||||
return self.openmct.telemetry.isTelemetryObject(object);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -150,13 +150,13 @@ define(
|
||||
[
|
||||
'subscribe',
|
||||
'request',
|
||||
'canProvideTelemetry',
|
||||
'isTelemetryObject',
|
||||
'getMetadata',
|
||||
'limitEvaluator',
|
||||
'getValueFormatter'
|
||||
]
|
||||
);
|
||||
mockTelemetryAPI.canProvideTelemetry.andReturn(true);
|
||||
mockTelemetryAPI.isTelemetryObject.andReturn(true);
|
||||
mockTelemetryAPI.request.andReturn(Promise.resolve([]));
|
||||
|
||||
testGrid = [123, 456];
|
||||
@ -201,7 +201,7 @@ define(
|
||||
'off',
|
||||
'get'
|
||||
]);
|
||||
mockSelection.get.andCallThrough();
|
||||
mockSelection.get.andReturn([]);
|
||||
|
||||
mockOpenMCT = {
|
||||
time: mockConductor,
|
||||
@ -596,7 +596,7 @@ define(
|
||||
expect(controller.getSelectedElementStyle()).not.toEqual(oldStyle);
|
||||
});
|
||||
|
||||
it("cleans up slection on scope destroy", function () {
|
||||
it("cleans up selection on scope destroy", function () {
|
||||
expect(mockScope.$on).toHaveBeenCalledWith(
|
||||
'$destroy',
|
||||
jasmine.any(Function)
|
||||
|
@ -399,14 +399,14 @@ define(
|
||||
var compositionApi = this.openmct.composition;
|
||||
|
||||
function filterForTelemetry(objects) {
|
||||
return objects.filter(telemetryApi.canProvideTelemetry.bind(telemetryApi));
|
||||
return objects.filter(telemetryApi.isTelemetryObject.bind(telemetryApi));
|
||||
}
|
||||
|
||||
/*
|
||||
* If parent object is a telemetry object, subscribe to it. Do not
|
||||
* test composees.
|
||||
*/
|
||||
if (telemetryApi.canProvideTelemetry(this.domainObject)) {
|
||||
if (telemetryApi.isTelemetryObject(this.domainObject)) {
|
||||
return Promise.resolve([this.domainObject]);
|
||||
} else {
|
||||
/*
|
||||
|
@ -91,7 +91,7 @@ define(
|
||||
mockScope.domainObject = mockDomainObject;
|
||||
|
||||
mockTelemetryAPI = jasmine.createSpyObj("telemetryAPI", [
|
||||
"canProvideTelemetry",
|
||||
"isTelemetryObject",
|
||||
"subscribe",
|
||||
"getMetadata",
|
||||
"commonValuesForHints",
|
||||
@ -117,7 +117,7 @@ define(
|
||||
return formatter;
|
||||
});
|
||||
|
||||
mockTelemetryAPI.canProvideTelemetry.andReturn(false);
|
||||
mockTelemetryAPI.isTelemetryObject.andReturn(false);
|
||||
|
||||
mockTimeout = jasmine.createSpy("timeout");
|
||||
mockTimeout.andReturn(1); // Return something
|
||||
@ -199,7 +199,7 @@ define(
|
||||
mockComposition.load.andReturn(Promise.resolve(mockChildren));
|
||||
mockCompositionAPI.get.andReturn(mockComposition);
|
||||
|
||||
mockTelemetryAPI.canProvideTelemetry.andCallFake(function (obj) {
|
||||
mockTelemetryAPI.isTelemetryObject.andCallFake(function (obj) {
|
||||
return obj.identifier.key === mockTelemetryObject.identifier.key;
|
||||
});
|
||||
|
||||
@ -287,7 +287,7 @@ define(
|
||||
mockChildren = mockChildren.concat(mockTelemetryChildren);
|
||||
mockComposition.load.andReturn(Promise.resolve(mockChildren));
|
||||
|
||||
mockTelemetryAPI.canProvideTelemetry.andCallFake(function (object) {
|
||||
mockTelemetryAPI.isTelemetryObject.andCallFake(function (object) {
|
||||
if (object === mockTelemetryObject) {
|
||||
return false;
|
||||
} else {
|
||||
|
@ -53,7 +53,7 @@
|
||||
width: $d;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@include transform(translateX(-50%));
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
&:before {
|
||||
// Icon blocker
|
||||
|
@ -177,7 +177,7 @@
|
||||
top: 20px; bottom: 5px;
|
||||
.l-labels-holder {
|
||||
@include absPosDefault();
|
||||
@include justify-content(space-between);
|
||||
justify-content: space-between;
|
||||
left: $m;
|
||||
.t-resource-graph-tick-label {
|
||||
font-size: 0.9em;
|
||||
|
@ -31,7 +31,8 @@ define([
|
||||
'./policies/AdapterCompositionPolicy',
|
||||
'./policies/AdaptedViewPolicy',
|
||||
'./runs/AlternateCompositionInitializer',
|
||||
'./runs/TimeSettingsURLHandler'
|
||||
'./runs/TimeSettingsURLHandler',
|
||||
'./runs/TypeDeprecationChecker'
|
||||
], function (
|
||||
legacyRegistry,
|
||||
ActionDialogDecorator,
|
||||
@ -43,7 +44,8 @@ define([
|
||||
AdapterCompositionPolicy,
|
||||
AdaptedViewPolicy,
|
||||
AlternateCompositionInitializer,
|
||||
TimeSettingsURLHandler
|
||||
TimeSettingsURLHandler,
|
||||
TypeDeprecationChecker
|
||||
) {
|
||||
legacyRegistry.register('src/adapter', {
|
||||
"extensions": {
|
||||
@ -107,6 +109,10 @@ define([
|
||||
}
|
||||
],
|
||||
runs: [
|
||||
{
|
||||
implementation: TypeDeprecationChecker,
|
||||
depends: ["types[]"]
|
||||
},
|
||||
{
|
||||
implementation: AlternateCompositionInitializer,
|
||||
depends: ["openmct"]
|
||||
|
47
src/adapter/runs/TypeDeprecationChecker.js
Normal file
47
src/adapter/runs/TypeDeprecationChecker.js
Normal file
@ -0,0 +1,47 @@
|
||||
/*****************************************************************************
|
||||
* Open openmct, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open openmct is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open openmct includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
/* global console */
|
||||
define([
|
||||
|
||||
], function (
|
||||
|
||||
) {
|
||||
|
||||
function checkForDeprecatedFunctionality(typeDef) {
|
||||
if (typeDef.hasOwnProperty('telemetry')) {
|
||||
console.warn(
|
||||
'DEPRECATION WARNING: Telemetry data on type ' +
|
||||
'registrations will be deprecated in a future version, ' +
|
||||
'please convert to a custom telemetry metadata provider ' +
|
||||
'for type: ' + typeDef.key
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function TypeDeprecationChecker(types) {
|
||||
types.forEach(checkForDeprecatedFunctionality);
|
||||
}
|
||||
|
||||
return TypeDeprecationChecker;
|
||||
|
||||
});
|
128
src/api/telemetry/DefaultMetadataProvider.js
Normal file
128
src/api/telemetry/DefaultMetadataProvider.js
Normal file
@ -0,0 +1,128 @@
|
||||
/*****************************************************************************
|
||||
* Open openmct, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open openmct is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open openmct includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'lodash'
|
||||
], function (
|
||||
_
|
||||
) {
|
||||
|
||||
/**
|
||||
* This is the default metadata provider; for any object with a "telemetry"
|
||||
* property, this provider will return the value of that property as the
|
||||
* telemetry metadata.
|
||||
*
|
||||
* This provider also implements legacy support for telemetry metadata
|
||||
* defined on the type. Telemetry metadata definitions on type will be
|
||||
* depreciated in the future.
|
||||
*/
|
||||
function DefaultMetadataProvider(openmct) {
|
||||
this.openmct = openmct;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies to any domain object with a telemetry property, or whose type
|
||||
* definition has a telemetry property.
|
||||
*/
|
||||
DefaultMetadataProvider.prototype.supportsMetadata = function (domainObject) {
|
||||
return !!domainObject.telemetry || !!this.typeHasTelemetry(domainObject);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves valueMetadata from legacy metadata.
|
||||
* @private
|
||||
*/
|
||||
function valueMetadatasFromOldFormat(metadata) {
|
||||
var valueMetadatas = [];
|
||||
|
||||
valueMetadatas.push({
|
||||
key: 'name',
|
||||
name: 'Name'
|
||||
});
|
||||
|
||||
metadata.domains.forEach(function (domain, index) {
|
||||
var valueMetadata = _.clone(domain);
|
||||
valueMetadata.hints = {
|
||||
domain: index + 1
|
||||
};
|
||||
valueMetadatas.push(valueMetadata);
|
||||
});
|
||||
|
||||
metadata.ranges.forEach(function (range, index) {
|
||||
var valueMetadata = _.clone(range);
|
||||
valueMetadata.hints = {
|
||||
range: index,
|
||||
priority: index + metadata.domains.length + 1
|
||||
};
|
||||
|
||||
if (valueMetadata.type === 'enum') {
|
||||
valueMetadata.key = 'enum';
|
||||
valueMetadata.hints.y -= 10;
|
||||
valueMetadata.hints.range -= 10;
|
||||
valueMetadata.enumerations =
|
||||
_.sortBy(valueMetadata.enumerations.map(function (e) {
|
||||
return {
|
||||
string: e.string,
|
||||
value: +e.value
|
||||
};
|
||||
}), 'e.value');
|
||||
valueMetadata.values = _.pluck(valueMetadata.enumerations, 'value');
|
||||
valueMetadata.max = _.max(valueMetadata.values);
|
||||
valueMetadata.min = _.min(valueMetadata.values);
|
||||
}
|
||||
|
||||
valueMetadatas.push(valueMetadata);
|
||||
});
|
||||
|
||||
return valueMetadatas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns telemetry metadata for a given domain object.
|
||||
*/
|
||||
DefaultMetadataProvider.prototype.getMetadata = function (domainObject) {
|
||||
var metadata = domainObject.telemetry || {};
|
||||
if (this.typeHasTelemetry(domainObject)) {
|
||||
var typeMetadata = this.typeService.getType(domainObject.type).typeDef.telemetry;
|
||||
_.extend(metadata, typeMetadata);
|
||||
if (!metadata.values) {
|
||||
metadata.values = valueMetadatasFromOldFormat(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
return metadata;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
DefaultMetadataProvider.prototype.typeHasTelemetry = function (domainObject) {
|
||||
if (!this.typeService) {
|
||||
this.typeService = this.openmct.$injector.get('typeService');
|
||||
}
|
||||
return !!this.typeService.getType(domainObject.type).typeDef.telemetry;
|
||||
};
|
||||
|
||||
|
||||
return DefaultMetadataProvider;
|
||||
|
||||
});
|
@ -141,17 +141,20 @@ define([
|
||||
return capability.subscribe(callbackWrapper, request);
|
||||
};
|
||||
|
||||
LegacyTelemetryProvider.prototype.limitEvaluator = function (domainObject) {
|
||||
LegacyTelemetryProvider.prototype.supportsLimits = function (domainObject) {
|
||||
var oldObject = this.instantiate(
|
||||
utils.toOldFormat(domainObject),
|
||||
utils.makeKeyString(domainObject.identifier));
|
||||
var limitEvaluator = oldObject.getCapability("limit");
|
||||
utils.makeKeyString(domainObject.identifier)
|
||||
);
|
||||
return oldObject.hasCapability("limit");
|
||||
};
|
||||
|
||||
if (!limitEvaluator) {
|
||||
return {
|
||||
evaluate: function () {}
|
||||
};
|
||||
}
|
||||
LegacyTelemetryProvider.prototype.getLimitEvaluator = function (domainObject) {
|
||||
var oldObject = this.instantiate(
|
||||
utils.toOldFormat(domainObject),
|
||||
utils.makeKeyString(domainObject.identifier)
|
||||
);
|
||||
var limitEvaluator = oldObject.getCapability("limit");
|
||||
|
||||
return {
|
||||
evaluate: function (datum, property) {
|
||||
@ -166,6 +169,7 @@ define([
|
||||
openmct.telemetry.legacyProvider = provider;
|
||||
openmct.telemetry.requestProviders.push(provider);
|
||||
openmct.telemetry.subscriptionProviders.push(provider);
|
||||
openmct.telemetry.limitProviders.push(provider);
|
||||
};
|
||||
|
||||
});
|
||||
|
@ -3,7 +3,7 @@
|
||||
* 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
|
||||
* Open openmct is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
@ -14,20 +14,22 @@
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* Open openmct includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
/*global console*/
|
||||
define([
|
||||
'./TelemetryMetadataManager',
|
||||
'./TelemetryValueFormatter',
|
||||
'./DefaultMetadataProvider',
|
||||
'../objects/object-utils',
|
||||
'lodash'
|
||||
], function (
|
||||
TelemetryMetadataManager,
|
||||
TelemetryValueFormatter,
|
||||
DefaultMetadataProvider,
|
||||
objectUtils,
|
||||
_
|
||||
) {
|
||||
@ -122,7 +124,6 @@ define([
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An interface for retrieving telemetry data associated with a domain
|
||||
* object.
|
||||
@ -131,15 +132,29 @@ define([
|
||||
* @augments module:openmct.TelemetryAPI~TelemetryProvider
|
||||
* @memberof module:openmct
|
||||
*/
|
||||
function TelemetryAPI(MCT) {
|
||||
this.MCT = MCT;
|
||||
function TelemetryAPI(openmct) {
|
||||
this.openmct = openmct;
|
||||
this.requestProviders = [];
|
||||
this.subscriptionProviders = [];
|
||||
this.metadataProviders = [new DefaultMetadataProvider(this.openmct)];
|
||||
this.limitProviders = [];
|
||||
this.metadataCache = new WeakMap();
|
||||
this.formatMapCache = new WeakMap();
|
||||
this.valueFormatterCache = new WeakMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the given domainObject is a telemetry object. A telemetry
|
||||
* object is any object which has telemetry metadata-- regardless of whether
|
||||
* the telemetry object has an available telemetry provider.
|
||||
*
|
||||
* @param {module:openmct.DomainObject} domainObject
|
||||
* @returns {boolean} true if the object is a telemetry object.
|
||||
*/
|
||||
TelemetryAPI.prototype.isTelemetryObject = function (domainObject) {
|
||||
return !!this.findMetadataProvider(domainObject);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if this provider can supply telemetry data associated with
|
||||
* this domain object.
|
||||
@ -151,6 +166,11 @@ define([
|
||||
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
|
||||
*/
|
||||
TelemetryAPI.prototype.canProvideTelemetry = function (domainObject) {
|
||||
console.warn(
|
||||
'DEPRECATION WARNING: openmct.telemetry.canProvideTelemetry ' +
|
||||
'will not be supported in future versions of Open MCT. Please ' +
|
||||
'use openmct.telemetry.isTelemetryObject instead.'
|
||||
);
|
||||
return !!this.findSubscriptionProvider(domainObject) ||
|
||||
!!this.findRequestProvider(domainObject);
|
||||
};
|
||||
@ -170,6 +190,12 @@ define([
|
||||
if (provider.supportsSubscribe) {
|
||||
this.subscriptionProviders.unshift(provider);
|
||||
}
|
||||
if (provider.supportsMetadata) {
|
||||
this.metadataProviders.unshift(provider);
|
||||
}
|
||||
if (provider.supportsLimits) {
|
||||
this.limitProviders.unshift(provider);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -196,18 +222,36 @@ define([
|
||||
return this.requestProviders.filter(supportsDomainObject)[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
TelemetryAPI.prototype.findMetadataProvider = function (domainObject) {
|
||||
return this.metadataProviders.filter(function (p) {
|
||||
return p.supportsMetadata(domainObject);
|
||||
})[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
TelemetryAPI.prototype.findLimitEvaluator = function (domainObject) {
|
||||
return this.limitProviders.filter(function (p) {
|
||||
return p.supportsLimits(domainObject);
|
||||
})[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
TelemetryAPI.prototype.standardizeRequestOptions = function (options) {
|
||||
if (!options.hasOwnProperty('start')) {
|
||||
options.start = this.MCT.time.bounds().start;
|
||||
options.start = this.openmct.time.bounds().start;
|
||||
}
|
||||
if (!options.hasOwnProperty('end')) {
|
||||
options.end = this.MCT.time.bounds().end;
|
||||
options.end = this.openmct.time.bounds().end;
|
||||
}
|
||||
if (!options.hasOwnProperty('domain')) {
|
||||
options.domain = this.MCT.time.timeSystem().key;
|
||||
options.domain = this.openmct.time.timeSystem().key;
|
||||
}
|
||||
};
|
||||
|
||||
@ -300,12 +344,15 @@ define([
|
||||
*/
|
||||
TelemetryAPI.prototype.getMetadata = function (domainObject) {
|
||||
if (!this.metadataCache.has(domainObject)) {
|
||||
if (!this.typeService) {
|
||||
this.typeService = this.MCT.$injector.get('typeService');
|
||||
var metadataProvider = this.findMetadataProvider(domainObject);
|
||||
if (!metadataProvider) {
|
||||
return;
|
||||
}
|
||||
var metadata = metadataProvider.getMetadata(domainObject);
|
||||
|
||||
this.metadataCache.set(
|
||||
domainObject,
|
||||
new TelemetryMetadataManager(domainObject, this.typeService)
|
||||
new TelemetryMetadataManager(metadata)
|
||||
);
|
||||
}
|
||||
return this.metadataCache.get(domainObject);
|
||||
@ -343,7 +390,7 @@ define([
|
||||
TelemetryAPI.prototype.getValueFormatter = function (valueMetadata) {
|
||||
if (!this.valueFormatterCache.has(valueMetadata)) {
|
||||
if (!this.formatService) {
|
||||
this.formatService = this.MCT.$injector.get('formatService');
|
||||
this.formatService = this.openmct.$injector.get('formatService');
|
||||
}
|
||||
this.valueFormatterCache.set(
|
||||
valueMetadata,
|
||||
@ -375,7 +422,7 @@ define([
|
||||
* @param {Format} format the
|
||||
*/
|
||||
TelemetryAPI.prototype.addFormat = function (format) {
|
||||
this.MCT.legacyExtension('formats', {
|
||||
this.openmct.legacyExtension('formats', {
|
||||
key: format.key,
|
||||
implementation: function () {
|
||||
return format;
|
||||
@ -385,7 +432,9 @@ define([
|
||||
|
||||
/**
|
||||
* Get a limit evaluator for this domain object.
|
||||
* Limit Evaluators help you evaluate limit and alarm status of individual telemetry datums for display purposes without having to interact directly with the Limit API.
|
||||
* Limit Evaluators help you evaluate limit and alarm status of individual
|
||||
* telemetry datums for display purposes without having to interact directly
|
||||
* with the Limit API.
|
||||
*
|
||||
* This method is optional.
|
||||
* If a provider does not implement this method, it is presumed
|
||||
@ -397,8 +446,34 @@ define([
|
||||
* @method limitEvaluator
|
||||
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
|
||||
*/
|
||||
TelemetryAPI.prototype.limitEvaluator = function () {
|
||||
return this.legacyProvider.limitEvaluator.apply(this.legacyProvider, arguments);
|
||||
TelemetryAPI.prototype.limitEvaluator = function (domainObject) {
|
||||
return this.getLimitEvaluator(domainObject);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a limit evaluator for this domain object.
|
||||
* Limit Evaluators help you evaluate limit and alarm status of individual
|
||||
* telemetry datums for display purposes without having to interact directly
|
||||
* with the Limit API.
|
||||
*
|
||||
* This method is optional.
|
||||
* If a provider does not implement this method, it is presumed
|
||||
* that no limits are defined for this domain object's telemetry.
|
||||
*
|
||||
* @param {module:openmct.DomainObject} domainObject the domain
|
||||
* object for which to evaluate limits
|
||||
* @returns {module:openmct.TelemetryAPI~LimitEvaluator}
|
||||
* @method limitEvaluator
|
||||
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
|
||||
*/
|
||||
TelemetryAPI.prototype.getLimitEvaluator = function (domainObject) {
|
||||
var provider = this.findLimitEvaluator(domainObject);
|
||||
if (!provider) {
|
||||
return {
|
||||
evaluate: function () {}
|
||||
};
|
||||
}
|
||||
return provider.getLimitEvaluator(domainObject);
|
||||
};
|
||||
|
||||
return TelemetryAPI;
|
||||
|
@ -1,3 +1,25 @@
|
||||
/*****************************************************************************
|
||||
* 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([
|
||||
'./TelemetryAPI'
|
||||
], function (
|
||||
|
@ -27,51 +27,6 @@ define([
|
||||
_
|
||||
) {
|
||||
|
||||
function valueMetadatasFromOldFormat(metadata) {
|
||||
var valueMetadatas = [];
|
||||
|
||||
valueMetadatas.push({
|
||||
key: 'name',
|
||||
name: 'Name'
|
||||
});
|
||||
|
||||
metadata.domains.forEach(function (domain, index) {
|
||||
var valueMetadata = _.clone(domain);
|
||||
valueMetadata.hints = {
|
||||
domain: index + 1
|
||||
};
|
||||
valueMetadatas.push(valueMetadata);
|
||||
});
|
||||
|
||||
metadata.ranges.forEach(function (range, index) {
|
||||
var valueMetadata = _.clone(range);
|
||||
valueMetadata.hints = {
|
||||
range: index,
|
||||
priority: index + metadata.domains.length + 1
|
||||
};
|
||||
|
||||
if (valueMetadata.type === 'enum') {
|
||||
valueMetadata.key = 'enum';
|
||||
valueMetadata.hints.y -= 10;
|
||||
valueMetadata.hints.range -= 10;
|
||||
valueMetadata.enumerations =
|
||||
_.sortBy(valueMetadata.enumerations.map(function (e) {
|
||||
return {
|
||||
string: e.string,
|
||||
value: +e.value
|
||||
};
|
||||
}), 'e.value');
|
||||
valueMetadata.values = _.pluck(valueMetadata.enumerations, 'value');
|
||||
valueMetadata.max = _.max(valueMetadata.values);
|
||||
valueMetadata.min = _.min(valueMetadata.values);
|
||||
}
|
||||
|
||||
valueMetadatas.push(valueMetadata);
|
||||
});
|
||||
|
||||
return valueMetadatas;
|
||||
}
|
||||
|
||||
function applyReasonableDefaults(valueMetadata, index) {
|
||||
valueMetadata.source = valueMetadata.source || valueMetadata.key;
|
||||
valueMetadata.hints = valueMetadata.hints || {};
|
||||
@ -119,24 +74,14 @@ define([
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility class for handling telemetry metadata for a domain object.
|
||||
* Wraps old format metadata to new format metadata.
|
||||
* Provides methods for interrogating telemetry metadata.
|
||||
* Utility class for handling and inspecting telemetry metadata. Applies
|
||||
* reasonable defaults to simplify the task of providing metadata, while
|
||||
* also providing methods for interrogating telemetry metadata.
|
||||
*/
|
||||
function TelemetryMetadataManager(domainObject, typeService) {
|
||||
this.metadata = domainObject.telemetry || {};
|
||||
function TelemetryMetadataManager(metadata) {
|
||||
this.metadata = metadata;
|
||||
|
||||
if (this.metadata.values) {
|
||||
this.valueMetadatas = this.metadata.values;
|
||||
} else {
|
||||
var typeMetadata = typeService
|
||||
.getType(domainObject.type).typeDef.telemetry;
|
||||
|
||||
_.extend(this.metadata, typeMetadata);
|
||||
this.valueMetadatas = valueMetadatasFromOldFormat(this.metadata);
|
||||
}
|
||||
|
||||
this.valueMetadatas = this.valueMetadatas.map(applyReasonableDefaults);
|
||||
this.valueMetadatas = this.metadata.values.map(applyReasonableDefaults);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -90,7 +90,7 @@ define(['./Type'], function (Type) {
|
||||
/**
|
||||
* Retrieve a registered type by its key.
|
||||
* @method get
|
||||
* @param {string} typeKey the key for htis type
|
||||
* @param {string} typeKey the key for this type
|
||||
* @memberof module:openmct.TypeRegistry#
|
||||
* @returns {module:openmct.Type} the registered type
|
||||
*/
|
||||
|
55
src/api/types/TypeRegistrySpec.js
Normal file
55
src/api/types/TypeRegistrySpec.js
Normal file
@ -0,0 +1,55 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2017, 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(['./TypeRegistry', './Type'], function (TypeRegistry, Type) {
|
||||
describe('The Type API', function () {
|
||||
var typeRegistryInstance;
|
||||
|
||||
beforeEach(function () {
|
||||
typeRegistryInstance = new TypeRegistry ();
|
||||
typeRegistryInstance.addType('testType', {
|
||||
name: 'Test Type',
|
||||
description: 'This is a test type.',
|
||||
creatable: true
|
||||
});
|
||||
});
|
||||
|
||||
it('types can be standardized', function () {
|
||||
typeRegistryInstance.addType('standardizationTestType', {
|
||||
label: 'Test Type',
|
||||
description: 'This is a test type.',
|
||||
creatable: true
|
||||
});
|
||||
typeRegistryInstance.standardizeType(typeRegistryInstance.types.standardizationTestType);
|
||||
expect(typeRegistryInstance.get('standardizationTestType').definition.label).toBeUndefined();
|
||||
expect(typeRegistryInstance.get('standardizationTestType').definition.name).toBe('Test Type');
|
||||
});
|
||||
|
||||
it('new types are registered successfully and can be retrieved', function () {
|
||||
expect(typeRegistryInstance.get('testType').definition.name).toBe('Test Type');
|
||||
});
|
||||
|
||||
it('type registry contains new keys', function () {
|
||||
expect(typeRegistryInstance.listKeys ()).toContain('testType');
|
||||
});
|
||||
});
|
||||
});
|
@ -61,7 +61,7 @@ function (
|
||||
this.alarmSets = [];
|
||||
this.offset = {};
|
||||
this.config = $scope.config;
|
||||
this.listenTo(this.$scope, '$destoy', this.destroy, this);
|
||||
this.listenTo(this.$scope, '$destroy', this.destroy, this);
|
||||
this.draw = this.draw.bind(this);
|
||||
this.scheduleDraw = this.scheduleDraw.bind(this);
|
||||
this.seriesElements = new WeakMap();
|
||||
@ -197,9 +197,29 @@ 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);
|
||||
|
||||
@ -211,6 +231,10 @@ 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);
|
||||
};
|
||||
|
||||
|
@ -43,6 +43,7 @@ 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];
|
||||
|
||||
|
@ -75,11 +75,14 @@ define([
|
||||
openmct: options.openmct
|
||||
});
|
||||
|
||||
this.removeMutationListener = this.openmct.objects.observe(
|
||||
this.get('domainObject'),
|
||||
'*',
|
||||
this.updateDomainObject.bind(this)
|
||||
);
|
||||
if (this.get('domainObject').type === 'telemetry.plot.overlay') {
|
||||
this.removeMutationListener = this.openmct.objects.observe(
|
||||
this.get('domainObject'),
|
||||
'*',
|
||||
this.updateDomainObject.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
this.yAxis.listenToSeriesCollection(this.series);
|
||||
this.legend.listenToSeriesCollection(this.series);
|
||||
|
||||
@ -112,7 +115,9 @@ define([
|
||||
this.yAxis.destroy();
|
||||
this.series.destroy();
|
||||
this.legend.destroy();
|
||||
this.removeMutationListener();
|
||||
if (this.removeMutationListener) {
|
||||
this.removeMutationListener();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Return defaults, which are extracted from the passed in domain
|
||||
|
@ -22,9 +22,13 @@
|
||||
|
||||
|
||||
define([
|
||||
|
||||
'lodash',
|
||||
'EventEmitter',
|
||||
'../lib/eventHelpers'
|
||||
], function (
|
||||
|
||||
_,
|
||||
EventEmitter,
|
||||
eventHelpers
|
||||
) {
|
||||
|
||||
/**
|
||||
@ -47,6 +51,9 @@ 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;
|
||||
|
@ -35,7 +35,7 @@ define(
|
||||
ALLOCATIONS: []
|
||||
},
|
||||
{
|
||||
MAX_INSTANCES: Number.MAX_INFINITY,
|
||||
MAX_INSTANCES: Number.POSITIVE_INFINITY,
|
||||
API: Draw2D,
|
||||
ALLOCATIONS: []
|
||||
}
|
||||
@ -83,12 +83,24 @@ 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();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -22,9 +22,13 @@
|
||||
|
||||
|
||||
define([
|
||||
|
||||
'lodash',
|
||||
'EventEmitter',
|
||||
'../lib/eventHelpers'
|
||||
], function (
|
||||
|
||||
_,
|
||||
EventEmitter,
|
||||
eventHelpers
|
||||
) {
|
||||
|
||||
// WebGL shader sources (for drawing plain colors)
|
||||
@ -69,6 +73,21 @@ 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);
|
||||
@ -103,7 +122,12 @@ 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) {
|
||||
@ -117,6 +141,9 @@ 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);
|
||||
@ -125,6 +152,9 @@ 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;
|
||||
@ -151,6 +181,9 @@ 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);
|
||||
@ -169,6 +202,9 @@ 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);
|
||||
};
|
||||
|
||||
@ -177,6 +213,9 @@ 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);
|
||||
};
|
||||
@ -191,6 +230,9 @@ 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);
|
||||
|
@ -59,6 +59,19 @@ 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');
|
||||
|
||||
@ -82,6 +95,7 @@ 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);
|
||||
|
@ -82,6 +82,10 @@ define([
|
||||
};
|
||||
|
||||
PlotController.prototype.loadSeriesData = function (series) {
|
||||
if (this.$element[0].offsetWidth === 0) {
|
||||
this.scheduleLoad(series);
|
||||
return;
|
||||
}
|
||||
this.startLoading();
|
||||
var options = {
|
||||
size: this.$element[0].offsetWidth,
|
||||
@ -92,6 +96,26 @@ define([
|
||||
.then(this.stopLoading.bind(this));
|
||||
};
|
||||
|
||||
PlotController.prototype.scheduleLoad = function (series) {
|
||||
if (!this.scheduledLoads) {
|
||||
this.startLoading();
|
||||
this.scheduledLoads = [];
|
||||
this.checkForSize = setInterval(function () {
|
||||
if (this.$element[0].offsetWidth === 0) {
|
||||
return;
|
||||
}
|
||||
this.stopLoading();
|
||||
this.scheduledLoads.forEach(this.loadSeriesData, this);
|
||||
delete this.scheduledLoads;
|
||||
clearInterval(this.checkForSize);
|
||||
delete this.checkForSize;
|
||||
}.bind(this));
|
||||
}
|
||||
if (this.scheduledLoads.indexOf(series) === -1) {
|
||||
this.scheduledLoads.push(series);
|
||||
}
|
||||
};
|
||||
|
||||
PlotController.prototype.addSeries = function (series) {
|
||||
this.listenTo(series, 'change:yKey', function () {
|
||||
this.loadSeriesData(series);
|
||||
@ -126,6 +150,10 @@ define([
|
||||
PlotController.prototype.destroy = function () {
|
||||
configStore.untrack(this.config.id);
|
||||
this.stopListening();
|
||||
if (this.checkForSize) {
|
||||
clearInterval(this.checkForSize);
|
||||
delete this.checkForSize;
|
||||
}
|
||||
};
|
||||
|
||||
PlotController.prototype.loadMoreData = function (range, purge) {
|
||||
|
@ -32,7 +32,7 @@ define(
|
||||
var parentType = parent.getCapability('type');
|
||||
var newStyleChild = child.useCapability('adapter');
|
||||
|
||||
if (parentType.instanceOf('summary-widget') && !this.openmct.telemetry.canProvideTelemetry(newStyleChild)) {
|
||||
if (parentType.instanceOf('summary-widget') && !this.openmct.telemetry.isTelemetryObject(newStyleChild)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,14 @@
|
||||
define(['./src/SummaryWidget', './SummaryWidgetsCompositionPolicy'], function (SummaryWidget, SummaryWidgetsCompositionPolicy) {
|
||||
define([
|
||||
'./SummaryWidgetsCompositionPolicy',
|
||||
'./src/telemetry/SummaryWidgetMetadataProvider',
|
||||
'./src/telemetry/SummaryWidgetTelemetryProvider',
|
||||
'./src/views/SummaryWidgetViewProvider'
|
||||
], function (
|
||||
SummaryWidgetsCompositionPolicy,
|
||||
SummaryWidgetMetadataProvider,
|
||||
SummaryWidgetTelemetryProvider,
|
||||
SummaryWidgetViewProvider
|
||||
) {
|
||||
|
||||
function plugin() {
|
||||
|
||||
@ -9,8 +19,40 @@ define(['./src/SummaryWidget', './SummaryWidgetsCompositionPolicy'], function (S
|
||||
cssClass: 'icon-summary-widget',
|
||||
initialize: function (domainObject) {
|
||||
domainObject.composition = [];
|
||||
domainObject.configuration = {};
|
||||
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.openNewTab = 'thisTab';
|
||||
domainObject.telemetry = {};
|
||||
},
|
||||
form: [
|
||||
{
|
||||
@ -40,26 +82,14 @@ define(['./src/SummaryWidget', './SummaryWidgetsCompositionPolicy'], function (S
|
||||
]
|
||||
};
|
||||
|
||||
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));
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
define ([
|
||||
'./ConditionEvaluator',
|
||||
'../../../api/objects/object-utils',
|
||||
'EventEmitter',
|
||||
'zepto',
|
||||
'lodash'
|
||||
], function (
|
||||
ConditionEvaluator,
|
||||
objectUtils,
|
||||
EventEmitter,
|
||||
$,
|
||||
_
|
||||
@ -123,21 +125,23 @@ define ([
|
||||
* has completed and types have been parsed
|
||||
*/
|
||||
ConditionManager.prototype.parsePropertyTypes = function (object) {
|
||||
var telemetryAPI = this.openmct.telemetry,
|
||||
key,
|
||||
type,
|
||||
self = this;
|
||||
var objectId = objectUtils.makeKeyString(object.identifier);
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
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);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -147,23 +151,9 @@ define ([
|
||||
* and property types parsed
|
||||
*/
|
||||
ConditionManager.prototype.parseAllPropertyTypes = function () {
|
||||
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;
|
||||
Object.values(this.compositionObjs).forEach(this.parsePropertyTypes, this);
|
||||
this.metadataLoadComplete = true;
|
||||
this.eventEmitter.emit('metadata');
|
||||
};
|
||||
|
||||
/**
|
||||
@ -187,18 +177,17 @@ define ([
|
||||
ConditionManager.prototype.onCompositionAdd = function (obj) {
|
||||
var compositionKeys,
|
||||
telemetryAPI = this.openmct.telemetry,
|
||||
objId = obj.identifier.key,
|
||||
objId = objectUtils.makeKeyString(obj.identifier),
|
||||
telemetryMetadata,
|
||||
self = this;
|
||||
|
||||
if (telemetryAPI.canProvideTelemetry(obj)) {
|
||||
if (telemetryAPI.isTelemetryObject(obj)) {
|
||||
self.compositionObjs[objId] = obj;
|
||||
self.telemetryMetadataById[objId] = {};
|
||||
|
||||
compositionKeys = self.domainObject.composition.map(function (object) {
|
||||
return object.key;
|
||||
});
|
||||
if (!compositionKeys.includes(obj.identifier.key)) {
|
||||
// FIXME: this should just update based on listener.
|
||||
compositionKeys = self.domainObject.composition.map(objectUtils.makeKeyString);
|
||||
if (!compositionKeys.includes(objId)) {
|
||||
self.domainObject.composition.push(obj.identifier);
|
||||
}
|
||||
|
||||
@ -212,6 +201,12 @@ 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
|
||||
@ -234,11 +229,14 @@ 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;
|
||||
return id.key === identifier.key &&
|
||||
id.namespace === identifier.namespace;
|
||||
});
|
||||
delete this.compositionObjs[identifier.key];
|
||||
this.subscriptions[identifier.key](); //unsubscribe from telemetry source
|
||||
delete this.compositionObjs[objectId];
|
||||
this.subscriptions[objectId](); //unsubscribe from telemetry source
|
||||
this.eventEmitter.emit('remove', identifier);
|
||||
|
||||
if (_.isEmpty(this.compositionObjs)) {
|
||||
@ -253,13 +251,9 @@ define ([
|
||||
* @private
|
||||
*/
|
||||
ConditionManager.prototype.onCompositionLoad = function () {
|
||||
var self = this;
|
||||
self.loadComplete = true;
|
||||
self.eventEmitter.emit('load');
|
||||
self.parseAllPropertyTypes().then(function () {
|
||||
self.metadataLoadComplete = true;
|
||||
self.eventEmitter.emit('metadata');
|
||||
});
|
||||
this.loadComplete = true;
|
||||
this.eventEmitter.emit('load');
|
||||
this.parseAllPropertyTypes();
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -126,6 +126,13 @@ 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
|
||||
*/
|
||||
|
@ -131,15 +131,20 @@ 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);
|
||||
newItem.on('remove', self.removeItem, self);
|
||||
newItem.on('duplicate', self.initItem, self);
|
||||
newItem.on('change', self.onItemChange, self);
|
||||
self.listenTo(newItem, 'remove', self.removeItem, self);
|
||||
self.listenTo(newItem, 'duplicate', self.initItem, self);
|
||||
self.listenTo(newItem, 'change', self.onItemChange, self);
|
||||
self.items.push(newItem);
|
||||
});
|
||||
|
||||
@ -190,10 +195,10 @@ define([
|
||||
};
|
||||
|
||||
TestDataManager.prototype.destroy = function () {
|
||||
this.stopListening();
|
||||
this.items.forEach(function (item) {
|
||||
item.remove();
|
||||
});
|
||||
this.stopListening();
|
||||
};
|
||||
|
||||
return TestDataManager;
|
||||
|
@ -1,4 +1,10 @@
|
||||
define(['./Select'], function (Select) {
|
||||
define([
|
||||
'./Select',
|
||||
'../../../../api/objects/object-utils'
|
||||
], function (
|
||||
Select,
|
||||
objectUtils
|
||||
) {
|
||||
|
||||
/**
|
||||
* Create a {Select} element whose composition is dynamically updated with
|
||||
@ -37,7 +43,7 @@ define(['./Select'], function (Select) {
|
||||
* @private
|
||||
*/
|
||||
function onCompositionAdd(obj) {
|
||||
self.select.addOption(obj.identifier.key, obj.name);
|
||||
self.select.addOption(objectUtils.makeKeyString(obj.identifier), obj.name);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -75,7 +81,7 @@ define(['./Select'], function (Select) {
|
||||
*/
|
||||
ObjectSelect.prototype.generateOptions = function () {
|
||||
var items = Object.values(this.compositionObjs).map(function (obj) {
|
||||
return [obj.identifier.key, obj.name];
|
||||
return [objectUtils.makeKeyString(obj.identifier), obj.name];
|
||||
});
|
||||
this.baseOptions.forEach(function (option, index) {
|
||||
items.splice(index, 0, option);
|
||||
|
64
src/plugins/summaryWidget/src/telemetry/EvaluatorPool.js
Normal file
64
src/plugins/summaryWidget/src/telemetry/EvaluatorPool.js
Normal file
@ -0,0 +1,64 @@
|
||||
/*****************************************************************************
|
||||
* 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;
|
||||
});
|
102
src/plugins/summaryWidget/src/telemetry/EvaluatorPoolSpec.js
Normal file
102
src/plugins/summaryWidget/src/telemetry/EvaluatorPoolSpec.js
Normal file
@ -0,0 +1,102 @@
|
||||
/*****************************************************************************
|
||||
* 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);
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,80 @@
|
||||
/*****************************************************************************
|
||||
* 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;
|
||||
});
|
@ -0,0 +1,142 @@
|
||||
/*****************************************************************************
|
||||
* 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);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
@ -0,0 +1,281 @@
|
||||
/*****************************************************************************
|
||||
* 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;
|
||||
|
||||
});
|
@ -0,0 +1,119 @@
|
||||
/*****************************************************************************
|
||||
* 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;
|
||||
|
||||
});
|
73
src/plugins/summaryWidget/src/telemetry/SummaryWidgetRule.js
Normal file
73
src/plugins/summaryWidget/src/telemetry/SummaryWidgetRule.js
Normal file
@ -0,0 +1,73 @@
|
||||
/*****************************************************************************
|
||||
* 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;
|
||||
});
|
||||
|
163
src/plugins/summaryWidget/src/telemetry/SummaryWidgetRuleSpec.js
Normal file
163
src/plugins/summaryWidget/src/telemetry/SummaryWidgetRuleSpec.js
Normal file
@ -0,0 +1,163 @@
|
||||
/*****************************************************************************
|
||||
* 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);
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,64 @@
|
||||
/*****************************************************************************
|
||||
* 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;
|
||||
});
|
@ -0,0 +1,475 @@
|
||||
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'
|
||||
}]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
197
src/plugins/summaryWidget/src/telemetry/operations.js
Normal file
197
src/plugins/summaryWidget/src/telemetry/operations.js
Normal file
@ -0,0 +1,197 @@
|
||||
/*****************************************************************************
|
||||
* 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;
|
||||
});
|
92
src/plugins/summaryWidget/src/views/SummaryWidgetView.js
Normal file
92
src/plugins/summaryWidget/src/views/SummaryWidgetView.js
Normal file
@ -0,0 +1,92 @@
|
||||
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;
|
||||
|
||||
});
|
@ -0,0 +1,42 @@
|
||||
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;
|
||||
});
|
5
src/plugins/summaryWidget/src/views/summary-widget.html
Normal file
5
src/plugins/summaryWidget/src/views/summary-widget.html
Normal file
@ -0,0 +1,5 @@
|
||||
<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>
|
@ -19,6 +19,7 @@ define(['../src/ConditionManager'], function (ConditionManager) {
|
||||
removeCallbackSpy,
|
||||
telemetryCallbackSpy,
|
||||
metadataCallbackSpy,
|
||||
telemetryRequests,
|
||||
mockTelemetryValues,
|
||||
mockTelemetryValues2,
|
||||
mockConditionEvaluator;
|
||||
@ -61,31 +62,43 @@ define(['../src/ConditionManager'], function (ConditionManager) {
|
||||
mockCompObject1: {
|
||||
property1: {
|
||||
key: 'property1',
|
||||
name: 'Property 1'
|
||||
name: 'Property 1',
|
||||
format: 'string',
|
||||
hints: {}
|
||||
},
|
||||
property2: {
|
||||
key: 'property2',
|
||||
name: 'Property 2'
|
||||
name: 'Property 2',
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
}
|
||||
},
|
||||
mockCompObject2: {
|
||||
property3: {
|
||||
key: 'property3',
|
||||
name: 'Property 3'
|
||||
name: 'Property 3',
|
||||
format: 'string',
|
||||
hints: {}
|
||||
},
|
||||
property4: {
|
||||
key: 'property4',
|
||||
name: 'Property 4'
|
||||
name: 'Property 4',
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
}
|
||||
},
|
||||
mockCompObject3: {
|
||||
property1: {
|
||||
key: 'property1',
|
||||
name: 'Property 1'
|
||||
name: 'Property 1',
|
||||
hints: {}
|
||||
},
|
||||
property2: {
|
||||
key: 'property2',
|
||||
name: 'Property 2'
|
||||
name: 'Property 2',
|
||||
hints: {}
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -160,35 +173,39 @@ define(['../src/ConditionManager'], function (ConditionManager) {
|
||||
unregisterSpies[event]();
|
||||
});
|
||||
mockComposition.load.andCallFake(function () {
|
||||
mockEventCallbacks.add(mockCompObject1);
|
||||
mockEventCallbacks.add(mockCompObject2);
|
||||
mockEventCallbacks.load();
|
||||
mockComposition.triggerCallback('add', mockCompObject1);
|
||||
mockComposition.triggerCallback('add', mockCompObject2);
|
||||
mockComposition.triggerCallback('load');
|
||||
});
|
||||
mockComposition.triggerCallback.andCallFake(function (event) {
|
||||
mockComposition.triggerCallback.andCallFake(function (event, obj) {
|
||||
if (event === 'add') {
|
||||
mockEventCallbacks.add(mockCompObject3);
|
||||
mockEventCallbacks.add(obj);
|
||||
} else if (event === 'remove') {
|
||||
mockEventCallbacks.remove({
|
||||
key: 'mockCompObject2'
|
||||
});
|
||||
mockEventCallbacks.remove(obj.identifier);
|
||||
} else {
|
||||
mockEventCallbacks[event]();
|
||||
}
|
||||
});
|
||||
|
||||
telemetryRequests = [];
|
||||
mockTelemetryAPI = jasmine.createSpyObj('telemetryAPI', [
|
||||
'request',
|
||||
'canProvideTelemetry',
|
||||
'isTelemetryObject',
|
||||
'getMetadata',
|
||||
'subscribe',
|
||||
'triggerTelemetryCallback'
|
||||
]);
|
||||
mockTelemetryAPI.request.andCallFake(function (obj) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
resolve(mockTelemetryValues[obj.identifer.key]);
|
||||
var req = {
|
||||
object: obj
|
||||
};
|
||||
req.promise = new Promise(function (resolve, reject) {
|
||||
req.resolve = resolve;
|
||||
req.reject = reject;
|
||||
});
|
||||
telemetryRequests.push(req);
|
||||
return req.promise;
|
||||
});
|
||||
mockTelemetryAPI.canProvideTelemetry.andReturn(true);
|
||||
mockTelemetryAPI.isTelemetryObject.andReturn(true);
|
||||
mockTelemetryAPI.getMetadata.andCallFake(function (obj) {
|
||||
return mockMetadataManagers[obj.identifier.key];
|
||||
});
|
||||
@ -245,41 +262,50 @@ define(['../src/ConditionManager'], function (ConditionManager) {
|
||||
var allKeys = {
|
||||
property1: {
|
||||
key: 'property1',
|
||||
name: 'Property 1'
|
||||
name: 'Property 1',
|
||||
format: 'string',
|
||||
hints: {}
|
||||
},
|
||||
property2: {
|
||||
key: 'property2',
|
||||
name: 'Property 2'
|
||||
name: 'Property 2',
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
},
|
||||
property3: {
|
||||
key: 'property3',
|
||||
name: 'Property 3'
|
||||
name: 'Property 3',
|
||||
format: 'string',
|
||||
hints: {}
|
||||
},
|
||||
property4: {
|
||||
key: 'property4',
|
||||
name: 'Property 4'
|
||||
name: 'Property 4',
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
}
|
||||
};
|
||||
expect(conditionManager.getTelemetryMetadata('all')).toEqual(allKeys);
|
||||
expect(conditionManager.getTelemetryMetadata('any')).toEqual(allKeys);
|
||||
mockComposition.triggerCallback('add');
|
||||
mockComposition.triggerCallback('add', mockCompObject3);
|
||||
expect(conditionManager.getTelemetryMetadata('all')).toEqual(allKeys);
|
||||
expect(conditionManager.getTelemetryMetadata('any')).toEqual(allKeys);
|
||||
});
|
||||
|
||||
it('loads and gets telemetry property types', function () {
|
||||
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();
|
||||
});
|
||||
conditionManager.parseAllPropertyTypes();
|
||||
expect(conditionManager.getTelemetryPropertyType('mockCompObject1', 'property1'))
|
||||
.toEqual('string');
|
||||
expect(conditionManager.getTelemetryPropertyType('mockCompObject2', 'property4'))
|
||||
.toEqual('number');
|
||||
expect(conditionManager.metadataLoadCompleted()).toEqual(true);
|
||||
expect(metadataCallbackSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('responds to a composition add event and invokes the appropriate handlers', function () {
|
||||
mockComposition.triggerCallback('add');
|
||||
mockComposition.triggerCallback('add', mockCompObject3);
|
||||
expect(addCallbackSpy).toHaveBeenCalledWith(mockCompObject3);
|
||||
expect(conditionManager.getComposition()).toEqual({
|
||||
mockCompObject1: mockCompObject1,
|
||||
@ -289,7 +315,7 @@ define(['../src/ConditionManager'], function (ConditionManager) {
|
||||
});
|
||||
|
||||
it('responds to a composition remove event and invokes the appropriate handlers', function () {
|
||||
mockComposition.triggerCallback('remove');
|
||||
mockComposition.triggerCallback('remove', mockCompObject2);
|
||||
expect(removeCallbackSpy).toHaveBeenCalledWith({
|
||||
key: 'mockCompObject2'
|
||||
});
|
||||
@ -300,7 +326,7 @@ define(['../src/ConditionManager'], function (ConditionManager) {
|
||||
});
|
||||
|
||||
it('unregisters telemetry subscriptions and composition listeners on destroy', function () {
|
||||
mockComposition.triggerCallback('add');
|
||||
mockComposition.triggerCallback('add', mockCompObject3);
|
||||
conditionManager.destroy();
|
||||
Object.values(unsubscribeSpies).forEach(function (spy) {
|
||||
expect(spy).toHaveBeenCalled();
|
||||
@ -311,7 +337,19 @@ define(['../src/ConditionManager'], function (ConditionManager) {
|
||||
});
|
||||
|
||||
it('populates its LAD cache with historial data on load, if available', function () {
|
||||
conditionManager.parseAllPropertyTypes().then(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 () {
|
||||
expect(conditionManager.subscriptionCache.mockCompObject1.property1).toEqual('Its a string');
|
||||
expect(conditionManager.subscriptionCache.mockCompObject2.property4).toEqual(66);
|
||||
});
|
||||
@ -352,12 +390,10 @@ define(['../src/ConditionManager'], function (ConditionManager) {
|
||||
});
|
||||
|
||||
it('gets the human-readable name of a telemetry field', function () {
|
||||
conditionManager.parseAllPropertyTypes().then(function () {
|
||||
expect(conditionManager.getTelemetryPropertyName('mockCompObject1', 'property1'))
|
||||
.toEqual('Property 1');
|
||||
expect(conditionManager.getTelemetryPropertyName('mockCompObject2', 'property4'))
|
||||
.toEqual('Property 4');
|
||||
});
|
||||
expect(conditionManager.getTelemetryPropertyName('mockCompObject1', 'property1'))
|
||||
.toEqual('Property 1');
|
||||
expect(conditionManager.getTelemetryPropertyName('mockCompObject2', 'property4'))
|
||||
.toEqual('Property 4');
|
||||
});
|
||||
|
||||
it('gets its associated ConditionEvaluator', function () {
|
||||
|
Reference in New Issue
Block a user