Compare commits

...

19 Commits

Author SHA1 Message Date
fe9f740611 large plot value testing 2023-09-19 13:02:22 -07:00
798eb4444d reimplement fix for smushed keys 2023-09-12 13:53:21 -07:00
59ae7cddc5 Merge branch 'omm-release/5.1' of https://github.com/nasa/openmct into omm-release/5.1
mergin
2023-07-07 11:14:50 -07:00
321c7a3af5 suppressing abort errors as they are expected 2023-07-07 11:13:43 -07:00
609cf72bd1 chicken needs to come first 2023-07-06 14:39:26 -07:00
a447b0ada8 removing eval-source-map, dont debuggin 2023-06-21 16:32:57 -07:00
5788f4cc69 temp source maps for debugin 2023-06-21 14:11:23 -07:00
f94b4e53c7 adding back in abort on item close in tree 2023-06-21 13:31:12 -07:00
faf71f1e67 omm-cherry-pick(#6602) : [ExportAsJson] Multiple Aliases in Export an… (#6662)
omm-cherry-pick(#6602) : [ExportAsJson] Multiple Aliases in Export and Conditional Styles Fixes (#6602)

Fixes issues that prevent import and export from being completed successfully. Specifically:

* if multiple aliases are detected, the first is created as a new object and and added to it's parent's composition, any subsequent aliases of the same object will not be recreated, but the originally created one will be added to the current parent's composition, creating an alias.

* Also, there are cases were conditionSetIdentifiers are stored in an object keyed by an item id in the configuration.objectstyles object, this fix will handle these as well.

* Replaces an errant `return` statement with a `continue` statement to prevent early exit from a recursive function.

---------

Co-authored-by: Andrew Henry <akhenry@gmail.com>
2023-05-15 13:12:37 -07:00
23310f85ae cherry pick retain styles on url change 2023-05-02 13:38:13 -07:00
d80819634b revert opening in new tab 2023-04-12 15:01:53 -07:00
483b62c152 Only decrement and save if there is composition but no child object reference 2023-04-07 14:08:58 -07:00
1254279635 Fix ExportAsJSONAction to not lose layout configurations on linked objects (#6562) 2023-04-05 23:05:27 -07:00
c768a71656 free findSubscriptionProvider 2023-03-31 15:50:38 -07:00
678a92bd29 Revert "enable source maps in prod"
This reverts commit 34b488944a.
2023-03-31 14:43:02 -07:00
34b488944a enable source maps in prod 2023-03-31 14:42:39 -07:00
4d1dd2f51d add temporary debugging 2023-03-31 12:23:23 -07:00
080f7b8f4b pass openmct and object key to function
re-enable historical row action
2023-03-29 17:09:35 -07:00
483f2feac8 allow contextual domain objects to row actions 2023-03-29 16:58:51 -07:00
23 changed files with 1256 additions and 1136 deletions

View File

@ -1,150 +1,138 @@
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
}
}
]
define(['lodash'], function (_) {
var METADATA_BY_TYPE = {
generator: {
values: [
{
key: 'name',
name: 'Name',
format: 'string'
},
'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: '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
}
}
};
function GeneratorMetadataProvider() {
]
},
'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
}
}
]
}
};
GeneratorMetadataProvider.prototype.supportsMetadata = function (domainObject) {
return Object.prototype.hasOwnProperty.call(METADATA_BY_TYPE, domainObject.type);
};
function GeneratorMetadataProvider() {}
GeneratorMetadataProvider.prototype.getMetadata = function (domainObject) {
return Object.assign(
{},
domainObject.telemetry,
METADATA_BY_TYPE[domainObject.type]
);
};
GeneratorMetadataProvider.prototype.supportsMetadata = function (domainObject) {
return Object.prototype.hasOwnProperty.call(METADATA_BY_TYPE, domainObject.type);
};
return GeneratorMetadataProvider;
GeneratorMetadataProvider.prototype.getMetadata = function (domainObject) {
return Object.assign({}, domainObject.telemetry, METADATA_BY_TYPE[domainObject.type]);
};
return GeneratorMetadataProvider;
});

View File

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

View File

@ -20,155 +20,147 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
], function (
) {
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"
},
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';
define([], function () {
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'
},
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'
}
};
SinewaveLimitProvider.prototype.getLimitEvaluator = function (domainObject) {
return {
evaluate: function (datum, valueMetadata) {
var range = valueMetadata && valueMetadata.key;
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] > RED[range]) {
return LIMITS.rh;
}
if (datum[range] < -YELLOW[range]) {
return LIMITS.yl;
}
}
};
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
},
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
}
}
});
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
}
};
},
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;
return SinewaveLimitProvider;
});

View File

