feat: AMD -> ES6 (#7029)

* feat: full amd -> es6 conversion

* fix: move MCT to ES6 class

* fix: default drop, correct imports

* fix: correct all imports

* fix: property typo

* fix: avoid anonymous functions

* fix: correct typo

scarily small - can see why this e2e coverage issue is high priority

* fix: use proper uuid format

* style: fmt

* fix: import vue correctly, get correct layout

* fix: createApp without JSON

fixes template issues

* fix: don't use default on InspectorDataVisualization

* fix: remove more .default calls

* Update src/api/api.js

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>

* Update src/plugins/plugins.js

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>

* Update src/plugins/plugins.js

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>

* fix: suggestions

* fix: drop unnecessary this.annotation initialization

* fix: move all initialization calls to constructor

* refactor: move vue dist import to webpack alias

---------

Co-authored-by: Jesse Mazzella <ozyx@users.noreply.github.com>
This commit is contained in:
Tristan F 2023-12-27 15:15:51 -05:00 committed by GitHub
parent 715a44864e
commit 2e03bc394c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
102 changed files with 11730 additions and 12016 deletions

View File

@ -76,7 +76,8 @@ const config = {
MCT: path.join(projectRootDir, 'src/MCT'),
testUtils: path.join(projectRootDir, 'src/utils/testUtils.js'),
objectUtils: path.join(projectRootDir, 'src/api/objects/object-utils.js'),
utils: path.join(projectRootDir, 'src/utils')
utils: path.join(projectRootDir, 'src/utils'),
vue: 'vue/dist/vue.esm-bundler'
}
},
plugins: [

View File

@ -1,138 +1,134 @@
define(['lodash'], function (_) {
var METADATA_BY_TYPE = {
generator: {
values: [
{
key: 'name',
name: 'Name',
format: 'string'
},
{
key: 'utc',
name: 'Time',
format: 'utc',
hints: {
domain: 1
}
},
{
key: 'yesterday',
name: 'Yesterday',
format: 'utc',
hints: {
domain: 2
}
},
{
key: 'wavelengths',
name: 'Wavelength',
unit: 'nm',
format: 'string[]',
hints: {
range: 4
}
},
// Need to enable "LocalTimeSystem" plugin to make use of this
// {
// key: "local",
// name: "Time",
// format: "local-format",
// source: "utc",
// hints: {
// domain: 3
// }
// },
{
key: 'sin',
name: 'Sine',
unit: 'Hz',
formatString: '%0.2f',
hints: {
range: 1
}
},
{
key: 'cos',
name: 'Cosine',
unit: 'deg',
formatString: '%0.2f',
hints: {
range: 2
}
},
{
key: 'intensities',
name: 'Intensities',
format: 'number[]',
hints: {
range: 3
}
const METADATA_BY_TYPE = {
generator: {
values: [
{
key: 'name',
name: 'Name',
format: 'string'
},
{
key: 'utc',
name: 'Time',
format: 'utc',
hints: {
domain: 1
}
]
},
'example.state-generator': {
values: [
{
key: 'name',
name: 'Name',
format: 'string'
},
{
key: 'utc',
name: 'Time',
format: 'utc',
hints: {
domain: 1
}
},
{
key: 'local',
name: 'Time',
format: 'utc',
source: 'utc',
hints: {
domain: 2
}
},
{
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
}
},
{
key: 'yesterday',
name: 'Yesterday',
format: 'utc',
hints: {
domain: 2
}
]
}
};
},
{
key: 'wavelengths',
name: 'Wavelength',
unit: 'nm',
format: 'string[]',
hints: {
range: 4
}
},
// Need to enable "LocalTimeSystem" plugin to make use of this
// {
// key: "local",
// name: "Time",
// format: "local-format",
// source: "utc",
// hints: {
// domain: 3
// }
// },
{
key: 'sin',
name: 'Sine',
unit: 'Hz',
formatString: '%0.2f',
hints: {
range: 1
}
},
{
key: 'cos',
name: 'Cosine',
unit: 'deg',
formatString: '%0.2f',
hints: {
range: 2
}
},
{
key: 'intensities',
name: 'Intensities',
format: 'number[]',
hints: {
range: 3
}
}
]
},
'example.state-generator': {
values: [
{
key: 'name',
name: 'Name',
format: 'string'
},
{
key: 'utc',
name: 'Time',
format: 'utc',
hints: {
domain: 1
}
},
{
key: 'local',
name: 'Time',
format: 'utc',
source: 'utc',
hints: {
domain: 2
}
},
{
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() {}
export default function GeneratorMetadataProvider() {}
GeneratorMetadataProvider.prototype.supportsMetadata = function (domainObject) {
return Object.prototype.hasOwnProperty.call(METADATA_BY_TYPE, domainObject.type);
};
GeneratorMetadataProvider.prototype.supportsMetadata = function (domainObject) {
return Object.prototype.hasOwnProperty.call(METADATA_BY_TYPE, domainObject.type);
};
GeneratorMetadataProvider.prototype.getMetadata = function (domainObject) {
return Object.assign({}, domainObject.telemetry, METADATA_BY_TYPE[domainObject.type]);
};
return GeneratorMetadataProvider;
});
GeneratorMetadataProvider.prototype.getMetadata = function (domainObject) {
return Object.assign({}, domainObject.telemetry, METADATA_BY_TYPE[domainObject.type]);
};

View File

@ -20,86 +20,84 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./WorkerInterface'], function (WorkerInterface) {
var REQUEST_DEFAULTS = {
amplitude: 1,
period: 10,
offset: 0,
dataRateInHz: 1,
randomness: 0,
phase: 0,
loadDelay: 0,
infinityValues: false,
exceedFloat32: false
};
import WorkerInterface from './WorkerInterface';
function GeneratorProvider(openmct, StalenessProvider) {
this.openmct = openmct;
this.workerInterface = new WorkerInterface(openmct, StalenessProvider);
}
const REQUEST_DEFAULTS = {
amplitude: 1,
period: 10,
offset: 0,
dataRateInHz: 1,
randomness: 0,
phase: 0,
loadDelay: 0,
infinityValues: false,
exceedFloat32: false
};
GeneratorProvider.prototype.canProvideTelemetry = function (domainObject) {
return domainObject.type === 'generator';
};
export default function GeneratorProvider(openmct, StalenessProvider) {
this.openmct = openmct;
this.workerInterface = new WorkerInterface(openmct, StalenessProvider);
}
GeneratorProvider.prototype.supportsRequest = GeneratorProvider.prototype.supportsSubscribe =
GeneratorProvider.prototype.canProvideTelemetry;
GeneratorProvider.prototype.canProvideTelemetry = function (domainObject) {
return domainObject.type === 'generator';
};
GeneratorProvider.prototype.makeWorkerRequest = function (domainObject, request) {
var props = [
'amplitude',
'period',
'offset',
'dataRateInHz',
'randomness',
'phase',
'loadDelay',
'infinityValues',
'exceedFloat32'
];
GeneratorProvider.prototype.supportsRequest = GeneratorProvider.prototype.supportsSubscribe =
GeneratorProvider.prototype.canProvideTelemetry;
request = request || {};
GeneratorProvider.prototype.makeWorkerRequest = function (domainObject, request) {
var props = [
'amplitude',
'period',
'offset',
'dataRateInHz',
'randomness',
'phase',
'loadDelay',
'infinityValues',
'exceedFloat32'
];
var workerRequest = {};
request = request || {};
props.forEach(function (prop) {
if (
domainObject.telemetry &&
Object.prototype.hasOwnProperty.call(domainObject.telemetry, prop)
) {
workerRequest[prop] = domainObject.telemetry[prop];
}
var workerRequest = {};
if (request && Object.prototype.hasOwnProperty.call(request, prop)) {
workerRequest[prop] = request[prop];
}
props.forEach(function (prop) {
if (
domainObject.telemetry &&
Object.prototype.hasOwnProperty.call(domainObject.telemetry, prop)
) {
workerRequest[prop] = domainObject.telemetry[prop];
}
if (!Object.prototype.hasOwnProperty.call(workerRequest, prop)) {
workerRequest[prop] = REQUEST_DEFAULTS[prop];
}
if (request && Object.prototype.hasOwnProperty.call(request, prop)) {
workerRequest[prop] = request[prop];
}
workerRequest[prop] = Number(workerRequest[prop]);
});
if (!Object.prototype.hasOwnProperty.call(workerRequest, prop)) {
workerRequest[prop] = REQUEST_DEFAULTS[prop];
}
workerRequest.id = this.openmct.objects.makeKeyString(domainObject.identifier);
workerRequest.name = domainObject.name;
workerRequest[prop] = Number(workerRequest[prop]);
});
return workerRequest;
};
workerRequest.id = this.openmct.objects.makeKeyString(domainObject.identifier);
workerRequest.name = domainObject.name;
GeneratorProvider.prototype.request = function (domainObject, request) {
var workerRequest = this.makeWorkerRequest(domainObject, request);
workerRequest.start = request.start;
workerRequest.end = request.end;
return workerRequest;
};
return this.workerInterface.request(workerRequest);
};
GeneratorProvider.prototype.request = function (domainObject, request) {
var workerRequest = this.makeWorkerRequest(domainObject, request);
workerRequest.start = request.start;
workerRequest.end = request.end;
GeneratorProvider.prototype.subscribe = function (domainObject, callback) {
var workerRequest = this.makeWorkerRequest(domainObject, {});
return this.workerInterface.request(workerRequest);
};
return this.workerInterface.subscribe(workerRequest, callback);
};
GeneratorProvider.prototype.subscribe = function (domainObject, callback) {
var workerRequest = this.makeWorkerRequest(domainObject, {});
return GeneratorProvider;
});
return this.workerInterface.subscribe(workerRequest, callback);
};

View File

@ -20,147 +20,143 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
var PURPLE = {
sin: 2.2,
cos: 2.2
var PURPLE = {
sin: 2.2,
cos: 2.2
},
RED = {
sin: 0.9,
cos: 0.9
},
ORANGE = {
sin: 0.7,
cos: 0.7
},
YELLOW = {
sin: 0.5,
cos: 0.5
},
CYAN = {
sin: 0.45,
cos: 0.45
},
LIMITS = {
rh: {
cssClass: 'is-limit--upr is-limit--red',
low: RED,
high: Number.POSITIVE_INFINITY,
name: 'Red High'
},
RED = {
sin: 0.9,
cos: 0.9
rl: {
cssClass: 'is-limit--lwr is-limit--red',
high: -RED,
low: Number.NEGATIVE_INFINITY,
name: 'Red Low'
},
ORANGE = {
sin: 0.7,
cos: 0.7
yh: {
cssClass: 'is-limit--upr is-limit--yellow',
low: YELLOW,
high: RED,
name: 'Yellow High'
},
YELLOW = {
sin: 0.5,
cos: 0.5
},
CYAN = {
sin: 0.45,
cos: 0.45
},
LIMITS = {
rh: {
cssClass: 'is-limit--upr is-limit--red',
low: RED,
high: Number.POSITIVE_INFINITY,
name: 'Red High'
},
rl: {
cssClass: 'is-limit--lwr is-limit--red',
high: -RED,
low: Number.NEGATIVE_INFINITY,
name: 'Red Low'
},
yh: {
cssClass: 'is-limit--upr is-limit--yellow',
low: YELLOW,
high: RED,
name: 'Yellow High'
},
yl: {
cssClass: 'is-limit--lwr is-limit--yellow',
low: -RED,
high: -YELLOW,
name: 'Yellow Low'
}
};
function SinewaveLimitProvider() {}
SinewaveLimitProvider.prototype.supportsLimits = function (domainObject) {
return domainObject.type === 'generator';
yl: {
cssClass: 'is-limit--lwr is-limit--yellow',
low: -RED,
high: -YELLOW,
name: 'Yellow Low'
}
};
SinewaveLimitProvider.prototype.getLimitEvaluator = function (domainObject) {
return {
evaluate: function (datum, valueMetadata) {
var range = valueMetadata && valueMetadata.key;
export default function SinewaveLimitProvider() {}
if (datum[range] > RED[range]) {
return LIMITS.rh;
}
SinewaveLimitProvider.prototype.supportsLimits = function (domainObject) {
return domainObject.type === 'generator';
};
if (datum[range] < -RED[range]) {
return LIMITS.rl;
}
SinewaveLimitProvider.prototype.getLimitEvaluator = function (domainObject) {
return {
evaluate: function (datum, valueMetadata) {
var range = valueMetadata && valueMetadata.key;
if (datum[range] > YELLOW[range]) {
return LIMITS.yh;
}
if (datum[range] < -YELLOW[range]) {
return LIMITS.yl;
}
if (datum[range] > RED[range]) {
return LIMITS.rh;
}
};
};
SinewaveLimitProvider.prototype.getLimits = function (domainObject) {
return {
limits: function () {
return Promise.resolve({
WATCH: {
low: {
color: 'cyan',
sin: -CYAN.sin,
cos: -CYAN.cos
},
high: {
color: 'cyan',
...CYAN
}
if (datum[range] < -RED[range]) {
return LIMITS.rl;
}
if (datum[range] > YELLOW[range]) {
return LIMITS.yh;
}
if (datum[range] < -YELLOW[range]) {
return LIMITS.yl;
}
}
};
};
SinewaveLimitProvider.prototype.getLimits = function (domainObject) {
return {
limits: function () {
return Promise.resolve({
WATCH: {
low: {
color: 'cyan',
sin: -CYAN.sin,
cos: -CYAN.cos
},
WARNING: {
low: {
color: 'yellow',
sin: -YELLOW.sin,
cos: -YELLOW.cos
},
high: {
color: 'yellow',
...YELLOW
}
},
DISTRESS: {
low: {
color: 'orange',
sin: -ORANGE.sin,
cos: -ORANGE.cos
},
high: {
color: 'orange',
...ORANGE
}
},
CRITICAL: {
low: {
color: 'red',
sin: -RED.sin,
cos: -RED.cos
},
high: {
color: 'red',
...RED
}
},
SEVERE: {
low: {
color: 'purple',
sin: -PURPLE.sin,
cos: -PURPLE.cos
},
high: {
color: 'purple',
...PURPLE
}
high: {
color: 'cyan',
...CYAN
}
});
}
};
},
WARNING: {
low: {
color: 'yellow',
sin: -YELLOW.sin,
cos: -YELLOW.cos
},
high: {
color: 'yellow',
...YELLOW
}
},
DISTRESS: {
low: {
color: 'orange',
sin: -ORANGE.sin,
cos: -ORANGE.cos
},
high: {
color: 'orange',
...ORANGE
}
},
CRITICAL: {
low: {
color: 'red',
sin: -RED.sin,
cos: -RED.cos
},
high: {
color: 'red',
...RED
}
},
SEVERE: {
low: {
color: 'purple',
sin: -PURPLE.sin,
cos: -PURPLE.cos
},
high: {
color: 'purple',
...PURPLE
}
}
});
}
};
return SinewaveLimitProvider;
});
};

View File

@ -20,56 +20,52 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
function StateGeneratorProvider() {}
export default function StateGeneratorProvider() {}
function pointForTimestamp(timestamp, duration, name) {
return {
name: name,
utc: Math.floor(timestamp / duration) * duration,
value: Math.floor(timestamp / duration) % 2
};
function pointForTimestamp(timestamp, duration, name) {
return {
name: name,
utc: Math.floor(timestamp / duration) * duration,
value: Math.floor(timestamp / duration) % 2
};
}
StateGeneratorProvider.prototype.supportsSubscribe = function (domainObject) {
return domainObject.type === 'example.state-generator';
};
StateGeneratorProvider.prototype.subscribe = function (domainObject, callback) {
var duration = domainObject.telemetry.duration * 1000;
var interval = setInterval(function () {
var now = Date.now();
var datum = pointForTimestamp(now, duration, domainObject.name);
datum.value = String(datum.value);
callback(datum);
}, duration);
return function () {
clearInterval(interval);
};
};
StateGeneratorProvider.prototype.supportsRequest = function (domainObject, options) {
return domainObject.type === 'example.state-generator';
};
StateGeneratorProvider.prototype.request = function (domainObject, options) {
var start = options.start;
var end = Math.min(Date.now(), options.end); // no future values
var duration = domainObject.telemetry.duration * 1000;
if (options.strategy === 'latest' || options.size === 1) {
start = end;
}
StateGeneratorProvider.prototype.supportsSubscribe = function (domainObject) {
return domainObject.type === 'example.state-generator';
};
var data = [];
while (start <= end && data.length < 5000) {
data.push(pointForTimestamp(start, duration, domainObject.name));
start += duration;
}
StateGeneratorProvider.prototype.subscribe = function (domainObject, callback) {
var duration = domainObject.telemetry.duration * 1000;
var interval = setInterval(function () {
var now = Date.now();
var datum = pointForTimestamp(now, duration, domainObject.name);
datum.value = String(datum.value);
callback(datum);
}, duration);
return function () {
clearInterval(interval);
};
};
StateGeneratorProvider.prototype.supportsRequest = function (domainObject, options) {
return domainObject.type === 'example.state-generator';
};
StateGeneratorProvider.prototype.request = function (domainObject, options) {
var start = options.start;
var end = Math.min(Date.now(), options.end); // no future values
var duration = domainObject.telemetry.duration * 1000;
if (options.strategy === 'latest' || options.size === 1) {
start = end;
}
var data = [];
while (start <= end && data.length < 5000) {
data.push(pointForTimestamp(start, duration, domainObject.name));
start += duration;
}
return Promise.resolve(data);
};
return StateGeneratorProvider;
});
return Promise.resolve(data);
};

View File

@ -20,88 +20,86 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['uuid'], function ({ v4: uuid }) {
function WorkerInterface(openmct, StalenessProvider) {
// eslint-disable-next-line no-undef
const workerUrl = `${openmct.getAssetPath()}${__OPENMCT_ROOT_RELATIVE__}generatorWorker.js`;
this.StalenessProvider = StalenessProvider;
this.worker = new Worker(workerUrl);
this.worker.onmessage = this.onMessage.bind(this);
this.callbacks = {};
this.staleTelemetryIds = {};
import { v4 as uuid } from 'uuid';
this.watchStaleness();
export default function WorkerInterface(openmct, StalenessProvider) {
// eslint-disable-next-line no-undef
const workerUrl = `${openmct.getAssetPath()}${__OPENMCT_ROOT_RELATIVE__}generatorWorker.js`;
this.StalenessProvider = StalenessProvider;
this.worker = new Worker(workerUrl);
this.worker.onmessage = this.onMessage.bind(this);
this.callbacks = {};
this.staleTelemetryIds = {};
this.watchStaleness();
}
WorkerInterface.prototype.watchStaleness = function () {
this.StalenessProvider.on('stalenessEvent', ({ id, isStale }) => {
this.staleTelemetryIds[id] = isStale;
});
};
WorkerInterface.prototype.onMessage = function (message) {
message = message.data;
var callback = this.callbacks[message.id];
if (callback) {
callback(message);
}
};
WorkerInterface.prototype.dispatch = function (request, data, callback) {
var message = {
request: request,
data: data,
id: uuid()
};
if (callback) {
this.callbacks[message.id] = callback;
}
WorkerInterface.prototype.watchStaleness = function () {
this.StalenessProvider.on('stalenessEvent', ({ id, isStale }) => {
this.staleTelemetryIds[id] = isStale;
});
};
this.worker.postMessage(message);
WorkerInterface.prototype.onMessage = function (message) {
message = message.data;
var callback = this.callbacks[message.id];
if (callback) {
callback(message);
}
};
return message.id;
};
WorkerInterface.prototype.dispatch = function (request, data, callback) {
var message = {
request: request,
data: data,
id: uuid()
};
WorkerInterface.prototype.request = function (request) {
var deferred = {};
var promise = new Promise(function (resolve, reject) {
deferred.resolve = resolve;
deferred.reject = reject;
});
var messageId;
if (callback) {
this.callbacks[message.id] = callback;
let self = this;
function callback(message) {
if (message.error) {
deferred.reject(message.error);
} else {
deferred.resolve(message.data);
}
this.worker.postMessage(message);
delete self.callbacks[messageId];
}
return message.id;
};
messageId = this.dispatch('request', request, callback.bind(this));
WorkerInterface.prototype.request = function (request) {
var deferred = {};
var promise = new Promise(function (resolve, reject) {
deferred.resolve = resolve;
deferred.reject = reject;
});
var messageId;
return promise;
};
let self = this;
function callback(message) {
if (message.error) {
deferred.reject(message.error);
} else {
deferred.resolve(message.data);
}
delete self.callbacks[messageId];
WorkerInterface.prototype.subscribe = function (request, cb) {
const { id, loadDelay } = request;
const messageId = this.dispatch('subscribe', request, (message) => {
if (!this.staleTelemetryIds[id]) {
setTimeout(() => cb(message.data), Math.max(loadDelay, 0));
}
});
messageId = this.dispatch('request', request, callback.bind(this));
return promise;
};
WorkerInterface.prototype.subscribe = function (request, cb) {
const { id, loadDelay } = request;
const messageId = this.dispatch('subscribe', request, (message) => {
if (!this.staleTelemetryIds[id]) {
setTimeout(() => cb(message.data), Math.max(loadDelay, 0));
}
return function () {
this.dispatch('unsubscribe', {
id: messageId
});
return function () {
this.dispatch('unsubscribe', {
id: messageId
});
delete this.callbacks[messageId];
}.bind(this);
};
return WorkerInterface;
});
delete this.callbacks[messageId];
}.bind(this);
};

View File

@ -75,7 +75,7 @@ if (document.currentScript) {
* @property {OpenMCTComponent[]} components
*/
const MCT = require('./src/MCT');
const { MCT } = require('./src/MCT');
/** @type {OpenMCT} */
const openmct = new MCT();

View File

@ -20,72 +20,66 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
/* eslint-disable no-undef */
define([
'EventEmitter',
'./api/api',
'./api/overlays/OverlayAPI',
'./api/tooltips/ToolTipAPI',
'./selection/Selection',
'./plugins/plugins',
'./ui/registries/ViewRegistry',
'./plugins/imagery/plugin',
'./ui/registries/InspectorViewRegistry',
'./ui/registries/ToolbarRegistry',
'./ui/router/ApplicationRouter',
'./ui/router/Browse',
'./ui/layout/AppLayout.vue',
'./ui/preview/plugin',
'./api/Branding',
'./plugins/licenses/plugin',
'./plugins/remove/plugin',
'./plugins/move/plugin',
'./plugins/linkAction/plugin',
'./plugins/duplicate/plugin',
'./plugins/importFromJSONAction/plugin',
'./plugins/exportAsJSONAction/plugin',
'vue'
], function (
EventEmitter,
api,
OverlayAPI,
ToolTipAPI,
Selection,
plugins,
ViewRegistry,
ImageryPlugin,
InspectorViewRegistry,
ToolbarRegistry,
ApplicationRouter,
Browse,
Layout,
PreviewPlugin,
BrandingAPI,
LicensesPlugin,
RemoveActionPlugin,
MoveActionPlugin,
LinkActionPlugin,
DuplicateActionPlugin,
ImportFromJSONAction,
ExportAsJSONAction,
Vue
) {
/**
* Open MCT is an extensible web application for building mission
* control user interfaces. This module is itself an instance of
* [MCT]{@link module:openmct.MCT}, which provides an interface for
* configuring and executing the application.
*
* @exports openmct
*/
import EventEmitter from 'EventEmitter';
import { createApp, markRaw } from 'vue';
/**
* The Open MCT application. This may be configured by installing plugins
* or registering extensions before the application is started.
* @constructor
* @memberof module:openmct
*/
function MCT() {
import ActionsAPI from './api/actions/ActionsAPI';
import AnnotationAPI from './api/annotation/AnnotationAPI';
import BrandingAPI from './api/Branding';
import CompositionAPI from './api/composition/CompositionAPI';
import EditorAPI from './api/Editor';
import FaultManagementAPI from './api/faultmanagement/FaultManagementAPI';
import FormsAPI from './api/forms/FormsAPI';
import IndicatorAPI from './api/indicators/IndicatorAPI';
import MenuAPI from './api/menu/MenuAPI';
import NotificationAPI from './api/notifications/NotificationAPI';
import ObjectAPI from './api/objects/ObjectAPI';
import OverlayAPI from './api/overlays/OverlayAPI';
import PriorityAPI from './api/priority/PriorityAPI';
import StatusAPI from './api/status/StatusAPI';
import TelemetryAPI from './api/telemetry/TelemetryAPI';
import TimeAPI from './api/time/TimeAPI';
import ToolTipAPI from './api/tooltips/ToolTipAPI';
import TypeRegistry from './api/types/TypeRegistry';
import UserAPI from './api/user/UserAPI';
import DuplicateActionPlugin from './plugins/duplicate/plugin';
import ExportAsJSONAction from './plugins/exportAsJSONAction/plugin';
import ImageryPlugin from './plugins/imagery/plugin';
import ImportFromJSONAction from './plugins/importFromJSONAction/plugin';
import LicensesPlugin from './plugins/licenses/plugin';
import LinkActionPlugin from './plugins/linkAction/plugin';
import MoveActionPlugin from './plugins/move/plugin';
import plugins from './plugins/plugins';
import RemoveActionPlugin from './plugins/remove/plugin';
import Selection from './selection/Selection';
import Layout from './ui/layout/AppLayout.vue';
import PreviewPlugin from './ui/preview/plugin';
import InspectorViewRegistry from './ui/registries/InspectorViewRegistry';
import ToolbarRegistry from './ui/registries/ToolbarRegistry';
import ViewRegistry from './ui/registries/ViewRegistry';
import ApplicationRouter from './ui/router/ApplicationRouter';
import Browse from './ui/router/Browse';
/**
* Open MCT is an extensible web application for building mission
* control user interfaces. This module is itself an instance of
* [MCT]{@link module:openmct.MCT}, which provides an interface for
* configuring and executing the application.
*
* @exports openmct
*/
/**
* The Open MCT application. This may be configured by installing plugins
* or registering extensions before the application is started.
* @constructor
* @memberof module:openmct
*/
export class MCT extends EventEmitter {
constructor() {
super();
EventEmitter.call(this);
this.buildInfo = {
version: __OPENMCT_VERSION__,
buildDate: __OPENMCT_BUILD_DATE__,
@ -95,169 +89,140 @@ define([
this.destroy = this.destroy.bind(this);
this.defaultClock = 'local';
[
/**
* Tracks current selection state of the application.
* @private
*/
['selection', () => new Selection.default(this)],
/**
* MCT's time conductor, which may be used to synchronize view contents
* for telemetry- or time-based views.
* @type {module:openmct.TimeConductor}
* @memberof module:openmct.MCT#
* @name conductor
*/
['time', () => new api.TimeAPI(this)],
this.plugins = plugins;
/**
* An interface for interacting with the composition of domain objects.
* The composition of a domain object is the list of other domain
* objects it "contains" (for instance, that should be displayed
* beneath it in the tree.)
*
* `composition` may be called as a function, in which case it acts
* as [`composition.get`]{@link module:openmct.CompositionAPI#get}.
*
* @type {module:openmct.CompositionAPI}
* @memberof module:openmct.MCT#
* @name composition
*/
['composition', () => new api.CompositionAPI.default(this)],
/**
* Tracks current selection state of the application.
* @private
*/
this.selection = new Selection(this);
/**
* Registry for views of domain objects which should appear in the
* main viewing area.
*
* @type {module:openmct.ViewRegistry}
* @memberof module:openmct.MCT#
* @name objectViews
*/
['objectViews', () => new ViewRegistry()],
/**
* MCT's time conductor, which may be used to synchronize view contents
* for telemetry- or time-based views.
* @type {module:openmct.TimeConductor}
* @memberof module:openmct.MCT#
* @name conductor
*/
this.time = new TimeAPI(this);
/**
* Registry for views which should appear in the Inspector area.
* These views will be chosen based on the selection state.
*
* @type {module:openmct.InspectorViewRegistry}
* @memberof module:openmct.MCT#
* @name inspectorViews
*/
['inspectorViews', () => new InspectorViewRegistry.default()],
/**
* An interface for interacting with the composition of domain objects.
* The composition of a domain object is the list of other domain
* objects it "contains" (for instance, that should be displayed
* beneath it in the tree.)
*
* `composition` may be called as a function, in which case it acts
* as [`composition.get`]{@link module:openmct.CompositionAPI#get}.
*
* @type {module:openmct.CompositionAPI}
* @memberof module:openmct.MCT#
* @name composition
*/
this.composition = new CompositionAPI(this);
/**
* Registry for views which should appear in Edit Properties
* dialogs, and similar user interface elements used for
* modifying domain objects external to its regular views.
*
* @type {module:openmct.ViewRegistry}
* @memberof module:openmct.MCT#
* @name propertyEditors
*/
['propertyEditors', () => new ViewRegistry()],
/**
* Registry for views of domain objects which should appear in the
* main viewing area.
*
* @type {module:openmct.ViewRegistry}
* @memberof module:openmct.MCT#
* @name objectViews
*/
this.objectViews = new ViewRegistry();
/**
* Registry for views which should appear in the toolbar area while
* editing. These views will be chosen based on the selection state.
*
* @type {module:openmct.ToolbarRegistry}
* @memberof module:openmct.MCT#
* @name toolbars
*/
['toolbars', () => new ToolbarRegistry()],
/**
* Registry for views which should appear in the Inspector area.
* These views will be chosen based on the selection state.
*
* @type {module:openmct.InspectorViewRegistry}
* @memberof module:openmct.MCT#
* @name inspectorViews
*/
this.inspectorViews = new InspectorViewRegistry();
/**
* Registry for domain object types which may exist within this
* instance of Open MCT.
*
* @type {module:openmct.TypeRegistry}
* @memberof module:openmct.MCT#
* @name types
*/
['types', () => new api.TypeRegistry()],
/**
* Registry for views which should appear in Edit Properties
* dialogs, and similar user interface elements used for
* modifying domain objects external to its regular views.
*
* @type {module:openmct.ViewRegistry}
* @memberof module:openmct.MCT#
* @name propertyEditors
*/
this.propertyEditors = new ViewRegistry();
/**
* An interface for interacting with domain objects and the domain
* object hierarchy.
*
* @type {module:openmct.ObjectAPI}
* @memberof module:openmct.MCT#
* @name objects
*/
['objects', () => new api.ObjectAPI.default(this.types, this)],
/**
* Registry for views which should appear in the toolbar area while
* editing. These views will be chosen based on the selection state.
*
* @type {module:openmct.ToolbarRegistry}
* @memberof module:openmct.MCT#
* @name toolbars
*/
this.toolbars = new ToolbarRegistry();
/**
* An interface for retrieving and interpreting telemetry data associated
* with a domain object.
*
* @type {module:openmct.TelemetryAPI}
* @memberof module:openmct.MCT#
* @name telemetry
*/
['telemetry', () => new api.TelemetryAPI.default(this)],
/**
* Registry for domain object types which may exist within this
* instance of Open MCT.
*
* @type {module:openmct.TypeRegistry}
* @memberof module:openmct.MCT#
* @name types
*/
this.types = new TypeRegistry();
/**
* An interface for creating new indicators and changing them dynamically.
*
* @type {module:openmct.IndicatorAPI}
* @memberof module:openmct.MCT#
* @name indicators
*/
['indicators', () => new api.IndicatorAPI(this)],
/**
* An interface for interacting with domain objects and the domain
* object hierarchy.
*
* @type {module:openmct.ObjectAPI}
* @memberof module:openmct.MCT#
* @name objects
*/
this.objects = new ObjectAPI(this.types, this);
/**
* MCT's user awareness management, to enable user and
* role specific functionality.
* @type {module:openmct.UserAPI}
* @memberof module:openmct.MCT#
* @name user
*/
['user', () => new api.UserAPI(this)],
/**
* An interface for retrieving and interpreting telemetry data associated
* with a domain object.
*
* @type {module:openmct.TelemetryAPI}
* @memberof module:openmct.MCT#
* @name telemetry
*/
this.telemetry = new TelemetryAPI(this);
['notifications', () => new api.NotificationAPI()],
/**
* An interface for creating new indicators and changing them dynamically.
*
* @type {module:openmct.IndicatorAPI}
* @memberof module:openmct.MCT#
* @name indicators
*/
this.indicators = new IndicatorAPI(this);
['editor', () => new api.EditorAPI.default(this)],
/**
* MCT's user awareness management, to enable user and
* role specific functionality.
* @type {module:openmct.UserAPI}
* @memberof module:openmct.MCT#
* @name user
*/
this.user = new UserAPI(this);
['overlays', () => new OverlayAPI.default()],
['tooltips', () => new ToolTipAPI.default()],
['menus', () => new api.MenuAPI(this)],
['actions', () => new api.ActionsAPI(this)],
['status', () => new api.StatusAPI(this)],
['priority', () => api.PriorityAPI],
['router', () => new ApplicationRouter(this)],
['faults', () => new api.FaultManagementAPI.default(this)],
['forms', () => new api.FormsAPI.default(this)],
['branding', () => BrandingAPI.default],
/**
* MCT's annotation API that enables
* human-created comments and categorization linked to data products
* @type {module:openmct.AnnotationAPI}
* @memberof module:openmct.MCT#
* @name annotation
*/
['annotation', () => new api.AnnotationAPI(this)]
].forEach((apiEntry) => {
const apiName = apiEntry[0];
const apiObject = apiEntry[1]();
Object.defineProperty(this, apiName, {
value: apiObject,
enumerable: false,
configurable: false,
writable: true
});
});
this.notifications = new NotificationAPI();
this.editor = new EditorAPI(this);
this.overlays = new OverlayAPI();
this.tooltips = new ToolTipAPI();
this.menus = new MenuAPI(this);
this.actions = new ActionsAPI(this);
this.status = new StatusAPI(this);
this.priority = PriorityAPI;
this.router = new ApplicationRouter(this);
this.faults = new FaultManagementAPI(this);
this.forms = new FormsAPI(this);
this.branding = BrandingAPI;
/**
* MCT's annotation API that enables
@ -266,23 +231,23 @@ define([
* @memberof module:openmct.MCT#
* @name annotation
*/
this.annotation = new api.AnnotationAPI(this);
this.annotation = new AnnotationAPI(this);
// Plugins that are installed by default
this.install(this.plugins.Plot());
this.install(this.plugins.TelemetryTable.default());
this.install(PreviewPlugin.default());
this.install(LicensesPlugin.default());
this.install(RemoveActionPlugin.default());
this.install(MoveActionPlugin.default());
this.install(LinkActionPlugin.default());
this.install(DuplicateActionPlugin.default());
this.install(ExportAsJSONAction.default());
this.install(ImportFromJSONAction.default());
this.install(this.plugins.FormActions.default());
this.install(this.plugins.TelemetryTable());
this.install(PreviewPlugin());
this.install(LicensesPlugin());
this.install(RemoveActionPlugin());
this.install(MoveActionPlugin());
this.install(LinkActionPlugin());
this.install(DuplicateActionPlugin());
this.install(ExportAsJSONAction());
this.install(ImportFromJSONAction());
this.install(this.plugins.FormActions());
this.install(this.plugins.FolderView());
this.install(this.plugins.Tabs());
this.install(ImageryPlugin.default());
this.install(ImageryPlugin());
this.install(this.plugins.FlexibleLayout());
this.install(this.plugins.GoToOriginalAction());
this.install(this.plugins.OpenInNewTabAction());
@ -300,26 +265,20 @@ define([
this.install(this.plugins.Gauge());
this.install(this.plugins.InspectorViews());
}
MCT.prototype = Object.create(EventEmitter.prototype);
MCT.prototype.MCT = MCT;
/**
* Set path to where assets are hosted. This should be the path to main.js.
* @memberof module:openmct.MCT#
* @method setAssetPath
*/
MCT.prototype.setAssetPath = function (assetPath) {
setAssetPath(assetPath) {
this._assetPath = assetPath;
};
}
/**
* Get path to where assets are hosted.
* @memberof module:openmct.MCT#
* @method getAssetPath
*/
MCT.prototype.getAssetPath = function () {
getAssetPath() {
const assetPathLength = this._assetPath && this._assetPath.length;
if (!assetPathLength) {
return '/';
@ -330,8 +289,7 @@ define([
}
return this._assetPath;
};
}
/**
* Start running Open MCT. This should be called only after any plugins
* have been installed.
@ -341,10 +299,7 @@ define([
* @param {HTMLElement} [domElement] the DOM element in which to run
* MCT; if undefined, MCT will be run in the body of the document
*/
MCT.prototype.start = function (
domElement = document.body.firstElementChild,
isHeadlessMode = false
) {
start(domElement = document.body.firstElementChild, isHeadlessMode = false) {
// Create element to mount Layout if it doesn't exist
if (domElement === null) {
domElement = document.createElement('div');
@ -376,20 +331,12 @@ define([
* @event start
* @memberof module:openmct.MCT~
*/
if (!isHeadlessMode) {
const appLayout = Vue.createApp({
components: {
Layout: Layout.default
},
provide: {
openmct: Vue.markRaw(this)
},
template: '<Layout ref="layout"></Layout>'
});
const appLayout = createApp(Layout);
appLayout.provide('openmct', markRaw(this));
const component = appLayout.mount(domElement);
component.$nextTick(() => {
this.layout = component.$refs.layout;
this.layout = component;
this.app = appLayout;
Browse(this);
window.addEventListener('beforeunload', this.destroy);
@ -402,14 +349,12 @@ define([
this.router.start();
this.emit('start');
}
};
MCT.prototype.startHeadless = function () {
}
startHeadless() {
let unreachableNode = document.createElement('div');
return this.start(unreachableNode, true);
};
}
/**
* Install a plugin in MCT.
*
@ -417,17 +362,13 @@ define([
* invoked with the mct instance.
* @memberof module:openmct.MCT#
*/
MCT.prototype.install = function (plugin) {
install(plugin) {
plugin(this);
};
}
MCT.prototype.destroy = function () {
destroy() {
window.removeEventListener('beforeunload', this.destroy);
this.emit('destroy');
this.router.destroy();
};
MCT.prototype.plugins = plugins;
return MCT;
});
}
}

View File

@ -20,96 +20,98 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./plugins/plugins', 'utils/testing'], function (plugins, testUtils) {
describe('MCT', function () {
let openmct;
let mockPlugin;
let mockPlugin2;
let mockListener;
import * as testUtils from 'utils/testing';
beforeEach(function () {
mockPlugin = jasmine.createSpy('plugin');
mockPlugin2 = jasmine.createSpy('plugin2');
mockListener = jasmine.createSpy('listener');
import plugins from './plugins/plugins';
openmct = testUtils.createOpenMct();
describe('MCT', function () {
let openmct;
let mockPlugin;
let mockPlugin2;
let mockListener;
openmct.install(mockPlugin);
openmct.install(mockPlugin2);
openmct.on('start', mockListener);
beforeEach(function () {
mockPlugin = jasmine.createSpy('plugin');
mockPlugin2 = jasmine.createSpy('plugin2');
mockListener = jasmine.createSpy('listener');
openmct = testUtils.createOpenMct();
openmct.install(mockPlugin);
openmct.install(mockPlugin2);
openmct.on('start', mockListener);
});
// Clean up the dirty singleton.
afterEach(function () {
return testUtils.resetApplicationState(openmct);
});
it('exposes plugins', function () {
expect(openmct.plugins).toEqual(plugins);
});
it('does not issue a start event before started', function () {
expect(mockListener).not.toHaveBeenCalled();
});
describe('start', function () {
let appHolder;
beforeEach(function (done) {
appHolder = document.createElement('div');
openmct.on('start', done);
openmct.start(appHolder);
});
// Clean up the dirty singleton.
afterEach(function () {
return testUtils.resetApplicationState(openmct);
it('calls plugins for configuration', function () {
expect(mockPlugin).toHaveBeenCalledWith(openmct);
expect(mockPlugin2).toHaveBeenCalledWith(openmct);
});
it('exposes plugins', function () {
expect(openmct.plugins).toEqual(plugins);
it('emits a start event', function () {
expect(mockListener).toHaveBeenCalled();
});
it('does not issue a start event before started', function () {
expect(mockListener).not.toHaveBeenCalled();
it('Renders the application into the provided container element', function () {
let openMctShellElements = appHolder.querySelectorAll('div.l-shell');
expect(openMctShellElements.length).toBe(1);
});
});
describe('startHeadless', function () {
beforeEach(function (done) {
openmct.on('start', done);
openmct.startHeadless();
});
describe('start', function () {
let appHolder;
beforeEach(function (done) {
appHolder = document.createElement('div');
openmct.on('start', done);
openmct.start(appHolder);
});
it('calls plugins for configuration', function () {
expect(mockPlugin).toHaveBeenCalledWith(openmct);
expect(mockPlugin2).toHaveBeenCalledWith(openmct);
});
it('emits a start event', function () {
expect(mockListener).toHaveBeenCalled();
});
it('Renders the application into the provided container element', function () {
let openMctShellElements = appHolder.querySelectorAll('div.l-shell');
expect(openMctShellElements.length).toBe(1);
});
it('calls plugins for configuration', function () {
expect(mockPlugin).toHaveBeenCalledWith(openmct);
expect(mockPlugin2).toHaveBeenCalledWith(openmct);
});
describe('startHeadless', function () {
beforeEach(function (done) {
openmct.on('start', done);
openmct.startHeadless();
});
it('calls plugins for configuration', function () {
expect(mockPlugin).toHaveBeenCalledWith(openmct);
expect(mockPlugin2).toHaveBeenCalledWith(openmct);
});
it('emits a start event', function () {
expect(mockListener).toHaveBeenCalled();
});
it('Does not render Open MCT', function () {
let openMctShellElements = document.body.querySelectorAll('div.l-shell');
expect(openMctShellElements.length).toBe(0);
});
it('emits a start event', function () {
expect(mockListener).toHaveBeenCalled();
});
describe('setAssetPath', function () {
let testAssetPath;
it('Does not render Open MCT', function () {
let openMctShellElements = document.body.querySelectorAll('div.l-shell');
expect(openMctShellElements.length).toBe(0);
});
});
it('configures the path for assets', function () {
testAssetPath = 'some/path/';
openmct.setAssetPath(testAssetPath);
expect(openmct.getAssetPath()).toBe(testAssetPath);
});
describe('setAssetPath', function () {
let testAssetPath;
it('adds a trailing /', function () {
testAssetPath = 'some/path';
openmct.setAssetPath(testAssetPath);
expect(openmct.getAssetPath()).toBe(testAssetPath + '/');
});
it('configures the path for assets', function () {
testAssetPath = 'some/path/';
openmct.setAssetPath(testAssetPath);
expect(openmct.getAssetPath()).toBe(testAssetPath);
});
it('adds a trailing /', function () {
testAssetPath = 'some/path';
openmct.setAssetPath(testAssetPath);
expect(openmct.getAssetPath()).toBe(testAssetPath + '/');
});
});
});

View File

@ -20,24 +20,24 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'./actions/ActionsAPI',
'./composition/CompositionAPI',
'./Editor',
'./faultmanagement/FaultManagementAPI',
'./forms/FormsAPI',
'./indicators/IndicatorAPI',
'./menu/MenuAPI',
'./notifications/NotificationAPI',
'./objects/ObjectAPI',
'./priority/PriorityAPI',
'./status/StatusAPI',
'./telemetry/TelemetryAPI',
'./time/TimeAPI',
'./types/TypeRegistry',
'./user/UserAPI',
'./annotation/AnnotationAPI'
], function (
import ActionsAPI from './actions/ActionsAPI';
import AnnotationAPI from './annotation/AnnotationAPI';
import CompositionAPI from './composition/CompositionAPI';
import EditorAPI from './Editor';
import FaultManagementAPI from './faultmanagement/FaultManagementAPI';
import FormsAPI from './forms/FormsAPI';
import IndicatorAPI from './indicators/IndicatorAPI';
import MenuAPI from './menu/MenuAPI';
import NotificationAPI from './notifications/NotificationAPI';
import ObjectAPI from './objects/ObjectAPI';
import PriorityAPI from './priority/PriorityAPI';
import StatusAPI from './status/StatusAPI';
import TelemetryAPI from './telemetry/TelemetryAPI';
import TimeAPI from './time/TimeAPI';
import TypeRegistry from './types/TypeRegistry';
import UserAPI from './user/UserAPI';
export default {
ActionsAPI,
CompositionAPI,
EditorAPI,
@ -54,23 +54,4 @@ define([
TypeRegistry,
UserAPI,
AnnotationAPI
) {
return {
ActionsAPI: ActionsAPI.default,
CompositionAPI: CompositionAPI,
EditorAPI: EditorAPI,
FaultManagementAPI: FaultManagementAPI,
FormsAPI: FormsAPI,
IndicatorAPI: IndicatorAPI.default,
MenuAPI: MenuAPI.default,
NotificationAPI: NotificationAPI.default,
ObjectAPI: ObjectAPI,
PriorityAPI: PriorityAPI.default,
StatusAPI: StatusAPI.default,
TelemetryAPI: TelemetryAPI,
TimeAPI: TimeAPI.default,
TypeRegistry: TypeRegistry.default,
UserAPI: UserAPI.default,
AnnotationAPI: AnnotationAPI.default
};
});
};

View File

@ -20,163 +20,161 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
/**
* Utility for checking if a thing is an Open MCT Identifier.
* @private
*/
function isIdentifier(thing) {
return (
typeof thing === 'object' &&
Object.prototype.hasOwnProperty.call(thing, 'key') &&
Object.prototype.hasOwnProperty.call(thing, 'namespace')
);
/**
* Utility for checking if a thing is an Open MCT Identifier.
* @private
*/
function isIdentifier(thing) {
return (
typeof thing === 'object' &&
Object.prototype.hasOwnProperty.call(thing, 'key') &&
Object.prototype.hasOwnProperty.call(thing, 'namespace')
);
}
/**
* Utility for checking if a thing is a key string. Not perfect.
* @private
*/
function isKeyString(thing) {
return typeof thing === 'string';
}
/**
* Convert a keyString into an Open MCT Identifier, ex:
* 'scratch:root' ==> {namespace: 'scratch', key: 'root'}
*
* Idempotent.
*
* @param keyString
* @returns identifier
*/
function parseKeyString(keyString) {
if (isIdentifier(keyString)) {
return keyString;
}
/**
* Utility for checking if a thing is a key string. Not perfect.
* @private
*/
function isKeyString(thing) {
return typeof thing === 'string';
let namespace = '';
let key = keyString;
for (let i = 0; i < key.length; i++) {
if (key[i] === '\\' && key[i + 1] === ':') {
i++; // skip escape character.
} else if (key[i] === ':') {
key = key.slice(i + 1);
break;
}
namespace += key[i];
}
/**
* Convert a keyString into an Open MCT Identifier, ex:
* 'scratch:root' ==> {namespace: 'scratch', key: 'root'}
*
* Idempotent.
*
* @param keyString
* @returns identifier
*/
function parseKeyString(keyString) {
if (isIdentifier(keyString)) {
return keyString;
}
let namespace = '';
let key = keyString;
for (let i = 0; i < key.length; i++) {
if (key[i] === '\\' && key[i + 1] === ':') {
i++; // skip escape character.
} else if (key[i] === ':') {
key = key.slice(i + 1);
break;
}
namespace += key[i];
}
if (keyString === namespace) {
namespace = '';
}
return {
namespace: namespace,
key: key
};
}
/**
* Convert an Open MCT Identifier into a keyString, ex:
* {namespace: 'scratch', key: 'root'} ==> 'scratch:root'
*
* Idempotent
*
* @param identifier
* @returns keyString
*/
function makeKeyString(identifier) {
if (!identifier) {
throw new Error('Cannot make key string from null identifier');
}
if (isKeyString(identifier)) {
return identifier;
}
if (!identifier.namespace) {
return identifier.key;
}
return [identifier.namespace.replace(/:/g, '\\:'), identifier.key].join(':');
}
/**
* Convert a new domain object into an old format model, removing the
* identifier and converting the composition array from Open MCT Identifiers
* to old format keyStrings.
*
* @param domainObject
* @returns oldFormatModel
*/
function toOldFormat(model) {
model = JSON.parse(JSON.stringify(model));
delete model.identifier;
if (model.composition) {
model.composition = model.composition.map(makeKeyString);
}
return model;
}
/**
* Convert an old format domain object model into a new format domain
* object. Adds an identifier using the provided keyString, and converts
* the composition array to utilize Open MCT Identifiers.
*
* @param model
* @param keyString
* @returns domainObject
*/
function toNewFormat(model, keyString) {
model = JSON.parse(JSON.stringify(model));
model.identifier = parseKeyString(keyString);
if (model.composition) {
model.composition = model.composition.map(parseKeyString);
}
return model;
}
/**
* Compare two Open MCT Identifiers, returning true if they are equal.
*
* @param identifier
* @param otherIdentifier
* @returns Boolean true if identifiers are equal.
*/
function identifierEquals(a, b) {
return a.key === b.key && a.namespace === b.namespace;
}
/**
* Compare two domain objects, return true if they're the same object.
* Equality is determined by identifier.
*
* @param domainObject
* @param otherDomainOBject
* @returns Boolean true if objects are equal.
*/
function objectEquals(a, b) {
return identifierEquals(a.identifier, b.identifier);
}
function refresh(oldObject, newObject) {
let deleted = _.difference(Object.keys(oldObject), Object.keys(newObject));
deleted.forEach((propertyName) => delete oldObject[propertyName]);
Object.assign(oldObject, newObject);
if (keyString === namespace) {
namespace = '';
}
return {
isIdentifier: isIdentifier,
toOldFormat: toOldFormat,
toNewFormat: toNewFormat,
makeKeyString: makeKeyString,
parseKeyString: parseKeyString,
equals: objectEquals,
identifierEquals: identifierEquals,
refresh: refresh
namespace: namespace,
key: key
};
});
}
/**
* Convert an Open MCT Identifier into a keyString, ex:
* {namespace: 'scratch', key: 'root'} ==> 'scratch:root'
*
* Idempotent
*
* @param identifier
* @returns keyString
*/
function makeKeyString(identifier) {
if (!identifier) {
throw new Error('Cannot make key string from null identifier');
}
if (isKeyString(identifier)) {
return identifier;
}
if (!identifier.namespace) {
return identifier.key;
}
return [identifier.namespace.replace(/:/g, '\\:'), identifier.key].join(':');
}
/**
* Convert a new domain object into an old format model, removing the
* identifier and converting the composition array from Open MCT Identifiers
* to old format keyStrings.
*
* @param domainObject
* @returns oldFormatModel
*/
function toOldFormat(model) {
model = JSON.parse(JSON.stringify(model));
delete model.identifier;
if (model.composition) {
model.composition = model.composition.map(makeKeyString);
}
return model;
}
/**
* Convert an old format domain object model into a new format domain
* object. Adds an identifier using the provided keyString, and converts
* the composition array to utilize Open MCT Identifiers.
*
* @param model
* @param keyString
* @returns domainObject
*/
function toNewFormat(model, keyString) {
model = JSON.parse(JSON.stringify(model));
model.identifier = parseKeyString(keyString);
if (model.composition) {
model.composition = model.composition.map(parseKeyString);
}
return model;
}
/**
* Compare two Open MCT Identifiers, returning true if they are equal.
*
* @param identifier
* @param otherIdentifier
* @returns Boolean true if identifiers are equal.
*/
function identifierEquals(a, b) {
return a.key === b.key && a.namespace === b.namespace;
}
/**
* Compare two domain objects, return true if they're the same object.
* Equality is determined by identifier.
*
* @param domainObject
* @param otherDomainOBject
* @returns Boolean true if objects are equal.
*/
function objectEquals(a, b) {
return identifierEquals(a.identifier, b.identifier);
}
function refresh(oldObject, newObject) {
let deleted = _.difference(Object.keys(oldObject), Object.keys(newObject));
deleted.forEach((propertyName) => delete oldObject[propertyName]);
Object.assign(oldObject, newObject);
}
export default {
isIdentifier: isIdentifier,
toOldFormat: toOldFormat,
toNewFormat: toNewFormat,
makeKeyString: makeKeyString,
parseKeyString: parseKeyString,
equals: objectEquals,
identifierEquals: identifierEquals,
refresh: refresh
};

View File

@ -1,89 +1,127 @@
define(['objectUtils'], function (objectUtils) {
describe('objectUtils', function () {
describe('keyString util', function () {
const EXPECTATIONS = {
ROOT: {
import objectUtils from 'objectUtils';
describe('objectUtils', function () {
describe('keyString util', function () {
const EXPECTATIONS = {
ROOT: {
namespace: '',
key: 'ROOT'
},
mine: {
namespace: '',
key: 'mine'
},
'extended:something:with:colons': {
key: 'something:with:colons',
namespace: 'extended'
},
'https\\://some/url:resourceId': {
key: 'resourceId',
namespace: 'https://some/url'
},
'scratch:root': {
namespace: 'scratch',
key: 'root'
},
'thingy\\:thing:abc123': {
namespace: 'thingy:thing',
key: 'abc123'
}
};
Object.keys(EXPECTATIONS).forEach(function (keyString) {
it('parses "' + keyString + '".', function () {
expect(objectUtils.parseKeyString(keyString)).toEqual(EXPECTATIONS[keyString]);
});
it('parses and re-encodes "' + keyString + '"', function () {
const identifier = objectUtils.parseKeyString(keyString);
expect(objectUtils.makeKeyString(identifier)).toEqual(keyString);
});
it('is idempotent for "' + keyString + '".', function () {
const identifier = objectUtils.parseKeyString(keyString);
let again = objectUtils.parseKeyString(identifier);
expect(identifier).toEqual(again);
again = objectUtils.parseKeyString(again);
again = objectUtils.parseKeyString(again);
expect(identifier).toEqual(again);
let againKeyString = objectUtils.makeKeyString(again);
expect(againKeyString).toEqual(keyString);
againKeyString = objectUtils.makeKeyString(againKeyString);
againKeyString = objectUtils.makeKeyString(againKeyString);
againKeyString = objectUtils.makeKeyString(againKeyString);
expect(againKeyString).toEqual(keyString);
});
});
});
describe('old object conversions', function () {
it('translate ids', function () {
expect(
objectUtils.toNewFormat(
{
prop: 'someValue'
},
'objId'
)
).toEqual({
prop: 'someValue',
identifier: {
namespace: '',
key: 'ROOT'
},
mine: {
namespace: '',
key: 'mine'
},
'extended:something:with:colons': {
key: 'something:with:colons',
namespace: 'extended'
},
'https\\://some/url:resourceId': {
key: 'resourceId',
namespace: 'https://some/url'
},
'scratch:root': {
namespace: 'scratch',
key: 'root'
},
'thingy\\:thing:abc123': {
namespace: 'thingy:thing',
key: 'abc123'
key: 'objId'
}
};
Object.keys(EXPECTATIONS).forEach(function (keyString) {
it('parses "' + keyString + '".', function () {
expect(objectUtils.parseKeyString(keyString)).toEqual(EXPECTATIONS[keyString]);
});
it('parses and re-encodes "' + keyString + '"', function () {
const identifier = objectUtils.parseKeyString(keyString);
expect(objectUtils.makeKeyString(identifier)).toEqual(keyString);
});
it('is idempotent for "' + keyString + '".', function () {
const identifier = objectUtils.parseKeyString(keyString);
let again = objectUtils.parseKeyString(identifier);
expect(identifier).toEqual(again);
again = objectUtils.parseKeyString(again);
again = objectUtils.parseKeyString(again);
expect(identifier).toEqual(again);
let againKeyString = objectUtils.makeKeyString(again);
expect(againKeyString).toEqual(keyString);
againKeyString = objectUtils.makeKeyString(againKeyString);
againKeyString = objectUtils.makeKeyString(againKeyString);
againKeyString = objectUtils.makeKeyString(againKeyString);
expect(againKeyString).toEqual(keyString);
});
});
});
describe('old object conversions', function () {
it('translate ids', function () {
expect(
objectUtils.toNewFormat(
{
prop: 'someValue'
},
'objId'
)
).toEqual({
it('translates composition', function () {
expect(
objectUtils.toNewFormat(
{
prop: 'someValue',
composition: ['anotherObjectId', 'scratch:anotherObjectId']
},
'objId'
)
).toEqual({
prop: 'someValue',
composition: [
{
namespace: '',
key: 'anotherObjectId'
},
{
namespace: 'scratch',
key: 'anotherObjectId'
}
],
identifier: {
namespace: '',
key: 'objId'
}
});
});
});
describe('new object conversions', function () {
it('removes ids', function () {
expect(
objectUtils.toOldFormat({
prop: 'someValue',
identifier: {
namespace: '',
key: 'objId'
}
});
})
).toEqual({
prop: 'someValue'
});
});
it('translates composition', function () {
expect(
objectUtils.toNewFormat(
{
prop: 'someValue',
composition: ['anotherObjectId', 'scratch:anotherObjectId']
},
'objId'
)
).toEqual({
it('translates composition', function () {
expect(
objectUtils.toOldFormat({
prop: 'someValue',
composition: [
{
@ -99,48 +137,10 @@ define(['objectUtils'], function (objectUtils) {
namespace: '',
key: 'objId'
}
});
});
});
describe('new object conversions', function () {
it('removes ids', function () {
expect(
objectUtils.toOldFormat({
prop: 'someValue',
identifier: {
namespace: '',
key: 'objId'
}
})
).toEqual({
prop: 'someValue'
});
});
it('translates composition', function () {
expect(
objectUtils.toOldFormat({
prop: 'someValue',
composition: [
{
namespace: '',
key: 'anotherObjectId'
},
{
namespace: 'scratch',
key: 'anotherObjectId'
}
],
identifier: {
namespace: '',
key: 'objId'
}
})
).toEqual({
prop: 'someValue',
composition: ['anotherObjectId', 'scratch:anotherObjectId']
});
})
).toEqual({
prop: 'someValue',
composition: ['anotherObjectId', 'scratch:anotherObjectId']
});
});
});

View File

@ -20,17 +20,19 @@
* 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) {
import _ from 'lodash';
/**
* 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.
*/
export default class DefaultMetadataProvider {
constructor(openmct) {
this.openmct = openmct;
}
@ -38,65 +40,14 @@ define(['lodash'], function (_) {
* Applies to any domain object with a telemetry property, or whose type
* definition has a telemetry property.
*/
DefaultMetadataProvider.prototype.supportsMetadata = function (domainObject) {
supportsMetadata(domainObject) {
return Boolean(domainObject.telemetry) || Boolean(this.typeHasTelemetry(domainObject));
};
/**
* Retrieves valueMetadata from legacy metadata.
* @private
*/
function valueMetadatasFromOldFormat(metadata) {
const valueMetadatas = [];
valueMetadatas.push({
key: 'name',
name: 'Name'
});
metadata.domains.forEach(function (domain, index) {
const valueMetadata = _.clone(domain);
valueMetadata.hints = {
domain: index + 1
};
valueMetadatas.push(valueMetadata);
});
metadata.ranges.forEach(function (range, index) {
const 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: Number(e.value)
};
}),
'e.value'
);
valueMetadata.values = valueMetadata.enumerations.map((e) => e.value);
valueMetadata.max = Math.max(valueMetadata.values);
valueMetadata.min = Math.min(valueMetadata.values);
}
valueMetadatas.push(valueMetadata);
});
return valueMetadatas;
}
/**
* Returns telemetry metadata for a given domain object.
*/
DefaultMetadataProvider.prototype.getMetadata = function (domainObject) {
getMetadata(domainObject) {
const metadata = domainObject.telemetry || {};
if (this.typeHasTelemetry(domainObject)) {
const typeMetadata = this.openmct.types.get(domainObject.type).definition.telemetry;
@ -109,16 +60,65 @@ define(['lodash'], function (_) {
}
return metadata;
};
}
/**
* @private
*/
DefaultMetadataProvider.prototype.typeHasTelemetry = function (domainObject) {
typeHasTelemetry(domainObject) {
const type = this.openmct.types.get(domainObject.type);
return Boolean(type.definition.telemetry);
};
}
}
return DefaultMetadataProvider;
});
/**
* Retrieves valueMetadata from legacy metadata.
* @private
*/
function valueMetadatasFromOldFormat(metadata) {
const valueMetadatas = [];
valueMetadatas.push({
key: 'name',
name: 'Name'
});
metadata.domains.forEach(function (domain, index) {
const valueMetadata = _.clone(domain);
valueMetadata.hints = {
domain: index + 1
};
valueMetadatas.push(valueMetadata);
});
metadata.ranges.forEach(function (range, index) {
const 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: Number(e.value)
};
}),
'e.value'
);
valueMetadata.values = valueMetadata.enumerations.map((e) => e.value);
valueMetadata.max = Math.max(valueMetadata.values);
valueMetadata.min = Math.min(valueMetadata.values);
}
valueMetadatas.push(valueMetadata);
});
return valueMetadatas;
}

View File

@ -20,143 +20,141 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['lodash'], function (_) {
function applyReasonableDefaults(valueMetadata, index) {
valueMetadata.source = valueMetadata.source || valueMetadata.key;
valueMetadata.hints = valueMetadata.hints || {};
import _ from 'lodash';
if (Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'x')) {
if (!Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'domain')) {
valueMetadata.hints.domain = valueMetadata.hints.x;
}
function applyReasonableDefaults(valueMetadata, index) {
valueMetadata.source = valueMetadata.source || valueMetadata.key;
valueMetadata.hints = valueMetadata.hints || {};
delete valueMetadata.hints.x;
if (Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'x')) {
if (!Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'domain')) {
valueMetadata.hints.domain = valueMetadata.hints.x;
}
if (Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'y')) {
if (!Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'range')) {
valueMetadata.hints.range = valueMetadata.hints.y;
}
delete valueMetadata.hints.y;
}
if (valueMetadata.format === 'enum') {
if (!valueMetadata.values) {
valueMetadata.values = valueMetadata.enumerations.map((e) => e.value);
}
if (!Object.prototype.hasOwnProperty.call(valueMetadata, 'max')) {
valueMetadata.max = Math.max(valueMetadata.values) + 1;
}
if (!Object.prototype.hasOwnProperty.call(valueMetadata, 'min')) {
valueMetadata.min = Math.min(valueMetadata.values) - 1;
}
}
if (!Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'priority')) {
valueMetadata.hints.priority = index;
}
return valueMetadata;
delete valueMetadata.hints.x;
}
/**
* 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(metadata) {
this.metadata = metadata;
if (Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'y')) {
if (!Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'range')) {
valueMetadata.hints.range = valueMetadata.hints.y;
}
this.valueMetadatas = this.metadata.values
? this.metadata.values.map(applyReasonableDefaults)
: [];
delete valueMetadata.hints.y;
}
/**
* Get value metadata for a single key.
*/
TelemetryMetadataManager.prototype.value = function (key) {
return this.valueMetadatas.filter(function (metadata) {
return metadata.key === key;
if (valueMetadata.format === 'enum') {
if (!valueMetadata.values) {
valueMetadata.values = valueMetadata.enumerations.map((e) => e.value);
}
if (!Object.prototype.hasOwnProperty.call(valueMetadata, 'max')) {
valueMetadata.max = Math.max(valueMetadata.values) + 1;
}
if (!Object.prototype.hasOwnProperty.call(valueMetadata, 'min')) {
valueMetadata.min = Math.min(valueMetadata.values) - 1;
}
}
if (!Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'priority')) {
valueMetadata.hints.priority = index;
}
return valueMetadata;
}
/**
* 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.
*/
export default function TelemetryMetadataManager(metadata) {
this.metadata = metadata;
this.valueMetadatas = this.metadata.values
? this.metadata.values.map(applyReasonableDefaults)
: [];
}
/**
* Get value metadata for a single key.
*/
TelemetryMetadataManager.prototype.value = function (key) {
return this.valueMetadatas.filter(function (metadata) {
return metadata.key === key;
})[0];
};
/**
* Returns all value metadatas, sorted by priority.
*/
TelemetryMetadataManager.prototype.values = function () {
return this.valuesForHints(['priority']);
};
/**
* Get an array of valueMetadatas that possess all hints requested.
* Array is sorted based on hint priority.
*
*/
TelemetryMetadataManager.prototype.valuesForHints = function (hints) {
function hasHint(hint) {
// eslint-disable-next-line no-invalid-this
return Object.prototype.hasOwnProperty.call(this.hints, hint);
}
function hasHints(metadata) {
return hints.every(hasHint, metadata);
}
const matchingMetadata = this.valueMetadatas.filter(hasHints);
let iteratees = hints.map((hint) => {
return (metadata) => {
return metadata.hints[hint];
};
});
return _.sortBy(matchingMetadata, ...iteratees);
};
/**
* check out of a given metadata has array values
*/
TelemetryMetadataManager.prototype.isArrayValue = function (metadata) {
const regex = /\[\]$/g;
if (!metadata.format && !metadata.formatString) {
return false;
}
return (metadata.format || metadata.formatString).match(regex) !== null;
};
TelemetryMetadataManager.prototype.getFilterableValues = function () {
return this.valueMetadatas.filter(
(metadatum) => metadatum.filters && metadatum.filters.length > 0
);
};
TelemetryMetadataManager.prototype.getUseToUpdateInPlaceValue = function () {
return this.valueMetadatas.find(this.isInPlaceUpdateValue);
};
TelemetryMetadataManager.prototype.isInPlaceUpdateValue = function (metadatum) {
return metadatum.useToUpdateInPlace === true;
};
TelemetryMetadataManager.prototype.getDefaultDisplayValue = function () {
let valueMetadata = this.valuesForHints(['range'])[0];
if (valueMetadata === undefined) {
valueMetadata = this.values().filter((values) => {
return !values.hints.domain;
})[0];
};
}
/**
* Returns all value metadatas, sorted by priority.
*/
TelemetryMetadataManager.prototype.values = function () {
return this.valuesForHints(['priority']);
};
if (valueMetadata === undefined) {
valueMetadata = this.values()[0];
}
/**
* Get an array of valueMetadatas that possess all hints requested.
* Array is sorted based on hint priority.
*
*/
TelemetryMetadataManager.prototype.valuesForHints = function (hints) {
function hasHint(hint) {
// eslint-disable-next-line no-invalid-this
return Object.prototype.hasOwnProperty.call(this.hints, hint);
}
function hasHints(metadata) {
return hints.every(hasHint, metadata);
}
const matchingMetadata = this.valueMetadatas.filter(hasHints);
let iteratees = hints.map((hint) => {
return (metadata) => {
return metadata.hints[hint];
};
});
return _.sortBy(matchingMetadata, ...iteratees);
};
/**
* check out of a given metadata has array values
*/
TelemetryMetadataManager.prototype.isArrayValue = function (metadata) {
const regex = /\[\]$/g;
if (!metadata.format && !metadata.formatString) {
return false;
}
return (metadata.format || metadata.formatString).match(regex) !== null;
};
TelemetryMetadataManager.prototype.getFilterableValues = function () {
return this.valueMetadatas.filter(
(metadatum) => metadatum.filters && metadatum.filters.length > 0
);
};
TelemetryMetadataManager.prototype.getUseToUpdateInPlaceValue = function () {
return this.valueMetadatas.find(this.isInPlaceUpdateValue);
};
TelemetryMetadataManager.prototype.isInPlaceUpdateValue = function (metadatum) {
return metadatum.useToUpdateInPlace === true;
};
TelemetryMetadataManager.prototype.getDefaultDisplayValue = function () {
let valueMetadata = this.valuesForHints(['range'])[0];
if (valueMetadata === undefined) {
valueMetadata = this.values().filter((values) => {
return !values.hints.domain;
})[0];
}
if (valueMetadata === undefined) {
valueMetadata = this.values()[0];
}
return valueMetadata;
};
return TelemetryMetadataManager;
});
return valueMetadata;
};

View File

@ -20,96 +20,92 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
// Set of connection states; changing among these states will be
// reflected in the indicator's appearance.
// CONNECTED: Everything nominal, expect to be able to read/write.
// DISCONNECTED: HTTP failed; maybe misconfigured, disconnected.
// PENDING: Still trying to connect, and haven't failed yet.
const CONNECTED = {
statusClass: 's-status-on'
};
const PENDING = {
statusClass: 's-status-warning-lo'
};
const DISCONNECTED = {
statusClass: 's-status-warning-hi'
};
function URLIndicator(options, simpleIndicator) {
this.bindMethods();
this.count = 0;
// Set of connection states; changing among these states will be
// reflected in the indicator's appearance.
// CONNECTED: Everything nominal, expect to be able to read/write.
// DISCONNECTED: HTTP failed; maybe misconfigured, disconnected.
// PENDING: Still trying to connect, and haven't failed yet.
const CONNECTED = {
statusClass: 's-status-on'
};
const PENDING = {
statusClass: 's-status-warning-lo'
};
const DISCONNECTED = {
statusClass: 's-status-warning-hi'
};
export default function URLIndicator(options, simpleIndicator) {
this.bindMethods();
this.count = 0;
this.indicator = simpleIndicator;
this.setDefaultsFromOptions(options);
this.setIndicatorToState(PENDING);
this.indicator = simpleIndicator;
this.setDefaultsFromOptions(options);
this.setIndicatorToState(PENDING);
this.fetchUrl();
setInterval(this.fetchUrl, this.interval);
}
this.fetchUrl();
setInterval(this.fetchUrl, this.interval);
}
URLIndicator.prototype.setIndicatorToState = function (state) {
switch (state) {
case CONNECTED: {
this.indicator.text(this.label + ' is connected');
this.indicator.description(
this.label + ' is online, checking status every ' + this.interval + ' milliseconds.'
);
break;
}
case PENDING: {
this.indicator.text('Checking status of ' + this.label + ' please stand by...');
this.indicator.description('Checking status of ' + this.label + ' please stand by...');
break;
}
case DISCONNECTED: {
this.indicator.text(this.label + ' is offline');
this.indicator.description(
this.label + ' is offline, checking status every ' + this.interval + ' milliseconds'
);
break;
}
URLIndicator.prototype.setIndicatorToState = function (state) {
switch (state) {
case CONNECTED: {
this.indicator.text(this.label + ' is connected');
this.indicator.description(
this.label + ' is online, checking status every ' + this.interval + ' milliseconds.'
);
break;
}
this.indicator.statusClass(state.statusClass);
};
case PENDING: {
this.indicator.text('Checking status of ' + this.label + ' please stand by...');
this.indicator.description('Checking status of ' + this.label + ' please stand by...');
break;
}
URLIndicator.prototype.fetchUrl = function () {
fetch(this.URLpath)
.then((response) => {
if (response.ok) {
this.handleSuccess();
} else {
this.handleError();
}
})
.catch((error) => {
case DISCONNECTED: {
this.indicator.text(this.label + ' is offline');
this.indicator.description(
this.label + ' is offline, checking status every ' + this.interval + ' milliseconds'
);
break;
}
}
this.indicator.statusClass(state.statusClass);
};
URLIndicator.prototype.fetchUrl = function () {
fetch(this.URLpath)
.then((response) => {
if (response.ok) {
this.handleSuccess();
} else {
this.handleError();
});
};
}
})
.catch((error) => {
this.handleError();
});
};
URLIndicator.prototype.handleError = function (e) {
this.setIndicatorToState(DISCONNECTED);
};
URLIndicator.prototype.handleError = function (e) {
this.setIndicatorToState(DISCONNECTED);
};
URLIndicator.prototype.handleSuccess = function () {
this.setIndicatorToState(CONNECTED);
};
URLIndicator.prototype.handleSuccess = function () {
this.setIndicatorToState(CONNECTED);
};
URLIndicator.prototype.setDefaultsFromOptions = function (options) {
this.URLpath = options.url;
this.label = options.label || options.url;
this.interval = options.interval || 10000;
this.indicator.iconClass(options.iconClass || 'icon-chain-links');
};
URLIndicator.prototype.setDefaultsFromOptions = function (options) {
this.URLpath = options.url;
this.label = options.label || options.url;
this.interval = options.interval || 10000;
this.indicator.iconClass(options.iconClass || 'icon-chain-links');
};
URLIndicator.prototype.bindMethods = function () {
this.fetchUrl = this.fetchUrl.bind(this);
this.handleSuccess = this.handleSuccess.bind(this);
this.handleError = this.handleError.bind(this);
this.setIndicatorToState = this.setIndicatorToState.bind(this);
};
return URLIndicator;
});
URLIndicator.prototype.bindMethods = function () {
this.fetchUrl = this.fetchUrl.bind(this);
this.handleSuccess = this.handleSuccess.bind(this);
this.handleError = this.handleError.bind(this);
this.setIndicatorToState = this.setIndicatorToState.bind(this);
};

View File

@ -19,15 +19,15 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./URLIndicator'], function URLIndicatorPlugin(URLIndicator) {
return function (opts) {
return function install(openmct) {
const simpleIndicator = openmct.indicators.simpleIndicator();
const urlIndicator = new URLIndicator(opts, simpleIndicator);
import URLIndicator from './URLIndicator';
openmct.indicators.add(simpleIndicator);
export default function URLIndicatorPlugin(opts) {
return function install(openmct) {
const simpleIndicator = openmct.indicators.simpleIndicator();
const urlIndicator = new URLIndicator(opts, simpleIndicator);
return urlIndicator;
};
openmct.indicators.add(simpleIndicator);
return urlIndicator;
};
});
}

View File

@ -20,118 +20,115 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['utils/testing', './URLIndicator', './URLIndicatorPlugin', '../../MCT'], function (
testingUtils,
URLIndicator,
URLIndicatorPlugin,
MCT
) {
describe('The URLIndicator', function () {
let openmct;
let indicatorElement;
let pluginOptions;
let urlIndicator; // eslint-disable-line
let fetchSpy;
import * as testingUtils from 'utils/testing';
beforeEach(function () {
jasmine.clock().install();
openmct = new testingUtils.createOpenMct();
spyOn(openmct.indicators, 'add');
fetchSpy = spyOn(window, 'fetch').and.callFake(() =>
Promise.resolve({
ok: true
})
);
});
import URLIndicatorPlugin from './URLIndicatorPlugin';
afterEach(function () {
if (window.fetch.restore) {
window.fetch.restore();
}
describe('The URLIndicator', function () {
let openmct;
let indicatorElement;
let pluginOptions;
let urlIndicator; // eslint-disable-line
let fetchSpy;
jasmine.clock().uninstall();
beforeEach(function () {
jasmine.clock().install();
openmct = new testingUtils.createOpenMct();
spyOn(openmct.indicators, 'add');
fetchSpy = spyOn(window, 'fetch').and.callFake(() =>
Promise.resolve({
ok: true
})
);
});
return testingUtils.resetApplicationState(openmct);
});
afterEach(function () {
if (window.fetch.restore) {
window.fetch.restore();
}
describe('on initialization', function () {
describe('with default options', function () {
beforeEach(function () {
pluginOptions = {
url: 'someURL'
};
urlIndicator = URLIndicatorPlugin(pluginOptions)(openmct);
indicatorElement = openmct.indicators.add.calls.mostRecent().args[0].element;
});
jasmine.clock().uninstall();
it('has a default icon class if none supplied', function () {
expect(indicatorElement.classList.contains('icon-chain-links')).toBe(true);
});
return testingUtils.resetApplicationState(openmct);
});
it('defaults to the URL if no label supplied', function () {
expect(indicatorElement.textContent.indexOf(pluginOptions.url) >= 0).toBe(true);
});
});
describe('with custom options', function () {
beforeEach(function () {
pluginOptions = {
url: 'customURL',
interval: 1814,
iconClass: 'iconClass-checked',
label: 'custom label'
};
urlIndicator = URLIndicatorPlugin(pluginOptions)(openmct);
indicatorElement = openmct.indicators.add.calls.mostRecent().args[0].element;
});
it('uses the custom iconClass', function () {
expect(indicatorElement.classList.contains('iconClass-checked')).toBe(true);
});
it('uses custom interval', function () {
expect(window.fetch).toHaveBeenCalledTimes(1);
jasmine.clock().tick(1);
expect(window.fetch).toHaveBeenCalledTimes(1);
jasmine.clock().tick(pluginOptions.interval + 1);
expect(window.fetch).toHaveBeenCalledTimes(2);
});
it('uses custom label if supplied in initialization', function () {
expect(indicatorElement.textContent.indexOf(pluginOptions.label) >= 0).toBe(true);
});
});
});
describe('when running', function () {
describe('on initialization', function () {
describe('with default options', function () {
beforeEach(function () {
pluginOptions = {
url: 'someURL',
interval: 100
url: 'someURL'
};
urlIndicator = URLIndicatorPlugin(pluginOptions)(openmct);
indicatorElement = openmct.indicators.add.calls.mostRecent().args[0].element;
});
it('requests the provided URL', function () {
jasmine.clock().tick(pluginOptions.interval + 1);
expect(window.fetch).toHaveBeenCalledWith(pluginOptions.url);
it('has a default icon class if none supplied', function () {
expect(indicatorElement.classList.contains('icon-chain-links')).toBe(true);
});
it('indicates success if connection is nominal', async function () {
jasmine.clock().tick(pluginOptions.interval + 1);
await urlIndicator.fetchUrl();
expect(indicatorElement.classList.contains('s-status-on')).toBe(true);
it('defaults to the URL if no label supplied', function () {
expect(indicatorElement.textContent.indexOf(pluginOptions.url) >= 0).toBe(true);
});
});
describe('with custom options', function () {
beforeEach(function () {
pluginOptions = {
url: 'customURL',
interval: 1814,
iconClass: 'iconClass-checked',
label: 'custom label'
};
urlIndicator = URLIndicatorPlugin(pluginOptions)(openmct);
indicatorElement = openmct.indicators.add.calls.mostRecent().args[0].element;
});
it('indicates an error when the server cannot be reached', async function () {
fetchSpy.and.callFake(() =>
Promise.resolve({
ok: false
})
);
it('uses the custom iconClass', function () {
expect(indicatorElement.classList.contains('iconClass-checked')).toBe(true);
});
it('uses custom interval', function () {
expect(window.fetch).toHaveBeenCalledTimes(1);
jasmine.clock().tick(1);
expect(window.fetch).toHaveBeenCalledTimes(1);
jasmine.clock().tick(pluginOptions.interval + 1);
await urlIndicator.fetchUrl();
expect(indicatorElement.classList.contains('s-status-warning-hi')).toBe(true);
expect(window.fetch).toHaveBeenCalledTimes(2);
});
it('uses custom label if supplied in initialization', function () {
expect(indicatorElement.textContent.indexOf(pluginOptions.label) >= 0).toBe(true);
});
});
});
describe('when running', function () {
beforeEach(function () {
pluginOptions = {
url: 'someURL',
interval: 100
};
urlIndicator = URLIndicatorPlugin(pluginOptions)(openmct);
indicatorElement = openmct.indicators.add.calls.mostRecent().args[0].element;
});
it('requests the provided URL', function () {
jasmine.clock().tick(pluginOptions.interval + 1);
expect(window.fetch).toHaveBeenCalledWith(pluginOptions.url);
});
it('indicates success if connection is nominal', async function () {
jasmine.clock().tick(pluginOptions.interval + 1);
await urlIndicator.fetchUrl();
expect(indicatorElement.classList.contains('s-status-on')).toBe(true);
});
it('indicates an error when the server cannot be reached', async function () {
fetchSpy.and.callFake(() =>
Promise.resolve({
ok: false
})
);
jasmine.clock().tick(pluginOptions.interval + 1);
await urlIndicator.fetchUrl();
expect(indicatorElement.classList.contains('s-status-warning-hi')).toBe(true);
});
});
});

View File

@ -20,15 +20,13 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
/**
* Constant values used by the Autoflow Tabular View.
*/
return {
ROW_HEIGHT: 16,
SLIDER_HEIGHT: 10,
INITIAL_COLUMN_WIDTH: 225,
MAX_COLUMN_WIDTH: 525,
COLUMN_WIDTH_STEP: 25
};
});
/**
* Constant values used by the Autoflow Tabular View.
*/
export default {
ROW_HEIGHT: 16,
SLIDER_HEIGHT: 10,
INITIAL_COLUMN_WIDTH: 225,
MAX_COLUMN_WIDTH: 525,
COLUMN_WIDTH_STEP: 25
};

View File

@ -20,104 +20,102 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./AutoflowTabularRowController'], function (AutoflowTabularRowController) {
/**
* Controller for an Autoflow Tabular View. Subscribes to telemetry
* associated with children of the domain object and passes that
* information on to the view.
*
* @param {DomainObject} domainObject the object being viewed
* @param {*} data the view data
* @param openmct a reference to the openmct application
*/
function AutoflowTabularController(domainObject, data, openmct) {
this.composition = openmct.composition.get(domainObject);
this.data = data;
this.openmct = openmct;
import AutoflowTabularRowController from './AutoflowTabularRowController';
this.rows = {};
this.controllers = {};
/**
* Controller for an Autoflow Tabular View. Subscribes to telemetry
* associated with children of the domain object and passes that
* information on to the view.
*
* @param {DomainObject} domainObject the object being viewed
* @param {*} data the view data
* @param openmct a reference to the openmct application
*/
export default function AutoflowTabularController(domainObject, data, openmct) {
this.composition = openmct.composition.get(domainObject);
this.data = data;
this.openmct = openmct;
this.addRow = this.addRow.bind(this);
this.removeRow = this.removeRow.bind(this);
this.rows = {};
this.controllers = {};
this.addRow = this.addRow.bind(this);
this.removeRow = this.removeRow.bind(this);
}
/**
* Set the "Last Updated" value to be displayed.
* @param {String} value the value to display
* @private
*/
AutoflowTabularController.prototype.trackLastUpdated = function (value) {
this.data.updated = value;
};
/**
* Respond to an `add` event from composition by adding a new row.
* @private
*/
AutoflowTabularController.prototype.addRow = function (childObject) {
const identifier = childObject.identifier;
const id = [identifier.namespace, identifier.key].join(':');
if (!this.rows[id]) {
this.rows[id] = {
classes: '',
name: childObject.name,
value: undefined
};
this.controllers[id] = new AutoflowTabularRowController(
childObject,
this.rows[id],
this.openmct,
this.trackLastUpdated.bind(this)
);
this.controllers[id].activate();
this.data.items.push(this.rows[id]);
}
};
/**
* Set the "Last Updated" value to be displayed.
* @param {String} value the value to display
* @private
*/
AutoflowTabularController.prototype.trackLastUpdated = function (value) {
this.data.updated = value;
};
/**
* Respond to an `remove` event from composition by removing any
* related row.
* @private
*/
AutoflowTabularController.prototype.removeRow = function (identifier) {
const id = [identifier.namespace, identifier.key].join(':');
/**
* Respond to an `add` event from composition by adding a new row.
* @private
*/
AutoflowTabularController.prototype.addRow = function (childObject) {
const identifier = childObject.identifier;
const id = [identifier.namespace, identifier.key].join(':');
if (!this.rows[id]) {
this.rows[id] = {
classes: '',
name: childObject.name,
value: undefined
};
this.controllers[id] = new AutoflowTabularRowController(
childObject,
this.rows[id],
this.openmct,
this.trackLastUpdated.bind(this)
);
this.controllers[id].activate();
this.data.items.push(this.rows[id]);
}
};
/**
* Respond to an `remove` event from composition by removing any
* related row.
* @private
*/
AutoflowTabularController.prototype.removeRow = function (identifier) {
const id = [identifier.namespace, identifier.key].join(':');
if (this.rows[id]) {
this.data.items = this.data.items.filter(
function (item) {
return item !== this.rows[id];
}.bind(this)
);
this.controllers[id].destroy();
delete this.controllers[id];
delete this.rows[id];
}
};
/**
* Activate this controller; begin listening for changes.
*/
AutoflowTabularController.prototype.activate = function () {
this.composition.on('add', this.addRow);
this.composition.on('remove', this.removeRow);
this.composition.load();
};
/**
* Destroy this controller; detach any associated resources.
*/
AutoflowTabularController.prototype.destroy = function () {
Object.keys(this.controllers).forEach(
function (id) {
this.controllers[id].destroy();
if (this.rows[id]) {
this.data.items = this.data.items.filter(
function (item) {
return item !== this.rows[id];
}.bind(this)
);
this.controllers = {};
this.composition.off('add', this.addRow);
this.composition.off('remove', this.removeRow);
};
this.controllers[id].destroy();
delete this.controllers[id];
delete this.rows[id];
}
};
return AutoflowTabularController;
});
/**
* Activate this controller; begin listening for changes.
*/
AutoflowTabularController.prototype.activate = function () {
this.composition.on('add', this.addRow);
this.composition.on('remove', this.removeRow);
this.composition.load();
};
/**
* Destroy this controller; detach any associated resources.
*/
AutoflowTabularController.prototype.destroy = function () {
Object.keys(this.controllers).forEach(
function (id) {
this.controllers[id].destroy();
}.bind(this)
);
this.controllers = {};
this.composition.off('add', this.addRow);
this.composition.off('remove', this.removeRow);
};

View File

@ -20,23 +20,23 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./AutoflowTabularView'], function (AutoflowTabularView) {
return function (options) {
return function (openmct) {
const views = openmct.mainViews || openmct.objectViews;
import AutoflowTabularView from './AutoflowTabularView';
views.addProvider({
name: 'Autoflow Tabular',
key: 'autoflow',
cssClass: 'icon-packet',
description: 'A tabular view of packet contents.',
canView: function (d) {
return !options || options.type === d.type;
},
view: function (domainObject) {
return new AutoflowTabularView(domainObject, openmct, document);
}
});
};
export default function (options) {
return function (openmct) {
const views = openmct.mainViews || openmct.objectViews;
views.addProvider({
name: 'Autoflow Tabular',
key: 'autoflow',
cssClass: 'icon-packet',
description: 'A tabular view of packet contents.',
canView: function (d) {
return !options || options.type === d.type;
},
view: function (domainObject) {
return new AutoflowTabularView(domainObject, openmct, document);
}
});
};
});
}

View File

@ -20,76 +20,72 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
/**
* Controller for individual rows of an Autoflow Tabular View.
* Subscribes to telemetry and updates row data.
*
* @param {DomainObject} domainObject the object being viewed
* @param {*} data the view data
* @param openmct a reference to the openmct application
* @param {Function} callback a callback to invoke with "last updated" timestamps
*/
function AutoflowTabularRowController(domainObject, data, openmct, callback) {
this.domainObject = domainObject;
this.data = data;
this.openmct = openmct;
this.callback = callback;
/**
* Controller for individual rows of an Autoflow Tabular View.
* Subscribes to telemetry and updates row data.
*
* @param {DomainObject} domainObject the object being viewed
* @param {*} data the view data
* @param openmct a reference to the openmct application
* @param {Function} callback a callback to invoke with "last updated" timestamps
*/
export default function AutoflowTabularRowController(domainObject, data, openmct, callback) {
this.domainObject = domainObject;
this.data = data;
this.openmct = openmct;
this.callback = callback;
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
this.ranges = this.metadata.valuesForHints(['range']);
this.domains = this.metadata.valuesForHints(['domain']);
this.rangeFormatter = this.openmct.telemetry.getValueFormatter(this.ranges[0]);
this.domainFormatter = this.openmct.telemetry.getValueFormatter(this.domains[0]);
this.evaluator = this.openmct.telemetry.limitEvaluator(this.domainObject);
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
this.ranges = this.metadata.valuesForHints(['range']);
this.domains = this.metadata.valuesForHints(['domain']);
this.rangeFormatter = this.openmct.telemetry.getValueFormatter(this.ranges[0]);
this.domainFormatter = this.openmct.telemetry.getValueFormatter(this.domains[0]);
this.evaluator = this.openmct.telemetry.limitEvaluator(this.domainObject);
this.initialized = false;
this.initialized = false;
}
/**
* Update row to reflect incoming telemetry data.
* @private
*/
AutoflowTabularRowController.prototype.updateRowData = function (datum) {
const violations = this.evaluator.evaluate(datum, this.ranges[0]);
this.initialized = true;
this.data.classes = violations ? violations.cssClass : '';
this.data.value = this.rangeFormatter.format(datum);
this.callback(this.domainFormatter.format(datum));
};
/**
* Activate this controller; begin listening for changes.
*/
AutoflowTabularRowController.prototype.activate = function () {
this.unsubscribe = this.openmct.telemetry.subscribe(
this.domainObject,
this.updateRowData.bind(this)
);
const options = {
size: 1,
strategy: 'latest',
timeContext: this.openmct.time.getContextForView([])
};
this.openmct.telemetry.request(this.domainObject, options).then(
function (history) {
if (!this.initialized && history.length > 0) {
this.updateRowData(history[history.length - 1]);
}
}.bind(this)
);
};
/**
* Destroy this controller; detach any associated resources.
*/
AutoflowTabularRowController.prototype.destroy = function () {
if (this.unsubscribe) {
this.unsubscribe();
}
/**
* Update row to reflect incoming telemetry data.
* @private
*/
AutoflowTabularRowController.prototype.updateRowData = function (datum) {
const violations = this.evaluator.evaluate(datum, this.ranges[0]);
this.initialized = true;
this.data.classes = violations ? violations.cssClass : '';
this.data.value = this.rangeFormatter.format(datum);
this.callback(this.domainFormatter.format(datum));
};
/**
* Activate this controller; begin listening for changes.
*/
AutoflowTabularRowController.prototype.activate = function () {
this.unsubscribe = this.openmct.telemetry.subscribe(
this.domainObject,
this.updateRowData.bind(this)
);
const options = {
size: 1,
strategy: 'latest',
timeContext: this.openmct.time.getContextForView([])
};
this.openmct.telemetry.request(this.domainObject, options).then(
function (history) {
if (!this.initialized && history.length > 0) {
this.updateRowData(history[history.length - 1]);
}
}.bind(this)
);
};
/**
* Destroy this controller; detach any associated resources.
*/
AutoflowTabularRowController.prototype.destroy = function () {
if (this.unsubscribe) {
this.unsubscribe();
}
};
return AutoflowTabularRowController;
});
};

View File

@ -20,96 +20,92 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'./AutoflowTabularController',
'./AutoflowTabularConstants',
'./VueView',
'./autoflow-tabular.html'
], function (AutoflowTabularController, AutoflowTabularConstants, VueView, autoflowTemplate) {
const ROW_HEIGHT = AutoflowTabularConstants.ROW_HEIGHT;
const SLIDER_HEIGHT = AutoflowTabularConstants.SLIDER_HEIGHT;
const INITIAL_COLUMN_WIDTH = AutoflowTabularConstants.INITIAL_COLUMN_WIDTH;
const MAX_COLUMN_WIDTH = AutoflowTabularConstants.MAX_COLUMN_WIDTH;
const COLUMN_WIDTH_STEP = AutoflowTabularConstants.COLUMN_WIDTH_STEP;
import autoflowTemplate from './autoflow-tabular.html';
import AutoflowTabularConstants from './AutoflowTabularConstants';
import AutoflowTabularController from './AutoflowTabularController';
import VueView from './VueView';
/**
* Implements the Autoflow Tabular view of a domain object.
*/
function AutoflowTabularView(domainObject, openmct) {
const data = {
items: [],
columns: [],
width: INITIAL_COLUMN_WIDTH,
filter: '',
updated: 'No updates',
rowCount: 1
};
const controller = new AutoflowTabularController(domainObject, data, openmct);
let interval;
const ROW_HEIGHT = AutoflowTabularConstants.ROW_HEIGHT;
const SLIDER_HEIGHT = AutoflowTabularConstants.SLIDER_HEIGHT;
const INITIAL_COLUMN_WIDTH = AutoflowTabularConstants.INITIAL_COLUMN_WIDTH;
const MAX_COLUMN_WIDTH = AutoflowTabularConstants.MAX_COLUMN_WIDTH;
const COLUMN_WIDTH_STEP = AutoflowTabularConstants.COLUMN_WIDTH_STEP;
VueView.call(this, {
data: data,
methods: {
increaseColumnWidth: function () {
data.width += COLUMN_WIDTH_STEP;
data.width = data.width > MAX_COLUMN_WIDTH ? INITIAL_COLUMN_WIDTH : data.width;
},
reflow: function () {
let column = [];
let index = 0;
const filteredItems = data.items.filter(function (item) {
return item.name.toLowerCase().indexOf(data.filter.toLowerCase()) !== -1;
});
/**
* Implements the Autoflow Tabular view of a domain object.
*/
export default function AutoflowTabularView(domainObject, openmct) {
const data = {
items: [],
columns: [],
width: INITIAL_COLUMN_WIDTH,
filter: '',
updated: 'No updates',
rowCount: 1
};
const controller = new AutoflowTabularController(domainObject, data, openmct);
let interval;
data.columns = [];
VueView.call(this, {
data: data,
methods: {
increaseColumnWidth: function () {
data.width += COLUMN_WIDTH_STEP;
data.width = data.width > MAX_COLUMN_WIDTH ? INITIAL_COLUMN_WIDTH : data.width;
},
reflow: function () {
let column = [];
let index = 0;
const filteredItems = data.items.filter(function (item) {
return item.name.toLowerCase().indexOf(data.filter.toLowerCase()) !== -1;
});
while (index < filteredItems.length) {
if (column.length >= data.rowCount) {
data.columns.push(column);
column = [];
}
data.columns = [];
column.push(filteredItems[index]);
index += 1;
}
if (column.length > 0) {
while (index < filteredItems.length) {
if (column.length >= data.rowCount) {
data.columns.push(column);
column = [];
}
column.push(filteredItems[index]);
index += 1;
}
},
watch: {
filter: 'reflow',
items: 'reflow',
rowCount: 'reflow'
},
template: autoflowTemplate,
unmounted: function () {
controller.destroy();
if (interval) {
clearInterval(interval);
interval = undefined;
if (column.length > 0) {
data.columns.push(column);
}
},
mounted: function () {
controller.activate();
const updateRowHeight = function () {
const tabularArea = this.$refs.autoflowItems;
const height = tabularArea ? tabularArea.clientHeight : 0;
const available = height - SLIDER_HEIGHT;
const rows = Math.max(1, Math.floor(available / ROW_HEIGHT));
data.rowCount = rows;
}.bind(this);
interval = setInterval(updateRowHeight, 50);
this.$nextTick(updateRowHeight);
}
});
}
},
watch: {
filter: 'reflow',
items: 'reflow',
rowCount: 'reflow'
},
template: autoflowTemplate,
unmounted: function () {
controller.destroy();
AutoflowTabularView.prototype = Object.create(VueView.default.prototype);
if (interval) {
clearInterval(interval);
interval = undefined;
}
},
mounted: function () {
controller.activate();
return AutoflowTabularView;
});
const updateRowHeight = function () {
const tabularArea = this.$refs.autoflowItems;
const height = tabularArea ? tabularArea.clientHeight : 0;
const available = height - SLIDER_HEIGHT;
const rows = Math.max(1, Math.floor(available / ROW_HEIGHT));
data.rowCount = rows;
}.bind(this);
interval = setInterval(updateRowHeight, 50);
this.$nextTick(updateRowHeight);
}
});
}
AutoflowTabularView.prototype = Object.create(VueView.prototype);

View File

@ -22,13 +22,15 @@
import mount from 'utils/mount';
export default function () {
return function VueView(options) {
const { vNode, destroy } = mount(options);
class VueView {
constructor(options) {
const { vNode, destroy } = mount(options);
this.show = function (container) {
container.appendChild(vNode.el);
};
this.destroy = destroy;
}
}
this.show = function (container) {
container.appendChild(vNode.el);
};
this.destroy = destroy;
};
return VueView;
}

View File

@ -20,44 +20,40 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
function DOMObserver(element) {
this.element = element;
this.observers = [];
}
export default function DOMObserver(element) {
this.element = element;
this.observers = [];
}
DOMObserver.prototype.when = function (latchFunction) {
return new Promise(
function (resolve, reject) {
//Test latch function at least once
if (latchFunction()) {
resolve();
} else {
//Latch condition not true yet, create observer on DOM and test again on change.
const config = {
attributes: true,
childList: true,
subtree: true
};
const observer = new MutationObserver(function () {
if (latchFunction()) {
resolve();
}
});
observer.observe(this.element, config);
this.observers.push(observer);
}
}.bind(this)
);
};
DOMObserver.prototype.when = function (latchFunction) {
return new Promise(
function (resolve, reject) {
//Test latch function at least once
if (latchFunction()) {
resolve();
} else {
//Latch condition not true yet, create observer on DOM and test again on change.
const config = {
attributes: true,
childList: true,
subtree: true
};
const observer = new MutationObserver(function () {
if (latchFunction()) {
resolve();
}
});
observer.observe(this.element, config);
this.observers.push(observer);
}
}.bind(this)
);
};
DOMObserver.prototype.destroy = function () {
this.observers.forEach(
function (observer) {
observer.disconnect();
}.bind(this)
);
};
return DOMObserver;
});
DOMObserver.prototype.destroy = function () {
this.observers.forEach(
function (observer) {
observer.disconnect();
}.bind(this)
);
};

View File

@ -20,53 +20,49 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(function () {
function DisplayLayoutType() {
return {
name: 'Display Layout',
creatable: true,
description:
'Assemble other objects and components together into a reusable screen layout. Simply drag in the objects you want, position and size them. Save your design and view or edit it at any time.',
cssClass: 'icon-layout',
initialize(domainObject) {
domainObject.composition = [];
domainObject.configuration = {
items: [],
layoutGrid: [10, 10]
};
export default function DisplayLayoutType() {
return {
name: 'Display Layout',
creatable: true,
description:
'Assemble other objects and components together into a reusable screen layout. Simply drag in the objects you want, position and size them. Save your design and view or edit it at any time.',
cssClass: 'icon-layout',
initialize(domainObject) {
domainObject.composition = [];
domainObject.configuration = {
items: [],
layoutGrid: [10, 10]
};
},
form: [
{
name: 'Horizontal grid (px)',
control: 'numberfield',
cssClass: 'l-input-sm l-numeric',
property: ['configuration', 'layoutGrid', 0],
required: true
},
form: [
{
name: 'Horizontal grid (px)',
control: 'numberfield',
cssClass: 'l-input-sm l-numeric',
property: ['configuration', 'layoutGrid', 0],
required: true
},
{
name: 'Vertical grid (px)',
control: 'numberfield',
cssClass: 'l-input-sm l-numeric',
property: ['configuration', 'layoutGrid', 1],
required: true
},
{
name: 'Horizontal size (px)',
control: 'numberfield',
cssClass: 'l-input-sm l-numeric',
property: ['configuration', 'layoutDimensions', 0],
required: false
},
{
name: 'Vertical size (px)',
control: 'numberfield',
cssClass: 'l-input-sm l-numeric',
property: ['configuration', 'layoutDimensions', 1],
required: false
}
]
};
}
return DisplayLayoutType;
});
{
name: 'Vertical grid (px)',
control: 'numberfield',
cssClass: 'l-input-sm l-numeric',
property: ['configuration', 'layoutGrid', 1],
required: true
},
{
name: 'Horizontal size (px)',
control: 'numberfield',
cssClass: 'l-input-sm l-numeric',
property: ['configuration', 'layoutDimensions', 0],
required: false
},
{
name: 'Vertical size (px)',
control: 'numberfield',
cssClass: 'l-input-sm l-numeric',
property: ['configuration', 'layoutDimensions', 1],
required: false
}
]
};
}

View File

@ -20,93 +20,89 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
/**
* Handles drag interactions on frames in layouts. This will
* provides new positions/dimensions for frames based on
* relative pixel positions provided; these will take into account
* the grid size (in a snap-to sense) and will enforce some minimums
* on both position and dimensions.
*
* The provided position and dimensions factors will determine
* whether this is a move or a resize, and what type of resize it
* will be. For instance, a position factor of [1, 1]
* will move a frame along with the mouse as the drag
* proceeds, while a dimension factor of [0, 0] will leave
* dimensions unchanged. Combining these in different
* ways results in different handles; a position factor of
* [1, 0] and a dimensions factor of [-1, 0] will implement
* a left-edge resize, as the horizontal position will move
* with the mouse while the horizontal dimensions shrink in
* kind (and vertical properties remain unmodified.)
*
* @param {object} rawPosition the initial position/dimensions
* of the frame being interacted with
* @param {number[]} posFactor the position factor
* @param {number[]} dimFactor the dimensions factor
* @param {number[]} the size of each grid element, in pixels
* @constructor
* @memberof platform/features/layout
*/
function LayoutDrag(rawPosition, posFactor, dimFactor, gridSize) {
this.rawPosition = rawPosition;
this.posFactor = posFactor;
this.dimFactor = dimFactor;
this.gridSize = gridSize;
}
/**
* Handles drag interactions on frames in layouts. This will
* provides new positions/dimensions for frames based on
* relative pixel positions provided; these will take into account
* the grid size (in a snap-to sense) and will enforce some minimums
* on both position and dimensions.
*
* The provided position and dimensions factors will determine
* whether this is a move or a resize, and what type of resize it
* will be. For instance, a position factor of [1, 1]
* will move a frame along with the mouse as the drag
* proceeds, while a dimension factor of [0, 0] will leave
* dimensions unchanged. Combining these in different
* ways results in different handles; a position factor of
* [1, 0] and a dimensions factor of [-1, 0] will implement
* a left-edge resize, as the horizontal position will move
* with the mouse while the horizontal dimensions shrink in
* kind (and vertical properties remain unmodified.)
*
* @param {object} rawPosition the initial position/dimensions
* of the frame being interacted with
* @param {number[]} posFactor the position factor
* @param {number[]} dimFactor the dimensions factor
* @param {number[]} the size of each grid element, in pixels
* @constructor
* @memberof platform/features/layout
*/
export default function LayoutDrag(rawPosition, posFactor, dimFactor, gridSize) {
this.rawPosition = rawPosition;
this.posFactor = posFactor;
this.dimFactor = dimFactor;
this.gridSize = gridSize;
}
// Convert a delta from pixel coordinates to grid coordinates,
// rounding to whole-number grid coordinates.
function toGridDelta(gridSize, pixelDelta) {
return pixelDelta.map(function (v, i) {
return Math.round(v / gridSize[i]);
});
}
// Convert a delta from pixel coordinates to grid coordinates,
// rounding to whole-number grid coordinates.
function toGridDelta(gridSize, pixelDelta) {
return pixelDelta.map(function (v, i) {
return Math.round(v / gridSize[i]);
});
}
// Utility function to perform element-by-element multiplication
function multiply(array, factors) {
return array.map(function (v, i) {
return v * factors[i];
});
}
// Utility function to perform element-by-element multiplication
function multiply(array, factors) {
return array.map(function (v, i) {
return v * factors[i];
});
}
// Utility function to perform element-by-element addition
function add(array, other) {
return array.map(function (v, i) {
return v + other[i];
});
}
// Utility function to perform element-by-element addition
function add(array, other) {
return array.map(function (v, i) {
return v + other[i];
});
}
// Utility function to perform element-by-element max-choosing
function max(array, other) {
return array.map(function (v, i) {
return Math.max(v, other[i]);
});
}
// Utility function to perform element-by-element max-choosing
function max(array, other) {
return array.map(function (v, i) {
return Math.max(v, other[i]);
});
}
/**
* Get a new position object in grid coordinates, with
* position and dimensions both offset appropriately
* according to the factors supplied in the constructor.
* @param {number[]} pixelDelta the offset from the
* original position, in pixels
*/
LayoutDrag.prototype.getAdjustedPositionAndDimensions = function (pixelDelta) {
const gridDelta = toGridDelta(this.gridSize, pixelDelta);
/**
* Get a new position object in grid coordinates, with
* position and dimensions both offset appropriately
* according to the factors supplied in the constructor.
* @param {number[]} pixelDelta the offset from the
* original position, in pixels
*/
LayoutDrag.prototype.getAdjustedPositionAndDimensions = function (pixelDelta) {
const gridDelta = toGridDelta(this.gridSize, pixelDelta);
return {
position: max(add(this.rawPosition.position, multiply(gridDelta, this.posFactor)), [0, 0]),
dimensions: max(add(this.rawPosition.dimensions, multiply(gridDelta, this.dimFactor)), [1, 1])
};
return {
position: max(add(this.rawPosition.position, multiply(gridDelta, this.posFactor)), [0, 0]),
dimensions: max(add(this.rawPosition.dimensions, multiply(gridDelta, this.dimFactor)), [1, 1])
};
};
LayoutDrag.prototype.getAdjustedPosition = function (pixelDelta) {
const gridDelta = toGridDelta(this.gridSize, pixelDelta);
LayoutDrag.prototype.getAdjustedPosition = function (pixelDelta) {
const gridDelta = toGridDelta(this.gridSize, pixelDelta);
return {
position: max(add(this.rawPosition.position, multiply(gridDelta, this.posFactor)), [0, 0])
};
return {
position: max(add(this.rawPosition.position, multiply(gridDelta, this.posFactor)), [0, 0])
};
return LayoutDrag;
});
};

View File

@ -20,12 +20,12 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./FiltersInspectorViewProvider'], function (FiltersInspectorViewProvider) {
return function plugin(supportedObjectTypesArray) {
return function install(openmct) {
openmct.inspectorViews.addProvider(
new FiltersInspectorViewProvider.default(openmct, supportedObjectTypesArray)
);
};
import FiltersInspectorViewProvider from './FiltersInspectorViewProvider';
export default function plugin(supportedObjectTypesArray) {
return function install(openmct) {
openmct.inspectorViews.addProvider(
new FiltersInspectorViewProvider(openmct, supportedObjectTypesArray)
);
};
});
}

View File

@ -20,33 +20,31 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./flexibleLayoutViewProvider', './utils/container', './toolbarProvider'], function (
FlexibleLayoutViewProvider,
Container,
ToolBarProvider
) {
return function plugin() {
return function install(openmct) {
openmct.objectViews.addProvider(new FlexibleLayoutViewProvider.default(openmct));
import FlexibleLayoutViewProvider from './flexibleLayoutViewProvider';
import ToolBarProvider from './toolbarProvider';
import Container from './utils/container';
openmct.types.addType('flexible-layout', {
name: 'Flexible Layout',
creatable: true,
description:
'A fluid, flexible layout canvas that can display multiple objects in rows or columns.',
cssClass: 'icon-flexible-layout',
initialize: function (domainObject) {
domainObject.configuration = {
containers: [new Container.default(50), new Container.default(50)],
rowsLayout: false
};
domainObject.composition = [];
}
});
export default function plugin() {
return function install(openmct) {
openmct.objectViews.addProvider(new FlexibleLayoutViewProvider(openmct));
let toolbar = ToolBarProvider.default(openmct);
openmct.types.addType('flexible-layout', {
name: 'Flexible Layout',
creatable: true,
description:
'A fluid, flexible layout canvas that can display multiple objects in rows or columns.',
cssClass: 'icon-flexible-layout',
initialize: function (domainObject) {
domainObject.configuration = {
containers: [new Container(50), new Container(50)],
rowsLayout: false
};
domainObject.composition = [];
}
});
openmct.toolbars.addProvider(toolbar);
};
let toolbar = ToolBarProvider(openmct);
openmct.toolbars.addProvider(toolbar);
};
});
}

View File

@ -20,80 +20,78 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
const helperFunctions = {
listenTo: function (object, event, callback, context) {
if (!this._listeningTo) {
this._listeningTo = [];
}
const listener = {
object: object,
event: event,
callback: callback,
context: context,
_cb: context ? callback.bind(context) : callback
};
if (object.$watch && event.indexOf('change:') === 0) {
const scopePath = event.replace('change:', '');
listener.unlisten = object.$watch(scopePath, listener._cb, true);
} else if (object.$on) {
listener.unlisten = object.$on(event, listener._cb);
} else if (object.addEventListener) {
object.addEventListener(event, listener._cb);
} else {
object.on(event, listener._cb);
}
this._listeningTo.push(listener);
},
stopListening: function (object, event, callback, context) {
if (!this._listeningTo) {
this._listeningTo = [];
}
this._listeningTo
.filter(function (listener) {
if (object && object !== listener.object) {
return false;
}
if (event && event !== listener.event) {
return false;
}
if (callback && callback !== listener.callback) {
return false;
}
if (context && context !== listener.context) {
return false;
}
return true;
})
.map(function (listener) {
if (listener.unlisten) {
listener.unlisten();
} else if (listener.object.removeEventListener) {
listener.object.removeEventListener(listener.event, listener._cb);
} else {
listener.object.off(listener.event, listener._cb);
}
return listener;
})
.forEach(function (listener) {
this._listeningTo.splice(this._listeningTo.indexOf(listener), 1);
}, this);
},
extend: function (object) {
object.listenTo = helperFunctions.listenTo;
object.stopListening = helperFunctions.stopListening;
const helperFunctions = {
listenTo: function (object, event, callback, context) {
if (!this._listeningTo) {
this._listeningTo = [];
}
};
return helperFunctions;
});
const listener = {
object: object,
event: event,
callback: callback,
context: context,
_cb: context ? callback.bind(context) : callback
};
if (object.$watch && event.indexOf('change:') === 0) {
const scopePath = event.replace('change:', '');
listener.unlisten = object.$watch(scopePath, listener._cb, true);
} else if (object.$on) {
listener.unlisten = object.$on(event, listener._cb);
} else if (object.addEventListener) {
object.addEventListener(event, listener._cb);
} else {
object.on(event, listener._cb);
}
this._listeningTo.push(listener);
},
stopListening: function (object, event, callback, context) {
if (!this._listeningTo) {
this._listeningTo = [];
}
this._listeningTo
.filter(function (listener) {
if (object && object !== listener.object) {
return false;
}
if (event && event !== listener.event) {
return false;
}
if (callback && callback !== listener.callback) {
return false;
}
if (context && context !== listener.context) {
return false;
}
return true;
})
.map(function (listener) {
if (listener.unlisten) {
listener.unlisten();
} else if (listener.object.removeEventListener) {
listener.object.removeEventListener(listener.event, listener._cb);
} else {
listener.object.off(listener.event, listener._cb);
}
return listener;
})
.forEach(function (listener) {
this._listeningTo.splice(this._listeningTo.indexOf(listener), 1);
}, this);
},
extend: function (object) {
object.listenTo = helperFunctions.listenTo;
object.stopListening = helperFunctions.stopListening;
}
};
export default helperFunctions;

View File

@ -20,24 +20,24 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['../../../src/plugins/utcTimeSystem/LocalClock'], function (LocalClock) {
import LocalClock from '../../../src/plugins/utcTimeSystem/LocalClock';
class LADClock extends LocalClock {
/**
* A {@link Clock} that mocks a "latest available data" type tick source.
* This is for testing purposes only, and behaves identically to a local clock.
* It DOES NOT tick on receipt of data.
* @constructor
*/
function LADClock(period) {
LocalClock.call(this, period);
constructor(period) {
super(period);
this.key = 'test-lad';
this.mode = 'lad';
this.cssClass = 'icon-suitcase';
this.name = 'Latest available data';
this.description = 'Updates when when new data is available';
this.description = 'Updates when new data is available';
}
}
LADClock.prototype = Object.create(LocalClock.prototype);
return LADClock;
});
export default LADClock;

View File

@ -20,10 +20,10 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./LADClock'], function (LADClock) {
return function () {
return function (openmct) {
openmct.time.addClock(new LADClock());
};
import LADClock from './LADClock';
export default function () {
return function (openmct) {
openmct.time.addClock(new LADClock());
};
});
}

View File

@ -20,49 +20,47 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['moment'], function (moment) {
const DATE_FORMAT = 'YYYY-MM-DD h:mm:ss.SSS a';
import moment from 'moment';
const DATE_FORMATS = [DATE_FORMAT, 'YYYY-MM-DD h:mm:ss a', 'YYYY-MM-DD h:mm a', 'YYYY-MM-DD'];
const DATE_FORMAT = 'YYYY-MM-DD h:mm:ss.SSS a';
/**
* @typedef Scale
* @property {number} min the minimum scale value, in ms
* @property {number} max the maximum scale value, in ms
*/
const DATE_FORMATS = [DATE_FORMAT, 'YYYY-MM-DD h:mm:ss a', 'YYYY-MM-DD h:mm a', 'YYYY-MM-DD'];
/**
* Formatter for UTC timestamps. Interprets numeric values as
* milliseconds since the start of 1970.
*
* @implements {Format}
* @constructor
* @memberof platform/commonUI/formats
*/
function LocalTimeFormat() {
this.key = 'local-format';
/**
* @typedef Scale
* @property {number} min the minimum scale value, in ms
* @property {number} max the maximum scale value, in ms
*/
/**
* Formatter for UTC timestamps. Interprets numeric values as
* milliseconds since the start of 1970.
*
* @implements {Format}
* @constructor
* @memberof platform/commonUI/formats
*/
export default function LocalTimeFormat() {
this.key = 'local-format';
}
/**
*
* @param value
* @returns {string} the formatted date
*/
LocalTimeFormat.prototype.format = function (value, scale) {
return moment(value).format(DATE_FORMAT);
};
LocalTimeFormat.prototype.parse = function (text) {
if (typeof text === 'number') {
return text;
}
/**
*
* @param value
* @returns {string} the formatted date
*/
LocalTimeFormat.prototype.format = function (value, scale) {
return moment(value).format(DATE_FORMAT);
};
return moment(text, DATE_FORMATS).valueOf();
};
LocalTimeFormat.prototype.parse = function (text) {
if (typeof text === 'number') {
return text;
}
return moment(text, DATE_FORMATS).valueOf();
};
LocalTimeFormat.prototype.validate = function (text) {
return moment(text, DATE_FORMATS).isValid();
};
return LocalTimeFormat;
});
LocalTimeFormat.prototype.validate = function (text) {
return moment(text, DATE_FORMATS).isValid();
};

View File

@ -20,13 +20,13 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
/**
* This time system supports UTC dates and provides a ticking clock source.
* @implements TimeSystem
* @constructor
*/
function LocalTimeSystem() {
/**
* This time system supports UTC dates and provides a ticking clock source.
* @implements TimeSystem
* @constructor
*/
export default class LocalTimeSystem {
constructor() {
/**
* Some metadata, which will be used to identify the time system in
* the UI
@ -41,6 +41,4 @@ define([], function () {
this.isUTCBased = true;
}
return LocalTimeSystem;
});
}

View File

@ -20,11 +20,12 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./LocalTimeSystem', './LocalTimeFormat'], function (LocalTimeSystem, LocalTimeFormat) {
return function () {
return function (openmct) {
openmct.time.addTimeSystem(new LocalTimeSystem());
openmct.telemetry.addFormat(new LocalTimeFormat());
};
import LocalTimeFormat from './LocalTimeFormat';
import LocalTimeSystem from './LocalTimeSystem';
export default function () {
return function (openmct) {
openmct.time.addTimeSystem(new LocalTimeSystem());
openmct.telemetry.addFormat(new LocalTimeFormat());
};
});
}

View File

@ -20,260 +20,260 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['uuid'], function ({ v4: uuid }) {
return function Migrations(openmct) {
function getColumnNameKeyMap(domainObject) {
let composition = openmct.composition.get(domainObject);
if (composition) {
return composition.load().then((composees) => {
return composees.reduce((nameKeyMap, composee) => {
let metadata = openmct.telemetry.getMetadata(composee);
if (metadata !== undefined) {
metadata.values().forEach((value) => {
nameKeyMap[value.name] = value.key;
});
}
import { v4 as uuid } from 'uuid';
return nameKeyMap;
}, {});
});
} else {
return Promise.resolve([]);
}
}
function isTelemetry(domainObject) {
if (
openmct.telemetry.isTelemetryObject(domainObject) &&
domainObject.type !== 'summary-widget' &&
domainObject.type !== 'example.imagery'
) {
return true;
} else {
return false;
}
}
function migrateDisplayLayout(domainObject, childObjects) {
const DEFAULT_GRID_SIZE = [32, 32];
let migratedObject = Object.assign({}, domainObject);
let panels = migratedObject.configuration.layout.panels;
let items = [];
Object.keys(panels).forEach((key) => {
let panel = panels[key];
let childDomainObject = childObjects[key];
let identifier = undefined;
if (isTelemetry(childDomainObject)) {
// If object is a telemetry point, convert it to a plot and
// replace the object in migratedObject composition with the plot.
identifier = {
key: uuid(),
namespace: migratedObject.identifier.namespace
};
let plotObject = {
identifier: identifier,
location: childDomainObject.location,
name: childDomainObject.name,
type: 'telemetry.plot.overlay'
};
let plotType = openmct.types.get('telemetry.plot.overlay');
plotType.definition.initialize(plotObject);
plotObject.composition.push(childDomainObject.identifier);
openmct.objects.mutate(plotObject, 'persisted', Date.now());
let keyString = openmct.objects.makeKeyString(childDomainObject.identifier);
let clonedComposition = Object.assign([], migratedObject.composition);
clonedComposition.forEach((objIdentifier, index) => {
if (openmct.objects.makeKeyString(objIdentifier) === keyString) {
migratedObject.composition[index] = plotObject.identifier;
}
});
}
items.push({
width: panel.dimensions[0],
height: panel.dimensions[1],
x: panel.position[0],
y: panel.position[1],
identifier: identifier || childDomainObject.identifier,
id: uuid(),
type: 'subobject-view',
hasFrame: panel.hasFrame
});
});
migratedObject.configuration.items = items;
migratedObject.configuration.layoutGrid = migratedObject.layoutGrid || DEFAULT_GRID_SIZE;
delete migratedObject.layoutGrid;
delete migratedObject.configuration.layout;
return migratedObject;
}
function migrateFixedPositionConfiguration(elements, telemetryObjects, gridSize) {
const DEFAULT_STROKE = 'transparent';
const DEFAULT_SIZE = '13px';
const DEFAULT_COLOR = '';
const DEFAULT_FILL = '';
let items = [];
elements.forEach((element) => {
let item = {
x: element.x,
y: element.y,
width: element.width,
height: element.height,
id: uuid()
};
if (!element.useGrid) {
item.x = Math.round(item.x / gridSize[0]);
item.y = Math.round(item.y / gridSize[1]);
item.width = Math.round(item.width / gridSize[0]);
item.height = Math.round(item.height / gridSize[1]);
}
if (element.type === 'fixed.telemetry') {
item.type = 'telemetry-view';
item.stroke = element.stroke || DEFAULT_STROKE;
item.fill = element.fill || DEFAULT_FILL;
item.color = element.color || DEFAULT_COLOR;
item.size = element.size || DEFAULT_SIZE;
item.identifier = telemetryObjects[element.id].identifier;
item.displayMode = element.titled ? 'all' : 'value';
item.value = openmct.telemetry
.getMetadata(telemetryObjects[element.id])
.getDefaultDisplayValue()?.key;
} else if (element.type === 'fixed.box') {
item.type = 'box-view';
item.stroke = element.stroke || DEFAULT_STROKE;
item.fill = element.fill || DEFAULT_FILL;
} else if (element.type === 'fixed.line') {
item.type = 'line-view';
item.x2 = element.x2;
item.y2 = element.y2;
item.stroke = element.stroke || DEFAULT_STROKE;
delete item.height;
delete item.width;
} else if (element.type === 'fixed.text') {
item.type = 'text-view';
item.text = element.text;
item.stroke = element.stroke || DEFAULT_STROKE;
item.fill = element.fill || DEFAULT_FILL;
item.color = element.color || DEFAULT_COLOR;
item.size = element.size || DEFAULT_SIZE;
} else if (element.type === 'fixed.image') {
item.type = 'image-view';
item.url = element.url;
item.stroke = element.stroke || DEFAULT_STROKE;
}
items.push(item);
});
return items;
}
return [
{
check(domainObject) {
return (
domainObject?.type === 'layout' &&
domainObject.configuration &&
domainObject.configuration.layout
);
},
migrate(domainObject) {
let childObjects = {};
let promises = Object.keys(domainObject.configuration.layout.panels).map((key) => {
return openmct.objects.get(key).then((object) => {
childObjects[key] = object;
export default function Migrations(openmct) {
function getColumnNameKeyMap(domainObject) {
let composition = openmct.composition.get(domainObject);
if (composition) {
return composition.load().then((composees) => {
return composees.reduce((nameKeyMap, composee) => {
let metadata = openmct.telemetry.getMetadata(composee);
if (metadata !== undefined) {
metadata.values().forEach((value) => {
nameKeyMap[value.name] = value.key;
});
});
}
return Promise.all(promises).then(function () {
return migrateDisplayLayout(domainObject, childObjects);
});
}
},
{
check(domainObject) {
return (
domainObject?.type === 'telemetry.fixed' &&
domainObject.configuration &&
domainObject.configuration['fixed-display']
);
},
migrate(domainObject) {
const DEFAULT_GRID_SIZE = [64, 16];
let newLayoutObject = {
identifier: domainObject.identifier,
location: domainObject.location,
name: domainObject.name,
type: 'layout'
};
let gridSize = domainObject.layoutGrid || DEFAULT_GRID_SIZE;
let layoutType = openmct.types.get('layout');
layoutType.definition.initialize(newLayoutObject);
newLayoutObject.composition = domainObject.composition;
newLayoutObject.configuration.layoutGrid = gridSize;
return nameKeyMap;
}, {});
});
} else {
return Promise.resolve([]);
}
}
let elements = domainObject.configuration['fixed-display'].elements;
let telemetryObjects = {};
let promises = elements.map((element) => {
if (element.id) {
return openmct.objects.get(element.id).then((object) => {
telemetryObjects[element.id] = object;
});
} else {
return Promise.resolve(false);
}
});
function isTelemetry(domainObject) {
if (
openmct.telemetry.isTelemetryObject(domainObject) &&
domainObject.type !== 'summary-widget' &&
domainObject.type !== 'example.imagery'
) {
return true;
} else {
return false;
}
}
return Promise.all(promises).then(function () {
newLayoutObject.configuration.items = migrateFixedPositionConfiguration(
elements,
telemetryObjects,
gridSize
);
function migrateDisplayLayout(domainObject, childObjects) {
const DEFAULT_GRID_SIZE = [32, 32];
let migratedObject = Object.assign({}, domainObject);
let panels = migratedObject.configuration.layout.panels;
let items = [];
return newLayoutObject;
});
}
},
{
check(domainObject) {
return (
domainObject?.type === 'table' &&
domainObject.configuration &&
domainObject.configuration.table
);
},
migrate(domainObject) {
let currentTableConfiguration = domainObject.configuration.table || {};
let currentColumnConfiguration = currentTableConfiguration.columns || {};
Object.keys(panels).forEach((key) => {
let panel = panels[key];
let childDomainObject = childObjects[key];
let identifier = undefined;
return getColumnNameKeyMap(domainObject).then((nameKeyMap) => {
let hiddenColumns = Object.keys(currentColumnConfiguration)
.filter((columnName) => {
return currentColumnConfiguration[columnName] === false;
})
.reduce((hiddenColumnsMap, hiddenColumnName) => {
let key = nameKeyMap[hiddenColumnName];
hiddenColumnsMap[key] = true;
if (isTelemetry(childDomainObject)) {
// If object is a telemetry point, convert it to a plot and
// replace the object in migratedObject composition with the plot.
identifier = {
key: uuid(),
namespace: migratedObject.identifier.namespace
};
let plotObject = {
identifier: identifier,
location: childDomainObject.location,
name: childDomainObject.name,
type: 'telemetry.plot.overlay'
};
let plotType = openmct.types.get('telemetry.plot.overlay');
plotType.definition.initialize(plotObject);
plotObject.composition.push(childDomainObject.identifier);
openmct.objects.mutate(plotObject, 'persisted', Date.now());
return hiddenColumnsMap;
}, {});
domainObject.configuration.hiddenColumns = hiddenColumns;
delete domainObject.configuration.table;
return domainObject;
});
}
let keyString = openmct.objects.makeKeyString(childDomainObject.identifier);
let clonedComposition = Object.assign([], migratedObject.composition);
clonedComposition.forEach((objIdentifier, index) => {
if (openmct.objects.makeKeyString(objIdentifier) === keyString) {
migratedObject.composition[index] = plotObject.identifier;
}
});
}
];
};
});
items.push({
width: panel.dimensions[0],
height: panel.dimensions[1],
x: panel.position[0],
y: panel.position[1],
identifier: identifier || childDomainObject.identifier,
id: uuid(),
type: 'subobject-view',
hasFrame: panel.hasFrame
});
});
migratedObject.configuration.items = items;
migratedObject.configuration.layoutGrid = migratedObject.layoutGrid || DEFAULT_GRID_SIZE;
delete migratedObject.layoutGrid;
delete migratedObject.configuration.layout;
return migratedObject;
}
function migrateFixedPositionConfiguration(elements, telemetryObjects, gridSize) {
const DEFAULT_STROKE = 'transparent';
const DEFAULT_SIZE = '13px';
const DEFAULT_COLOR = '';
const DEFAULT_FILL = '';
let items = [];
elements.forEach((element) => {
let item = {
x: element.x,
y: element.y,
width: element.width,
height: element.height,
id: uuid()
};
if (!element.useGrid) {
item.x = Math.round(item.x / gridSize[0]);
item.y = Math.round(item.y / gridSize[1]);
item.width = Math.round(item.width / gridSize[0]);
item.height = Math.round(item.height / gridSize[1]);
}
if (element.type === 'fixed.telemetry') {
item.type = 'telemetry-view';
item.stroke = element.stroke || DEFAULT_STROKE;
item.fill = element.fill || DEFAULT_FILL;
item.color = element.color || DEFAULT_COLOR;
item.size = element.size || DEFAULT_SIZE;
item.identifier = telemetryObjects[element.id].identifier;
item.displayMode = element.titled ? 'all' : 'value';
item.value = openmct.telemetry
.getMetadata(telemetryObjects[element.id])
.getDefaultDisplayValue()?.key;
} else if (element.type === 'fixed.box') {
item.type = 'box-view';
item.stroke = element.stroke || DEFAULT_STROKE;
item.fill = element.fill || DEFAULT_FILL;
} else if (element.type === 'fixed.line') {
item.type = 'line-view';
item.x2 = element.x2;
item.y2 = element.y2;
item.stroke = element.stroke || DEFAULT_STROKE;
delete item.height;
delete item.width;
} else if (element.type === 'fixed.text') {
item.type = 'text-view';
item.text = element.text;
item.stroke = element.stroke || DEFAULT_STROKE;
item.fill = element.fill || DEFAULT_FILL;
item.color = element.color || DEFAULT_COLOR;
item.size = element.size || DEFAULT_SIZE;
} else if (element.type === 'fixed.image') {
item.type = 'image-view';
item.url = element.url;
item.stroke = element.stroke || DEFAULT_STROKE;
}
items.push(item);
});
return items;
}
return [
{
check(domainObject) {
return (
domainObject?.type === 'layout' &&
domainObject.configuration &&
domainObject.configuration.layout
);
},
migrate(domainObject) {
let childObjects = {};
let promises = Object.keys(domainObject.configuration.layout.panels).map((key) => {
return openmct.objects.get(key).then((object) => {
childObjects[key] = object;
});
});
return Promise.all(promises).then(function () {
return migrateDisplayLayout(domainObject, childObjects);
});
}
},
{
check(domainObject) {
return (
domainObject?.type === 'telemetry.fixed' &&
domainObject.configuration &&
domainObject.configuration['fixed-display']
);
},
migrate(domainObject) {
const DEFAULT_GRID_SIZE = [64, 16];
let newLayoutObject = {
identifier: domainObject.identifier,
location: domainObject.location,
name: domainObject.name,
type: 'layout'
};
let gridSize = domainObject.layoutGrid || DEFAULT_GRID_SIZE;
let layoutType = openmct.types.get('layout');
layoutType.definition.initialize(newLayoutObject);
newLayoutObject.composition = domainObject.composition;
newLayoutObject.configuration.layoutGrid = gridSize;
let elements = domainObject.configuration['fixed-display'].elements;
let telemetryObjects = {};
let promises = elements.map((element) => {
if (element.id) {
return openmct.objects.get(element.id).then((object) => {
telemetryObjects[element.id] = object;
});
} else {
return Promise.resolve(false);
}
});
return Promise.all(promises).then(function () {
newLayoutObject.configuration.items = migrateFixedPositionConfiguration(
elements,
telemetryObjects,
gridSize
);
return newLayoutObject;
});
}
},
{
check(domainObject) {
return (
domainObject?.type === 'table' &&
domainObject.configuration &&
domainObject.configuration.table
);
},
migrate(domainObject) {
let currentTableConfiguration = domainObject.configuration.table || {};
let currentColumnConfiguration = currentTableConfiguration.columns || {};
return getColumnNameKeyMap(domainObject).then((nameKeyMap) => {
let hiddenColumns = Object.keys(currentColumnConfiguration)
.filter((columnName) => {
return currentColumnConfiguration[columnName] === false;
})
.reduce((hiddenColumnsMap, hiddenColumnName) => {
let key = nameKeyMap[hiddenColumnName];
hiddenColumnsMap[key] = true;
return hiddenColumnsMap;
}, {});
domainObject.configuration.hiddenColumns = hiddenColumns;
delete domainObject.configuration.table;
return domainObject;
});
}
}
];
}

View File

@ -20,225 +20,155 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'lodash',
'./utcTimeSystem/plugin',
'./remoteClock/plugin',
'./localTimeSystem/plugin',
'./ISOTimeFormat/plugin',
'./myItems/plugin',
'../../example/generator/plugin',
'../../example/eventGenerator/plugin',
'../../example/dataVisualization/plugin',
'./autoflow/AutoflowTabularPlugin',
'./timeConductor/plugin',
'../../example/imagery/plugin',
'../../example/faultManagement/exampleFaultSource',
'./imagery/plugin',
'./summaryWidget/plugin',
'./URLIndicatorPlugin/URLIndicatorPlugin',
'./telemetryMean/plugin',
'./plot/plugin',
'./charts/bar/plugin',
'./charts/scatter/plugin',
'./telemetryTable/plugin',
'./staticRootPlugin/plugin',
'./notebook/plugin',
'./displayLayout/plugin',
'./formActions/plugin',
'./folderView/plugin',
'./flexibleLayout/plugin',
'./tabs/plugin',
'./LADTable/plugin',
'./filters/plugin',
'./objectMigration/plugin',
'./goToOriginalAction/plugin',
'./openInNewTabAction/plugin',
'./clearData/plugin',
'./webPage/plugin',
'./condition/plugin',
'./conditionWidget/plugin',
'./themes/espresso',
'./themes/snow',
'./URLTimeSettingsSynchronizer/plugin',
'./notificationIndicator/plugin',
'./newFolderAction/plugin',
'./persistence/couch/plugin',
'./defaultRootName/plugin',
'./plan/plugin',
'./viewDatumAction/plugin',
'./viewLargeAction/plugin',
'./interceptors/plugin',
'./performanceIndicator/plugin',
'./CouchDBSearchFolder/plugin',
'./timeline/plugin',
'./hyperlink/plugin',
'./clock/plugin',
'./DeviceClassifier/plugin',
'./timer/plugin',
'./userIndicator/plugin',
'../../example/exampleUser/plugin',
'./localStorage/plugin',
'./operatorStatus/plugin',
'./gauge/GaugePlugin',
'./timelist/plugin',
'./faultManagement/FaultManagementPlugin',
'../../example/exampleTags/plugin',
'./inspectorViews/plugin',
'./inspectorDataVisualization/plugin'
], function (
_,
UTCTimeSystem,
RemoteClock,
LocalTimeSystem,
ISOTimeFormat,
MyItems,
GeneratorPlugin,
EventGeneratorPlugin,
ExampleDataVisualizationSourcePlugin,
AutoflowPlugin,
TimeConductorPlugin,
ExampleImagery,
ExampleFaultSource,
ImageryPlugin,
SummaryWidget,
URLIndicatorPlugin,
TelemetryMean,
PlotPlugin,
BarChartPlugin,
ScatterPlotPlugin,
TelemetryTablePlugin,
StaticRootPlugin,
Notebook,
DisplayLayoutPlugin,
FormActions,
FolderView,
FlexibleLayout,
Tabs,
LADTable,
Filters,
ObjectMigration,
GoToOriginalAction,
OpenInNewTabAction,
ClearData,
WebPagePlugin,
ConditionPlugin,
ConditionWidgetPlugin,
Espresso,
Snow,
URLTimeSettingsSynchronizer,
NotificationIndicator,
NewFolderAction,
CouchDBPlugin,
DefaultRootName,
PlanLayout,
ViewDatumAction,
ViewLargeAction,
ObjectInterceptors,
PerformanceIndicator,
CouchDBSearchFolder,
Timeline,
Hyperlink,
Clock,
DeviceClassifier,
Timer,
UserIndicator,
ExampleUser,
LocalStorage,
OperatorStatus,
GaugePlugin,
TimeList,
FaultManagementPlugin,
ExampleTags,
InspectorViews,
InspectorDataVisualization
) {
const plugins = {};
import ExampleDataVisualizationSourcePlugin from '../../example/dataVisualization/plugin';
import EventGeneratorPlugin from '../../example/eventGenerator/plugin';
import ExampleTags from '../../example/exampleTags/plugin';
import ExampleUser from '../../example/exampleUser/plugin';
import ExampleFaultSource from '../../example/faultManagement/exampleFaultSource';
import GeneratorPlugin from '../../example/generator/plugin';
import ExampleImagery from '../../example/imagery/plugin';
import AutoflowPlugin from './autoflow/AutoflowTabularPlugin';
import BarChartPlugin from './charts/bar/plugin';
import ScatterPlotPlugin from './charts/scatter/plugin';
import ClearData from './clearData/plugin';
import Clock from './clock/plugin';
import ConditionPlugin from './condition/plugin';
import ConditionWidgetPlugin from './conditionWidget/plugin';
import CouchDBSearchFolder from './CouchDBSearchFolder/plugin';
import DefaultRootName from './defaultRootName/plugin';
import DeviceClassifier from './DeviceClassifier/plugin';
import DisplayLayoutPlugin from './displayLayout/plugin';
import FaultManagementPlugin from './faultManagement/FaultManagementPlugin';
import Filters from './filters/plugin';
import FlexibleLayout from './flexibleLayout/plugin';
import FolderView from './folderView/plugin';
import FormActions from './formActions/plugin';
import GaugePlugin from './gauge/GaugePlugin';
import GoToOriginalAction from './goToOriginalAction/plugin';
import Hyperlink from './hyperlink/plugin';
import ImageryPlugin from './imagery/plugin';
import InspectorDataVisualization from './inspectorDataVisualization/plugin';
import InspectorViews from './inspectorViews/plugin';
import ObjectInterceptors from './interceptors/plugin';
import ISOTimeFormat from './ISOTimeFormat/plugin';
import LADTable from './LADTable/plugin';
import LocalStorage from './localStorage/plugin';
import LocalTimeSystem from './localTimeSystem/plugin';
import MyItems from './myItems/plugin';
import NewFolderAction from './newFolderAction/plugin';
import { NotebookPlugin, RestrictedNotebookPlugin } from './notebook/plugin';
import NotificationIndicator from './notificationIndicator/plugin';
import ObjectMigration from './objectMigration/plugin';
import OpenInNewTabAction from './openInNewTabAction/plugin';
import OperatorStatus from './operatorStatus/plugin';
import PerformanceIndicator from './performanceIndicator/plugin';
import CouchDBPlugin from './persistence/couch/plugin';
import PlanLayout from './plan/plugin';
import PlotPlugin from './plot/plugin';
import RemoteClock from './remoteClock/plugin';
import StaticRootPlugin from './staticRootPlugin/plugin';
import SummaryWidget from './summaryWidget/plugin';
import Tabs from './tabs/plugin';
import TelemetryMean from './telemetryMean/plugin';
import TelemetryTablePlugin from './telemetryTable/plugin';
import Espresso from './themes/espresso';
import Snow from './themes/snow';
import TimeConductorPlugin from './timeConductor/plugin';
import Timeline from './timeline/plugin';
import TimeList from './timelist/plugin';
import Timer from './timer/plugin';
import URLIndicatorPlugin from './URLIndicatorPlugin/URLIndicatorPlugin';
import URLTimeSettingsSynchronizer from './URLTimeSettingsSynchronizer/plugin';
import UserIndicator from './userIndicator/plugin';
import UTCTimeSystem from './utcTimeSystem/plugin';
import ViewDatumAction from './viewDatumAction/plugin';
import ViewLargeAction from './viewLargeAction/plugin';
import WebPagePlugin from './webPage/plugin';
plugins.example = {};
plugins.example.ExampleUser = ExampleUser.default;
plugins.example.ExampleImagery = ExampleImagery.default;
plugins.example.ExampleFaultSource = ExampleFaultSource.default;
plugins.example.EventGeneratorPlugin = EventGeneratorPlugin.default;
plugins.example.ExampleDataVisualizationSourcePlugin =
ExampleDataVisualizationSourcePlugin.default;
plugins.example.ExampleTags = ExampleTags.default;
plugins.example.Generator = () => GeneratorPlugin.default;
const plugins = {};
plugins.UTCTimeSystem = UTCTimeSystem.default;
plugins.LocalTimeSystem = LocalTimeSystem;
plugins.RemoteClock = RemoteClock.default;
plugins.example = {};
plugins.example.ExampleUser = ExampleUser;
plugins.example.ExampleImagery = ExampleImagery;
plugins.example.ExampleFaultSource = ExampleFaultSource;
plugins.example.EventGeneratorPlugin = EventGeneratorPlugin;
plugins.example.ExampleDataVisualizationSourcePlugin = ExampleDataVisualizationSourcePlugin;
plugins.example.ExampleTags = ExampleTags;
plugins.example.Generator = () => GeneratorPlugin;
plugins.MyItems = MyItems.default;
plugins.UTCTimeSystem = UTCTimeSystem;
plugins.LocalTimeSystem = LocalTimeSystem;
plugins.RemoteClock = RemoteClock;
plugins.StaticRootPlugin = StaticRootPlugin.default;
plugins.MyItems = MyItems;
/**
* A tabular view showing the latest values of multiple telemetry points at
* once. Formatted so that labels and values are aligned.
*
* @param {Object} [options] Optional settings to apply to the autoflow
* tabular view. Currently supports one option, 'type'.
* @param {string} [options.type] The key of an object type to apply this view
* to exclusively.
*/
plugins.AutoflowView = AutoflowPlugin;
plugins.StaticRootPlugin = StaticRootPlugin;
plugins.Conductor = TimeConductorPlugin.default;
/**
* A tabular view showing the latest values of multiple telemetry points at
* once. Formatted so that labels and values are aligned.
*
* @param {Object} [options] Optional settings to apply to the autoflow
* tabular view. Currently supports one option, 'type'.
* @param {string} [options.type] The key of an object type to apply this view
* to exclusively.
*/
plugins.AutoflowView = AutoflowPlugin;
plugins.CouchDB = CouchDBPlugin.default;
plugins.Conductor = TimeConductorPlugin;
plugins.ImageryPlugin = ImageryPlugin;
plugins.Plot = PlotPlugin.default;
plugins.BarChart = BarChartPlugin.default;
plugins.ScatterPlot = ScatterPlotPlugin.default;
plugins.TelemetryTable = TelemetryTablePlugin;
plugins.CouchDB = CouchDBPlugin;
plugins.SummaryWidget = SummaryWidget;
plugins.TelemetryMean = TelemetryMean;
plugins.URLIndicator = URLIndicatorPlugin;
plugins.Notebook = Notebook.NotebookPlugin;
plugins.RestrictedNotebook = Notebook.RestrictedNotebookPlugin;
plugins.DisplayLayout = DisplayLayoutPlugin.default;
plugins.FaultManagement = FaultManagementPlugin.default;
plugins.FormActions = FormActions;
plugins.FolderView = FolderView.default;
plugins.Tabs = Tabs;
plugins.FlexibleLayout = FlexibleLayout;
plugins.LADTable = LADTable.default;
plugins.Filters = Filters;
plugins.ObjectMigration = ObjectMigration.default;
plugins.GoToOriginalAction = GoToOriginalAction.default;
plugins.OpenInNewTabAction = OpenInNewTabAction.default;
plugins.ClearData = ClearData.default;
plugins.WebPage = WebPagePlugin.default;
plugins.Espresso = Espresso.default;
plugins.Snow = Snow.default;
plugins.Condition = ConditionPlugin.default;
plugins.ConditionWidget = ConditionWidgetPlugin.default;
plugins.URLTimeSettingsSynchronizer = URLTimeSettingsSynchronizer.default;
plugins.NotificationIndicator = NotificationIndicator.default;
plugins.NewFolderAction = NewFolderAction.default;
plugins.ISOTimeFormat = ISOTimeFormat.default;
plugins.DefaultRootName = DefaultRootName.default;
plugins.PlanLayout = PlanLayout.default;
plugins.ViewDatumAction = ViewDatumAction.default;
plugins.ViewLargeAction = ViewLargeAction.default;
plugins.ObjectInterceptors = ObjectInterceptors.default;
plugins.PerformanceIndicator = PerformanceIndicator.default;
plugins.CouchDBSearchFolder = CouchDBSearchFolder.default;
plugins.Timeline = Timeline.default;
plugins.Hyperlink = Hyperlink.default;
plugins.Clock = Clock.default;
plugins.Timer = Timer.default;
plugins.DeviceClassifier = DeviceClassifier.default;
plugins.UserIndicator = UserIndicator.default;
plugins.LocalStorage = LocalStorage.default;
plugins.OperatorStatus = OperatorStatus.default;
plugins.Gauge = GaugePlugin.default;
plugins.Timelist = TimeList.default;
plugins.InspectorViews = InspectorViews.default;
plugins.InspectorDataVisualization = InspectorDataVisualization.default;
plugins.ImageryPlugin = ImageryPlugin;
plugins.Plot = PlotPlugin;
plugins.BarChart = BarChartPlugin;
plugins.ScatterPlot = ScatterPlotPlugin;
plugins.TelemetryTable = TelemetryTablePlugin;
return plugins;
});
plugins.SummaryWidget = SummaryWidget;
plugins.TelemetryMean = TelemetryMean;
plugins.URLIndicator = URLIndicatorPlugin;
plugins.Notebook = NotebookPlugin;
plugins.RestrictedNotebook = RestrictedNotebookPlugin;
plugins.DisplayLayout = DisplayLayoutPlugin;
plugins.FaultManagement = FaultManagementPlugin;
plugins.FormActions = FormActions;
plugins.FolderView = FolderView;
plugins.Tabs = Tabs;
plugins.FlexibleLayout = FlexibleLayout;
plugins.LADTable = LADTable;
plugins.Filters = Filters;
plugins.ObjectMigration = ObjectMigration;
plugins.GoToOriginalAction = GoToOriginalAction;
plugins.OpenInNewTabAction = OpenInNewTabAction;
plugins.ClearData = ClearData;
plugins.WebPage = WebPagePlugin;
plugins.Espresso = Espresso;
plugins.Snow = Snow;
plugins.Condition = ConditionPlugin;
plugins.ConditionWidget = ConditionWidgetPlugin;
plugins.URLTimeSettingsSynchronizer = URLTimeSettingsSynchronizer;
plugins.NotificationIndicator = NotificationIndicator;
plugins.NewFolderAction = NewFolderAction;
plugins.ISOTimeFormat = ISOTimeFormat;
plugins.DefaultRootName = DefaultRootName;
plugins.PlanLayout = PlanLayout;
plugins.ViewDatumAction = ViewDatumAction;
plugins.ViewLargeAction = ViewLargeAction;
plugins.ObjectInterceptors = ObjectInterceptors;
plugins.PerformanceIndicator = PerformanceIndicator;
plugins.CouchDBSearchFolder = CouchDBSearchFolder;
plugins.Timeline = Timeline;
plugins.Hyperlink = Hyperlink;
plugins.Clock = Clock;
plugins.Timer = Timer;
plugins.DeviceClassifier = DeviceClassifier;
plugins.UserIndicator = UserIndicator;
plugins.LocalStorage = LocalStorage;
plugins.OperatorStatus = OperatorStatus;
plugins.Gauge = GaugePlugin;
plugins.Timelist = TimeList;
plugins.InspectorViews = InspectorViews;
plugins.InspectorDataVisualization = InspectorDataVisualization;
export default plugins;

View File

@ -20,20 +20,16 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
/**
* Policy determining which views can apply to summary widget. Disables
* any view other than normal summary widget view.
*/
function SummaryWidgetViewPolicy() {}
/**
* Policy determining which views can apply to summary widget. Disables
* any view other than normal summary widget view.
*/
export default function SummaryWidgetViewPolicy() {}
SummaryWidgetViewPolicy.prototype.allow = function (view, domainObject) {
if (domainObject.getModel().type === 'summary-widget') {
return view.key === 'summary-widget-viewer';
}
SummaryWidgetViewPolicy.prototype.allow = function (view, domainObject) {
if (domainObject.getModel().type === 'summary-widget') {
return view.key === 'summary-widget-viewer';
}
return true;
};
return SummaryWidgetViewPolicy;
});
return true;
};

View File

@ -20,20 +20,16 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
function SummaryWidgetsCompositionPolicy(openmct) {
this.openmct = openmct;
export default function SummaryWidgetsCompositionPolicy(openmct) {
this.openmct = openmct;
}
SummaryWidgetsCompositionPolicy.prototype.allow = function (parent, child) {
const parentType = parent.type;
if (parentType === 'summary-widget' && !this.openmct.telemetry.isTelemetryObject(child)) {
return false;
}
SummaryWidgetsCompositionPolicy.prototype.allow = function (parent, child) {
const parentType = parent.type;
if (parentType === 'summary-widget' && !this.openmct.telemetry.isTelemetryObject(child)) {
return false;
}
return true;
};
return SummaryWidgetsCompositionPolicy;
});
return true;
};

View File

@ -1,98 +1,87 @@
define([
'./SummaryWidgetsCompositionPolicy',
'./src/telemetry/SummaryWidgetMetadataProvider',
'./src/telemetry/SummaryWidgetTelemetryProvider',
'./src/views/SummaryWidgetViewProvider',
'./SummaryWidgetViewPolicy'
], function (
SummaryWidgetsCompositionPolicy,
SummaryWidgetMetadataProvider,
SummaryWidgetTelemetryProvider,
SummaryWidgetViewProvider,
SummaryWidgetViewPolicy
) {
function plugin() {
const widgetType = {
name: 'Summary Widget',
description: 'A compact status update for collections of telemetry-producing items',
cssClass: 'icon-summary-widget',
initialize: function (domainObject) {
domainObject.composition = [];
domainObject.configuration = {
ruleOrder: ['default'],
ruleConfigById: {
default: {
name: 'Default',
label: 'Unnamed Rule',
message: '',
id: 'default',
icon: ' ',
style: {
color: '#ffffff',
'background-color': '#38761d',
'border-color': 'rgba(0,0,0,0)'
},
description: 'Default appearance for the widget',
conditions: [
{
object: '',
key: '',
operation: '',
values: []
}
],
jsCondition: '',
trigger: 'any',
expanded: 'true'
}
},
testDataConfig: [
{
object: '',
key: '',
value: ''
}
]
};
domainObject.openNewTab = 'thisTab';
domainObject.telemetry = {};
},
form: [
{
key: 'url',
name: 'URL',
control: 'textfield',
required: false,
cssClass: 'l-input-lg'
},
{
key: 'openNewTab',
name: 'Tab to Open Hyperlink',
control: 'select',
options: [
{
value: 'thisTab',
name: 'Open in this tab'
import SummaryWidgetMetadataProvider from './src/telemetry/SummaryWidgetMetadataProvider';
import SummaryWidgetTelemetryProvider from './src/telemetry/SummaryWidgetTelemetryProvider';
import SummaryWidgetViewProvider from './src/views/SummaryWidgetViewProvider';
import SummaryWidgetsCompositionPolicy from './SummaryWidgetsCompositionPolicy';
export default function plugin() {
const widgetType = {
name: 'Summary Widget',
description: 'A compact status update for collections of telemetry-producing items',
cssClass: 'icon-summary-widget',
initialize: function (domainObject) {
domainObject.composition = [];
domainObject.configuration = {
ruleOrder: ['default'],
ruleConfigById: {
default: {
name: 'Default',
label: 'Unnamed Rule',
message: '',
id: 'default',
icon: ' ',
style: {
color: '#ffffff',
'background-color': '#38761d',
'border-color': 'rgba(0,0,0,0)'
},
{
value: 'newTab',
name: 'Open in a new tab'
}
],
cssClass: 'l-inline'
}
]
};
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: [
{
key: 'url',
name: 'URL',
control: 'textfield',
required: false,
cssClass: 'l-input-lg'
},
{
key: 'openNewTab',
name: 'Tab to Open Hyperlink',
control: 'select',
options: [
{
value: 'thisTab',
name: 'Open in this tab'
},
{
value: 'newTab',
name: 'Open in a new tab'
}
],
cssClass: 'l-inline'
}
]
};
return function install(openmct) {
openmct.types.addType('summary-widget', widgetType);
let compositionPolicy = new SummaryWidgetsCompositionPolicy(openmct);
openmct.composition.addPolicy(compositionPolicy.allow.bind(compositionPolicy));
openmct.telemetry.addProvider(new SummaryWidgetMetadataProvider(openmct));
openmct.telemetry.addProvider(new SummaryWidgetTelemetryProvider(openmct));
openmct.objectViews.addProvider(new SummaryWidgetViewProvider(openmct));
};
}
return plugin;
});
return function install(openmct) {
openmct.types.addType('summary-widget', widgetType);
let compositionPolicy = new SummaryWidgetsCompositionPolicy(openmct);
openmct.composition.addPolicy(compositionPolicy.allow.bind(compositionPolicy));
openmct.telemetry.addProvider(new SummaryWidgetMetadataProvider(openmct));
openmct.telemetry.addProvider(new SummaryWidgetTelemetryProvider(openmct));
openmct.objectViews.addProvider(new SummaryWidgetViewProvider(openmct));
};
}

View File

@ -19,242 +19,232 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'../res/conditionTemplate.html',
'./input/ObjectSelect',
'./input/KeySelect',
'./input/OperationSelect',
'./eventHelpers',
'../../../utils/template/templateHelpers',
'EventEmitter'
], function (
conditionTemplate,
ObjectSelect,
KeySelect,
OperationSelect,
eventHelpers,
templateHelpers,
EventEmitter
) {
import EventEmitter from 'EventEmitter';
import * as templateHelpers from '../../../utils/template/templateHelpers';
import conditionTemplate from '../res/conditionTemplate.html';
import eventHelpers from './eventHelpers';
import KeySelect from './input/KeySelect';
import ObjectSelect from './input/ObjectSelect';
import OperationSelect from './input/OperationSelect';
/**
* Represents an individual condition for a summary widget rule. Manages the
* associated inputs and view.
* @param {Object} conditionConfig The configuration for this condition, consisting
* of object, key, operation, and values fields
* @param {number} index the index of this Condition object in it's parent Rule's data model,
* to be injected into callbacks for removes
* @param {ConditionManager} conditionManager A ConditionManager instance for populating
* selects with configuration data
*/
export default function Condition(conditionConfig, index, conditionManager) {
eventHelpers.extend(this);
this.config = conditionConfig;
this.index = index;
this.conditionManager = conditionManager;
this.domElement = templateHelpers.convertTemplateToHTML(conditionTemplate)[0];
this.eventEmitter = new EventEmitter();
this.supportedCallbacks = ['remove', 'duplicate', 'change'];
this.deleteButton = this.domElement.querySelector('.t-delete');
this.duplicateButton = this.domElement.querySelector('.t-duplicate');
this.selects = {};
this.valueInputs = [];
const self = this;
/**
* Represents an individual condition for a summary widget rule. Manages the
* associated inputs and view.
* @param {Object} conditionConfig The configuration for this condition, consisting
* of object, key, operation, and values fields
* @param {number} index the index of this Condition object in it's parent Rule's data model,
* to be injected into callbacks for removes
* @param {ConditionManager} conditionManager A ConditionManager instance for populating
* selects with configuration data
* Event handler for a change in one of this conditions' custom selects
* @param {string} value The new value of this selects
* @param {string} property The property of this condition to modify
* @private
*/
function Condition(conditionConfig, index, conditionManager) {
eventHelpers.extend(this);
this.config = conditionConfig;
this.index = index;
this.conditionManager = conditionManager;
this.domElement = templateHelpers.convertTemplateToHTML(conditionTemplate)[0];
this.eventEmitter = new EventEmitter();
this.supportedCallbacks = ['remove', 'duplicate', 'change'];
this.deleteButton = this.domElement.querySelector('.t-delete');
this.duplicateButton = this.domElement.querySelector('.t-duplicate');
this.selects = {};
this.valueInputs = [];
const self = this;
/**
* Event handler for a change in one of this conditions' custom selects
* @param {string} value The new value of this selects
* @param {string} property The property of this condition to modify
* @private
*/
function onSelectChange(value, property) {
if (property === 'operation') {
self.generateValueInputs(value);
}
self.eventEmitter.emit('change', {
value: value,
property: property,
index: self.index
});
function onSelectChange(value, property) {
if (property === 'operation') {
self.generateValueInputs(value);
}
this.handleObjectChange = (value) => onSelectChange(value, 'object');
this.handleKeyChange = (value) => onSelectChange(value, 'key');
/**
* Event handler for this conditions value inputs
* @param {Event} event The oninput event that triggered this callback
* @private
*/
function onValueInput(event) {
const elem = event.target;
const value = isNaN(Number(elem.value)) ? elem.value : Number(elem.value);
const inputIndex = self.valueInputs.indexOf(elem);
self.eventEmitter.emit('change', {
value: value,
property: 'values[' + inputIndex + ']',
index: self.index
});
}
this.listenTo(this.deleteButton, 'click', this.remove, this);
this.listenTo(this.duplicateButton, 'click', this.duplicate, this);
this.selects.object = new ObjectSelect(this.config, this.conditionManager, [
['any', 'any telemetry'],
['all', 'all telemetry']
]);
this.selects.key = new KeySelect(this.config, this.selects.object, this.conditionManager);
this.selects.operation = new OperationSelect(
this.config,
this.selects.key,
this.conditionManager,
function (value) {
onSelectChange(value, 'operation');
}
);
this.selects.object.on('change', this.handleObjectChange);
this.selects.key.on('change', this.handleKeyChange);
Object.values(this.selects).forEach(function (select) {
self.domElement.querySelector('.t-configuration').append(select.getDOM());
self.eventEmitter.emit('change', {
value: value,
property: property,
index: self.index
});
this.listenTo(this.domElement.querySelector('.t-value-inputs'), 'input', onValueInput);
}
Condition.prototype.getDOM = function (container) {
return this.domElement;
};
this.handleObjectChange = (value) => onSelectChange(value, 'object');
this.handleKeyChange = (value) => onSelectChange(value, 'key');
/**
* Register a callback with this condition: supported callbacks are remove, change,
* duplicate
* @param {string} event The key for the event to listen to
* @param {function} callback The function that this rule will invoke on this event
* @param {Object} context A reference to a scope to use as the context for
* context for the callback function
* Event handler for this conditions value inputs
* @param {Event} event The oninput event that triggered this callback
* @private
*/
Condition.prototype.on = function (event, callback, context) {
if (this.supportedCallbacks.includes(event)) {
this.eventEmitter.on(event, callback, context || this);
function onValueInput(event) {
const elem = event.target;
const value = isNaN(Number(elem.value)) ? elem.value : Number(elem.value);
const inputIndex = self.valueInputs.indexOf(elem);
self.eventEmitter.emit('change', {
value: value,
property: 'values[' + inputIndex + ']',
index: self.index
});
}
this.listenTo(this.deleteButton, 'click', this.remove, this);
this.listenTo(this.duplicateButton, 'click', this.duplicate, this);
this.selects.object = new ObjectSelect(this.config, this.conditionManager, [
['any', 'any telemetry'],
['all', 'all telemetry']
]);
this.selects.key = new KeySelect(this.config, this.selects.object, this.conditionManager);
this.selects.operation = new OperationSelect(
this.config,
this.selects.key,
this.conditionManager,
function (value) {
onSelectChange(value, 'operation');
}
};
);
/**
* Hide the appropriate inputs when this is the only condition
*/
Condition.prototype.hideButtons = function () {
this.deleteButton.style.display = 'none';
};
this.selects.object.on('change', this.handleObjectChange);
this.selects.key.on('change', this.handleKeyChange);
/**
* Remove this condition from the configuration. Invokes any registered
* remove callbacks
*/
Condition.prototype.remove = function () {
this.selects.object.off('change', this.handleObjectChange);
this.selects.key.off('change', this.handleKeyChange);
this.eventEmitter.emit('remove', this.index);
this.destroy();
};
Object.values(this.selects).forEach(function (select) {
self.domElement.querySelector('.t-configuration').append(select.getDOM());
});
Condition.prototype.destroy = function () {
this.stopListening();
Object.values(this.selects).forEach(function (select) {
select.destroy();
});
};
this.listenTo(this.domElement.querySelector('.t-value-inputs'), 'input', onValueInput);
}
/**
* Make a deep clone of this condition's configuration and invoke any duplicate
* callbacks with the cloned configuration and this rule's index
*/
Condition.prototype.duplicate = function () {
const sourceCondition = JSON.parse(JSON.stringify(this.config));
this.eventEmitter.emit('duplicate', {
sourceCondition: sourceCondition,
index: this.index
});
};
Condition.prototype.getDOM = function (container) {
return this.domElement;
};
/**
* When an operation is selected, create the appropriate value inputs
* and add them to the view. If an operation is of type enum, create
* a drop-down menu instead.
*
* @param {string} operation The key of currently selected operation
*/
Condition.prototype.generateValueInputs = function (operation) {
const evaluator = this.conditionManager.getEvaluator();
const inputArea = this.domElement.querySelector('.t-value-inputs');
let inputCount;
let inputType;
let newInput;
let index = 0;
let emitChange = false;
/**
* Register a callback with this condition: supported callbacks are remove, change,
* duplicate
* @param {string} event The key for the event to listen to
* @param {function} callback The function that this rule will invoke on this event
* @param {Object} context A reference to a scope to use as the context for
* context for the callback function
*/
Condition.prototype.on = function (event, callback, context) {
if (this.supportedCallbacks.includes(event)) {
this.eventEmitter.on(event, callback, context || this);
}
};
inputArea.innerHTML = '';
this.valueInputs = [];
this.config.values = this.config.values || [];
/**
* Hide the appropriate inputs when this is the only condition
*/
Condition.prototype.hideButtons = function () {
this.deleteButton.style.display = 'none';
};
if (evaluator.getInputCount(operation)) {
inputCount = evaluator.getInputCount(operation);
inputType = evaluator.getInputType(operation);
/**
* Remove this condition from the configuration. Invokes any registered
* remove callbacks
*/
Condition.prototype.remove = function () {
this.selects.object.off('change', this.handleObjectChange);
this.selects.key.off('change', this.handleKeyChange);
this.eventEmitter.emit('remove', this.index);
this.destroy();
};
while (index < inputCount) {
if (inputType === 'select') {
const options = this.generateSelectOptions();
Condition.prototype.destroy = function () {
this.stopListening();
Object.values(this.selects).forEach(function (select) {
select.destroy();
});
};
newInput = document.createElement('select');
newInput.appendChild(options);
/**
* Make a deep clone of this condition's configuration and invoke any duplicate
* callbacks with the cloned configuration and this rule's index
*/
Condition.prototype.duplicate = function () {
const sourceCondition = JSON.parse(JSON.stringify(this.config));
this.eventEmitter.emit('duplicate', {
sourceCondition: sourceCondition,
index: this.index
});
};
emitChange = true;
} else {
const defaultValue = inputType === 'number' ? 0 : '';
const value = this.config.values[index] || defaultValue;
this.config.values[index] = value;
/**
* When an operation is selected, create the appropriate value inputs
* and add them to the view. If an operation is of type enum, create
* a drop-down menu instead.
*
* @param {string} operation The key of currently selected operation
*/
Condition.prototype.generateValueInputs = function (operation) {
const evaluator = this.conditionManager.getEvaluator();
const inputArea = this.domElement.querySelector('.t-value-inputs');
let inputCount;
let inputType;
let newInput;
let index = 0;
let emitChange = false;
newInput = document.createElement('input');
newInput.type = `${inputType}`;
newInput.value = `${value}`;
}
inputArea.innerHTML = '';
this.valueInputs = [];
this.config.values = this.config.values || [];
this.valueInputs.push(newInput);
inputArea.appendChild(newInput);
index += 1;
if (evaluator.getInputCount(operation)) {
inputCount = evaluator.getInputCount(operation);
inputType = evaluator.getInputType(operation);
while (index < inputCount) {
if (inputType === 'select') {
const options = this.generateSelectOptions();
newInput = document.createElement('select');
newInput.appendChild(options);
emitChange = true;
} else {
const defaultValue = inputType === 'number' ? 0 : '';
const value = this.config.values[index] || defaultValue;
this.config.values[index] = value;
newInput = document.createElement('input');
newInput.type = `${inputType}`;
newInput.value = `${value}`;
}
if (emitChange) {
this.eventEmitter.emit('change', {
value: Number(newInput[0].options[0].value),
property: 'values[0]',
index: this.index
});
}
this.valueInputs.push(newInput);
inputArea.appendChild(newInput);
index += 1;
}
};
Condition.prototype.generateSelectOptions = function () {
let telemetryMetadata = this.conditionManager.getTelemetryMetadata(this.config.object);
let fragment = document.createDocumentFragment();
if (emitChange) {
this.eventEmitter.emit('change', {
value: Number(newInput[0].options[0].value),
property: 'values[0]',
index: this.index
});
}
}
};
telemetryMetadata[this.config.key].enumerations.forEach((enumeration) => {
const option = document.createElement('option');
option.value = enumeration.value;
option.textContent = enumeration.string;
fragment.appendChild(option);
});
Condition.prototype.generateSelectOptions = function () {
let telemetryMetadata = this.conditionManager.getTelemetryMetadata(this.config.object);
let fragment = document.createDocumentFragment();
return fragment;
};
telemetryMetadata[this.config.key].enumerations.forEach((enumeration) => {
const option = document.createElement('option');
option.value = enumeration.value;
option.textContent = enumeration.string;
fragment.appendChild(option);
});
return Condition;
});
return fragment;
};

View File

@ -1,486 +1,482 @@
define([], function () {
/**
* Responsible for maintaining the possible operations for conditions
* in this widget, and evaluating the boolean value of conditions passed as
* input.
* @constructor
* @param {Object} subscriptionCache A cache consisting of the latest available
* data for any telemetry sources in the widget's
* composition.
* @param {Object} compositionObjs The current set of composition objects to
* evaluate for 'any' and 'all' conditions
*/
export default function ConditionEvaluator(subscriptionCache, compositionObjs) {
this.subscriptionCache = subscriptionCache;
this.compositionObjs = compositionObjs;
this.testCache = {};
this.useTestCache = false;
/**
* Responsible for maintaining the possible operations for conditions
* in this widget, and evaluating the boolean value of conditions passed as
* input.
* @constructor
* @param {Object} subscriptionCache A cache consisting of the latest available
* data for any telemetry sources in the widget's
* composition.
* @param {Object} compositionObjs The current set of composition objects to
* evaluate for 'any' and 'all' conditions
* Maps value types to HTML input field types. These
* type of inputs will be generated by conditions expecting this data type
*/
function ConditionEvaluator(subscriptionCache, compositionObjs) {
this.subscriptionCache = subscriptionCache;
this.compositionObjs = compositionObjs;
this.inputTypes = {
number: 'number',
string: 'text',
enum: 'select'
};
this.testCache = {};
this.useTestCache = false;
/**
* Functions to validate that the input to an operation is of the type
* that it expects, in order to prevent unexpected behavior. Will be
* invoked before the corresponding operation is executed
*/
this.inputValidators = {
number: this.validateNumberInput,
string: this.validateStringInput,
enum: this.validateNumberInput
};
/**
* Maps value types to HTML input field types. These
* type of inputs will be generated by conditions expecting this data type
*/
this.inputTypes = {
number: 'number',
string: 'text',
enum: 'select'
};
/**
* Functions to validate that the input to an operation is of the type
* that it expects, in order to prevent unexpected behavior. Will be
* invoked before the corresponding operation is executed
*/
this.inputValidators = {
number: this.validateNumberInput,
string: this.validateStringInput,
enum: this.validateNumberInput
};
/**
* A library of operations supported by this rule evaluator. Each operation
* consists of the following fields:
* operation: a function with boolean return type to be invoked when this
* operation is used. Will be called with an array of inputs
* where input [0] is the telemetry value and input [1..n] are
* any comparison values
* text: a human-readable description of this operation to populate selects
* appliesTo: an array of identifiers for types that operation may be used on
* inputCount: the number of inputs required to get any necessary comparison
* values for the operation
* getDescription: A function returning a human-readable shorthand description of
* this operation to populate the 'description' field in the rule header.
* Will be invoked with an array of a condition's comparison values.
*/
this.operations = {
equalTo: {
operation: function (input) {
return input[0] === input[1];
},
text: 'is equal to',
appliesTo: ['number'],
inputCount: 1,
getDescription: function (values) {
return ' == ' + values[0];
}
/**
* A library of operations supported by this rule evaluator. Each operation
* consists of the following fields:
* operation: a function with boolean return type to be invoked when this
* operation is used. Will be called with an array of inputs
* where input [0] is the telemetry value and input [1..n] are
* any comparison values
* text: a human-readable description of this operation to populate selects
* appliesTo: an array of identifiers for types that operation may be used on
* inputCount: the number of inputs required to get any necessary comparison
* values for the operation
* getDescription: A function returning a human-readable shorthand description of
* this operation to populate the 'description' field in the rule header.
* Will be invoked with an array of a condition's comparison values.
*/
this.operations = {
equalTo: {
operation: function (input) {
return input[0] === input[1];
},
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', 'enum'],
inputCount: 0,
getDescription: function () {
return ' is undefined';
}
},
isDefined: {
operation: function (input) {
return typeof input[0] !== 'undefined';
},
text: 'is defined',
appliesTo: ['string', 'number', 'enum'],
inputCount: 0,
getDescription: function () {
return ' is defined';
}
},
enumValueIs: {
operation: function (input) {
return input[0] === input[1];
},
text: 'is',
appliesTo: ['enum'],
inputCount: 1,
getDescription: function (values) {
return ' == ' + values[0];
}
},
enumValueIsNot: {
operation: function (input) {
return input[0] !== input[1];
},
text: 'is not',
appliesTo: ['enum'],
inputCount: 1,
getDescription: function (values) {
return ' != ' + values[0];
}
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', 'enum'],
inputCount: 0,
getDescription: function () {
return ' is undefined';
}
},
isDefined: {
operation: function (input) {
return typeof input[0] !== 'undefined';
},
text: 'is defined',
appliesTo: ['string', 'number', 'enum'],
inputCount: 0,
getDescription: function () {
return ' is defined';
}
},
enumValueIs: {
operation: function (input) {
return input[0] === input[1];
},
text: 'is',
appliesTo: ['enum'],
inputCount: 1,
getDescription: function (values) {
return ' == ' + values[0];
}
},
enumValueIsNot: {
operation: function (input) {
return input[0] !== input[1];
},
text: 'is not',
appliesTo: ['enum'],
inputCount: 1,
getDescription: function (values) {
return ' != ' + values[0];
}
}
};
}
/**
* Evaluate the conditions passed in as an argument, and return the boolean
* value of these conditions. Available evaluation modes are 'any', which will
* return true if any of the conditions evaluates to true (i.e. logical OR); 'all',
* which returns true only if all conditions evalute to true (i.e. logical AND);
* or 'js', which returns the boolean value of a custom JavaScript conditional.
* @param {} conditions Either an array of objects with object, key, operation,
* and value fields, or a string representing a JavaScript
* condition.
* @param {string} mode The key of the mode to use when evaluating the conditions.
* @return {boolean} The boolean value of the conditions
*/
ConditionEvaluator.prototype.execute = function (conditions, mode) {
let active = false;
let conditionValue;
let conditionDefined = false;
const self = this;
let firstRuleEvaluated = false;
const compositionObjs = this.compositionObjs;
/**
* Evaluate the conditions passed in as an argument, and return the boolean
* value of these conditions. Available evaluation modes are 'any', which will
* return true if any of the conditions evaluates to true (i.e. logical OR); 'all',
* which returns true only if all conditions evalute to true (i.e. logical AND);
* or 'js', which returns the boolean value of a custom JavaScript conditional.
* @param {} conditions Either an array of objects with object, key, operation,
* and value fields, or a string representing a JavaScript
* condition.
* @param {string} mode The key of the mode to use when evaluating the conditions.
* @return {boolean} The boolean value of the conditions
*/
ConditionEvaluator.prototype.execute = function (conditions, mode) {
let active = false;
let conditionValue;
let conditionDefined = false;
const self = this;
let firstRuleEvaluated = false;
const compositionObjs = this.compositionObjs;
if (mode === 'js') {
active = this.executeJavaScriptCondition(conditions);
} else {
(conditions || []).forEach(function (condition) {
conditionDefined = false;
if (condition.object === 'any') {
conditionValue = false;
Object.keys(compositionObjs).forEach(function (objId) {
try {
conditionValue =
conditionValue ||
self.executeCondition(objId, condition.key, condition.operation, condition.values);
conditionDefined = true;
} catch (e) {
//ignore a malformed condition
}
});
} else if (condition.object === 'all') {
conditionValue = true;
Object.keys(compositionObjs).forEach(function (objId) {
try {
conditionValue =
conditionValue &&
self.executeCondition(objId, condition.key, condition.operation, condition.values);
conditionDefined = true;
} catch (e) {
//ignore a malformed condition
}
});
} else {
if (mode === 'js') {
active = this.executeJavaScriptCondition(conditions);
} else {
(conditions || []).forEach(function (condition) {
conditionDefined = false;
if (condition.object === 'any') {
conditionValue = false;
Object.keys(compositionObjs).forEach(function (objId) {
try {
conditionValue = self.executeCondition(
condition.object,
condition.key,
condition.operation,
condition.values
);
conditionValue =
conditionValue ||
self.executeCondition(objId, condition.key, condition.operation, condition.values);
conditionDefined = true;
} catch (e) {
//ignore malformed condition
//ignore a malformed condition
}
}
if (conditionDefined) {
active = mode === 'all' && !firstRuleEvaluated ? true : active;
firstRuleEvaluated = true;
if (mode === 'any') {
active = active || conditionValue;
} else if (mode === 'all') {
active = active && conditionValue;
});
} else if (condition.object === 'all') {
conditionValue = true;
Object.keys(compositionObjs).forEach(function (objId) {
try {
conditionValue =
conditionValue &&
self.executeCondition(objId, condition.key, condition.operation, condition.values);
conditionDefined = true;
} catch (e) {
//ignore a malformed condition
}
}
});
}
return active;
};
/**
* Execute a condition defined as an object.
* @param {string} object The identifier of the telemetry object to retrieve data from
* @param {string} key The property of the telemetry object
* @param {string} operation The key of the operation in this ConditionEvaluator to executeCondition
* @param {string} values An array of comparison values to invoke the operation with
* @return {boolean} The value of this condition
*/
ConditionEvaluator.prototype.executeCondition = function (object, key, operation, values) {
const cache = this.useTestCache ? this.testCache : this.subscriptionCache;
let telemetryValue;
let op;
let input;
let validator;
if (cache[object] && typeof cache[object][key] !== 'undefined') {
let value = cache[object][key];
telemetryValue = [isNaN(Number(value)) ? value : Number(value)];
}
op = this.operations[operation] && this.operations[operation].operation;
input = telemetryValue && telemetryValue.concat(values);
validator = op && this.inputValidators[this.operations[operation].appliesTo[0]];
if (op && input && validator) {
if (this.operations[operation].appliesTo.length > 1) {
return (this.validateNumberInput(input) || this.validateStringInput(input)) && op(input);
});
} else {
return validator(input) && op(input);
try {
conditionValue = self.executeCondition(
condition.object,
condition.key,
condition.operation,
condition.values
);
conditionDefined = true;
} catch (e) {
//ignore malformed condition
}
}
if (conditionDefined) {
active = mode === 'all' && !firstRuleEvaluated ? true : active;
firstRuleEvaluated = true;
if (mode === 'any') {
active = active || conditionValue;
} else if (mode === 'all') {
active = active && conditionValue;
}
}
});
}
return active;
};
/**
* Execute a condition defined as an object.
* @param {string} object The identifier of the telemetry object to retrieve data from
* @param {string} key The property of the telemetry object
* @param {string} operation The key of the operation in this ConditionEvaluator to executeCondition
* @param {string} values An array of comparison values to invoke the operation with
* @return {boolean} The value of this condition
*/
ConditionEvaluator.prototype.executeCondition = function (object, key, operation, values) {
const cache = this.useTestCache ? this.testCache : this.subscriptionCache;
let telemetryValue;
let op;
let input;
let validator;
if (cache[object] && typeof cache[object][key] !== 'undefined') {
let value = cache[object][key];
telemetryValue = [isNaN(Number(value)) ? value : Number(value)];
}
op = this.operations[operation] && this.operations[operation].operation;
input = telemetryValue && telemetryValue.concat(values);
validator = op && this.inputValidators[this.operations[operation].appliesTo[0]];
if (op && input && validator) {
if (this.operations[operation].appliesTo.length > 1) {
return (this.validateNumberInput(input) || this.validateStringInput(input)) && op(input);
} else {
throw new Error('Malformed condition');
return validator(input) && op(input);
}
};
} else {
throw new Error('Malformed condition');
}
};
/**
* A function that returns true only if each value in its input argument is
* of a numerical type
* @param {[]} input An array of values
* @returns {boolean}
*/
ConditionEvaluator.prototype.validateNumberInput = function (input) {
let valid = true;
input.forEach(function (value) {
valid = valid && typeof value === 'number';
});
/**
* A function that returns true only if each value in its input argument is
* of a numerical type
* @param {[]} input An array of values
* @returns {boolean}
*/
ConditionEvaluator.prototype.validateNumberInput = function (input) {
let valid = true;
input.forEach(function (value) {
valid = valid && typeof value === 'number';
});
return valid;
};
return valid;
};
/**
* A function that returns true only if each value in its input argument is
* a string
* @param {[]} input An array of values
* @returns {boolean}
*/
ConditionEvaluator.prototype.validateStringInput = function (input) {
let valid = true;
input.forEach(function (value) {
valid = valid && typeof value === 'string';
});
/**
* A function that returns true only if each value in its input argument is
* a string
* @param {[]} input An array of values
* @returns {boolean}
*/
ConditionEvaluator.prototype.validateStringInput = function (input) {
let valid = true;
input.forEach(function (value) {
valid = valid && typeof value === 'string';
});
return valid;
};
return valid;
};
/**
* Get the keys of operations supported by this evaluator
* @return {string[]} An array of the keys of supported operations
*/
ConditionEvaluator.prototype.getOperationKeys = function () {
return Object.keys(this.operations);
};
/**
* Get the keys of operations supported by this evaluator
* @return {string[]} An array of the keys of supported operations
*/
ConditionEvaluator.prototype.getOperationKeys = function () {
return Object.keys(this.operations);
};
/**
* Get the human-readable text corresponding to a given operation
* @param {string} key The key of the operation
* @return {string} The text description of the operation
*/
ConditionEvaluator.prototype.getOperationText = function (key) {
return this.operations[key].text;
};
/**
* Get the human-readable text corresponding to a given operation
* @param {string} key The key of the operation
* @return {string} The text description of the operation
*/
ConditionEvaluator.prototype.getOperationText = function (key) {
return this.operations[key].text;
};
/**
* Returns true only if the given operation applies to a given type
* @param {string} key The key of the operation
* @param {string} type The value type to query
* @returns {boolean} True if the condition applies, false otherwise
*/
ConditionEvaluator.prototype.operationAppliesTo = function (key, type) {
return this.operations[key].appliesTo.includes(type);
};
/**
* Returns true only if the given operation applies to a given type
* @param {string} key The key of the operation
* @param {string} type The value type to query
* @returns {boolean} True if the condition applies, false otherwise
*/
ConditionEvaluator.prototype.operationAppliesTo = function (key, type) {
return this.operations[key].appliesTo.includes(type);
};
/**
* Return the number of value inputs required by an operation
* @param {string} key The key of the operation to query
* @return {number}
*/
ConditionEvaluator.prototype.getInputCount = function (key) {
if (this.operations[key]) {
return this.operations[key].inputCount;
}
};
/**
* Return the number of value inputs required by an operation
* @param {string} key The key of the operation to query
* @return {number}
*/
ConditionEvaluator.prototype.getInputCount = function (key) {
if (this.operations[key]) {
return this.operations[key].inputCount;
}
};
/**
* Return the human-readable shorthand description of the operation for a rule header
* @param {string} key The key of the operation to query
* @param {} values An array of values with which to invoke the getDescription function
* of the operation
* @return {string} A text description of this operation
*/
ConditionEvaluator.prototype.getOperationDescription = function (key, values) {
if (this.operations[key]) {
return this.operations[key].getDescription(values);
}
};
/**
* Return the human-readable shorthand description of the operation for a rule header
* @param {string} key The key of the operation to query
* @param {} values An array of values with which to invoke the getDescription function
* of the operation
* @return {string} A text description of this operation
*/
ConditionEvaluator.prototype.getOperationDescription = function (key, values) {
if (this.operations[key]) {
return this.operations[key].getDescription(values);
}
};
/**
* Return the HTML input type associated with a given operation
* @param {string} key The key of the operation to query
* @return {string} The key for an HTML5 input type
*/
ConditionEvaluator.prototype.getInputType = function (key) {
let type;
if (this.operations[key]) {
type = this.operations[key].appliesTo[0];
}
/**
* Return the HTML input type associated with a given operation
* @param {string} key The key of the operation to query
* @return {string} The key for an HTML5 input type
*/
ConditionEvaluator.prototype.getInputType = function (key) {
let type;
if (this.operations[key]) {
type = this.operations[key].appliesTo[0];
}
if (this.inputTypes[type]) {
return this.inputTypes[type];
}
};
if (this.inputTypes[type]) {
return this.inputTypes[type];
}
};
/**
* Returns the HTML input type associated with a value type
* @param {string} dataType The JavaScript value type
* @return {string} The key for an HTML5 input type
*/
ConditionEvaluator.prototype.getInputTypeById = function (dataType) {
return this.inputTypes[dataType];
};
/**
* Returns the HTML input type associated with a value type
* @param {string} dataType The JavaScript value type
* @return {string} The key for an HTML5 input type
*/
ConditionEvaluator.prototype.getInputTypeById = function (dataType) {
return this.inputTypes[dataType];
};
/**
* Set the test data cache used by this rule evaluator
* @param {object} testCache A mock cache following the format of the real
* subscription cache
*/
ConditionEvaluator.prototype.setTestDataCache = function (testCache) {
this.testCache = testCache;
};
/**
* Set the test data cache used by this rule evaluator
* @param {object} testCache A mock cache following the format of the real
* subscription cache
*/
ConditionEvaluator.prototype.setTestDataCache = function (testCache) {
this.testCache = testCache;
};
/**
* Have this RuleEvaluator pull data values from the provided test cache
* instead of its actual subscription cache when evaluating. If invoked with true,
* will use the test cache; otherwise, will use the subscription cache
* @param {boolean} useTestData Boolean flag
*/
ConditionEvaluator.prototype.useTestData = function (useTestCache) {
this.useTestCache = useTestCache;
};
return ConditionEvaluator;
});
/**
* Have this RuleEvaluator pull data values from the provided test cache
* instead of its actual subscription cache when evaluating. If invoked with true,
* will use the test cache; otherwise, will use the subscription cache
* @param {boolean} useTestData Boolean flag
*/
ConditionEvaluator.prototype.useTestData = function (useTestCache) {
this.useTestCache = useTestCache;
};

View File

@ -1,386 +1,383 @@
define(['./ConditionEvaluator', 'objectUtils', 'EventEmitter', 'lodash'], function (
ConditionEvaluator,
objectUtils,
EventEmitter,
_
) {
/**
* Provides a centralized content manager for conditions in the summary widget.
* Loads and caches composition and telemetry subscriptions, and maintains a
* {ConditionEvaluator} instance to handle evaluation
* @constructor
* @param {Object} domainObject the Summary Widget domain object
* @param {MCT} openmct an MCT instance
*/
function ConditionManager(domainObject, openmct) {
this.domainObject = domainObject;
this.openmct = openmct;
import EventEmitter from 'EventEmitter';
import _ from 'lodash';
import objectUtils from 'objectUtils';
this.composition = this.openmct.composition.get(this.domainObject);
this.compositionObjs = {};
this.eventEmitter = new EventEmitter();
this.supportedCallbacks = ['add', 'remove', 'load', 'metadata', 'receiveTelemetry'];
import ConditionEvaluator from './ConditionEvaluator';
this.keywordLabels = {
any: 'any Telemetry',
all: 'all Telemetry'
};
/**
* Provides a centralized content manager for conditions in the summary widget.
* Loads and caches composition and telemetry subscriptions, and maintains a
* {ConditionEvaluator} instance to handle evaluation
* @constructor
* @param {Object} domainObject the Summary Widget domain object
* @param {MCT} openmct an MCT instance
*/
export default function ConditionManager(domainObject, openmct) {
this.domainObject = domainObject;
this.openmct = openmct;
this.telemetryMetadataById = {
any: {},
all: {}
};
this.composition = this.openmct.composition.get(this.domainObject);
this.compositionObjs = {};
this.eventEmitter = new EventEmitter();
this.supportedCallbacks = ['add', 'remove', 'load', 'metadata', 'receiveTelemetry'];
this.telemetryTypesById = {
any: {},
all: {}
};
this.subscriptions = {};
this.subscriptionCache = {};
this.loadComplete = false;
this.metadataLoadComplete = false;
this.evaluator = new ConditionEvaluator(this.subscriptionCache, this.compositionObjs);
this.composition.on('add', this.onCompositionAdd, this);
this.composition.on('remove', this.onCompositionRemove, this);
this.composition.on('load', this.onCompositionLoad, this);
this.composition.load();
}
/**
* Register a callback with this ConditionManager: supported callbacks are add
* remove, load, metadata, and receiveTelemetry
* @param {string} event The key for the event to listen to
* @param {function} callback The function that this rule will invoke on this event
* @param {Object} context A reference to a scope to use as the context for
* context for the callback function
*/
ConditionManager.prototype.on = function (event, callback, context) {
if (this.supportedCallbacks.includes(event)) {
this.eventEmitter.on(event, callback, context || this);
} else {
throw (
event + ' is not a supported callback. Supported callbacks are ' + this.supportedCallbacks
);
}
this.keywordLabels = {
any: 'any Telemetry',
all: 'all Telemetry'
};
/**
* Given a set of rules, execute the conditions associated with each rule
* and return the id of the last rule whose conditions evaluate to true
* @param {string[]} ruleOrder An array of rule IDs indicating what order They
* should be evaluated in
* @param {Object} rules An object mapping rule IDs to rule configurations
* @return {string} The ID of the rule to display on the widget
*/
ConditionManager.prototype.executeRules = function (ruleOrder, rules) {
const self = this;
let activeId = ruleOrder[0];
let rule;
let conditions;
this.telemetryMetadataById = {
any: {},
all: {}
};
ruleOrder.forEach(function (ruleId) {
rule = rules[ruleId];
conditions = rule.getProperty('conditions');
if (self.evaluator.execute(conditions, rule.getProperty('trigger'))) {
activeId = ruleId;
}
this.telemetryTypesById = {
any: {},
all: {}
};
this.subscriptions = {};
this.subscriptionCache = {};
this.loadComplete = false;
this.metadataLoadComplete = false;
this.evaluator = new ConditionEvaluator(this.subscriptionCache, this.compositionObjs);
this.composition.on('add', this.onCompositionAdd, this);
this.composition.on('remove', this.onCompositionRemove, this);
this.composition.on('load', this.onCompositionLoad, this);
this.composition.load();
}
/**
* Register a callback with this ConditionManager: supported callbacks are add
* remove, load, metadata, and receiveTelemetry
* @param {string} event The key for the event to listen to
* @param {function} callback The function that this rule will invoke on this event
* @param {Object} context A reference to a scope to use as the context for
* context for the callback function
*/
ConditionManager.prototype.on = function (event, callback, context) {
if (this.supportedCallbacks.includes(event)) {
this.eventEmitter.on(event, callback, context || this);
} else {
throw (
event + ' is not a supported callback. Supported callbacks are ' + this.supportedCallbacks
);
}
};
/**
* Given a set of rules, execute the conditions associated with each rule
* and return the id of the last rule whose conditions evaluate to true
* @param {string[]} ruleOrder An array of rule IDs indicating what order They
* should be evaluated in
* @param {Object} rules An object mapping rule IDs to rule configurations
* @return {string} The ID of the rule to display on the widget
*/
ConditionManager.prototype.executeRules = function (ruleOrder, rules) {
const self = this;
let activeId = ruleOrder[0];
let rule;
let conditions;
ruleOrder.forEach(function (ruleId) {
rule = rules[ruleId];
conditions = rule.getProperty('conditions');
if (self.evaluator.execute(conditions, rule.getProperty('trigger'))) {
activeId = ruleId;
}
});
return activeId;
};
/**
* Adds a field to the list of all available metadata fields in the widget
* @param {Object} metadatum An object representing a set of telemetry metadata
*/
ConditionManager.prototype.addGlobalMetadata = function (metadatum) {
this.telemetryMetadataById.any[metadatum.key] = metadatum;
this.telemetryMetadataById.all[metadatum.key] = metadatum;
};
/**
* Adds a field to the list of properties for globally available metadata
* @param {string} key The key for the property this type applies to
* @param {string} type The type that should be associated with this property
*/
ConditionManager.prototype.addGlobalPropertyType = function (key, type) {
this.telemetryTypesById.any[key] = type;
this.telemetryTypesById.all[key] = type;
};
/**
* Given a telemetry-producing domain object, associate each of it's telemetry
* fields with a type, parsing from historical data.
* @param {Object} object a domain object that can produce telemetry
* @return {Promise} A promise that resolves when a telemetry request
* has completed and types have been parsed
*/
ConditionManager.prototype.parsePropertyTypes = function (object) {
const objectId = objectUtils.makeKeyString(object.identifier);
this.telemetryTypesById[objectId] = {};
Object.values(this.telemetryMetadataById[objectId]).forEach(function (valueMetadata) {
let type;
if (valueMetadata.enumerations !== undefined) {
type = 'enum';
} else if (Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'range')) {
type = 'number';
} else if (Object.prototype.hasOwnProperty.call(valueMetadata.hints, '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);
};
/**
* Parse types of telemetry fields from all composition objects; used internally
* to perform a block types load once initial composition load has completed
* @return {Promise} A promise that resolves when all metadata has been loaded
* and property types parsed
*/
ConditionManager.prototype.parseAllPropertyTypes = function () {
Object.values(this.compositionObjs).forEach(this.parsePropertyTypes, this);
this.metadataLoadComplete = true;
this.eventEmitter.emit('metadata');
};
/**
* Invoked when a telemetry subscription yields new data. Updates the LAD
* cache and invokes any registered receiveTelemetry callbacks
* @param {string} objId The key associated with the telemetry source
* @param {datum} datum The new data from the telemetry source
* @private
*/
ConditionManager.prototype.handleSubscriptionCallback = function (objId, telemetryDatum) {
this.subscriptionCache[objId] = this.createNormalizedDatum(objId, telemetryDatum);
this.eventEmitter.emit('receiveTelemetry');
};
ConditionManager.prototype.createNormalizedDatum = function (objId, telemetryDatum) {
return Object.values(this.telemetryMetadataById[objId]).reduce((normalizedDatum, metadatum) => {
normalizedDatum[metadatum.key] = telemetryDatum[metadatum.source];
return normalizedDatum;
}, {});
};
/**
* Event handler for an add event in this Summary Widget's composition.
* Sets up subscription handlers and parses its property types.
* @param {Object} obj The newly added domain object
* @private
*/
ConditionManager.prototype.onCompositionAdd = function (obj) {
let compositionKeys;
const telemetryAPI = this.openmct.telemetry;
const objId = objectUtils.makeKeyString(obj.identifier);
let telemetryMetadata;
const self = this;
if (telemetryAPI.isTelemetryObject(obj)) {
self.compositionObjs[objId] = obj;
self.telemetryMetadataById[objId] = {};
// FIXME: this should just update based on listener.
compositionKeys = self.domainObject.composition.map(objectUtils.makeKeyString);
if (!compositionKeys.includes(objId)) {
self.domainObject.composition.push(obj.identifier);
}
telemetryMetadata = telemetryAPI.getMetadata(obj).values();
telemetryMetadata.forEach(function (metaDatum) {
self.telemetryMetadataById[objId][metaDatum.key] = metaDatum;
self.addGlobalMetadata(metaDatum);
});
return activeId;
};
/**
* Adds a field to the list of all available metadata fields in the widget
* @param {Object} metadatum An object representing a set of telemetry metadata
*/
ConditionManager.prototype.addGlobalMetadata = function (metadatum) {
this.telemetryMetadataById.any[metadatum.key] = metadatum;
this.telemetryMetadataById.all[metadatum.key] = metadatum;
};
/**
* Adds a field to the list of properties for globally available metadata
* @param {string} key The key for the property this type applies to
* @param {string} type The type that should be associated with this property
*/
ConditionManager.prototype.addGlobalPropertyType = function (key, type) {
this.telemetryTypesById.any[key] = type;
this.telemetryTypesById.all[key] = type;
};
/**
* Given a telemetry-producing domain object, associate each of it's telemetry
* fields with a type, parsing from historical data.
* @param {Object} object a domain object that can produce telemetry
* @return {Promise} A promise that resolves when a telemetry request
* has completed and types have been parsed
*/
ConditionManager.prototype.parsePropertyTypes = function (object) {
const objectId = objectUtils.makeKeyString(object.identifier);
this.telemetryTypesById[objectId] = {};
Object.values(this.telemetryMetadataById[objectId]).forEach(function (valueMetadata) {
let type;
if (valueMetadata.enumerations !== undefined) {
type = 'enum';
} else if (Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'range')) {
type = 'number';
} else if (Object.prototype.hasOwnProperty.call(valueMetadata.hints, '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);
};
/**
* Parse types of telemetry fields from all composition objects; used internally
* to perform a block types load once initial composition load has completed
* @return {Promise} A promise that resolves when all metadata has been loaded
* and property types parsed
*/
ConditionManager.prototype.parseAllPropertyTypes = function () {
Object.values(this.compositionObjs).forEach(this.parsePropertyTypes, this);
this.metadataLoadComplete = true;
this.eventEmitter.emit('metadata');
};
/**
* Invoked when a telemetry subscription yields new data. Updates the LAD
* cache and invokes any registered receiveTelemetry callbacks
* @param {string} objId The key associated with the telemetry source
* @param {datum} datum The new data from the telemetry source
* @private
*/
ConditionManager.prototype.handleSubscriptionCallback = function (objId, telemetryDatum) {
this.subscriptionCache[objId] = this.createNormalizedDatum(objId, telemetryDatum);
this.eventEmitter.emit('receiveTelemetry');
};
ConditionManager.prototype.createNormalizedDatum = function (objId, telemetryDatum) {
return Object.values(this.telemetryMetadataById[objId]).reduce((normalizedDatum, metadatum) => {
normalizedDatum[metadatum.key] = telemetryDatum[metadatum.source];
return normalizedDatum;
}, {});
};
/**
* Event handler for an add event in this Summary Widget's composition.
* Sets up subscription handlers and parses its property types.
* @param {Object} obj The newly added domain object
* @private
*/
ConditionManager.prototype.onCompositionAdd = function (obj) {
let compositionKeys;
const telemetryAPI = this.openmct.telemetry;
const objId = objectUtils.makeKeyString(obj.identifier);
let telemetryMetadata;
const self = this;
if (telemetryAPI.isTelemetryObject(obj)) {
self.compositionObjs[objId] = obj;
self.telemetryMetadataById[objId] = {};
// FIXME: this should just update based on listener.
compositionKeys = self.domainObject.composition.map(objectUtils.makeKeyString);
if (!compositionKeys.includes(objId)) {
self.domainObject.composition.push(obj.identifier);
}
telemetryMetadata = telemetryAPI.getMetadata(obj).values();
telemetryMetadata.forEach(function (metaDatum) {
self.telemetryMetadataById[objId][metaDatum.key] = metaDatum;
self.addGlobalMetadata(metaDatum);
self.subscriptionCache[objId] = {};
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]);
}
});
self.subscriptionCache[objId] = {};
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
* until all composition objects have been loaded
*/
if (self.loadComplete) {
self.parsePropertyTypes(obj);
}
self.eventEmitter.emit('add', obj);
const summaryWidget = document.querySelector('.w-summary-widget');
if (summaryWidget) {
summaryWidget.classList.remove('s-status-no-data');
}
}
};
/**
* Invoked on a remove event in this Summary Widget's composition. Removes
* the object from the local composition, and untracks it
* @param {object} identifier The identifier of the object to be removed
* @private
*/
ConditionManager.prototype.onCompositionRemove = function (identifier) {
const objectId = objectUtils.makeKeyString(identifier);
// FIXME: this should just update by listener.
_.remove(this.domainObject.composition, function (id) {
return id.key === identifier.key && id.namespace === identifier.namespace;
});
delete this.compositionObjs[objectId];
delete this.subscriptionCache[objectId];
this.subscriptions[objectId](); //unsubscribe from telemetry source
delete this.subscriptions[objectId];
this.eventEmitter.emit('remove', identifier);
if (_.isEmpty(this.compositionObjs)) {
const summaryWidget = document.querySelector('.w-summary-widget');
if (summaryWidget) {
summaryWidget.classList.add('s-status-no-data');
}
}
};
/**
* Invoked when the Summary Widget's composition finishes its initial load.
* Invokes any registered load callbacks, does a block load of all metadata,
* and then invokes any registered metadata load callbacks.
* @private
*/
ConditionManager.prototype.onCompositionLoad = function () {
this.loadComplete = true;
this.eventEmitter.emit('load');
this.parseAllPropertyTypes();
};
/**
* Returns the currently tracked telemetry sources
* @return {Object} An object mapping object keys to domain objects
*/
ConditionManager.prototype.getComposition = function () {
return this.compositionObjs;
};
/**
* Get the human-readable name of a domain object from its key
* @param {string} id The key of the domain object
* @return {string} The human-readable name of the domain object
*/
ConditionManager.prototype.getObjectName = function (id) {
let name;
if (this.keywordLabels[id]) {
name = this.keywordLabels[id];
} else if (this.compositionObjs[id]) {
name = this.compositionObjs[id].name;
/**
* if this is the initial load, parsing property types will be postponed
* until all composition objects have been loaded
*/
if (self.loadComplete) {
self.parsePropertyTypes(obj);
}
return name;
};
self.eventEmitter.emit('add', obj);
/**
* Returns the property metadata associated with a given telemetry source
* @param {string} id The key associated with the domain object
* @return {Object} Returns an object with fields representing each telemetry field
*/
ConditionManager.prototype.getTelemetryMetadata = function (id) {
return this.telemetryMetadataById[id];
};
/**
* Returns the type associated with a telemetry data field of a particular domain
* object
* @param {string} id The key associated with the domain object
* @param {string} property The telemetry field key to retrieve the type of
* @return {string} The type name
*/
ConditionManager.prototype.getTelemetryPropertyType = function (id, property) {
if (this.telemetryTypesById[id]) {
return this.telemetryTypesById[id][property];
const summaryWidget = document.querySelector('.w-summary-widget');
if (summaryWidget) {
summaryWidget.classList.remove('s-status-no-data');
}
};
}
};
/**
* Returns the human-readable name of a telemetry data field of a particular domain
* object
* @param {string} id The key associated with the domain object
* @param {string} property The telemetry field key to retrieve the type of
* @return {string} The telemetry field name
*/
ConditionManager.prototype.getTelemetryPropertyName = function (id, property) {
if (this.telemetryMetadataById[id] && this.telemetryMetadataById[id][property]) {
return this.telemetryMetadataById[id][property].name;
/**
* Invoked on a remove event in this Summary Widget's composition. Removes
* the object from the local composition, and untracks it
* @param {object} identifier The identifier of the object to be removed
* @private
*/
ConditionManager.prototype.onCompositionRemove = function (identifier) {
const objectId = objectUtils.makeKeyString(identifier);
// FIXME: this should just update by listener.
_.remove(this.domainObject.composition, function (id) {
return id.key === identifier.key && id.namespace === identifier.namespace;
});
delete this.compositionObjs[objectId];
delete this.subscriptionCache[objectId];
this.subscriptions[objectId](); //unsubscribe from telemetry source
delete this.subscriptions[objectId];
this.eventEmitter.emit('remove', identifier);
if (_.isEmpty(this.compositionObjs)) {
const summaryWidget = document.querySelector('.w-summary-widget');
if (summaryWidget) {
summaryWidget.classList.add('s-status-no-data');
}
};
}
};
/**
* Returns the {ConditionEvaluator} instance associated with this condition
* manager
* @return {ConditionEvaluator}
*/
ConditionManager.prototype.getEvaluator = function () {
return this.evaluator;
};
/**
* Invoked when the Summary Widget's composition finishes its initial load.
* Invokes any registered load callbacks, does a block load of all metadata,
* and then invokes any registered metadata load callbacks.
* @private
*/
ConditionManager.prototype.onCompositionLoad = function () {
this.loadComplete = true;
this.eventEmitter.emit('load');
this.parseAllPropertyTypes();
};
/**
* Returns true if the initial composition load has completed
* @return {boolean}
*/
ConditionManager.prototype.loadCompleted = function () {
return this.loadComplete;
};
/**
* Returns the currently tracked telemetry sources
* @return {Object} An object mapping object keys to domain objects
*/
ConditionManager.prototype.getComposition = function () {
return this.compositionObjs;
};
/**
* Returns true if the initial block metadata load has completed
*/
ConditionManager.prototype.metadataLoadCompleted = function () {
return this.metadataLoadComplete;
};
/**
* Get the human-readable name of a domain object from its key
* @param {string} id The key of the domain object
* @return {string} The human-readable name of the domain object
*/
ConditionManager.prototype.getObjectName = function (id) {
let name;
/**
* Triggers the telemetryReceive callbacks registered to this ConditionManager,
* used by the {TestDataManager} to force a rule evaluation when test data is
* enabled
*/
ConditionManager.prototype.triggerTelemetryCallback = function () {
this.eventEmitter.emit('receiveTelemetry');
};
if (this.keywordLabels[id]) {
name = this.keywordLabels[id];
} else if (this.compositionObjs[id]) {
name = this.compositionObjs[id].name;
}
/**
* Unsubscribe from all registered telemetry sources and unregister all event
* listeners registered with the Open MCT APIs
*/
ConditionManager.prototype.destroy = function () {
Object.values(this.subscriptions).forEach(function (unsubscribeFunction) {
unsubscribeFunction();
});
this.composition.off('add', this.onCompositionAdd, this);
this.composition.off('remove', this.onCompositionRemove, this);
this.composition.off('load', this.onCompositionLoad, this);
};
return name;
};
return ConditionManager;
});
/**
* Returns the property metadata associated with a given telemetry source
* @param {string} id The key associated with the domain object
* @return {Object} Returns an object with fields representing each telemetry field
*/
ConditionManager.prototype.getTelemetryMetadata = function (id) {
return this.telemetryMetadataById[id];
};
/**
* Returns the type associated with a telemetry data field of a particular domain
* object
* @param {string} id The key associated with the domain object
* @param {string} property The telemetry field key to retrieve the type of
* @return {string} The type name
*/
ConditionManager.prototype.getTelemetryPropertyType = function (id, property) {
if (this.telemetryTypesById[id]) {
return this.telemetryTypesById[id][property];
}
};
/**
* Returns the human-readable name of a telemetry data field of a particular domain
* object
* @param {string} id The key associated with the domain object
* @param {string} property The telemetry field key to retrieve the type of
* @return {string} The telemetry field name
*/
ConditionManager.prototype.getTelemetryPropertyName = function (id, property) {
if (this.telemetryMetadataById[id] && this.telemetryMetadataById[id][property]) {
return this.telemetryMetadataById[id][property].name;
}
};
/**
* Returns the {ConditionEvaluator} instance associated with this condition
* manager
* @return {ConditionEvaluator}
*/
ConditionManager.prototype.getEvaluator = function () {
return this.evaluator;
};
/**
* Returns true if the initial composition load has completed
* @return {boolean}
*/
ConditionManager.prototype.loadCompleted = function () {
return this.loadComplete;
};
/**
* Returns true if the initial block metadata load has completed
*/
ConditionManager.prototype.metadataLoadCompleted = function () {
return this.metadataLoadComplete;
};
/**
* Triggers the telemetryReceive callbacks registered to this ConditionManager,
* used by the {TestDataManager} to force a rule evaluation when test data is
* enabled
*/
ConditionManager.prototype.triggerTelemetryCallback = function () {
this.eventEmitter.emit('receiveTelemetry');
};
/**
* Unsubscribe from all registered telemetry sources and unregister all event
* listeners registered with the Open MCT APIs
*/
ConditionManager.prototype.destroy = function () {
Object.values(this.subscriptions).forEach(function (unsubscribeFunction) {
unsubscribeFunction();
});
this.composition.off('add', this.onCompositionAdd, this);
this.composition.off('remove', this.onCompositionRemove, this);
this.composition.off('load', this.onCompositionLoad, this);
};

File diff suppressed because it is too large Load Diff

View File

@ -1,413 +1,393 @@
define([
'../res/widgetTemplate.html',
'./Rule',
'./ConditionManager',
'./TestDataManager',
'./WidgetDnD',
'./eventHelpers',
'../../../utils/template/templateHelpers',
'objectUtils',
'lodash',
'@braintree/sanitize-url'
], function (
widgetTemplate,
Rule,
ConditionManager,
TestDataManager,
WidgetDnD,
eventHelpers,
templateHelpers,
objectUtils,
_,
urlSanitizeLib
) {
//default css configuration for new rules
const DEFAULT_PROPS = {
color: '#cccccc',
'background-color': '#666666',
'border-color': 'rgba(0,0,0,0)'
};
import * as urlSanitizeLib from '@braintree/sanitize-url';
import * as templateHelpers from '../../../utils/template/templateHelpers';
import widgetTemplate from '../res/widgetTemplate.html';
import ConditionManager from './ConditionManager';
import eventHelpers from './eventHelpers';
import Rule from './Rule';
import TestDataManager from './TestDataManager';
import WidgetDnD from './WidgetDnD';
//default css configuration for new rules
const DEFAULT_PROPS = {
color: '#cccccc',
'background-color': '#666666',
'border-color': 'rgba(0,0,0,0)'
};
/**
* A Summary Widget object, which allows a user to configure rules based
* on telemetry producing domain objects, and update a compact display
* accordingly.
* @constructor
* @param {Object} domainObject The domain Object represented by this Widget
* @param {MCT} openmct An MCT instance
*/
export default function SummaryWidget(domainObject, openmct) {
eventHelpers.extend(this);
this.domainObject = domainObject;
this.openmct = openmct;
this.domainObject.configuration = this.domainObject.configuration || {};
this.domainObject.configuration.ruleConfigById =
this.domainObject.configuration.ruleConfigById || {};
this.domainObject.configuration.ruleOrder = this.domainObject.configuration.ruleOrder || [
'default'
];
this.domainObject.configuration.testDataConfig = this.domainObject.configuration
.testDataConfig || [
{
object: '',
key: '',
value: ''
}
];
this.activeId = 'default';
this.rulesById = {};
this.domElement = templateHelpers.convertTemplateToHTML(widgetTemplate)[0];
this.toggleRulesControl = this.domElement.querySelector('.t-view-control-rules');
this.toggleTestDataControl = this.domElement.querySelector('.t-view-control-test-data');
this.widgetButton = this.domElement.querySelector(':scope > #widget');
this.editing = false;
this.container = '';
this.editListenerUnsubscribe = () => {};
this.outerWrapper = this.domElement.querySelector('.widget-edit-holder');
this.ruleArea = this.domElement.querySelector('#ruleArea');
this.configAreaRules = this.domElement.querySelector('.widget-rules-wrapper');
this.testDataArea = this.domElement.querySelector('.widget-test-data');
this.addRuleButton = this.domElement.querySelector('#addRule');
this.conditionManager = new ConditionManager(this.domainObject, this.openmct);
this.testDataManager = new TestDataManager(
this.domainObject,
this.conditionManager,
this.openmct
);
this.watchForChanges = this.watchForChanges.bind(this);
this.show = this.show.bind(this);
this.destroy = this.destroy.bind(this);
this.addRule = this.addRule.bind(this);
this.addHyperlink(domainObject.url, domainObject.openNewTab);
this.watchForChanges(openmct, domainObject);
const self = this;
/**
* A Summary Widget object, which allows a user to configure rules based
* on telemetry producing domain objects, and update a compact display
* accordingly.
* @constructor
* @param {Object} domainObject The domain Object represented by this Widget
* @param {MCT} openmct An MCT instance
* Toggles the configuration area for test data in the view
* @private
*/
function SummaryWidget(domainObject, openmct) {
eventHelpers.extend(this);
this.domainObject = domainObject;
this.openmct = openmct;
this.domainObject.configuration = this.domainObject.configuration || {};
this.domainObject.configuration.ruleConfigById =
this.domainObject.configuration.ruleConfigById || {};
this.domainObject.configuration.ruleOrder = this.domainObject.configuration.ruleOrder || [
'default'
];
this.domainObject.configuration.testDataConfig = this.domainObject.configuration
.testDataConfig || [
{
object: '',
key: '',
value: ''
}
];
this.activeId = 'default';
this.rulesById = {};
this.domElement = templateHelpers.convertTemplateToHTML(widgetTemplate)[0];
this.toggleRulesControl = this.domElement.querySelector('.t-view-control-rules');
this.toggleTestDataControl = this.domElement.querySelector('.t-view-control-test-data');
this.widgetButton = this.domElement.querySelector(':scope > #widget');
this.editing = false;
this.container = '';
this.editListenerUnsubscribe = () => {};
this.outerWrapper = this.domElement.querySelector('.widget-edit-holder');
this.ruleArea = this.domElement.querySelector('#ruleArea');
this.configAreaRules = this.domElement.querySelector('.widget-rules-wrapper');
this.testDataArea = this.domElement.querySelector('.widget-test-data');
this.addRuleButton = this.domElement.querySelector('#addRule');
this.conditionManager = new ConditionManager(this.domainObject, this.openmct);
this.testDataManager = new TestDataManager(
this.domainObject,
this.conditionManager,
this.openmct
);
this.watchForChanges = this.watchForChanges.bind(this);
this.show = this.show.bind(this);
this.destroy = this.destroy.bind(this);
this.addRule = this.addRule.bind(this);
this.addHyperlink(domainObject.url, domainObject.openNewTab);
this.watchForChanges(openmct, domainObject);
const self = this;
/**
* Toggles the configuration area for test data in the view
* @private
*/
function toggleTestData() {
if (self.outerWrapper.classList.contains('expanded-widget-test-data')) {
self.outerWrapper.classList.remove('expanded-widget-test-data');
} else {
self.outerWrapper.classList.add('expanded-widget-test-data');
}
if (self.toggleTestDataControl.classList.contains('c-disclosure-triangle--expanded')) {
self.toggleTestDataControl.classList.remove('c-disclosure-triangle--expanded');
} else {
self.toggleTestDataControl.classList.add('c-disclosure-triangle--expanded');
}
function toggleTestData() {
if (self.outerWrapper.classList.contains('expanded-widget-test-data')) {
self.outerWrapper.classList.remove('expanded-widget-test-data');
} else {
self.outerWrapper.classList.add('expanded-widget-test-data');
}
this.listenTo(this.toggleTestDataControl, 'click', toggleTestData);
/**
* Toggles the configuration area for rules in the view
* @private
*/
function toggleRules() {
templateHelpers.toggleClass(self.outerWrapper, 'expanded-widget-rules');
templateHelpers.toggleClass(self.toggleRulesControl, 'c-disclosure-triangle--expanded');
if (self.toggleTestDataControl.classList.contains('c-disclosure-triangle--expanded')) {
self.toggleTestDataControl.classList.remove('c-disclosure-triangle--expanded');
} else {
self.toggleTestDataControl.classList.add('c-disclosure-triangle--expanded');
}
this.listenTo(this.toggleRulesControl, 'click', toggleRules);
}
/**
* adds or removes href to widget button and adds or removes openInNewTab
* @param {string} url String that denotes the url to be opened
* @param {string} openNewTab String that denotes wether to open link in new tab or not
*/
SummaryWidget.prototype.addHyperlink = function (url, openNewTab) {
if (url) {
this.widgetButton.href = urlSanitizeLib.sanitizeUrl(url);
} else {
this.widgetButton.removeAttribute('href');
}
if (openNewTab === 'newTab') {
this.widgetButton.target = '_blank';
} else {
this.widgetButton.removeAttribute('target');
}
};
this.listenTo(this.toggleTestDataControl, 'click', toggleTestData);
/**
* adds a listener to the object to watch for any changes made by user
* only executes if changes are observed
* @param {openmct} Object Instance of OpenMCT
* @param {domainObject} Object instance of this object
* Toggles the configuration area for rules in the view
* @private
*/
SummaryWidget.prototype.watchForChanges = function (openmct, domainObject) {
this.watchForChangesUnsubscribe = openmct.objects.observe(
domainObject,
'*',
function (newDomainObject) {
if (
newDomainObject.url !== this.domainObject.url ||
newDomainObject.openNewTab !== this.domainObject.openNewTab
) {
this.addHyperlink(newDomainObject.url, newDomainObject.openNewTab);
function toggleRules() {
templateHelpers.toggleClass(self.outerWrapper, 'expanded-widget-rules');
templateHelpers.toggleClass(self.toggleRulesControl, 'c-disclosure-triangle--expanded');
}
this.listenTo(this.toggleRulesControl, 'click', toggleRules);
}
/**
* adds or removes href to widget button and adds or removes openInNewTab
* @param {string} url String that denotes the url to be opened
* @param {string} openNewTab String that denotes wether to open link in new tab or not
*/
SummaryWidget.prototype.addHyperlink = function (url, openNewTab) {
if (url) {
this.widgetButton.href = urlSanitizeLib.sanitizeUrl(url);
} else {
this.widgetButton.removeAttribute('href');
}
if (openNewTab === 'newTab') {
this.widgetButton.target = '_blank';
} else {
this.widgetButton.removeAttribute('target');
}
};
/**
* adds a listener to the object to watch for any changes made by user
* only executes if changes are observed
* @param {openmct} Object Instance of OpenMCT
* @param {domainObject} Object instance of this object
*/
SummaryWidget.prototype.watchForChanges = function (openmct, domainObject) {
this.watchForChangesUnsubscribe = openmct.objects.observe(
domainObject,
'*',
function (newDomainObject) {
if (
newDomainObject.url !== this.domainObject.url ||
newDomainObject.openNewTab !== this.domainObject.openNewTab
) {
this.addHyperlink(newDomainObject.url, newDomainObject.openNewTab);
}
}.bind(this)
);
};
/**
* Builds the Summary Widget's DOM, performs other necessary setup, and attaches
* this Summary Widget's view to the supplied container.
* @param {element} container The DOM element that will contain this Summary
* Widget's view.
*/
SummaryWidget.prototype.show = function (container) {
const self = this;
this.container = container;
this.container.append(this.domElement);
this.domElement.querySelector('.widget-test-data').append(this.testDataManager.getDOM());
this.widgetDnD = new WidgetDnD(
this.domElement,
this.domainObject.configuration.ruleOrder,
this.rulesById
);
this.initRule('default', 'Default');
this.domainObject.configuration.ruleOrder.forEach(function (ruleId) {
if (ruleId !== 'default') {
self.initRule(ruleId);
}
});
this.refreshRules();
this.updateWidget();
this.listenTo(this.addRuleButton, 'click', this.addRule);
this.conditionManager.on('receiveTelemetry', this.executeRules, this);
this.widgetDnD.on('drop', this.reorder, this);
};
/**
* Unregister event listeners with the Open MCT APIs, unsubscribe from telemetry,
* and clean up event handlers
*/
SummaryWidget.prototype.destroy = function (container) {
this.editListenerUnsubscribe();
this.conditionManager.destroy();
this.testDataManager.destroy();
this.widgetDnD.destroy();
this.watchForChangesUnsubscribe();
Object.values(this.rulesById).forEach(function (rule) {
rule.destroy();
});
this.stopListening();
};
/**
* Update the view from the current rule configuration and order
*/
SummaryWidget.prototype.refreshRules = function () {
const self = this;
const ruleOrder = self.domainObject.configuration.ruleOrder;
const rules = self.rulesById;
self.ruleArea.innerHTML = '';
Object.values(ruleOrder).forEach(function (ruleId) {
self.ruleArea.append(rules[ruleId].getDOM());
});
this.executeRules();
this.addOrRemoveDragIndicator();
};
SummaryWidget.prototype.addOrRemoveDragIndicator = function () {
const rules = this.domainObject.configuration.ruleOrder;
const rulesById = this.rulesById;
rules.forEach(function (ruleKey, index, array) {
if (array.length > 2 && index > 0) {
rulesById[ruleKey].domElement.querySelector('.t-grippy').style.display = '';
} else {
rulesById[ruleKey].domElement.querySelector('.t-grippy').style.display = 'none';
}
});
};
/**
* Update the widget's appearance from the configuration of the active rule
*/
SummaryWidget.prototype.updateWidget = function () {
const WIDGET_ICON_CLASS = 'c-sw__icon js-sw__icon';
const activeRule = this.rulesById[this.activeId];
this.applyStyle(this.domElement.querySelector('#widget'), activeRule.getProperty('style'));
this.domElement.querySelector('#widget').title = activeRule.getProperty('message');
this.domElement.querySelector('#widgetLabel').textContent = activeRule.getProperty('label');
this.domElement.querySelector('#widgetIcon').classList =
WIDGET_ICON_CLASS + ' ' + activeRule.getProperty('icon');
};
/**
* Get the active rule and update the Widget's appearance.
*/
SummaryWidget.prototype.executeRules = function () {
this.activeId = this.conditionManager.executeRules(
this.domainObject.configuration.ruleOrder,
this.rulesById
);
this.updateWidget();
};
/**
* Add a new rule to this widget
*/
SummaryWidget.prototype.addRule = function () {
let ruleCount = 0;
let ruleId;
const ruleOrder = this.domainObject.configuration.ruleOrder;
while (Object.keys(this.rulesById).includes('rule' + ruleCount)) {
ruleCount++;
}
ruleId = 'rule' + ruleCount;
ruleOrder.push(ruleId);
this.domainObject.configuration.ruleOrder = ruleOrder;
this.initRule(ruleId, 'Rule');
this.updateDomainObject();
this.refreshRules();
};
/**
* Duplicate an existing widget rule from its configuration and splice it in
* after the rule it duplicates
* @param {Object} sourceConfig The configuration properties of the rule to be
* instantiated
*/
SummaryWidget.prototype.duplicateRule = function (sourceConfig) {
let ruleCount = 0;
let ruleId;
const sourceRuleId = sourceConfig.id;
const ruleOrder = this.domainObject.configuration.ruleOrder;
const ruleIds = Object.keys(this.rulesById);
while (ruleIds.includes('rule' + ruleCount)) {
ruleCount = ++ruleCount;
}
ruleId = 'rule' + ruleCount;
sourceConfig.id = ruleId;
sourceConfig.name += ' Copy';
ruleOrder.splice(ruleOrder.indexOf(sourceRuleId) + 1, 0, ruleId);
this.domainObject.configuration.ruleOrder = ruleOrder;
this.domainObject.configuration.ruleConfigById[ruleId] = sourceConfig;
this.initRule(ruleId, sourceConfig.name);
this.updateDomainObject();
this.refreshRules();
};
/**
* Initialize a new rule from a default configuration, or build a {Rule} object
* from it if already exists
* @param {string} ruleId An key to be used to identify this ruleId, or the key
of the rule to be instantiated
* @param {string} ruleName The initial human-readable name of this rule
*/
SummaryWidget.prototype.initRule = function (ruleId, ruleName) {
let ruleConfig;
const styleObj = {};
Object.assign(styleObj, DEFAULT_PROPS);
if (!this.domainObject.configuration.ruleConfigById[ruleId]) {
this.domainObject.configuration.ruleConfigById[ruleId] = {
name: ruleName || 'Rule',
label: 'Unnamed Rule',
message: '',
id: ruleId,
icon: ' ',
style: styleObj,
description: ruleId === 'default' ? 'Default appearance for the widget' : 'A new rule',
conditions: [
{
object: '',
key: '',
operation: '',
values: []
}
}.bind(this)
);
};
],
jsCondition: '',
trigger: 'any',
expanded: 'true'
};
}
/**
* Builds the Summary Widget's DOM, performs other necessary setup, and attaches
* this Summary Widget's view to the supplied container.
* @param {element} container The DOM element that will contain this Summary
* Widget's view.
*/
SummaryWidget.prototype.show = function (container) {
const self = this;
this.container = container;
this.container.append(this.domElement);
this.domElement.querySelector('.widget-test-data').append(this.testDataManager.getDOM());
this.widgetDnD = new WidgetDnD(
this.domElement,
this.domainObject.configuration.ruleOrder,
this.rulesById
);
this.initRule('default', 'Default');
this.domainObject.configuration.ruleOrder.forEach(function (ruleId) {
if (ruleId !== 'default') {
self.initRule(ruleId);
}
});
this.refreshRules();
this.updateWidget();
ruleConfig = this.domainObject.configuration.ruleConfigById[ruleId];
this.rulesById[ruleId] = new Rule(
ruleConfig,
this.domainObject,
this.openmct,
this.conditionManager,
this.widgetDnD,
this.container
);
this.rulesById[ruleId].on('remove', this.refreshRules, this);
this.rulesById[ruleId].on('duplicate', this.duplicateRule, this);
this.rulesById[ruleId].on('change', this.updateWidget, this);
this.rulesById[ruleId].on('conditionChange', this.executeRules, this);
};
this.listenTo(this.addRuleButton, 'click', this.addRule);
this.conditionManager.on('receiveTelemetry', this.executeRules, this);
this.widgetDnD.on('drop', this.reorder, this);
};
/**
* Given two ruleIds, move the source rule after the target rule and update
* the view.
* @param {Object} event An event object representing this drop with draggingId
* and dropTarget fields
*/
SummaryWidget.prototype.reorder = function (event) {
const ruleOrder = this.domainObject.configuration.ruleOrder;
const sourceIndex = ruleOrder.indexOf(event.draggingId);
let targetIndex;
/**
* Unregister event listeners with the Open MCT APIs, unsubscribe from telemetry,
* and clean up event handlers
*/
SummaryWidget.prototype.destroy = function (container) {
this.editListenerUnsubscribe();
this.conditionManager.destroy();
this.testDataManager.destroy();
this.widgetDnD.destroy();
this.watchForChangesUnsubscribe();
Object.values(this.rulesById).forEach(function (rule) {
rule.destroy();
});
this.stopListening();
};
/**
* Update the view from the current rule configuration and order
*/
SummaryWidget.prototype.refreshRules = function () {
const self = this;
const ruleOrder = self.domainObject.configuration.ruleOrder;
const rules = self.rulesById;
self.ruleArea.innerHTML = '';
Object.values(ruleOrder).forEach(function (ruleId) {
self.ruleArea.append(rules[ruleId].getDOM());
});
this.executeRules();
this.addOrRemoveDragIndicator();
};
SummaryWidget.prototype.addOrRemoveDragIndicator = function () {
const rules = this.domainObject.configuration.ruleOrder;
const rulesById = this.rulesById;
rules.forEach(function (ruleKey, index, array) {
if (array.length > 2 && index > 0) {
rulesById[ruleKey].domElement.querySelector('.t-grippy').style.display = '';
} else {
rulesById[ruleKey].domElement.querySelector('.t-grippy').style.display = 'none';
}
});
};
/**
* Update the widget's appearance from the configuration of the active rule
*/
SummaryWidget.prototype.updateWidget = function () {
const WIDGET_ICON_CLASS = 'c-sw__icon js-sw__icon';
const activeRule = this.rulesById[this.activeId];
this.applyStyle(this.domElement.querySelector('#widget'), activeRule.getProperty('style'));
this.domElement.querySelector('#widget').title = activeRule.getProperty('message');
this.domElement.querySelector('#widgetLabel').textContent = activeRule.getProperty('label');
this.domElement.querySelector('#widgetIcon').classList =
WIDGET_ICON_CLASS + ' ' + activeRule.getProperty('icon');
};
/**
* Get the active rule and update the Widget's appearance.
*/
SummaryWidget.prototype.executeRules = function () {
this.activeId = this.conditionManager.executeRules(
this.domainObject.configuration.ruleOrder,
this.rulesById
);
this.updateWidget();
};
/**
* Add a new rule to this widget
*/
SummaryWidget.prototype.addRule = function () {
let ruleCount = 0;
let ruleId;
const ruleOrder = this.domainObject.configuration.ruleOrder;
while (Object.keys(this.rulesById).includes('rule' + ruleCount)) {
ruleCount++;
}
ruleId = 'rule' + ruleCount;
ruleOrder.push(ruleId);
if (event.draggingId !== event.dropTarget) {
ruleOrder.splice(sourceIndex, 1);
targetIndex = ruleOrder.indexOf(event.dropTarget);
ruleOrder.splice(targetIndex + 1, 0, event.draggingId);
this.domainObject.configuration.ruleOrder = ruleOrder;
this.initRule(ruleId, 'Rule');
this.updateDomainObject();
this.refreshRules();
};
}
/**
* Duplicate an existing widget rule from its configuration and splice it in
* after the rule it duplicates
* @param {Object} sourceConfig The configuration properties of the rule to be
* instantiated
*/
SummaryWidget.prototype.duplicateRule = function (sourceConfig) {
let ruleCount = 0;
let ruleId;
const sourceRuleId = sourceConfig.id;
const ruleOrder = this.domainObject.configuration.ruleOrder;
const ruleIds = Object.keys(this.rulesById);
this.refreshRules();
};
while (ruleIds.includes('rule' + ruleCount)) {
ruleCount = ++ruleCount;
}
/**
* Apply a list of css properties to an element
* @param {element} elem The DOM element to which the rules will be applied
* @param {object} style an object representing the style
*/
SummaryWidget.prototype.applyStyle = function (elem, style) {
Object.keys(style).forEach(function (propId) {
elem.style[propId] = style[propId];
});
};
ruleId = 'rule' + ruleCount;
sourceConfig.id = ruleId;
sourceConfig.name += ' Copy';
ruleOrder.splice(ruleOrder.indexOf(sourceRuleId) + 1, 0, ruleId);
this.domainObject.configuration.ruleOrder = ruleOrder;
this.domainObject.configuration.ruleConfigById[ruleId] = sourceConfig;
this.initRule(ruleId, sourceConfig.name);
this.updateDomainObject();
this.refreshRules();
};
/**
* Initialize a new rule from a default configuration, or build a {Rule} object
* from it if already exists
* @param {string} ruleId An key to be used to identify this ruleId, or the key
of the rule to be instantiated
* @param {string} ruleName The initial human-readable name of this rule
*/
SummaryWidget.prototype.initRule = function (ruleId, ruleName) {
let ruleConfig;
const styleObj = {};
Object.assign(styleObj, DEFAULT_PROPS);
if (!this.domainObject.configuration.ruleConfigById[ruleId]) {
this.domainObject.configuration.ruleConfigById[ruleId] = {
name: ruleName || 'Rule',
label: 'Unnamed Rule',
message: '',
id: ruleId,
icon: ' ',
style: styleObj,
description: ruleId === 'default' ? 'Default appearance for the widget' : 'A new rule',
conditions: [
{
object: '',
key: '',
operation: '',
values: []
}
],
jsCondition: '',
trigger: 'any',
expanded: 'true'
};
}
ruleConfig = this.domainObject.configuration.ruleConfigById[ruleId];
this.rulesById[ruleId] = new Rule(
ruleConfig,
this.domainObject,
this.openmct,
this.conditionManager,
this.widgetDnD,
this.container
);
this.rulesById[ruleId].on('remove', this.refreshRules, this);
this.rulesById[ruleId].on('duplicate', this.duplicateRule, this);
this.rulesById[ruleId].on('change', this.updateWidget, this);
this.rulesById[ruleId].on('conditionChange', this.executeRules, this);
};
/**
* Given two ruleIds, move the source rule after the target rule and update
* the view.
* @param {Object} event An event object representing this drop with draggingId
* and dropTarget fields
*/
SummaryWidget.prototype.reorder = function (event) {
const ruleOrder = this.domainObject.configuration.ruleOrder;
const sourceIndex = ruleOrder.indexOf(event.draggingId);
let targetIndex;
if (event.draggingId !== event.dropTarget) {
ruleOrder.splice(sourceIndex, 1);
targetIndex = ruleOrder.indexOf(event.dropTarget);
ruleOrder.splice(targetIndex + 1, 0, event.draggingId);
this.domainObject.configuration.ruleOrder = ruleOrder;
this.updateDomainObject();
}
this.refreshRules();
};
/**
* Apply a list of css properties to an element
* @param {element} elem The DOM element to which the rules will be applied
* @param {object} style an object representing the style
*/
SummaryWidget.prototype.applyStyle = function (elem, style) {
Object.keys(style).forEach(function (propId) {
elem.style[propId] = style[propId];
});
};
/**
* Mutate this domain object's configuration with the current local configuration
*/
SummaryWidget.prototype.updateDomainObject = function () {
this.openmct.objects.mutate(
this.domainObject,
'configuration',
this.domainObject.configuration
);
};
return SummaryWidget;
});
/**
* Mutate this domain object's configuration with the current local configuration
*/
SummaryWidget.prototype.updateDomainObject = function () {
this.openmct.objects.mutate(this.domainObject, 'configuration', this.domainObject.configuration);
};

View File

@ -1,193 +1,190 @@
define([
'../res/testDataItemTemplate.html',
'./input/ObjectSelect',
'./input/KeySelect',
'./eventHelpers',
'../../../utils/template/templateHelpers',
'EventEmitter'
], function (itemTemplate, ObjectSelect, KeySelect, eventHelpers, templateHelpers, EventEmitter) {
import EventEmitter from 'EventEmitter';
import * as templateHelpers from '../../../utils/template/templateHelpers';
import itemTemplate from '../res/testDataItemTemplate.html';
import eventHelpers from './eventHelpers';
import KeySelect from './input/KeySelect';
import ObjectSelect from './input/ObjectSelect';
/**
* An object representing a single mock telemetry value
* @param {object} itemConfig the configuration for this item, consisting of
* object, key, and value fields
* @param {number} index the index of this TestDataItem object in the data
* model of its parent {TestDataManager} o be injected into callbacks
* for removes
* @param {ConditionManager} conditionManager a conditionManager instance
* for populating selects with configuration data
* @constructor
*/
export default function TestDataItem(itemConfig, index, conditionManager) {
eventHelpers.extend(this);
this.config = itemConfig;
this.index = index;
this.conditionManager = conditionManager;
this.domElement = templateHelpers.convertTemplateToHTML(itemTemplate)[0];
this.eventEmitter = new EventEmitter();
this.supportedCallbacks = ['remove', 'duplicate', 'change'];
this.deleteButton = this.domElement.querySelector('.t-delete');
this.duplicateButton = this.domElement.querySelector('.t-duplicate');
this.selects = {};
this.valueInputs = [];
this.remove = this.remove.bind(this);
this.duplicate = this.duplicate.bind(this);
const self = this;
/**
* An object representing a single mock telemetry value
* @param {object} itemConfig the configuration for this item, consisting of
* object, key, and value fields
* @param {number} index the index of this TestDataItem object in the data
* model of its parent {TestDataManager} o be injected into callbacks
* for removes
* @param {ConditionManager} conditionManager a conditionManager instance
* for populating selects with configuration data
* @constructor
* A change event handler for this item's select inputs, which also invokes
* change callbacks registered with this item
* @param {string} value The new value of this select item
* @param {string} property The property of this item to modify
* @private
*/
function TestDataItem(itemConfig, index, conditionManager) {
eventHelpers.extend(this);
this.config = itemConfig;
this.index = index;
this.conditionManager = conditionManager;
this.domElement = templateHelpers.convertTemplateToHTML(itemTemplate)[0];
this.eventEmitter = new EventEmitter();
this.supportedCallbacks = ['remove', 'duplicate', 'change'];
this.deleteButton = this.domElement.querySelector('.t-delete');
this.duplicateButton = this.domElement.querySelector('.t-duplicate');
this.selects = {};
this.valueInputs = [];
this.remove = this.remove.bind(this);
this.duplicate = this.duplicate.bind(this);
const self = this;
/**
* A change event handler for this item's select inputs, which also invokes
* change callbacks registered with this item
* @param {string} value The new value of this select item
* @param {string} property The property of this item to modify
* @private
*/
function onSelectChange(value, property) {
if (property === 'key') {
self.generateValueInput(value);
}
self.eventEmitter.emit('change', {
value: value,
property: property,
index: self.index
});
function onSelectChange(value, property) {
if (property === 'key') {
self.generateValueInput(value);
}
/**
* An input event handler for this item's value field. Invokes any change
* callbacks associated with this item
* @param {Event} event The input event that initiated this callback
* @private
*/
function onValueInput(event) {
const elem = event.target;
const value = isNaN(elem.valueAsNumber) ? elem.value : elem.valueAsNumber;
if (elem.tagName.toUpperCase() === 'INPUT') {
self.eventEmitter.emit('change', {
value: value,
property: 'value',
index: self.index
});
}
}
this.listenTo(this.deleteButton, 'click', this.remove);
this.listenTo(this.duplicateButton, 'click', this.duplicate);
this.selects.object = new ObjectSelect(this.config, this.conditionManager);
this.selects.key = new KeySelect(
this.config,
this.selects.object,
this.conditionManager,
function (value) {
onSelectChange(value, 'key');
}
);
this.selects.object.on('change', function (value) {
onSelectChange(value, 'object');
self.eventEmitter.emit('change', {
value: value,
property: property,
index: self.index
});
Object.values(this.selects).forEach(function (select) {
self.domElement.querySelector('.t-configuration').append(select.getDOM());
});
this.listenTo(this.domElement, 'input', onValueInput);
}
/**
* Gets the DOM associated with this element's view
* @return {Element}
* An input event handler for this item's value field. Invokes any change
* callbacks associated with this item
* @param {Event} event The input event that initiated this callback
* @private
*/
TestDataItem.prototype.getDOM = function (container) {
return this.domElement;
};
function onValueInput(event) {
const elem = event.target;
const value = isNaN(elem.valueAsNumber) ? elem.value : elem.valueAsNumber;
/**
* Register a callback with this item: supported callbacks are remove, change,
* and duplicate
* @param {string} event The key for the event to listen to
* @param {function} callback The function that this rule will invoke on this event
* @param {Object} context A reference to a scope to use as the context for
* context for the callback function
*/
TestDataItem.prototype.on = function (event, callback, context) {
if (this.supportedCallbacks.includes(event)) {
this.eventEmitter.on(event, callback, context || this);
if (elem.tagName.toUpperCase() === 'INPUT') {
self.eventEmitter.emit('change', {
value: value,
property: 'value',
index: self.index
});
}
};
}
/**
* Implement "off" to complete event emitter interface.
*/
TestDataItem.prototype.off = function (event, callback, context) {
this.eventEmitter.off(event, callback, context);
};
this.listenTo(this.deleteButton, 'click', this.remove);
this.listenTo(this.duplicateButton, 'click', this.duplicate);
/**
* Hide the appropriate inputs when this is the only item
*/
TestDataItem.prototype.hideButtons = function () {
this.deleteButton.style.display = 'none';
};
/**
* Remove this item from the configuration. Invokes any registered
* remove callbacks
*/
TestDataItem.prototype.remove = function () {
const self = this;
this.eventEmitter.emit('remove', self.index);
this.stopListening();
Object.values(this.selects).forEach(function (select) {
select.destroy();
});
};
/**
* Makes a deep clone of this item's configuration, and invokes any registered
* duplicate callbacks with the cloned configuration as an argument
*/
TestDataItem.prototype.duplicate = function () {
const sourceItem = JSON.parse(JSON.stringify(this.config));
const self = this;
this.eventEmitter.emit('duplicate', {
sourceItem: sourceItem,
index: self.index
});
};
/**
* When a telemetry property key is selected, create the appropriate value input
* and add it to the view
* @param {string} key The key of currently selected telemetry property
*/
TestDataItem.prototype.generateValueInput = function (key) {
const evaluator = this.conditionManager.getEvaluator();
const inputArea = this.domElement.querySelector('.t-value-inputs');
const dataType = this.conditionManager.getTelemetryPropertyType(this.config.object, key);
const inputType = evaluator.getInputTypeById(dataType);
inputArea.innerHTML = '';
if (inputType) {
if (!this.config.value) {
this.config.value = inputType === 'number' ? 0 : '';
}
const newInput = document.createElement('input');
newInput.type = `${inputType}`;
newInput.value = `${this.config.value}`;
this.valueInput = newInput;
inputArea.append(this.valueInput);
this.selects.object = new ObjectSelect(this.config, this.conditionManager);
this.selects.key = new KeySelect(
this.config,
this.selects.object,
this.conditionManager,
function (value) {
onSelectChange(value, 'key');
}
};
);
return TestDataItem;
});
this.selects.object.on('change', function (value) {
onSelectChange(value, 'object');
});
Object.values(this.selects).forEach(function (select) {
self.domElement.querySelector('.t-configuration').append(select.getDOM());
});
this.listenTo(this.domElement, 'input', onValueInput);
}
/**
* Gets the DOM associated with this element's view
* @return {Element}
*/
TestDataItem.prototype.getDOM = function (container) {
return this.domElement;
};
/**
* Register a callback with this item: supported callbacks are remove, change,
* and duplicate
* @param {string} event The key for the event to listen to
* @param {function} callback The function that this rule will invoke on this event
* @param {Object} context A reference to a scope to use as the context for
* context for the callback function
*/
TestDataItem.prototype.on = function (event, callback, context) {
if (this.supportedCallbacks.includes(event)) {
this.eventEmitter.on(event, callback, context || this);
}
};
/**
* 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
*/
TestDataItem.prototype.hideButtons = function () {
this.deleteButton.style.display = 'none';
};
/**
* Remove this item from the configuration. Invokes any registered
* remove callbacks
*/
TestDataItem.prototype.remove = function () {
const self = this;
this.eventEmitter.emit('remove', self.index);
this.stopListening();
Object.values(this.selects).forEach(function (select) {
select.destroy();
});
};
/**
* Makes a deep clone of this item's configuration, and invokes any registered
* duplicate callbacks with the cloned configuration as an argument
*/
TestDataItem.prototype.duplicate = function () {
const sourceItem = JSON.parse(JSON.stringify(this.config));
const self = this;
this.eventEmitter.emit('duplicate', {
sourceItem: sourceItem,
index: self.index
});
};
/**
* When a telemetry property key is selected, create the appropriate value input
* and add it to the view
* @param {string} key The key of currently selected telemetry property
*/
TestDataItem.prototype.generateValueInput = function (key) {
const evaluator = this.conditionManager.getEvaluator();
const inputArea = this.domElement.querySelector('.t-value-inputs');
const dataType = this.conditionManager.getTelemetryPropertyType(this.config.object, key);
const inputType = evaluator.getInputTypeById(dataType);
inputArea.innerHTML = '';
if (inputType) {
if (!this.config.value) {
this.config.value = inputType === 'number' ? 0 : '';
}
const newInput = document.createElement('input');
newInput.type = `${inputType}`;
newInput.value = `${this.config.value}`;
this.valueInput = newInput;
inputArea.append(this.valueInput);
}
};

View File

@ -1,201 +1,198 @@
define([
'./eventHelpers',
'../res/testDataTemplate.html',
'./TestDataItem',
'../../../utils/template/templateHelpers',
'lodash'
], function (eventHelpers, testDataTemplate, TestDataItem, templateHelpers, _) {
import _ from 'lodash';
import * as templateHelpers from '../../../utils/template/templateHelpers';
import testDataTemplate from '../res/testDataTemplate.html';
import eventHelpers from './eventHelpers';
import TestDataItem from './TestDataItem';
/**
* Controls the input and usage of test data in the summary widget.
* @constructor
* @param {Object} domainObject The summary widget domain object
* @param {ConditionManager} conditionManager A conditionManager instance
* @param {MCT} openmct and MCT instance
*/
export default function TestDataManager(domainObject, conditionManager, openmct) {
eventHelpers.extend(this);
const self = this;
this.domainObject = domainObject;
this.manager = conditionManager;
this.openmct = openmct;
this.evaluator = this.manager.getEvaluator();
this.domElement = templateHelpers.convertTemplateToHTML(testDataTemplate)[0];
this.config = this.domainObject.configuration.testDataConfig;
this.testCache = {};
this.itemArea = this.domElement.querySelector('.t-test-data-config');
this.addItemButton = this.domElement.querySelector('.add-test-condition');
this.testDataInput = this.domElement.querySelector('.t-test-data-checkbox');
/**
* Controls the input and usage of test data in the summary widget.
* @constructor
* @param {Object} domainObject The summary widget domain object
* @param {ConditionManager} conditionManager A conditionManager instance
* @param {MCT} openmct and MCT instance
* Toggles whether the associated {ConditionEvaluator} uses the actual
* subscription cache or the test data cache
* @param {Event} event The change event that triggered this callback
* @private
*/
function TestDataManager(domainObject, conditionManager, openmct) {
eventHelpers.extend(this);
const self = this;
this.domainObject = domainObject;
this.manager = conditionManager;
this.openmct = openmct;
this.evaluator = this.manager.getEvaluator();
this.domElement = templateHelpers.convertTemplateToHTML(testDataTemplate)[0];
this.config = this.domainObject.configuration.testDataConfig;
this.testCache = {};
this.itemArea = this.domElement.querySelector('.t-test-data-config');
this.addItemButton = this.domElement.querySelector('.add-test-condition');
this.testDataInput = this.domElement.querySelector('.t-test-data-checkbox');
/**
* Toggles whether the associated {ConditionEvaluator} uses the actual
* subscription cache or the test data cache
* @param {Event} event The change event that triggered this callback
* @private
*/
function toggleTestData(event) {
const elem = event.target;
self.evaluator.useTestData(elem.checked);
self.updateTestCache();
}
this.listenTo(this.addItemButton, 'click', function () {
self.initItem();
});
this.listenTo(this.testDataInput, 'change', toggleTestData);
this.evaluator.setTestDataCache(this.testCache);
this.evaluator.useTestData(false);
this.refreshItems();
function toggleTestData(event) {
const elem = event.target;
self.evaluator.useTestData(elem.checked);
self.updateTestCache();
}
/**
* Get the DOM element representing this test data manager in the view
*/
TestDataManager.prototype.getDOM = function () {
return this.domElement;
this.listenTo(this.addItemButton, 'click', function () {
self.initItem();
});
this.listenTo(this.testDataInput, 'change', toggleTestData);
this.evaluator.setTestDataCache(this.testCache);
this.evaluator.useTestData(false);
this.refreshItems();
}
/**
* Get the DOM element representing this test data manager in the view
*/
TestDataManager.prototype.getDOM = function () {
return this.domElement;
};
/**
* Initialize a new test data item, either from a source configuration, or with
* the default empty configuration
* @param {Object} [config] An object with sourceItem and index fields to instantiate
* this rule from, optional
*/
TestDataManager.prototype.initItem = function (config) {
const sourceIndex = config && config.index;
const defaultItem = {
object: '',
key: '',
value: ''
};
let newItem;
/**
* Initialize a new test data item, either from a source configuration, or with
* the default empty configuration
* @param {Object} [config] An object with sourceItem and index fields to instantiate
* this rule from, optional
*/
TestDataManager.prototype.initItem = function (config) {
const sourceIndex = config && config.index;
const defaultItem = {
object: '',
key: '',
value: ''
};
let newItem;
newItem = config !== undefined ? config.sourceItem : defaultItem;
if (sourceIndex !== undefined) {
this.config.splice(sourceIndex + 1, 0, newItem);
} else {
this.config.push(newItem);
}
newItem = config !== undefined ? config.sourceItem : defaultItem;
if (sourceIndex !== undefined) {
this.config.splice(sourceIndex + 1, 0, newItem);
} else {
this.config.push(newItem);
}
this.updateDomainObject();
this.refreshItems();
};
this.updateDomainObject();
this.refreshItems();
};
/**
* Remove an item from this TestDataManager at the given index
* @param {number} removeIndex The index of the item to remove
*/
TestDataManager.prototype.removeItem = function (removeIndex) {
_.remove(this.config, function (item, index) {
return index === removeIndex;
});
this.updateDomainObject();
this.refreshItems();
};
/**
* Remove an item from this TestDataManager at the given index
* @param {number} removeIndex The index of the item to remove
*/
TestDataManager.prototype.removeItem = function (removeIndex) {
_.remove(this.config, function (item, index) {
return index === removeIndex;
});
this.updateDomainObject();
this.refreshItems();
};
/**
* Change event handler for the test data items which compose this
* test data generator
* @param {Object} event An object representing this event, with value, property,
* and index fields
*/
TestDataManager.prototype.onItemChange = function (event) {
this.config[event.index][event.property] = event.value;
this.updateDomainObject();
this.updateTestCache();
};
/**
* Change event handler for the test data items which compose this
* test data generator
* @param {Object} event An object representing this event, with value, property,
* and index fields
*/
TestDataManager.prototype.onItemChange = function (event) {
this.config[event.index][event.property] = event.value;
this.updateDomainObject();
this.updateTestCache();
};
/**
* Builds the test cache from the current item configuration, and passes
* the new test cache to the associated {ConditionEvaluator} instance
*/
TestDataManager.prototype.updateTestCache = function () {
this.generateTestCache();
this.evaluator.setTestDataCache(this.testCache);
this.manager.triggerTelemetryCallback();
};
/**
* Builds the test cache from the current item configuration, and passes
* the new test cache to the associated {ConditionEvaluator} instance
*/
TestDataManager.prototype.updateTestCache = function () {
this.generateTestCache();
this.evaluator.setTestDataCache(this.testCache);
this.manager.triggerTelemetryCallback();
};
/**
* Instantiate {TestDataItem} objects from the current configuration, and
* update the view accordingly
*/
TestDataManager.prototype.refreshItems = function () {
const self = this;
if (this.items) {
this.items.forEach(function (item) {
this.stopListening(item);
}, this);
}
self.items = [];
this.domElement.querySelectorAll('.t-test-data-item').forEach((item) => {
item.remove();
});
this.config.forEach(function (item, index) {
const newItem = new TestDataItem(item, index, self.manager);
self.listenTo(newItem, 'remove', self.removeItem, self);
self.listenTo(newItem, 'duplicate', self.initItem, self);
self.listenTo(newItem, 'change', self.onItemChange, self);
self.items.push(newItem);
});
self.items.forEach(function (item) {
self.itemArea.prepend(item.getDOM());
});
if (self.items.length === 1) {
self.items[0].hideButtons();
}
this.updateTestCache();
};
/**
* Builds a test data cache in the format of a telemetry subscription cache
* as expected by a {ConditionEvaluator}
*/
TestDataManager.prototype.generateTestCache = function () {
let testCache = this.testCache;
const manager = this.manager;
const compositionObjs = manager.getComposition();
let metadata;
testCache = {};
Object.keys(compositionObjs).forEach(function (id) {
testCache[id] = {};
metadata = manager.getTelemetryMetadata(id);
Object.keys(metadata).forEach(function (key) {
testCache[id][key] = '';
});
});
this.config.forEach(function (item) {
if (testCache[item.object]) {
testCache[item.object][item.key] = item.value;
}
});
this.testCache = testCache;
};
/**
* Update the domain object configuration associated with this test data manager
*/
TestDataManager.prototype.updateDomainObject = function () {
this.openmct.objects.mutate(this.domainObject, 'configuration.testDataConfig', this.config);
};
TestDataManager.prototype.destroy = function () {
this.stopListening();
/**
* Instantiate {TestDataItem} objects from the current configuration, and
* update the view accordingly
*/
TestDataManager.prototype.refreshItems = function () {
const self = this;
if (this.items) {
this.items.forEach(function (item) {
item.remove();
});
};
this.stopListening(item);
}, this);
}
return TestDataManager;
});
self.items = [];
this.domElement.querySelectorAll('.t-test-data-item').forEach((item) => {
item.remove();
});
this.config.forEach(function (item, index) {
const newItem = new TestDataItem(item, index, self.manager);
self.listenTo(newItem, 'remove', self.removeItem, self);
self.listenTo(newItem, 'duplicate', self.initItem, self);
self.listenTo(newItem, 'change', self.onItemChange, self);
self.items.push(newItem);
});
self.items.forEach(function (item) {
self.itemArea.prepend(item.getDOM());
});
if (self.items.length === 1) {
self.items[0].hideButtons();
}
this.updateTestCache();
};
/**
* Builds a test data cache in the format of a telemetry subscription cache
* as expected by a {ConditionEvaluator}
*/
TestDataManager.prototype.generateTestCache = function () {
let testCache = this.testCache;
const manager = this.manager;
const compositionObjs = manager.getComposition();
let metadata;
testCache = {};
Object.keys(compositionObjs).forEach(function (id) {
testCache[id] = {};
metadata = manager.getTelemetryMetadata(id);
Object.keys(metadata).forEach(function (key) {
testCache[id][key] = '';
});
});
this.config.forEach(function (item) {
if (testCache[item.object]) {
testCache[item.object][item.key] = item.value;
}
});
this.testCache = testCache;
};
/**
* Update the domain object configuration associated with this test data manager
*/
TestDataManager.prototype.updateDomainObject = function () {
this.openmct.objects.mutate(this.domainObject, 'configuration.testDataConfig', this.config);
};
TestDataManager.prototype.destroy = function () {
this.stopListening();
this.items.forEach(function (item) {
item.remove();
});
};

View File

@ -1,165 +1,162 @@
define([
'../res/ruleImageTemplate.html',
'EventEmitter',
'../../../utils/template/templateHelpers'
], function (ruleImageTemplate, EventEmitter, templateHelpers) {
/**
* Manages the Sortable List interface for reordering rules by drag and drop
* @param {Element} container The DOM element that contains this Summary Widget's view
* @param {string[]} ruleOrder An array of rule IDs representing the current rule order
* @param {Object} rulesById An object mapping rule IDs to rule configurations
*/
function WidgetDnD(container, ruleOrder, rulesById) {
this.container = container;
this.ruleOrder = ruleOrder;
this.rulesById = rulesById;
import EventEmitter from 'EventEmitter';
this.imageContainer = templateHelpers.convertTemplateToHTML(ruleImageTemplate)[0];
this.image = this.imageContainer.querySelector('.t-drag-rule-image');
this.draggingId = '';
this.draggingRulePrevious = '';
this.eventEmitter = new EventEmitter();
this.supportedCallbacks = ['drop'];
import * as templateHelpers from '../../../utils/template/templateHelpers';
import ruleImageTemplate from '../res/ruleImageTemplate.html';
this.drag = this.drag.bind(this);
this.drop = this.drop.bind(this);
/**
* Manages the Sortable List interface for reordering rules by drag and drop
* @param {Element} container The DOM element that contains this Summary Widget's view
* @param {string[]} ruleOrder An array of rule IDs representing the current rule order
* @param {Object} rulesById An object mapping rule IDs to rule configurations
*/
export default function WidgetDnD(container, ruleOrder, rulesById) {
this.container = container;
this.ruleOrder = ruleOrder;
this.rulesById = rulesById;
this.container.addEventListener('mousemove', this.drag);
document.addEventListener('mouseup', this.drop);
this.container.parentNode.insertBefore(this.imageContainer, this.container);
this.imageContainer.style.display = 'none';
this.imageContainer = templateHelpers.convertTemplateToHTML(ruleImageTemplate)[0];
this.image = this.imageContainer.querySelector('.t-drag-rule-image');
this.draggingId = '';
this.draggingRulePrevious = '';
this.eventEmitter = new EventEmitter();
this.supportedCallbacks = ['drop'];
this.drag = this.drag.bind(this);
this.drop = this.drop.bind(this);
this.container.addEventListener('mousemove', this.drag);
document.addEventListener('mouseup', this.drop);
this.container.parentNode.insertBefore(this.imageContainer, this.container);
this.imageContainer.style.display = 'none';
}
/**
* Remove event listeners registered to elements external to the widget
*/
WidgetDnD.prototype.destroy = function () {
this.container.removeEventListener('mousemove', this.drag);
document.removeEventListener('mouseup', this.drop);
};
/**
* Register a callback with this WidgetDnD: supported callback is drop
* @param {string} event The key for the event to listen to
* @param {function} callback The function that this rule will invoke on this event
* @param {Object} context A reference to a scope to use as the context for
* context for the callback function
*/
WidgetDnD.prototype.on = function (event, callback, context) {
if (this.supportedCallbacks.includes(event)) {
this.eventEmitter.on(event, callback, context || this);
}
};
/**
* Remove event listeners registered to elements external to the widget
*/
WidgetDnD.prototype.destroy = function () {
this.container.removeEventListener('mousemove', this.drag);
document.removeEventListener('mouseup', this.drop);
};
/**
* Sets the image for the dragged element to the given DOM element
* @param {Element} image The HTML element to set as the drap image
*/
WidgetDnD.prototype.setDragImage = function (image) {
this.image.html(image);
};
/**
* Register a callback with this WidgetDnD: supported callback is drop
* @param {string} event The key for the event to listen to
* @param {function} callback The function that this rule will invoke on this event
* @param {Object} context A reference to a scope to use as the context for
* context for the callback function
*/
WidgetDnD.prototype.on = function (event, callback, context) {
if (this.supportedCallbacks.includes(event)) {
this.eventEmitter.on(event, callback, context || this);
}
};
/**
* Calculate where this rule has been dragged relative to the other rules
* @param {Event} event The mousemove or mouseup event that triggered this
event handler
* @return {string} The ID of the rule whose drag indicator should be displayed
*/
WidgetDnD.prototype.getDropLocation = function (event) {
const ruleOrder = this.ruleOrder;
const rulesById = this.rulesById;
const draggingId = this.draggingId;
let offset;
let y;
let height;
const dropY = event.pageY;
let target = '';
/**
* Sets the image for the dragged element to the given DOM element
* @param {Element} image The HTML element to set as the drap image
*/
WidgetDnD.prototype.setDragImage = function (image) {
this.image.html(image);
};
/**
* Calculate where this rule has been dragged relative to the other rules
* @param {Event} event The mousemove or mouseup event that triggered this
event handler
* @return {string} The ID of the rule whose drag indicator should be displayed
*/
WidgetDnD.prototype.getDropLocation = function (event) {
const ruleOrder = this.ruleOrder;
const rulesById = this.rulesById;
const draggingId = this.draggingId;
let offset;
let y;
let height;
const dropY = event.pageY;
let target = '';
ruleOrder.forEach(function (ruleId, index) {
const ruleDOM = rulesById[ruleId].getDOM();
offset = window.innerWidth - (ruleDOM.offsetLeft + ruleDOM.offsetWidth);
y = offset.top;
height = offset.height;
if (index === 0) {
if (dropY < y + (7 * height) / 3) {
target = ruleId;
}
} else if (index === ruleOrder.length - 1 && ruleId !== draggingId) {
if (y + height / 3 < dropY) {
target = ruleId;
}
} else {
if (y + height / 3 < dropY && dropY < y + (7 * height) / 3) {
target = ruleId;
}
ruleOrder.forEach(function (ruleId, index) {
const ruleDOM = rulesById[ruleId].getDOM();
offset = window.innerWidth - (ruleDOM.offsetLeft + ruleDOM.offsetWidth);
y = offset.top;
height = offset.height;
if (index === 0) {
if (dropY < y + (7 * height) / 3) {
target = ruleId;
}
});
} else if (index === ruleOrder.length - 1 && ruleId !== draggingId) {
if (y + height / 3 < dropY) {
target = ruleId;
}
} else {
if (y + height / 3 < dropY && dropY < y + (7 * height) / 3) {
target = ruleId;
}
}
});
return target;
};
return target;
};
/**
* Called by a {Rule} instance that initiates a drag gesture
* @param {string} ruleId The identifier of the rule which is being dragged
*/
WidgetDnD.prototype.dragStart = function (ruleId) {
const ruleOrder = this.ruleOrder;
this.draggingId = ruleId;
this.draggingRulePrevious = ruleOrder[ruleOrder.indexOf(ruleId) - 1];
this.rulesById[this.draggingRulePrevious].showDragIndicator();
this.imageContainer.show();
/**
* Called by a {Rule} instance that initiates a drag gesture
* @param {string} ruleId The identifier of the rule which is being dragged
*/
WidgetDnD.prototype.dragStart = function (ruleId) {
const ruleOrder = this.ruleOrder;
this.draggingId = ruleId;
this.draggingRulePrevious = ruleOrder[ruleOrder.indexOf(ruleId) - 1];
this.rulesById[this.draggingRulePrevious].showDragIndicator();
this.imageContainer.show();
this.imageContainer.offset({
top: event.pageY - this.image.height() / 2,
left: event.pageX - this.image.querySelector('.t-grippy').style.width
});
};
/**
* An event handler for a mousemove event, once a rule has begun a drag gesture
* @param {Event} event The mousemove event that triggered this callback
*/
WidgetDnD.prototype.drag = function (event) {
let dragTarget;
if (this.draggingId && this.draggingId !== '') {
event.preventDefault();
dragTarget = this.getDropLocation(event);
this.imageContainer.offset({
top: event.pageY - this.image.height() / 2,
left: event.pageX - this.image.querySelector('.t-grippy').style.width
});
};
/**
* An event handler for a mousemove event, once a rule has begun a drag gesture
* @param {Event} event The mousemove event that triggered this callback
*/
WidgetDnD.prototype.drag = function (event) {
let dragTarget;
if (this.draggingId && this.draggingId !== '') {
event.preventDefault();
dragTarget = this.getDropLocation(event);
this.imageContainer.offset({
top: event.pageY - this.image.height() / 2,
left: event.pageX - this.image.querySelector('.t-grippy').style.width
});
if (this.rulesById[dragTarget]) {
this.rulesById[dragTarget].showDragIndicator();
} else {
this.rulesById[this.draggingRulePrevious].showDragIndicator();
}
if (this.rulesById[dragTarget]) {
this.rulesById[dragTarget].showDragIndicator();
} else {
this.rulesById[this.draggingRulePrevious].showDragIndicator();
}
};
}
};
/**
* Handles the mouseup event that corresponds to the user dropping the rule
* in its final location. Invokes any registered drop callbacks with the dragged
* rule's ID and the ID of the target rule that the dragged rule should be
* inserted after
* @param {Event} event The mouseup event that triggered this callback
*/
WidgetDnD.prototype.drop = function (event) {
let dropTarget = this.getDropLocation(event);
const draggingId = this.draggingId;
/**
* Handles the mouseup event that corresponds to the user dropping the rule
* in its final location. Invokes any registered drop callbacks with the dragged
* rule's ID and the ID of the target rule that the dragged rule should be
* inserted after
* @param {Event} event The mouseup event that triggered this callback
*/
WidgetDnD.prototype.drop = function (event) {
let dropTarget = this.getDropLocation(event);
const draggingId = this.draggingId;
if (this.draggingId && this.draggingId !== '') {
if (!this.rulesById[dropTarget]) {
dropTarget = this.draggingId;
}
this.eventEmitter.emit('drop', {
draggingId: draggingId,
dropTarget: dropTarget
});
this.draggingId = '';
this.draggingRulePrevious = '';
this.imageContainer.hide();
if (this.draggingId && this.draggingId !== '') {
if (!this.rulesById[dropTarget]) {
dropTarget = this.draggingId;
}
};
return WidgetDnD;
});
this.eventEmitter.emit('drop', {
draggingId: draggingId,
dropTarget: dropTarget
});
this.draggingId = '';
this.draggingRulePrevious = '';
this.imageContainer.hide();
}
};

View File

@ -20,80 +20,78 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
const helperFunctions = {
listenTo: function (object, event, callback, context) {
if (!this._listeningTo) {
this._listeningTo = [];
}
const listener = {
object: object,
event: event,
callback: callback,
context: context,
_cb: context ? callback.bind(context) : callback
};
if (object.$watch && event.indexOf('change:') === 0) {
const scopePath = event.replace('change:', '');
listener.unlisten = object.$watch(scopePath, listener._cb, true);
} else if (object.$on) {
listener.unlisten = object.$on(event, listener._cb);
} else if (object.addEventListener) {
object.addEventListener(event, listener._cb);
} else {
object.on(event, listener._cb);
}
this._listeningTo.push(listener);
},
stopListening: function (object, event, callback, context) {
if (!this._listeningTo) {
this._listeningTo = [];
}
this._listeningTo
.filter(function (listener) {
if (object && object !== listener.object) {
return false;
}
if (event && event !== listener.event) {
return false;
}
if (callback && callback !== listener.callback) {
return false;
}
if (context && context !== listener.context) {
return false;
}
return true;
})
.map(function (listener) {
if (listener.unlisten) {
listener.unlisten();
} else if (listener.object.removeEventListener) {
listener.object.removeEventListener(listener.event, listener._cb);
} else {
listener.object.off(listener.event, listener._cb);
}
return listener;
})
.forEach(function (listener) {
this._listeningTo.splice(this._listeningTo.indexOf(listener), 1);
}, this);
},
extend: function (object) {
object.listenTo = helperFunctions.listenTo;
object.stopListening = helperFunctions.stopListening;
const helperFunctions = {
listenTo(object, event, callback, context) {
if (!this._listeningTo) {
this._listeningTo = [];
}
};
return helperFunctions;
});
const listener = {
object: object,
event: event,
callback: callback,
context: context,
_cb: context ? callback.bind(context) : callback
};
if (object.$watch && event.indexOf('change:') === 0) {
const scopePath = event.replace('change:', '');
listener.unlisten = object.$watch(scopePath, listener._cb, true);
} else if (object.$on) {
listener.unlisten = object.$on(event, listener._cb);
} else if (object.addEventListener) {
object.addEventListener(event, listener._cb);
} else {
object.on(event, listener._cb);
}
this._listeningTo.push(listener);
},
stopListening(object, event, callback, context) {
if (!this._listeningTo) {
this._listeningTo = [];
}
this._listeningTo
.filter(function (listener) {
if (object && object !== listener.object) {
return false;
}
if (event && event !== listener.event) {
return false;
}
if (callback && callback !== listener.callback) {
return false;
}
if (context && context !== listener.context) {
return false;
}
return true;
})
.map(function (listener) {
if (listener.unlisten) {
listener.unlisten();
} else if (listener.object.removeEventListener) {
listener.object.removeEventListener(listener.event, listener._cb);
} else {
listener.object.off(listener.event, listener._cb);
}
return listener;
})
.forEach(function (listener) {
this._listeningTo.splice(this._listeningTo.indexOf(listener), 1);
}, this);
},
extend: function (object) {
object.listenTo = helperFunctions.listenTo;
object.stopListening = helperFunctions.stopListening;
}
};
export default helperFunctions;

View File

@ -1,128 +1,126 @@
define(['./Palette'], function (Palette) {
//The colors that will be used to instantiate this palette if none are provided
const DEFAULT_COLORS = [
'#000000',
'#434343',
'#666666',
'#999999',
'#b7b7b7',
'#cccccc',
'#d9d9d9',
'#efefef',
'#f3f3f3',
'#ffffff',
'#980000',
'#ff0000',
'#ff9900',
'#ffff00',
'#00ff00',
'#00ffff',
'#4a86e8',
'#0000ff',
'#9900ff',
'#ff00ff',
'#e6b8af',
'#f4cccc',
'#fce5cd',
'#fff2cc',
'#d9ead3',
'#d0e0e3',
'#c9daf8',
'#cfe2f3',
'#d9d2e9',
'#ead1dc',
'#dd7e6b',
'#dd7e6b',
'#f9cb9c',
'#ffe599',
'#b6d7a8',
'#a2c4c9',
'#a4c2f4',
'#9fc5e8',
'#b4a7d6',
'#d5a6bd',
'#cc4125',
'#e06666',
'#f6b26b',
'#ffd966',
'#93c47d',
'#76a5af',
'#6d9eeb',
'#6fa8dc',
'#8e7cc3',
'#c27ba0',
'#a61c00',
'#cc0000',
'#e69138',
'#f1c232',
'#6aa84f',
'#45818e',
'#3c78d8',
'#3d85c6',
'#674ea7',
'#a64d79',
'#85200c',
'#990000',
'#b45f06',
'#bf9000',
'#38761d',
'#134f5c',
'#1155cc',
'#0b5394',
'#351c75',
'#741b47',
'#5b0f00',
'#660000',
'#783f04',
'#7f6000',
'#274e13',
'#0c343d',
'#1c4587',
'#073763',
'#20124d',
'#4c1130'
];
import Palette from './Palette';
// The colors that will be used to instantiate this palette if none are provided
const DEFAULT_COLORS = [
'#000000',
'#434343',
'#666666',
'#999999',
'#b7b7b7',
'#cccccc',
'#d9d9d9',
'#efefef',
'#f3f3f3',
'#ffffff',
'#980000',
'#ff0000',
'#ff9900',
'#ffff00',
'#00ff00',
'#00ffff',
'#4a86e8',
'#0000ff',
'#9900ff',
'#ff00ff',
'#e6b8af',
'#f4cccc',
'#fce5cd',
'#fff2cc',
'#d9ead3',
'#d0e0e3',
'#c9daf8',
'#cfe2f3',
'#d9d2e9',
'#ead1dc',
'#dd7e6b',
'#dd7e6b',
'#f9cb9c',
'#ffe599',
'#b6d7a8',
'#a2c4c9',
'#a4c2f4',
'#9fc5e8',
'#b4a7d6',
'#d5a6bd',
'#cc4125',
'#e06666',
'#f6b26b',
'#ffd966',
'#93c47d',
'#76a5af',
'#6d9eeb',
'#6fa8dc',
'#8e7cc3',
'#c27ba0',
'#a61c00',
'#cc0000',
'#e69138',
'#f1c232',
'#6aa84f',
'#45818e',
'#3c78d8',
'#3d85c6',
'#674ea7',
'#a64d79',
'#85200c',
'#990000',
'#b45f06',
'#bf9000',
'#38761d',
'#134f5c',
'#1155cc',
'#0b5394',
'#351c75',
'#741b47',
'#5b0f00',
'#660000',
'#783f04',
'#7f6000',
'#274e13',
'#0c343d',
'#1c4587',
'#073763',
'#20124d',
'#4c1130'
];
/**
* Instantiates a new Open MCT Color Palette input
* @constructor
* @param {string} cssClass The class name of the icon which should be applied
* to this palette
* @param {Element} container The view that contains this palette
* @param {string[]} colors (optional) A list of colors that should be used to instantiate this palette
*/
export default function ColorPalette(cssClass, container, colors) {
this.colors = colors || DEFAULT_COLORS;
this.palette = new Palette(cssClass, container, this.colors);
this.palette.setNullOption('rgba(0,0,0,0)');
const domElement = this.palette.getDOM();
const self = this;
domElement.querySelector('.c-button--menu').classList.add('c-button--swatched');
domElement.querySelector('.t-swatch').classList.add('color-swatch');
domElement.querySelector('.c-palette').classList.add('c-palette--color');
domElement.querySelectorAll('.c-palette__item').forEach((item) => {
// eslint-disable-next-line no-invalid-this
item.style.backgroundColor = item.dataset.item;
});
/**
* Instantiates a new Open MCT Color Palette input
* @constructor
* @param {string} cssClass The class name of the icon which should be applied
* to this palette
* @param {Element} container The view that contains this palette
* @param {string[]} colors (optional) A list of colors that should be used to instantiate this palette
* Update this palette's current selection indicator with the style
* of the currently selected item
* @private
*/
function ColorPalette(cssClass, container, colors) {
this.colors = colors || DEFAULT_COLORS;
this.palette = new Palette(cssClass, container, this.colors);
this.palette.setNullOption('rgba(0,0,0,0)');
const domElement = this.palette.getDOM();
const self = this;
domElement.querySelector('.c-button--menu').classList.add('c-button--swatched');
domElement.querySelector('.t-swatch').classList.add('color-swatch');
domElement.querySelector('.c-palette').classList.add('c-palette--color');
domElement.querySelectorAll('.c-palette__item').forEach((item) => {
// eslint-disable-next-line no-invalid-this
item.style.backgroundColor = item.dataset.item;
});
/**
* Update this palette's current selection indicator with the style
* of the currently selected item
* @private
*/
function updateSwatch() {
const color = self.palette.getCurrent();
domElement.querySelector('.color-swatch').style.backgroundColor = color;
}
this.palette.on('change', updateSwatch);
return this.palette;
function updateSwatch() {
const color = self.palette.getCurrent();
domElement.querySelector('.color-swatch').style.backgroundColor = color;
}
return ColorPalette;
});
this.palette.on('change', updateSwatch);
return this.palette;
}

View File

@ -1,77 +1,75 @@
define(['./Palette'], function (Palette) {
//The icons that will be used to instantiate this palette if none are provided
const DEFAULT_ICONS = [
'icon-alert-rect',
'icon-alert-triangle',
'icon-arrow-down',
'icon-arrow-left',
'icon-arrow-right',
'icon-arrow-double-up',
'icon-arrow-tall-up',
'icon-arrow-tall-down',
'icon-arrow-double-down',
'icon-arrow-up',
'icon-asterisk',
'icon-bell',
'icon-check',
'icon-eye-open',
'icon-gear',
'icon-hourglass',
'icon-info',
'icon-link',
'icon-lock',
'icon-people',
'icon-person',
'icon-plus',
'icon-trash',
'icon-x'
];
import Palette from './Palette';
//The icons that will be used to instantiate this palette if none are provided
const DEFAULT_ICONS = [
'icon-alert-rect',
'icon-alert-triangle',
'icon-arrow-down',
'icon-arrow-left',
'icon-arrow-right',
'icon-arrow-double-up',
'icon-arrow-tall-up',
'icon-arrow-tall-down',
'icon-arrow-double-down',
'icon-arrow-up',
'icon-asterisk',
'icon-bell',
'icon-check',
'icon-eye-open',
'icon-gear',
'icon-hourglass',
'icon-info',
'icon-link',
'icon-lock',
'icon-people',
'icon-person',
'icon-plus',
'icon-trash',
'icon-x'
];
/**
* Instantiates a new Open MCT Icon Palette input
* @constructor
* @param {string} cssClass The class name of the icon which should be applied
* to this palette
* @param {Element} container The view that contains this palette
* @param {string[]} icons (optional) A list of icons that should be used to instantiate this palette
*/
export default function IconPalette(cssClass, container, icons) {
this.icons = icons || DEFAULT_ICONS;
this.palette = new Palette(cssClass, container, this.icons);
this.palette.setNullOption('');
this.oldIcon = this.palette.current || '';
const domElement = this.palette.getDOM();
const self = this;
domElement.querySelector('.c-button--menu').classList.add('c-button--swatched');
domElement.querySelector('.t-swatch').classList.add('icon-swatch');
domElement.querySelector('.c-palette').classList.add('c-palette--icon');
domElement.querySelectorAll('.c-palette-item').forEach((item) => {
// eslint-disable-next-line no-invalid-this
item.classList.add(item.dataset.item);
});
/**
* Instantiates a new Open MCT Icon Palette input
* @constructor
* @param {string} cssClass The class name of the icon which should be applied
* to this palette
* @param {Element} container The view that contains this palette
* @param {string[]} icons (optional) A list of icons that should be used to instantiate this palette
* Update this palette's current selection indicator with the style
* of the currently selected item
* @private
*/
function IconPalette(cssClass, container, icons) {
this.icons = icons || DEFAULT_ICONS;
this.palette = new Palette(cssClass, container, this.icons);
this.palette.setNullOption('');
this.oldIcon = this.palette.current || '';
const domElement = this.palette.getDOM();
const self = this;
domElement.querySelector('.c-button--menu').classList.add('c-button--swatched');
domElement.querySelector('.t-swatch').classList.add('icon-swatch');
domElement.querySelector('.c-palette').classList.add('c-palette--icon');
domElement.querySelectorAll('.c-palette-item').forEach((item) => {
// eslint-disable-next-line no-invalid-this
item.classList.add(item.dataset.item);
});
/**
* Update this palette's current selection indicator with the style
* of the currently selected item
* @private
*/
function updateSwatch() {
if (self.oldIcon) {
domElement.querySelector('.icon-swatch').classList.remove(self.oldIcon);
}
domElement.querySelector('.icon-swatch').classList.add(self.palette.getCurrent());
self.oldIcon = self.palette.getCurrent();
function updateSwatch() {
if (self.oldIcon) {
domElement.querySelector('.icon-swatch').classList.remove(self.oldIcon);
}
this.palette.on('change', updateSwatch);
return this.palette;
domElement.querySelector('.icon-swatch').classList.add(self.palette.getCurrent());
self.oldIcon = self.palette.getCurrent();
}
return IconPalette;
});
this.palette.on('change', updateSwatch);
return this.palette;
}

View File

@ -1,95 +1,93 @@
define(['./Select'], function (Select) {
/**
* Create a {Select} element whose composition is dynamically updated with
* the telemetry fields of a particular domain object
* @constructor
* @param {Object} config The current state of this select. Must have object
* and key fields
* @param {ObjectSelect} objectSelect The linked ObjectSelect instance to which
* this KeySelect should listen to for change
* events
* @param {ConditionManager} manager A ConditionManager instance from which
* to receive telemetry metadata
* @param {function} changeCallback A change event callback to register with this
* select on initialization
*/
const NULLVALUE = '- Select Field -';
import Select from './Select';
function KeySelect(config, objectSelect, manager, changeCallback) {
const self = this;
/**
* Create a {Select} element whose composition is dynamically updated with
* the telemetry fields of a particular domain object
* @constructor
* @param {Object} config The current state of this select. Must have object
* and key fields
* @param {ObjectSelect} objectSelect The linked ObjectSelect instance to which
* this KeySelect should listen to for change
* events
* @param {ConditionManager} manager A ConditionManager instance from which
* to receive telemetry metadata
* @param {function} changeCallback A change event callback to register with this
* select on initialization
*/
const NULLVALUE = '- Select Field -';
this.config = config;
this.objectSelect = objectSelect;
this.manager = manager;
export default function KeySelect(config, objectSelect, manager, changeCallback) {
const self = this;
this.select = new Select();
this.select.hide();
this.select.addOption('', NULLVALUE);
if (changeCallback) {
this.select.on('change', changeCallback);
}
this.config = config;
this.objectSelect = objectSelect;
this.manager = manager;
/**
* Change event handler for the {ObjectSelect} to which this KeySelect instance
* is linked. Loads the new object's metadata and updates its select element's
* composition.
* @param {Object} key The key identifying the newly selected domain object
* @private
*/
function onObjectChange(key) {
const selected = self.manager.metadataLoadCompleted()
? self.select.getSelected()
: self.config.key;
self.telemetryMetadata = self.manager.getTelemetryMetadata(key) || {};
self.generateOptions();
self.select.setSelected(selected);
}
/**
* Event handler for the initial metadata load event from the associated
* ConditionManager. Retrieves metadata from the manager and populates
* the select element.
* @private
*/
function onMetadataLoad() {
if (self.manager.getTelemetryMetadata(self.config.object)) {
self.telemetryMetadata = self.manager.getTelemetryMetadata(self.config.object);
self.generateOptions();
}
self.select.setSelected(self.config.key);
}
if (self.manager.metadataLoadCompleted()) {
onMetadataLoad();
}
this.objectSelect.on('change', onObjectChange, this);
this.manager.on('metadata', onMetadataLoad);
return this.select;
this.select = new Select();
this.select.hide();
this.select.addOption('', NULLVALUE);
if (changeCallback) {
this.select.on('change', changeCallback);
}
/**
* Populate this select with options based on its current composition
* Change event handler for the {ObjectSelect} to which this KeySelect instance
* is linked. Loads the new object's metadata and updates its select element's
* composition.
* @param {Object} key The key identifying the newly selected domain object
* @private
*/
KeySelect.prototype.generateOptions = function () {
const items = Object.entries(this.telemetryMetadata).map(function (metaDatum) {
return [metaDatum[0], metaDatum[1].name];
});
items.splice(0, 0, ['', NULLVALUE]);
this.select.setOptions(items);
function onObjectChange(key) {
const selected = self.manager.metadataLoadCompleted()
? self.select.getSelected()
: self.config.key;
self.telemetryMetadata = self.manager.getTelemetryMetadata(key) || {};
self.generateOptions();
self.select.setSelected(selected);
}
if (this.select.options.length < 2) {
this.select.hide();
} else if (this.select.options.length > 1) {
this.select.show();
/**
* Event handler for the initial metadata load event from the associated
* ConditionManager. Retrieves metadata from the manager and populates
* the select element.
* @private
*/
function onMetadataLoad() {
if (self.manager.getTelemetryMetadata(self.config.object)) {
self.telemetryMetadata = self.manager.getTelemetryMetadata(self.config.object);
self.generateOptions();
}
};
KeySelect.prototype.destroy = function () {
this.objectSelect.destroy();
};
self.select.setSelected(self.config.key);
}
return KeySelect;
});
if (self.manager.metadataLoadCompleted()) {
onMetadataLoad();
}
this.objectSelect.on('change', onObjectChange, this);
this.manager.on('metadata', onMetadataLoad);
return this.select;
}
/**
* Populate this select with options based on its current composition
*/
KeySelect.prototype.generateOptions = function () {
const items = Object.entries(this.telemetryMetadata).map(function (metaDatum) {
return [metaDatum[0], metaDatum[1].name];
});
items.splice(0, 0, ['', NULLVALUE]);
this.select.setOptions(items);
if (this.select.options.length < 2) {
this.select.hide();
} else if (this.select.options.length > 1) {
this.select.show();
}
};
KeySelect.prototype.destroy = function () {
this.objectSelect.destroy();
};

View File

@ -1,86 +1,86 @@
define(['./Select', 'objectUtils'], function (Select, objectUtils) {
import objectUtils from 'objectUtils';
import Select from './Select';
/**
* Create a {Select} element whose composition is dynamically updated with
* the current composition of the Summary Widget
* @constructor
* @param {Object} config The current state of this select. Must have an
* object field
* @param {ConditionManager} manager A ConditionManager instance from which
* to receive the current composition status
* @param {string[][]} baseOptions A set of [value, label] keyword pairs to
* display regardless of the composition state
*/
export default function ObjectSelect(config, manager, baseOptions) {
const self = this;
this.config = config;
this.manager = manager;
this.select = new Select();
this.baseOptions = [['', '- Select Telemetry -']];
if (baseOptions) {
this.baseOptions = this.baseOptions.concat(baseOptions);
}
this.baseOptions.forEach(function (option) {
self.select.addOption(option[0], option[1]);
});
this.compositionObjs = this.manager.getComposition();
self.generateOptions();
/**
* Create a {Select} element whose composition is dynamically updated with
* the current composition of the Summary Widget
* @constructor
* @param {Object} config The current state of this select. Must have an
* object field
* @param {ConditionManager} manager A ConditionManager instance from which
* to receive the current composition status
* @param {string[][]} baseOptions A set of [value, label] keyword pairs to
* display regardless of the composition state
* Add a new composition object to this select when a composition added
* is detected on the Summary Widget
* @param {Object} obj The newly added domain object
* @private
*/
function ObjectSelect(config, manager, baseOptions) {
const self = this;
this.config = config;
this.manager = manager;
this.select = new Select();
this.baseOptions = [['', '- Select Telemetry -']];
if (baseOptions) {
this.baseOptions = this.baseOptions.concat(baseOptions);
}
this.baseOptions.forEach(function (option) {
self.select.addOption(option[0], option[1]);
});
this.compositionObjs = this.manager.getComposition();
self.generateOptions();
/**
* Add a new composition object to this select when a composition added
* is detected on the Summary Widget
* @param {Object} obj The newly added domain object
* @private
*/
function onCompositionAdd(obj) {
self.select.addOption(objectUtils.makeKeyString(obj.identifier), obj.name);
}
/**
* Refresh the composition of this select when a domain object is removed
* from the Summary Widget's composition
* @private
*/
function onCompositionRemove() {
const selected = self.select.getSelected();
self.generateOptions();
self.select.setSelected(selected);
}
/**
* Defer setting the selected state on initial load until load is complete
* @private
*/
function onCompositionLoad() {
self.select.setSelected(self.config.object);
}
this.manager.on('add', onCompositionAdd);
this.manager.on('remove', onCompositionRemove);
this.manager.on('load', onCompositionLoad);
if (this.manager.loadCompleted()) {
onCompositionLoad();
}
return this.select;
function onCompositionAdd(obj) {
self.select.addOption(objectUtils.makeKeyString(obj.identifier), obj.name);
}
/**
* Populate this select with options based on its current composition
* Refresh the composition of this select when a domain object is removed
* from the Summary Widget's composition
* @private
*/
ObjectSelect.prototype.generateOptions = function () {
const items = Object.values(this.compositionObjs).map(function (obj) {
return [objectUtils.makeKeyString(obj.identifier), obj.name];
});
this.baseOptions.forEach(function (option, index) {
items.splice(index, 0, option);
});
this.select.setOptions(items);
};
function onCompositionRemove() {
const selected = self.select.getSelected();
self.generateOptions();
self.select.setSelected(selected);
}
return ObjectSelect;
});
/**
* Defer setting the selected state on initial load until load is complete
* @private
*/
function onCompositionLoad() {
self.select.setSelected(self.config.object);
}
this.manager.on('add', onCompositionAdd);
this.manager.on('remove', onCompositionRemove);
this.manager.on('load', onCompositionLoad);
if (this.manager.loadCompleted()) {
onCompositionLoad();
}
return this.select;
}
/**
* Populate this select with options based on its current composition
*/
ObjectSelect.prototype.generateOptions = function () {
const items = Object.values(this.compositionObjs).map(function (obj) {
return [objectUtils.makeKeyString(obj.identifier), obj.name];
});
this.baseOptions.forEach(function (option, index) {
items.splice(index, 0, option);
});
this.select.setOptions(items);
};

View File

@ -1,120 +1,119 @@
define(['./Select', '../eventHelpers'], function (Select, eventHelpers) {
/**
* Create a {Select} element whose composition is dynamically updated with
* the operations applying to a particular telemetry property
* @constructor
* @param {Object} config The current state of this select. Must have object,
* key, and operation fields
* @param {KeySelect} keySelect The linked Key Select instance to which
* this OperationSelect should listen to for change
* events
* @param {ConditionManager} manager A ConditionManager instance from which
* to receive telemetry metadata
* @param {function} changeCallback A change event callback to register with this
* select on initialization
*/
const NULLVALUE = '- Select Comparison -';
import eventHelpers from '../eventHelpers';
import Select from './Select';
function OperationSelect(config, keySelect, manager, changeCallback) {
eventHelpers.extend(this);
const self = this;
/**
* Create a {Select} element whose composition is dynamically updated with
* the operations applying to a particular telemetry property
* @constructor
* @param {Object} config The current state of this select. Must have object,
* key, and operation fields
* @param {KeySelect} keySelect The linked Key Select instance to which
* this OperationSelect should listen to for change
* events
* @param {ConditionManager} manager A ConditionManager instance from which
* to receive telemetry metadata
* @param {function} changeCallback A change event callback to register with this
* select on initialization
*/
const NULLVALUE = '- Select Comparison -';
this.config = config;
this.keySelect = keySelect;
this.manager = manager;
export default function OperationSelect(config, keySelect, manager, changeCallback) {
eventHelpers.extend(this);
const self = this;
this.operationKeys = [];
this.evaluator = this.manager.getEvaluator();
this.loadComplete = false;
this.config = config;
this.keySelect = keySelect;
this.manager = manager;
this.select = new Select();
this.select.hide();
this.select.addOption('', NULLVALUE);
if (changeCallback) {
this.listenTo(this.select, 'change', changeCallback);
}
this.operationKeys = [];
this.evaluator = this.manager.getEvaluator();
this.loadComplete = false;
/**
* Change event handler for the {KeySelect} to which this OperationSelect instance
* is linked. Loads the operations applicable to the given telemetry property and updates
* its select element's composition
* @param {Object} key The key identifying the newly selected property
* @private
*/
function onKeyChange(key) {
const selected = self.config.operation;
if (self.manager.metadataLoadCompleted()) {
self.loadOptions(key);
self.generateOptions();
self.select.setSelected(selected);
}
}
/**
* Event handler for the initial metadata load event from the associated
* ConditionManager. Retrieves telemetry property types and updates the
* select
* @private
*/
function onMetadataLoad() {
if (self.manager.getTelemetryPropertyType(self.config.object, self.config.key)) {
self.loadOptions(self.config.key);
self.generateOptions();
}
self.select.setSelected(self.config.operation);
}
this.keySelect.on('change', onKeyChange);
this.manager.on('metadata', onMetadataLoad);
if (this.manager.metadataLoadCompleted()) {
onMetadataLoad();
}
return this.select;
this.select = new Select();
this.select.hide();
this.select.addOption('', NULLVALUE);
if (changeCallback) {
this.listenTo(this.select, 'change', changeCallback);
}
/**
* Populate this select with options based on its current composition
* Change event handler for the {KeySelect} to which this OperationSelect instance
* is linked. Loads the operations applicable to the given telemetry property and updates
* its select element's composition
* @param {Object} key The key identifying the newly selected property
* @private
*/
OperationSelect.prototype.generateOptions = function () {
const self = this;
const items = this.operationKeys.map(function (operation) {
return [operation, self.evaluator.getOperationText(operation)];
});
items.splice(0, 0, ['', NULLVALUE]);
this.select.setOptions(items);
if (this.select.options.length < 2) {
this.select.hide();
} else {
this.select.show();
function onKeyChange(key) {
const selected = self.config.operation;
if (self.manager.metadataLoadCompleted()) {
self.loadOptions(key);
self.generateOptions();
self.select.setSelected(selected);
}
};
}
/**
* Retrieve the data type associated with a given telemetry property and
* the applicable operations from the {ConditionEvaluator}
* @param {string} key The telemetry property to load operations for
* Event handler for the initial metadata load event from the associated
* ConditionManager. Retrieves telemetry property types and updates the
* select
* @private
*/
OperationSelect.prototype.loadOptions = function (key) {
const self = this;
const operations = self.evaluator.getOperationKeys();
let type;
type = self.manager.getTelemetryPropertyType(self.config.object, key);
if (type !== undefined) {
self.operationKeys = operations.filter(function (operation) {
return self.evaluator.operationAppliesTo(operation, type);
});
function onMetadataLoad() {
if (self.manager.getTelemetryPropertyType(self.config.object, self.config.key)) {
self.loadOptions(self.config.key);
self.generateOptions();
}
};
OperationSelect.prototype.destroy = function () {
this.stopListening();
};
self.select.setSelected(self.config.operation);
}
return OperationSelect;
});
this.keySelect.on('change', onKeyChange);
this.manager.on('metadata', onMetadataLoad);
if (this.manager.metadataLoadCompleted()) {
onMetadataLoad();
}
return this.select;
}
/**
* Populate this select with options based on its current composition
*/
OperationSelect.prototype.generateOptions = function () {
const self = this;
const items = this.operationKeys.map(function (operation) {
return [operation, self.evaluator.getOperationText(operation)];
});
items.splice(0, 0, ['', NULLVALUE]);
this.select.setOptions(items);
if (this.select.options.length < 2) {
this.select.hide();
} else {
this.select.show();
}
};
/**
* Retrieve the data type associated with a given telemetry property and
* the applicable operations from the {ConditionEvaluator}
* @param {string} key The telemetry property to load operations for
*/
OperationSelect.prototype.loadOptions = function (key) {
const self = this;
const operations = self.evaluator.getOperationKeys();
let type;
type = self.manager.getTelemetryPropertyType(self.config.object, key);
if (type !== undefined) {
self.operationKeys = operations.filter(function (operation) {
return self.evaluator.operationAppliesTo(operation, type);
});
}
};
OperationSelect.prototype.destroy = function () {
this.stopListening();
};

View File

@ -1,184 +1,181 @@
define([
'../eventHelpers',
'../../res/input/paletteTemplate.html',
'../../../../utils/template/templateHelpers',
'EventEmitter'
], function (eventHelpers, paletteTemplate, templateHelpers, EventEmitter) {
/**
* Instantiates a new Open MCT Color Palette input
* @constructor
* @param {string} cssClass The class name of the icon which should be applied
* to this palette
* @param {Element} container The view that contains this palette
* @param {string[]} items A list of data items that will be associated with each
* palette item in the view; how this data is represented is
* up to the descendent class
*/
function Palette(cssClass, container, items) {
eventHelpers.extend(this);
import EventEmitter from 'EventEmitter';
const self = this;
import * as templateHelpers from '../../../../utils/template/templateHelpers';
import paletteTemplate from '../../res/input/paletteTemplate.html';
import eventHelpers from '../eventHelpers';
this.cssClass = cssClass;
this.items = items;
this.container = container;
/**
* Instantiates a new Open MCT Color Palette input
* @constructor
* @param {string} cssClass The class name of the icon which should be applied
* to this palette
* @param {Element} container The view that contains this palette
* @param {string[]} items A list of data items that will be associated with each
* palette item in the view; how this data is represented is
* up to the descendent class
*/
export default function Palette(cssClass, container, items) {
eventHelpers.extend(this);
this.domElement = templateHelpers.convertTemplateToHTML(paletteTemplate)[0];
const self = this;
this.itemElements = {
nullOption: this.domElement.querySelector('.c-palette__item-none .c-palette__item')
};
this.eventEmitter = new EventEmitter();
this.supportedCallbacks = ['change'];
this.value = this.items[0];
this.nullOption = ' ';
this.button = this.domElement.querySelector('.js-button');
this.menu = this.domElement.querySelector('.c-menu');
this.cssClass = cssClass;
this.items = items;
this.container = container;
this.hideMenu = this.hideMenu.bind(this);
this.domElement = templateHelpers.convertTemplateToHTML(paletteTemplate)[0];
if (this.cssClass) {
self.button.classList.add(this.cssClass);
}
this.itemElements = {
nullOption: this.domElement.querySelector('.c-palette__item-none .c-palette__item')
};
this.eventEmitter = new EventEmitter();
this.supportedCallbacks = ['change'];
this.value = this.items[0];
this.nullOption = ' ';
this.button = this.domElement.querySelector('.js-button');
this.menu = this.domElement.querySelector('.c-menu');
self.setNullOption(this.nullOption);
this.hideMenu = this.hideMenu.bind(this);
self.items.forEach(function (item) {
const itemElement = document.createElement('div');
itemElement.className = 'c-palette__item ' + item;
itemElement.setAttribute('data-item', item);
self.itemElements[item] = itemElement;
self.domElement.querySelector('.c-palette__items').appendChild(itemElement);
});
self.domElement.querySelector('.c-menu').style.display = 'none';
this.listenTo(window.document, 'click', this.hideMenu);
this.listenTo(self.domElement.querySelector('.js-button'), 'click', function (event) {
event.stopPropagation();
self.container.querySelector('.c-menu').style.display = 'none';
self.domElement.querySelector('.c-menu').style.display = '';
});
/**
* Event handler for selection of an individual palette item. Sets the
* currently selected element to be the one associated with that item's data
* @param {Event} event the click event that initiated this callback
* @private
*/
function handleItemClick(event) {
const elem = event.currentTarget;
const item = elem.dataset.item;
self.set(item);
self.domElement.querySelector('.c-menu').style.display = 'none';
}
self.domElement.querySelectorAll('.c-palette__item').forEach((item) => {
this.listenTo(item, 'click', handleItemClick);
});
if (this.cssClass) {
self.button.classList.add(this.cssClass);
}
/**
* Get the DOM element representing this palette in the view
*/
Palette.prototype.getDOM = function () {
return this.domElement;
};
self.setNullOption(this.nullOption);
self.items.forEach(function (item) {
const itemElement = document.createElement('div');
itemElement.className = 'c-palette__item ' + item;
itemElement.setAttribute('data-item', item);
self.itemElements[item] = itemElement;
self.domElement.querySelector('.c-palette__items').appendChild(itemElement);
});
self.domElement.querySelector('.c-menu').style.display = 'none';
this.listenTo(window.document, 'click', this.hideMenu);
this.listenTo(self.domElement.querySelector('.js-button'), 'click', function (event) {
event.stopPropagation();
self.container.querySelector('.c-menu').style.display = 'none';
self.domElement.querySelector('.c-menu').style.display = '';
});
/**
* Clean up any event listeners registered to DOM elements external to the widget
* Event handler for selection of an individual palette item. Sets the
* currently selected element to be the one associated with that item's data
* @param {Event} event the click event that initiated this callback
* @private
*/
Palette.prototype.destroy = function () {
this.stopListening();
};
function handleItemClick(event) {
const elem = event.currentTarget;
const item = elem.dataset.item;
self.set(item);
self.domElement.querySelector('.c-menu').style.display = 'none';
}
Palette.prototype.hideMenu = function () {
this.domElement.querySelector('.c-menu').style.display = 'none';
};
self.domElement.querySelectorAll('.c-palette__item').forEach((item) => {
this.listenTo(item, 'click', handleItemClick);
});
}
/**
* Register a callback with this palette: supported callback is change
* @param {string} event The key for the event to listen to
* @param {function} callback The function that this rule will invoke on this event
* @param {Object} context A reference to a scope to use as the context for
* context for the callback function
*/
Palette.prototype.on = function (event, callback, context) {
if (this.supportedCallbacks.includes(event)) {
this.eventEmitter.on(event, callback, context || this);
/**
* Get the DOM element representing this palette in the view
*/
Palette.prototype.getDOM = function () {
return this.domElement;
};
/**
* Clean up any event listeners registered to DOM elements external to the widget
*/
Palette.prototype.destroy = function () {
this.stopListening();
};
Palette.prototype.hideMenu = function () {
this.domElement.querySelector('.c-menu').style.display = 'none';
};
/**
* Register a callback with this palette: supported callback is change
* @param {string} event The key for the event to listen to
* @param {function} callback The function that this rule will invoke on this event
* @param {Object} context A reference to a scope to use as the context for
* context for the callback function
*/
Palette.prototype.on = function (event, callback, context) {
if (this.supportedCallbacks.includes(event)) {
this.eventEmitter.on(event, callback, context || this);
} else {
throw new Error('Unsupported event type: ' + event);
}
};
/**
* Get the currently selected value of this palette
* @return {string} The selected value
*/
Palette.prototype.getCurrent = function () {
return this.value;
};
/**
* Set the selected value of this palette; if the item doesn't exist in the
* palette's data model, the selected value will not change. Invokes any
* change callbacks associated with this palette.
* @param {string} item The key of the item to set as selected
*/
Palette.prototype.set = function (item) {
const self = this;
if (this.items.includes(item) || item === this.nullOption) {
this.value = item;
if (item === this.nullOption) {
this.updateSelected('nullOption');
} else {
throw new Error('Unsupported event type: ' + event);
this.updateSelected(item);
}
};
}
/**
* Get the currently selected value of this palette
* @return {string} The selected value
*/
Palette.prototype.getCurrent = function () {
return this.value;
};
this.eventEmitter.emit('change', self.value);
};
/**
* Set the selected value of this palette; if the item doesn't exist in the
* palette's data model, the selected value will not change. Invokes any
* change callbacks associated with this palette.
* @param {string} item The key of the item to set as selected
*/
Palette.prototype.set = function (item) {
const self = this;
if (this.items.includes(item) || item === this.nullOption) {
this.value = item;
if (item === this.nullOption) {
this.updateSelected('nullOption');
} else {
this.updateSelected(item);
}
/**
* Update the view associated with the currently selected item
*/
Palette.prototype.updateSelected = function (item) {
this.domElement.querySelectorAll('.c-palette__item').forEach((paletteItem) => {
if (paletteItem.classList.contains('is-selected')) {
paletteItem.classList.remove('is-selected');
}
});
this.itemElements[item].classList.add('is-selected');
if (item === 'nullOption') {
this.domElement.querySelector('.t-swatch').classList.add('no-selection');
} else {
this.domElement.querySelector('.t-swatch').classList.remove('no-selection');
}
};
this.eventEmitter.emit('change', self.value);
};
/**
* set the property to be used for the 'no selection' item. If not set, this
* defaults to a single space
* @param {string} item The key to use as the 'no selection' item
*/
Palette.prototype.setNullOption = function (item) {
this.nullOption = item;
this.itemElements.nullOption.data = { item: item };
};
/**
* Update the view associated with the currently selected item
*/
Palette.prototype.updateSelected = function (item) {
this.domElement.querySelectorAll('.c-palette__item').forEach((paletteItem) => {
if (paletteItem.classList.contains('is-selected')) {
paletteItem.classList.remove('is-selected');
}
});
this.itemElements[item].classList.add('is-selected');
if (item === 'nullOption') {
this.domElement.querySelector('.t-swatch').classList.add('no-selection');
} else {
this.domElement.querySelector('.t-swatch').classList.remove('no-selection');
}
};
/**
* Hides the 'no selection' option to be hidden in the view if it doesn't apply
*/
Palette.prototype.toggleNullOption = function () {
const elem = this.domElement.querySelector('.c-palette__item-none');
/**
* set the property to be used for the 'no selection' item. If not set, this
* defaults to a single space
* @param {string} item The key to use as the 'no selection' item
*/
Palette.prototype.setNullOption = function (item) {
this.nullOption = item;
this.itemElements.nullOption.data = { item: item };
};
/**
* Hides the 'no selection' option to be hidden in the view if it doesn't apply
*/
Palette.prototype.toggleNullOption = function () {
const elem = this.domElement.querySelector('.c-palette__item-none');
if (elem.style.display === 'none') {
this.domElement.querySelector('.c-palette__item-none').style.display = 'flex';
} else {
this.domElement.querySelector('.c-palette__item-none').style.display = 'none';
}
};
return Palette;
});
if (elem.style.display === 'none') {
this.domElement.querySelector('.c-palette__item-none').style.display = 'flex';
} else {
this.domElement.querySelector('.c-palette__item-none').style.display = 'none';
}
};

View File

@ -1,168 +1,165 @@
define([
'../eventHelpers',
'../../res/input/selectTemplate.html',
'../../../../utils/template/templateHelpers',
'EventEmitter'
], function (eventHelpers, selectTemplate, templateHelpers, EventEmitter) {
import EventEmitter from 'EventEmitter';
import * as templateHelpers from '../../../../utils/template/templateHelpers';
import selectTemplate from '../../res/input/selectTemplate.html';
import eventHelpers from '../eventHelpers';
/**
* Wraps an HTML select element, and provides methods for dynamically altering
* its composition from the data model
* @constructor
*/
export default function Select() {
eventHelpers.extend(this);
const self = this;
this.domElement = templateHelpers.convertTemplateToHTML(selectTemplate)[0];
this.options = [];
this.eventEmitter = new EventEmitter();
this.supportedCallbacks = ['change'];
this.populate();
/**
* Wraps an HTML select element, and provides methods for dynamically altering
* its composition from the data model
* @constructor
* Event handler for the wrapped select element. Also invokes any change
* callbacks registered with this select with the new value
* @param {Event} event The change event that triggered this callback
* @private
*/
function Select() {
eventHelpers.extend(this);
function onChange(event) {
const elem = event.target;
const value = self.options[elem.selectedIndex];
const self = this;
this.domElement = templateHelpers.convertTemplateToHTML(selectTemplate)[0];
this.options = [];
this.eventEmitter = new EventEmitter();
this.supportedCallbacks = ['change'];
this.populate();
/**
* Event handler for the wrapped select element. Also invokes any change
* callbacks registered with this select with the new value
* @param {Event} event The change event that triggered this callback
* @private
*/
function onChange(event) {
const elem = event.target;
const value = self.options[elem.selectedIndex];
self.eventEmitter.emit('change', value[0]);
}
this.listenTo(this.domElement.querySelector('select'), 'change', onChange, this);
self.eventEmitter.emit('change', value[0]);
}
/**
* Get the DOM element representing this Select in the view
* @return {Element}
*/
Select.prototype.getDOM = function () {
return this.domElement;
};
this.listenTo(this.domElement.querySelector('select'), 'change', onChange, this);
}
/**
* Register a callback with this select: supported callback is change
* @param {string} event The key for the event to listen to
* @param {function} callback The function that this rule will invoke on this event
* @param {Object} context A reference to a scope to use as the context for
* context for the callback function
*/
Select.prototype.on = function (event, callback, context) {
if (this.supportedCallbacks.includes(event)) {
this.eventEmitter.on(event, callback, context || this);
} else {
throw new Error('Unsupported event type' + event);
/**
* Get the DOM element representing this Select in the view
* @return {Element}
*/
Select.prototype.getDOM = function () {
return this.domElement;
};
/**
* Register a callback with this select: supported callback is change
* @param {string} event The key for the event to listen to
* @param {function} callback The function that this rule will invoke on this event
* @param {Object} context A reference to a scope to use as the context for
* context for the callback function
*/
Select.prototype.on = function (event, callback, context) {
if (this.supportedCallbacks.includes(event)) {
this.eventEmitter.on(event, callback, context || this);
} else {
throw new Error('Unsupported event type' + event);
}
};
/**
* Unregister a callback from this select.
* @param {string} event The key for the event to stop listening to
* @param {function} callback The function to unregister
* @param {Object} context A reference to a scope to use as the context for the callback function
*/
Select.prototype.off = function (event, callback, context) {
if (this.supportedCallbacks.includes(event)) {
this.eventEmitter.off(event, callback, context || this);
} else {
throw new Error('Unsupported event type: ' + event);
}
};
/**
* Update the select element in the view from the current state of the data
* model
*/
Select.prototype.populate = function () {
const self = this;
let selectedIndex = 0;
selectedIndex = this.domElement.querySelector('select').selectedIndex;
this.domElement.querySelector('select').innerHTML = '';
self.options.forEach(function (option) {
const optionElement = document.createElement('option');
optionElement.value = option[0];
optionElement.innerText = `+ ${option[1]}`;
self.domElement.querySelector('select').appendChild(optionElement);
});
this.domElement.querySelector('select').selectedIndex = selectedIndex;
};
/**
* Add a single option to this select
* @param {string} value The value for the new option
* @param {string} label The human-readable text for the new option
*/
Select.prototype.addOption = function (value, label) {
this.options.push([value, label]);
this.populate();
};
/**
* Set the available options for this select. Replaces any existing options
* @param {string[][]} options An array of [value, label] pairs to display
*/
Select.prototype.setOptions = function (options) {
this.options = options;
this.populate();
};
/**
* Sets the currently selected element an invokes any registered change
* callbacks with the new value. If the value doesn't exist in this select's
* model, its state will not change.
* @param {string} value The value to set as the selected option
*/
Select.prototype.setSelected = function (value) {
let selectedIndex = 0;
let selectedOption;
this.options.forEach(function (option, index) {
if (option[0] === value) {
selectedIndex = index;
}
};
});
this.domElement.querySelector('select').selectedIndex = selectedIndex;
/**
* Unregister a callback from this select.
* @param {string} event The key for the event to stop listening to
* @param {function} callback The function to unregister
* @param {Object} context A reference to a scope to use as the context for the callback function
*/
Select.prototype.off = function (event, callback, context) {
if (this.supportedCallbacks.includes(event)) {
this.eventEmitter.off(event, callback, context || this);
} else {
throw new Error('Unsupported event type: ' + event);
}
};
selectedOption = this.options[selectedIndex];
this.eventEmitter.emit('change', selectedOption[0]);
};
/**
* Update the select element in the view from the current state of the data
* model
*/
Select.prototype.populate = function () {
const self = this;
let selectedIndex = 0;
/**
* Get the value of the currently selected item
* @return {string}
*/
Select.prototype.getSelected = function () {
return this.domElement.querySelector('select').value;
};
selectedIndex = this.domElement.querySelector('select').selectedIndex;
Select.prototype.hide = function () {
this.domElement.classList.add('hidden');
if (this.domElement.querySelector('.equal-to')) {
this.domElement.querySelector('.equal-to').classList.add('hidden');
}
};
this.domElement.querySelector('select').innerHTML = '';
Select.prototype.show = function () {
this.domElement.classList.remove('hidden');
if (this.domElement.querySelector('.equal-to')) {
this.domElement.querySelector('.equal-to').classList.remove('hidden');
}
};
self.options.forEach(function (option) {
const optionElement = document.createElement('option');
optionElement.value = option[0];
optionElement.innerText = `+ ${option[1]}`;
self.domElement.querySelector('select').appendChild(optionElement);
});
this.domElement.querySelector('select').selectedIndex = selectedIndex;
};
/**
* Add a single option to this select
* @param {string} value The value for the new option
* @param {string} label The human-readable text for the new option
*/
Select.prototype.addOption = function (value, label) {
this.options.push([value, label]);
this.populate();
};
/**
* Set the available options for this select. Replaces any existing options
* @param {string[][]} options An array of [value, label] pairs to display
*/
Select.prototype.setOptions = function (options) {
this.options = options;
this.populate();
};
/**
* Sets the currently selected element an invokes any registered change
* callbacks with the new value. If the value doesn't exist in this select's
* model, its state will not change.
* @param {string} value The value to set as the selected option
*/
Select.prototype.setSelected = function (value) {
let selectedIndex = 0;
let selectedOption;
this.options.forEach(function (option, index) {
if (option[0] === value) {
selectedIndex = index;
}
});
this.domElement.querySelector('select').selectedIndex = selectedIndex;
selectedOption = this.options[selectedIndex];
this.eventEmitter.emit('change', selectedOption[0]);
};
/**
* Get the value of the currently selected item
* @return {string}
*/
Select.prototype.getSelected = function () {
return this.domElement.querySelector('select').value;
};
Select.prototype.hide = function () {
this.domElement.classList.add('hidden');
if (this.domElement.querySelector('.equal-to')) {
this.domElement.querySelector('.equal-to').classList.add('hidden');
}
};
Select.prototype.show = function () {
this.domElement.classList.remove('hidden');
if (this.domElement.querySelector('.equal-to')) {
this.domElement.querySelector('.equal-to').classList.remove('hidden');
}
};
Select.prototype.destroy = function () {
this.stopListening();
};
return Select;
});
Select.prototype.destroy = function () {
this.stopListening();
};

View File

@ -20,40 +20,40 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./SummaryWidgetEvaluator', 'objectUtils'], function (SummaryWidgetEvaluator, objectUtils) {
function EvaluatorPool(openmct) {
this.openmct = openmct;
this.byObjectId = {};
this.byEvaluator = new WeakMap();
import objectUtils from 'objectUtils';
import SummaryWidgetEvaluator from './SummaryWidgetEvaluator';
export default function EvaluatorPool(openmct) {
this.openmct = openmct;
this.byObjectId = {};
this.byEvaluator = new WeakMap();
}
EvaluatorPool.prototype.get = function (domainObject) {
const objectId = objectUtils.makeKeyString(domainObject.identifier);
let 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;
}
EvaluatorPool.prototype.get = function (domainObject) {
const objectId = objectUtils.makeKeyString(domainObject.identifier);
let 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;
poolEntry.leases += 1;
return poolEntry.evaluator;
};
return poolEntry.evaluator;
};
EvaluatorPool.prototype.release = function (evaluator) {
const 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;
});
EvaluatorPool.prototype.release = function (evaluator) {
const poolEntry = this.byEvaluator.get(evaluator);
poolEntry.leases -= 1;
if (poolEntry.leases === 0) {
evaluator.destroy();
this.byEvaluator.delete(evaluator);
delete this.byObjectId[poolEntry.objectId];
}
};

View File

@ -20,78 +20,75 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./EvaluatorPool', './SummaryWidgetEvaluator'], function (
EvaluatorPool,
SummaryWidgetEvaluator
) {
describe('EvaluatorPool', function () {
let pool;
let openmct;
let objectA;
let objectB;
import EvaluatorPool from './EvaluatorPool';
beforeEach(function () {
openmct = {
composition: jasmine.createSpyObj('compositionAPI', ['get']),
objects: jasmine.createSpyObj('objectAPI', ['observe'])
};
openmct.composition.get.and.callFake(function () {
const compositionCollection = jasmine.createSpyObj('compositionCollection', [
'load',
'on',
'off'
]);
compositionCollection.load.and.returnValue(Promise.resolve());
describe('EvaluatorPool', function () {
let pool;
let openmct;
let objectA;
let objectB;
return compositionCollection;
});
openmct.objects.observe.and.callFake(function () {
return function () {};
});
pool = new EvaluatorPool(openmct);
objectA = {
identifier: {
namespace: 'someNamespace',
key: 'someKey'
},
configuration: {
ruleOrder: []
}
};
objectB = {
identifier: {
namespace: 'otherNamespace',
key: 'otherKey'
},
configuration: {
ruleOrder: []
}
};
beforeEach(function () {
openmct = {
composition: jasmine.createSpyObj('compositionAPI', ['get']),
objects: jasmine.createSpyObj('objectAPI', ['observe'])
};
openmct.composition.get.and.callFake(function () {
const compositionCollection = jasmine.createSpyObj('compositionCollection', [
'load',
'on',
'off'
]);
compositionCollection.load.and.returnValue(Promise.resolve());
return compositionCollection;
});
it('returns new evaluators for different objects', function () {
const evaluatorA = pool.get(objectA);
const evaluatorB = pool.get(objectB);
expect(evaluatorA).not.toBe(evaluatorB);
openmct.objects.observe.and.callFake(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 the same evaluator for the same object', function () {
const evaluatorA = pool.get(objectA);
const evaluatorB = pool.get(objectA);
expect(evaluatorA).toBe(evaluatorB);
it('returns new evaluators for different objects', function () {
const evaluatorA = pool.get(objectA);
const evaluatorB = pool.get(objectB);
expect(evaluatorA).not.toBe(evaluatorB);
});
const evaluatorC = pool.get(JSON.parse(JSON.stringify(objectA)));
expect(evaluatorA).toBe(evaluatorC);
});
it('returns the same evaluator for the same object', function () {
const evaluatorA = pool.get(objectA);
const evaluatorB = pool.get(objectA);
expect(evaluatorA).toBe(evaluatorB);
it('returns new evaluator when old is released', function () {
const evaluatorA = pool.get(objectA);
const evaluatorB = pool.get(objectA);
expect(evaluatorA).toBe(evaluatorB);
pool.release(evaluatorA);
pool.release(evaluatorB);
const evaluatorC = pool.get(objectA);
expect(evaluatorA).not.toBe(evaluatorC);
});
const evaluatorC = pool.get(JSON.parse(JSON.stringify(objectA)));
expect(evaluatorA).toBe(evaluatorC);
});
it('returns new evaluator when old is released', function () {
const evaluatorA = pool.get(objectA);
const evaluatorB = pool.get(objectA);
expect(evaluatorA).toBe(evaluatorB);
pool.release(evaluatorA);
pool.release(evaluatorB);
const evaluatorC = pool.get(objectA);
expect(evaluatorA).not.toBe(evaluatorC);
});
});

View File

@ -20,57 +20,55 @@
* 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) {
const stateKeys = Object.keys(telemetryState);
let state;
let result;
let 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;
}
}
import OPERATIONS from './operations';
export default 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 {
return this.evaluateState(telemetryState[this.object]);
};
} else {
this.comparator = OPERATIONS[definition.operation].operation;
}
}
SummaryWidgetCondition.prototype.evaluate = function (telemetryState) {
const stateKeys = Object.keys(telemetryState);
let state;
let result;
let i;
if (this.object === 'any') {
for (i = 0; i < stateKeys.length; i++) {
state = telemetryState[stateKeys[i]];
result = this.evaluateState(state);
if (result) {
return true;
}
}
};
SummaryWidgetCondition.prototype.evaluateState = function (state) {
const testValues = [state.formats[this.key].parse(state.lastDatum)].concat(this.values);
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 this.comparator(testValues);
};
return true;
} else {
return this.evaluateState(telemetryState[this.object]);
}
};
return SummaryWidgetCondition;
});
SummaryWidgetCondition.prototype.evaluateState = function (state) {
const testValues = [state.formats[this.key].parse(state.lastDatum)].concat(this.values);
return this.comparator(testValues);
};

View File

@ -20,106 +20,106 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./SummaryWidgetCondition'], function (SummaryWidgetCondition) {
describe('SummaryWidgetCondition', function () {
let condition;
let telemetryState;
import SummaryWidgetCondition from './SummaryWidgetCondition';
beforeEach(function () {
// Format map intentionally uses different keys than those present
// in datum, which serves to verify conditions use format map to get
// data.
const formatMap = {
adjusted: {
parse: function (datum) {
return datum.value + 10;
}
},
raw: {
parse: function (datum) {
return datum.value;
}
describe('SummaryWidgetCondition', function () {
let condition;
let 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.
const formatMap = {
adjusted: {
parse: function (datum) {
return datum.value + 10;
}
};
telemetryState = {
objectId: {
formats: formatMap,
lastDatum: {}
},
otherObjectId: {
formats: formatMap,
lastDatum: {}
},
raw: {
parse: function (datum) {
return datum.value;
}
};
});
}
};
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);
});
telemetryState = {
objectId: {
formats: formatMap,
lastDatum: {}
},
otherObjectId: {
formats: formatMap,
lastDatum: {}
}
};
});
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 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 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 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 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);
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 evaluate "if any object matches"', function () {
condition = new SummaryWidgetCondition({
object: 'any',
key: 'raw',
operation: 'greaterThan',
values: [10]
});
telemetryState.objectId.lastDatum.value = 0;
telemetryState.otherObjectId.lastDatum.value = 0;
expect(condition.evaluate(telemetryState)).toBe(false);
telemetryState.objectId.lastDatum.value = 0;
telemetryState.otherObjectId.lastDatum.value = 15;
expect(condition.evaluate(telemetryState)).toBe(true);
telemetryState.objectId.lastDatum.value = 15;
telemetryState.otherObjectId.lastDatum.value = 0;
expect(condition.evaluate(telemetryState)).toBe(true);
telemetryState.objectId.lastDatum.value = 15;
telemetryState.otherObjectId.lastDatum.value = 15;
expect(condition.evaluate(telemetryState)).toBe(true);
});
});

View File

@ -20,251 +20,244 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./SummaryWidgetRule', '../eventHelpers', 'objectUtils', '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 = {};
import _ from 'lodash';
import objectUtils from 'objectUtils';
this.updateRules(domainObject);
this.removeObserver = openmct.objects.observe(domainObject, '*', this.updateRules.bind(this));
import eventHelpers from '../eventHelpers';
import SummaryWidgetRule from './SummaryWidgetRule';
const composition = openmct.composition.get(domainObject);
/**
* evaluates rules defined in a summary widget against either lad or
* realtime state.
*
*/
export default function SummaryWidgetEvaluator(domainObject, openmct) {
this.openmct = openmct;
this.baseState = {};
this.listenTo(composition, 'add', this.addChild, this);
this.listenTo(composition, 'remove', this.removeChild, this);
this.updateRules(domainObject);
this.removeObserver = openmct.objects.observe(domainObject, '*', this.updateRules.bind(this));
this.loadPromise = composition.load();
const 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) {
let active = true;
let unsubscribes = [];
this.getBaseStateClone().then(
function (realtimeStates) {
if (!active) {
return;
}
const updateCallback = function () {
const datum = this.evaluateState(realtimeStates, this.openmct.time.timeSystem().key);
if (datum) {
callback(datum);
}
}.bind(this);
/* eslint-disable you-dont-need-lodash-underscore/map */
unsubscribes = _.map(realtimeStates, this.subscribeToObjectState.bind(this, updateCallback));
/* eslint-enable you-dont-need-lodash-underscore/map */
}.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) {
const 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) {
const childId = objectUtils.makeKeyString(childObject.identifier);
const metadata = this.openmct.telemetry.getMetadata(childObject);
const formats = this.openmct.telemetry.getFormatMap(metadata);
this.baseState[childId] = {
id: childId,
domainObject: childObject,
metadata: metadata,
formats: formats
};
};
SummaryWidgetEvaluator.prototype.removeChild = function (childObject) {
const 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 () {
/* eslint-disable you-dont-need-lodash-underscore/values */
return _(this.baseState).values().map(_.clone).keyBy('id').value();
/* eslint-enable you-dont-need-lodash-underscore/values */
}.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 = Object.assign({}, 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) {
const 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) {
const 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) {
const hasRequiredData = Object.keys(state).reduce(function (itDoes, k) {
return itDoes && state[k].lastDatum;
}, true);
if (!hasRequiredData) {
return;
}
eventHelpers.extend(SummaryWidgetEvaluator.prototype);
/**
* Subscribes to realtime telemetry for the given summary widget.
*/
SummaryWidgetEvaluator.prototype.subscribe = function (callback) {
let active = true;
let unsubscribes = [];
this.getBaseStateClone().then(
function (realtimeStates) {
if (!active) {
return;
}
const updateCallback = function () {
const datum = this.evaluateState(realtimeStates, this.openmct.time.timeSystem().key);
if (datum) {
callback(datum);
}
}.bind(this);
/* eslint-disable you-dont-need-lodash-underscore/map */
unsubscribes = _.map(
realtimeStates,
this.subscribeToObjectState.bind(this, updateCallback)
);
/* eslint-enable you-dont-need-lodash-underscore/map */
}.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) {
const 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) {
const childId = objectUtils.makeKeyString(childObject.identifier);
const metadata = this.openmct.telemetry.getMetadata(childObject);
const formats = this.openmct.telemetry.getFormatMap(metadata);
this.baseState[childId] = {
id: childId,
domainObject: childObject,
metadata: metadata,
formats: formats
};
};
SummaryWidgetEvaluator.prototype.removeChild = function (childObject) {
const 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 () {
/* eslint-disable you-dont-need-lodash-underscore/values */
return _(this.baseState).values().map(_.clone).keyBy('id').value();
/* eslint-enable you-dont-need-lodash-underscore/values */
}.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 = Object.assign({}, 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) {
const 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) {
const 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) {
const hasRequiredData = Object.keys(state).reduce(function (itDoes, k) {
return itDoes && state[k].lastDatum;
}, true);
if (!hasRequiredData) {
return;
let i;
for (i = this.rules.length - 1; i > 0; i--) {
if (this.rules[i].evaluate(state, false)) {
break;
}
}
let i;
for (i = this.rules.length - 1; i > 0; i--) {
if (this.rules[i].evaluate(state, false)) {
break;
}
}
/* eslint-disable you-dont-need-lodash-underscore/map */
let latestTimestamp = _(state).map('timestamps').sortBy(timestampKey).last();
/* eslint-enable you-dont-need-lodash-underscore/map */
/* eslint-disable you-dont-need-lodash-underscore/map */
let latestTimestamp = _(state).map('timestamps').sortBy(timestampKey).last();
/* eslint-enable you-dont-need-lodash-underscore/map */
if (!latestTimestamp) {
latestTimestamp = {};
}
if (!latestTimestamp) {
latestTimestamp = {};
}
const baseDatum = _.clone(latestTimestamp);
const baseDatum = _.clone(latestTimestamp);
return this.makeDatumFromRule(i, baseDatum);
};
return this.makeDatumFromRule(i, baseDatum);
};
/**
* remove all listeners and clean up any resources.
*/
SummaryWidgetEvaluator.prototype.destroy = function () {
this.stopListening();
this.removeObserver();
};
return SummaryWidgetEvaluator;
});
/**
* remove all listeners and clean up any resources.
*/
SummaryWidgetEvaluator.prototype.destroy = function () {
this.stopListening();
this.removeObserver();
};

View File

@ -20,94 +20,90 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
function SummaryWidgetMetadataProvider(openmct) {
this.openmct = openmct;
}
export default function SummaryWidgetMetadataProvider(openmct) {
this.openmct = openmct;
}
SummaryWidgetMetadataProvider.prototype.supportsMetadata = function (domainObject) {
return domainObject.type === 'summary-widget';
};
SummaryWidgetMetadataProvider.prototype.supportsMetadata = function (domainObject) {
return domainObject.type === 'summary-widget';
};
SummaryWidgetMetadataProvider.prototype.getDomains = function (domainObject) {
return this.openmct.time.getAllTimeSystems().map(function (ts, i) {
SummaryWidgetMetadataProvider.prototype.getDomains = function (domainObject) {
return this.openmct.time.getAllTimeSystems().map(function (ts, i) {
return {
key: ts.key,
name: ts.name,
format: ts.timeFormat,
hints: {
domain: i
}
};
});
};
SummaryWidgetMetadataProvider.prototype.getMetadata = function (domainObject) {
const ruleOrder = domainObject.configuration.ruleOrder || [];
const enumerations = ruleOrder
.filter(function (ruleId) {
return Boolean(domainObject.configuration.ruleConfigById[ruleId]);
})
.map(function (ruleId, ruleIndex) {
return {
key: ts.key,
name: ts.name,
format: ts.timeFormat,
hints: {
domain: i
}
string: domainObject.configuration.ruleConfigById[ruleId].label,
value: ruleIndex
};
});
};
SummaryWidgetMetadataProvider.prototype.getMetadata = function (domainObject) {
const ruleOrder = domainObject.configuration.ruleOrder || [];
const enumerations = ruleOrder
.filter(function (ruleId) {
return Boolean(domainObject.configuration.ruleConfigById[ruleId]);
})
.map(function (ruleId, ruleIndex) {
return {
string: domainObject.configuration.ruleConfigById[ruleId].label,
value: ruleIndex
};
});
const 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'
const 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
}
])
};
return metadata;
},
{
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 SummaryWidgetMetadataProvider;
});
return metadata;
};

View File

@ -20,51 +20,49 @@
* 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;
}
import SummaryWidgetCondition from './SummaryWidgetCondition';
/**
* Evaluate the given rule against a telemetryState and return true if it
* matches.
*/
SummaryWidgetRule.prototype.evaluate = function (telemetryState) {
let i;
let result;
export default 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;
}
if (this.trigger === 'all') {
for (i = 0; i < this.conditions.length; i++) {
result = this.conditions[i].evaluate(telemetryState);
if (!result) {
return false;
}
/**
* Evaluate the given rule against a telemetryState and return true if it
* matches.
*/
SummaryWidgetRule.prototype.evaluate = function (telemetryState) {
let i;
let 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;
});
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);
}
};

View File

@ -20,134 +20,134 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./SummaryWidgetRule'], function (SummaryWidgetRule) {
describe('SummaryWidgetRule', function () {
let rule;
let telemetryState;
import SummaryWidgetRule from './SummaryWidgetRule';
beforeEach(function () {
const formatMap = {
raw: {
parse: function (datum) {
return datum.value;
}
describe('SummaryWidgetRule', function () {
let rule;
let telemetryState;
beforeEach(function () {
const formatMap = {
raw: {
parse: function (datum) {
return datum.value;
}
};
}
};
telemetryState = {
objectId: {
formats: formatMap,
lastDatum: {}
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]
},
otherObjectId: {
formats: formatMap,
lastDatum: {}
{
object: 'otherObjectId',
key: 'raw',
operation: 'greaterThan',
values: [20]
}
};
]
});
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;
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);
});
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 any', function () {
rule = new SummaryWidgetRule({
trigger: 'any',
conditions: [
{
object: 'objectId',
key: 'raw',
operation: 'greaterThan',
values: [10]
},
{
object: 'otherObjectId',
key: 'raw',
operation: 'greaterThan',
values: [20]
}
]
});
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);
});
telemetryState.objectId.lastDatum.value = 5;
telemetryState.otherObjectId.lastDatum.value = 5;
expect(rule.evaluate(telemetryState)).toBe(false);
telemetryState.objectId.lastDatum.value = 5;
telemetryState.otherObjectId.lastDatum.value = 25;
expect(rule.evaluate(telemetryState)).toBe(true);
telemetryState.objectId.lastDatum.value = 15;
telemetryState.otherObjectId.lastDatum.value = 5;
expect(rule.evaluate(telemetryState)).toBe(true);
telemetryState.objectId.lastDatum.value = 15;
telemetryState.otherObjectId.lastDatum.value = 25;
expect(rule.evaluate(telemetryState)).toBe(true);
});
});

View File

@ -20,44 +20,42 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./EvaluatorPool'], function (EvaluatorPool) {
function SummaryWidgetTelemetryProvider(openmct) {
this.pool = new EvaluatorPool(openmct);
import EvaluatorPool from './EvaluatorPool';
export default 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([]);
}
SummaryWidgetTelemetryProvider.prototype.supportsRequest = function (domainObject, options) {
return domainObject.type === 'summary-widget';
};
const evaluator = this.pool.get(domainObject);
SummaryWidgetTelemetryProvider.prototype.request = function (domainObject, options) {
if (options.strategy !== 'latest' && options.size !== 1) {
return Promise.resolve([]);
}
const evaluator = this.pool.get(domainObject);
return evaluator.requestLatest(options).then(
function (latestDatum) {
this.pool.release(evaluator);
return latestDatum ? [latestDatum] : [];
}.bind(this)
);
};
SummaryWidgetTelemetryProvider.prototype.supportsSubscribe = function (domainObject) {
return domainObject.type === 'summary-widget';
};
SummaryWidgetTelemetryProvider.prototype.subscribe = function (domainObject, callback) {
const evaluator = this.pool.get(domainObject);
const unsubscribe = evaluator.subscribe(callback);
return function () {
return evaluator.requestLatest(options).then(
function (latestDatum) {
this.pool.release(evaluator);
unsubscribe();
}.bind(this);
};
return SummaryWidgetTelemetryProvider;
});
return latestDatum ? [latestDatum] : [];
}.bind(this)
);
};
SummaryWidgetTelemetryProvider.prototype.supportsSubscribe = function (domainObject) {
return domainObject.type === 'summary-widget';
};
SummaryWidgetTelemetryProvider.prototype.subscribe = function (domainObject, callback) {
const evaluator = this.pool.get(domainObject);
const unsubscribe = evaluator.subscribe(callback);
return function () {
this.pool.release(evaluator);
unsubscribe();
}.bind(this);
};

View File

@ -20,444 +20,444 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./SummaryWidgetTelemetryProvider'], function (SummaryWidgetTelemetryProvider) {
xdescribe('SummaryWidgetTelemetryProvider', function () {
let telemObjectA;
let telemObjectB;
let summaryWidgetObject;
let openmct;
let telemUnsubscribes;
let unobserver;
let composition;
let telemetryProvider;
let loader;
import SummaryWidgetTelemetryProvider from './SummaryWidgetTelemetryProvider';
xdescribe('SummaryWidgetTelemetryProvider', function () {
let telemObjectA;
let telemObjectB;
let summaryWidgetObject;
let openmct;
let telemUnsubscribes;
let unobserver;
let composition;
let telemetryProvider;
let 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.and.returnValue([{ key: 'timestamp' }]);
openmct.time.timeSystem.and.returnValue({ key: 'timestamp' });
unobserver = jasmine.createSpy('unobserver');
openmct.objects.observe.and.returnValue(unobserver);
composition = jasmine.createSpyObj('compositionCollection', ['on', 'off', 'load']);
function notify(eventName, a, b) {
composition.on.calls
.all()
.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.and.callFake(function () {
setTimeout(function () {
notify('add', telemObjectA);
setTimeout(function () {
notify('add', telemObjectB);
setTimeout(function () {
loader.resolve();
});
});
});
return loader.promise;
});
openmct.composition.get.and.returnValue(composition);
telemUnsubscribes = [];
openmct.telemetry.subscribe.and.callFake(function () {
const unsubscriber = jasmine.createSpy('unsubscriber' + telemUnsubscribes.length);
telemUnsubscribes.push(unsubscriber);
return unsubscriber;
});
openmct.telemetry.getMetadata.and.callFake(function (object) {
return {
name: 'fake metadata manager',
object: object,
keys: ['timestamp', 'measurement']
};
});
openmct.telemetry.getFormatMap.and.callFake(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 () {
return telemetryProvider.request(summaryWidgetObject, {}).then(function (result) {
expect(result).toEqual([]);
});
});
it('provides realtime telemetry', function () {
const callback = jasmine.createSpy('callback');
telemetryProvider.subscribe(summaryWidgetObject, callback);
return loader.promise
.then(function () {
return new Promise(function (resolve) {
setTimeout(resolve);
});
})
.then(function () {
expect(openmct.telemetry.subscribe.calls.count()).toBe(2);
expect(openmct.telemetry.subscribe).toHaveBeenCalledWith(
telemObjectA,
jasmine.any(Function)
);
expect(openmct.telemetry.subscribe).toHaveBeenCalledWith(
telemObjectB,
jasmine.any(Function)
);
const aCallback = openmct.telemetry.subscribe.calls.all()[0].args[1];
const bCallback = openmct.telemetry.subscribe.calls.all()[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'
});
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'
});
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'
});
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'
});
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 () {
let responseDatums;
let resultsShouldBe;
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.and.returnValue([{ key: 'timestamp' }]);
openmct.time.timeSystem.and.returnValue({ key: 'timestamp' });
unobserver = jasmine.createSpy('unobserver');
openmct.objects.observe.and.returnValue(unobserver);
composition = jasmine.createSpyObj('compositionCollection', ['on', 'off', 'load']);
function notify(eventName, a, b) {
composition.on.calls
.all()
.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.and.callFake(function () {
setTimeout(function () {
notify('add', telemObjectA);
setTimeout(function () {
notify('add', telemObjectB);
setTimeout(function () {
loader.resolve();
});
});
openmct.telemetry.request.and.callFake(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 loader.promise;
return Promise.resolve([responseDatums[rObj.identifier.namespace]]);
});
openmct.composition.get.and.returnValue(composition);
responseDatums = {};
telemUnsubscribes = [];
openmct.telemetry.subscribe.and.callFake(function () {
const unsubscriber = jasmine.createSpy('unsubscriber' + telemUnsubscribes.length);
telemUnsubscribes.push(unsubscriber);
return unsubscriber;
});
openmct.telemetry.getMetadata.and.callFake(function (object) {
return {
name: 'fake metadata manager',
object: object,
keys: ['timestamp', 'measurement']
};
});
openmct.telemetry.getFormatMap.and.callFake(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 () {
return telemetryProvider.request(summaryWidgetObject, {}).then(function (result) {
expect(result).toEqual([]);
});
});
it('provides realtime telemetry', function () {
const callback = jasmine.createSpy('callback');
telemetryProvider.subscribe(summaryWidgetObject, callback);
return loader.promise
.then(function () {
return new Promise(function (resolve) {
setTimeout(resolve);
});
})
.then(function () {
expect(openmct.telemetry.subscribe.calls.count()).toBe(2);
expect(openmct.telemetry.subscribe).toHaveBeenCalledWith(
telemObjectA,
jasmine.any(Function)
);
expect(openmct.telemetry.subscribe).toHaveBeenCalledWith(
telemObjectB,
jasmine.any(Function)
);
const aCallback = openmct.telemetry.subscribe.calls.all()[0].args[1];
const bCallback = openmct.telemetry.subscribe.calls.all()[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'
});
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'
});
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'
});
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'
});
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 () {
let responseDatums;
let resultsShouldBe;
beforeEach(function () {
openmct.telemetry.request.and.callFake(function (rObj, options) {
expect(rObj).toEqual(jasmine.any(Object));
expect(options).toEqual({
resultsShouldBe = function (results) {
return telemetryProvider
.request(summaryWidgetObject, {
size: 1,
strategy: 'latest',
domain: 'timestamp'
})
.then(function (r) {
expect(r).toEqual(results);
});
expect(responseDatums[rObj.identifier.namespace]).toBeDefined();
};
});
return Promise.resolve([responseDatums[rObj.identifier.namespace]]);
});
responseDatums = {};
it('returns default when no rule matches', function () {
responseDatums = {
a: {
t: 122,
m: 25
},
b: {
t: 111,
m: 25
}
};
resultsShouldBe = function (results) {
return telemetryProvider
.request(summaryWidgetObject, {
size: 1,
strategy: 'latest',
domain: 'timestamp'
})
.then(function (r) {
expect(r).toEqual(results);
});
};
});
return 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 default when no rule matches', function () {
responseDatums = {
a: {
t: 122,
m: 25
},
b: {
t: 111,
m: 25
}
};
it('returns highest priority when multiple match', function () {
responseDatums = {
a: {
t: 131,
m: 55
},
b: {
t: 139,
m: 5
}
};
return 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'
}
]);
});
return 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 highest priority when multiple match', function () {
responseDatums = {
a: {
t: 131,
m: 55
},
b: {
t: 139,
m: 5
}
};
it('returns matching rule', function () {
responseDatums = {
a: {
t: 144,
m: 55
},
b: {
t: 141,
m: 15
}
};
return 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
}
};
return 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'
}
]);
});
return resultsShouldBe([
{
timestamp: 144,
ruleLabel: 'Start Worrying',
ruleName: 'A High',
message: 'A is a little high...',
ruleIndex: 1,
backgroundColor: '#ffff00',
textColor: '#000000',
borderColor: 'rgba(1,1,0,0)',
icon: 'a-high'
}
]);
});
});
});

View File

@ -20,196 +20,194 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
const OPERATIONS = {
equalTo: {
operation: function (input) {
return input[0] === input[1];
},
text: 'is equal to',
appliesTo: ['number'],
inputCount: 1,
getDescription: function (values) {
return ' == ' + values[0];
}
const OPERATIONS = {
equalTo: {
operation: function (input) {
return input[0] === input[1];
},
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', 'enum'],
inputCount: 0,
getDescription: function () {
return ' is undefined';
}
},
isDefined: {
operation: function (input) {
return typeof input[0] !== 'undefined';
},
text: 'is defined',
appliesTo: ['string', 'number', 'enum'],
inputCount: 0,
getDescription: function () {
return ' is defined';
}
},
enumValueIs: {
operation: function (input) {
return input[0] === input[1];
},
text: 'is',
appliesTo: ['enum'],
inputCount: 1,
getDescription: function (values) {
return ' == ' + values[0];
}
},
enumValueIsNot: {
operation: function (input) {
return input[0] !== input[1];
},
text: 'is not',
appliesTo: ['enum'],
inputCount: 1,
getDescription: function (values) {
return ' != ' + values[0];
}
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', 'enum'],
inputCount: 0,
getDescription: function () {
return ' is undefined';
}
},
isDefined: {
operation: function (input) {
return typeof input[0] !== 'undefined';
},
text: 'is defined',
appliesTo: ['string', 'number', 'enum'],
inputCount: 0,
getDescription: function () {
return ' is defined';
}
},
enumValueIs: {
operation: function (input) {
return input[0] === input[1];
},
text: 'is',
appliesTo: ['enum'],
inputCount: 1,
getDescription: function (values) {
return ' == ' + values[0];
}
},
enumValueIsNot: {
operation: function (input) {
return input[0] !== input[1];
},
text: 'is not',
appliesTo: ['enum'],
inputCount: 1,
getDescription: function (values) {
return ' != ' + values[0];
}
}
};
return OPERATIONS;
});
export default OPERATIONS;

View File

@ -1,38 +1,52 @@
define(['../SummaryWidget', './SummaryWidgetView', 'objectUtils'], function (
SummaryWidgetEditView,
SummaryWidgetView,
objectUtils
) {
const DEFAULT_VIEW_PRIORITY = 100;
/**
*
*/
function SummaryWidgetViewProvider(openmct) {
return {
key: 'summary-widget-viewer',
name: 'Summary View',
cssClass: 'icon-summary-widget',
canView: function (domainObject) {
return domainObject.type === 'summary-widget';
},
canEdit: function (domainObject) {
return domainObject.type === 'summary-widget';
},
view: function (domainObject) {
return new SummaryWidgetView(domainObject, openmct);
},
edit: function (domainObject) {
return new SummaryWidgetEditView(domainObject, openmct);
},
priority: function (domainObject) {
if (domainObject.type === 'summary-widget') {
return Number.MAX_VALUE;
} else {
return DEFAULT_VIEW_PRIORITY;
}
}
};
}
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, 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.
*****************************************************************************/
return SummaryWidgetViewProvider;
});
import SummaryWidgetEditView from '../SummaryWidget';
import SummaryWidgetView from './SummaryWidgetView';
const DEFAULT_VIEW_PRIORITY = 100;
export default function SummaryWidgetViewProvider(openmct) {
return {
key: 'summary-widget-viewer',
name: 'Summary View',
cssClass: 'icon-summary-widget',
canView: function (domainObject) {
return domainObject.type === 'summary-widget';
},
canEdit: function (domainObject) {
return domainObject.type === 'summary-widget';
},
view: function (domainObject) {
return new SummaryWidgetView(domainObject, openmct);
},
edit: function (domainObject) {
return new SummaryWidgetEditView(domainObject, openmct);
},
priority: function (domainObject) {
if (domainObject.type === 'summary-widget') {
return Number.MAX_VALUE;
} else {
return DEFAULT_VIEW_PRIORITY;
}
}
};
}

View File

@ -1,363 +1,363 @@
define(['../src/ConditionEvaluator'], function (ConditionEvaluator) {
describe('A Summary Widget Rule Evaluator', function () {
let evaluator;
let testEvaluator;
let testOperation;
let mockCache;
let mockTestCache;
let mockComposition;
let mockConditions;
let mockConditionsEmpty;
let mockConditionsUndefined;
let mockConditionsAnyTrue;
let mockConditionsAllTrue;
let mockConditionsAnyFalse;
let mockConditionsAllFalse;
let mockOperations;
import ConditionEvaluator from '../src/ConditionEvaluator';
beforeEach(function () {
mockCache = {
a: {
alpha: 3,
beta: 9,
gamma: 'Testing 1 2 3'
},
b: {
alpha: 44,
beta: 23,
gamma: 'Hello World'
},
c: {
foo: 'bar',
iAm: 'The Walrus',
creature: {
type: 'Centaur'
}
}
};
mockTestCache = {
a: {
alpha: 1,
beta: 1,
gamma: 'Testing 4 5 6'
},
b: {
alpha: 2,
beta: 2,
gamma: 'Goodbye world'
}
};
mockComposition = {
a: {},
b: {},
c: {}
};
mockConditions = [
{
object: 'a',
key: 'alpha',
operation: 'greaterThan',
values: [2]
},
{
object: 'b',
key: 'gamma',
operation: 'lessThan',
values: [5]
}
];
mockConditionsEmpty = [
{
object: '',
key: '',
operation: '',
values: []
}
];
mockConditionsUndefined = [
{
object: 'No Such Object',
key: '',
operation: '',
values: []
},
{
object: 'a',
key: 'No Such Key',
operation: '',
values: []
},
{
object: 'a',
key: 'alpha',
operation: 'No Such Operation',
values: []
},
{
object: 'all',
key: 'Nonexistent Field',
operation: 'Random Operation',
values: []
},
{
object: 'any',
key: 'Nonexistent Field',
operation: 'Whatever Operation',
values: []
}
];
mockConditionsAnyTrue = [
{
object: 'any',
key: 'alpha',
operation: 'greaterThan',
values: [5]
}
];
mockConditionsAnyFalse = [
{
object: 'any',
key: 'alpha',
operation: 'greaterThan',
values: [1000]
}
];
mockConditionsAllFalse = [
{
object: 'all',
key: 'alpha',
operation: 'greaterThan',
values: [5]
}
];
mockConditionsAllTrue = [
{
object: 'all',
key: 'alpha',
operation: 'greaterThan',
values: [0]
}
];
mockOperations = {
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
},
textContains: {
operation: function (input) {
return input[0] && input[1] && input[0].includes(input[1]);
},
text: 'text contains',
appliesTo: ['string'],
inputCount: 1
},
textIsExactly: {
operation: function (input) {
return input[0] === input[1];
},
text: 'text is exactly',
appliesTo: ['string'],
inputCount: 1
},
isHalfHorse: {
operation: function (input) {
return input[0].type === 'Centaur';
},
text: 'is Half Horse',
appliesTo: ['mythicalCreature'],
inputCount: 0,
getDescription: function () {
return 'is half horse';
}
}
};
evaluator = new ConditionEvaluator(mockCache, mockComposition);
testEvaluator = new ConditionEvaluator(mockCache, mockComposition);
evaluator.operations = mockOperations;
});
describe('A Summary Widget Rule Evaluator', function () {
let evaluator;
let testEvaluator;
let testOperation;
let mockCache;
let mockTestCache;
let mockComposition;
let mockConditions;
let mockConditionsEmpty;
let mockConditionsUndefined;
let mockConditionsAnyTrue;
let mockConditionsAllTrue;
let mockConditionsAnyFalse;
let mockConditionsAllFalse;
let mockOperations;
it('evaluates a condition when it has no configuration', function () {
expect(evaluator.execute(mockConditionsEmpty, 'any')).toEqual(false);
expect(evaluator.execute(mockConditionsEmpty, 'all')).toEqual(false);
});
it('correctly evaluates a set of conditions', function () {
expect(evaluator.execute(mockConditions, 'any')).toEqual(true);
expect(evaluator.execute(mockConditions, 'all')).toEqual(false);
});
it('correctly evaluates conditions involving "any telemetry"', function () {
expect(evaluator.execute(mockConditionsAnyTrue, 'any')).toEqual(true);
expect(evaluator.execute(mockConditionsAnyFalse, 'any')).toEqual(false);
});
it('correctly evaluates conditions involving "all telemetry"', function () {
expect(evaluator.execute(mockConditionsAllTrue, 'any')).toEqual(true);
expect(evaluator.execute(mockConditionsAllFalse, 'any')).toEqual(false);
});
it('handles malformed conditions gracefully', function () {
//if no conditions are fully defined, should return false for any mode
expect(evaluator.execute(mockConditionsUndefined, 'any')).toEqual(false);
expect(evaluator.execute(mockConditionsUndefined, 'all')).toEqual(false);
//these conditions are true: evaluator should ignore undefined conditions,
//and evaluate the rule as true
mockConditionsUndefined.push({
beforeEach(function () {
mockCache = {
a: {
alpha: 3,
beta: 9,
gamma: 'Testing 1 2 3'
},
b: {
alpha: 44,
beta: 23,
gamma: 'Hello World'
},
c: {
foo: 'bar',
iAm: 'The Walrus',
creature: {
type: 'Centaur'
}
}
};
mockTestCache = {
a: {
alpha: 1,
beta: 1,
gamma: 'Testing 4 5 6'
},
b: {
alpha: 2,
beta: 2,
gamma: 'Goodbye world'
}
};
mockComposition = {
a: {},
b: {},
c: {}
};
mockConditions = [
{
object: 'a',
key: 'alpha',
operation: 'greaterThan',
values: [2]
},
{
object: 'b',
key: 'gamma',
operation: 'textContains',
values: ['Testing']
});
expect(evaluator.execute(mockConditionsUndefined, 'any')).toEqual(true);
mockConditionsUndefined.push({
object: 'c',
key: 'iAm',
operation: 'textContains',
values: ['Walrus']
});
expect(evaluator.execute(mockConditionsUndefined, 'all')).toEqual(true);
});
it('gets the keys for possible operations', function () {
expect(evaluator.getOperationKeys()).toEqual([
'greaterThan',
'lessThan',
'textContains',
'textIsExactly',
'isHalfHorse'
]);
});
it('gets output text for a given operation', function () {
expect(evaluator.getOperationText('isHalfHorse')).toEqual('is Half Horse');
});
it('correctly returns whether an operation applies to a given type', function () {
expect(evaluator.operationAppliesTo('isHalfHorse', 'mythicalCreature')).toEqual(true);
expect(evaluator.operationAppliesTo('isHalfHorse', 'spaceJunk')).toEqual(false);
});
it('returns the HTML input type associated with a given data type', function () {
expect(evaluator.getInputTypeById('string')).toEqual('text');
});
it('gets the number of inputs required for a given operation', function () {
expect(evaluator.getInputCount('isHalfHorse')).toEqual(0);
expect(evaluator.getInputCount('greaterThan')).toEqual(1);
});
it('gets a human-readable description of a condition', function () {
expect(evaluator.getOperationDescription('isHalfHorse')).toEqual('is half horse');
expect(evaluator.getOperationDescription('greaterThan', [1])).toEqual(' > 1');
});
it('allows setting a substitute cache for testing purposes, and toggling its use', function () {
evaluator.setTestDataCache(mockTestCache);
evaluator.useTestData(true);
expect(evaluator.execute(mockConditions, 'any')).toEqual(false);
expect(evaluator.execute(mockConditions, 'all')).toEqual(false);
mockConditions.push({
operation: 'lessThan',
values: [5]
}
];
mockConditionsEmpty = [
{
object: '',
key: '',
operation: '',
values: []
}
];
mockConditionsUndefined = [
{
object: 'No Such Object',
key: '',
operation: '',
values: []
},
{
object: 'a',
key: 'gamma',
operation: 'textContains',
values: ['4 5 6']
});
expect(evaluator.execute(mockConditions, 'any')).toEqual(true);
expect(evaluator.execute(mockConditions, 'all')).toEqual(false);
mockConditions.pop();
evaluator.useTestData(false);
expect(evaluator.execute(mockConditions, 'any')).toEqual(true);
expect(evaluator.execute(mockConditions, 'all')).toEqual(false);
});
key: 'No Such Key',
operation: '',
values: []
},
{
object: 'a',
key: 'alpha',
operation: 'No Such Operation',
values: []
},
{
object: 'all',
key: 'Nonexistent Field',
operation: 'Random Operation',
values: []
},
{
object: 'any',
key: 'Nonexistent Field',
operation: 'Whatever Operation',
values: []
}
];
mockConditionsAnyTrue = [
{
object: 'any',
key: 'alpha',
operation: 'greaterThan',
values: [5]
}
];
mockConditionsAnyFalse = [
{
object: 'any',
key: 'alpha',
operation: 'greaterThan',
values: [1000]
}
];
mockConditionsAllFalse = [
{
object: 'all',
key: 'alpha',
operation: 'greaterThan',
values: [5]
}
];
mockConditionsAllTrue = [
{
object: 'all',
key: 'alpha',
operation: 'greaterThan',
values: [0]
}
];
mockOperations = {
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
},
textContains: {
operation: function (input) {
return input[0] && input[1] && input[0].includes(input[1]);
},
text: 'text contains',
appliesTo: ['string'],
inputCount: 1
},
textIsExactly: {
operation: function (input) {
return input[0] === input[1];
},
text: 'text is exactly',
appliesTo: ['string'],
inputCount: 1
},
isHalfHorse: {
operation: function (input) {
return input[0].type === 'Centaur';
},
text: 'is Half Horse',
appliesTo: ['mythicalCreature'],
inputCount: 0,
getDescription: function () {
return 'is half horse';
}
}
};
evaluator = new ConditionEvaluator(mockCache, mockComposition);
testEvaluator = new ConditionEvaluator(mockCache, mockComposition);
evaluator.operations = mockOperations;
});
it('supports all required operations', function () {
//equal to
testOperation = testEvaluator.operations.equalTo.operation;
expect(testOperation([33, 33])).toEqual(true);
expect(testOperation([55, 147])).toEqual(false);
//not equal to
testOperation = testEvaluator.operations.notEqualTo.operation;
expect(testOperation([33, 33])).toEqual(false);
expect(testOperation([55, 147])).toEqual(true);
//greater than
testOperation = testEvaluator.operations.greaterThan.operation;
expect(testOperation([100, 33])).toEqual(true);
expect(testOperation([33, 33])).toEqual(false);
expect(testOperation([55, 147])).toEqual(false);
//less than
testOperation = testEvaluator.operations.lessThan.operation;
expect(testOperation([100, 33])).toEqual(false);
expect(testOperation([33, 33])).toEqual(false);
expect(testOperation([55, 147])).toEqual(true);
//greater than or equal to
testOperation = testEvaluator.operations.greaterThanOrEq.operation;
expect(testOperation([100, 33])).toEqual(true);
expect(testOperation([33, 33])).toEqual(true);
expect(testOperation([55, 147])).toEqual(false);
//less than or equal to
testOperation = testEvaluator.operations.lessThanOrEq.operation;
expect(testOperation([100, 33])).toEqual(false);
expect(testOperation([33, 33])).toEqual(true);
expect(testOperation([55, 147])).toEqual(true);
//between
testOperation = testEvaluator.operations.between.operation;
expect(testOperation([100, 33, 66])).toEqual(false);
expect(testOperation([1, 33, 66])).toEqual(false);
expect(testOperation([45, 33, 66])).toEqual(true);
//not between
testOperation = testEvaluator.operations.notBetween.operation;
expect(testOperation([100, 33, 66])).toEqual(true);
expect(testOperation([1, 33, 66])).toEqual(true);
expect(testOperation([45, 33, 66])).toEqual(false);
//text contains
testOperation = testEvaluator.operations.textContains.operation;
expect(testOperation(['Testing', 'tin'])).toEqual(true);
expect(testOperation(['Testing', 'bind'])).toEqual(false);
//text does not contain
testOperation = testEvaluator.operations.textDoesNotContain.operation;
expect(testOperation(['Testing', 'tin'])).toEqual(false);
expect(testOperation(['Testing', 'bind'])).toEqual(true);
//text starts with
testOperation = testEvaluator.operations.textStartsWith.operation;
expect(testOperation(['Testing', 'Tes'])).toEqual(true);
expect(testOperation(['Testing', 'ting'])).toEqual(false);
//text ends with
testOperation = testEvaluator.operations.textEndsWith.operation;
expect(testOperation(['Testing', 'Tes'])).toEqual(false);
expect(testOperation(['Testing', 'ting'])).toEqual(true);
//text is exactly
testOperation = testEvaluator.operations.textIsExactly.operation;
expect(testOperation(['Testing', 'Testing'])).toEqual(true);
expect(testOperation(['Testing', 'Test'])).toEqual(false);
//undefined
testOperation = testEvaluator.operations.isUndefined.operation;
expect(testOperation([1])).toEqual(false);
expect(testOperation([])).toEqual(true);
//isDefined
testOperation = testEvaluator.operations.isDefined.operation;
expect(testOperation([1])).toEqual(true);
expect(testOperation([])).toEqual(false);
});
it('evaluates a condition when it has no configuration', function () {
expect(evaluator.execute(mockConditionsEmpty, 'any')).toEqual(false);
expect(evaluator.execute(mockConditionsEmpty, 'all')).toEqual(false);
});
it('can produce a description for all supported operations', function () {
testEvaluator.getOperationKeys().forEach(function (key) {
expect(testEvaluator.getOperationDescription(key, [])).toBeDefined();
});
it('correctly evaluates a set of conditions', function () {
expect(evaluator.execute(mockConditions, 'any')).toEqual(true);
expect(evaluator.execute(mockConditions, 'all')).toEqual(false);
});
it('correctly evaluates conditions involving "any telemetry"', function () {
expect(evaluator.execute(mockConditionsAnyTrue, 'any')).toEqual(true);
expect(evaluator.execute(mockConditionsAnyFalse, 'any')).toEqual(false);
});
it('correctly evaluates conditions involving "all telemetry"', function () {
expect(evaluator.execute(mockConditionsAllTrue, 'any')).toEqual(true);
expect(evaluator.execute(mockConditionsAllFalse, 'any')).toEqual(false);
});
it('handles malformed conditions gracefully', function () {
//if no conditions are fully defined, should return false for any mode
expect(evaluator.execute(mockConditionsUndefined, 'any')).toEqual(false);
expect(evaluator.execute(mockConditionsUndefined, 'all')).toEqual(false);
//these conditions are true: evaluator should ignore undefined conditions,
//and evaluate the rule as true
mockConditionsUndefined.push({
object: 'a',
key: 'gamma',
operation: 'textContains',
values: ['Testing']
});
expect(evaluator.execute(mockConditionsUndefined, 'any')).toEqual(true);
mockConditionsUndefined.push({
object: 'c',
key: 'iAm',
operation: 'textContains',
values: ['Walrus']
});
expect(evaluator.execute(mockConditionsUndefined, 'all')).toEqual(true);
});
it('gets the keys for possible operations', function () {
expect(evaluator.getOperationKeys()).toEqual([
'greaterThan',
'lessThan',
'textContains',
'textIsExactly',
'isHalfHorse'
]);
});
it('gets output text for a given operation', function () {
expect(evaluator.getOperationText('isHalfHorse')).toEqual('is Half Horse');
});
it('correctly returns whether an operation applies to a given type', function () {
expect(evaluator.operationAppliesTo('isHalfHorse', 'mythicalCreature')).toEqual(true);
expect(evaluator.operationAppliesTo('isHalfHorse', 'spaceJunk')).toEqual(false);
});
it('returns the HTML input type associated with a given data type', function () {
expect(evaluator.getInputTypeById('string')).toEqual('text');
});
it('gets the number of inputs required for a given operation', function () {
expect(evaluator.getInputCount('isHalfHorse')).toEqual(0);
expect(evaluator.getInputCount('greaterThan')).toEqual(1);
});
it('gets a human-readable description of a condition', function () {
expect(evaluator.getOperationDescription('isHalfHorse')).toEqual('is half horse');
expect(evaluator.getOperationDescription('greaterThan', [1])).toEqual(' > 1');
});
it('allows setting a substitute cache for testing purposes, and toggling its use', function () {
evaluator.setTestDataCache(mockTestCache);
evaluator.useTestData(true);
expect(evaluator.execute(mockConditions, 'any')).toEqual(false);
expect(evaluator.execute(mockConditions, 'all')).toEqual(false);
mockConditions.push({
object: 'a',
key: 'gamma',
operation: 'textContains',
values: ['4 5 6']
});
expect(evaluator.execute(mockConditions, 'any')).toEqual(true);
expect(evaluator.execute(mockConditions, 'all')).toEqual(false);
mockConditions.pop();
evaluator.useTestData(false);
expect(evaluator.execute(mockConditions, 'any')).toEqual(true);
expect(evaluator.execute(mockConditions, 'all')).toEqual(false);
});
it('supports all required operations', function () {
//equal to
testOperation = testEvaluator.operations.equalTo.operation;
expect(testOperation([33, 33])).toEqual(true);
expect(testOperation([55, 147])).toEqual(false);
//not equal to
testOperation = testEvaluator.operations.notEqualTo.operation;
expect(testOperation([33, 33])).toEqual(false);
expect(testOperation([55, 147])).toEqual(true);
//greater than
testOperation = testEvaluator.operations.greaterThan.operation;
expect(testOperation([100, 33])).toEqual(true);
expect(testOperation([33, 33])).toEqual(false);
expect(testOperation([55, 147])).toEqual(false);
//less than
testOperation = testEvaluator.operations.lessThan.operation;
expect(testOperation([100, 33])).toEqual(false);
expect(testOperation([33, 33])).toEqual(false);
expect(testOperation([55, 147])).toEqual(true);
//greater than or equal to
testOperation = testEvaluator.operations.greaterThanOrEq.operation;
expect(testOperation([100, 33])).toEqual(true);
expect(testOperation([33, 33])).toEqual(true);
expect(testOperation([55, 147])).toEqual(false);
//less than or equal to
testOperation = testEvaluator.operations.lessThanOrEq.operation;
expect(testOperation([100, 33])).toEqual(false);
expect(testOperation([33, 33])).toEqual(true);
expect(testOperation([55, 147])).toEqual(true);
//between
testOperation = testEvaluator.operations.between.operation;
expect(testOperation([100, 33, 66])).toEqual(false);
expect(testOperation([1, 33, 66])).toEqual(false);
expect(testOperation([45, 33, 66])).toEqual(true);
//not between
testOperation = testEvaluator.operations.notBetween.operation;
expect(testOperation([100, 33, 66])).toEqual(true);
expect(testOperation([1, 33, 66])).toEqual(true);
expect(testOperation([45, 33, 66])).toEqual(false);
//text contains
testOperation = testEvaluator.operations.textContains.operation;
expect(testOperation(['Testing', 'tin'])).toEqual(true);
expect(testOperation(['Testing', 'bind'])).toEqual(false);
//text does not contain
testOperation = testEvaluator.operations.textDoesNotContain.operation;
expect(testOperation(['Testing', 'tin'])).toEqual(false);
expect(testOperation(['Testing', 'bind'])).toEqual(true);
//text starts with
testOperation = testEvaluator.operations.textStartsWith.operation;
expect(testOperation(['Testing', 'Tes'])).toEqual(true);
expect(testOperation(['Testing', 'ting'])).toEqual(false);
//text ends with
testOperation = testEvaluator.operations.textEndsWith.operation;
expect(testOperation(['Testing', 'Tes'])).toEqual(false);
expect(testOperation(['Testing', 'ting'])).toEqual(true);
//text is exactly
testOperation = testEvaluator.operations.textIsExactly.operation;
expect(testOperation(['Testing', 'Testing'])).toEqual(true);
expect(testOperation(['Testing', 'Test'])).toEqual(false);
//undefined
testOperation = testEvaluator.operations.isUndefined.operation;
expect(testOperation([1])).toEqual(false);
expect(testOperation([])).toEqual(true);
//isDefined
testOperation = testEvaluator.operations.isDefined.operation;
expect(testOperation([1])).toEqual(true);
expect(testOperation([])).toEqual(false);
});
it('can produce a description for all supported operations', function () {
testEvaluator.getOperationKeys().forEach(function (key) {
expect(testEvaluator.getOperationDescription(key, [])).toBeDefined();
});
});
});

View File

@ -20,270 +20,71 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['../src/ConditionManager'], function (ConditionManager) {
describe('A Summary Widget Condition Manager', function () {
let conditionManager;
let mockDomainObject;
let mockCompObject1;
let mockCompObject2;
let mockCompObject3;
let mockMetadata;
let mockTelemetryCallbacks;
let mockEventCallbacks;
let unsubscribeSpies;
let unregisterSpies;
let mockMetadataManagers;
let mockComposition;
let mockOpenMCT;
let mockTelemetryAPI;
let addCallbackSpy;
let loadCallbackSpy;
let removeCallbackSpy;
let telemetryCallbackSpy;
let metadataCallbackSpy;
let telemetryRequests;
let mockTelemetryValues;
let mockTelemetryValues2;
let mockConditionEvaluator;
import ConditionManager from '../src/ConditionManager';
beforeEach(function () {
mockDomainObject = {
identifier: {
key: 'testKey'
},
name: 'Test Object',
composition: [
{
mockCompObject1: {
key: 'mockCompObject1'
},
mockCompObject2: {
key: 'mockCompObject2'
}
}
],
configuration: {}
};
mockCompObject1 = {
identifier: {
key: 'mockCompObject1'
},
name: 'Object 1'
};
mockCompObject2 = {
identifier: {
key: 'mockCompObject2'
},
name: 'Object 2'
};
mockCompObject3 = {
identifier: {
key: 'mockCompObject3'
},
name: 'Object 3'
};
mockMetadata = {
mockCompObject1: {
property1: {
key: 'property1',
name: 'Property 1',
format: 'string',
hints: {}
describe('A Summary Widget Condition Manager', function () {
let conditionManager;
let mockDomainObject;
let mockCompObject1;
let mockCompObject2;
let mockCompObject3;
let mockMetadata;
let mockTelemetryCallbacks;
let mockEventCallbacks;
let unsubscribeSpies;
let unregisterSpies;
let mockMetadataManagers;
let mockComposition;
let mockOpenMCT;
let mockTelemetryAPI;
let addCallbackSpy;
let loadCallbackSpy;
let removeCallbackSpy;
let telemetryCallbackSpy;
let metadataCallbackSpy;
let telemetryRequests;
let mockTelemetryValues;
let mockTelemetryValues2;
let mockConditionEvaluator;
beforeEach(function () {
mockDomainObject = {
identifier: {
key: 'testKey'
},
name: 'Test Object',
composition: [
{
mockCompObject1: {
key: 'mockCompObject1'
},
property2: {
key: 'property2',
name: 'Property 2',
hints: {
domain: 1
}
}
},
mockCompObject2: {
property3: {
key: 'property3',
name: 'Property 3',
format: 'string',
hints: {}
},
property4: {
key: 'property4',
name: 'Property 4',
hints: {
range: 1
}
}
},
mockCompObject3: {
property1: {
key: 'property1',
name: 'Property 1',
hints: {}
},
property2: {
key: 'property2',
name: 'Property 2',
hints: {}
mockCompObject2: {
key: 'mockCompObject2'
}
}
};
mockTelemetryCallbacks = {};
mockEventCallbacks = {};
unsubscribeSpies = jasmine.createSpyObj('mockUnsubscribeFunction', [
'mockCompObject1',
'mockCompObject2',
'mockCompObject3'
]);
unregisterSpies = jasmine.createSpyObj('mockUnregisterFunctions', ['load', 'remove', 'add']);
mockTelemetryValues = {
mockCompObject1: {
property1: 'Its a string',
property2: 42
},
mockCompObject2: {
property3: 'Execute order:',
property4: 66
},
mockCompObject3: {
property1: 'Testing 1 2 3',
property2: 9000
}
};
mockTelemetryValues2 = {
mockCompObject1: {
property1: 'Its a different string',
property2: 44
},
mockCompObject2: {
property3: 'Execute catch:',
property4: 22
},
mockCompObject3: {
property1: 'Walrus',
property2: 22
}
};
mockMetadataManagers = {
mockCompObject1: {
values: jasmine
.createSpy('metadataManager')
.and.returnValue(Object.values(mockMetadata.mockCompObject1))
},
mockCompObject2: {
values: jasmine
.createSpy('metadataManager')
.and.returnValue(Object.values(mockMetadata.mockCompObject2))
},
mockCompObject3: {
values: jasmine
.createSpy('metadataManager')
.and.returnValue(Object.values(mockMetadata.mockCompObject2))
}
};
mockComposition = jasmine.createSpyObj('composition', [
'on',
'off',
'load',
'triggerCallback'
]);
mockComposition.on.and.callFake(function (event, callback, context) {
mockEventCallbacks[event] = callback.bind(context);
});
mockComposition.off.and.callFake(function (event) {
unregisterSpies[event]();
});
mockComposition.load.and.callFake(function () {
mockComposition.triggerCallback('add', mockCompObject1);
mockComposition.triggerCallback('add', mockCompObject2);
mockComposition.triggerCallback('load');
});
mockComposition.triggerCallback.and.callFake(function (event, obj) {
if (event === 'add') {
mockEventCallbacks.add(obj);
} else if (event === 'remove') {
mockEventCallbacks.remove(obj.identifier);
} else {
mockEventCallbacks[event]();
}
});
telemetryRequests = [];
mockTelemetryAPI = jasmine.createSpyObj('telemetryAPI', [
'request',
'isTelemetryObject',
'getMetadata',
'subscribe',
'triggerTelemetryCallback'
]);
mockTelemetryAPI.request.and.callFake(function (obj) {
const req = {
object: obj
};
req.promise = new Promise(function (resolve, reject) {
req.resolve = resolve;
req.reject = reject;
});
telemetryRequests.push(req);
return req.promise;
});
mockTelemetryAPI.isTelemetryObject.and.returnValue(true);
mockTelemetryAPI.getMetadata.and.callFake(function (obj) {
return mockMetadataManagers[obj.identifier.key];
});
mockTelemetryAPI.subscribe.and.callFake(function (obj, callback) {
mockTelemetryCallbacks[obj.identifier.key] = callback;
return unsubscribeSpies[obj.identifier.key];
});
mockTelemetryAPI.triggerTelemetryCallback.and.callFake(function (key) {
mockTelemetryCallbacks[key](mockTelemetryValues2[key]);
});
mockOpenMCT = {
telemetry: mockTelemetryAPI,
composition: {}
};
mockOpenMCT.composition.get = jasmine.createSpy('get').and.returnValue(mockComposition);
loadCallbackSpy = jasmine.createSpy('loadCallbackSpy');
addCallbackSpy = jasmine.createSpy('addCallbackSpy');
removeCallbackSpy = jasmine.createSpy('removeCallbackSpy');
metadataCallbackSpy = jasmine.createSpy('metadataCallbackSpy');
telemetryCallbackSpy = jasmine.createSpy('telemetryCallbackSpy');
conditionManager = new ConditionManager(mockDomainObject, mockOpenMCT);
conditionManager.on('load', loadCallbackSpy);
conditionManager.on('add', addCallbackSpy);
conditionManager.on('remove', removeCallbackSpy);
conditionManager.on('metadata', metadataCallbackSpy);
conditionManager.on('receiveTelemetry', telemetryCallbackSpy);
mockConditionEvaluator = jasmine.createSpy('mockConditionEvaluator');
mockConditionEvaluator.execute = jasmine.createSpy('execute');
conditionManager.evaluator = mockConditionEvaluator;
});
it('loads the initial composition and invokes the appropriate handlers', function () {
mockComposition.triggerCallback('load');
expect(conditionManager.getComposition()).toEqual({
mockCompObject1: mockCompObject1,
mockCompObject2: mockCompObject2
});
expect(loadCallbackSpy).toHaveBeenCalled();
expect(conditionManager.loadCompleted()).toEqual(true);
});
it('loads metadata from composition and gets it upon request', function () {
expect(conditionManager.getTelemetryMetadata('mockCompObject1')).toEqual(
mockMetadata.mockCompObject1
);
expect(conditionManager.getTelemetryMetadata('mockCompObject2')).toEqual(
mockMetadata.mockCompObject2
);
});
it('maintains lists of global metadata, and does not duplicate repeated fields', function () {
const allKeys = {
],
configuration: {}
};
mockCompObject1 = {
identifier: {
key: 'mockCompObject1'
},
name: 'Object 1'
};
mockCompObject2 = {
identifier: {
key: 'mockCompObject2'
},
name: 'Object 2'
};
mockCompObject3 = {
identifier: {
key: 'mockCompObject3'
},
name: 'Object 3'
};
mockMetadata = {
mockCompObject1: {
property1: {
key: 'property1',
name: 'Property 1',
@ -296,7 +97,9 @@ define(['../src/ConditionManager'], function (ConditionManager) {
hints: {
domain: 1
}
},
}
},
mockCompObject2: {
property3: {
key: 'property3',
name: 'Property 3',
@ -310,134 +113,326 @@ define(['../src/ConditionManager'], function (ConditionManager) {
range: 1
}
}
};
expect(conditionManager.getTelemetryMetadata('all')).toEqual(allKeys);
expect(conditionManager.getTelemetryMetadata('any')).toEqual(allKeys);
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();
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', mockCompObject3);
expect(addCallbackSpy).toHaveBeenCalledWith(mockCompObject3);
expect(conditionManager.getComposition()).toEqual({
mockCompObject1: mockCompObject1,
mockCompObject2: mockCompObject2,
mockCompObject3: mockCompObject3
});
});
it('responds to a composition remove event and invokes the appropriate handlers', function () {
mockComposition.triggerCallback('remove', mockCompObject2);
expect(removeCallbackSpy).toHaveBeenCalledWith({
key: 'mockCompObject2'
});
expect(unsubscribeSpies.mockCompObject2).toHaveBeenCalled();
expect(conditionManager.getComposition()).toEqual({
mockCompObject1: mockCompObject1
});
});
it('unregisters telemetry subscriptions and composition listeners on destroy', function () {
mockComposition.triggerCallback('add', mockCompObject3);
conditionManager.destroy();
Object.values(unsubscribeSpies).forEach(function (spy) {
expect(spy).toHaveBeenCalled();
});
Object.values(unregisterSpies).forEach(function (spy) {
expect(spy).toHaveBeenCalled();
});
});
xit('populates its LAD cache with historical data on load, if available', function (done) {
expect(telemetryRequests.length).toBe(2);
expect(telemetryRequests[0].object).toBe(mockCompObject1);
expect(telemetryRequests[1].object).toBe(mockCompObject2);
expect(telemetryCallbackSpy).not.toHaveBeenCalled();
telemetryCallbackSpy.and.callFake(function () {
if (telemetryCallbackSpy.calls.count() === 2) {
expect(conditionManager.subscriptionCache.mockCompObject1.property1).toEqual(
'Its a string'
);
expect(conditionManager.subscriptionCache.mockCompObject2.property4).toEqual(66);
done();
},
mockCompObject3: {
property1: {
key: 'property1',
name: 'Property 1',
hints: {}
},
property2: {
key: 'property2',
name: 'Property 2',
hints: {}
}
});
telemetryRequests[0].resolve([mockTelemetryValues.mockCompObject1]);
telemetryRequests[1].resolve([mockTelemetryValues.mockCompObject2]);
});
xit('updates its LAD cache upon receiving telemetry and invokes the appropriate handlers', function () {
mockTelemetryAPI.triggerTelemetryCallback('mockCompObject1');
expect(conditionManager.subscriptionCache.mockCompObject1.property1).toEqual(
'Its a different string'
);
mockTelemetryAPI.triggerTelemetryCallback('mockCompObject2');
expect(conditionManager.subscriptionCache.mockCompObject2.property4).toEqual(22);
expect(telemetryCallbackSpy).toHaveBeenCalled();
});
it(
'evaluates a set of rules and returns the id of the ' +
'last active rule, or the first if no rules are active',
function () {
const mockRuleOrder = ['default', 'rule0', 'rule1'];
const mockRules = {
default: {
getProperty: function () {}
},
rule0: {
getProperty: function () {}
},
rule1: {
getProperty: function () {}
}
};
mockConditionEvaluator.execute.and.returnValue(false);
expect(conditionManager.executeRules(mockRuleOrder, mockRules)).toEqual('default');
mockConditionEvaluator.execute.and.returnValue(true);
expect(conditionManager.executeRules(mockRuleOrder, mockRules)).toEqual('rule1');
}
};
mockTelemetryCallbacks = {};
mockEventCallbacks = {};
unsubscribeSpies = jasmine.createSpyObj('mockUnsubscribeFunction', [
'mockCompObject1',
'mockCompObject2',
'mockCompObject3'
]);
unregisterSpies = jasmine.createSpyObj('mockUnregisterFunctions', ['load', 'remove', 'add']);
mockTelemetryValues = {
mockCompObject1: {
property1: 'Its a string',
property2: 42
},
mockCompObject2: {
property3: 'Execute order:',
property4: 66
},
mockCompObject3: {
property1: 'Testing 1 2 3',
property2: 9000
}
};
mockTelemetryValues2 = {
mockCompObject1: {
property1: 'Its a different string',
property2: 44
},
mockCompObject2: {
property3: 'Execute catch:',
property4: 22
},
mockCompObject3: {
property1: 'Walrus',
property2: 22
}
};
mockMetadataManagers = {
mockCompObject1: {
values: jasmine
.createSpy('metadataManager')
.and.returnValue(Object.values(mockMetadata.mockCompObject1))
},
mockCompObject2: {
values: jasmine
.createSpy('metadataManager')
.and.returnValue(Object.values(mockMetadata.mockCompObject2))
},
mockCompObject3: {
values: jasmine
.createSpy('metadataManager')
.and.returnValue(Object.values(mockMetadata.mockCompObject2))
}
};
mockComposition = jasmine.createSpyObj('composition', ['on', 'off', 'load', 'triggerCallback']);
mockComposition.on.and.callFake(function (event, callback, context) {
mockEventCallbacks[event] = callback.bind(context);
});
mockComposition.off.and.callFake(function (event) {
unregisterSpies[event]();
});
mockComposition.load.and.callFake(function () {
mockComposition.triggerCallback('add', mockCompObject1);
mockComposition.triggerCallback('add', mockCompObject2);
mockComposition.triggerCallback('load');
});
mockComposition.triggerCallback.and.callFake(function (event, obj) {
if (event === 'add') {
mockEventCallbacks.add(obj);
} else if (event === 'remove') {
mockEventCallbacks.remove(obj.identifier);
} else {
mockEventCallbacks[event]();
}
});
telemetryRequests = [];
mockTelemetryAPI = jasmine.createSpyObj('telemetryAPI', [
'request',
'isTelemetryObject',
'getMetadata',
'subscribe',
'triggerTelemetryCallback'
]);
mockTelemetryAPI.request.and.callFake(function (obj) {
const req = {
object: obj
};
req.promise = new Promise(function (resolve, reject) {
req.resolve = resolve;
req.reject = reject;
});
telemetryRequests.push(req);
return req.promise;
});
mockTelemetryAPI.isTelemetryObject.and.returnValue(true);
mockTelemetryAPI.getMetadata.and.callFake(function (obj) {
return mockMetadataManagers[obj.identifier.key];
});
mockTelemetryAPI.subscribe.and.callFake(function (obj, callback) {
mockTelemetryCallbacks[obj.identifier.key] = callback;
return unsubscribeSpies[obj.identifier.key];
});
mockTelemetryAPI.triggerTelemetryCallback.and.callFake(function (key) {
mockTelemetryCallbacks[key](mockTelemetryValues2[key]);
});
mockOpenMCT = {
telemetry: mockTelemetryAPI,
composition: {}
};
mockOpenMCT.composition.get = jasmine.createSpy('get').and.returnValue(mockComposition);
loadCallbackSpy = jasmine.createSpy('loadCallbackSpy');
addCallbackSpy = jasmine.createSpy('addCallbackSpy');
removeCallbackSpy = jasmine.createSpy('removeCallbackSpy');
metadataCallbackSpy = jasmine.createSpy('metadataCallbackSpy');
telemetryCallbackSpy = jasmine.createSpy('telemetryCallbackSpy');
conditionManager = new ConditionManager(mockDomainObject, mockOpenMCT);
conditionManager.on('load', loadCallbackSpy);
conditionManager.on('add', addCallbackSpy);
conditionManager.on('remove', removeCallbackSpy);
conditionManager.on('metadata', metadataCallbackSpy);
conditionManager.on('receiveTelemetry', telemetryCallbackSpy);
mockConditionEvaluator = jasmine.createSpy('mockConditionEvaluator');
mockConditionEvaluator.execute = jasmine.createSpy('execute');
conditionManager.evaluator = mockConditionEvaluator;
});
it('loads the initial composition and invokes the appropriate handlers', function () {
mockComposition.triggerCallback('load');
expect(conditionManager.getComposition()).toEqual({
mockCompObject1: mockCompObject1,
mockCompObject2: mockCompObject2
});
expect(loadCallbackSpy).toHaveBeenCalled();
expect(conditionManager.loadCompleted()).toEqual(true);
});
it('loads metadata from composition and gets it upon request', function () {
expect(conditionManager.getTelemetryMetadata('mockCompObject1')).toEqual(
mockMetadata.mockCompObject1
);
expect(conditionManager.getTelemetryMetadata('mockCompObject2')).toEqual(
mockMetadata.mockCompObject2
);
});
it('gets the human-readable name of a composition object', function () {
expect(conditionManager.getObjectName('mockCompObject1')).toEqual('Object 1');
expect(conditionManager.getObjectName('all')).toEqual('all Telemetry');
});
it('maintains lists of global metadata, and does not duplicate repeated fields', function () {
const allKeys = {
property1: {
key: 'property1',
name: 'Property 1',
format: 'string',
hints: {}
},
property2: {
key: 'property2',
name: 'Property 2',
hints: {
domain: 1
}
},
property3: {
key: 'property3',
name: 'Property 3',
format: 'string',
hints: {}
},
property4: {
key: 'property4',
name: 'Property 4',
hints: {
range: 1
}
}
};
expect(conditionManager.getTelemetryMetadata('all')).toEqual(allKeys);
expect(conditionManager.getTelemetryMetadata('any')).toEqual(allKeys);
mockComposition.triggerCallback('add', mockCompObject3);
expect(conditionManager.getTelemetryMetadata('all')).toEqual(allKeys);
expect(conditionManager.getTelemetryMetadata('any')).toEqual(allKeys);
});
it('gets the human-readable name of a telemetry field', function () {
expect(conditionManager.getTelemetryPropertyName('mockCompObject1', 'property1')).toEqual(
'Property 1'
);
expect(conditionManager.getTelemetryPropertyName('mockCompObject2', 'property4')).toEqual(
'Property 4'
);
});
it('loads and gets telemetry property types', function () {
conditionManager.parseAllPropertyTypes();
expect(conditionManager.getTelemetryPropertyType('mockCompObject1', 'property1')).toEqual(
'string'
);
expect(conditionManager.getTelemetryPropertyType('mockCompObject2', 'property4')).toEqual(
'number'
);
expect(conditionManager.metadataLoadCompleted()).toEqual(true);
expect(metadataCallbackSpy).toHaveBeenCalled();
});
it('gets its associated ConditionEvaluator', function () {
expect(conditionManager.getEvaluator()).toEqual(mockConditionEvaluator);
});
it('allows forcing a receive telemetry event', function () {
conditionManager.triggerTelemetryCallback();
expect(telemetryCallbackSpy).toHaveBeenCalled();
it('responds to a composition add event and invokes the appropriate handlers', function () {
mockComposition.triggerCallback('add', mockCompObject3);
expect(addCallbackSpy).toHaveBeenCalledWith(mockCompObject3);
expect(conditionManager.getComposition()).toEqual({
mockCompObject1: mockCompObject1,
mockCompObject2: mockCompObject2,
mockCompObject3: mockCompObject3
});
});
it('responds to a composition remove event and invokes the appropriate handlers', function () {
mockComposition.triggerCallback('remove', mockCompObject2);
expect(removeCallbackSpy).toHaveBeenCalledWith({
key: 'mockCompObject2'
});
expect(unsubscribeSpies.mockCompObject2).toHaveBeenCalled();
expect(conditionManager.getComposition()).toEqual({
mockCompObject1: mockCompObject1
});
});
it('unregisters telemetry subscriptions and composition listeners on destroy', function () {
mockComposition.triggerCallback('add', mockCompObject3);
conditionManager.destroy();
Object.values(unsubscribeSpies).forEach(function (spy) {
expect(spy).toHaveBeenCalled();
});
Object.values(unregisterSpies).forEach(function (spy) {
expect(spy).toHaveBeenCalled();
});
});
xit('populates its LAD cache with historical data on load, if available', function (done) {
expect(telemetryRequests.length).toBe(2);
expect(telemetryRequests[0].object).toBe(mockCompObject1);
expect(telemetryRequests[1].object).toBe(mockCompObject2);
expect(telemetryCallbackSpy).not.toHaveBeenCalled();
telemetryCallbackSpy.and.callFake(function () {
if (telemetryCallbackSpy.calls.count() === 2) {
expect(conditionManager.subscriptionCache.mockCompObject1.property1).toEqual(
'Its a string'
);
expect(conditionManager.subscriptionCache.mockCompObject2.property4).toEqual(66);
done();
}
});
telemetryRequests[0].resolve([mockTelemetryValues.mockCompObject1]);
telemetryRequests[1].resolve([mockTelemetryValues.mockCompObject2]);
});
xit('updates its LAD cache upon receiving telemetry and invokes the appropriate handlers', function () {
mockTelemetryAPI.triggerTelemetryCallback('mockCompObject1');
expect(conditionManager.subscriptionCache.mockCompObject1.property1).toEqual(
'Its a different string'
);
mockTelemetryAPI.triggerTelemetryCallback('mockCompObject2');
expect(conditionManager.subscriptionCache.mockCompObject2.property4).toEqual(22);
expect(telemetryCallbackSpy).toHaveBeenCalled();
});
it(
'evaluates a set of rules and returns the id of the ' +
'last active rule, or the first if no rules are active',
function () {
const mockRuleOrder = ['default', 'rule0', 'rule1'];
const mockRules = {
default: {
getProperty: function () {}
},
rule0: {
getProperty: function () {}
},
rule1: {
getProperty: function () {}
}
};
mockConditionEvaluator.execute.and.returnValue(false);
expect(conditionManager.executeRules(mockRuleOrder, mockRules)).toEqual('default');
mockConditionEvaluator.execute.and.returnValue(true);
expect(conditionManager.executeRules(mockRuleOrder, mockRules)).toEqual('rule1');
}
);
it('gets the human-readable name of a composition object', function () {
expect(conditionManager.getObjectName('mockCompObject1')).toEqual('Object 1');
expect(conditionManager.getObjectName('all')).toEqual('all Telemetry');
});
it('gets the human-readable name of a telemetry field', function () {
expect(conditionManager.getTelemetryPropertyName('mockCompObject1', 'property1')).toEqual(
'Property 1'
);
expect(conditionManager.getTelemetryPropertyName('mockCompObject2', 'property4')).toEqual(
'Property 4'
);
});
it('gets its associated ConditionEvaluator', function () {
expect(conditionManager.getEvaluator()).toEqual(mockConditionEvaluator);
});
it('allows forcing a receive telemetry event', function () {
conditionManager.triggerTelemetryCallback();
expect(telemetryCallbackSpy).toHaveBeenCalled();
});
});

View File

@ -20,186 +20,186 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['../src/Condition'], function (Condition) {
describe('A summary widget condition', function () {
let testCondition;
let mockConfig;
let mockConditionManager;
let mockContainer;
let mockEvaluator;
let changeSpy;
let duplicateSpy;
let removeSpy;
let generateValuesSpy;
import Condition from '../src/Condition';
beforeEach(function () {
mockContainer = document.createElement('div');
describe('A summary widget condition', function () {
let testCondition;
let mockConfig;
let mockConditionManager;
let mockContainer;
let mockEvaluator;
let changeSpy;
let duplicateSpy;
let removeSpy;
let generateValuesSpy;
mockConfig = {
object: 'object1',
key: 'property1',
operation: 'operation1',
values: [1, 2, 3]
};
beforeEach(function () {
mockContainer = document.createElement('div');
mockEvaluator = {};
mockEvaluator.getInputCount = jasmine.createSpy('inputCount');
mockEvaluator.getInputType = jasmine.createSpy('inputType');
mockConfig = {
object: 'object1',
key: 'property1',
operation: 'operation1',
values: [1, 2, 3]
};
mockConditionManager = jasmine.createSpyObj('mockConditionManager', [
'on',
'getComposition',
'loadCompleted',
'getEvaluator',
'getTelemetryMetadata',
'metadataLoadCompleted',
'getObjectName',
'getTelemetryPropertyName'
]);
mockConditionManager.loadCompleted.and.returnValue(false);
mockConditionManager.metadataLoadCompleted.and.returnValue(false);
mockConditionManager.getEvaluator.and.returnValue(mockEvaluator);
mockConditionManager.getComposition.and.returnValue({});
mockConditionManager.getTelemetryMetadata.and.returnValue({});
mockConditionManager.getObjectName.and.returnValue('Object Name');
mockConditionManager.getTelemetryPropertyName.and.returnValue('Property Name');
mockEvaluator = {};
mockEvaluator.getInputCount = jasmine.createSpy('inputCount');
mockEvaluator.getInputType = jasmine.createSpy('inputType');
duplicateSpy = jasmine.createSpy('duplicate');
removeSpy = jasmine.createSpy('remove');
changeSpy = jasmine.createSpy('change');
generateValuesSpy = jasmine.createSpy('generateValueInputs');
mockConditionManager = jasmine.createSpyObj('mockConditionManager', [
'on',
'getComposition',
'loadCompleted',
'getEvaluator',
'getTelemetryMetadata',
'metadataLoadCompleted',
'getObjectName',
'getTelemetryPropertyName'
]);
mockConditionManager.loadCompleted.and.returnValue(false);
mockConditionManager.metadataLoadCompleted.and.returnValue(false);
mockConditionManager.getEvaluator.and.returnValue(mockEvaluator);
mockConditionManager.getComposition.and.returnValue({});
mockConditionManager.getTelemetryMetadata.and.returnValue({});
mockConditionManager.getObjectName.and.returnValue('Object Name');
mockConditionManager.getTelemetryPropertyName.and.returnValue('Property Name');
testCondition = new Condition(mockConfig, 54, mockConditionManager);
duplicateSpy = jasmine.createSpy('duplicate');
removeSpy = jasmine.createSpy('remove');
changeSpy = jasmine.createSpy('change');
generateValuesSpy = jasmine.createSpy('generateValueInputs');
testCondition.on('duplicate', duplicateSpy);
testCondition.on('remove', removeSpy);
testCondition.on('change', changeSpy);
testCondition = new Condition(mockConfig, 54, mockConditionManager);
testCondition.on('duplicate', duplicateSpy);
testCondition.on('remove', removeSpy);
testCondition.on('change', changeSpy);
});
it('exposes a DOM element to represent itself in the view', function () {
mockContainer.append(testCondition.getDOM());
expect(mockContainer.querySelectorAll('.t-condition').length).toEqual(1);
});
it('responds to a change in its object select', function () {
testCondition.selects.object.setSelected('');
expect(changeSpy).toHaveBeenCalledWith({
value: '',
property: 'object',
index: 54
});
});
it('exposes a DOM element to represent itself in the view', function () {
mockContainer.append(testCondition.getDOM());
expect(mockContainer.querySelectorAll('.t-condition').length).toEqual(1);
it('responds to a change in its key select', function () {
testCondition.selects.key.setSelected('');
expect(changeSpy).toHaveBeenCalledWith({
value: '',
property: 'key',
index: 54
});
});
it('responds to a change in its object select', function () {
testCondition.selects.object.setSelected('');
expect(changeSpy).toHaveBeenCalledWith({
value: '',
property: 'object',
index: 54
});
it('responds to a change in its operation select', function () {
testCondition.generateValueInputs = generateValuesSpy;
testCondition.selects.operation.setSelected('');
expect(changeSpy).toHaveBeenCalledWith({
value: '',
property: 'operation',
index: 54
});
expect(generateValuesSpy).toHaveBeenCalledWith('');
});
it('responds to a change in its key select', function () {
testCondition.selects.key.setSelected('');
expect(changeSpy).toHaveBeenCalledWith({
value: '',
property: 'key',
index: 54
});
it('generates value inputs of the appropriate type and quantity', function () {
let inputs;
mockContainer.append(testCondition.getDOM());
mockEvaluator.getInputType.and.returnValue('number');
mockEvaluator.getInputCount.and.returnValue(3);
testCondition.generateValueInputs('');
inputs = mockContainer.querySelectorAll('input');
const numberInputs = Array.from(inputs).filter((input) => input.type === 'number');
expect(numberInputs.length).toEqual(3);
expect(numberInputs[0].valueAsNumber).toEqual(1);
expect(numberInputs[1].valueAsNumber).toEqual(2);
expect(numberInputs[2].valueAsNumber).toEqual(3);
mockEvaluator.getInputType.and.returnValue('text');
mockEvaluator.getInputCount.and.returnValue(2);
testCondition.config.values = ['Text I Am', 'Text It Is'];
testCondition.generateValueInputs('');
inputs = mockContainer.querySelectorAll('input');
const textInputs = Array.from(inputs).filter((input) => input.type === 'text');
expect(textInputs.length).toEqual(2);
expect(textInputs[0].value).toEqual('Text I Am');
expect(textInputs[1].value).toEqual('Text It Is');
});
it('ensures reasonable defaults on values if none are provided', function () {
let inputs;
mockContainer.append(testCondition.getDOM());
mockEvaluator.getInputType.and.returnValue('number');
mockEvaluator.getInputCount.and.returnValue(3);
testCondition.config.values = [];
testCondition.generateValueInputs('');
inputs = Array.from(mockContainer.querySelectorAll('input'));
expect(inputs[0].valueAsNumber).toEqual(0);
expect(inputs[1].valueAsNumber).toEqual(0);
expect(inputs[2].valueAsNumber).toEqual(0);
expect(testCondition.config.values).toEqual([0, 0, 0]);
mockEvaluator.getInputType.and.returnValue('text');
mockEvaluator.getInputCount.and.returnValue(2);
testCondition.config.values = [];
testCondition.generateValueInputs('');
inputs = Array.from(mockContainer.querySelectorAll('input'));
expect(inputs[0].value).toEqual('');
expect(inputs[1].value).toEqual('');
expect(testCondition.config.values).toEqual(['', '']);
});
it('responds to a change in its value inputs', function () {
mockContainer.append(testCondition.getDOM());
mockEvaluator.getInputType.and.returnValue('number');
mockEvaluator.getInputCount.and.returnValue(3);
testCondition.generateValueInputs('');
const event = new Event('input', {
bubbles: true,
cancelable: true
});
const inputs = mockContainer.querySelectorAll('input');
it('responds to a change in its operation select', function () {
testCondition.generateValueInputs = generateValuesSpy;
testCondition.selects.operation.setSelected('');
expect(changeSpy).toHaveBeenCalledWith({
value: '',
property: 'operation',
index: 54
});
expect(generateValuesSpy).toHaveBeenCalledWith('');
inputs[1].value = 9001;
inputs[1].dispatchEvent(event);
expect(changeSpy).toHaveBeenCalledWith({
value: 9001,
property: 'values[1]',
index: 54
});
});
it('generates value inputs of the appropriate type and quantity', function () {
let inputs;
it('can remove itself from the configuration', function () {
testCondition.remove();
expect(removeSpy).toHaveBeenCalledWith(54);
});
mockContainer.append(testCondition.getDOM());
mockEvaluator.getInputType.and.returnValue('number');
mockEvaluator.getInputCount.and.returnValue(3);
testCondition.generateValueInputs('');
inputs = mockContainer.querySelectorAll('input');
const numberInputs = Array.from(inputs).filter((input) => input.type === 'number');
expect(numberInputs.length).toEqual(3);
expect(numberInputs[0].valueAsNumber).toEqual(1);
expect(numberInputs[1].valueAsNumber).toEqual(2);
expect(numberInputs[2].valueAsNumber).toEqual(3);
mockEvaluator.getInputType.and.returnValue('text');
mockEvaluator.getInputCount.and.returnValue(2);
testCondition.config.values = ['Text I Am', 'Text It Is'];
testCondition.generateValueInputs('');
inputs = mockContainer.querySelectorAll('input');
const textInputs = Array.from(inputs).filter((input) => input.type === 'text');
expect(textInputs.length).toEqual(2);
expect(textInputs[0].value).toEqual('Text I Am');
expect(textInputs[1].value).toEqual('Text It Is');
});
it('ensures reasonable defaults on values if none are provided', function () {
let inputs;
mockContainer.append(testCondition.getDOM());
mockEvaluator.getInputType.and.returnValue('number');
mockEvaluator.getInputCount.and.returnValue(3);
testCondition.config.values = [];
testCondition.generateValueInputs('');
inputs = Array.from(mockContainer.querySelectorAll('input'));
expect(inputs[0].valueAsNumber).toEqual(0);
expect(inputs[1].valueAsNumber).toEqual(0);
expect(inputs[2].valueAsNumber).toEqual(0);
expect(testCondition.config.values).toEqual([0, 0, 0]);
mockEvaluator.getInputType.and.returnValue('text');
mockEvaluator.getInputCount.and.returnValue(2);
testCondition.config.values = [];
testCondition.generateValueInputs('');
inputs = Array.from(mockContainer.querySelectorAll('input'));
expect(inputs[0].value).toEqual('');
expect(inputs[1].value).toEqual('');
expect(testCondition.config.values).toEqual(['', '']);
});
it('responds to a change in its value inputs', function () {
mockContainer.append(testCondition.getDOM());
mockEvaluator.getInputType.and.returnValue('number');
mockEvaluator.getInputCount.and.returnValue(3);
testCondition.generateValueInputs('');
const event = new Event('input', {
bubbles: true,
cancelable: true
});
const inputs = mockContainer.querySelectorAll('input');
inputs[1].value = 9001;
inputs[1].dispatchEvent(event);
expect(changeSpy).toHaveBeenCalledWith({
value: 9001,
property: 'values[1]',
index: 54
});
});
it('can remove itself from the configuration', function () {
testCondition.remove();
expect(removeSpy).toHaveBeenCalledWith(54);
});
it('can duplicate itself', function () {
testCondition.duplicate();
expect(duplicateSpy).toHaveBeenCalledWith({
sourceCondition: mockConfig,
index: 54
});
it('can duplicate itself', function () {
testCondition.duplicate();
expect(duplicateSpy).toHaveBeenCalledWith({
sourceCondition: mockConfig,
index: 54
});
});
});

View File

@ -1,143 +1,53 @@
define(['../src/Rule'], function (Rule) {
describe('A Summary Widget Rule', function () {
let mockRuleConfig;
let mockDomainObject;
let mockOpenMCT;
let mockConditionManager;
let mockWidgetDnD;
let mockEvaluator;
let mockContainer;
let testRule;
let removeSpy;
let duplicateSpy;
let changeSpy;
let conditionChangeSpy;
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, 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.
*****************************************************************************/
beforeEach(function () {
mockRuleConfig = {
name: 'Name',
id: 'mockRule',
icon: 'test-icon-name',
style: {
'background-color': '',
'border-color': '',
color: ''
},
expanded: true,
conditions: [
{
object: '',
key: '',
operation: '',
values: []
},
{
object: 'blah',
key: 'blah',
operation: 'blah',
values: ['blah.', 'blah!', 'blah?']
}
]
};
mockDomainObject = {
configuration: {
ruleConfigById: {
mockRule: mockRuleConfig,
otherRule: {}
},
ruleOrder: ['default', 'mockRule', 'otherRule']
}
};
import Rule from '../src/Rule';
mockOpenMCT = {};
mockOpenMCT.objects = {};
mockOpenMCT.objects.mutate = jasmine.createSpy('mutate');
describe('A Summary Widget Rule', function () {
let mockRuleConfig;
let mockDomainObject;
let mockOpenMCT;
let mockConditionManager;
let mockWidgetDnD;
let mockEvaluator;
let mockContainer;
let testRule;
let removeSpy;
let duplicateSpy;
let changeSpy;
let conditionChangeSpy;
mockEvaluator = {};
mockEvaluator.getOperationDescription = jasmine
.createSpy('evaluator')
.and.returnValue('Operation Description');
mockConditionManager = jasmine.createSpyObj('mockConditionManager', [
'on',
'getComposition',
'loadCompleted',
'getEvaluator',
'getTelemetryMetadata',
'metadataLoadCompleted',
'getObjectName',
'getTelemetryPropertyName'
]);
mockConditionManager.loadCompleted.and.returnValue(false);
mockConditionManager.metadataLoadCompleted.and.returnValue(false);
mockConditionManager.getEvaluator.and.returnValue(mockEvaluator);
mockConditionManager.getComposition.and.returnValue({});
mockConditionManager.getTelemetryMetadata.and.returnValue({});
mockConditionManager.getObjectName.and.returnValue('Object Name');
mockConditionManager.getTelemetryPropertyName.and.returnValue('Property Name');
mockWidgetDnD = jasmine.createSpyObj('dnd', ['on', 'setDragImage', 'dragStart']);
mockContainer = document.createElement('div');
removeSpy = jasmine.createSpy('removeCallback');
duplicateSpy = jasmine.createSpy('duplicateCallback');
changeSpy = jasmine.createSpy('changeCallback');
conditionChangeSpy = jasmine.createSpy('conditionChangeCallback');
testRule = new Rule(
mockRuleConfig,
mockDomainObject,
mockOpenMCT,
mockConditionManager,
mockWidgetDnD
);
testRule.on('remove', removeSpy);
testRule.on('duplicate', duplicateSpy);
testRule.on('change', changeSpy);
testRule.on('conditionChange', conditionChangeSpy);
});
it('closes its configuration panel on initial load', function () {
expect(testRule.getProperty('expanded')).toEqual(false);
});
it('gets its DOM element', function () {
mockContainer.append(testRule.getDOM());
expect(mockContainer.querySelectorAll('.l-widget-rule').length).toBeGreaterThan(0);
});
it('gets its configuration properties', function () {
expect(testRule.getProperty('name')).toEqual('Name');
expect(testRule.getProperty('icon')).toEqual('test-icon-name');
});
it('can duplicate itself', function () {
testRule.duplicate();
mockRuleConfig.expanded = true;
expect(duplicateSpy).toHaveBeenCalledWith(mockRuleConfig);
});
it('can remove itself from the configuration', function () {
testRule.remove();
expect(removeSpy).toHaveBeenCalled();
expect(mockDomainObject.configuration.ruleConfigById.mockRule).not.toBeDefined();
expect(mockDomainObject.configuration.ruleOrder).toEqual(['default', 'otherRule']);
});
it('updates its configuration on a condition change and invokes callbacks', function () {
testRule.onConditionChange({
value: 'newValue',
property: 'object',
index: 0
});
expect(testRule.getProperty('conditions')[0].object).toEqual('newValue');
expect(conditionChangeSpy).toHaveBeenCalled();
});
it('allows initializing a new condition with a default configuration', function () {
testRule.initCondition();
expect(mockRuleConfig.conditions).toEqual([
beforeEach(function () {
mockRuleConfig = {
name: 'Name',
id: 'mockRule',
icon: 'test-icon-name',
style: {
'background-color': '',
'border-color': '',
color: ''
},
expanded: true,
conditions: [
{
object: '',
key: '',
@ -149,145 +59,257 @@ define(['../src/Rule'], function (Rule) {
key: 'blah',
operation: 'blah',
values: ['blah.', 'blah!', 'blah?']
},
{
object: '',
key: '',
operation: '',
values: []
}
]);
});
it('allows initializing a new condition from a given configuration', function () {
testRule.initCondition({
sourceCondition: {
object: 'object1',
key: 'key1',
operation: 'operation1',
values: [1, 2, 3]
]
};
mockDomainObject = {
configuration: {
ruleConfigById: {
mockRule: mockRuleConfig,
otherRule: {}
},
index: 0
});
expect(mockRuleConfig.conditions).toEqual([
{
object: '',
key: '',
operation: '',
values: []
},
{
object: 'object1',
key: 'key1',
operation: 'operation1',
values: [1, 2, 3]
},
{
object: 'blah',
key: 'blah',
operation: 'blah',
values: ['blah.', 'blah!', 'blah?']
}
]);
ruleOrder: ['default', 'mockRule', 'otherRule']
}
};
mockOpenMCT = {};
mockOpenMCT.objects = {};
mockOpenMCT.objects.mutate = jasmine.createSpy('mutate');
mockEvaluator = {};
mockEvaluator.getOperationDescription = jasmine
.createSpy('evaluator')
.and.returnValue('Operation Description');
mockConditionManager = jasmine.createSpyObj('mockConditionManager', [
'on',
'getComposition',
'loadCompleted',
'getEvaluator',
'getTelemetryMetadata',
'metadataLoadCompleted',
'getObjectName',
'getTelemetryPropertyName'
]);
mockConditionManager.loadCompleted.and.returnValue(false);
mockConditionManager.metadataLoadCompleted.and.returnValue(false);
mockConditionManager.getEvaluator.and.returnValue(mockEvaluator);
mockConditionManager.getComposition.and.returnValue({});
mockConditionManager.getTelemetryMetadata.and.returnValue({});
mockConditionManager.getObjectName.and.returnValue('Object Name');
mockConditionManager.getTelemetryPropertyName.and.returnValue('Property Name');
mockWidgetDnD = jasmine.createSpyObj('dnd', ['on', 'setDragImage', 'dragStart']);
mockContainer = document.createElement('div');
removeSpy = jasmine.createSpy('removeCallback');
duplicateSpy = jasmine.createSpy('duplicateCallback');
changeSpy = jasmine.createSpy('changeCallback');
conditionChangeSpy = jasmine.createSpy('conditionChangeCallback');
testRule = new Rule(
mockRuleConfig,
mockDomainObject,
mockOpenMCT,
mockConditionManager,
mockWidgetDnD
);
testRule.on('remove', removeSpy);
testRule.on('duplicate', duplicateSpy);
testRule.on('change', changeSpy);
testRule.on('conditionChange', conditionChangeSpy);
});
it('closes its configuration panel on initial load', function () {
expect(testRule.getProperty('expanded')).toEqual(false);
});
it('gets its DOM element', function () {
mockContainer.append(testRule.getDOM());
expect(mockContainer.querySelectorAll('.l-widget-rule').length).toBeGreaterThan(0);
});
it('gets its configuration properties', function () {
expect(testRule.getProperty('name')).toEqual('Name');
expect(testRule.getProperty('icon')).toEqual('test-icon-name');
});
it('can duplicate itself', function () {
testRule.duplicate();
mockRuleConfig.expanded = true;
expect(duplicateSpy).toHaveBeenCalledWith(mockRuleConfig);
});
it('can remove itself from the configuration', function () {
testRule.remove();
expect(removeSpy).toHaveBeenCalled();
expect(mockDomainObject.configuration.ruleConfigById.mockRule).not.toBeDefined();
expect(mockDomainObject.configuration.ruleOrder).toEqual(['default', 'otherRule']);
});
it('updates its configuration on a condition change and invokes callbacks', function () {
testRule.onConditionChange({
value: 'newValue',
property: 'object',
index: 0
});
expect(testRule.getProperty('conditions')[0].object).toEqual('newValue');
expect(conditionChangeSpy).toHaveBeenCalled();
});
it('invokes mutate when updating the domain object', function () {
testRule.updateDomainObject();
expect(mockOpenMCT.objects.mutate).toHaveBeenCalled();
it('allows initializing a new condition with a default configuration', function () {
testRule.initCondition();
expect(mockRuleConfig.conditions).toEqual([
{
object: '',
key: '',
operation: '',
values: []
},
{
object: 'blah',
key: 'blah',
operation: 'blah',
values: ['blah.', 'blah!', 'blah?']
},
{
object: '',
key: '',
operation: '',
values: []
}
]);
});
it('allows initializing a new condition from a given configuration', function () {
testRule.initCondition({
sourceCondition: {
object: 'object1',
key: 'key1',
operation: 'operation1',
values: [1, 2, 3]
},
index: 0
});
expect(mockRuleConfig.conditions).toEqual([
{
object: '',
key: '',
operation: '',
values: []
},
{
object: 'object1',
key: 'key1',
operation: 'operation1',
values: [1, 2, 3]
},
{
object: 'blah',
key: 'blah',
operation: 'blah',
values: ['blah.', 'blah!', 'blah?']
}
]);
});
it('builds condition view from condition configuration', function () {
mockContainer.append(testRule.getDOM());
expect(mockContainer.querySelectorAll('.t-condition').length).toEqual(2);
it('invokes mutate when updating the domain object', function () {
testRule.updateDomainObject();
expect(mockOpenMCT.objects.mutate).toHaveBeenCalled();
});
it('builds condition view from condition configuration', function () {
mockContainer.append(testRule.getDOM());
expect(mockContainer.querySelectorAll('.t-condition').length).toEqual(2);
});
it('responds to input of style properties, and updates the preview', function () {
testRule.colorInputs['background-color'].set('#434343');
expect(mockRuleConfig.style['background-color']).toEqual('#434343');
testRule.colorInputs['border-color'].set('#666666');
expect(mockRuleConfig.style['border-color']).toEqual('#666666');
testRule.colorInputs.color.set('#999999');
expect(mockRuleConfig.style.color).toEqual('#999999');
expect(testRule.thumbnail.style['background-color']).toEqual('rgb(67, 67, 67)');
expect(testRule.thumbnail.style['border-color']).toEqual('rgb(102, 102, 102)');
expect(testRule.thumbnail.style.color).toEqual('rgb(153, 153, 153)');
expect(changeSpy).toHaveBeenCalled();
});
it('responds to input for the icon property', function () {
testRule.iconInput.set('icon-alert-rect');
expect(mockRuleConfig.icon).toEqual('icon-alert-rect');
expect(changeSpy).toHaveBeenCalled();
});
/*
test for js condition commented out for v1
*/
// it('responds to input of text properties', function () {
// var testInputs = ['name', 'label', 'message', 'jsCondition'],
// input;
// testInputs.forEach(function (key) {
// input = testRule.textInputs[key];
// input.prop('value', 'A new ' + key);
// input.trigger('input');
// expect(mockRuleConfig[key]).toEqual('A new ' + key);
// });
// expect(changeSpy).toHaveBeenCalled();
// });
it('allows input for when the rule triggers', function () {
testRule.trigger.value = 'all';
const event = new Event('change', {
bubbles: true,
cancelable: true
});
testRule.trigger.dispatchEvent(event);
expect(testRule.config.trigger).toEqual('all');
expect(conditionChangeSpy).toHaveBeenCalled();
});
it('responds to input of style properties, and updates the preview', function () {
testRule.colorInputs['background-color'].set('#434343');
expect(mockRuleConfig.style['background-color']).toEqual('#434343');
testRule.colorInputs['border-color'].set('#666666');
expect(mockRuleConfig.style['border-color']).toEqual('#666666');
testRule.colorInputs.color.set('#999999');
expect(mockRuleConfig.style.color).toEqual('#999999');
it('generates a human-readable description from its conditions', function () {
testRule.generateDescription();
expect(testRule.config.description).toContain(
"Object Name's Property Name Operation Description"
);
testRule.config.trigger = 'js';
testRule.generateDescription();
expect(testRule.config.description).toContain(
'when a custom JavaScript condition evaluates to true'
);
});
expect(testRule.thumbnail.style['background-color']).toEqual('rgb(67, 67, 67)');
expect(testRule.thumbnail.style['border-color']).toEqual('rgb(102, 102, 102)');
expect(testRule.thumbnail.style.color).toEqual('rgb(153, 153, 153)');
expect(changeSpy).toHaveBeenCalled();
it('initiates a drag event when its grippy is clicked', function () {
const event = new Event('mousedown', {
bubbles: true,
cancelable: true
});
testRule.grippy.dispatchEvent(event);
it('responds to input for the icon property', function () {
testRule.iconInput.set('icon-alert-rect');
expect(mockRuleConfig.icon).toEqual('icon-alert-rect');
expect(changeSpy).toHaveBeenCalled();
});
expect(mockWidgetDnD.setDragImage).toHaveBeenCalled();
expect(mockWidgetDnD.dragStart).toHaveBeenCalledWith('mockRule');
});
/*
test for js condition commented out for v1
*/
/*
test for js condition commented out for v1
*/
// it('responds to input of text properties', function () {
// var testInputs = ['name', 'label', 'message', 'jsCondition'],
// input;
// testInputs.forEach(function (key) {
// input = testRule.textInputs[key];
// input.prop('value', 'A new ' + key);
// input.trigger('input');
// expect(mockRuleConfig[key]).toEqual('A new ' + key);
// });
// expect(changeSpy).toHaveBeenCalled();
// });
it('allows input for when the rule triggers', function () {
testRule.trigger.value = 'all';
const event = new Event('change', {
bubbles: true,
cancelable: true
});
testRule.trigger.dispatchEvent(event);
expect(testRule.config.trigger).toEqual('all');
expect(conditionChangeSpy).toHaveBeenCalled();
});
it('generates a human-readable description from its conditions', function () {
testRule.generateDescription();
expect(testRule.config.description).toContain(
"Object Name's Property Name Operation Description"
);
testRule.config.trigger = 'js';
testRule.generateDescription();
expect(testRule.config.description).toContain(
'when a custom JavaScript condition evaluates to true'
);
});
it('initiates a drag event when its grippy is clicked', function () {
const event = new Event('mousedown', {
bubbles: true,
cancelable: true
});
testRule.grippy.dispatchEvent(event);
expect(mockWidgetDnD.setDragImage).toHaveBeenCalled();
expect(mockWidgetDnD.dragStart).toHaveBeenCalledWith('mockRule');
});
/*
test for js condition commented out for v1
*/
it('can remove a condition from its configuration', function () {
testRule.removeCondition(0);
expect(testRule.config.conditions).toEqual([
{
object: 'blah',
key: 'blah',
operation: 'blah',
values: ['blah.', 'blah!', 'blah?']
}
]);
});
it('can remove a condition from its configuration', function () {
testRule.removeCondition(0);
expect(testRule.config.conditions).toEqual([
{
object: 'blah',
key: 'blah',
operation: 'blah',
values: ['blah.', 'blah!', 'blah?']
}
]);
});
});

View File

@ -20,173 +20,173 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['../src/SummaryWidget'], function (SummaryWidget) {
describe('The Summary Widget', function () {
let summaryWidget;
let mockDomainObject;
let mockOldDomainObject;
let mockOpenMCT;
let mockObjectService;
let mockStatusCapability;
let mockComposition;
let mockContainer;
let listenCallback;
let listenCallbackSpy;
import SummaryWidget from '../src/SummaryWidget';
beforeEach(function () {
mockDomainObject = {
identifier: {
key: 'testKey',
namespace: 'testNamespace'
},
name: 'testName',
composition: [],
configuration: {}
};
mockComposition = jasmine.createSpyObj('composition', ['on', 'off', 'load']);
mockStatusCapability = jasmine.createSpyObj('statusCapability', [
'get',
'listen',
'triggerCallback'
]);
describe('The Summary Widget', function () {
let summaryWidget;
let mockDomainObject;
let mockOldDomainObject;
let mockOpenMCT;
let mockObjectService;
let mockStatusCapability;
let mockComposition;
let mockContainer;
let listenCallback;
let listenCallbackSpy;
listenCallbackSpy = jasmine.createSpy('listenCallbackSpy', function () {});
mockStatusCapability.get.and.returnValue([]);
mockStatusCapability.listen.and.callFake(function (callback) {
listenCallback = callback;
beforeEach(function () {
mockDomainObject = {
identifier: {
key: 'testKey',
namespace: 'testNamespace'
},
name: 'testName',
composition: [],
configuration: {}
};
mockComposition = jasmine.createSpyObj('composition', ['on', 'off', 'load']);
mockStatusCapability = jasmine.createSpyObj('statusCapability', [
'get',
'listen',
'triggerCallback'
]);
return listenCallbackSpy;
});
mockStatusCapability.triggerCallback.and.callFake(function () {
listenCallback(['editing']);
});
listenCallbackSpy = jasmine.createSpy('listenCallbackSpy', function () {});
mockStatusCapability.get.and.returnValue([]);
mockStatusCapability.listen.and.callFake(function (callback) {
listenCallback = callback;
mockOldDomainObject = {};
mockOldDomainObject.getCapability = jasmine.createSpy('capability');
mockOldDomainObject.getCapability.and.returnValue(mockStatusCapability);
mockObjectService = {};
mockObjectService.getObjects = jasmine.createSpy('objectService');
mockObjectService.getObjects.and.returnValue(
new Promise(function (resolve, reject) {
resolve({
'testNamespace:testKey': mockOldDomainObject
});
})
);
mockOpenMCT = jasmine.createSpyObj('openmct', ['$injector', 'composition', 'objects']);
mockOpenMCT.$injector.get = jasmine.createSpy('get');
mockOpenMCT.$injector.get.and.returnValue(mockObjectService);
mockOpenMCT.composition = jasmine.createSpyObj('composition', ['get', 'on']);
mockOpenMCT.composition.get.and.returnValue(mockComposition);
mockOpenMCT.objects.mutate = jasmine.createSpy('mutate');
mockOpenMCT.objects.observe = jasmine.createSpy('observe');
mockOpenMCT.objects.observe.and.returnValue(function () {});
summaryWidget = new SummaryWidget(mockDomainObject, mockOpenMCT);
mockContainer = document.createElement('div');
summaryWidget.show(mockContainer);
return listenCallbackSpy;
});
mockStatusCapability.triggerCallback.and.callFake(function () {
listenCallback(['editing']);
});
xit('queries with legacyId', function () {
expect(mockObjectService.getObjects).toHaveBeenCalledWith(['testNamespace:testKey']);
mockOldDomainObject = {};
mockOldDomainObject.getCapability = jasmine.createSpy('capability');
mockOldDomainObject.getCapability.and.returnValue(mockStatusCapability);
mockObjectService = {};
mockObjectService.getObjects = jasmine.createSpy('objectService');
mockObjectService.getObjects.and.returnValue(
new Promise(function (resolve, reject) {
resolve({
'testNamespace:testKey': mockOldDomainObject
});
})
);
mockOpenMCT = jasmine.createSpyObj('openmct', ['$injector', 'composition', 'objects']);
mockOpenMCT.$injector.get = jasmine.createSpy('get');
mockOpenMCT.$injector.get.and.returnValue(mockObjectService);
mockOpenMCT.composition = jasmine.createSpyObj('composition', ['get', 'on']);
mockOpenMCT.composition.get.and.returnValue(mockComposition);
mockOpenMCT.objects.mutate = jasmine.createSpy('mutate');
mockOpenMCT.objects.observe = jasmine.createSpy('observe');
mockOpenMCT.objects.observe.and.returnValue(function () {});
summaryWidget = new SummaryWidget(mockDomainObject, mockOpenMCT);
mockContainer = document.createElement('div');
summaryWidget.show(mockContainer);
});
xit('queries with legacyId', function () {
expect(mockObjectService.getObjects).toHaveBeenCalledWith(['testNamespace:testKey']);
});
it('adds its DOM element to the view', function () {
expect(mockContainer.getElementsByClassName('w-summary-widget').length).toBeGreaterThan(0);
});
it('initializes a default rule', function () {
expect(mockDomainObject.configuration.ruleConfigById.default).toBeDefined();
expect(mockDomainObject.configuration.ruleOrder).toEqual(['default']);
});
it('builds rules and rule placeholders in view from configuration', function () {
expect(summaryWidget.ruleArea.querySelectorAll('.l-widget-rule').length).toEqual(2);
});
it('allows initializing a new rule with a particular identifier', function () {
summaryWidget.initRule('rule0', 'Rule');
expect(mockDomainObject.configuration.ruleConfigById.rule0).toBeDefined();
});
it('allows adding a new rule with a unique identifier to the configuration and view', function () {
summaryWidget.addRule();
expect(mockDomainObject.configuration.ruleOrder.length).toEqual(2);
mockDomainObject.configuration.ruleOrder.forEach(function (ruleId) {
expect(mockDomainObject.configuration.ruleConfigById[ruleId]).toBeDefined();
});
it('adds its DOM element to the view', function () {
expect(mockContainer.getElementsByClassName('w-summary-widget').length).toBeGreaterThan(0);
summaryWidget.addRule();
expect(mockDomainObject.configuration.ruleOrder.length).toEqual(3);
mockDomainObject.configuration.ruleOrder.forEach(function (ruleId) {
expect(mockDomainObject.configuration.ruleConfigById[ruleId]).toBeDefined();
});
expect(summaryWidget.ruleArea.querySelectorAll('.l-widget-rule').length).toEqual(6);
});
it('initializes a default rule', function () {
expect(mockDomainObject.configuration.ruleConfigById.default).toBeDefined();
expect(mockDomainObject.configuration.ruleOrder).toEqual(['default']);
it('allows duplicating a rule from source configuration', function () {
const sourceConfig = JSON.parse(
JSON.stringify(mockDomainObject.configuration.ruleConfigById.default)
);
summaryWidget.duplicateRule(sourceConfig);
expect(Object.keys(mockDomainObject.configuration.ruleConfigById).length).toEqual(2);
});
it('does not duplicate an existing rule in the configuration', function () {
summaryWidget.initRule('default', 'Default');
expect(Object.keys(mockDomainObject.configuration.ruleConfigById).length).toEqual(1);
});
it('uses mutate when updating the domain object only when in edit mode', function () {
summaryWidget.editing = true;
summaryWidget.updateDomainObject();
expect(mockOpenMCT.objects.mutate).toHaveBeenCalled();
});
xit('shows configuration interfaces when in edit mode, and hides them otherwise', function () {
setTimeout(function () {
summaryWidget.onEdit([]);
expect(summaryWidget.editing).toEqual(false);
expect(summaryWidget.ruleArea.css('display')).toEqual('none');
expect(summaryWidget.testDataArea.css('display')).toEqual('none');
expect(summaryWidget.addRuleButton.css('display')).toEqual('none');
summaryWidget.onEdit(['editing']);
expect(summaryWidget.editing).toEqual(true);
expect(summaryWidget.ruleArea.css('display')).not.toEqual('none');
expect(summaryWidget.testDataArea.css('display')).not.toEqual('none');
expect(summaryWidget.addRuleButton.css('display')).not.toEqual('none');
}, 100);
});
xit('unregisters any registered listeners on a destroy', function () {
setTimeout(function () {
summaryWidget.destroy();
expect(listenCallbackSpy).toHaveBeenCalled();
}, 100);
});
it('allows reorders of rules', function () {
summaryWidget.initRule('rule0');
summaryWidget.initRule('rule1');
summaryWidget.domainObject.configuration.ruleOrder = ['default', 'rule0', 'rule1'];
summaryWidget.reorder({
draggingId: 'rule1',
dropTarget: 'default'
});
expect(summaryWidget.domainObject.configuration.ruleOrder).toEqual([
'default',
'rule1',
'rule0'
]);
});
it('builds rules and rule placeholders in view from configuration', function () {
expect(summaryWidget.ruleArea.querySelectorAll('.l-widget-rule').length).toEqual(2);
});
it('adds hyperlink to the widget button and sets newTab preference', function () {
summaryWidget.addHyperlink('https://www.nasa.gov', 'newTab');
it('allows initializing a new rule with a particular identifier', function () {
summaryWidget.initRule('rule0', 'Rule');
expect(mockDomainObject.configuration.ruleConfigById.rule0).toBeDefined();
});
const widgetButton = mockContainer.querySelector('#widget');
it('allows adding a new rule with a unique identifier to the configuration and view', function () {
summaryWidget.addRule();
expect(mockDomainObject.configuration.ruleOrder.length).toEqual(2);
mockDomainObject.configuration.ruleOrder.forEach(function (ruleId) {
expect(mockDomainObject.configuration.ruleConfigById[ruleId]).toBeDefined();
});
summaryWidget.addRule();
expect(mockDomainObject.configuration.ruleOrder.length).toEqual(3);
mockDomainObject.configuration.ruleOrder.forEach(function (ruleId) {
expect(mockDomainObject.configuration.ruleConfigById[ruleId]).toBeDefined();
});
expect(summaryWidget.ruleArea.querySelectorAll('.l-widget-rule').length).toEqual(6);
});
it('allows duplicating a rule from source configuration', function () {
const sourceConfig = JSON.parse(
JSON.stringify(mockDomainObject.configuration.ruleConfigById.default)
);
summaryWidget.duplicateRule(sourceConfig);
expect(Object.keys(mockDomainObject.configuration.ruleConfigById).length).toEqual(2);
});
it('does not duplicate an existing rule in the configuration', function () {
summaryWidget.initRule('default', 'Default');
expect(Object.keys(mockDomainObject.configuration.ruleConfigById).length).toEqual(1);
});
it('uses mutate when updating the domain object only when in edit mode', function () {
summaryWidget.editing = true;
summaryWidget.updateDomainObject();
expect(mockOpenMCT.objects.mutate).toHaveBeenCalled();
});
xit('shows configuration interfaces when in edit mode, and hides them otherwise', function () {
setTimeout(function () {
summaryWidget.onEdit([]);
expect(summaryWidget.editing).toEqual(false);
expect(summaryWidget.ruleArea.css('display')).toEqual('none');
expect(summaryWidget.testDataArea.css('display')).toEqual('none');
expect(summaryWidget.addRuleButton.css('display')).toEqual('none');
summaryWidget.onEdit(['editing']);
expect(summaryWidget.editing).toEqual(true);
expect(summaryWidget.ruleArea.css('display')).not.toEqual('none');
expect(summaryWidget.testDataArea.css('display')).not.toEqual('none');
expect(summaryWidget.addRuleButton.css('display')).not.toEqual('none');
}, 100);
});
xit('unregisters any registered listeners on a destroy', function () {
setTimeout(function () {
summaryWidget.destroy();
expect(listenCallbackSpy).toHaveBeenCalled();
}, 100);
});
it('allows reorders of rules', function () {
summaryWidget.initRule('rule0');
summaryWidget.initRule('rule1');
summaryWidget.domainObject.configuration.ruleOrder = ['default', 'rule0', 'rule1'];
summaryWidget.reorder({
draggingId: 'rule1',
dropTarget: 'default'
});
expect(summaryWidget.domainObject.configuration.ruleOrder).toEqual([
'default',
'rule1',
'rule0'
]);
});
it('adds hyperlink to the widget button and sets newTab preference', function () {
summaryWidget.addHyperlink('https://www.nasa.gov', 'newTab');
const widgetButton = mockContainer.querySelector('#widget');
expect(widgetButton.href).toEqual('https://www.nasa.gov/');
expect(widgetButton.target).toEqual('_blank');
});
expect(widgetButton.href).toEqual('https://www.nasa.gov/');
expect(widgetButton.target).toEqual('_blank');
});
});

View File

@ -20,39 +20,39 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['../SummaryWidgetViewPolicy'], function (SummaryWidgetViewPolicy) {
describe('SummaryWidgetViewPolicy', function () {
let policy;
let domainObject;
let view;
beforeEach(function () {
policy = new SummaryWidgetViewPolicy();
domainObject = jasmine.createSpyObj('domainObject', ['getModel']);
domainObject.getModel.and.returnValue({});
view = {};
});
import SummaryWidgetViewPolicy from '../SummaryWidgetViewPolicy';
it('returns true for other object types', function () {
domainObject.getModel.and.returnValue({
type: 'random'
});
expect(policy.allow(view, domainObject)).toBe(true);
});
describe('SummaryWidgetViewPolicy', function () {
let policy;
let domainObject;
let view;
beforeEach(function () {
policy = new SummaryWidgetViewPolicy();
domainObject = jasmine.createSpyObj('domainObject', ['getModel']);
domainObject.getModel.and.returnValue({});
view = {};
});
it('allows summary widget view for summary widgets', function () {
domainObject.getModel.and.returnValue({
type: 'summary-widget'
});
view.key = 'summary-widget-viewer';
expect(policy.allow(view, domainObject)).toBe(true);
it('returns true for other object types', function () {
domainObject.getModel.and.returnValue({
type: 'random'
});
expect(policy.allow(view, domainObject)).toBe(true);
});
it('disallows other views for summary widgets', function () {
domainObject.getModel.and.returnValue({
type: 'summary-widget'
});
view.key = 'other view';
expect(policy.allow(view, domainObject)).toBe(false);
it('allows summary widget view for summary widgets', function () {
domainObject.getModel.and.returnValue({
type: 'summary-widget'
});
view.key = 'summary-widget-viewer';
expect(policy.allow(view, domainObject)).toBe(true);
});
it('disallows other views for summary widgets', function () {
domainObject.getModel.and.returnValue({
type: 'summary-widget'
});
view.key = 'other view';
expect(policy.allow(view, domainObject)).toBe(false);
});
});

View File

@ -1,167 +1,167 @@
define(['../src/TestDataItem'], function (TestDataItem) {
describe('A summary widget test data item', function () {
let testDataItem;
let mockConfig;
let mockConditionManager;
let mockContainer;
let mockEvaluator;
let changeSpy;
let duplicateSpy;
let removeSpy;
let generateValueSpy;
import TestDataItem from '../src/TestDataItem';
beforeEach(function () {
mockContainer = document.createElement('div');
describe('A summary widget test data item', function () {
let testDataItem;
let mockConfig;
let mockConditionManager;
let mockContainer;
let mockEvaluator;
let changeSpy;
let duplicateSpy;
let removeSpy;
let generateValueSpy;
mockConfig = {
object: 'object1',
key: 'property1',
value: 1
};
beforeEach(function () {
mockContainer = document.createElement('div');
mockEvaluator = {};
mockEvaluator.getInputTypeById = jasmine.createSpy('inputType');
mockConfig = {
object: 'object1',
key: 'property1',
value: 1
};
mockConditionManager = jasmine.createSpyObj('mockConditionManager', [
'on',
'getComposition',
'loadCompleted',
'getEvaluator',
'getTelemetryMetadata',
'metadataLoadCompleted',
'getObjectName',
'getTelemetryPropertyName',
'getTelemetryPropertyType'
]);
mockConditionManager.loadCompleted.and.returnValue(false);
mockConditionManager.metadataLoadCompleted.and.returnValue(false);
mockConditionManager.getEvaluator.and.returnValue(mockEvaluator);
mockConditionManager.getComposition.and.returnValue({});
mockConditionManager.getTelemetryMetadata.and.returnValue({});
mockConditionManager.getObjectName.and.returnValue('Object Name');
mockConditionManager.getTelemetryPropertyName.and.returnValue('Property Name');
mockConditionManager.getTelemetryPropertyType.and.returnValue('');
mockEvaluator = {};
mockEvaluator.getInputTypeById = jasmine.createSpy('inputType');
duplicateSpy = jasmine.createSpy('duplicate');
removeSpy = jasmine.createSpy('remove');
changeSpy = jasmine.createSpy('change');
generateValueSpy = jasmine.createSpy('generateValueInput');
mockConditionManager = jasmine.createSpyObj('mockConditionManager', [
'on',
'getComposition',
'loadCompleted',
'getEvaluator',
'getTelemetryMetadata',
'metadataLoadCompleted',
'getObjectName',
'getTelemetryPropertyName',
'getTelemetryPropertyType'
]);
mockConditionManager.loadCompleted.and.returnValue(false);
mockConditionManager.metadataLoadCompleted.and.returnValue(false);
mockConditionManager.getEvaluator.and.returnValue(mockEvaluator);
mockConditionManager.getComposition.and.returnValue({});
mockConditionManager.getTelemetryMetadata.and.returnValue({});
mockConditionManager.getObjectName.and.returnValue('Object Name');
mockConditionManager.getTelemetryPropertyName.and.returnValue('Property Name');
mockConditionManager.getTelemetryPropertyType.and.returnValue('');
testDataItem = new TestDataItem(mockConfig, 54, mockConditionManager);
duplicateSpy = jasmine.createSpy('duplicate');
removeSpy = jasmine.createSpy('remove');
changeSpy = jasmine.createSpy('change');
generateValueSpy = jasmine.createSpy('generateValueInput');
testDataItem.on('duplicate', duplicateSpy);
testDataItem.on('remove', removeSpy);
testDataItem.on('change', changeSpy);
testDataItem = new TestDataItem(mockConfig, 54, mockConditionManager);
testDataItem.on('duplicate', duplicateSpy);
testDataItem.on('remove', removeSpy);
testDataItem.on('change', changeSpy);
});
it('exposes a DOM element to represent itself in the view', function () {
mockContainer.append(testDataItem.getDOM());
expect(mockContainer.querySelectorAll('.t-test-data-item').length).toEqual(1);
});
it('responds to a change in its object select', function () {
testDataItem.selects.object.setSelected('');
expect(changeSpy).toHaveBeenCalledWith({
value: '',
property: 'object',
index: 54
});
});
it('responds to a change in its key select', function () {
testDataItem.generateValueInput = generateValueSpy;
testDataItem.selects.key.setSelected('');
expect(changeSpy).toHaveBeenCalledWith({
value: '',
property: 'key',
index: 54
});
expect(generateValueSpy).toHaveBeenCalledWith('');
});
it('generates a value input of the appropriate type', function () {
let inputs;
mockContainer.append(testDataItem.getDOM());
mockEvaluator.getInputTypeById.and.returnValue('number');
testDataItem.generateValueInput('');
inputs = mockContainer.querySelectorAll('input');
const numberInputs = Array.from(inputs).filter((input) => input.type === 'number');
expect(numberInputs.length).toEqual(1);
expect(inputs[0].valueAsNumber).toEqual(1);
mockEvaluator.getInputTypeById.and.returnValue('text');
testDataItem.config.value = 'Text I Am';
testDataItem.generateValueInput('');
inputs = mockContainer.querySelectorAll('input');
const textInputs = Array.from(inputs).filter((input) => input.type === 'text');
expect(textInputs.length).toEqual(1);
expect(inputs[0].value).toEqual('Text I Am');
});
it('ensures reasonable defaults on values if none are provided', function () {
let inputs;
mockContainer.append(testDataItem.getDOM());
mockEvaluator.getInputTypeById.and.returnValue('number');
testDataItem.config.value = undefined;
testDataItem.generateValueInput('');
inputs = mockContainer.querySelectorAll('input');
const numberInputs = Array.from(inputs).filter((input) => input.type === 'number');
expect(numberInputs.length).toEqual(1);
expect(inputs[0].valueAsNumber).toEqual(0);
expect(testDataItem.config.value).toEqual(0);
mockEvaluator.getInputTypeById.and.returnValue('text');
testDataItem.config.value = undefined;
testDataItem.generateValueInput('');
inputs = mockContainer.querySelectorAll('input');
const textInputs = Array.from(inputs).filter((input) => input.type === 'text');
expect(textInputs.length).toEqual(1);
expect(inputs[0].value).toEqual('');
expect(testDataItem.config.value).toEqual('');
});
it('responds to a change in its value inputs', function () {
mockContainer.append(testDataItem.getDOM());
mockEvaluator.getInputTypeById.and.returnValue('number');
testDataItem.generateValueInput('');
const event = new Event('input', {
bubbles: true,
cancelable: true
});
it('exposes a DOM element to represent itself in the view', function () {
mockContainer.append(testDataItem.getDOM());
expect(mockContainer.querySelectorAll('.t-test-data-item').length).toEqual(1);
mockContainer.querySelector('input').value = 9001;
mockContainer.querySelector('input').dispatchEvent(event);
expect(changeSpy).toHaveBeenCalledWith({
value: 9001,
property: 'value',
index: 54
});
});
it('responds to a change in its object select', function () {
testDataItem.selects.object.setSelected('');
expect(changeSpy).toHaveBeenCalledWith({
value: '',
property: 'object',
index: 54
});
});
it('can remove itself from the configuration', function () {
testDataItem.remove();
expect(removeSpy).toHaveBeenCalledWith(54);
});
it('responds to a change in its key select', function () {
testDataItem.generateValueInput = generateValueSpy;
testDataItem.selects.key.setSelected('');
expect(changeSpy).toHaveBeenCalledWith({
value: '',
property: 'key',
index: 54
});
expect(generateValueSpy).toHaveBeenCalledWith('');
});
it('generates a value input of the appropriate type', function () {
let inputs;
mockContainer.append(testDataItem.getDOM());
mockEvaluator.getInputTypeById.and.returnValue('number');
testDataItem.generateValueInput('');
inputs = mockContainer.querySelectorAll('input');
const numberInputs = Array.from(inputs).filter((input) => input.type === 'number');
expect(numberInputs.length).toEqual(1);
expect(inputs[0].valueAsNumber).toEqual(1);
mockEvaluator.getInputTypeById.and.returnValue('text');
testDataItem.config.value = 'Text I Am';
testDataItem.generateValueInput('');
inputs = mockContainer.querySelectorAll('input');
const textInputs = Array.from(inputs).filter((input) => input.type === 'text');
expect(textInputs.length).toEqual(1);
expect(inputs[0].value).toEqual('Text I Am');
});
it('ensures reasonable defaults on values if none are provided', function () {
let inputs;
mockContainer.append(testDataItem.getDOM());
mockEvaluator.getInputTypeById.and.returnValue('number');
testDataItem.config.value = undefined;
testDataItem.generateValueInput('');
inputs = mockContainer.querySelectorAll('input');
const numberInputs = Array.from(inputs).filter((input) => input.type === 'number');
expect(numberInputs.length).toEqual(1);
expect(inputs[0].valueAsNumber).toEqual(0);
expect(testDataItem.config.value).toEqual(0);
mockEvaluator.getInputTypeById.and.returnValue('text');
testDataItem.config.value = undefined;
testDataItem.generateValueInput('');
inputs = mockContainer.querySelectorAll('input');
const textInputs = Array.from(inputs).filter((input) => input.type === 'text');
expect(textInputs.length).toEqual(1);
expect(inputs[0].value).toEqual('');
expect(testDataItem.config.value).toEqual('');
});
it('responds to a change in its value inputs', function () {
mockContainer.append(testDataItem.getDOM());
mockEvaluator.getInputTypeById.and.returnValue('number');
testDataItem.generateValueInput('');
const event = new Event('input', {
bubbles: true,
cancelable: true
});
mockContainer.querySelector('input').value = 9001;
mockContainer.querySelector('input').dispatchEvent(event);
expect(changeSpy).toHaveBeenCalledWith({
value: 9001,
property: 'value',
index: 54
});
});
it('can remove itself from the configuration', function () {
testDataItem.remove();
expect(removeSpy).toHaveBeenCalledWith(54);
});
it('can duplicate itself', function () {
testDataItem.duplicate();
expect(duplicateSpy).toHaveBeenCalledWith({
sourceItem: mockConfig,
index: 54
});
it('can duplicate itself', function () {
testDataItem.duplicate();
expect(duplicateSpy).toHaveBeenCalledWith({
sourceItem: mockConfig,
index: 54
});
});
});

View File

@ -1,250 +1,244 @@
define(['../src/TestDataManager'], function (TestDataManager) {
describe('A Summary Widget Rule', function () {
let mockDomainObject;
let mockOpenMCT;
let mockConditionManager;
let mockEvaluator;
let mockContainer;
let mockTelemetryMetadata;
let testDataManager;
let mockCompObject1;
let mockCompObject2;
import TestDataManager from '../src/TestDataManager';
beforeEach(function () {
mockDomainObject = {
configuration: {
testDataConfig: [
{
object: '',
key: '',
value: ''
},
{
object: 'object1',
key: 'property1',
value: 66
},
{
object: 'object2',
key: 'property4',
value: 'Text It Is'
}
]
},
composition: [
describe('A Summary Widget Rule', function () {
let mockDomainObject;
let mockOpenMCT;
let mockConditionManager;
let mockEvaluator;
let mockContainer;
let mockTelemetryMetadata;
let testDataManager;
let mockCompObject1;
let mockCompObject2;
beforeEach(function () {
mockDomainObject = {
configuration: {
testDataConfig: [
{
object1: {
key: 'object1',
name: 'Object 1'
},
object2: {
key: 'object2',
name: 'Object 2'
}
object: '',
key: '',
value: ''
},
{
object: 'object1',
key: 'property1',
value: 66
},
{
object: 'object2',
key: 'property4',
value: 'Text It Is'
}
]
};
mockTelemetryMetadata = {
object1: {
property1: {
key: 'property1'
},
property2: {
key: 'property2'
}
},
object2: {
property3: {
key: 'property3'
},
property4: {
key: 'property4'
}
}
};
mockCompObject1 = {
identifier: {
key: 'object1'
},
name: 'Object 1'
};
mockCompObject2 = {
identifier: {
key: 'object2'
},
name: 'Object 2'
};
mockOpenMCT = {};
mockOpenMCT.objects = {};
mockOpenMCT.objects.mutate = jasmine.createSpy('mutate');
mockEvaluator = {};
mockEvaluator.setTestDataCache = jasmine.createSpy('testDataCache');
mockEvaluator.useTestData = jasmine.createSpy('useTestData');
mockConditionManager = jasmine.createSpyObj('mockConditionManager', [
'on',
'getComposition',
'loadCompleted',
'getEvaluator',
'getTelemetryMetadata',
'metadataLoadCompleted',
'getObjectName',
'getTelemetryPropertyName',
'triggerTelemetryCallback'
]);
mockConditionManager.loadCompleted.and.returnValue(false);
mockConditionManager.metadataLoadCompleted.and.returnValue(false);
mockConditionManager.getEvaluator.and.returnValue(mockEvaluator);
mockConditionManager.getComposition.and.returnValue({
object1: mockCompObject1,
object2: mockCompObject2
});
mockConditionManager.getTelemetryMetadata.and.callFake(function (id) {
return mockTelemetryMetadata[id];
});
mockConditionManager.getObjectName.and.returnValue('Object Name');
mockConditionManager.getTelemetryPropertyName.and.returnValue('Property Name');
mockContainer = document.createElement('div');
testDataManager = new TestDataManager(mockDomainObject, mockConditionManager, mockOpenMCT);
});
it('closes its configuration panel on initial load', function () {});
it('exposes a DOM element to represent itself in the view', function () {
mockContainer.append(testDataManager.getDOM());
expect(mockContainer.querySelectorAll('.t-widget-test-data-content').length).toBeGreaterThan(
0
);
});
it('generates a test cache in the format expected by a condition evaluator', function () {
testDataManager.updateTestCache();
expect(mockEvaluator.setTestDataCache).toHaveBeenCalledWith({
object1: {
property1: 66,
property2: ''
},
object2: {
property3: '',
property4: 'Text It Is'
}
});
});
it(
'updates its configuration on a item change and provides an updated' +
'cache to the evaluator',
function () {
testDataManager.onItemChange({
value: 26,
property: 'value',
index: 1
});
expect(testDataManager.config[1].value).toEqual(26);
expect(mockEvaluator.setTestDataCache).toHaveBeenCalledWith({
},
composition: [
{
object1: {
property1: 26,
property2: ''
key: 'object1',
name: 'Object 1'
},
object2: {
property3: '',
property4: 'Text It Is'
key: 'object2',
name: 'Object 2'
}
});
}
]
};
mockTelemetryMetadata = {
object1: {
property1: {
key: 'property1'
},
property2: {
key: 'property2'
}
},
object2: {
property3: {
key: 'property3'
},
property4: {
key: 'property4'
}
}
);
};
it('allows initializing a new item with a default configuration', function () {
testDataManager.initItem();
expect(mockDomainObject.configuration.testDataConfig).toEqual([
{
object: '',
key: '',
value: ''
},
{
object: 'object1',
key: 'property1',
value: 66
},
{
object: 'object2',
key: 'property4',
value: 'Text It Is'
},
{
object: '',
key: '',
value: ''
}
]);
mockCompObject1 = {
identifier: {
key: 'object1'
},
name: 'Object 1'
};
mockCompObject2 = {
identifier: {
key: 'object2'
},
name: 'Object 2'
};
mockOpenMCT = {};
mockOpenMCT.objects = {};
mockOpenMCT.objects.mutate = jasmine.createSpy('mutate');
mockEvaluator = {};
mockEvaluator.setTestDataCache = jasmine.createSpy('testDataCache');
mockEvaluator.useTestData = jasmine.createSpy('useTestData');
mockConditionManager = jasmine.createSpyObj('mockConditionManager', [
'on',
'getComposition',
'loadCompleted',
'getEvaluator',
'getTelemetryMetadata',
'metadataLoadCompleted',
'getObjectName',
'getTelemetryPropertyName',
'triggerTelemetryCallback'
]);
mockConditionManager.loadCompleted.and.returnValue(false);
mockConditionManager.metadataLoadCompleted.and.returnValue(false);
mockConditionManager.getEvaluator.and.returnValue(mockEvaluator);
mockConditionManager.getComposition.and.returnValue({
object1: mockCompObject1,
object2: mockCompObject2
});
it('allows initializing a new item from a given configuration', function () {
testDataManager.initItem({
sourceItem: {
object: 'object2',
key: 'property3',
value: 1
},
index: 0
});
expect(mockDomainObject.configuration.testDataConfig).toEqual([
{
object: '',
key: '',
value: ''
},
{
object: 'object2',
key: 'property3',
value: 1
},
{
object: 'object1',
key: 'property1',
value: 66
},
{
object: 'object2',
key: 'property4',
value: 'Text It Is'
}
]);
mockConditionManager.getTelemetryMetadata.and.callFake(function (id) {
return mockTelemetryMetadata[id];
});
mockConditionManager.getObjectName.and.returnValue('Object Name');
mockConditionManager.getTelemetryPropertyName.and.returnValue('Property Name');
it('invokes mutate when updating the domain object', function () {
testDataManager.updateDomainObject();
expect(mockOpenMCT.objects.mutate).toHaveBeenCalled();
});
mockContainer = document.createElement('div');
it('builds item view from item configuration', function () {
mockContainer.append(testDataManager.getDOM());
expect(mockContainer.querySelectorAll('.t-test-data-item').length).toEqual(3);
});
it('can remove a item from its configuration', function () {
testDataManager.removeItem(0);
expect(mockDomainObject.configuration.testDataConfig).toEqual([
{
object: 'object1',
key: 'property1',
value: 66
},
{
object: 'object2',
key: 'property4',
value: 'Text It Is'
}
]);
});
it('exposes a UI element to toggle test data on and off', function () {});
testDataManager = new TestDataManager(mockDomainObject, mockConditionManager, mockOpenMCT);
});
it('closes its configuration panel on initial load', function () {});
it('exposes a DOM element to represent itself in the view', function () {
mockContainer.append(testDataManager.getDOM());
expect(mockContainer.querySelectorAll('.t-widget-test-data-content').length).toBeGreaterThan(0);
});
it('generates a test cache in the format expected by a condition evaluator', function () {
testDataManager.updateTestCache();
expect(mockEvaluator.setTestDataCache).toHaveBeenCalledWith({
object1: {
property1: 66,
property2: ''
},
object2: {
property3: '',
property4: 'Text It Is'
}
});
});
it('updates its configuration on a item change and provides an updated cache to the evaluator', function () {
testDataManager.onItemChange({
value: 26,
property: 'value',
index: 1
});
expect(testDataManager.config[1].value).toEqual(26);
expect(mockEvaluator.setTestDataCache).toHaveBeenCalledWith({
object1: {
property1: 26,
property2: ''
},
object2: {
property3: '',
property4: 'Text It Is'
}
});
});
it('allows initializing a new item with a default configuration', function () {
testDataManager.initItem();
expect(mockDomainObject.configuration.testDataConfig).toEqual([
{
object: '',
key: '',
value: ''
},
{
object: 'object1',
key: 'property1',
value: 66
},
{
object: 'object2',
key: 'property4',
value: 'Text It Is'
},
{
object: '',
key: '',
value: ''
}
]);
});
it('allows initializing a new item from a given configuration', function () {
testDataManager.initItem({
sourceItem: {
object: 'object2',
key: 'property3',
value: 1
},
index: 0
});
expect(mockDomainObject.configuration.testDataConfig).toEqual([
{
object: '',
key: '',
value: ''
},
{
object: 'object2',
key: 'property3',
value: 1
},
{
object: 'object1',
key: 'property1',
value: 66
},
{
object: 'object2',
key: 'property4',
value: 'Text It Is'
}
]);
});
it('invokes mutate when updating the domain object', function () {
testDataManager.updateDomainObject();
expect(mockOpenMCT.objects.mutate).toHaveBeenCalled();
});
it('builds item view from item configuration', function () {
mockContainer.append(testDataManager.getDOM());
expect(mockContainer.querySelectorAll('.t-test-data-item').length).toEqual(3);
});
it('can remove a item from its configuration', function () {
testDataManager.removeItem(0);
expect(mockDomainObject.configuration.testDataConfig).toEqual([
{
object: 'object1',
key: 'property1',
value: 66
},
{
object: 'object2',
key: 'property4',
value: 'Text It Is'
}
]);
});
it('exposes a UI element to toggle test data on and off', function () {});
});

View File

@ -1,24 +1,24 @@
define(['../../src/input/ColorPalette'], function (ColorPalette) {
describe('An Open MCT color palette', function () {
let colorPalette;
let changeCallback;
import ColorPalette from '../../src/input/ColorPalette';
beforeEach(function () {
changeCallback = jasmine.createSpy('changeCallback');
});
describe('An Open MCT color palette', function () {
let colorPalette;
let changeCallback;
it('allows defining a custom color set', function () {
colorPalette = new ColorPalette('someClass', 'someContainer', ['color1', 'color2', 'color3']);
expect(colorPalette.getCurrent()).toEqual('color1');
colorPalette.on('change', changeCallback);
colorPalette.set('color2');
expect(colorPalette.getCurrent()).toEqual('color2');
expect(changeCallback).toHaveBeenCalledWith('color2');
});
beforeEach(function () {
changeCallback = jasmine.createSpy('changeCallback');
});
it('loads with a default color set if one is not provided', function () {
colorPalette = new ColorPalette('someClass', 'someContainer');
expect(colorPalette.getCurrent()).toBeDefined();
});
it('allows defining a custom color set', function () {
colorPalette = new ColorPalette('someClass', 'someContainer', ['color1', 'color2', 'color3']);
expect(colorPalette.getCurrent()).toEqual('color1');
colorPalette.on('change', changeCallback);
colorPalette.set('color2');
expect(colorPalette.getCurrent()).toEqual('color2');
expect(changeCallback).toHaveBeenCalledWith('color2');
});
it('loads with a default color set if one is not provided', function () {
colorPalette = new ColorPalette('someClass', 'someContainer');
expect(colorPalette.getCurrent()).toBeDefined();
});
});

View File

@ -1,24 +1,24 @@
define(['../../src/input/IconPalette'], function (IconPalette) {
describe('An Open MCT icon palette', function () {
let iconPalette;
let changeCallback;
import IconPalette from '../../src/input/IconPalette';
beforeEach(function () {
changeCallback = jasmine.createSpy('changeCallback');
});
describe('An Open MCT icon palette', function () {
let iconPalette;
let changeCallback;
it('allows defining a custom icon set', function () {
iconPalette = new IconPalette('', 'someContainer', ['icon1', 'icon2', 'icon3']);
expect(iconPalette.getCurrent()).toEqual('icon1');
iconPalette.on('change', changeCallback);
iconPalette.set('icon2');
expect(iconPalette.getCurrent()).toEqual('icon2');
expect(changeCallback).toHaveBeenCalledWith('icon2');
});
beforeEach(function () {
changeCallback = jasmine.createSpy('changeCallback');
});
it('loads with a default icon set if one is not provided', function () {
iconPalette = new IconPalette('someClass', 'someContainer');
expect(iconPalette.getCurrent()).toBeDefined();
});
it('allows defining a custom icon set', function () {
iconPalette = new IconPalette('', 'someContainer', ['icon1', 'icon2', 'icon3']);
expect(iconPalette.getCurrent()).toEqual('icon1');
iconPalette.on('change', changeCallback);
iconPalette.set('icon2');
expect(iconPalette.getCurrent()).toEqual('icon2');
expect(changeCallback).toHaveBeenCalledWith('icon2');
});
it('loads with a default icon set if one is not provided', function () {
iconPalette = new IconPalette('someClass', 'someContainer');
expect(iconPalette.getCurrent()).toBeDefined();
});
});

View File

@ -1,123 +1,123 @@
define(['../../src/input/KeySelect'], function (KeySelect) {
describe('A select for choosing composition object properties', function () {
let mockConfig;
let mockBadConfig;
let mockManager;
let keySelect;
let mockMetadata;
let mockObjectSelect;
beforeEach(function () {
mockConfig = {
object: 'object1',
key: 'a'
};
import KeySelect from '../../src/input/KeySelect';
mockBadConfig = {
object: 'object1',
key: 'someNonexistentKey'
};
describe('A select for choosing composition object properties', function () {
let mockConfig;
let mockBadConfig;
let mockManager;
let keySelect;
let mockMetadata;
let mockObjectSelect;
beforeEach(function () {
mockConfig = {
object: 'object1',
key: 'a'
};
mockMetadata = {
object1: {
a: {
name: 'A'
},
b: {
name: 'B'
}
mockBadConfig = {
object: 'object1',
key: 'someNonexistentKey'
};
mockMetadata = {
object1: {
a: {
name: 'A'
},
object2: {
alpha: {
name: 'Alpha'
},
beta: {
name: 'Beta'
}
},
object3: {
a: {
name: 'A'
}
b: {
name: 'B'
}
};
},
object2: {
alpha: {
name: 'Alpha'
},
beta: {
name: 'Beta'
}
},
object3: {
a: {
name: 'A'
}
}
};
mockManager = jasmine.createSpyObj('mockManager', [
'on',
'metadataLoadCompleted',
'triggerCallback',
'getTelemetryMetadata'
]);
mockManager = jasmine.createSpyObj('mockManager', [
'on',
'metadataLoadCompleted',
'triggerCallback',
'getTelemetryMetadata'
]);
mockObjectSelect = jasmine.createSpyObj('mockObjectSelect', ['on', 'triggerCallback']);
mockObjectSelect = jasmine.createSpyObj('mockObjectSelect', ['on', 'triggerCallback']);
mockObjectSelect.on.and.callFake((event, callback) => {
mockObjectSelect.callbacks = mockObjectSelect.callbacks || {};
mockObjectSelect.callbacks[event] = callback;
});
mockObjectSelect.triggerCallback.and.callFake((event, key) => {
mockObjectSelect.callbacks[event](key);
});
mockManager.on.and.callFake((event, callback) => {
mockManager.callbacks = mockManager.callbacks || {};
mockManager.callbacks[event] = callback;
});
mockManager.triggerCallback.and.callFake((event) => {
mockManager.callbacks[event]();
});
mockManager.getTelemetryMetadata.and.callFake(function (key) {
return mockMetadata[key];
});
mockObjectSelect.on.and.callFake((event, callback) => {
mockObjectSelect.callbacks = mockObjectSelect.callbacks || {};
mockObjectSelect.callbacks[event] = callback;
});
it('waits until the metadata fully loads to populate itself', function () {
mockManager.metadataLoadCompleted.and.returnValue(false);
keySelect = new KeySelect(mockConfig, mockObjectSelect, mockManager);
expect(keySelect.getSelected()).toEqual('');
mockObjectSelect.triggerCallback.and.callFake((event, key) => {
mockObjectSelect.callbacks[event](key);
});
it('populates itself with metadata on a metadata load', function () {
mockManager.metadataLoadCompleted.and.returnValue(false);
keySelect = new KeySelect(mockConfig, mockObjectSelect, mockManager);
mockManager.triggerCallback('metadata');
expect(keySelect.getSelected()).toEqual('a');
mockManager.on.and.callFake((event, callback) => {
mockManager.callbacks = mockManager.callbacks || {};
mockManager.callbacks[event] = callback;
});
it('populates itself with metadata if metadata load is already complete', function () {
mockManager.metadataLoadCompleted.and.returnValue(true);
keySelect = new KeySelect(mockConfig, mockObjectSelect, mockManager);
expect(keySelect.getSelected()).toEqual('a');
mockManager.triggerCallback.and.callFake((event) => {
mockManager.callbacks[event]();
});
it('clears its selection state if the property in its config is not in its object', function () {
mockManager.metadataLoadCompleted.and.returnValue(true);
keySelect = new KeySelect(mockBadConfig, mockObjectSelect, mockManager);
expect(keySelect.getSelected()).toEqual('');
});
it('populates with the appropriate options when its linked object changes', function () {
mockManager.metadataLoadCompleted.and.returnValue(true);
keySelect = new KeySelect(mockConfig, mockObjectSelect, mockManager);
mockObjectSelect.triggerCallback('change', 'object2');
keySelect.setSelected('alpha');
expect(keySelect.getSelected()).toEqual('alpha');
});
it('clears its selected state on change if the field is not present in the new object', function () {
mockManager.metadataLoadCompleted.and.returnValue(true);
keySelect = new KeySelect(mockConfig, mockObjectSelect, mockManager);
mockObjectSelect.triggerCallback('change', 'object2');
expect(keySelect.getSelected()).toEqual('');
});
it('maintains its selected state on change if field is present in new object', function () {
mockManager.metadataLoadCompleted.and.returnValue(true);
keySelect = new KeySelect(mockConfig, mockObjectSelect, mockManager);
mockObjectSelect.triggerCallback('change', 'object3');
expect(keySelect.getSelected()).toEqual('a');
mockManager.getTelemetryMetadata.and.callFake(function (key) {
return mockMetadata[key];
});
});
it('waits until the metadata fully loads to populate itself', function () {
mockManager.metadataLoadCompleted.and.returnValue(false);
keySelect = new KeySelect(mockConfig, mockObjectSelect, mockManager);
expect(keySelect.getSelected()).toEqual('');
});
it('populates itself with metadata on a metadata load', function () {
mockManager.metadataLoadCompleted.and.returnValue(false);
keySelect = new KeySelect(mockConfig, mockObjectSelect, mockManager);
mockManager.triggerCallback('metadata');
expect(keySelect.getSelected()).toEqual('a');
});
it('populates itself with metadata if metadata load is already complete', function () {
mockManager.metadataLoadCompleted.and.returnValue(true);
keySelect = new KeySelect(mockConfig, mockObjectSelect, mockManager);
expect(keySelect.getSelected()).toEqual('a');
});
it('clears its selection state if the property in its config is not in its object', function () {
mockManager.metadataLoadCompleted.and.returnValue(true);
keySelect = new KeySelect(mockBadConfig, mockObjectSelect, mockManager);
expect(keySelect.getSelected()).toEqual('');
});
it('populates with the appropriate options when its linked object changes', function () {
mockManager.metadataLoadCompleted.and.returnValue(true);
keySelect = new KeySelect(mockConfig, mockObjectSelect, mockManager);
mockObjectSelect.triggerCallback('change', 'object2');
keySelect.setSelected('alpha');
expect(keySelect.getSelected()).toEqual('alpha');
});
it('clears its selected state on change if the field is not present in the new object', function () {
mockManager.metadataLoadCompleted.and.returnValue(true);
keySelect = new KeySelect(mockConfig, mockObjectSelect, mockManager);
mockObjectSelect.triggerCallback('change', 'object2');
expect(keySelect.getSelected()).toEqual('');
});
it('maintains its selected state on change if field is present in new object', function () {
mockManager.metadataLoadCompleted.and.returnValue(true);
keySelect = new KeySelect(mockConfig, mockObjectSelect, mockManager);
mockObjectSelect.triggerCallback('change', 'object3');
expect(keySelect.getSelected()).toEqual('a');
});
});

View File

@ -1,112 +1,134 @@
define(['../../src/input/ObjectSelect'], function (ObjectSelect) {
describe('A select for choosing composition objects', function () {
let mockConfig;
let mockBadConfig;
let mockManager;
let objectSelect;
let mockComposition;
beforeEach(function () {
mockConfig = {
object: 'key1'
};
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, 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.
*****************************************************************************/
mockBadConfig = {
object: 'someNonexistentObject'
};
import ObjectSelect from '../../src/input/ObjectSelect';
mockComposition = {
key1: {
identifier: {
key: 'key1'
},
name: 'Object 1'
},
key2: {
identifier: {
key: 'key2'
},
name: 'Object 2'
}
};
mockManager = jasmine.createSpyObj('mockManager', [
'on',
'loadCompleted',
'triggerCallback',
'getComposition'
]);
describe('A select for choosing composition objects', function () {
let mockConfig;
let mockBadConfig;
let mockManager;
let objectSelect;
let mockComposition;
beforeEach(function () {
mockConfig = {
object: 'key1'
};
mockManager.on.and.callFake((event, callback) => {
mockManager.callbacks = mockManager.callbacks || {};
mockManager.callbacks[event] = callback;
});
mockBadConfig = {
object: 'someNonexistentObject'
};
mockManager.triggerCallback.and.callFake((event, newObj) => {
if (event === 'add') {
mockManager.callbacks.add(newObj);
} else {
mockManager.callbacks[event]();
}
});
mockManager.getComposition.and.callFake(function () {
return mockComposition;
});
});
it('allows setting special keyword options', function () {
mockManager.loadCompleted.and.returnValue(true);
objectSelect = new ObjectSelect(mockConfig, mockManager, [
['keyword1', 'A special option'],
['keyword2', 'A special option']
]);
objectSelect.setSelected('keyword1');
expect(objectSelect.getSelected()).toEqual('keyword1');
});
it('waits until the composition fully loads to populate itself', function () {
mockManager.loadCompleted.and.returnValue(false);
objectSelect = new ObjectSelect(mockConfig, mockManager);
expect(objectSelect.getSelected()).toEqual('');
});
it('populates itself with composition objects on a composition load', function () {
mockManager.loadCompleted.and.returnValue(false);
objectSelect = new ObjectSelect(mockConfig, mockManager);
mockManager.triggerCallback('load');
expect(objectSelect.getSelected()).toEqual('key1');
});
it('populates itself with composition objects if load is already complete', function () {
mockManager.loadCompleted.and.returnValue(true);
objectSelect = new ObjectSelect(mockConfig, mockManager);
expect(objectSelect.getSelected()).toEqual('key1');
});
it('clears its selection state if the object in its config is not in the composition', function () {
mockManager.loadCompleted.and.returnValue(true);
objectSelect = new ObjectSelect(mockBadConfig, mockManager);
expect(objectSelect.getSelected()).toEqual('');
});
it('adds a new option on a composition add', function () {
mockManager.loadCompleted.and.returnValue(true);
objectSelect = new ObjectSelect(mockConfig, mockManager);
mockManager.triggerCallback('add', {
mockComposition = {
key1: {
identifier: {
key: 'key3'
key: 'key1'
},
name: 'Object 3'
});
objectSelect.setSelected('key3');
expect(objectSelect.getSelected()).toEqual('key3');
name: 'Object 1'
},
key2: {
identifier: {
key: 'key2'
},
name: 'Object 2'
}
};
mockManager = jasmine.createSpyObj('mockManager', [
'on',
'loadCompleted',
'triggerCallback',
'getComposition'
]);
mockManager.on.and.callFake((event, callback) => {
mockManager.callbacks = mockManager.callbacks || {};
mockManager.callbacks[event] = callback;
});
it('removes an option on a composition remove', function () {
mockManager.loadCompleted.and.returnValue(true);
objectSelect = new ObjectSelect(mockConfig, mockManager);
delete mockComposition.key1;
mockManager.triggerCallback('remove');
expect(objectSelect.getSelected()).not.toEqual('key1');
mockManager.triggerCallback.and.callFake((event, newObj) => {
if (event === 'add') {
mockManager.callbacks.add(newObj);
} else {
mockManager.callbacks[event]();
}
});
mockManager.getComposition.and.callFake(function () {
return mockComposition;
});
});
it('allows setting special keyword options', function () {
mockManager.loadCompleted.and.returnValue(true);
objectSelect = new ObjectSelect(mockConfig, mockManager, [
['keyword1', 'A special option'],
['keyword2', 'A special option']
]);
objectSelect.setSelected('keyword1');
expect(objectSelect.getSelected()).toEqual('keyword1');
});
it('waits until the composition fully loads to populate itself', function () {
mockManager.loadCompleted.and.returnValue(false);
objectSelect = new ObjectSelect(mockConfig, mockManager);
expect(objectSelect.getSelected()).toEqual('');
});
it('populates itself with composition objects on a composition load', function () {
mockManager.loadCompleted.and.returnValue(false);
objectSelect = new ObjectSelect(mockConfig, mockManager);
mockManager.triggerCallback('load');
expect(objectSelect.getSelected()).toEqual('key1');
});
it('populates itself with composition objects if load is already complete', function () {
mockManager.loadCompleted.and.returnValue(true);
objectSelect = new ObjectSelect(mockConfig, mockManager);
expect(objectSelect.getSelected()).toEqual('key1');
});
it('clears its selection state if the object in its config is not in the composition', function () {
mockManager.loadCompleted.and.returnValue(true);
objectSelect = new ObjectSelect(mockBadConfig, mockManager);
expect(objectSelect.getSelected()).toEqual('');
});
it('adds a new option on a composition add', function () {
mockManager.loadCompleted.and.returnValue(true);
objectSelect = new ObjectSelect(mockConfig, mockManager);
mockManager.triggerCallback('add', {
identifier: {
key: 'key3'
},
name: 'Object 3'
});
objectSelect.setSelected('key3');
expect(objectSelect.getSelected()).toEqual('key3');
});
it('removes an option on a composition remove', function () {
mockManager.loadCompleted.and.returnValue(true);
objectSelect = new ObjectSelect(mockConfig, mockManager);
delete mockComposition.key1;
mockManager.triggerCallback('remove');
expect(objectSelect.getSelected()).not.toEqual('key1');
});
});

View File

@ -1,143 +1,165 @@
define(['../../src/input/OperationSelect'], function (OperationSelect) {
describe('A select for choosing composition object properties', function () {
let mockConfig;
let mockBadConfig;
let mockManager;
let operationSelect;
let mockOperations;
let mockPropertyTypes;
let mockKeySelect;
let mockEvaluator;
beforeEach(function () {
mockConfig = {
object: 'object1',
key: 'a',
operation: 'operation1'
};
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2023, 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.
*****************************************************************************/
mockBadConfig = {
object: 'object1',
key: 'a',
operation: 'someNonexistentOperation'
};
import OperationSelect from '../../src/input/OperationSelect';
mockOperations = {
operation1: {
text: 'An operation',
appliesTo: ['number']
},
operation2: {
text: 'Another operation',
appliesTo: ['string']
}
};
describe('A select for choosing composition object properties', function () {
let mockConfig;
let mockBadConfig;
let mockManager;
let operationSelect;
let mockOperations;
let mockPropertyTypes;
let mockKeySelect;
let mockEvaluator;
beforeEach(function () {
mockConfig = {
object: 'object1',
key: 'a',
operation: 'operation1'
};
mockPropertyTypes = {
object1: {
a: 'number',
b: 'string',
c: 'number'
}
};
mockBadConfig = {
object: 'object1',
key: 'a',
operation: 'someNonexistentOperation'
};
mockManager = jasmine.createSpyObj('mockManager', [
'on',
'metadataLoadCompleted',
'triggerCallback',
'getTelemetryPropertyType',
'getEvaluator'
]);
mockOperations = {
operation1: {
text: 'An operation',
appliesTo: ['number']
},
operation2: {
text: 'Another operation',
appliesTo: ['string']
}
};
mockKeySelect = jasmine.createSpyObj('mockKeySelect', ['on', 'triggerCallback']);
mockPropertyTypes = {
object1: {
a: 'number',
b: 'string',
c: 'number'
}
};
mockEvaluator = jasmine.createSpyObj('mockEvaluator', [
'getOperationKeys',
'operationAppliesTo',
'getOperationText'
]);
mockManager = jasmine.createSpyObj('mockManager', [
'on',
'metadataLoadCompleted',
'triggerCallback',
'getTelemetryPropertyType',
'getEvaluator'
]);
mockEvaluator.getOperationKeys.and.returnValue(Object.keys(mockOperations));
mockKeySelect = jasmine.createSpyObj('mockKeySelect', ['on', 'triggerCallback']);
mockEvaluator.getOperationText.and.callFake(function (key) {
return mockOperations[key].text;
});
mockEvaluator = jasmine.createSpyObj('mockEvaluator', [
'getOperationKeys',
'operationAppliesTo',
'getOperationText'
]);
mockEvaluator.operationAppliesTo.and.callFake(function (operation, type) {
return mockOperations[operation].appliesTo.includes(type);
});
mockEvaluator.getOperationKeys.and.returnValue(Object.keys(mockOperations));
mockKeySelect.on.and.callFake((event, callback) => {
mockKeySelect.callbacks = mockKeySelect.callbacks || {};
mockKeySelect.callbacks[event] = callback;
});
mockKeySelect.triggerCallback.and.callFake((event, key) => {
mockKeySelect.callbacks[event](key);
});
mockManager.on.and.callFake((event, callback) => {
mockManager.callbacks = mockManager.callbacks || {};
mockManager.callbacks[event] = callback;
});
mockManager.triggerCallback.and.callFake((event) => {
mockManager.callbacks[event]();
});
mockManager.getTelemetryPropertyType.and.callFake(function (object, key) {
return mockPropertyTypes[object][key];
});
mockManager.getEvaluator.and.returnValue(mockEvaluator);
mockEvaluator.getOperationText.and.callFake(function (key) {
return mockOperations[key].text;
});
it('waits until the metadata fully loads to populate itself', function () {
mockManager.metadataLoadCompleted.and.returnValue(false);
operationSelect = new OperationSelect(mockConfig, mockKeySelect, mockManager);
expect(operationSelect.getSelected()).toEqual('');
mockEvaluator.operationAppliesTo.and.callFake(function (operation, type) {
return mockOperations[operation].appliesTo.includes(type);
});
it('populates itself with operations on a metadata load', function () {
mockManager.metadataLoadCompleted.and.returnValue(false);
operationSelect = new OperationSelect(mockConfig, mockKeySelect, mockManager);
mockManager.triggerCallback('metadata');
expect(operationSelect.getSelected()).toEqual('operation1');
mockKeySelect.on.and.callFake((event, callback) => {
mockKeySelect.callbacks = mockKeySelect.callbacks || {};
mockKeySelect.callbacks[event] = callback;
});
it('populates itself with operations if metadata load is already complete', function () {
mockManager.metadataLoadCompleted.and.returnValue(true);
operationSelect = new OperationSelect(mockConfig, mockKeySelect, mockManager);
expect(operationSelect.getSelected()).toEqual('operation1');
mockKeySelect.triggerCallback.and.callFake((event, key) => {
mockKeySelect.callbacks[event](key);
});
it('clears its selection state if the operation in its config does not apply', function () {
mockManager.metadataLoadCompleted.and.returnValue(true);
operationSelect = new OperationSelect(mockBadConfig, mockKeySelect, mockManager);
expect(operationSelect.getSelected()).toEqual('');
mockManager.on.and.callFake((event, callback) => {
mockManager.callbacks = mockManager.callbacks || {};
mockManager.callbacks[event] = callback;
});
it('populates with the appropriate options when its linked key changes', function () {
mockManager.metadataLoadCompleted.and.returnValue(true);
operationSelect = new OperationSelect(mockConfig, mockKeySelect, mockManager);
mockKeySelect.triggerCallback('change', 'b');
operationSelect.setSelected('operation2');
expect(operationSelect.getSelected()).toEqual('operation2');
operationSelect.setSelected('operation1');
expect(operationSelect.getSelected()).not.toEqual('operation1');
mockManager.triggerCallback.and.callFake((event) => {
mockManager.callbacks[event]();
});
it('clears its selection on a change if the operation does not apply', function () {
mockManager.metadataLoadCompleted.and.returnValue(true);
operationSelect = new OperationSelect(mockConfig, mockKeySelect, mockManager);
mockKeySelect.triggerCallback('change', 'b');
expect(operationSelect.getSelected()).toEqual('');
mockManager.getTelemetryPropertyType.and.callFake(function (object, key) {
return mockPropertyTypes[object][key];
});
it('maintains its selected state on change if the operation does apply', function () {
mockManager.metadataLoadCompleted.and.returnValue(true);
operationSelect = new OperationSelect(mockConfig, mockKeySelect, mockManager);
mockKeySelect.triggerCallback('change', 'c');
expect(operationSelect.getSelected()).toEqual('operation1');
});
mockManager.getEvaluator.and.returnValue(mockEvaluator);
});
it('waits until the metadata fully loads to populate itself', function () {
mockManager.metadataLoadCompleted.and.returnValue(false);
operationSelect = new OperationSelect(mockConfig, mockKeySelect, mockManager);
expect(operationSelect.getSelected()).toEqual('');
});
it('populates itself with operations on a metadata load', function () {
mockManager.metadataLoadCompleted.and.returnValue(false);
operationSelect = new OperationSelect(mockConfig, mockKeySelect, mockManager);
mockManager.triggerCallback('metadata');
expect(operationSelect.getSelected()).toEqual('operation1');
});
it('populates itself with operations if metadata load is already complete', function () {
mockManager.metadataLoadCompleted.and.returnValue(true);
operationSelect = new OperationSelect(mockConfig, mockKeySelect, mockManager);
expect(operationSelect.getSelected()).toEqual('operation1');
});
it('clears its selection state if the operation in its config does not apply', function () {
mockManager.metadataLoadCompleted.and.returnValue(true);
operationSelect = new OperationSelect(mockBadConfig, mockKeySelect, mockManager);
expect(operationSelect.getSelected()).toEqual('');
});
it('populates with the appropriate options when its linked key changes', function () {
mockManager.metadataLoadCompleted.and.returnValue(true);
operationSelect = new OperationSelect(mockConfig, mockKeySelect, mockManager);
mockKeySelect.triggerCallback('change', 'b');
operationSelect.setSelected('operation2');
expect(operationSelect.getSelected()).toEqual('operation2');
operationSelect.setSelected('operation1');
expect(operationSelect.getSelected()).not.toEqual('operation1');
});
it('clears its selection on a change if the operation does not apply', function () {
mockManager.metadataLoadCompleted.and.returnValue(true);
operationSelect = new OperationSelect(mockConfig, mockKeySelect, mockManager);
mockKeySelect.triggerCallback('change', 'b');
expect(operationSelect.getSelected()).toEqual('');
});
it('maintains its selected state on change if the operation does apply', function () {
mockManager.metadataLoadCompleted.and.returnValue(true);
operationSelect = new OperationSelect(mockConfig, mockKeySelect, mockManager);
mockKeySelect.triggerCallback('change', 'c');
expect(operationSelect.getSelected()).toEqual('operation1');
});
});

View File

@ -1,44 +1,44 @@
define(['../../src/input/Palette'], function (Palette) {
describe('A generic Open MCT palette input', function () {
let palette;
let callbackSpy1;
let callbackSpy2;
import Palette from '../../src/input/Palette';
beforeEach(function () {
palette = new Palette('someClass', 'someContainer', ['item1', 'item2', 'item3']);
callbackSpy1 = jasmine.createSpy('changeCallback1');
callbackSpy2 = jasmine.createSpy('changeCallback2');
});
describe('A generic Open MCT palette input', function () {
let palette;
let callbackSpy1;
let callbackSpy2;
it('gets the current item', function () {
expect(palette.getCurrent()).toEqual('item1');
});
beforeEach(function () {
palette = new Palette('someClass', 'someContainer', ['item1', 'item2', 'item3']);
callbackSpy1 = jasmine.createSpy('changeCallback1');
callbackSpy2 = jasmine.createSpy('changeCallback2');
});
it('allows setting the current item', function () {
palette.set('item2');
expect(palette.getCurrent()).toEqual('item2');
});
it('gets the current item', function () {
expect(palette.getCurrent()).toEqual('item1');
});
it('allows registering change callbacks, and errors when an unsupported event is registered', function () {
expect(function () {
palette.on('change', callbackSpy1);
}).not.toThrow();
expect(function () {
palette.on('someUnsupportedEvent', callbackSpy1);
}).toThrow();
});
it('allows setting the current item', function () {
palette.set('item2');
expect(palette.getCurrent()).toEqual('item2');
});
it('injects its callbacks with the new selected item on change', function () {
it('allows registering change callbacks, and errors when an unsupported event is registered', function () {
expect(function () {
palette.on('change', callbackSpy1);
palette.on('change', callbackSpy2);
palette.set('item2');
expect(callbackSpy1).toHaveBeenCalledWith('item2');
expect(callbackSpy2).toHaveBeenCalledWith('item2');
});
}).not.toThrow();
expect(function () {
palette.on('someUnsupportedEvent', callbackSpy1);
}).toThrow();
});
it('gracefully handles being set to an item not included in its set', function () {
palette.set('foobar');
expect(palette.getCurrent()).not.toEqual('foobar');
});
it('injects its callbacks with the new selected item on change', function () {
palette.on('change', callbackSpy1);
palette.on('change', callbackSpy2);
palette.set('item2');
expect(callbackSpy1).toHaveBeenCalledWith('item2');
expect(callbackSpy2).toHaveBeenCalledWith('item2');
});
it('gracefully handles being set to an item not included in its set', function () {
palette.set('foobar');
expect(palette.getCurrent()).not.toEqual('foobar');
});
});

View File

@ -1,61 +1,61 @@
define(['../../src/input/Select'], function (Select) {
describe('A select wrapper', function () {
let select;
let testOptions;
let callbackSpy1;
let callbackSpy2;
beforeEach(function () {
select = new Select();
testOptions = [
['item1', 'Item 1'],
['item2', 'Item 2'],
['item3', 'Item 3']
];
select.setOptions(testOptions);
callbackSpy1 = jasmine.createSpy('callbackSpy1');
callbackSpy2 = jasmine.createSpy('callbackSpy2');
});
import Select from '../../src/input/Select';
it('gets and sets the current item', function () {
select.setSelected('item1');
expect(select.getSelected()).toEqual('item1');
});
describe('A select wrapper', function () {
let select;
let testOptions;
let callbackSpy1;
let callbackSpy2;
beforeEach(function () {
select = new Select();
testOptions = [
['item1', 'Item 1'],
['item2', 'Item 2'],
['item3', 'Item 3']
];
select.setOptions(testOptions);
callbackSpy1 = jasmine.createSpy('callbackSpy1');
callbackSpy2 = jasmine.createSpy('callbackSpy2');
});
it('allows adding a single new option', function () {
select.addOption('newOption', 'A New Option');
select.setSelected('newOption');
expect(select.getSelected()).toEqual('newOption');
});
it('gets and sets the current item', function () {
select.setSelected('item1');
expect(select.getSelected()).toEqual('item1');
});
it('allows populating with a new set of options', function () {
select.setOptions([
['newItem1', 'Item 1'],
['newItem2', 'Item 2']
]);
select.setSelected('newItem1');
expect(select.getSelected()).toEqual('newItem1');
});
it('allows adding a single new option', function () {
select.addOption('newOption', 'A New Option');
select.setSelected('newOption');
expect(select.getSelected()).toEqual('newOption');
});
it('allows registering change callbacks, and errors when an unsupported event is registered', function () {
expect(function () {
select.on('change', callbackSpy1);
}).not.toThrow();
expect(function () {
select.on('someUnsupportedEvent', callbackSpy1);
}).toThrow();
});
it('allows populating with a new set of options', function () {
select.setOptions([
['newItem1', 'Item 1'],
['newItem2', 'Item 2']
]);
select.setSelected('newItem1');
expect(select.getSelected()).toEqual('newItem1');
});
it('injects its callbacks with its property and value on a change', function () {
it('allows registering change callbacks, and errors when an unsupported event is registered', function () {
expect(function () {
select.on('change', callbackSpy1);
select.on('change', callbackSpy2);
select.setSelected('item2');
expect(callbackSpy1).toHaveBeenCalledWith('item2');
expect(callbackSpy2).toHaveBeenCalledWith('item2');
});
}).not.toThrow();
expect(function () {
select.on('someUnsupportedEvent', callbackSpy1);
}).toThrow();
});
it('gracefully handles being set to an item not included in its set', function () {
select.setSelected('foobar');
expect(select.getSelected()).not.toEqual('foobar');
});
it('injects its callbacks with its property and value on a change', function () {
select.on('change', callbackSpy1);
select.on('change', callbackSpy2);
select.setSelected('item2');
expect(callbackSpy1).toHaveBeenCalledWith('item2');
expect(callbackSpy2).toHaveBeenCalledWith('item2');
});
it('gracefully handles being set to an item not included in its set', function () {
select.setSelected('foobar');
expect(select.getSelected()).not.toEqual('foobar');
});
});

View File

@ -19,41 +19,40 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import Tabs from './tabs';
define(['./tabs'], function (Tabs) {
return function plugin() {
return function install(openmct) {
openmct.objectViews.addProvider(new Tabs.default(openmct));
export default function plugin() {
return function install(openmct) {
openmct.objectViews.addProvider(new Tabs(openmct));
openmct.types.addType('tabs', {
name: 'Tabs View',
description: 'Quickly navigate between multiple objects of any type using tabs.',
creatable: true,
cssClass: 'icon-tabs-view',
initialize(domainObject) {
domainObject.composition = [];
domainObject.keep_alive = true;
},
form: [
{
key: 'keep_alive',
name: 'Eager Load Tabs',
control: 'select',
options: [
{
name: 'True',
value: true
},
{
name: 'False',
value: false
}
],
required: true,
cssClass: 'l-input'
}
]
});
};
openmct.types.addType('tabs', {
name: 'Tabs View',
description: 'Quickly navigate between multiple objects of any type using tabs.',
creatable: true,
cssClass: 'icon-tabs-view',
initialize(domainObject) {
domainObject.composition = [];
domainObject.keep_alive = true;
},
form: [
{
key: 'keep_alive',
name: 'Eager Load Tabs',
control: 'select',
options: [
{
name: 'True',
value: true
},
{
name: 'False',
value: false
}
],
required: true,
cssClass: 'l-input'
}
]
});
};
});
}

View File

@ -47,7 +47,7 @@ export default class Tabs {
let component = null;
return {
show: function (element, editMode) {
show(element, editMode) {
const { vNode, destroy } = mount(
{
el: element,

View File

@ -20,59 +20,57 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./src/MeanTelemetryProvider'], function (MeanTelemetryProvider) {
const DEFAULT_SAMPLES = 10;
import MeanTelemetryProvider from './src/MeanTelemetryProvider';
function plugin() {
return function install(openmct) {
openmct.types.addType('telemetry-mean', {
name: 'Telemetry Filter',
description:
'Provides telemetry values that represent the mean of the last N values of a telemetry stream',
creatable: true,
cssClass: 'icon-telemetry',
initialize: function (domainObject) {
domainObject.samples = DEFAULT_SAMPLES;
domainObject.telemetry = {};
domainObject.telemetry.values = openmct.time
.getAllTimeSystems()
.map(function (timeSystem, index) {
return {
key: timeSystem.key,
name: timeSystem.name,
hints: {
domain: index + 1
}
};
});
domainObject.telemetry.values.push({
key: 'value',
name: 'Value',
hints: {
range: 1
}
const DEFAULT_SAMPLES = 10;
export default function plugin() {
return function install(openmct) {
openmct.types.addType('telemetry-mean', {
name: 'Telemetry Filter',
description:
'Provides telemetry values that represent the mean of the last N values of a telemetry stream',
creatable: true,
cssClass: 'icon-telemetry',
initialize: function (domainObject) {
domainObject.samples = DEFAULT_SAMPLES;
domainObject.telemetry = {};
domainObject.telemetry.values = openmct.time
.getAllTimeSystems()
.map(function (timeSystem, index) {
return {
key: timeSystem.key,
name: timeSystem.name,
hints: {
domain: index + 1
}
};
});
},
form: [
{
key: 'telemetryPoint',
name: 'Telemetry Point',
control: 'textfield',
required: true,
cssClass: 'l-input-lg'
},
{
key: 'samples',
name: 'Samples to Average',
control: 'textfield',
required: true,
cssClass: 'l-input-sm'
domainObject.telemetry.values.push({
key: 'value',
name: 'Value',
hints: {
range: 1
}
]
});
openmct.telemetry.addProvider(new MeanTelemetryProvider(openmct));
};
}
return plugin;
});
});
},
form: [
{
key: 'telemetryPoint',
name: 'Telemetry Point',
control: 'textfield',
required: true,
cssClass: 'l-input-lg'
},
{
key: 'samples',
name: 'Samples to Average',
control: 'textfield',
required: true,
cssClass: 'l-input-sm'
}
]
});
openmct.telemetry.addProvider(new MeanTelemetryProvider(openmct));
};
}

View File

@ -20,114 +20,114 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['objectUtils', './TelemetryAverager'], function (objectUtils, TelemetryAverager) {
function MeanTelemetryProvider(openmct) {
this.openmct = openmct;
this.telemetryAPI = openmct.telemetry;
this.timeAPI = openmct.time;
this.objectAPI = openmct.objects;
this.perObjectProviders = {};
}
import objectUtils from 'objectUtils';
MeanTelemetryProvider.prototype.canProvideTelemetry = function (domainObject) {
return domainObject.type === 'telemetry-mean';
};
import TelemetryAverager from './TelemetryAverager';
MeanTelemetryProvider.prototype.supportsRequest =
MeanTelemetryProvider.prototype.supportsSubscribe =
MeanTelemetryProvider.prototype.canProvideTelemetry;
export default function MeanTelemetryProvider(openmct) {
this.openmct = openmct;
this.telemetryAPI = openmct.telemetry;
this.timeAPI = openmct.time;
this.objectAPI = openmct.objects;
this.perObjectProviders = {};
}
MeanTelemetryProvider.prototype.subscribe = function (domainObject, callback) {
let wrappedUnsubscribe;
let unsubscribeCalled = false;
const objectId = objectUtils.parseKeyString(domainObject.telemetryPoint);
const samples = domainObject.samples;
MeanTelemetryProvider.prototype.canProvideTelemetry = function (domainObject) {
return domainObject.type === 'telemetry-mean';
};
this.objectAPI
.get(objectId)
.then(
function (linkedDomainObject) {
if (!unsubscribeCalled) {
wrappedUnsubscribe = this.subscribeToAverage(linkedDomainObject, samples, callback);
}
}.bind(this)
)
.catch(logError);
MeanTelemetryProvider.prototype.supportsRequest =
MeanTelemetryProvider.prototype.supportsSubscribe =
MeanTelemetryProvider.prototype.canProvideTelemetry;
return function unsubscribe() {
unsubscribeCalled = true;
if (wrappedUnsubscribe !== undefined) {
wrappedUnsubscribe();
}
};
};
MeanTelemetryProvider.prototype.subscribe = function (domainObject, callback) {
let wrappedUnsubscribe;
let unsubscribeCalled = false;
const objectId = objectUtils.parseKeyString(domainObject.telemetryPoint);
const samples = domainObject.samples;
MeanTelemetryProvider.prototype.subscribeToAverage = function (domainObject, samples, callback) {
const telemetryAverager = new TelemetryAverager(
this.telemetryAPI,
this.timeAPI,
domainObject,
samples,
callback
);
const createAverageDatum = telemetryAverager.createAverageDatum.bind(telemetryAverager);
return this.telemetryAPI.subscribe(domainObject, createAverageDatum);
};
MeanTelemetryProvider.prototype.request = function (domainObject, request) {
const objectId = objectUtils.parseKeyString(domainObject.telemetryPoint);
const samples = domainObject.samples;
return this.objectAPI.get(objectId).then(
this.objectAPI
.get(objectId)
.then(
function (linkedDomainObject) {
return this.requestAverageTelemetry(linkedDomainObject, request, samples);
if (!unsubscribeCalled) {
wrappedUnsubscribe = this.subscribeToAverage(linkedDomainObject, samples, callback);
}
}.bind(this)
);
};
)
.catch(logError);
/**
* @private
*/
MeanTelemetryProvider.prototype.requestAverageTelemetry = function (
domainObject,
request,
samples
) {
const averageData = [];
const addToAverageData = averageData.push.bind(averageData);
const telemetryAverager = new TelemetryAverager(
this.telemetryAPI,
this.timeAPI,
domainObject,
samples,
addToAverageData
);
const createAverageDatum = telemetryAverager.createAverageDatum.bind(telemetryAverager);
return this.telemetryAPI.request(domainObject, request).then(function (telemetryData) {
telemetryData.forEach(createAverageDatum);
return averageData;
});
};
/**
* @private
*/
MeanTelemetryProvider.prototype.getLinkedObject = function (domainObject) {
const objectId = objectUtils.parseKeyString(domainObject.telemetryPoint);
return this.objectAPI.get(objectId);
};
function logError(error) {
if (error.stack) {
console.error(error.stack);
} else {
console.error(error);
return function unsubscribe() {
unsubscribeCalled = true;
if (wrappedUnsubscribe !== undefined) {
wrappedUnsubscribe();
}
}
};
};
return MeanTelemetryProvider;
});
MeanTelemetryProvider.prototype.subscribeToAverage = function (domainObject, samples, callback) {
const telemetryAverager = new TelemetryAverager(
this.telemetryAPI,
this.timeAPI,
domainObject,
samples,
callback
);
const createAverageDatum = telemetryAverager.createAverageDatum.bind(telemetryAverager);
return this.telemetryAPI.subscribe(domainObject, createAverageDatum);
};
MeanTelemetryProvider.prototype.request = function (domainObject, request) {
const objectId = objectUtils.parseKeyString(domainObject.telemetryPoint);
const samples = domainObject.samples;
return this.objectAPI.get(objectId).then(
function (linkedDomainObject) {
return this.requestAverageTelemetry(linkedDomainObject, request, samples);
}.bind(this)
);
};
/**
* @private
*/
MeanTelemetryProvider.prototype.requestAverageTelemetry = function (
domainObject,
request,
samples
) {
const averageData = [];
const addToAverageData = averageData.push.bind(averageData);
const telemetryAverager = new TelemetryAverager(
this.telemetryAPI,
this.timeAPI,
domainObject,
samples,
addToAverageData
);
const createAverageDatum = telemetryAverager.createAverageDatum.bind(telemetryAverager);
return this.telemetryAPI.request(domainObject, request).then(function (telemetryData) {
telemetryData.forEach(createAverageDatum);
return averageData;
});
};
/**
* @private
*/
MeanTelemetryProvider.prototype.getLinkedObject = function (domainObject) {
const objectId = objectUtils.parseKeyString(domainObject.telemetryPoint);
return this.objectAPI.get(objectId);
};
function logError(error) {
if (error.stack) {
console.error(error.stack);
} else {
console.error(error);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -20,82 +20,78 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
function MockTelemetryApi() {
this.createSpy('subscribe');
this.createSpy('getMetadata');
export default function MockTelemetryApi() {
this.createSpy('subscribe');
this.createSpy('getMetadata');
this.metadata = this.createMockMetadata();
this.setDefaultRangeTo('defaultRange');
this.unsubscribe = jasmine.createSpy('unsubscribe');
this.mockReceiveTelemetry = this.mockReceiveTelemetry.bind(this);
}
this.metadata = this.createMockMetadata();
this.setDefaultRangeTo('defaultRange');
this.unsubscribe = jasmine.createSpy('unsubscribe');
this.mockReceiveTelemetry = this.mockReceiveTelemetry.bind(this);
}
MockTelemetryApi.prototype.subscribe = function () {
return this.unsubscribe;
MockTelemetryApi.prototype.subscribe = function () {
return this.unsubscribe;
};
MockTelemetryApi.prototype.getMetadata = function (object) {
return this.metadata;
};
MockTelemetryApi.prototype.request = jasmine.createSpy('request');
MockTelemetryApi.prototype.getValueFormatter = function (valueMetadata) {
const mockValueFormatter = jasmine.createSpyObj('valueFormatter', ['parse']);
mockValueFormatter.parse.and.callFake(function (value) {
return value[valueMetadata.key];
});
return mockValueFormatter;
};
MockTelemetryApi.prototype.mockReceiveTelemetry = function (newTelemetryDatum) {
const subscriptionCallback = this.subscribe.calls.mostRecent().args[1];
subscriptionCallback(newTelemetryDatum);
};
/**
* @private
*/
MockTelemetryApi.prototype.onRequestReturn = function (telemetryData) {
this.requestTelemetry = telemetryData;
};
/**
* @private
*/
MockTelemetryApi.prototype.setDefaultRangeTo = function (rangeKey) {
const mockMetadataValue = {
key: rangeKey
};
this.metadata.valuesForHints.and.returnValue([mockMetadataValue]);
};
MockTelemetryApi.prototype.getMetadata = function (object) {
return this.metadata;
};
/**
* @private
*/
MockTelemetryApi.prototype.createMockMetadata = function () {
const mockMetadata = jasmine.createSpyObj('metadata', ['value', 'valuesForHints']);
MockTelemetryApi.prototype.request = jasmine.createSpy('request');
MockTelemetryApi.prototype.getValueFormatter = function (valueMetadata) {
const mockValueFormatter = jasmine.createSpyObj('valueFormatter', ['parse']);
mockValueFormatter.parse.and.callFake(function (value) {
return value[valueMetadata.key];
});
return mockValueFormatter;
};
MockTelemetryApi.prototype.mockReceiveTelemetry = function (newTelemetryDatum) {
const subscriptionCallback = this.subscribe.calls.mostRecent().args[1];
subscriptionCallback(newTelemetryDatum);
};
/**
* @private
*/
MockTelemetryApi.prototype.onRequestReturn = function (telemetryData) {
this.requestTelemetry = telemetryData;
};
/**
* @private
*/
MockTelemetryApi.prototype.setDefaultRangeTo = function (rangeKey) {
const mockMetadataValue = {
key: rangeKey
mockMetadata.value.and.callFake(function (key) {
return {
key: key
};
this.metadata.valuesForHints.and.returnValue([mockMetadataValue]);
};
});
/**
* @private
*/
MockTelemetryApi.prototype.createMockMetadata = function () {
const mockMetadata = jasmine.createSpyObj('metadata', ['value', 'valuesForHints']);
return mockMetadata;
};
mockMetadata.value.and.callFake(function (key) {
return {
key: key
};
});
return mockMetadata;
};
/**
* @private
*/
MockTelemetryApi.prototype.createSpy = function (functionName) {
this[functionName] = this[functionName].bind(this);
spyOn(this, functionName);
this[functionName].and.callThrough();
};
return MockTelemetryApi;
});
/**
* @private
*/
MockTelemetryApi.prototype.createSpy = function (functionName) {
this[functionName] = this[functionName].bind(this);
spyOn(this, functionName);
this[functionName].and.callThrough();
};

View File

@ -20,100 +20,102 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
function TelemetryAverager(telemetryAPI, timeAPI, domainObject, samples, averageDatumCallback) {
this.telemetryAPI = telemetryAPI;
this.timeAPI = timeAPI;
export default function TelemetryAverager(
telemetryAPI,
timeAPI,
domainObject,
samples,
averageDatumCallback
) {
this.telemetryAPI = telemetryAPI;
this.timeAPI = timeAPI;
this.domainObject = domainObject;
this.samples = samples;
this.averagingWindow = [];
this.domainObject = domainObject;
this.samples = samples;
this.averagingWindow = [];
this.rangeKey = undefined;
this.rangeFormatter = undefined;
this.setRangeKeyAndFormatter();
this.rangeKey = undefined;
this.rangeFormatter = undefined;
this.setRangeKeyAndFormatter();
// Defined dynamically based on current time system
this.domainKey = undefined;
this.domainFormatter = undefined;
// Defined dynamically based on current time system
this.domainKey = undefined;
this.domainFormatter = undefined;
this.averageDatumCallback = averageDatumCallback;
this.averageDatumCallback = averageDatumCallback;
}
TelemetryAverager.prototype.createAverageDatum = function (telemetryDatum) {
this.setDomainKeyAndFormatter();
const timeValue = this.domainFormatter.parse(telemetryDatum);
const rangeValue = this.rangeFormatter.parse(telemetryDatum);
this.averagingWindow.push(rangeValue);
if (this.averagingWindow.length < this.samples) {
// We do not have enough data to produce an average
return;
} else if (this.averagingWindow.length > this.samples) {
//Do not let averaging window grow beyond defined sample size
this.averagingWindow.shift();
}
TelemetryAverager.prototype.createAverageDatum = function (telemetryDatum) {
this.setDomainKeyAndFormatter();
const averageValue = this.calculateMean();
const timeValue = this.domainFormatter.parse(telemetryDatum);
const rangeValue = this.rangeFormatter.parse(telemetryDatum);
const meanDatum = {};
meanDatum[this.domainKey] = timeValue;
meanDatum.value = averageValue;
this.averagingWindow.push(rangeValue);
this.averageDatumCallback(meanDatum);
};
if (this.averagingWindow.length < this.samples) {
// We do not have enough data to produce an average
return;
} else if (this.averagingWindow.length > this.samples) {
//Do not let averaging window grow beyond defined sample size
this.averagingWindow.shift();
}
/**
* @private
*/
TelemetryAverager.prototype.calculateMean = function () {
let sum = 0;
let i = 0;
const averageValue = this.calculateMean();
for (; i < this.averagingWindow.length; i++) {
sum += this.averagingWindow[i];
}
const meanDatum = {};
meanDatum[this.domainKey] = timeValue;
meanDatum.value = averageValue;
return sum / this.averagingWindow.length;
};
this.averageDatumCallback(meanDatum);
};
/**
* The mean telemetry filter produces domain values in whatever time
* system is currently selected from the conductor. Because this can
* change dynamically, the averager needs to be updated regularly with
* the current domain.
* @private
*/
TelemetryAverager.prototype.setDomainKeyAndFormatter = function () {
const domainKey = this.timeAPI.timeSystem().key;
if (domainKey !== this.domainKey) {
this.domainKey = domainKey;
this.domainFormatter = this.getFormatter(domainKey);
}
};
/**
* @private
*/
TelemetryAverager.prototype.calculateMean = function () {
let sum = 0;
let i = 0;
/**
* @private
*/
TelemetryAverager.prototype.setRangeKeyAndFormatter = function () {
const metadatas = this.telemetryAPI.getMetadata(this.domainObject);
const rangeValues = metadatas.valuesForHints(['range']);
for (; i < this.averagingWindow.length; i++) {
sum += this.averagingWindow[i];
}
this.rangeKey = rangeValues[0].key;
this.rangeFormatter = this.getFormatter(this.rangeKey);
};
return sum / this.averagingWindow.length;
};
/**
* @private
*/
TelemetryAverager.prototype.getFormatter = function (key) {
const objectMetadata = this.telemetryAPI.getMetadata(this.domainObject);
const valueMetadata = objectMetadata.value(key);
/**
* The mean telemetry filter produces domain values in whatever time
* system is currently selected from the conductor. Because this can
* change dynamically, the averager needs to be updated regularly with
* the current domain.
* @private
*/
TelemetryAverager.prototype.setDomainKeyAndFormatter = function () {
const domainKey = this.timeAPI.timeSystem().key;
if (domainKey !== this.domainKey) {
this.domainKey = domainKey;
this.domainFormatter = this.getFormatter(domainKey);
}
};
/**
* @private
*/
TelemetryAverager.prototype.setRangeKeyAndFormatter = function () {
const metadatas = this.telemetryAPI.getMetadata(this.domainObject);
const rangeValues = metadatas.valuesForHints(['range']);
this.rangeKey = rangeValues[0].key;
this.rangeFormatter = this.getFormatter(this.rangeKey);
};
/**
* @private
*/
TelemetryAverager.prototype.getFormatter = function (key) {
const objectMetadata = this.telemetryAPI.getMetadata(this.domainObject);
const valueMetadata = objectMetadata.value(key);
return this.telemetryAPI.getValueFormatter(valueMetadata);
};
return TelemetryAverager;
});
return this.telemetryAPI.getValueFormatter(valueMetadata);
};

View File

@ -20,457 +20,444 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
'EventEmitter',
'lodash',
'./collections/TableRowCollection',
'./TelemetryTableRow',
'./TelemetryTableNameColumn',
'./TelemetryTableColumn',
'./TelemetryTableUnitColumn',
'./TelemetryTableConfiguration',
'../../utils/staleness'
], function (
EventEmitter,
_,
TableRowCollection,
TelemetryTableRow,
TelemetryTableNameColumn,
TelemetryTableColumn,
TelemetryTableUnitColumn,
TelemetryTableConfiguration,
StalenessUtils
) {
class TelemetryTable extends EventEmitter {
constructor(domainObject, openmct) {
super();
import EventEmitter from 'EventEmitter';
import _ from 'lodash';
this.domainObject = domainObject;
this.openmct = openmct;
this.rowCount = 100;
this.tableComposition = undefined;
this.datumCache = [];
this.configuration = new TelemetryTableConfiguration(domainObject, openmct);
this.paused = false;
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
import StalenessUtils from '../../utils/staleness';
import TableRowCollection from './collections/TableRowCollection';
import TelemetryTableColumn from './TelemetryTableColumn';
import TelemetryTableConfiguration from './TelemetryTableConfiguration';
import TelemetryTableNameColumn from './TelemetryTableNameColumn';
import TelemetryTableRow from './TelemetryTableRow';
import TelemetryTableUnitColumn from './TelemetryTableUnitColumn';
this.telemetryObjects = {};
this.subscribedStaleObjects = new Map();
this.telemetryCollections = {};
this.delayedActions = [];
this.outstandingRequests = 0;
this.stalenessSubscription = {};
export default class TelemetryTable extends EventEmitter {
constructor(domainObject, openmct) {
super();
this.addTelemetryObject = this.addTelemetryObject.bind(this);
this.removeTelemetryObject = this.removeTelemetryObject.bind(this);
this.removeTelemetryCollection = this.removeTelemetryCollection.bind(this);
this.incrementOutstandingRequests = this.incrementOutstandingRequests.bind(this);
this.decrementOutstandingRequests = this.decrementOutstandingRequests.bind(this);
this.resetRowsFromAllData = this.resetRowsFromAllData.bind(this);
this.isTelemetryObject = this.isTelemetryObject.bind(this);
this.updateFilters = this.updateFilters.bind(this);
this.clearData = this.clearData.bind(this);
this.buildOptionsFromConfiguration = this.buildOptionsFromConfiguration.bind(this);
this.domainObject = domainObject;
this.openmct = openmct;
this.rowCount = 100;
this.tableComposition = undefined;
this.datumCache = [];
this.configuration = new TelemetryTableConfiguration(domainObject, openmct);
this.paused = false;
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.filterObserver = undefined;
this.telemetryObjects = {};
this.subscribedStaleObjects = new Map();
this.telemetryCollections = {};
this.delayedActions = [];
this.outstandingRequests = 0;
this.stalenessSubscription = {};
this.createTableRowCollections();
this.resubscribeToStaleness = this.resubscribeAllObjectsToStaleness.bind(this);
this.openmct.time.on('clockChanged', this.resubscribeToStaleness);
}
this.addTelemetryObject = this.addTelemetryObject.bind(this);
this.removeTelemetryObject = this.removeTelemetryObject.bind(this);
this.removeTelemetryCollection = this.removeTelemetryCollection.bind(this);
this.incrementOutstandingRequests = this.incrementOutstandingRequests.bind(this);
this.decrementOutstandingRequests = this.decrementOutstandingRequests.bind(this);
this.resetRowsFromAllData = this.resetRowsFromAllData.bind(this);
this.isTelemetryObject = this.isTelemetryObject.bind(this);
this.updateFilters = this.updateFilters.bind(this);
this.clearData = this.clearData.bind(this);
this.buildOptionsFromConfiguration = this.buildOptionsFromConfiguration.bind(this);
/**
* @private
*/
addNameColumn(telemetryObject, metadataValues) {
let metadatum = metadataValues.find((m) => m.key === 'name');
if (!metadatum) {
metadatum = {
format: 'string',
key: 'name',
name: 'Name'
};
}
this.filterObserver = undefined;
const column = new TelemetryTableNameColumn(this.openmct, telemetryObject, metadatum);
this.createTableRowCollections();
this.resubscribeToStaleness = this.resubscribeAllObjectsToStaleness.bind(this);
this.openmct.time.on('clockChanged', this.resubscribeToStaleness);
}
this.configuration.addSingleColumnForObject(telemetryObject, column);
}
initialize() {
if (this.domainObject.type === 'table') {
this.filterObserver = this.openmct.objects.observe(
this.domainObject,
'configuration.filters',
this.updateFilters
);
this.filters = this.domainObject.configuration.filters;
this.loadComposition();
} else {
this.addTelemetryObject(this.domainObject);
}
}
createTableRowCollections() {
this.tableRows = new TableRowCollection();
//Fetch any persisted default sort
let sortOptions = this.configuration.getConfiguration().sortOptions;
//If no persisted sort order, default to sorting by time system, ascending.
sortOptions = sortOptions || {
key: this.openmct.time.timeSystem().key,
direction: 'asc'
/**
* @private
*/
addNameColumn(telemetryObject, metadataValues) {
let metadatum = metadataValues.find((m) => m.key === 'name');
if (!metadatum) {
metadatum = {
format: 'string',
key: 'name',
name: 'Name'
};
this.tableRows.sortBy(sortOptions);
this.tableRows.on('resetRowsFromAllData', this.resetRowsFromAllData);
}
loadComposition() {
this.tableComposition = this.openmct.composition.get(this.domainObject);
const column = new TelemetryTableNameColumn(this.openmct, telemetryObject, metadatum);
if (this.tableComposition !== undefined) {
this.tableComposition.load().then((composition) => {
composition = composition.filter(this.isTelemetryObject);
composition.forEach(this.addTelemetryObject);
this.configuration.addSingleColumnForObject(telemetryObject, column);
}
this.tableComposition.on('add', this.addTelemetryObject);
this.tableComposition.on('remove', this.removeTelemetryObject);
});
}
}
addTelemetryObject(telemetryObject) {
this.addColumnsForObject(telemetryObject, true);
const keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
let requestOptions = this.buildOptionsFromConfiguration(telemetryObject);
let columnMap = this.getColumnMapForObject(keyString);
let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
const telemetryProcessor = this.getTelemetryProcessor(keyString, columnMap, limitEvaluator);
const telemetryRemover = this.getTelemetryRemover();
this.removeTelemetryCollection(keyString);
this.telemetryCollections[keyString] = this.openmct.telemetry.requestCollection(
telemetryObject,
requestOptions
initialize() {
if (this.domainObject.type === 'table') {
this.filterObserver = this.openmct.objects.observe(
this.domainObject,
'configuration.filters',
this.updateFilters
);
this.telemetryCollections[keyString].on('requestStarted', this.incrementOutstandingRequests);
this.telemetryCollections[keyString].on('requestEnded', this.decrementOutstandingRequests);
this.telemetryCollections[keyString].on('remove', telemetryRemover);
this.telemetryCollections[keyString].on('add', telemetryProcessor);
this.telemetryCollections[keyString].on('clear', this.clearData);
this.telemetryCollections[keyString].load();
this.subscribeToStaleness(telemetryObject);
this.telemetryObjects[keyString] = {
telemetryObject,
keyString,
requestOptions,
columnMap,
limitEvaluator
};
this.emit('object-added', telemetryObject);
}
resubscribeAllObjectsToStaleness() {
if (!this.subscribedStaleObjects || this.subscribedStaleObjects.size < 1) {
return;
}
for (const [, telemetryObject] of this.subscribedStaleObjects) {
this.subscribeToStaleness(telemetryObject);
}
}
subscribeToStaleness(domainObject) {
const keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
if (this.stalenessSubscription?.[keyString]) {
this.unsubscribeFromStaleness(domainObject.identifier);
}
this.stalenessSubscription[keyString] = {};
this.stalenessSubscription[keyString].stalenessUtils = new StalenessUtils.default(
this.openmct,
domainObject
);
this.openmct.telemetry.isStale(domainObject).then((stalenessResponse) => {
if (stalenessResponse !== undefined) {
this.handleStaleness(keyString, stalenessResponse);
}
});
const stalenessSubscription = this.openmct.telemetry.subscribeToStaleness(
domainObject,
(stalenessResponse) => {
this.handleStaleness(keyString, stalenessResponse);
}
);
this.subscribedStaleObjects.set(keyString, domainObject);
this.stalenessSubscription[keyString].unsubscribe = stalenessSubscription;
}
handleStaleness(keyString, stalenessResponse, skipCheck = false) {
if (
skipCheck ||
this.stalenessSubscription[keyString].stalenessUtils.shouldUpdateStaleness(
stalenessResponse,
keyString
)
) {
this.emit('telemetry-staleness', {
keyString,
stalenessResponse: stalenessResponse
});
}
}
getTelemetryProcessor(keyString, columnMap, limitEvaluator) {
return (telemetry) => {
//Check that telemetry object has not been removed since telemetry was requested.
if (!this.telemetryObjects[keyString]) {
return;
}
const metadataValue = this.openmct.telemetry
.getMetadata(this.telemetryObjects[keyString].telemetryObject)
.getUseToUpdateInPlaceValue();
let telemetryRows = telemetry.map(
(datum) =>
new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator, metadataValue?.key)
);
if (this.paused) {
this.delayedActions.push(this.tableRows.addRows.bind(this, telemetryRows, 'add'));
} else {
this.tableRows.addRows(telemetryRows);
}
};
}
getTelemetryRemover() {
return (telemetry) => {
if (this.paused) {
this.delayedActions.push(this.tableRows.removeRowsByData.bind(this, telemetry));
} else {
this.tableRows.removeRowsByData(telemetry);
}
};
}
/**
* @private
*/
incrementOutstandingRequests() {
if (this.outstandingRequests === 0) {
this.emit('outstanding-requests', true);
}
this.outstandingRequests++;
}
/**
* @private
*/
decrementOutstandingRequests() {
this.outstandingRequests--;
if (this.outstandingRequests === 0) {
this.emit('outstanding-requests', false);
}
}
// will pull all necessary information for all existing bounded telemetry
// and pass to table row collection to reset without making any new requests
// triggered by filtering
resetRowsFromAllData() {
let allRows = [];
Object.keys(this.telemetryCollections).forEach((keyString) => {
let { columnMap, limitEvaluator } = this.telemetryObjects[keyString];
const metadataValue = this.openmct.telemetry
.getMetadata(this.telemetryObjects[keyString].telemetryObject)
.getUseToUpdateInPlaceValue();
this.telemetryCollections[keyString].getAll().forEach((datum) => {
allRows.push(
new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator, metadataValue?.key)
);
});
});
this.tableRows.clearRowsFromTableAndFilter(allRows);
}
updateFilters(updatedFilters) {
let deepCopiedFilters = JSON.parse(JSON.stringify(updatedFilters));
if (this.filters && !_.isEqual(this.filters, deepCopiedFilters)) {
this.filters = deepCopiedFilters;
this.tableRows.clear();
this.clearAndResubscribe();
} else {
this.filters = deepCopiedFilters;
}
}
clearAndResubscribe() {
let objectKeys = Object.keys(this.telemetryObjects);
this.tableRows.clear();
objectKeys.forEach((keyString) => {
this.addTelemetryObject(this.telemetryObjects[keyString].telemetryObject);
});
}
removeTelemetryObject(objectIdentifier) {
const keyString = this.openmct.objects.makeKeyString(objectIdentifier);
this.configuration.removeColumnsForObject(objectIdentifier, true);
this.tableRows.removeRowsByObject(keyString);
this.removeTelemetryCollection(keyString);
delete this.telemetryObjects[keyString];
this.emit('object-removed', objectIdentifier);
this.unsubscribeFromStaleness(objectIdentifier);
}
unsubscribeFromStaleness(objectIdentifier) {
const keyString = this.openmct.objects.makeKeyString(objectIdentifier);
const SKIP_CHECK = true;
this.stalenessSubscription[keyString].unsubscribe();
this.stalenessSubscription[keyString].stalenessUtils.destroy();
this.handleStaleness(keyString, { isStale: false }, SKIP_CHECK);
delete this.stalenessSubscription[keyString];
}
clearData() {
this.tableRows.clear();
this.emit('refresh');
}
addColumnsForObject(telemetryObject) {
const metadata = this.openmct.telemetry.getMetadata(telemetryObject);
let metadataValues = metadata.values();
this.addNameColumn(telemetryObject, metadataValues);
metadataValues.forEach((metadatum) => {
if (metadatum.key === 'name' || metadata.isInPlaceUpdateValue(metadatum)) {
return;
}
let column = this.createColumn(metadatum);
this.configuration.addSingleColumnForObject(telemetryObject, column);
// add units column if available
if (metadatum.unit !== undefined) {
let unitColumn = this.createUnitColumn(metadatum);
this.configuration.addSingleColumnForObject(telemetryObject, unitColumn);
}
});
}
getColumnMapForObject(objectKeyString) {
let columns = this.configuration.getColumns();
if (columns[objectKeyString]) {
return columns[objectKeyString].reduce((map, column) => {
map[column.getKey()] = column;
return map;
}, {});
}
return {};
}
buildOptionsFromConfiguration(telemetryObject) {
let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
let filters =
this.domainObject.configuration &&
this.domainObject.configuration.filters &&
this.domainObject.configuration.filters[keyString];
return { filters } || {};
}
createColumn(metadatum) {
return new TelemetryTableColumn(this.openmct, metadatum);
}
createUnitColumn(metadatum) {
return new TelemetryTableUnitColumn(this.openmct, metadatum);
}
isTelemetryObject(domainObject) {
return Object.prototype.hasOwnProperty.call(domainObject, 'telemetry');
}
sortBy(sortOptions) {
this.tableRows.sortBy(sortOptions);
if (this.openmct.editor.isEditing()) {
let configuration = this.configuration.getConfiguration();
configuration.sortOptions = sortOptions;
this.configuration.updateConfiguration(configuration);
}
}
runDelayedActions() {
this.delayedActions.forEach((action) => action());
this.delayedActions = [];
}
removeTelemetryCollection(keyString) {
if (this.telemetryCollections[keyString]) {
this.telemetryCollections[keyString].destroy();
this.telemetryCollections[keyString] = undefined;
delete this.telemetryCollections[keyString];
}
}
pause() {
this.paused = true;
}
unpause() {
this.paused = false;
this.runDelayedActions();
}
destroy() {
this.tableRows.destroy();
this.tableRows.off('resetRowsFromAllData', this.resetRowsFromAllData);
this.openmct.time.off('clockChanged', this.resubscribeToStaleness);
let keystrings = Object.keys(this.telemetryCollections);
keystrings.forEach(this.removeTelemetryCollection);
if (this.filterObserver) {
this.filterObserver();
}
Object.values(this.stalenessSubscription).forEach((stalenessSubscription) => {
stalenessSubscription.unsubscribe();
stalenessSubscription.stalenessUtils.destroy();
});
if (this.tableComposition !== undefined) {
this.tableComposition.off('add', this.addTelemetryObject);
this.tableComposition.off('remove', this.removeTelemetryObject);
}
this.filters = this.domainObject.configuration.filters;
this.loadComposition();
} else {
this.addTelemetryObject(this.domainObject);
}
}
return TelemetryTable;
});
createTableRowCollections() {
this.tableRows = new TableRowCollection();
//Fetch any persisted default sort
let sortOptions = this.configuration.getConfiguration().sortOptions;
//If no persisted sort order, default to sorting by time system, ascending.
sortOptions = sortOptions || {
key: this.openmct.time.timeSystem().key,
direction: 'asc'
};
this.tableRows.sortBy(sortOptions);
this.tableRows.on('resetRowsFromAllData', this.resetRowsFromAllData);
}
loadComposition() {
this.tableComposition = this.openmct.composition.get(this.domainObject);
if (this.tableComposition !== undefined) {
this.tableComposition.load().then((composition) => {
composition = composition.filter(this.isTelemetryObject);
composition.forEach(this.addTelemetryObject);
this.tableComposition.on('add', this.addTelemetryObject);
this.tableComposition.on('remove', this.removeTelemetryObject);
});
}
}
addTelemetryObject(telemetryObject) {
this.addColumnsForObject(telemetryObject, true);
const keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
let requestOptions = this.buildOptionsFromConfiguration(telemetryObject);
let columnMap = this.getColumnMapForObject(keyString);
let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
const telemetryProcessor = this.getTelemetryProcessor(keyString, columnMap, limitEvaluator);
const telemetryRemover = this.getTelemetryRemover();
this.removeTelemetryCollection(keyString);
this.telemetryCollections[keyString] = this.openmct.telemetry.requestCollection(
telemetryObject,
requestOptions
);
this.telemetryCollections[keyString].on('requestStarted', this.incrementOutstandingRequests);
this.telemetryCollections[keyString].on('requestEnded', this.decrementOutstandingRequests);
this.telemetryCollections[keyString].on('remove', telemetryRemover);
this.telemetryCollections[keyString].on('add', telemetryProcessor);
this.telemetryCollections[keyString].on('clear', this.clearData);
this.telemetryCollections[keyString].load();
this.subscribeToStaleness(telemetryObject);
this.telemetryObjects[keyString] = {
telemetryObject,
keyString,
requestOptions,
columnMap,
limitEvaluator
};
this.emit('object-added', telemetryObject);
}
resubscribeAllObjectsToStaleness() {
if (!this.subscribedStaleObjects || this.subscribedStaleObjects.size < 1) {
return;
}
for (const [, telemetryObject] of this.subscribedStaleObjects) {
this.subscribeToStaleness(telemetryObject);
}
}
subscribeToStaleness(domainObject) {
const keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
if (this.stalenessSubscription?.[keyString]) {
this.unsubscribeFromStaleness(domainObject.identifier);
}
this.stalenessSubscription[keyString] = {};
this.stalenessSubscription[keyString].stalenessUtils = new StalenessUtils(
this.openmct,
domainObject
);
this.openmct.telemetry.isStale(domainObject).then((stalenessResponse) => {
if (stalenessResponse !== undefined) {
this.handleStaleness(keyString, stalenessResponse);
}
});
const stalenessSubscription = this.openmct.telemetry.subscribeToStaleness(
domainObject,
(stalenessResponse) => {
this.handleStaleness(keyString, stalenessResponse);
}
);
this.subscribedStaleObjects.set(keyString, domainObject);
this.stalenessSubscription[keyString].unsubscribe = stalenessSubscription;
}
handleStaleness(keyString, stalenessResponse, skipCheck = false) {
if (
skipCheck ||
this.stalenessSubscription[keyString].stalenessUtils.shouldUpdateStaleness(
stalenessResponse,
keyString
)
) {
this.emit('telemetry-staleness', {
keyString,
stalenessResponse: stalenessResponse
});
}
}
getTelemetryProcessor(keyString, columnMap, limitEvaluator) {
return (telemetry) => {
//Check that telemetry object has not been removed since telemetry was requested.
if (!this.telemetryObjects[keyString]) {
return;
}
const metadataValue = this.openmct.telemetry
.getMetadata(this.telemetryObjects[keyString].telemetryObject)
.getUseToUpdateInPlaceValue();
let telemetryRows = telemetry.map(
(datum) =>
new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator, metadataValue?.key)
);
if (this.paused) {
this.delayedActions.push(this.tableRows.addRows.bind(this, telemetryRows, 'add'));
} else {
this.tableRows.addRows(telemetryRows);
}
};
}
getTelemetryRemover() {
return (telemetry) => {
if (this.paused) {
this.delayedActions.push(this.tableRows.removeRowsByData.bind(this, telemetry));
} else {
this.tableRows.removeRowsByData(telemetry);
}
};
}
/**
* @private
*/
incrementOutstandingRequests() {
if (this.outstandingRequests === 0) {
this.emit('outstanding-requests', true);
}
this.outstandingRequests++;
}
/**
* @private
*/
decrementOutstandingRequests() {
this.outstandingRequests--;
if (this.outstandingRequests === 0) {
this.emit('outstanding-requests', false);
}
}
// will pull all necessary information for all existing bounded telemetry
// and pass to table row collection to reset without making any new requests
// triggered by filtering
resetRowsFromAllData() {
let allRows = [];
Object.keys(this.telemetryCollections).forEach((keyString) => {
let { columnMap, limitEvaluator } = this.telemetryObjects[keyString];
const metadataValue = this.openmct.telemetry
.getMetadata(this.telemetryObjects[keyString].telemetryObject)
.getUseToUpdateInPlaceValue();
this.telemetryCollections[keyString].getAll().forEach((datum) => {
allRows.push(
new TelemetryTableRow(datum, columnMap, keyString, limitEvaluator, metadataValue?.key)
);
});
});
this.tableRows.clearRowsFromTableAndFilter(allRows);
}
updateFilters(updatedFilters) {
let deepCopiedFilters = JSON.parse(JSON.stringify(updatedFilters));
if (this.filters && !_.isEqual(this.filters, deepCopiedFilters)) {
this.filters = deepCopiedFilters;
this.tableRows.clear();
this.clearAndResubscribe();
} else {
this.filters = deepCopiedFilters;
}
}
clearAndResubscribe() {
let objectKeys = Object.keys(this.telemetryObjects);
this.tableRows.clear();
objectKeys.forEach((keyString) => {
this.addTelemetryObject(this.telemetryObjects[keyString].telemetryObject);
});
}
removeTelemetryObject(objectIdentifier) {
const keyString = this.openmct.objects.makeKeyString(objectIdentifier);
this.configuration.removeColumnsForObject(objectIdentifier, true);
this.tableRows.removeRowsByObject(keyString);
this.removeTelemetryCollection(keyString);
delete this.telemetryObjects[keyString];
this.emit('object-removed', objectIdentifier);
this.unsubscribeFromStaleness(objectIdentifier);
}
unsubscribeFromStaleness(objectIdentifier) {
const keyString = this.openmct.objects.makeKeyString(objectIdentifier);
const SKIP_CHECK = true;
this.stalenessSubscription[keyString].unsubscribe();
this.stalenessSubscription[keyString].stalenessUtils.destroy();
this.handleStaleness(keyString, { isStale: false }, SKIP_CHECK);
delete this.stalenessSubscription[keyString];
}
clearData() {
this.tableRows.clear();
this.emit('refresh');
}
addColumnsForObject(telemetryObject) {
const metadata = this.openmct.telemetry.getMetadata(telemetryObject);
let metadataValues = metadata.values();
this.addNameColumn(telemetryObject, metadataValues);
metadataValues.forEach((metadatum) => {
if (metadatum.key === 'name' || metadata.isInPlaceUpdateValue(metadatum)) {
return;
}
let column = this.createColumn(metadatum);
this.configuration.addSingleColumnForObject(telemetryObject, column);
// add units column if available
if (metadatum.unit !== undefined) {
let unitColumn = this.createUnitColumn(metadatum);
this.configuration.addSingleColumnForObject(telemetryObject, unitColumn);
}
});
}
getColumnMapForObject(objectKeyString) {
let columns = this.configuration.getColumns();
if (columns[objectKeyString]) {
return columns[objectKeyString].reduce((map, column) => {
map[column.getKey()] = column;
return map;
}, {});
}
return {};
}
buildOptionsFromConfiguration(telemetryObject) {
let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
let filters =
this.domainObject.configuration &&
this.domainObject.configuration.filters &&
this.domainObject.configuration.filters[keyString];
return { filters } || {};
}
createColumn(metadatum) {
return new TelemetryTableColumn(this.openmct, metadatum);
}
createUnitColumn(metadatum) {
return new TelemetryTableUnitColumn(this.openmct, metadatum);
}
isTelemetryObject(domainObject) {
return Object.prototype.hasOwnProperty.call(domainObject, 'telemetry');
}
sortBy(sortOptions) {
this.tableRows.sortBy(sortOptions);
if (this.openmct.editor.isEditing()) {
let configuration = this.configuration.getConfiguration();
configuration.sortOptions = sortOptions;
this.configuration.updateConfiguration(configuration);
}
}
runDelayedActions() {
this.delayedActions.forEach((action) => action());
this.delayedActions = [];
}
removeTelemetryCollection(keyString) {
if (this.telemetryCollections[keyString]) {
this.telemetryCollections[keyString].destroy();
this.telemetryCollections[keyString] = undefined;
delete this.telemetryCollections[keyString];
}
}
pause() {
this.paused = true;
}
unpause() {
this.paused = false;
this.runDelayedActions();
}
destroy() {
this.tableRows.destroy();
this.tableRows.off('resetRowsFromAllData', this.resetRowsFromAllData);
this.openmct.time.off('clockChanged', this.resubscribeToStaleness);
let keystrings = Object.keys(this.telemetryCollections);
keystrings.forEach(this.removeTelemetryCollection);
if (this.filterObserver) {
this.filterObserver();
}
Object.values(this.stalenessSubscription).forEach((stalenessSubscription) => {
stalenessSubscription.unsubscribe();
stalenessSubscription.stalenessUtils.destroy();
});
if (this.tableComposition !== undefined) {
this.tableComposition.off('add', this.addTelemetryObject);
this.tableComposition.off('remove', this.removeTelemetryObject);
}
}
}

View File

@ -19,48 +19,44 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(function () {
class TelemetryTableColumn {
constructor(openmct, metadatum, options = { selectable: false }) {
this.metadatum = metadatum;
this.formatter = openmct.telemetry.getValueFormatter(metadatum);
this.titleValue = this.metadatum.name;
this.selectable = options.selectable;
}
export default class TelemetryTableColumn {
constructor(openmct, metadatum, options = { selectable: false }) {
this.metadatum = metadatum;
this.formatter = openmct.telemetry.getValueFormatter(metadatum);
this.titleValue = this.metadatum.name;
this.selectable = options.selectable;
}
getKey() {
return this.metadatum.key;
}
getKey() {
return this.metadatum.key;
}
getTitle() {
return this.metadatum.name;
}
getTitle() {
return this.metadatum.name;
}
getMetadatum() {
return this.metadatum;
}
getMetadatum() {
return this.metadatum;
}
hasValueForDatum(telemetryDatum) {
return Object.prototype.hasOwnProperty.call(telemetryDatum, this.metadatum.source);
}
hasValueForDatum(telemetryDatum) {
return Object.prototype.hasOwnProperty.call(telemetryDatum, this.metadatum.source);
}
getRawValue(telemetryDatum) {
return telemetryDatum[this.metadatum.source];
}
getRawValue(telemetryDatum) {
return telemetryDatum[this.metadatum.source];
}
getFormattedValue(telemetryDatum) {
let formattedValue = this.formatter.format(telemetryDatum);
if (formattedValue !== undefined && typeof formattedValue !== 'string') {
return formattedValue.toString();
} else {
return formattedValue;
}
}
getParsedValue(telemetryDatum) {
return this.formatter.parse(telemetryDatum);
getFormattedValue(telemetryDatum) {
let formattedValue = this.formatter.format(telemetryDatum);
if (formattedValue !== undefined && typeof formattedValue !== 'string') {
return formattedValue.toString();
} else {
return formattedValue;
}
}
return TelemetryTableColumn;
});
getParsedValue(telemetryDatum) {
return this.formatter.parse(telemetryDatum);
}
}

View File

@ -20,149 +20,148 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['lodash', 'EventEmitter'], function (_, EventEmitter) {
class TelemetryTableConfiguration extends EventEmitter {
constructor(domainObject, openmct) {
super();
import EventEmitter from 'EventEmitter';
import _ from 'lodash';
this.domainObject = domainObject;
this.openmct = openmct;
this.columns = {};
export default class TelemetryTableConfiguration extends EventEmitter {
constructor(domainObject, openmct) {
super();
this.removeColumnsForObject = this.removeColumnsForObject.bind(this);
this.objectMutated = this.objectMutated.bind(this);
this.domainObject = domainObject;
this.openmct = openmct;
this.columns = {};
this.unlistenFromMutation = openmct.objects.observe(
domainObject,
'configuration',
this.objectMutated
);
}
this.removeColumnsForObject = this.removeColumnsForObject.bind(this);
this.objectMutated = this.objectMutated.bind(this);
getConfiguration() {
let configuration = this.domainObject.configuration || {};
configuration.hiddenColumns = configuration.hiddenColumns || {};
configuration.columnWidths = configuration.columnWidths || {};
configuration.columnOrder = configuration.columnOrder || [];
configuration.cellFormat = configuration.cellFormat || {};
configuration.autosize = configuration.autosize === undefined ? true : configuration.autosize;
this.unlistenFromMutation = openmct.objects.observe(
domainObject,
'configuration',
this.objectMutated
);
}
return configuration;
}
getConfiguration() {
let configuration = this.domainObject.configuration || {};
configuration.hiddenColumns = configuration.hiddenColumns || {};
configuration.columnWidths = configuration.columnWidths || {};
configuration.columnOrder = configuration.columnOrder || [];
configuration.cellFormat = configuration.cellFormat || {};
configuration.autosize = configuration.autosize === undefined ? true : configuration.autosize;
updateConfiguration(configuration) {
this.openmct.objects.mutate(this.domainObject, 'configuration', configuration);
}
return configuration;
}
/**
* @private
* @param {*} object
*/
objectMutated(configuration) {
if (configuration !== undefined) {
this.emit('change', configuration);
}
}
updateConfiguration(configuration) {
this.openmct.objects.mutate(this.domainObject, 'configuration', configuration);
}
addSingleColumnForObject(telemetryObject, column, position) {
let objectKeyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
this.columns[objectKeyString] = this.columns[objectKeyString] || [];
position = position || this.columns[objectKeyString].length;
this.columns[objectKeyString].splice(position, 0, column);
}
removeColumnsForObject(objectIdentifier) {
let objectKeyString = this.openmct.objects.makeKeyString(objectIdentifier);
let columnsToRemove = this.columns[objectKeyString];
delete this.columns[objectKeyString];
let configuration = this.domainObject.configuration;
let configurationChanged = false;
columnsToRemove.forEach((column) => {
//There may be more than one column with the same key (eg. time system columns)
if (!this.hasColumnWithKey(column.getKey())) {
delete configuration.hiddenColumns[column.getKey()];
configurationChanged = true;
}
});
if (configurationChanged) {
this.updateConfiguration(configuration);
}
}
hasColumnWithKey(columnKey) {
return _.flatten(Object.values(this.columns)).some((column) => column.getKey() === columnKey);
}
getColumns() {
return this.columns;
}
getAllHeaders() {
let flattenedColumns = _.flatten(Object.values(this.columns));
/* eslint-disable you-dont-need-lodash-underscore/uniq */
let headers = _.uniq(flattenedColumns, false, (column) => column.getKey()).reduce(
fromColumnsToHeadersMap,
{}
);
/* eslint-enable you-dont-need-lodash-underscore/uniq */
function fromColumnsToHeadersMap(headersMap, column) {
headersMap[column.getKey()] = column.getTitle();
return headersMap;
}
return headers;
}
getVisibleHeaders() {
let allHeaders = this.getAllHeaders();
let configuration = this.getConfiguration();
let orderedColumns = this.getColumnOrder();
let unorderedColumns = _.difference(Object.keys(allHeaders), orderedColumns);
return orderedColumns
.concat(unorderedColumns)
.filter((headerKey) => {
return configuration.hiddenColumns[headerKey] !== true;
})
.reduce((headers, headerKey) => {
headers[headerKey] = allHeaders[headerKey];
return headers;
}, {});
}
getColumnWidths() {
let configuration = this.getConfiguration();
return configuration.columnWidths;
}
setColumnWidths(columnWidths) {
let configuration = this.getConfiguration();
configuration.columnWidths = columnWidths;
this.updateConfiguration(configuration);
}
getColumnOrder() {
let configuration = this.getConfiguration();
return configuration.columnOrder;
}
setColumnOrder(columnOrder) {
let configuration = this.getConfiguration();
configuration.columnOrder = columnOrder;
this.updateConfiguration(configuration);
}
destroy() {
this.unlistenFromMutation();
/**
* @private
* @param {*} object
*/
objectMutated(configuration) {
if (configuration !== undefined) {
this.emit('change', configuration);
}
}
return TelemetryTableConfiguration;
});
addSingleColumnForObject(telemetryObject, column, position) {
let objectKeyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
this.columns[objectKeyString] = this.columns[objectKeyString] || [];
position = position || this.columns[objectKeyString].length;
this.columns[objectKeyString].splice(position, 0, column);
}
removeColumnsForObject(objectIdentifier) {
let objectKeyString = this.openmct.objects.makeKeyString(objectIdentifier);
let columnsToRemove = this.columns[objectKeyString];
delete this.columns[objectKeyString];
let configuration = this.domainObject.configuration;
let configurationChanged = false;
columnsToRemove.forEach((column) => {
//There may be more than one column with the same key (eg. time system columns)
if (!this.hasColumnWithKey(column.getKey())) {
delete configuration.hiddenColumns[column.getKey()];
configurationChanged = true;
}
});
if (configurationChanged) {
this.updateConfiguration(configuration);
}
}
hasColumnWithKey(columnKey) {
return _.flatten(Object.values(this.columns)).some((column) => column.getKey() === columnKey);
}
getColumns() {
return this.columns;
}
getAllHeaders() {
let flattenedColumns = _.flatten(Object.values(this.columns));
/* eslint-disable you-dont-need-lodash-underscore/uniq */
let headers = _.uniq(flattenedColumns, false, (column) => column.getKey()).reduce(
fromColumnsToHeadersMap,
{}
);
/* eslint-enable you-dont-need-lodash-underscore/uniq */
function fromColumnsToHeadersMap(headersMap, column) {
headersMap[column.getKey()] = column.getTitle();
return headersMap;
}
return headers;
}
getVisibleHeaders() {
let allHeaders = this.getAllHeaders();
let configuration = this.getConfiguration();
let orderedColumns = this.getColumnOrder();
let unorderedColumns = _.difference(Object.keys(allHeaders), orderedColumns);
return orderedColumns
.concat(unorderedColumns)
.filter((headerKey) => {
return configuration.hiddenColumns[headerKey] !== true;
})
.reduce((headers, headerKey) => {
headers[headerKey] = allHeaders[headerKey];
return headers;
}, {});
}
getColumnWidths() {
let configuration = this.getConfiguration();
return configuration.columnWidths;
}
setColumnWidths(columnWidths) {
let configuration = this.getConfiguration();
configuration.columnWidths = columnWidths;
this.updateConfiguration(configuration);
}
getColumnOrder() {
let configuration = this.getConfiguration();
return configuration.columnOrder;
}
setColumnOrder(columnOrder) {
let configuration = this.getConfiguration();
configuration.columnOrder = columnOrder;
this.updateConfiguration(configuration);
}
destroy() {
this.unlistenFromMutation();
}
}

View File

@ -19,22 +19,21 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./TelemetryTableColumn.js'], function (TelemetryTableColumn) {
class TelemetryTableNameColumn extends TelemetryTableColumn {
constructor(openmct, telemetryObject, metadatum) {
super(openmct, metadatum);
this.telemetryObject = telemetryObject;
}
import TelemetryTableColumn from './TelemetryTableColumn';
getRawValue() {
return this.telemetryObject.name;
}
export default class TelemetryTableNameColumn extends TelemetryTableColumn {
constructor(openmct, telemetryObject, metadatum) {
super(openmct, metadatum);
getFormattedValue() {
return this.telemetryObject.name;
}
this.telemetryObject = telemetryObject;
}
return TelemetryTableNameColumn;
});
getRawValue() {
return this.telemetryObject.name;
}
getFormattedValue() {
return this.telemetryObject.name;
}
}

View File

@ -20,108 +20,104 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
class TelemetryTableRow {
constructor(datum, columns, objectKeyString, limitEvaluator, inPlaceUpdateKey) {
this.columns = columns;
export default class TelemetryTableRow {
constructor(datum, columns, objectKeyString, limitEvaluator, inPlaceUpdateKey) {
this.columns = columns;
this.datum = createNormalizedDatum(datum, columns);
this.fullDatum = datum;
this.limitEvaluator = limitEvaluator;
this.objectKeyString = objectKeyString;
this.inPlaceUpdateKey = inPlaceUpdateKey;
this.datum = createNormalizedDatum(datum, columns);
this.fullDatum = datum;
this.limitEvaluator = limitEvaluator;
this.objectKeyString = objectKeyString;
this.inPlaceUpdateKey = inPlaceUpdateKey;
}
getFormattedDatum(headers) {
return Object.keys(headers).reduce((formattedDatum, columnKey) => {
formattedDatum[columnKey] = this.getFormattedValue(columnKey);
return formattedDatum;
}, {});
}
getFormattedValue(key) {
let column = this.columns[key];
return column && column.getFormattedValue(this.datum[key]);
}
getParsedValue(key) {
let column = this.columns[key];
return column && column.getParsedValue(this.datum[key]);
}
getCellComponentName(key) {
let column = this.columns[key];
return column && column.getCellComponentName && column.getCellComponentName();
}
getRowClass() {
if (!this.rowClass) {
let limitEvaluation = this.limitEvaluator.evaluate(this.datum);
this.rowClass = limitEvaluation && limitEvaluation.cssClass;
}
getFormattedDatum(headers) {
return Object.keys(headers).reduce((formattedDatum, columnKey) => {
formattedDatum[columnKey] = this.getFormattedValue(columnKey);
return this.rowClass;
}
return formattedDatum;
getCellLimitClasses() {
if (!this.cellLimitClasses) {
this.cellLimitClasses = Object.values(this.columns).reduce((alarmStateMap, column) => {
if (!column.isUnit) {
let limitEvaluation = this.limitEvaluator.evaluate(this.datum, column.getMetadatum());
alarmStateMap[column.getKey()] = limitEvaluation && limitEvaluation.cssClass;
}
return alarmStateMap;
}, {});
}
getFormattedValue(key) {
let column = this.columns[key];
return column && column.getFormattedValue(this.datum[key]);
}
getParsedValue(key) {
let column = this.columns[key];
return column && column.getParsedValue(this.datum[key]);
}
getCellComponentName(key) {
let column = this.columns[key];
return column && column.getCellComponentName && column.getCellComponentName();
}
getRowClass() {
if (!this.rowClass) {
let limitEvaluation = this.limitEvaluator.evaluate(this.datum);
this.rowClass = limitEvaluation && limitEvaluation.cssClass;
}
return this.rowClass;
}
getCellLimitClasses() {
if (!this.cellLimitClasses) {
this.cellLimitClasses = Object.values(this.columns).reduce((alarmStateMap, column) => {
if (!column.isUnit) {
let limitEvaluation = this.limitEvaluator.evaluate(this.datum, column.getMetadatum());
alarmStateMap[column.getKey()] = limitEvaluation && limitEvaluation.cssClass;
}
return alarmStateMap;
}, {});
}
return this.cellLimitClasses;
}
getContextualDomainObject(openmct, objectKeyString) {
return openmct.objects.get(objectKeyString);
}
getContextMenuActions() {
return ['viewDatumAction', 'viewHistoricalData'];
}
updateWithDatum(updatesToDatum) {
const normalizedUpdatesToDatum = createNormalizedDatum(updatesToDatum, this.columns);
this.datum = {
...this.datum,
...normalizedUpdatesToDatum
};
this.fullDatum = {
...this.fullDatum,
...updatesToDatum
};
}
return this.cellLimitClasses;
}
/**
* Normalize the structure of datums to assist sorting and merging of columns.
* Maps all sources to keys.
* @private
* @param {*} telemetryDatum
* @param {*} metadataValues
*/
function createNormalizedDatum(datum, columns) {
const normalizedDatum = JSON.parse(JSON.stringify(datum));
Object.values(columns).forEach((column) => {
const rawValue = column.getRawValue(datum);
if (rawValue !== undefined) {
normalizedDatum[column.getKey()] = rawValue;
}
});
return normalizedDatum;
getContextualDomainObject(openmct, objectKeyString) {
return openmct.objects.get(objectKeyString);
}
return TelemetryTableRow;
});
getContextMenuActions() {
return ['viewDatumAction', 'viewHistoricalData'];
}
updateWithDatum(updatesToDatum) {
const normalizedUpdatesToDatum = createNormalizedDatum(updatesToDatum, this.columns);
this.datum = {
...this.datum,
...normalizedUpdatesToDatum
};
this.fullDatum = {
...this.fullDatum,
...updatesToDatum
};
}
}
/**
* Normalize the structure of datums to assist sorting and merging of columns.
* Maps all sources to keys.
* @private
* @param {*} telemetryDatum
* @param {*} metadataValues
*/
function createNormalizedDatum(datum, columns) {
const normalizedDatum = JSON.parse(JSON.stringify(datum));
Object.values(columns).forEach((column) => {
const rawValue = column.getRawValue(datum);
if (rawValue !== undefined) {
normalizedDatum[column.getKey()] = rawValue;
}
});
return normalizedDatum;
}

View File

@ -20,19 +20,17 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(function () {
return {
name: 'Telemetry Table',
description:
'Display values for one or more telemetry end points in a scrolling table. Each row is a time-stamped value.',
creatable: true,
cssClass: 'icon-tabular-scrolling',
initialize(domainObject) {
domainObject.composition = [];
domainObject.configuration = {
columnWidths: {},
hiddenColumns: {}
};
}
};
});
export default {
name: 'Telemetry Table',
description:
'Display values for one or more telemetry end points in a scrolling table. Each row is a time-stamped value.',
creatable: true,
cssClass: 'icon-tabular-scrolling',
initialize(domainObject) {
domainObject.composition = [];
domainObject.configuration = {
columnWidths: {},
hiddenColumns: {}
};
}
};

View File

@ -19,38 +19,38 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['./TelemetryTableColumn.js'], function (TelemetryTableColumn) {
class TelemetryTableUnitColumn extends TelemetryTableColumn {
constructor(openmct, metadatum) {
super(openmct, metadatum);
this.isUnit = true;
this.titleValue += ' Unit';
this.formatter = {
format: (datum) => {
return this.metadatum.unit;
},
parse: (datum) => {
return this.metadatum.unit;
}
};
}
import TelemetryTableColumn from './TelemetryTableColumn.js';
getKey() {
return this.metadatum.key + '-unit';
}
getTitle() {
return this.metadatum.name + ' Unit';
}
getRawValue(telemetryDatum) {
return this.metadatum.unit;
}
getFormattedValue(telemetryDatum) {
return this.formatter.format(telemetryDatum);
}
class TelemetryTableUnitColumn extends TelemetryTableColumn {
constructor(openmct, metadatum) {
super(openmct, metadatum);
this.isUnit = true;
this.titleValue += ' Unit';
this.formatter = {
format: (datum) => {
return this.metadatum.unit;
},
parse: (datum) => {
return this.metadatum.unit;
}
};
}
return TelemetryTableUnitColumn;
});
getKey() {
return this.metadatum.key + '-unit';
}
getTitle() {
return this.metadatum.name + ' Unit';
}
getRawValue(telemetryDatum) {
return this.metadatum.unit;
}
getFormattedValue(telemetryDatum) {
return this.formatter.format(telemetryDatum);
}
}
export default TelemetryTableUnitColumn;

View File

@ -19,371 +19,369 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import EventEmitter from 'EventEmitter';
import _ from 'lodash';
define(['lodash', 'EventEmitter'], function (_, EventEmitter) {
/**
* @constructor
*/
class TableRowCollection extends EventEmitter {
constructor() {
super();
/**
* @constructor
*/
export default class TableRowCollection extends EventEmitter {
constructor() {
super();
this.rows = [];
this.columnFilters = {};
this.addRows = this.addRows.bind(this);
this.removeRowsByObject = this.removeRowsByObject.bind(this);
this.removeRowsByData = this.removeRowsByData.bind(this);
this.rows = [];
this.columnFilters = {};
this.addRows = this.addRows.bind(this);
this.removeRowsByObject = this.removeRowsByObject.bind(this);
this.removeRowsByData = this.removeRowsByData.bind(this);
this.clear = this.clear.bind(this);
}
this.clear = this.clear.bind(this);
}
removeRowsByObject(keyString) {
let removed = [];
removeRowsByObject(keyString) {
let removed = [];
this.rows = this.rows.filter((row) => {
if (row.objectKeyString === keyString) {
removed.push(row);
this.rows = this.rows.filter((row) => {
if (row.objectKeyString === keyString) {
removed.push(row);
return false;
} else {
return true;
}
});
this.emit('remove', removed);
}
addRows(rows) {
let rowsToAdd = this.filterRows(rows);
this.sortAndMergeRows(rowsToAdd);
// we emit filter no matter what to trigger
// an update of visible rows
if (rowsToAdd.length > 0) {
this.emit('add', rowsToAdd);
}
}
clearRowsFromTableAndFilter(rows) {
let rowsToAdd = this.filterRows(rows);
// Reset of all rows, need to wipe current rows
this.rows = [];
this.sortAndMergeRows(rowsToAdd);
// We emit filter and update of visible rows
this.emit('filter', rowsToAdd);
}
filterRows(rows) {
if (Object.keys(this.columnFilters).length > 0) {
return rows.filter(this.matchesFilters, this);
}
return rows;
}
sortAndMergeRows(rows) {
const sortedRows = this.sortCollection(rows);
if (this.rows.length === 0) {
this.rows = sortedRows;
return;
}
const firstIncomingRow = sortedRows[0];
const lastIncomingRow = sortedRows[sortedRows.length - 1];
const firstExistingRow = this.rows[0];
const lastExistingRow = this.rows[this.rows.length - 1];
if (this.firstRowInSortOrder(lastIncomingRow, firstExistingRow) === lastIncomingRow) {
this.insertOrUpdateRows(sortedRows, true);
} else if (this.firstRowInSortOrder(lastExistingRow, firstIncomingRow) === lastExistingRow) {
this.insertOrUpdateRows(sortedRows, false);
} else {
this.mergeSortedRows(sortedRows);
}
}
getInPlaceUpdateIndex(row) {
const inPlaceUpdateKey = row.inPlaceUpdateKey;
if (!inPlaceUpdateKey) {
return -1;
}
const foundIndex = this.rows.findIndex(
(existingRow) =>
existingRow.datum[inPlaceUpdateKey] &&
existingRow.datum[inPlaceUpdateKey] === row.datum[inPlaceUpdateKey]
);
return foundIndex;
}
updateRowInPlace(row, index) {
const foundRow = this.rows[index];
foundRow.updateWithDatum(row.datum);
this.rows[index] = foundRow;
}
sortCollection(rows) {
const sortedRows = _.orderBy(
rows,
(row) => row.getParsedValue(this.sortOptions.key),
this.sortOptions.direction
);
return sortedRows;
}
insertOrUpdateRows(rowsToAdd, addToBeginning) {
rowsToAdd.forEach((row) => {
const index = this.getInPlaceUpdateIndex(row);
if (index > -1) {
this.updateRowInPlace(row, index);
} else {
if (addToBeginning) {
this.rows.unshift(row);
} else {
this.rows.push(row);
}
}
});
}
mergeSortedRows(incomingRows) {
const mergedRows = [];
let existingRowIndex = 0;
let incomingRowIndex = 0;
while (existingRowIndex < this.rows.length && incomingRowIndex < incomingRows.length) {
const existingRow = this.rows[existingRowIndex];
const incomingRow = incomingRows[incomingRowIndex];
const inPlaceIndex = this.getInPlaceUpdateIndex(incomingRow);
if (inPlaceIndex > -1) {
this.updateRowInPlace(incomingRow, inPlaceIndex);
incomingRowIndex++;
} else {
if (this.firstRowInSortOrder(existingRow, incomingRow) === existingRow) {
mergedRows.push(existingRow);
existingRowIndex++;
} else {
mergedRows.push(incomingRow);
incomingRowIndex++;
}
}
}
// tail of existing rows is all that is left to merge
if (existingRowIndex < this.rows.length) {
for (existingRowIndex; existingRowIndex < this.rows.length; existingRowIndex++) {
mergedRows.push(this.rows[existingRowIndex]);
}
}
// tail of incoming rows is all that is left to merge
if (incomingRowIndex < incomingRows.length) {
for (incomingRowIndex; incomingRowIndex < incomingRows.length; incomingRowIndex++) {
mergedRows.push(incomingRows[incomingRowIndex]);
}
}
this.rows = mergedRows;
}
firstRowInSortOrder(row1, row2) {
const val1 = this.getValueForSortColumn(row1);
const val2 = this.getValueForSortColumn(row2);
if (this.sortOptions.direction === 'asc') {
return val1 <= val2 ? row1 : row2;
} else {
return val1 >= val2 ? row1 : row2;
}
}
removeRowsByData(data) {
let removed = [];
this.rows = this.rows.filter((row) => {
if (data.includes(row.fullDatum)) {
removed.push(row);
return false;
} else {
return true;
}
});
this.emit('remove', removed);
}
/**
* Sorts the telemetry collection based on the provided sort field
* specifier. Subsequent inserts are sorted to maintain specified sport
* order.
*
* @example
* // First build some mock telemetry for the purpose of an example
* let now = Date.now();
* let telemetry = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(function (value) {
* return {
* // define an object property to demonstrate nested paths
* timestamp: {
* ms: now - value * 1000,
* text:
* },
* value: value
* }
* });
* let collection = new TelemetryCollection();
*
* collection.add(telemetry);
*
* // Sort by telemetry value
* collection.sortBy({
* key: 'value', direction: 'asc'
* });
*
* // Sort by ms since epoch
* collection.sort({
* key: 'timestamp.ms',
* direction: 'asc'
* });
*
* // Sort by 'text' attribute, descending
* collection.sort("timestamp.text");
*
*
* @param {object} sortOptions An object specifying a sort key, and direction.
*/
sortBy(sortOptions) {
if (arguments.length > 0) {
this.sortOptions = sortOptions;
this.rows = _.orderBy(
this.rows,
(row) => row.getParsedValue(sortOptions.key),
sortOptions.direction
);
this.emit('sort');
}
// Return duplicate to avoid direct modification of underlying object
return Object.assign({}, this.sortOptions);
}
setColumnFilter(columnKey, filter) {
filter = filter.trim().toLowerCase();
let wasBlank = this.columnFilters[columnKey] === undefined;
let isSubset = this.isSubsetOfCurrentFilter(columnKey, filter);
if (filter.length === 0) {
delete this.columnFilters[columnKey];
} else {
this.columnFilters[columnKey] = filter;
}
if (isSubset || wasBlank) {
this.rows = this.rows.filter(this.matchesFilters, this);
this.emit('filter');
} else {
this.emit('resetRowsFromAllData');
}
}
setColumnRegexFilter(columnKey, filter) {
filter = filter.trim();
this.columnFilters[columnKey] = new RegExp(filter);
this.emit('resetRowsFromAllData');
}
getColumnMapForObject(objectKeyString) {
let columns = this.configuration.getColumns();
if (columns[objectKeyString]) {
return columns[objectKeyString].reduce((map, column) => {
map[column.getKey()] = column;
return map;
}, {});
}
return {};
}
// /**
// * @private
// */
isSubsetOfCurrentFilter(columnKey, filter) {
if (this.columnFilters[columnKey] instanceof RegExp) {
return false;
} else {
return true;
}
});
return (
this.columnFilters[columnKey] &&
filter.startsWith(this.columnFilters[columnKey]) &&
// startsWith check will otherwise fail when filter cleared
// because anyString.startsWith('') === true
filter !== ''
);
}
this.emit('remove', removed);
}
/**
* @private
*/
matchesFilters(row) {
let doesMatchFilters = true;
Object.keys(this.columnFilters).forEach((key) => {
if (!doesMatchFilters || !this.rowHasColumn(row, key)) {
return false;
}
addRows(rows) {
let rowsToAdd = this.filterRows(rows);
let formattedValue = row.getFormattedValue(key);
if (formattedValue === undefined) {
return false;
}
this.sortAndMergeRows(rowsToAdd);
if (this.columnFilters[key] instanceof RegExp) {
doesMatchFilters = this.columnFilters[key].test(formattedValue);
} else {
doesMatchFilters = formattedValue.toLowerCase().indexOf(this.columnFilters[key]) !== -1;
}
});
return doesMatchFilters;
}
rowHasColumn(row, key) {
return Object.prototype.hasOwnProperty.call(row.columns, key);
}
getRows() {
return this.rows;
}
getRowsLength() {
return this.rows.length;
}
getValueForSortColumn(row) {
return row.getParsedValue(this.sortOptions.key);
}
clear() {
let removedRows = this.rows;
this.rows = [];
this.emit('remove', removedRows);
}
destroy() {
this.removeAllListeners();
// we emit filter no matter what to trigger
// an update of visible rows
if (rowsToAdd.length > 0) {
this.emit('add', rowsToAdd);
}
}
return TableRowCollection;
});
clearRowsFromTableAndFilter(rows) {
let rowsToAdd = this.filterRows(rows);
// Reset of all rows, need to wipe current rows
this.rows = [];
this.sortAndMergeRows(rowsToAdd);
// We emit filter and update of visible rows
this.emit('filter', rowsToAdd);
}
filterRows(rows) {
if (Object.keys(this.columnFilters).length > 0) {
return rows.filter(this.matchesFilters, this);
}
return rows;
}
sortAndMergeRows(rows) {
const sortedRows = this.sortCollection(rows);
if (this.rows.length === 0) {
this.rows = sortedRows;
return;
}
const firstIncomingRow = sortedRows[0];
const lastIncomingRow = sortedRows[sortedRows.length - 1];
const firstExistingRow = this.rows[0];
const lastExistingRow = this.rows[this.rows.length - 1];
if (this.firstRowInSortOrder(lastIncomingRow, firstExistingRow) === lastIncomingRow) {
this.insertOrUpdateRows(sortedRows, true);
} else if (this.firstRowInSortOrder(lastExistingRow, firstIncomingRow) === lastExistingRow) {
this.insertOrUpdateRows(sortedRows, false);
} else {
this.mergeSortedRows(sortedRows);
}
}
getInPlaceUpdateIndex(row) {
const inPlaceUpdateKey = row.inPlaceUpdateKey;
if (!inPlaceUpdateKey) {
return -1;
}
const foundIndex = this.rows.findIndex(
(existingRow) =>
existingRow.datum[inPlaceUpdateKey] &&
existingRow.datum[inPlaceUpdateKey] === row.datum[inPlaceUpdateKey]
);
return foundIndex;
}
updateRowInPlace(row, index) {
const foundRow = this.rows[index];
foundRow.updateWithDatum(row.datum);
this.rows[index] = foundRow;
}
sortCollection(rows) {
const sortedRows = _.orderBy(
rows,
(row) => row.getParsedValue(this.sortOptions.key),
this.sortOptions.direction
);
return sortedRows;
}
insertOrUpdateRows(rowsToAdd, addToBeginning) {
rowsToAdd.forEach((row) => {
const index = this.getInPlaceUpdateIndex(row);
if (index > -1) {
this.updateRowInPlace(row, index);
} else {
if (addToBeginning) {
this.rows.unshift(row);
} else {
this.rows.push(row);
}
}
});
}
mergeSortedRows(incomingRows) {
const mergedRows = [];
let existingRowIndex = 0;
let incomingRowIndex = 0;
while (existingRowIndex < this.rows.length && incomingRowIndex < incomingRows.length) {
const existingRow = this.rows[existingRowIndex];
const incomingRow = incomingRows[incomingRowIndex];
const inPlaceIndex = this.getInPlaceUpdateIndex(incomingRow);
if (inPlaceIndex > -1) {
this.updateRowInPlace(incomingRow, inPlaceIndex);
incomingRowIndex++;
} else {
if (this.firstRowInSortOrder(existingRow, incomingRow) === existingRow) {
mergedRows.push(existingRow);
existingRowIndex++;
} else {
mergedRows.push(incomingRow);
incomingRowIndex++;
}
}
}
// tail of existing rows is all that is left to merge
if (existingRowIndex < this.rows.length) {
for (existingRowIndex; existingRowIndex < this.rows.length; existingRowIndex++) {
mergedRows.push(this.rows[existingRowIndex]);
}
}
// tail of incoming rows is all that is left to merge
if (incomingRowIndex < incomingRows.length) {
for (incomingRowIndex; incomingRowIndex < incomingRows.length; incomingRowIndex++) {
mergedRows.push(incomingRows[incomingRowIndex]);
}
}
this.rows = mergedRows;
}
firstRowInSortOrder(row1, row2) {
const val1 = this.getValueForSortColumn(row1);
const val2 = this.getValueForSortColumn(row2);
if (this.sortOptions.direction === 'asc') {
return val1 <= val2 ? row1 : row2;
} else {
return val1 >= val2 ? row1 : row2;
}
}
removeRowsByData(data) {
let removed = [];
this.rows = this.rows.filter((row) => {
if (data.includes(row.fullDatum)) {
removed.push(row);
return false;
} else {
return true;
}
});
this.emit('remove', removed);
}
/**
* Sorts the telemetry collection based on the provided sort field
* specifier. Subsequent inserts are sorted to maintain specified sport
* order.
*
* @example
* // First build some mock telemetry for the purpose of an example
* let now = Date.now();
* let telemetry = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(function (value) {
* return {
* // define an object property to demonstrate nested paths
* timestamp: {
* ms: now - value * 1000,
* text:
* },
* value: value
* }
* });
* let collection = new TelemetryCollection();
*
* collection.add(telemetry);
*
* // Sort by telemetry value
* collection.sortBy({
* key: 'value', direction: 'asc'
* });
*
* // Sort by ms since epoch
* collection.sort({
* key: 'timestamp.ms',
* direction: 'asc'
* });
*
* // Sort by 'text' attribute, descending
* collection.sort("timestamp.text");
*
*
* @param {object} sortOptions An object specifying a sort key, and direction.
*/
sortBy(sortOptions) {
if (arguments.length > 0) {
this.sortOptions = sortOptions;
this.rows = _.orderBy(
this.rows,
(row) => row.getParsedValue(sortOptions.key),
sortOptions.direction
);
this.emit('sort');
}
// Return duplicate to avoid direct modification of underlying object
return Object.assign({}, this.sortOptions);
}
setColumnFilter(columnKey, filter) {
filter = filter.trim().toLowerCase();
let wasBlank = this.columnFilters[columnKey] === undefined;
let isSubset = this.isSubsetOfCurrentFilter(columnKey, filter);
if (filter.length === 0) {
delete this.columnFilters[columnKey];
} else {
this.columnFilters[columnKey] = filter;
}
if (isSubset || wasBlank) {
this.rows = this.rows.filter(this.matchesFilters, this);
this.emit('filter');
} else {
this.emit('resetRowsFromAllData');
}
}
setColumnRegexFilter(columnKey, filter) {
filter = filter.trim();
this.columnFilters[columnKey] = new RegExp(filter);
this.emit('resetRowsFromAllData');
}
getColumnMapForObject(objectKeyString) {
let columns = this.configuration.getColumns();
if (columns[objectKeyString]) {
return columns[objectKeyString].reduce((map, column) => {
map[column.getKey()] = column;
return map;
}, {});
}
return {};
}
// /**
// * @private
// */
isSubsetOfCurrentFilter(columnKey, filter) {
if (this.columnFilters[columnKey] instanceof RegExp) {
return false;
}
return (
this.columnFilters[columnKey] &&
filter.startsWith(this.columnFilters[columnKey]) &&
// startsWith check will otherwise fail when filter cleared
// because anyString.startsWith('') === true
filter !== ''
);
}
/**
* @private
*/
matchesFilters(row) {
let doesMatchFilters = true;
Object.keys(this.columnFilters).forEach((key) => {
if (!doesMatchFilters || !this.rowHasColumn(row, key)) {
return false;
}
let formattedValue = row.getFormattedValue(key);
if (formattedValue === undefined) {
return false;
}
if (this.columnFilters[key] instanceof RegExp) {
doesMatchFilters = this.columnFilters[key].test(formattedValue);
} else {
doesMatchFilters = formattedValue.toLowerCase().indexOf(this.columnFilters[key]) !== -1;
}
});
return doesMatchFilters;
}
rowHasColumn(row, key) {
return Object.prototype.hasOwnProperty.call(row.columns, key);
}
getRows() {
return this.rows;
}
getRowsLength() {
return this.rows.length;
}
getValueForSortColumn(row) {
return row.getParsedValue(this.sortOptions.key);
}
clear() {
let removedRows = this.rows;
this.rows = [];
this.emit('remove', removedRows);
}
destroy() {
this.removeAllListeners();
}
}

View File

@ -20,17 +20,17 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
/**
* This time system supports UTC dates.
* @implements TimeSystem
* @constructor
*/
class UTCTimeSystem {
/**
* This time system supports UTC dates.
* @implements TimeSystem
* @constructor
* Metadata used to identify the time system in
* the UI
*/
function UTCTimeSystem() {
/**
* Metadata used to identify the time system in
* the UI
*/
constructor() {
this.key = 'utc';
this.name = 'UTC';
this.cssClass = 'icon-clock';
@ -38,6 +38,6 @@ define([], function () {
this.durationFormat = 'duration';
this.isUTCBased = true;
}
}
return UTCTimeSystem;
});
export default UTCTimeSystem;

View File

@ -20,102 +20,98 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
/**
* A ToolbarRegistry maintains the definitions for toolbars.
*
* @interface ToolbarRegistry
* @memberof module:openmct
*/
function ToolbarRegistry() {
this.providers = {};
/**
* A ToolbarRegistry maintains the definitions for toolbars.
*
* @interface ToolbarRegistry
* @memberof module:openmct
*/
export default function ToolbarRegistry() {
this.providers = {};
}
/**
* Gets toolbar controls from providers which can provide a toolbar for this selection.
*
* @param {object} selection the selection object
* @returns {Object[]} an array of objects defining controls for the toolbar
* @private for platform-internal use
*/
ToolbarRegistry.prototype.get = function (selection) {
const providers = this.getAllProviders().filter(function (provider) {
return provider.forSelection(selection);
});
const structure = [];
providers.forEach((provider) => {
provider.toolbar(selection).forEach((item) => structure.push(item));
});
return structure;
};
/**
* @private
*/
ToolbarRegistry.prototype.getAllProviders = function () {
return Object.values(this.providers);
};
/**
* @private
*/
ToolbarRegistry.prototype.getByProviderKey = function (key) {
return this.providers[key];
};
/**
* Registers a new type of toolbar.
*
* @param {module:openmct.ToolbarRegistry} provider the provider for this toolbar
* @method addProvider
* @memberof module:openmct.ToolbarRegistry#
*/
ToolbarRegistry.prototype.addProvider = function (provider) {
const key = provider.key;
if (key === undefined) {
throw "Toolbar providers must have a unique 'key' property defined.";
}
/**
* Gets toolbar controls from providers which can provide a toolbar for this selection.
*
* @param {object} selection the selection object
* @returns {Object[]} an array of objects defining controls for the toolbar
* @private for platform-internal use
*/
ToolbarRegistry.prototype.get = function (selection) {
const providers = this.getAllProviders().filter(function (provider) {
return provider.forSelection(selection);
});
if (this.providers[key] !== undefined) {
console.warn("Provider already defined for key '%s'. Provider keys must be unique.", key);
}
const structure = [];
this.providers[key] = provider;
};
providers.forEach((provider) => {
provider.toolbar(selection).forEach((item) => structure.push(item));
});
/**
* Exposes types of toolbars in Open MCT.
*
* @interface ToolbarProvider
* @property {string} key a unique identifier for this toolbar
* @property {string} name the human-readable name of this toolbar
* @property {string} [description] a longer-form description (typically
* a single sentence or short paragraph) of this kind of toolbar
* @memberof module:openmct
*/
return structure;
};
/**
* Checks if this provider can supply toolbar for a selection.
*
* @method forSelection
* @memberof module:openmct.ToolbarProvider#
* @param {module:openmct.selection} selection
* @returns {boolean} 'true' if the toolbar applies to the provided selection,
* otherwise 'false'.
*/
/**
* @private
*/
ToolbarRegistry.prototype.getAllProviders = function () {
return Object.values(this.providers);
};
/**
* @private
*/
ToolbarRegistry.prototype.getByProviderKey = function (key) {
return this.providers[key];
};
/**
* Registers a new type of toolbar.
*
* @param {module:openmct.ToolbarRegistry} provider the provider for this toolbar
* @method addProvider
* @memberof module:openmct.ToolbarRegistry#
*/
ToolbarRegistry.prototype.addProvider = function (provider) {
const key = provider.key;
if (key === undefined) {
throw "Toolbar providers must have a unique 'key' property defined.";
}
if (this.providers[key] !== undefined) {
console.warn("Provider already defined for key '%s'. Provider keys must be unique.", key);
}
this.providers[key] = provider;
};
/**
* Exposes types of toolbars in Open MCT.
*
* @interface ToolbarProvider
* @property {string} key a unique identifier for this toolbar
* @property {string} name the human-readable name of this toolbar
* @property {string} [description] a longer-form description (typically
* a single sentence or short paragraph) of this kind of toolbar
* @memberof module:openmct
*/
/**
* Checks if this provider can supply toolbar for a selection.
*
* @method forSelection
* @memberof module:openmct.ToolbarProvider#
* @param {module:openmct.selection} selection
* @returns {boolean} 'true' if the toolbar applies to the provided selection,
* otherwise 'false'.
*/
/**
* Provides controls that comprise a toolbar.
*
* @method toolbar
* @memberof module:openmct.ToolbarProvider#
* @param {object} selection the selection object
* @returns {Object[]} an array of objects defining controls for the toolbar.
*/
return ToolbarRegistry;
});
/**
* Provides controls that comprise a toolbar.
*
* @method toolbar
* @memberof module:openmct.ToolbarProvider#
* @param {object} selection the selection object
* @returns {Object[]} an array of objects defining controls for the toolbar.
*/

View File

@ -20,255 +20,253 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(['EventEmitter'], function (EventEmitter) {
const DEFAULT_VIEW_PRIORITY = 100;
import EventEmitter from 'EventEmitter';
/**
* A ViewRegistry maintains the definitions for different kinds of views
* that may occur in different places in the user interface.
* @interface ViewRegistry
* @memberof module:openmct
*/
function ViewRegistry() {
EventEmitter.apply(this);
this.providers = {};
const DEFAULT_VIEW_PRIORITY = 100;
/**
* A ViewRegistry maintains the definitions for different kinds of views
* that may occur in different places in the user interface.
* @interface ViewRegistry
* @memberof module:openmct
*/
export default function ViewRegistry() {
EventEmitter.apply(this);
this.providers = {};
}
ViewRegistry.prototype = Object.create(EventEmitter.prototype);
/**
* @private for platform-internal use
* @param {*} item the object to be viewed
* @param {array} objectPath - The current contextual object path of the view object
* eg current domainObject is located under MyItems which is under Root
* @returns {module:openmct.ViewProvider[]} any providers
* which can provide views of this object
*/
ViewRegistry.prototype.get = function (item, objectPath) {
if (objectPath === undefined) {
throw 'objectPath must be provided to get applicable views for an object';
}
ViewRegistry.prototype = Object.create(EventEmitter.prototype);
function byPriority(providerA, providerB) {
let priorityA = providerA.priority ? providerA.priority(item) : DEFAULT_VIEW_PRIORITY;
let priorityB = providerB.priority ? providerB.priority(item) : DEFAULT_VIEW_PRIORITY;
/**
* @private for platform-internal use
* @param {*} item the object to be viewed
* @param {array} objectPath - The current contextual object path of the view object
* eg current domainObject is located under MyItems which is under Root
* @returns {module:openmct.ViewProvider[]} any providers
* which can provide views of this object
*/
ViewRegistry.prototype.get = function (item, objectPath) {
if (objectPath === undefined) {
throw 'objectPath must be provided to get applicable views for an object';
}
return priorityB - priorityA;
}
function byPriority(providerA, providerB) {
let priorityA = providerA.priority ? providerA.priority(item) : DEFAULT_VIEW_PRIORITY;
let priorityB = providerB.priority ? providerB.priority(item) : DEFAULT_VIEW_PRIORITY;
return this.getAllProviders()
.filter(function (provider) {
return provider.canView(item, objectPath);
})
.sort(byPriority);
};
return priorityB - priorityA;
}
/**
* @private
*/
ViewRegistry.prototype.getAllProviders = function () {
return Object.values(this.providers);
};
return this.getAllProviders()
.filter(function (provider) {
return provider.canView(item, objectPath);
})
.sort(byPriority);
};
/**
* Register a new type of view.
*
* @param {module:openmct.ViewProvider} provider the provider for this view
* @method addProvider
* @memberof module:openmct.ViewRegistry#
*/
ViewRegistry.prototype.addProvider = function (provider) {
const key = provider.key;
if (key === undefined) {
throw "View providers must have a unique 'key' property defined";
}
/**
* @private
*/
ViewRegistry.prototype.getAllProviders = function () {
return Object.values(this.providers);
};
if (this.providers[key] !== undefined) {
console.warn("Provider already defined for key '%s'. Provider keys must be unique.", key);
}
/**
* Register a new type of view.
*
* @param {module:openmct.ViewProvider} provider the provider for this view
* @method addProvider
* @memberof module:openmct.ViewRegistry#
*/
ViewRegistry.prototype.addProvider = function (provider) {
const key = provider.key;
if (key === undefined) {
throw "View providers must have a unique 'key' property defined";
}
if (this.providers[key] !== undefined) {
console.warn("Provider already defined for key '%s'. Provider keys must be unique.", key);
}
const wrappedView = provider.view.bind(provider);
provider.view = (domainObject, objectPath) => {
const viewObject = wrappedView(domainObject, objectPath);
const wrappedShow = viewObject.show.bind(viewObject);
viewObject.key = key; // provide access to provider key on view object
viewObject.show = (element, isEditing, viewOptions) => {
viewObject.parentElement = element.parentElement;
wrappedShow(element, isEditing, viewOptions);
};
return viewObject;
const wrappedView = provider.view.bind(provider);
provider.view = (domainObject, objectPath) => {
const viewObject = wrappedView(domainObject, objectPath);
const wrappedShow = viewObject.show.bind(viewObject);
viewObject.key = key; // provide access to provider key on view object
viewObject.show = (element, isEditing, viewOptions) => {
viewObject.parentElement = element.parentElement;
wrappedShow(element, isEditing, viewOptions);
};
this.providers[key] = provider;
return viewObject;
};
/**
* @private
*/
ViewRegistry.prototype.getByProviderKey = function (key) {
return this.providers[key];
};
this.providers[key] = provider;
};
/**
* Used internally to support seamless usage of new views with old
* views.
* @private
*/
ViewRegistry.prototype.getByVPID = function (vpid) {
return this.providers.filter(function (p) {
return p.vpid === vpid;
})[0];
};
/**
* @private
*/
ViewRegistry.prototype.getByProviderKey = function (key) {
return this.providers[key];
};
/**
* A View is used to provide displayable content, and to react to
* associated life cycle events.
*
* @name View
* @interface
* @memberof module:openmct
*/
/**
* Used internally to support seamless usage of new views with old
* views.
* @private
*/
ViewRegistry.prototype.getByVPID = function (vpid) {
return this.providers.filter(function (p) {
return p.vpid === vpid;
})[0];
};
/**
* Populate the supplied DOM element with the contents of this view.
*
* View implementations should use this method to attach any
* listeners or acquire other resources that are necessary to keep
* the contents of this view up-to-date.
*
* @param {HTMLElement} container the DOM element to populate
* @method show
* @memberof module:openmct.View#
*/
/**
* A View is used to provide displayable content, and to react to
* associated life cycle events.
*
* @name View
* @interface
* @memberof module:openmct
*/
/**
* Indicates whether or not the application is in edit mode. This supports
* views that have distinct visual and behavioral elements when the
* navigated object is being edited.
*
* For cases where a completely separate view is desired for editing purposes,
* see {@link openmct.ViewProvider#edit}
*
* @param {boolean} isEditing
* @method show
* @memberof module:openmct.View#
*/
/**
* Populate the supplied DOM element with the contents of this view.
*
* View implementations should use this method to attach any
* listeners or acquire other resources that are necessary to keep
* the contents of this view up-to-date.
*
* @param {HTMLElement} container the DOM element to populate
* @method show
* @memberof module:openmct.View#
*/
/**
* Release any resources associated with this view.
*
* View implementations should use this method to detach any
* listeners or release other resources that are no longer necessary
* once a view is no longer used.
*
* @method destroy
* @memberof module:openmct.View#
*/
/**
* Indicates whether or not the application is in edit mode. This supports
* views that have distinct visual and behavioral elements when the
* navigated object is being edited.
*
* For cases where a completely separate view is desired for editing purposes,
* see {@link openmct.ViewProvider#edit}
*
* @param {boolean} isEditing
* @method show
* @memberof module:openmct.View#
*/
/**
* Returns the selection context.
*
* View implementations should use this method to customize
* the selection context.
*
* @method getSelectionContext
* @memberof module:openmct.View#
*/
/**
* Release any resources associated with this view.
*
* View implementations should use this method to detach any
* listeners or release other resources that are no longer necessary
* once a view is no longer used.
*
* @method destroy
* @memberof module:openmct.View#
*/
/**
* Exposes types of views in Open MCT.
*
* @interface ViewProvider
* @property {string} key a unique identifier for this view
* @property {string} name the human-readable name of this view
* @property {string} [description] a longer-form description (typically
* a single sentence or short paragraph) of this kind of view
* @property {string} [cssClass] the CSS class to apply to labels for this
* view (to add icons, for instance)
* @memberof module:openmct
*/
/**
* Returns the selection context.
*
* View implementations should use this method to customize
* the selection context.
*
* @method getSelectionContext
* @memberof module:openmct.View#
*/
/**
* Check if this provider can supply views for a domain object.
*
* When called by Open MCT, this may include additional arguments
* which are on the path to the object to be viewed; for instance,
* when viewing "A Folder" within "My Items", this method will be
* invoked with "A Folder" (as a domain object) as the first argument
*
* @method canView
* @memberof module:openmct.ViewProvider#
* @param {module:openmct.DomainObject} domainObject the domain object
* to be viewed
* @param {array} objectPath - The current contextual object path of the view object
* eg current domainObject is located under MyItems which is under Root
* @returns {boolean} 'true' if the view applies to the provided object,
* otherwise 'false'.
*/
/**
* Exposes types of views in Open MCT.
*
* @interface ViewProvider
* @property {string} key a unique identifier for this view
* @property {string} name the human-readable name of this view
* @property {string} [description] a longer-form description (typically
* a single sentence or short paragraph) of this kind of view
* @property {string} [cssClass] the CSS class to apply to labels for this
* view (to add icons, for instance)
* @memberof module:openmct
*/
/**
* An optional function that defines whether or not this view can be used to edit a given object.
* If not provided, will default to `false` and the view will not support editing. To support editing,
* return true from this function and then -
* * Return a {@link openmct.View} from the `view` function, using the `onEditModeChange` callback to
* add and remove editing elements from the view
* OR
* * Return a {@link openmct.View} from the `view` function defining a read-only view.
* AND
* * Define an {@link openmct.ViewProvider#Edit} function on the view provider that returns an
* editing-specific view.
*
* @method canEdit
* @memberof module:openmct.ViewProvider#
* @param {module:openmct.DomainObject} domainObject the domain object
* to be edited
* @param {array} objectPath - The current contextual object path of the view object
* eg current domainObject is located under MyItems which is under Root
* @returns {boolean} 'true' if the view can be used to edit the provided object,
* otherwise 'false'.
*/
/**
* Check if this provider can supply views for a domain object.
*
* When called by Open MCT, this may include additional arguments
* which are on the path to the object to be viewed; for instance,
* when viewing "A Folder" within "My Items", this method will be
* invoked with "A Folder" (as a domain object) as the first argument
*
* @method canView
* @memberof module:openmct.ViewProvider#
* @param {module:openmct.DomainObject} domainObject the domain object
* to be viewed
* @param {array} objectPath - The current contextual object path of the view object
* eg current domainObject is located under MyItems which is under Root
* @returns {boolean} 'true' if the view applies to the provided object,
* otherwise 'false'.
*/
/**
* Optional method determining the priority of a given view. If this
* function is not defined on a view provider, then a default priority
* of 100 will be applicable for all objects supported by this view.
*
* @method priority
* @memberof module:openmct.ViewProvider#
* @param {module:openmct.DomainObject} domainObject the domain object
* to be viewed
* @returns {number} The priority of the view. If multiple views could apply
* to an object, the view that returns the lowest number will be
* the default view.
*/
/**
* An optional function that defines whether or not this view can be used to edit a given object.
* If not provided, will default to `false` and the view will not support editing. To support editing,
* return true from this function and then -
* * Return a {@link openmct.View} from the `view` function, using the `onEditModeChange` callback to
* add and remove editing elements from the view
* OR
* * Return a {@link openmct.View} from the `view` function defining a read-only view.
* AND
* * Define an {@link openmct.ViewProvider#Edit} function on the view provider that returns an
* editing-specific view.
*
* @method canEdit
* @memberof module:openmct.ViewProvider#
* @param {module:openmct.DomainObject} domainObject the domain object
* to be edited
* @param {array} objectPath - The current contextual object path of the view object
* eg current domainObject is located under MyItems which is under Root
* @returns {boolean} 'true' if the view can be used to edit the provided object,
* otherwise 'false'.
*/
/**
* Provide a view of this object.
*
* When called by Open MCT, the following arguments will be passed to it:
* @param {object} domainObject - the domainObject that the view is provided for
* @param {array} objectPath - The current contextual object path of the view object
* eg current domainObject is located under MyItems which is under Root
*
* @method view
* @memberof module:openmct.ViewProvider#
* @param {*} object the object to be viewed
* @returns {module:openmct.View} a view of this domain object
*/
/**
* Optional method determining the priority of a given view. If this
* function is not defined on a view provider, then a default priority
* of 100 will be applicable for all objects supported by this view.
*
* @method priority
* @memberof module:openmct.ViewProvider#
* @param {module:openmct.DomainObject} domainObject the domain object
* to be viewed
* @returns {number} The priority of the view. If multiple views could apply
* to an object, the view that returns the lowest number will be
* the default view.
*/
/**
* Provide an edit-mode specific view of this object.
*
* If optionally specified, this function will be called when the application
* enters edit mode. This will cause the active non-edit mode view and its
* dom element to be destroyed.
*
* @method edit
* @memberof module:openmct.ViewProvider#
* @param {*} object the object to be edit
* @returns {module:openmct.View} an editable view of this domain object
*/
/**
* Provide a view of this object.
*
* When called by Open MCT, the following arguments will be passed to it:
* @param {object} domainObject - the domainObject that the view is provided for
* @param {array} objectPath - The current contextual object path of the view object
* eg current domainObject is located under MyItems which is under Root
*
* @method view
* @memberof module:openmct.ViewProvider#
* @param {*} object the object to be viewed
* @returns {module:openmct.View} a view of this domain object
*/
return ViewRegistry;
});
/**
* Provide an edit-mode specific view of this object.
*
* If optionally specified, this function will be called when the application
* enters edit mode. This will cause the active non-edit mode view and its
* dom element to be destroyed.
*
* @method edit
* @memberof module:openmct.ViewProvider#
* @param {*} object the object to be edit
* @returns {module:openmct.View} an editable view of this domain object
*/

Some files were not shown because too many files have changed in this diff Show More