@ -23,135 +23,135 @@
import EventEmitter from 'EventEmitter';
export default class SinewaveLimitProvider extends EventEmitter {
#openmct;
#observingStaleness;
#watchingTheClock;
#isRealTime;
#openmct;
#observingStaleness;
#watchingTheClock;
#isRealTime;
constructor(openmct) {
super();
constructor(openmct) {
super();
this.#openmct = openmct;
this.#observingStaleness = {};
this.#watchingTheClock = false;
this.#isRealTime = undefined;
this.#openmct = openmct;
this.#observingStaleness = {};
this.#watchingTheClock = false;
this.#isRealTime = undefined;
}
supportsStaleness(domainObject) {
return domainObject.type === 'generator';
}
isStale(domainObject, options) {
if (!this.#providingStaleness(domainObject)) {
return;
}
supportsStaleness(domainObject) {
return domainObject.type === 'generator';
const id = this.#getObjectKeyString(domainObject);
if (!this.#observerExists(id)) {
this.#createObserver(id);
}
isStale(domainObject, options) {
if (!this.#providingStaleness(domainObject)) {
return;
}
return Promise.resolve({
isStale: this.#observingStaleness[id].isStale,
utc: Date.now()
});
}
const id = this.#getObjectKeyString(domainObject);
subscribeToStaleness(domainObject, callback) {
const id = this.#getObjectKeyString(domainObject);
if (!this.#observerExists(id)) {
this.#createObserver(id);
}
return Promise.resolve({
isStale: this.#observingStaleness[id].isStale,
utc: Date.now()
});
if (this.#isRealTime === undefined) {
this.#updateRealTime(this.#openmct.time.getMode());
}
subscribeToStaleness(domainObject, callback) {
const id = this.#getObjectKeyString(domainObject);
this.#handleClockUpdate();
if (this.#isRealTime === undefined) {
this.#updateRealTime(this.#openmct.time.clock());
}
this.#handleClockUpdate();
if (this.#observerExists(id)) {
this.#addCallbackToObserver(id, callback);
} else {
this.#createObserver(id, callback);
}
const intervalId = setInterval(() => {
if (this.#providingStaleness(domainObject)) {
this.#updateStaleness(id, !this.#observingStaleness[id].isStale);
}
}, 10000);
return () => {
clearInterval(intervalId);
this.#updateStaleness(id, false);
this.#handleClockUpdate();
this.#destroyObserver(id);
};
if (this.#observerExists(id)) {
this.#addCallbackToObserver(id, callback);
} else {
this.#createObserver(id, callback);
}
#handleClockUpdate() {
let observers = Object.values(this.#observingStaleness).length > 0;
const intervalId = setInterval(() => {
if (this.#providingStaleness(domainObject)) {
this.#updateStaleness(id, !this.#observingStaleness[id].isStale);
}
}, 10000);
if (observers && !this.#watchingTheClock) {
this.#watchingTheClock = true;
this.#openmct.time.on('clock', this.#updateRealTime, this);
} else if (!observers && this.#watchingTheClock) {
this.#watchingTheClock = false;
this.#openmct.time.off('clock', this.#updateRealTime, this);
}
return () => {
clearInterval(intervalId);
this.#updateStaleness(id, false);
this.#handleClockUpdate();
this.#destroyObserver(id);
};
}
#handleClockUpdate() {
let observers = Object.values(this.#observingStaleness).length > 0;
if (observers && !this.#watchingTheClock) {
this.#watchingTheClock = true;
this.#openmct.time.on('modeChanged', this.#updateRealTime, this);
} else if (!observers && this.#watchingTheClock) {
this.#watchingTheClock = false;
this.#openmct.time.off('modeChanged', this.#updateRealTime, this);
}
}
#updateRealTime(clock) {
this.#isRealTime = clock !== undefined;
#updateRealTime(mode) {
this.#isRealTime = mode !== 'fixed';
if (!this.#isRealTime) {
Object.keys(this.#observingStaleness).forEach((id) => {
this.#updateStaleness(id, false);
});
}
if (!this.#isRealTime) {
Object.keys(this.#observingStaleness).forEach((id) => {
this.#updateStaleness(id, false);
});
}
}
#updateStaleness(id, isStale) {
this.#observingStaleness[id].isStale = isStale;
this.#observingStaleness[id].utc = Date.now();
this.#observingStaleness[id].callback({
isStale: this.#observingStaleness[id].isStale,
utc: this.#observingStaleness[id].utc
});
this.emit('stalenessEvent', {
id,
isStale: this.#observingStaleness[id].isStale
});
#updateStaleness(id, isStale) {
this.#observingStaleness[id].isStale = isStale;
this.#observingStaleness[id].utc = Date.now();
this.#observingStaleness[id].callback({
isStale: this.#observingStaleness[id].isStale,
utc: this.#observingStaleness[id].utc
});
this.emit('stalenessEvent', {
id,
isStale: this.#observingStaleness[id].isStale
});
}
#createObserver(id, callback) {
this.#observingStaleness[id] = {
isStale: false,
utc: Date.now()
};
if (typeof callback === 'function') {
this.#addCallbackToObserver(id, callback);
}
}
#createObserver(id, callback) {
this.#observingStaleness[id] = {
isStale: false,
utc: Date.now()
};
if (typeof callback === 'function') {
this.#addCallbackToObserver(id, callback);
}
#destroyObserver(id) {
if (this.#observingStaleness[id]) {
delete this.#observingStaleness[id];
}
}
#destroyObserver(id) {
if (this.#observingStaleness[id]) {
delete this.#observingStaleness[id];
}
}
#providingStaleness(domainObject) {
return domainObject.telemetry?.staleness === true && this.#isRealTime;
}
#providingStaleness(domainObject) {
return domainObject.telemetry?.staleness === true && this.#isRealTime;
}
#getObjectKeyString(object) {
return this.#openmct.objects.makeKeyString(object.identifier);
}
#getObjectKeyString(object) {
return this.#openmct.objects.makeKeyString(object.identifier);
}
#addCallbackToObserver(id, callback) {
this.#observingStaleness[id].callback = callback;
}
#addCallbackToObserver(id, callback) {
this.#observingStaleness[id].callback = callback;
}
#observerExists(id) {
return this.#observingStaleness?.[id];
}
#observerExists(id) {
return this.#observingStaleness?.[id];
}
}

View File

@ -20,64 +20,56 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
define([], function () {
function StateGeneratorProvider() {}
], function (
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';
};
function StateGeneratorProvider() {
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;
}
function pointForTimestamp(timestamp, duration, name) {
return {
name: name,
utc: Math.floor(timestamp / duration) * duration,
value: Math.floor(timestamp / duration) % 2
};
var data = [];
while (start <= end && data.length < 5000) {
data.push(pointForTimestamp(start, duration, domainObject.name));
start += duration;
}
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;
}
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);
};
return StateGeneratorProvider;
});

View File

@ -20,93 +20,88 @@
* 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 = {};
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 = {};
this.watchStaleness();
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);
}
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));
}
});
delete self.callbacks[messageId];
return function () {
this.dispatch('unsubscribe', {
id: messageId
});
delete this.callbacks[messageId];
}.bind(this);
};
}
messageId = this.dispatch('request', request, callback.bind(this));
return promise;
};
WorkerInterface.prototype.subscribe = function (request, cb) {
const id = request.id;
const messageId = this.dispatch('subscribe', request, (message) => {
if (!this.staleTelemetryIds[id]) {
cb(message.data);
}
});
return function () {
this.dispatch('unsubscribe', {
id: messageId
});
delete this.callbacks[messageId];
}.bind(this);
};
return WorkerInterface;
return WorkerInterface;
});

View File

@ -21,204 +21,254 @@
*****************************************************************************/
(function () {
var FIFTEEN_MINUTES = 15 * 60 * 1000;
var FIFTEEN_MINUTES = 15 * 60 * 1000;
var handlers = {
subscribe: onSubscribe,
unsubscribe: onUnsubscribe,
request: onRequest
};
var handlers = {
subscribe: onSubscribe,
unsubscribe: onUnsubscribe,
request: onRequest
};
var subscriptions = {};
var subscriptions = {};
function workSubscriptions(timestamp) {
var now = Date.now();
var nextWork = Math.min.apply(Math, Object.values(subscriptions).map(function (subscription) {
return subscription(now);
}));
var wait = nextWork - now;
if (wait < 0) {
wait = 0;
}
if (Number.isFinite(wait)) {
setTimeout(workSubscriptions, wait);
}
function workSubscriptions(timestamp) {
var now = Date.now();
var nextWork = Math.min.apply(
Math,
Object.values(subscriptions).map(function (subscription) {
return subscription(now);
})
);
var wait = nextWork - now;
if (wait < 0) {
wait = 0;
}
function onSubscribe(message) {
var data = message.data;
// Keep
var start = Date.now();
var step = 1000 / data.dataRateInHz;
var nextStep = start - (start % step) + step;
let work;
if (data.spectra) {
work = function (now) {
while (nextStep < now) {
const messageCopy = Object.create(message);
message.data.start = nextStep - (60 * 1000);
message.data.end = nextStep;
onRequest(messageCopy);
nextStep += step;
}
return nextStep;
};
} else {
work = function (now) {
while (nextStep < now) {
self.postMessage({
id: message.id,
data: {
name: data.name,
utc: nextStep,
yesterday: nextStep - 60 * 60 * 24 * 1000,
sin: sin(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness, data.infinityValues),
wavelengths: wavelengths(),
intensities: intensities(),
cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness, data.infinityValues)
}
});
nextStep += step;
}
return nextStep;
};
}
subscriptions[message.id] = work;
workSubscriptions();
if (Number.isFinite(wait)) {
setTimeout(workSubscriptions, wait);
}
}
function onUnsubscribe(message) {
delete subscriptions[message.data.id];
}
function onSubscribe(message) {
var data = message.data;
function onRequest(message) {
var request = message.data;
if (request.end === undefined) {
request.end = Date.now();
// Keep
var start = Date.now();
var step = 1000 / data.dataRateInHz;
var nextStep = start - (start % step) + step;
let work;
if (data.spectra) {
work = function (now) {
while (nextStep < now) {
const messageCopy = Object.create(message);
message.data.start = nextStep - 60 * 1000;
message.data.end = nextStep;
onRequest(messageCopy);
nextStep += step;
}
if (request.start === undefined) {
request.start = request.end - FIFTEEN_MINUTES;
}
var now = Date.now();
var start = request.start;
var end = request.end > now ? now : request.end;
var amplitude = request.amplitude;
var period = request.period;
var offset = request.offset;
var dataRateInHz = request.dataRateInHz;
var phase = request.phase;
var randomness = request.randomness;
var loadDelay = Math.max(request.loadDelay, 0);
var infinityValues = request.infinityValues;
var step = 1000 / dataRateInHz;
var nextStep = start - (start % step) + step;
var data = [];
for (; nextStep < end && data.length < 5000; nextStep += step) {
data.push({
utc: nextStep,
yesterday: nextStep - 60 * 60 * 24 * 1000,
sin: sin(nextStep, period, amplitude, offset, phase, randomness, infinityValues),
wavelengths: wavelengths(),
intensities: intensities(),
cos: cos(nextStep, period, amplitude, offset, phase, randomness, infinityValues)
});
}
if (loadDelay === 0) {
postOnRequest(message, request, data);
} else {
setTimeout(() => postOnRequest(message, request, data), loadDelay);
}
}
function postOnRequest(message, request, data) {
self.postMessage({
return nextStep;
};
} else {
work = function (now) {
while (nextStep < now) {
self.postMessage({
id: message.id,
data: request.spectra ? {
wavelength: data.map((item) => {
return item.wavelength;
}),
cos: data.map((item) => {
return item.cos;
})
} : data
});
data: {
name: data.name,
utc: nextStep,
yesterday: nextStep - 60 * 60 * 24 * 1000,
sin: sin(
nextStep,
data.period,
data.amplitude,
data.offset,
data.phase,
data.randomness,
data.infinityValues,
data.veryLargeValues
),
wavelengths: wavelengths(),
intensities: intensities(),
cos: cos(
nextStep,
data.period,
data.amplitude,
data.offset,
data.phase,
data.randomness,
data.infinityValues,
data.veryLargeValues
)
}
});
nextStep += step;
}
return nextStep;
};
}
function cos(timestamp, period, amplitude, offset, phase, randomness, infinityValues) {
if (infinityValues && Math.random() > 0.5) {
subscriptions[message.id] = work;
workSubscriptions();
}
function onUnsubscribe(message) {
delete subscriptions[message.data.id];
}
function onRequest(message) {
var request = message.data;
if (request.end === undefined) {
request.end = Date.now();
}
if (request.start === undefined) {
request.start = request.end - FIFTEEN_MINUTES;
}
var now = Date.now();
var start = request.start;
var end = request.end > now ? now : request.end;
var amplitude = request.amplitude;
var period = request.period;
var offset = request.offset;
var dataRateInHz = request.dataRateInHz;
var phase = request.phase;
var randomness = request.randomness;
var loadDelay = Math.max(request.loadDelay, 0);
var infinityValues = request.infinityValues;
var veryLargeValues = request.veryLargeValues;
var step = 1000 / dataRateInHz;
var nextStep = start - (start % step) + step;
var data = [];
for (; nextStep < end && data.length < 5000; nextStep += step) {
data.push({
utc: nextStep,
yesterday: nextStep - 60 * 60 * 24 * 1000,
sin: sin(nextStep, period, amplitude, offset, phase, randomness, infinityValues, veryLargeValues),
wavelengths: wavelengths(),
intensities: intensities(),
cos: cos(nextStep, period, amplitude, offset, phase, randomness, infinityValues, veryLargeValues)
});
}
if (loadDelay === 0) {
postOnRequest(message, request, data);
} else {
setTimeout(() => postOnRequest(message, request, data), loadDelay);
}
}
function postOnRequest(message, request, data) {
self.postMessage({
id: message.id,
data: request.spectra
? {
wavelength: data.map((item) => {
return item.wavelength;
}),
cos: data.map((item) => {
return item.cos;
})
}
: data
});
}
function cos(timestamp, period, amplitude, offset, phase, randomness, infinityValues, veryLargeValues) {
const randomValue = Math.random();
if (infinityValues && veryLargeValues) {
if (randomValue < 0.33) return Number.POSITIVE_INFINITY;
if (randomValue < 0.66) return (randomValue > 0.5 ? -1 : 1) * 1e+177;
// if neither condition is met, it just proceeds to the normal return value
} else {
if (infinityValues && randomValue > 0.5) {
return Number.POSITIVE_INFINITY;
}
return amplitude
* Math.cos(phase + (timestamp / period / 1000 * Math.PI * 2)) + (amplitude * Math.random() * randomness) + offset;
if (veryLargeValues && randomValue > 0.5) {
return (randomValue > 0.5 ? -1 : 1) * 1e+177;
}
}
function sin(timestamp, period, amplitude, offset, phase, randomness, infinityValues) {
if (infinityValues && Math.random() > 0.5) {
return (
amplitude * Math.cos(phase + (timestamp / period / 1000) * Math.PI * 2) +
amplitude * Math.random() * randomness +
offset
);
}
function sin(timestamp, period, amplitude, offset, phase, randomness, infinityValues, veryLargeValues) {
const randomValue = Math.random();
if (infinityValues && veryLargeValues) {
if (randomValue < 0.33) return Number.POSITIVE_INFINITY;
if (randomValue < 0.66) return (randomValue > 0.5 ? -1 : 1) * 1e+177;
// if neither condition is met, it just proceeds to the normal return value
} else {
if (infinityValues && randomValue > 0.5) {
return Number.POSITIVE_INFINITY;
}
return amplitude
* Math.sin(phase + (timestamp / period / 1000 * Math.PI * 2)) + (amplitude * Math.random() * randomness) + offset;
}
function wavelengths() {
let values = [];
while (values.length < 5) {
const randomValue = Math.random() * 100;
if (!values.includes(randomValue)) {
values.push(String(randomValue));
}
if (veryLargeValues && randomValue > 0.5) {
return (randomValue > 0.5 ? -1 : 1) * 1e+177;
}
return values;
}
function intensities() {
let values = [];
while (values.length < 5) {
const randomValue = Math.random() * 10;
if (!values.includes(randomValue)) {
values.push(String(randomValue));
}
}
return (
amplitude * Math.sin(phase + (timestamp / period / 1000) * Math.PI * 2) +
amplitude * Math.random() * randomness +
offset
);
}
return values;
function wavelengths() {
let values = [];
while (values.length < 5) {
const randomValue = Math.random() * 100;
if (!values.includes(randomValue)) {
values.push(String(randomValue));
}
}
function sendError(error, message) {
self.postMessage({
error: error.name + ': ' + error.message,
message: message,
id: message.id
});
return values;
}
function intensities() {
let values = [];
while (values.length < 5) {
const randomValue = Math.random() * 10;
if (!values.includes(randomValue)) {
values.push(String(randomValue));
}
}
self.onmessage = function handleMessage(event) {
var message = event.data;
var handler = handlers[message.request];
return values;
}
if (!handler) {
sendError(new Error('unknown message type'), message);
} else {
try {
handler(message);
} catch (e) {
sendError(e, message);
}
}
};
function sendError(error, message) {
self.postMessage({
error: error.name + ': ' + error.message,
message: message,
id: message.id
});
}
}());
self.onmessage = function handleMessage(event) {
var message = event.data;
var handler = handlers[message.request];
if (!handler) {
sendError(new Error('unknown message type'), message);
} else {
try {
handler(message);
} catch (e) {
sendError(e, message);
}
}
};
})();

View File

@ -20,163 +20,142 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import GeneratorProvider from "./GeneratorProvider";
import SinewaveLimitProvider from "./SinewaveLimitProvider";
import SinewaveStalenessProvider from "./SinewaveStalenessProvider";
import StateGeneratorProvider from "./StateGeneratorProvider";
import GeneratorMetadataProvider from "./GeneratorMetadataProvider";
import GeneratorMetadataProvider from './GeneratorMetadataProvider';
import GeneratorProvider from './GeneratorProvider';
import SinewaveLimitProvider from './SinewaveLimitProvider';
import SinewaveStalenessProvider from './SinewaveStalenessProvider';
import StateGeneratorProvider from './StateGeneratorProvider';
export default function (openmct) {
openmct.types.addType('example.state-generator', {
name: 'State Generator',
description:
'For development use. Generates example enumerated telemetry by cycling through a given set of states.',
cssClass: 'icon-generator-telemetry',
creatable: true,
form: [
{
name: 'State Duration (seconds)',
control: 'numberfield',
cssClass: 'l-input-sm l-numeric',
key: 'duration',
required: true,
property: ['telemetry', 'duration']
}
],
initialize: function (object) {
object.telemetry = {
duration: 5
};
}
});
openmct.types.addType("example.state-generator", {
name: "State Generator",
description: "For development use. Generates example enumerated telemetry by cycling through a given set of states.",
cssClass: "icon-generator-telemetry",
creatable: true,
form: [
{
name: "State Duration (seconds)",
control: "numberfield",
cssClass: "l-input-sm l-numeric",
key: "duration",
required: true,
property: [
"telemetry",
"duration"
]
}
],
initialize: function (object) {
object.telemetry = {
duration: 5
};
}
});
openmct.telemetry.addProvider(new StateGeneratorProvider());
openmct.telemetry.addProvider(new StateGeneratorProvider());
openmct.types.addType('generator', {
name: 'Sine Wave Generator',
description:
'For development use. Generates example streaming telemetry data using a simple sine wave algorithm.',
cssClass: 'icon-generator-telemetry',
creatable: true,
form: [
{
name: 'Period',
control: 'numberfield',
cssClass: 'l-input-sm l-numeric',
key: 'period',
required: true,
property: ['telemetry', 'period']
},
{
name: 'Amplitude',
control: 'numberfield',
cssClass: 'l-numeric',
key: 'amplitude',
required: true,
property: ['telemetry', 'amplitude']
},
{
name: 'Offset',
control: 'numberfield',
cssClass: 'l-numeric',
key: 'offset',
required: true,
property: ['telemetry', 'offset']
},
{
name: 'Data Rate (hz)',
control: 'numberfield',
cssClass: 'l-input-sm l-numeric',
key: 'dataRateInHz',
required: true,
property: ['telemetry', 'dataRateInHz']
},
{
name: 'Phase (radians)',
control: 'numberfield',
cssClass: 'l-input-sm l-numeric',
key: 'phase',
required: true,
property: ['telemetry', 'phase']
},
{
name: 'Randomness',
control: 'numberfield',
cssClass: 'l-input-sm l-numeric',
key: 'randomness',
required: true,
property: ['telemetry', 'randomness']
},
{
name: 'Loading Delay (ms)',
control: 'numberfield',
cssClass: 'l-input-sm l-numeric',
key: 'loadDelay',
required: true,
property: ['telemetry', 'loadDelay']
},
{
name: 'Include Infinity Values',
control: 'toggleSwitch',
cssClass: 'l-input',
key: 'infinityValues',
property: ['telemetry', 'infinityValues']
},
{
name: 'Include Very Large Values',
control: 'toggleSwitch',
cssClass: 'l-input',
key: 'veryLargeValues',
property: ['telemetry', 'veryLargeValues']
},
{
name: 'Provide Staleness Updates',
control: 'toggleSwitch',
cssClass: 'l-input',
key: 'staleness',
property: ['telemetry', 'staleness']
}
],
initialize: function (object) {
object.telemetry = {
period: 10,
amplitude: 1,
offset: 0,
dataRateInHz: 1,
phase: 0,
randomness: 0,
loadDelay: 0,
infinityValues: false,
veryLargeValues: false,
staleness: false
};
}
});
const stalenessProvider = new SinewaveStalenessProvider(openmct);
openmct.types.addType("generator", {
name: "Sine Wave Generator",
description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
cssClass: "icon-generator-telemetry",
creatable: true,
form: [
{
name: "Period",
control: "numberfield",
cssClass: "l-input-sm l-numeric",
key: "period",
required: true,
property: [
"telemetry",
"period"
]
},
{
name: "Amplitude",
control: "numberfield",
cssClass: "l-numeric",
key: "amplitude",
required: true,
property: [
"telemetry",
"amplitude"
]
},
{
name: "Offset",
control: "numberfield",
cssClass: "l-numeric",
key: "offset",
required: true,
property: [
"telemetry",
"offset"
]
},
{
name: "Data Rate (hz)",
control: "numberfield",
cssClass: "l-input-sm l-numeric",
key: "dataRateInHz",
required: true,
property: [
"telemetry",
"dataRateInHz"
]
},
{
name: "Phase (radians)",
control: "numberfield",
cssClass: "l-input-sm l-numeric",
key: "phase",
required: true,
property: [
"telemetry",
"phase"
]
},
{
name: "Randomness",
control: "numberfield",
cssClass: "l-input-sm l-numeric",
key: "randomness",
required: true,
property: [
"telemetry",
"randomness"
]
},
{
name: "Loading Delay (ms)",
control: "numberfield",
cssClass: "l-input-sm l-numeric",
key: "loadDelay",
required: true,
property: [
"telemetry",
"loadDelay"
]
},
{
name: "Include Infinity Values",
control: "toggleSwitch",
cssClass: "l-input",
key: "infinityValues",
property: [
"telemetry",
"infinityValues"
]
},
{
name: "Provide Staleness Updates",
control: "toggleSwitch",
cssClass: "l-input",
key: "staleness",
property: [
"telemetry",
"staleness"
]
}
],
initialize: function (object) {
object.telemetry = {
period: 10,
amplitude: 1,
offset: 0,
dataRateInHz: 1,
phase: 0,
randomness: 0,
loadDelay: 0,
infinityValues: false,
staleness: false
};
}
});
const stalenessProvider = new SinewaveStalenessProvider(openmct);
openmct.telemetry.addProvider(new GeneratorProvider(openmct, stalenessProvider));
openmct.telemetry.addProvider(new GeneratorMetadataProvider());
openmct.telemetry.addProvider(new SinewaveLimitProvider());
openmct.telemetry.addProvider(stalenessProvider);
openmct.telemetry.addProvider(new GeneratorProvider(openmct, stalenessProvider));
openmct.telemetry.addProvider(new GeneratorMetadataProvider());
openmct.telemetry.addProvider(new SinewaveLimitProvider());
openmct.telemetry.addProvider(stalenessProvider);
}

View File

@ -239,9 +239,15 @@ export default class ObjectAPI {
return domainObject;
}).catch((error) => {
console.warn(`Failed to retrieve ${keystring}:`, error);
let result;
delete this.cache[keystring];
const result = this.applyGetInterceptors(identifier);
// suppress abort errors
if (error.name !== 'AbortError') {
console.warn(`Failed to retrieve ${keystring}:`, error);
result = this.applyGetInterceptors(identifier);
}
return result;
});

View File

@ -55,6 +55,13 @@ define([
*/
function parseKeyString(keyString) {
if (isIdentifier(keyString)) {
// TODO REMOVE FOR OMM-RELEASE-5.0
if (!keyString.namespace && keyString.key.includes(':')) {
console.warn(`smushed key: ${keyString.key}`);
return parseKeyString(keyString.key);
}
return keyString;
}

View File

@ -88,7 +88,7 @@ export default class TelemetryAPI {
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
*/
canProvideTelemetry(domainObject) {
return Boolean(this.#findSubscriptionProvider(domainObject))
return Boolean(this.findSubscriptionProvider(domainObject))
|| Boolean(this.findRequestProvider(domainObject));
}
@ -123,9 +123,10 @@ export default class TelemetryAPI {
}
/**
* @private
* Returns a telemetry subscription provider that supports
* a given domain object and options.
*/
#findSubscriptionProvider() {
findSubscriptionProvider() {
const args = Array.prototype.slice.apply(arguments);
function supportsDomainObject(provider) {
return provider.supportsSubscribe.apply(provider, args);
@ -348,7 +349,7 @@ export default class TelemetryAPI {
return () => {};
}
const provider = this.#findSubscriptionProvider(domainObject);
const provider = this.findSubscriptionProvider(domainObject);
if (!this.subscribeCache) {
this.subscribeCache = {};

View File

@ -21,16 +21,18 @@
*****************************************************************************/
<template>
<component
:is="urlDefined ? 'a' : 'span'"
<div
ref="conditionWidgetElement"
class="c-condition-widget u-style-receiver js-style-receiver"
:href="url"
:target="url ? '_BLANK' : ''"
>
<div class="c-condition-widget__label">
{{ label }}
</div>
</component>
<component
:is="urlDefined ? 'a' : 'div'"
class="c-condition-widget__label-wrapper"
:href="url"
>
<div class="c-condition-widget__label">{{ label }}</div>
</component>
</div>
</template>
<script>
@ -40,19 +42,26 @@ export default {
inject: ['openmct', 'domainObject'],
data: function () {
return {
conditionalLabel: '',
conditionSetIdentifier: null,
domainObjectLabel: '',
url: null,
urlDefined: false,
useConditionSetOutputAsLabel: false
conditionalLabel: ''
};
},
computed: {
urlDefined() {
return this.domainObject.url?.length > 0;
},
url() {
return this.urlDefined ? sanitizeUrl(this.domainObject.url) : null;
},
useConditionSetOutputAsLabel() {
return this.conditionSetIdentifier && this.domainObject.configuration.useConditionSetOutputAsLabel;
},
conditionSetIdentifier() {
return this.domainObject.configuration?.objectStyles?.conditionSetIdentifier;
},
label() {
return this.useConditionSetOutputAsLabel
? this.conditionalLabel
: this.domainObjectLabel
: this.domainObject.label
;
}
},
@ -69,20 +78,11 @@ export default {
}
},
mounted() {
this.unlisten = this.openmct.objects.observe(this.domainObject, '*', this.updateDomainObject);
if (this.domainObject) {
this.updateDomainObject(this.domainObject);
this.listenToConditionSetChanges();
}
},
beforeDestroy() {
this.conditionSetIdentifier = null;
if (this.unlisten) {
this.unlisten();
}
this.stopListeningToConditionSetChanges();
},
methods: {
@ -121,31 +121,6 @@ export default {
}
this.conditionalLabel = latestDatum.output || '';
},
updateDomainObject(domainObject) {
if (this.domainObjectLabel !== domainObject.label) {
this.domainObjectLabel = domainObject.label;
}
const urlDefined = domainObject.url && domainObject.url.length > 0;
if (this.urlDefined !== urlDefined) {
this.urlDefined = urlDefined;
}
const url = this.urlDefined ? sanitizeUrl(domainObject.url) : null;
if (this.url !== url) {
this.url = url;
}
const conditionSetIdentifier = domainObject.configuration?.objectStyles?.conditionSetIdentifier;
if (conditionSetIdentifier && this.conditionSetIdentifier !== conditionSetIdentifier) {
this.conditionSetIdentifier = conditionSetIdentifier;
}
const useConditionSetOutputAsLabel = this.conditionSetIdentifier && domainObject.configuration.useConditionSetOutputAsLabel;
if (this.useConditionSetOutputAsLabel !== useConditionSetOutputAsLabel) {
this.useConditionSetOutputAsLabel = useConditionSetOutputAsLabel;
}
}
}
};

View File

@ -26,31 +26,35 @@
background-color: rgba($colorBodyFg, 0.1); // Give a little presence if the user hasn't defined a fill color
border-radius: $basicCr;
border: 1px solid transparent;
display: inline-block;
padding: $interiorMarginLg $interiorMarginLg * 2;
display: block;
max-width: max-content;
a {
display: block;
color: inherit;
}
}
.c-condition-widget__label {
padding: $interiorMargin;
// Either a <div> or an <a> tag
padding: $interiorMargin $interiorMargin * 1.5;
text-align: center;
white-space: normal;
}
a.c-condition-widget {
// Widget is conditionally made into a <a> when URL property has been defined
cursor: pointer !important;
pointer-events: inherit;
}
// Make Condition Widget expand when in a hidden frame Layout context
// For both static and Flexible Layouts
.c-so-view--conditionWidget.c-so-view--no-frame {
.c-condition-widget {
@include abs();
display: flex;
align-items: center;
justify-content: center;
padding: 0;
max-width: unset;
&__label-wrapper {
@include abs();
display: flex;
align-items: center;
justify-content: center;
}
}
.c-so-view__frame-controls { display: none; }

View File

@ -36,6 +36,7 @@ export default function plugin() {
domainObject.configuration = {};
domainObject.label = 'Condition Widget';
domainObject.conditionalLabel = '';
domainObject.url = '';
},
form: [
{

View File

@ -20,8 +20,6 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
import JSONExporter from '/src/exporters/JSONExporter.js';
import _ from 'lodash';
import { v4 as uuid } from 'uuid';
export default class ExportAsJSONAction {
@ -35,10 +33,9 @@ export default class ExportAsJSONAction {
this.group = "json";
this.priority = 1;
this.externalIdentifiers = [];
this.tree = {};
this.calls = 0;
this.idMap = {};
this.tree = null;
this.calls = null;
this.idMap = null;
this.JSONExportService = new JSONExporter();
}
@ -60,21 +57,164 @@ export default class ExportAsJSONAction {
*/
invoke(objectpath) {
this.tree = {};
this.calls = 0;
this.idMap = {};
const root = objectpath[0];
this.root = JSON.parse(JSON.stringify(root));
const rootId = this._getId(this.root);
this.root = this._copy(root);
const rootId = this._getKeystring(this.root);
this.tree[rootId] = this.root;
this._write(this.root);
}
/**
* @private
* @param {object} parent
*/
async _write(parent) {
this.calls++;
//conditional object styles are not saved on the composition, so we need to check for them
const conditionSetIdentifier = this._getConditionSetIdentifier(parent);
const hasItemConditionSetIdentifiers = this._hasItemConditionSetIdentifiers(parent);
const composition = this.openmct.composition.get(parent);
if (composition) {
const children = await composition.load();
children.forEach((child) => {
this._exportObject(child, parent);
});
}
if (!conditionSetIdentifier && !hasItemConditionSetIdentifiers) {
this._decrementCallsAndSave();
} else {
const conditionSetObjects = [];
// conditionSetIdentifiers directly in objectStyles object
if (conditionSetIdentifier) {
conditionSetObjects.push(await this.openmct.objects.get(conditionSetIdentifier));
}
// conditionSetIdentifiers stored on item ids in the objectStyles object
if (hasItemConditionSetIdentifiers) {
const itemConditionSetIdentifiers = this._getItemConditionSetIdentifiers(parent);
for (const itemConditionSetIdentifier of itemConditionSetIdentifiers) {
conditionSetObjects.push(await this.openmct.objects.get(itemConditionSetIdentifier));
}
}
for (const conditionSetObject of conditionSetObjects) {
this._exportObject(conditionSetObject, parent);
}
this._decrementCallsAndSave();
}
}
_exportObject(child, parent) {
const originalKeyString = this._getKeystring(child);
const createable = this._isCreatableAndPersistable(child);
const isNotInfinite = !Object.prototype.hasOwnProperty.call(this.tree, originalKeyString);
if (createable && isNotInfinite) {
// for external or linked objects we generate new keys, if they don't exist already
if (this._isLinkedObject(child, parent)) {
child = this._rewriteLink(child, parent);
} else {
this.tree[originalKeyString] = child;
}
this._write(child);
}
}
/**
* @private
* @param {object} child
* @param {object} parent
* @returns {object}
*/
_rewriteLink(child, parent) {
const originalKeyString = this._getKeystring(child);
const parentKeyString = this._getKeystring(parent);
const conditionSetIdentifier = this._getConditionSetIdentifier(parent);
const hasItemConditionSetIdentifiers = this._hasItemConditionSetIdentifiers(parent);
const existingMappedKeyString = this.idMap[originalKeyString];
let copy;
if (!existingMappedKeyString) {
copy = this._copy(child);
copy.identifier.key = uuid();
if (!conditionSetIdentifier && !hasItemConditionSetIdentifiers) {
copy.location = parentKeyString;
}
let newKeyString = this._getKeystring(copy);
this.idMap[originalKeyString] = newKeyString;
this.tree[newKeyString] = copy;
} else {
copy = this.tree[existingMappedKeyString];
}
if (conditionSetIdentifier || hasItemConditionSetIdentifiers) {
// update objectStyle object
if (conditionSetIdentifier) {
const directObjectStylesIdentifier = this.openmct.objects.areIdsEqual(
parent.configuration.objectStyles.conditionSetIdentifier,
child.identifier
);
if (directObjectStylesIdentifier) {
parent.configuration.objectStyles.conditionSetIdentifier = copy.identifier;
this.tree[parentKeyString].configuration.objectStyles.conditionSetIdentifier = copy.identifier;
}
}
// update per item id on objectStyle object
if (hasItemConditionSetIdentifiers) {
for (const itemId in parent.configuration.objectStyles) {
if (parent.configuration.objectStyles[itemId]) {
const itemConditionSetIdentifier = parent.configuration.objectStyles[itemId].conditionSetIdentifier;
if (
itemConditionSetIdentifier
&& this.openmct.objects.areIdsEqual(itemConditionSetIdentifier, child.identifier)
) {
parent.configuration.objectStyles[itemId].conditionSetIdentifier = copy.identifier;
this.tree[parentKeyString].configuration.objectStyles[itemId].conditionSetIdentifier = copy.identifier;
}
}
}
}
} else {
// just update parent
const index = parent.composition.findIndex(identifier => {
return this.openmct.objects.areIdsEqual(child.identifier, identifier);
});
parent.composition[index] = copy.identifier;
this.tree[parentKeyString].composition[index] = copy.identifier;
}
return copy;
}
/**
* @private
* @param {object} domainObject
* @returns {string} A string representation of the given identifier, including namespace and key
*/
_getId(domainObject) {
_getKeystring(domainObject) {
return this.openmct.objects.makeKeyString(domainObject.identifier);
}
/**
* @private
* @param {object} domainObject
@ -86,6 +226,7 @@ export default class ExportAsJSONAction {
return type && type.definition.creatable && isPersistable;
}
/**
* @private
* @param {object} child
@ -93,74 +234,80 @@ export default class ExportAsJSONAction {
* @returns {boolean}
*/
_isLinkedObject(child, parent) {
if (child.location !== this._getId(parent)
&& !Object.keys(this.tree).includes(child.location)
&& this._getId(child) !== this._getId(this.root)
|| this.externalIdentifiers.includes(this._getId(child))) {
const rootKeyString = this._getKeystring(this.root);
const childKeyString = this._getKeystring(child);
const parentKeyString = this._getKeystring(parent);
return true;
return (child.location !== parentKeyString
&& !Object.keys(this.tree).includes(child.location)
&& childKeyString !== rootKeyString)
|| this.idMap[childKeyString] !== undefined;
}
_getConditionSetIdentifier(object) {
return object.configuration?.objectStyles?.conditionSetIdentifier;
}
_hasItemConditionSetIdentifiers(parent) {
const objectStyles = parent.configuration?.objectStyles;
for (const itemId in objectStyles) {
if (Object.prototype.hasOwnProperty.call(objectStyles[itemId], 'conditionSetIdentifier')) {
return true;
}
}
return false;
}
/**
* @private
* @param {object} child
* @param {object} parent
* @returns {object}
*/
_rewriteLink(child, parent) {
this.externalIdentifiers.push(this._getId(child));
const index = parent.composition.findIndex(id => {
return _.isEqual(child.identifier, id);
});
const copyOfChild = JSON.parse(JSON.stringify(child));
copyOfChild.identifier.key = uuid();
const newIdString = this._getId(copyOfChild);
const parentId = this._getId(parent);
_getItemConditionSetIdentifiers(parent) {
const objectStyles = parent.configuration?.objectStyles;
let identifiers = new Set();
this.idMap[this._getId(child)] = newIdString;
copyOfChild.location = parentId;
parent.composition[index] = copyOfChild.identifier;
this.tree[newIdString] = copyOfChild;
this.tree[parentId].composition[index] = copyOfChild.identifier;
if (objectStyles) {
Object.keys(objectStyles).forEach(itemId => {
if (objectStyles[itemId].conditionSetIdentifier) {
identifiers.add(objectStyles[itemId].conditionSetIdentifier);
}
});
}
return copyOfChild;
return Array.from(identifiers);
}
/**
* @private
* @param {object} child
* @param {object} parent
* @returns {object}
*/
_rewriteLinkForReference(child, parent) {
const childId = this._getId(child);
this.externalIdentifiers.push(childId);
const copyOfChild = JSON.parse(JSON.stringify(child));
copyOfChild.identifier.key = uuid();
const newIdString = this._getId(copyOfChild);
const parentId = this._getId(parent);
this.idMap[childId] = newIdString;
copyOfChild.location = null;
parent.configuration.objectStyles.conditionSetIdentifier = copyOfChild.identifier;
this.tree[newIdString] = copyOfChild;
this.tree[parentId].configuration.objectStyles.conditionSetIdentifier = copyOfChild.identifier;
return copyOfChild;
}
/**
* @private
*/
_rewriteReferences() {
const oldKeyStrings = Object.keys(this.idMap);
let treeString = JSON.stringify(this.tree);
Object.keys(this.idMap).forEach(function (oldId) {
const newId = this.idMap[oldId];
treeString = treeString.split(oldId).join(newId);
}.bind(this));
oldKeyStrings.forEach((oldKeyString) => {
// this will cover keyStrings, identifiers and identifiers created
// by hand that may be structured differently from those created with 'makeKeyString'
const newKeyString = this.idMap[oldKeyString];
const newIdentifier = JSON.stringify(this.openmct.objects.parseKeyString(newKeyString));
const oldIdentifier = this.openmct.objects.parseKeyString(oldKeyString);
const oldIdentifierNamespaceFirst = JSON.stringify(oldIdentifier);
const oldIdentifierKeyFirst = JSON.stringify({
key: oldIdentifier.key,
namespace: oldIdentifier.namespace
});
// replace keyStrings
treeString = treeString.split(oldKeyString).join(newKeyString);
// check for namespace first identifiers, replace if necessary
if (treeString.includes(oldIdentifierNamespaceFirst)) {
treeString = treeString.split(oldIdentifierNamespaceFirst).join(newIdentifier);
}
// check for key first identifiers, replace if necessary
if (treeString.includes(oldIdentifierKeyFirst)) {
treeString = treeString.split(oldIdentifierKeyFirst).join(newIdentifier);
}
});
this.tree = JSON.parse(treeString);
}
/**
@ -180,70 +327,10 @@ export default class ExportAsJSONAction {
_wrapTree() {
return {
"openmct": this.tree,
"rootId": this._getId(this.root)
"rootId": this._getKeystring(this.root)
};
}
/**
* @private
* @param {object} parent
*/
_write(parent) {
this.calls++;
//conditional object styles are not saved on the composition, so we need to check for them
let childObjectReferenceId = parent.configuration?.objectStyles?.conditionSetIdentifier;
const composition = this.openmct.composition.get(parent);
if (composition !== undefined) {
composition.load()
.then((children) => {
children.forEach((child, index) => {
// Only export if object is creatable
if (this._isCreatableAndPersistable(child)) {
// Prevents infinite export of self-contained objs
if (!Object.prototype.hasOwnProperty.call(this.tree, this._getId(child))) {
// If object is a link to something absent from
// tree, generate new id and treat as new object
if (this._isLinkedObject(child, parent)) {
child = this._rewriteLink(child, parent);
} else {
this.tree[this._getId(child)] = child;
}
this._write(child);
}
}
});
this._decrementCallsAndSave();
});
} else if (!childObjectReferenceId) {
this._decrementCallsAndSave();
}
if (childObjectReferenceId) {
this.openmct.objects.get(childObjectReferenceId)
.then((child) => {
// Only export if object is creatable
if (this._isCreatableAndPersistable(child)) {
// Prevents infinite export of self-contained objs
if (!Object.prototype.hasOwnProperty.call(this.tree, this._getId(child))) {
// If object is a link to something absent from
// tree, generate new id and treat as new object
if (this._isLinkedObject(child, parent)) {
child = this._rewriteLinkForReference(child, parent);
} else {
this.tree[this._getId(child)] = child;
}
this._write(child);
}
}
this._decrementCallsAndSave();
});
}
}
_decrementCallsAndSave() {
this.calls--;
if (this.calls === 0) {
@ -251,4 +338,8 @@ export default class ExportAsJSONAction {
this._saveAs(this._wrapTree());
}
}
_copy(object) {
return JSON.parse(JSON.stringify(object));
}
}

View File

@ -56,6 +56,10 @@ define([
});
},
showTab: function (isEditing) {
if (isEditing) {
return true;
}
const hasPersistedFilters = Boolean(domainObject?.configuration?.filters);
const hasGlobalFilters = Boolean(domainObject?.configuration?.globalFilters);

View File

@ -64,6 +64,11 @@ export default class CreateAction extends PropertiesAction {
const parentDomainObject = this.openmct.objects.toMutable(parentDomainObjectPath[0]);
// TODO REMOVE FOR OMM-RELEASE-5.0
if (!parentDomainObject.identifier.namespace && parentDomainObject.key) {
console.error(`parent namespace in key: ${parentDomainObject.key}`);
}
this.domainObject.modified = Date.now();
this.domainObject.location = this.openmct.objects.makeKeyString(parentDomainObject.identifier);
this.domainObject.identifier.namespace = parentDomainObject.identifier.namespace;

View File

@ -31,6 +31,7 @@ export default class ImportAsJSONAction {
this.cssClass = "icon-import";
this.group = "json";
this.priority = 2;
this.newObjects = [];
this.openmct = openmct;
}
@ -85,22 +86,25 @@ export default class ImportAsJSONAction {
let objectIdentifiers = this._getObjectReferenceIds(parent);
if (objectIdentifiers.length) {
let newObj;
const parentId = this.openmct.objects.makeKeyString(parent.identifier);
seen.push(parentId);
seen.push(parent.id);
objectIdentifiers.forEach(async (childId) => {
for (const childId of objectIdentifiers) {
const keystring = this.openmct.objects.makeKeyString(childId);
if (!tree[keystring] || seen.includes(keystring)) {
return;
continue;
}
const newModel = tree[keystring];
delete newModel.persisted;
newObj = await this._instantiate(newModel);
this._deepInstantiate(newObj, tree, seen);
}, this);
this.newObjects.push(newModel);
// make sure there weren't any errors saving
if (newModel) {
this._deepInstantiate(newModel, tree, seen);
}
}
}
}
/**
@ -110,19 +114,32 @@ export default class ImportAsJSONAction {
*/
_getObjectReferenceIds(parent) {
let objectIdentifiers = [];
let itemObjectReferences = [];
const objectStyles = parent?.configuration?.objectStyles;
const parentComposition = this.openmct.composition.get(parent);
let parentComposition = this.openmct.composition.get(parent);
if (parentComposition) {
objectIdentifiers = Array.from(parentComposition.domainObject.composition);
objectIdentifiers = Array.from(parent.composition);
}
//conditional object styles are not saved on the composition, so we need to check for them
let parentObjectReference = parent.configuration?.objectStyles?.conditionSetIdentifier;
if (parentObjectReference) {
objectIdentifiers.push(parentObjectReference);
if (objectStyles) {
const parentObjectReference = objectStyles.conditionSetIdentifier;
if (parentObjectReference) {
objectIdentifiers.push(parentObjectReference);
}
function hasConditionSetIdentifier(item) {
return Boolean(item.conditionSetIdentifier);
}
itemObjectReferences = Object.values(objectStyles)
.filter(hasConditionSetIdentifier)
.map(item => item.conditionSetIdentifier);
}
return objectIdentifiers;
return Array.from(new Set([...objectIdentifiers, ...itemObjectReferences]));
}
/**
* @private
@ -155,13 +172,21 @@ export default class ImportAsJSONAction {
const tree = this._generateNewIdentifiers(objTree, namespace);
const rootId = tree.rootId;
const rootModel = tree.openmct[rootId];
delete rootModel.persisted;
const rootObj = tree.openmct[rootId];
delete rootObj.persisted;
this.newObjects.push(rootObj);
const rootObj = await this._instantiate(rootModel);
if (this.openmct.composition.checkPolicy(domainObject, rootObj)) {
this._deepInstantiate(rootObj, tree.openmct, []);
try {
await Promise.all(this.newObjects.map(this._instantiate, this));
} catch (error) {
this.openmct.notifications.error('Error saving objects');
throw error;
}
const compositionCollection = this.openmct.composition.get(domainObject);
let domainObjectKeyString = this.openmct.objects.makeKeyString(domainObject.identifier);
this.openmct.objects.mutate(rootObj, 'location', domainObjectKeyString);
@ -184,16 +209,11 @@ export default class ImportAsJSONAction {
}
/**
* @private
* @param {object} rootModel
* @param {object} model
* @returns {object}
*/
async _instantiate(rootModel) {
const success = await this.openmct.objects.save(rootModel);
if (success) {
return rootModel;
}
this.openmct.notifications.error('Error saving objects');
_instantiate(model) {
return this.openmct.objects.save(model);
}
/**
* @private

View File

@ -19,7 +19,7 @@
class="c-icon-button c-button--menu icon-font"
@click.prevent.stop="showFontMenu"
>
<span class="c-button__label">{{ fontTypeLable }}</span>
<span class="c-button__label">{{ fontTypeLabel }}</span>
</button>
</div>
</div>
@ -43,7 +43,7 @@ export default {
}
},
computed: {
fontTypeLable() {
fontTypeLabel() {
const fontType = FONTS.find(f => f.value === this.fontStyle.font);
if (!fontType) {
return '??';

View File

@ -88,7 +88,7 @@ define([], function () {
}
getContextMenuActions() {
return ['viewDatumAction'];
return ['viewDatumAction', 'viewHistoricalData'];
}
}

View File

@ -175,14 +175,22 @@ export default {
getDatum() {
return this.row.fullDatum;
},
showContextMenu: function (event) {
showContextMenu: async function (event) {
event.preventDefault();
this.updateViewContext();
this.markRow(event);
const contextualDomainObject = await this.row.getContextualDomainObject?.(this.openmct, this.row.objectKeyString);
let objectPath = this.objectPath;
if (contextualDomainObject) {
objectPath = objectPath.slice();
objectPath.unshift(contextualDomainObject);
}
const actions = this.row.getContextMenuActions().map(key => this.openmct.actions.getAction(key));
const menuItems = this.openmct.menus.actionsToMenuItems(actions, this.objectPath, this.currentView);
const menuItems = this.openmct.menus.actionsToMenuItems(actions, objectPath, this.currentView);
if (menuItems.length) {
this.openmct.menus.showMenu(event.x, event.y, menuItems);
}

View File

@ -74,7 +74,7 @@ export default {
return this.domainObject && (this.currentObjectPath || this.objectPath);
},
objectFontStyle() {
return this.domainObject && this.domainObject.configuration && this.domainObject.configuration.fontStyle;
return this.domainObject?.configuration?.fontStyle;
},
fontSize() {
return this.objectFontStyle ? this.objectFontStyle.fontSize : this.layoutFontSize;
@ -287,6 +287,8 @@ export default {
this.$nextTick(() => {
this.updateStyle(this.styleRuleManager?.currentStyle);
this.setFontSize(this.fontSize);
this.setFont(this.font);
this.getActionCollection();
});
},
@ -329,9 +331,9 @@ export default {
},
initObjectStyles() {
if (!this.styleRuleManager) {
this.styleRuleManager = new StyleRuleManager((this.domainObject.configuration && this.domainObject.configuration.objectStyles), this.openmct, this.updateStyle.bind(this), true);
this.styleRuleManager = new StyleRuleManager((this.domainObject.configuration?.objectStyles), this.openmct, this.updateStyle.bind(this), true);
} else {
this.styleRuleManager.updateObjectStyleConfig(this.domainObject.configuration && this.domainObject.configuration.objectStyles);
this.styleRuleManager.updateObjectStyleConfig(this.domainObject.configuration?.objectStyles);
}
if (this.stopListeningStyles) {
@ -343,9 +345,6 @@ export default {
this.styleRuleManager.updateObjectStyleConfig(newObjectStyle);
});
this.setFontSize(this.fontSize);
this.setFont(this.font);
this.stopListeningFontStyles = this.openmct.objects.observe(this.domainObject, 'configuration.fontStyle', (newFontStyle) => {
this.setFontSize(newFontStyle.fontSize);
this.setFont(newFontStyle.font);

View File

@ -322,10 +322,14 @@ export default {
},
async openTreeItem(parentItem) {
const parentPath = parentItem.navigationPath;
const abortSignal = this.startItemLoad(parentPath);
this.startItemLoad(parentPath);
// pass in abort signal when functional
const childrenItems = await this.loadAndBuildTreeItemsFor(parentItem.object.identifier, parentItem.objectPath);
const childrenItems = await this.loadAndBuildTreeItemsFor(
parentItem.object.identifier,
parentItem.objectPath,
abortSignal
);
const parentIndex = this.treeItems.indexOf(parentItem);
// if it's not loading, it was aborted