mirror of
https://github.com/nasa/openmct.git
synced 2025-06-25 18:50:11 +00:00
Compare commits
21 Commits
Author | SHA1 | Date | |
---|---|---|---|
8c56edc5fa | |||
eadcd2dead | |||
c36fa211d2 | |||
510d3bd333 | |||
a908eb1d65 | |||
c0bda64927 | |||
d0c5731287 | |||
5eaf222f88 | |||
0249ab4df5 | |||
4f8cba160d | |||
c269e089da | |||
4873f40614 | |||
10bb9173ec | |||
ea8c9c7cc8 | |||
4c9c084eec | |||
b64ee10812 | |||
ee1ecf43db | |||
4d8db8eb7c | |||
1b13965200 | |||
38db8f7fe5 | |||
4ba8f893a6 |
@ -42,6 +42,7 @@ jobs:
|
||||
- ~/.npm
|
||||
- ~/.cache
|
||||
- node_modules
|
||||
- run: npm run lint
|
||||
- run: npm run test:coverage -- --browsers=<<parameters.browser>> || <<parameters.always-pass>>
|
||||
- store_test_results:
|
||||
path: dist/reports/tests/
|
||||
|
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -2,6 +2,7 @@
|
||||
|
||||
* [ ] Have you followed the guidelines in our [Contributing document](https://github.com/nasa/openmct/blob/master/CONTRIBUTING.md)?
|
||||
* [ ] Have you checked to ensure there aren't other open [Pull Requests](https://github.com/nasa/openmct/pulls) for the same update/change?
|
||||
* [ ] Is this change backwards compatible? For example, developers won't need to change how they are calling the API or how they've extended core plugins such as Tables or Plots.
|
||||
|
||||
### Author Checklist
|
||||
|
||||
|
@ -317,6 +317,7 @@ checklist).
|
||||
### Reviewer Checklist
|
||||
|
||||
* [ ] Changes appear to address issue?
|
||||
* [ ] Changes appear not to be breaking changes?
|
||||
* [ ] Appropriate unit tests included?
|
||||
* [ ] Code style and in-line documentation are appropriate?
|
||||
* [ ] Commit messages meet standards?
|
||||
|
@ -118,100 +118,6 @@ define([
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
'example.spectral-generator': {
|
||||
values: [
|
||||
{
|
||||
key: "name",
|
||||
name: "Name",
|
||||
format: "string"
|
||||
},
|
||||
{
|
||||
key: "utc",
|
||||
name: "Time",
|
||||
format: "utc",
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "wavelength",
|
||||
name: "Wavelength",
|
||||
unit: "Hz",
|
||||
formatString: '%0.2f',
|
||||
hints: {
|
||||
domain: 2,
|
||||
spectralAttribute: true
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "cos",
|
||||
name: "Cosine",
|
||||
unit: "deg",
|
||||
formatString: '%0.2f',
|
||||
hints: {
|
||||
range: 2,
|
||||
spectralAttribute: true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
'example.spectral-aggregate-generator': {
|
||||
values: [
|
||||
{
|
||||
key: "name",
|
||||
name: "Name",
|
||||
format: "string"
|
||||
},
|
||||
{
|
||||
key: "utc",
|
||||
name: "Time",
|
||||
format: "utc",
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "ch1",
|
||||
name: "Channel 1",
|
||||
format: "string",
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "ch2",
|
||||
name: "Channel 2",
|
||||
format: "string",
|
||||
hints: {
|
||||
range: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "ch3",
|
||||
name: "Channel 3",
|
||||
format: "string",
|
||||
hints: {
|
||||
range: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "ch4",
|
||||
name: "Channel 4",
|
||||
format: "string",
|
||||
hints: {
|
||||
range: 4
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "ch5",
|
||||
name: "Channel 5",
|
||||
format: "string",
|
||||
hints: {
|
||||
range: 5
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,86 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
|
||||
], function (
|
||||
|
||||
) {
|
||||
|
||||
function SpectralAggregateGeneratorProvider() {
|
||||
|
||||
}
|
||||
|
||||
function pointForTimestamp(timestamp, count, name) {
|
||||
return {
|
||||
name: name,
|
||||
utc: String(Math.floor(timestamp / count) * count),
|
||||
ch1: String(Math.floor(timestamp / count) % 1),
|
||||
ch2: String(Math.floor(timestamp / count) % 2),
|
||||
ch3: String(Math.floor(timestamp / count) % 3),
|
||||
ch4: String(Math.floor(timestamp / count) % 4),
|
||||
ch5: String(Math.floor(timestamp / count) % 5)
|
||||
};
|
||||
}
|
||||
|
||||
SpectralAggregateGeneratorProvider.prototype.supportsSubscribe = function (domainObject) {
|
||||
return domainObject.type === 'example.spectral-aggregate-generator';
|
||||
};
|
||||
|
||||
SpectralAggregateGeneratorProvider.prototype.subscribe = function (domainObject, callback) {
|
||||
var count = 5000;
|
||||
|
||||
var interval = setInterval(function () {
|
||||
var now = Date.now();
|
||||
var datum = pointForTimestamp(now, count, domainObject.name);
|
||||
callback(datum);
|
||||
}, count);
|
||||
|
||||
return function () {
|
||||
clearInterval(interval);
|
||||
};
|
||||
};
|
||||
|
||||
SpectralAggregateGeneratorProvider.prototype.supportsRequest = function (domainObject, options) {
|
||||
return domainObject.type === 'example.spectral-aggregate-generator';
|
||||
};
|
||||
|
||||
SpectralAggregateGeneratorProvider.prototype.request = function (domainObject, options) {
|
||||
var start = options.start;
|
||||
var end = Math.min(Date.now(), options.end); // no future values
|
||||
var count = 5000;
|
||||
if (options.strategy === 'latest' || options.size === 1) {
|
||||
start = end;
|
||||
}
|
||||
|
||||
var data = [];
|
||||
while (start <= end && data.length < 5000) {
|
||||
data.push(pointForTimestamp(start, count, domainObject.name));
|
||||
start += count;
|
||||
}
|
||||
|
||||
return Promise.resolve(data);
|
||||
};
|
||||
|
||||
return SpectralAggregateGeneratorProvider;
|
||||
|
||||
});
|
@ -1,102 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./WorkerInterface'
|
||||
], function (
|
||||
WorkerInterface
|
||||
) {
|
||||
|
||||
var REQUEST_DEFAULTS = {
|
||||
amplitude: 1,
|
||||
wavelength: 1,
|
||||
period: 10,
|
||||
offset: 0,
|
||||
dataRateInHz: 1,
|
||||
randomness: 0,
|
||||
phase: 0
|
||||
};
|
||||
|
||||
function SpectralGeneratorProvider() {
|
||||
this.workerInterface = new WorkerInterface();
|
||||
}
|
||||
|
||||
SpectralGeneratorProvider.prototype.canProvideTelemetry = function (domainObject) {
|
||||
return domainObject.type === 'example.spectral-generator';
|
||||
};
|
||||
|
||||
SpectralGeneratorProvider.prototype.supportsRequest =
|
||||
SpectralGeneratorProvider.prototype.supportsSubscribe =
|
||||
SpectralGeneratorProvider.prototype.canProvideTelemetry;
|
||||
|
||||
SpectralGeneratorProvider.prototype.makeWorkerRequest = function (domainObject, request = {}) {
|
||||
var props = [
|
||||
'amplitude',
|
||||
'wavelength',
|
||||
'period',
|
||||
'offset',
|
||||
'dataRateInHz',
|
||||
'phase',
|
||||
'randomness'
|
||||
];
|
||||
|
||||
var workerRequest = {};
|
||||
|
||||
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 (!Object.prototype.hasOwnProperty.call(workerRequest, prop)) {
|
||||
workerRequest[prop] = REQUEST_DEFAULTS[prop];
|
||||
}
|
||||
|
||||
workerRequest[prop] = Number(workerRequest[prop]);
|
||||
});
|
||||
|
||||
workerRequest.name = domainObject.name;
|
||||
|
||||
return workerRequest;
|
||||
};
|
||||
|
||||
SpectralGeneratorProvider.prototype.request = function (domainObject, request) {
|
||||
var workerRequest = this.makeWorkerRequest(domainObject, request);
|
||||
workerRequest.start = request.start;
|
||||
workerRequest.end = request.end;
|
||||
workerRequest.spectra = true;
|
||||
|
||||
return this.workerInterface.request(workerRequest);
|
||||
};
|
||||
|
||||
SpectralGeneratorProvider.prototype.subscribe = function (domainObject, callback) {
|
||||
var workerRequest = this.makeWorkerRequest(domainObject, {});
|
||||
workerRequest.spectra = true;
|
||||
|
||||
return this.workerInterface.subscribe(workerRequest, callback);
|
||||
};
|
||||
|
||||
return SpectralGeneratorProvider;
|
||||
});
|
@ -24,15 +24,11 @@ define([
|
||||
"./GeneratorProvider",
|
||||
"./SinewaveLimitProvider",
|
||||
"./StateGeneratorProvider",
|
||||
"./SpectralGeneratorProvider",
|
||||
"./SpectralAggregateGeneratorProvider",
|
||||
"./GeneratorMetadataProvider"
|
||||
], function (
|
||||
GeneratorProvider,
|
||||
SinewaveLimitProvider,
|
||||
StateGeneratorProvider,
|
||||
SpectralGeneratorProvider,
|
||||
SpectralAggregateGeneratorProvider,
|
||||
GeneratorMetadataProvider
|
||||
) {
|
||||
|
||||
@ -65,37 +61,6 @@ define([
|
||||
|
||||
openmct.telemetry.addProvider(new StateGeneratorProvider());
|
||||
|
||||
openmct.types.addType("example.spectral-generator", {
|
||||
name: "Spectral Generator",
|
||||
description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
|
||||
cssClass: "icon-generator-telemetry",
|
||||
creatable: true,
|
||||
initialize: function (object) {
|
||||
object.telemetry = {
|
||||
period: 10,
|
||||
amplitude: 1,
|
||||
wavelength: 1,
|
||||
frequency: 1,
|
||||
offset: 0,
|
||||
dataRateInHz: 1,
|
||||
phase: 0,
|
||||
randomness: 0
|
||||
};
|
||||
}
|
||||
});
|
||||
openmct.telemetry.addProvider(new SpectralGeneratorProvider());
|
||||
|
||||
openmct.types.addType("example.spectral-aggregate-generator", {
|
||||
name: "Spectral Aggregate Generator",
|
||||
description: "For development use. Generates example streaming telemetry data using a simple state algorithm.",
|
||||
cssClass: "icon-generator-telemetry",
|
||||
creatable: true,
|
||||
initialize: function (object) {
|
||||
object.telemetry = {};
|
||||
}
|
||||
});
|
||||
openmct.telemetry.addProvider(new SpectralAggregateGeneratorProvider());
|
||||
|
||||
openmct.types.addType("generator", {
|
||||
name: "Sine Wave Generator",
|
||||
description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.",
|
||||
|
@ -25,7 +25,7 @@
|
||||
const devMode = process.env.NODE_ENV !== 'production';
|
||||
const browsers = [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'ChromeHeadless'];
|
||||
const coverageEnabled = process.env.COVERAGE === 'true';
|
||||
const reporters = ['progress', 'html', 'junit'];
|
||||
const reporters = ['spec', 'html', 'junit'];
|
||||
|
||||
if (coverageEnabled) {
|
||||
reporters.push('coverage-istanbul');
|
||||
@ -60,7 +60,7 @@ module.exports = (config) => {
|
||||
client: {
|
||||
jasmine: {
|
||||
random: false,
|
||||
timeoutInterval: 30000
|
||||
timeoutInterval: 5000
|
||||
}
|
||||
},
|
||||
customLaunchers: {
|
||||
@ -88,11 +88,6 @@ module.exports = (config) => {
|
||||
outputFile: "test-results.xml",
|
||||
useBrowserName: false
|
||||
},
|
||||
browserConsoleLogOptions: {
|
||||
level: "error",
|
||||
format: "%b %T: %m",
|
||||
terminal: true
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
fixWebpackSourcePaths: true,
|
||||
dir: process.env.CIRCLE_ARTIFACTS
|
||||
@ -105,6 +100,15 @@ module.exports = (config) => {
|
||||
}
|
||||
}
|
||||
},
|
||||
specReporter: {
|
||||
maxLogLines: 5,
|
||||
suppressErrorSummary: true,
|
||||
suppressFailed: false,
|
||||
suppressPassed: false,
|
||||
suppressSkipped: true,
|
||||
showSpecTiming: true,
|
||||
failFast: false
|
||||
},
|
||||
preprocessors: {
|
||||
'indexTest.js': ['webpack', 'sourcemap']
|
||||
},
|
||||
|
@ -1,8 +1,9 @@
|
||||
{
|
||||
"name": "openmct",
|
||||
"version": "1.7.8-SNAPSHOT",
|
||||
"version": "1.8.0",
|
||||
"description": "The Open MCT core platform",
|
||||
"devDependencies": {
|
||||
"@braintree/sanitize-url": "^5.0.2",
|
||||
"angular": ">=1.8.0",
|
||||
"angular-route": "1.4.14",
|
||||
"babel-eslint": "10.0.3",
|
||||
@ -41,6 +42,7 @@
|
||||
"karma-jasmine": "4.0.1",
|
||||
"karma-junit-reporter": "2.0.1",
|
||||
"karma-sourcemap-loader": "0.3.8",
|
||||
"karma-spec-reporter": "0.0.32",
|
||||
"karma-webpack": "4.0.2",
|
||||
"location-bar": "^3.0.1",
|
||||
"lodash": "^4.17.12",
|
||||
@ -64,6 +66,7 @@
|
||||
"uuid": "^3.3.3",
|
||||
"v8-compile-cache": "^1.1.0",
|
||||
"vue": "2.5.6",
|
||||
"vue-eslint-parser": "7.11.0",
|
||||
"vue-loader": "^15.2.6",
|
||||
"vue-template-compiler": "2.5.6",
|
||||
"webpack": "^4.16.2",
|
||||
|
@ -34,9 +34,6 @@ define([
|
||||
"./src/policies/EditPersistableObjectsPolicy",
|
||||
"./src/representers/EditRepresenter",
|
||||
"./src/capabilities/EditorCapability",
|
||||
"./src/capabilities/TransactionCapabilityDecorator",
|
||||
"./src/services/TransactionManager",
|
||||
"./src/services/TransactionService",
|
||||
"./src/creation/CreateMenuController",
|
||||
"./src/creation/LocatorController",
|
||||
"./src/creation/CreationPolicy",
|
||||
@ -63,9 +60,6 @@ define([
|
||||
EditPersistableObjectsPolicy,
|
||||
EditRepresenter,
|
||||
EditorCapability,
|
||||
TransactionCapabilityDecorator,
|
||||
TransactionManager,
|
||||
TransactionService,
|
||||
CreateMenuController,
|
||||
LocatorController,
|
||||
CreationPolicy,
|
||||
@ -263,26 +257,6 @@ define([
|
||||
}
|
||||
],
|
||||
"components": [
|
||||
{
|
||||
"type": "decorator",
|
||||
"provides": "capabilityService",
|
||||
"implementation": TransactionCapabilityDecorator,
|
||||
"depends": [
|
||||
"$q",
|
||||
"transactionManager"
|
||||
],
|
||||
"priority": "fallback"
|
||||
},
|
||||
{
|
||||
"type": "provider",
|
||||
"provides": "transactionService",
|
||||
"implementation": TransactionService,
|
||||
"depends": [
|
||||
"$q",
|
||||
"$log",
|
||||
"cacheService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "CreateActionProvider",
|
||||
"provides": "actionService",
|
||||
@ -320,7 +294,6 @@ define([
|
||||
"description": "Provides transactional editing capabilities",
|
||||
"implementation": EditorCapability,
|
||||
"depends": [
|
||||
"transactionService",
|
||||
"openmct"
|
||||
]
|
||||
}
|
||||
@ -331,15 +304,6 @@ define([
|
||||
"template": locatorTemplate
|
||||
}
|
||||
],
|
||||
"services": [
|
||||
{
|
||||
"key": "transactionManager",
|
||||
"implementation": TransactionManager,
|
||||
"depends": [
|
||||
"transactionService"
|
||||
]
|
||||
}
|
||||
],
|
||||
"runs": [
|
||||
{
|
||||
depends: [
|
||||
|
@ -96,8 +96,7 @@ function (
|
||||
SaveAsAction.prototype.save = function () {
|
||||
var self = this,
|
||||
domainObject = this.domainObject,
|
||||
dialog = new SaveInProgressDialog(this.dialogService),
|
||||
toUndirty = [];
|
||||
dialog = new SaveInProgressDialog(this.dialogService);
|
||||
|
||||
function doWizardSave(parent) {
|
||||
var wizard = self.createWizard(parent);
|
||||
@ -132,11 +131,14 @@ function (
|
||||
return fetchObject(object.getModel().location);
|
||||
}
|
||||
|
||||
function saveObject(parent) {
|
||||
return self.openmct.editor.save().then(() => {
|
||||
// Force mutation for search indexing
|
||||
return parent;
|
||||
});
|
||||
function saveObject(object) {
|
||||
//persist the object, which adds it to the transaction and then call editor.save
|
||||
return object.getCapability("persistence").persist()
|
||||
.then(() => {
|
||||
return self.openmct.editor.save().then(() => {
|
||||
return object;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function addSavedObjectToParent(parent) {
|
||||
@ -150,17 +152,6 @@ function (
|
||||
});
|
||||
}
|
||||
|
||||
function undirty(object) {
|
||||
return object.getCapability('persistence').refresh();
|
||||
}
|
||||
|
||||
function undirtyOriginals(object) {
|
||||
return Promise.all(toUndirty.map(undirty))
|
||||
.then(() => {
|
||||
return object;
|
||||
});
|
||||
}
|
||||
|
||||
function indexForSearch(addedObject) {
|
||||
addedObject.useCapability('mutation', (model) => {
|
||||
return model;
|
||||
@ -187,10 +178,9 @@ function (
|
||||
return getParent(domainObject)
|
||||
.then(doWizardSave)
|
||||
.then(showBlockingDialog)
|
||||
.then(getParent)
|
||||
.then(saveObject)
|
||||
.then(getParent)
|
||||
.then(addSavedObjectToParent)
|
||||
.then(undirtyOriginals)
|
||||
.then((addedObject) => {
|
||||
return fetchObject(addedObject.getId());
|
||||
})
|
||||
|
@ -30,34 +30,17 @@ define(
|
||||
* Once initiated, any persist operations will be queued pending a
|
||||
* subsequent call to [.save()](@link #save) or [.finish()](@link
|
||||
* #finish).
|
||||
* @param transactionService
|
||||
* @param domainObject
|
||||
* @constructor
|
||||
*/
|
||||
function EditorCapability(
|
||||
transactionService,
|
||||
openmct,
|
||||
domainObject
|
||||
) {
|
||||
this.transactionService = transactionService;
|
||||
this.openmct = openmct;
|
||||
this.domainObject = domainObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate an editing session. This will start a transaction during
|
||||
* which any persist operations will be deferred until either save()
|
||||
* or finish() are called.
|
||||
*/
|
||||
EditorCapability.prototype.edit = function () {
|
||||
console.warn('DEPRECATED: cannot edit via edit capability, use openmct.editor instead.');
|
||||
|
||||
if (!this.openmct.editor.isEditing()) {
|
||||
this.openmct.editor.edit();
|
||||
this.domainObject.getCapability('status').set('editing', true);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines whether this object, or any of its ancestors are
|
||||
* currently being edited.
|
||||
@ -76,38 +59,6 @@ define(
|
||||
return this.openmct.editor.isEditing();
|
||||
};
|
||||
|
||||
/**
|
||||
* Save any unsaved changes from this editing session. This will
|
||||
* end the current transaction and continue with a new one.
|
||||
* @returns {*}
|
||||
*/
|
||||
EditorCapability.prototype.save = function () {
|
||||
console.warn('DEPRECATED: cannot save via edit capability, use openmct.editor instead.');
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
EditorCapability.prototype.invoke = EditorCapability.prototype.edit;
|
||||
|
||||
/**
|
||||
* Finish the current editing session. This will discard any pending
|
||||
* persist operations
|
||||
* @returns {*}
|
||||
*/
|
||||
EditorCapability.prototype.finish = function () {
|
||||
console.warn('DEPRECATED: cannot finish via edit capability, use openmct.editor instead.');
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {boolean} true if there have been any domain model
|
||||
* modifications since the last persist, false otherwise.
|
||||
*/
|
||||
EditorCapability.prototype.dirty = function () {
|
||||
return this.transactionService.size() > 0;
|
||||
};
|
||||
|
||||
return EditorCapability;
|
||||
}
|
||||
);
|
||||
|
@ -1,75 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
['./TransactionalPersistenceCapability'],
|
||||
function (TransactionalPersistenceCapability) {
|
||||
|
||||
/**
|
||||
* Wraps the [PersistenceCapability]{@link PersistenceCapability} with
|
||||
* transactional capabilities.
|
||||
* @param $q
|
||||
* @param transactionService
|
||||
* @param capabilityService
|
||||
* @see TransactionalPersistenceCapability
|
||||
* @constructor
|
||||
*/
|
||||
function TransactionCapabilityDecorator(
|
||||
$q,
|
||||
transactionService,
|
||||
capabilityService
|
||||
) {
|
||||
this.capabilityService = capabilityService;
|
||||
this.transactionService = transactionService;
|
||||
this.$q = $q;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorate PersistenceCapability to queue persistence calls when a
|
||||
* transaction is in progress.
|
||||
*/
|
||||
TransactionCapabilityDecorator.prototype.getCapabilities = function () {
|
||||
var self = this,
|
||||
capabilities = this.capabilityService.getCapabilities
|
||||
.apply(this.capabilityService, arguments),
|
||||
persistenceCapability = capabilities.persistence;
|
||||
|
||||
capabilities.persistence = function (domainObject) {
|
||||
var original =
|
||||
(typeof persistenceCapability === 'function')
|
||||
? persistenceCapability(domainObject)
|
||||
: persistenceCapability;
|
||||
|
||||
return new TransactionalPersistenceCapability(
|
||||
self.$q,
|
||||
self.transactionService,
|
||||
original,
|
||||
domainObject
|
||||
);
|
||||
};
|
||||
|
||||
return capabilities;
|
||||
};
|
||||
|
||||
return TransactionCapabilityDecorator;
|
||||
}
|
||||
);
|
@ -1,91 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
|
||||
/**
|
||||
* Wraps persistence capability to enable transactions. Transactions
|
||||
* will cause persist calls not to be invoked immediately, but
|
||||
* rather queued until [EditorCapability.save()]{@link EditorCapability#save}
|
||||
* or [EditorCapability.cancel()]{@link EditorCapability#cancel} are
|
||||
* called.
|
||||
* @memberof platform/commonUI/edit/capabilities
|
||||
* @param $q
|
||||
* @param transactionManager
|
||||
* @param persistenceCapability
|
||||
* @param domainObject
|
||||
* @constructor
|
||||
*/
|
||||
function TransactionalPersistenceCapability(
|
||||
$q,
|
||||
transactionManager,
|
||||
persistenceCapability,
|
||||
domainObject
|
||||
) {
|
||||
this.transactionManager = transactionManager;
|
||||
this.persistenceCapability = persistenceCapability;
|
||||
this.domainObject = domainObject;
|
||||
this.$q = $q;
|
||||
}
|
||||
|
||||
/**
|
||||
* The wrapped persist function. If a transaction is active, persist
|
||||
* will be queued until the transaction is committed or cancelled.
|
||||
* @returns {*}
|
||||
*/
|
||||
TransactionalPersistenceCapability.prototype.persist = function () {
|
||||
var wrappedPersistence = this.persistenceCapability;
|
||||
|
||||
if (this.transactionManager.isActive()) {
|
||||
this.transactionManager.addToTransaction(
|
||||
this.domainObject.getId(),
|
||||
wrappedPersistence.persist.bind(wrappedPersistence),
|
||||
wrappedPersistence.refresh.bind(wrappedPersistence)
|
||||
);
|
||||
|
||||
//Need to return a promise from this function
|
||||
return this.$q.when(true);
|
||||
} else {
|
||||
return this.persistenceCapability.persist();
|
||||
}
|
||||
};
|
||||
|
||||
TransactionalPersistenceCapability.prototype.refresh = function () {
|
||||
this.transactionManager
|
||||
.clearTransactionsFor(this.domainObject.getId());
|
||||
|
||||
return this.persistenceCapability.refresh();
|
||||
};
|
||||
|
||||
TransactionalPersistenceCapability.prototype.getSpace = function () {
|
||||
return this.persistenceCapability.getSpace();
|
||||
};
|
||||
|
||||
TransactionalPersistenceCapability.prototype.persisted = function () {
|
||||
return this.persistenceCapability.persisted();
|
||||
};
|
||||
|
||||
return TransactionalPersistenceCapability;
|
||||
}
|
||||
);
|
@ -1,99 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
define([], function () {
|
||||
/**
|
||||
* A Transaction represents a set of changes that are intended to
|
||||
* be kept or discarded as a unit.
|
||||
* @param $log Angular's `$log` service, for logging messages
|
||||
* @constructor
|
||||
* @memberof platform/commonUI/edit/services
|
||||
*/
|
||||
function Transaction($log) {
|
||||
this.$log = $log;
|
||||
this.callbacks = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a change to the current transaction, as expressed by functions
|
||||
* to either keep or discard the change.
|
||||
* @param {Function} commit called when the transaction is committed
|
||||
* @param {Function} cancel called when the transaction is cancelled
|
||||
* @returns {Function) a function which may be called to remove this
|
||||
* pair of callbacks from the transaction
|
||||
*/
|
||||
Transaction.prototype.add = function (commit, cancel) {
|
||||
var callback = {
|
||||
commit: commit,
|
||||
cancel: cancel
|
||||
};
|
||||
this.callbacks.push(callback);
|
||||
|
||||
return function () {
|
||||
this.callbacks = this.callbacks.filter(function (c) {
|
||||
return c !== callback;
|
||||
});
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the number of changes in the current transaction.
|
||||
* @returns {number} the size of the current transaction
|
||||
*/
|
||||
Transaction.prototype.size = function () {
|
||||
return this.callbacks.length;
|
||||
};
|
||||
|
||||
/**
|
||||
* Keep all changes associated with this transaction.
|
||||
* @method {platform/commonUI/edit/services.Transaction#commit}
|
||||
* @returns {Promise} a promise which will resolve when all callbacks
|
||||
* have been handled.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Discard all changes associated with this transaction.
|
||||
* @method {platform/commonUI/edit/services.Transaction#cancel}
|
||||
* @returns {Promise} a promise which will resolve when all callbacks
|
||||
* have been handled.
|
||||
*/
|
||||
|
||||
['commit', 'cancel'].forEach(function (method) {
|
||||
Transaction.prototype[method] = function () {
|
||||
var promises = [];
|
||||
var callback;
|
||||
|
||||
while (this.callbacks.length > 0) {
|
||||
callback = this.callbacks.shift();
|
||||
try {
|
||||
promises.push(callback[method]());
|
||||
} catch (e) {
|
||||
this.$log
|
||||
.error("Error trying to " + method + " transaction.");
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
};
|
||||
});
|
||||
|
||||
return Transaction;
|
||||
});
|
@ -1,119 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([], function () {
|
||||
/**
|
||||
* Manages transactions to support the TransactionalPersistenceCapability.
|
||||
* This assumes that all commit/cancel callbacks for a given domain
|
||||
* object are equivalent, and only need to be added once to any active
|
||||
* transaction. Violating this assumption may cause unexpected behavior.
|
||||
* @constructor
|
||||
* @memberof platform/commonUI/edit
|
||||
*/
|
||||
function TransactionManager(transactionService) {
|
||||
this.transactionService = transactionService;
|
||||
this.clearTransactionFns = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a transaction is currently active.
|
||||
* @returns {boolean} true if there is a transaction active
|
||||
*/
|
||||
TransactionManager.prototype.isActive = function () {
|
||||
return this.transactionService.isActive();
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if callbacks associated with this domain object have already
|
||||
* been added to the active transaction.
|
||||
* @private
|
||||
* @param {string} id the identifier of the domain object to check
|
||||
* @returns {boolean} true if callbacks have been added
|
||||
*/
|
||||
TransactionManager.prototype.isScheduled = function (id) {
|
||||
return Boolean(this.clearTransactionFns[id]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add callbacks associated with this domain object to the active
|
||||
* transaction. Both callbacks are expected to return promises that
|
||||
* resolve when their associated behavior is complete.
|
||||
*
|
||||
* If callbacks associated with this domain object have already been
|
||||
* added to the active transaction, this call will be ignored.
|
||||
*
|
||||
* @param {string} id the identifier of the associated domain object
|
||||
* @param {Function} onCommit behavior to invoke when committing transaction
|
||||
* @param {Function} onCancel behavior to invoke when cancelling transaction
|
||||
*/
|
||||
TransactionManager.prototype.addToTransaction = function (
|
||||
id,
|
||||
onCommit,
|
||||
onCancel
|
||||
) {
|
||||
var release = this.releaseClearFn.bind(this, id);
|
||||
|
||||
function chain(promiseFn, nextFn) {
|
||||
return function () {
|
||||
return promiseFn().then(nextFn);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear any existing persistence calls for object with given ID. This ensures only the most recent persistence
|
||||
* call is executed. This should prevent stale objects being persisted and overwriting fresh ones.
|
||||
*/
|
||||
if (this.isScheduled(id)) {
|
||||
this.clearTransactionsFor(id);
|
||||
}
|
||||
|
||||
this.clearTransactionFns[id] =
|
||||
this.transactionService.addToTransaction(
|
||||
chain(onCommit, release),
|
||||
chain(onCancel, release)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove any callbacks associated with this domain object from the
|
||||
* active transaction.
|
||||
* @param {string} id the identifier for the domain object
|
||||
*/
|
||||
TransactionManager.prototype.clearTransactionsFor = function (id) {
|
||||
if (this.isScheduled(id)) {
|
||||
this.clearTransactionFns[id]();
|
||||
this.releaseClearFn(id);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Release the cached "remove from transaction" function that has been
|
||||
* stored in association with this domain object.
|
||||
* @param {string} id the identifier for the domain object
|
||||
* @private
|
||||
*/
|
||||
TransactionManager.prototype.releaseClearFn = function (id) {
|
||||
delete this.clearTransactionFns[id];
|
||||
};
|
||||
|
||||
return TransactionManager;
|
||||
});
|
@ -1,138 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
define(
|
||||
['./Transaction', './NestedTransaction'],
|
||||
function (Transaction, NestedTransaction) {
|
||||
/**
|
||||
* Implements an application-wide transaction state. Once a
|
||||
* transaction is started, calls to
|
||||
* [PersistenceCapability.persist()]{@link PersistenceCapability#persist}
|
||||
* will be deferred until a subsequent call to
|
||||
* [TransactionService.commit]{@link TransactionService#commit} is made.
|
||||
*
|
||||
* @memberof platform/commonUI/edit/services
|
||||
* @param $q
|
||||
* @constructor
|
||||
*/
|
||||
function TransactionService($q, $log, cacheService) {
|
||||
this.$q = $q;
|
||||
this.$log = $log;
|
||||
this.cacheService = cacheService;
|
||||
this.transactions = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a transaction. While a transaction is active all calls to
|
||||
* [PersistenceCapability.persist](@link PersistenceCapability#persist)
|
||||
* will be queued until [commit]{@link #commit} or [cancel]{@link
|
||||
* #cancel} are called
|
||||
*/
|
||||
TransactionService.prototype.startTransaction = function () {
|
||||
var transaction = this.isActive()
|
||||
? new NestedTransaction(this.transactions[0])
|
||||
: new Transaction(this.$log);
|
||||
|
||||
this.transactions.push(transaction);
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {boolean} If true, indicates that a transaction is in progress
|
||||
*/
|
||||
TransactionService.prototype.isActive = function () {
|
||||
return this.transactions.length > 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds provided functions to a queue to be called on
|
||||
* [.commit()]{@link #commit} or
|
||||
* [.cancel()]{@link #commit}
|
||||
* @param onCommit A function to call on commit
|
||||
* @param onCancel A function to call on cancel
|
||||
*/
|
||||
TransactionService.prototype.addToTransaction = function (onCommit, onCancel) {
|
||||
if (this.isActive()) {
|
||||
return this.activeTransaction().add(onCommit, onCancel);
|
||||
} else {
|
||||
//Log error because this is a programming error if it occurs.
|
||||
this.$log.error("No transaction in progress");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the transaction at the top of the stack.
|
||||
* @private
|
||||
*/
|
||||
TransactionService.prototype.activeTransaction = function () {
|
||||
return this.transactions[this.transactions.length - 1];
|
||||
};
|
||||
|
||||
/**
|
||||
* All persist calls deferred since the beginning of the transaction
|
||||
* will be committed. If this is the last transaction, clears the
|
||||
* cache.
|
||||
*
|
||||
* @returns {Promise} resolved when all persist operations have
|
||||
* completed. Will reject if any commit operations fail
|
||||
*/
|
||||
TransactionService.prototype.commit = function () {
|
||||
var transaction = this.transactions.pop();
|
||||
if (!transaction) {
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
if (!this.isActive()) {
|
||||
return transaction.commit()
|
||||
.then(function (r) {
|
||||
this.cacheService.flush();
|
||||
|
||||
return r;
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
return transaction.commit();
|
||||
};
|
||||
|
||||
/**
|
||||
* Cancel the current transaction, replacing any dirty objects from
|
||||
* persistence. Not a true rollback, as it cannot be used to undo any
|
||||
* persist calls that were successful in the event one of a batch of
|
||||
* persists failing.
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
TransactionService.prototype.cancel = function () {
|
||||
var transaction = this.transactions.pop();
|
||||
|
||||
return transaction ? transaction.cancel() : Promise.reject();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the size (the number of commit/cancel callbacks) of
|
||||
* the active transaction.
|
||||
* @returns {number} size of the active transaction
|
||||
*/
|
||||
TransactionService.prototype.size = function () {
|
||||
return this.isActive() ? this.activeTransaction().size() : 0;
|
||||
};
|
||||
|
||||
return TransactionService;
|
||||
});
|
@ -1,192 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
["../../src/capabilities/EditorCapability"],
|
||||
function (EditorCapability) {
|
||||
|
||||
xdescribe("The editor capability", function () {
|
||||
var mockDomainObject,
|
||||
capabilities,
|
||||
mockParentObject,
|
||||
mockTransactionService,
|
||||
mockStatusCapability,
|
||||
mockParentStatus,
|
||||
mockContextCapability,
|
||||
capability;
|
||||
|
||||
function fastPromise(val) {
|
||||
return {
|
||||
then: function (callback) {
|
||||
return callback(val);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockDomainObject = jasmine.createSpyObj(
|
||||
"domainObject",
|
||||
["getId", "getModel", "hasCapability", "getCapability", "useCapability"]
|
||||
);
|
||||
mockParentObject = jasmine.createSpyObj(
|
||||
"domainObject",
|
||||
["getId", "getModel", "hasCapability", "getCapability", "useCapability"]
|
||||
);
|
||||
mockTransactionService = jasmine.createSpyObj(
|
||||
"transactionService",
|
||||
[
|
||||
"startTransaction",
|
||||
"size",
|
||||
"commit",
|
||||
"cancel"
|
||||
]
|
||||
);
|
||||
mockTransactionService.commit.and.returnValue(fastPromise());
|
||||
mockTransactionService.cancel.and.returnValue(fastPromise());
|
||||
mockTransactionService.isActive = jasmine.createSpy('isActive');
|
||||
|
||||
mockStatusCapability = jasmine.createSpyObj(
|
||||
"statusCapability",
|
||||
["get", "set"]
|
||||
);
|
||||
mockParentStatus = jasmine.createSpyObj(
|
||||
"statusCapability",
|
||||
["get", "set"]
|
||||
);
|
||||
mockContextCapability = jasmine.createSpyObj(
|
||||
"contextCapability",
|
||||
["getParent"]
|
||||
);
|
||||
mockContextCapability.getParent.and.returnValue(mockParentObject);
|
||||
|
||||
capabilities = {
|
||||
context: mockContextCapability,
|
||||
status: mockStatusCapability
|
||||
};
|
||||
|
||||
mockDomainObject.hasCapability.and.callFake(function (name) {
|
||||
return capabilities[name] !== undefined;
|
||||
});
|
||||
|
||||
mockDomainObject.getCapability.and.callFake(function (name) {
|
||||
return capabilities[name];
|
||||
});
|
||||
|
||||
mockParentObject.getCapability.and.returnValue(mockParentStatus);
|
||||
mockParentObject.hasCapability.and.returnValue(false);
|
||||
|
||||
capability = new EditorCapability(
|
||||
mockTransactionService,
|
||||
mockDomainObject
|
||||
);
|
||||
});
|
||||
|
||||
it("starts a transaction when edit is invoked", function () {
|
||||
capability.edit();
|
||||
expect(mockTransactionService.startTransaction).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("sets editing status on object", function () {
|
||||
capability.edit();
|
||||
expect(mockStatusCapability.set).toHaveBeenCalledWith("editing", true);
|
||||
});
|
||||
|
||||
it("uses editing status to determine editing context root", function () {
|
||||
capability.edit();
|
||||
mockStatusCapability.get.and.returnValue(false);
|
||||
expect(capability.isEditContextRoot()).toBe(false);
|
||||
mockStatusCapability.get.and.returnValue(true);
|
||||
expect(capability.isEditContextRoot()).toBe(true);
|
||||
});
|
||||
|
||||
it("inEditingContext returns true if parent object is being"
|
||||
+ " edited", function () {
|
||||
mockStatusCapability.get.and.returnValue(false);
|
||||
mockParentStatus.get.and.returnValue(false);
|
||||
expect(capability.inEditContext()).toBe(false);
|
||||
mockParentStatus.get.and.returnValue(true);
|
||||
expect(capability.inEditContext()).toBe(true);
|
||||
});
|
||||
|
||||
describe("save", function () {
|
||||
beforeEach(function () {
|
||||
capability.edit();
|
||||
capability.save();
|
||||
});
|
||||
it("commits the transaction", function () {
|
||||
expect(mockTransactionService.commit).toHaveBeenCalled();
|
||||
});
|
||||
it("begins a new transaction", function () {
|
||||
expect(mockTransactionService.startTransaction).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("finish", function () {
|
||||
beforeEach(function () {
|
||||
mockTransactionService.isActive.and.returnValue(true);
|
||||
capability.edit();
|
||||
capability.finish();
|
||||
});
|
||||
it("cancels the transaction", function () {
|
||||
expect(mockTransactionService.cancel).toHaveBeenCalled();
|
||||
});
|
||||
it("resets the edit state", function () {
|
||||
expect(mockStatusCapability.set).toHaveBeenCalledWith('editing', false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("finish", function () {
|
||||
beforeEach(function () {
|
||||
mockTransactionService.isActive.and.returnValue(false);
|
||||
capability.edit();
|
||||
});
|
||||
|
||||
it("does not cancel transaction when transaction is not active", function () {
|
||||
capability.finish();
|
||||
expect(mockTransactionService.cancel).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns a promise", function () {
|
||||
expect(capability.finish() instanceof Promise).toBe(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("dirty", function () {
|
||||
var model = {};
|
||||
|
||||
beforeEach(function () {
|
||||
mockDomainObject.getModel.and.returnValue(model);
|
||||
capability.edit();
|
||||
capability.finish();
|
||||
});
|
||||
it("returns true if the object has been modified since it"
|
||||
+ " was last persisted", function () {
|
||||
mockTransactionService.size.and.returnValue(0);
|
||||
expect(capability.dirty()).toBe(false);
|
||||
mockTransactionService.size.and.returnValue(1);
|
||||
expect(capability.dirty()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
@ -1,111 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
[
|
||||
"../../src/capabilities/TransactionalPersistenceCapability"
|
||||
],
|
||||
function (TransactionalPersistenceCapability) {
|
||||
|
||||
function fastPromise(val) {
|
||||
return {
|
||||
then: function (callback) {
|
||||
return callback(val);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
describe("The transactional persistence decorator", function () {
|
||||
var mockQ,
|
||||
mockTransactionManager,
|
||||
mockPersistence,
|
||||
mockDomainObject,
|
||||
testId,
|
||||
capability;
|
||||
|
||||
beforeEach(function () {
|
||||
testId = "test-id";
|
||||
|
||||
mockQ = jasmine.createSpyObj("$q", ["when"]);
|
||||
mockQ.when.and.callFake(function (val) {
|
||||
return fastPromise(val);
|
||||
});
|
||||
mockTransactionManager = jasmine.createSpyObj(
|
||||
"transactionService",
|
||||
["isActive", "addToTransaction", "clearTransactionsFor"]
|
||||
);
|
||||
mockPersistence = jasmine.createSpyObj(
|
||||
"persistenceCapability",
|
||||
["persist", "refresh", "getSpace"]
|
||||
);
|
||||
mockPersistence.persist.and.returnValue(fastPromise());
|
||||
mockPersistence.refresh.and.returnValue(fastPromise());
|
||||
|
||||
mockDomainObject = jasmine.createSpyObj(
|
||||
"domainObject",
|
||||
["getModel", "getId"]
|
||||
);
|
||||
mockDomainObject.getModel.and.returnValue({persisted: 1});
|
||||
mockDomainObject.getId.and.returnValue(testId);
|
||||
|
||||
capability = new TransactionalPersistenceCapability(
|
||||
mockQ,
|
||||
mockTransactionManager,
|
||||
mockPersistence,
|
||||
mockDomainObject
|
||||
);
|
||||
});
|
||||
|
||||
it("if no transaction is active, passes through to persistence"
|
||||
+ " provider", function () {
|
||||
mockTransactionManager.isActive.and.returnValue(false);
|
||||
capability.persist();
|
||||
expect(mockPersistence.persist).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("if transaction is active, persist and cancel calls are"
|
||||
+ " queued", function () {
|
||||
mockTransactionManager.isActive.and.returnValue(true);
|
||||
capability.persist();
|
||||
expect(mockTransactionManager.addToTransaction).toHaveBeenCalled();
|
||||
mockTransactionManager.addToTransaction.calls.mostRecent().args[1]();
|
||||
expect(mockPersistence.persist).toHaveBeenCalled();
|
||||
mockTransactionManager.addToTransaction.calls.mostRecent().args[2]();
|
||||
expect(mockPersistence.refresh).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("wraps getSpace", function () {
|
||||
mockPersistence.getSpace.and.returnValue('foo');
|
||||
expect(capability.getSpace()).toEqual('foo');
|
||||
});
|
||||
|
||||
it("clears transactions and delegates refresh calls", function () {
|
||||
capability.refresh();
|
||||
expect(mockTransactionManager.clearTransactionsFor)
|
||||
.toHaveBeenCalledWith(testId);
|
||||
expect(mockPersistence.refresh)
|
||||
.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
@ -1,75 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(["../../src/services/NestedTransaction"], function (NestedTransaction) {
|
||||
var TRANSACTION_METHODS = ['add', 'commit', 'cancel', 'size'];
|
||||
|
||||
describe("A NestedTransaction", function () {
|
||||
var mockTransaction,
|
||||
nestedTransaction;
|
||||
|
||||
beforeEach(function () {
|
||||
mockTransaction =
|
||||
jasmine.createSpyObj('transaction', TRANSACTION_METHODS);
|
||||
nestedTransaction = new NestedTransaction(mockTransaction);
|
||||
});
|
||||
|
||||
it("exposes a Transaction's interface", function () {
|
||||
TRANSACTION_METHODS.forEach(function (method) {
|
||||
expect(nestedTransaction[method])
|
||||
.toEqual(jasmine.any(Function));
|
||||
});
|
||||
});
|
||||
|
||||
describe("when callbacks are added", function () {
|
||||
var mockCommit,
|
||||
mockCancel;
|
||||
|
||||
beforeEach(function () {
|
||||
mockCommit = jasmine.createSpy('commit');
|
||||
mockCancel = jasmine.createSpy('cancel');
|
||||
nestedTransaction.add(mockCommit, mockCancel);
|
||||
});
|
||||
|
||||
it("does not interact with its parent transaction", function () {
|
||||
TRANSACTION_METHODS.forEach(function (method) {
|
||||
expect(mockTransaction[method])
|
||||
.not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("and the transaction is committed", function () {
|
||||
beforeEach(function () {
|
||||
nestedTransaction.commit();
|
||||
});
|
||||
|
||||
it("adds to its parent transaction", function () {
|
||||
expect(mockTransaction.add).toHaveBeenCalledWith(
|
||||
jasmine.any(Function),
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,141 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
["../../src/services/TransactionManager"],
|
||||
function (TransactionManager) {
|
||||
describe("TransactionManager", function () {
|
||||
var mockTransactionService,
|
||||
testId,
|
||||
mockOnCommit,
|
||||
mockOnCancel,
|
||||
mockRemoves,
|
||||
mockPromise,
|
||||
manager;
|
||||
|
||||
beforeEach(function () {
|
||||
mockRemoves = [];
|
||||
mockTransactionService = jasmine.createSpyObj(
|
||||
"transactionService",
|
||||
["addToTransaction", "isActive"]
|
||||
);
|
||||
mockOnCommit = jasmine.createSpy('commit');
|
||||
mockOnCancel = jasmine.createSpy('cancel');
|
||||
testId = 'test-id';
|
||||
mockPromise = jasmine.createSpyObj('promise', ['then']);
|
||||
|
||||
mockOnCommit.and.returnValue(mockPromise);
|
||||
mockOnCancel.and.returnValue(mockPromise);
|
||||
|
||||
mockTransactionService.addToTransaction.and.callFake(function () {
|
||||
var mockRemove =
|
||||
jasmine.createSpy('remove-' + mockRemoves.length);
|
||||
mockRemoves.push(mockRemove);
|
||||
|
||||
return mockRemove;
|
||||
});
|
||||
|
||||
manager = new TransactionManager(mockTransactionService);
|
||||
});
|
||||
|
||||
it("delegates isActive calls", function () {
|
||||
[false, true].forEach(function (state) {
|
||||
mockTransactionService.isActive.and.returnValue(state);
|
||||
expect(manager.isActive()).toBe(state);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when addToTransaction is called", function () {
|
||||
beforeEach(function () {
|
||||
manager.addToTransaction(
|
||||
testId,
|
||||
mockOnCommit,
|
||||
mockOnCancel
|
||||
);
|
||||
});
|
||||
|
||||
it("adds callbacks to the active transaction", function () {
|
||||
expect(mockTransactionService.addToTransaction)
|
||||
.toHaveBeenCalledWith(
|
||||
jasmine.any(Function),
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
it("invokes passed-in callbacks from its own callbacks", function () {
|
||||
expect(mockOnCommit).not.toHaveBeenCalled();
|
||||
mockTransactionService.addToTransaction
|
||||
.calls.mostRecent().args[0]();
|
||||
expect(mockOnCommit).toHaveBeenCalled();
|
||||
|
||||
expect(mockOnCancel).not.toHaveBeenCalled();
|
||||
mockTransactionService.addToTransaction
|
||||
.calls.mostRecent().args[1]();
|
||||
expect(mockOnCancel).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("Adds callbacks to transaction", function () {
|
||||
beforeEach(function () {
|
||||
spyOn(manager, 'clearTransactionsFor');
|
||||
manager.clearTransactionsFor.and.callThrough();
|
||||
});
|
||||
|
||||
it("and clears pending calls if same object", function () {
|
||||
manager.addToTransaction(
|
||||
testId,
|
||||
jasmine.createSpy(),
|
||||
jasmine.createSpy()
|
||||
);
|
||||
expect(manager.clearTransactionsFor).toHaveBeenCalledWith(testId);
|
||||
});
|
||||
|
||||
it("and does not clear pending calls if different object", function () {
|
||||
manager.addToTransaction(
|
||||
'other-id',
|
||||
jasmine.createSpy(),
|
||||
jasmine.createSpy()
|
||||
);
|
||||
expect(manager.clearTransactionsFor).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
expect(mockTransactionService.addToTransaction.calls.count()).toEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
it("does not remove callbacks from the transaction", function () {
|
||||
expect(mockRemoves[0]).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("and clearTransactionsFor is subsequently called", function () {
|
||||
beforeEach(function () {
|
||||
manager.clearTransactionsFor(testId);
|
||||
});
|
||||
|
||||
it("removes callbacks from the transaction", function () {
|
||||
expect(mockRemoves[0]).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
@ -1,139 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
["../../src/services/TransactionService"],
|
||||
function (TransactionService) {
|
||||
|
||||
describe("The Transaction Service", function () {
|
||||
var mockQ,
|
||||
mockLog,
|
||||
mockCacheService,
|
||||
transactionService;
|
||||
|
||||
function fastPromise(val) {
|
||||
return {
|
||||
then: function (callback) {
|
||||
return fastPromise(callback(val));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockQ = jasmine.createSpyObj("$q", ["all"]);
|
||||
mockCacheService = jasmine.createSpyObj("cacheService", ["flush"]);
|
||||
mockQ.all.and.returnValue(fastPromise());
|
||||
mockLog = jasmine.createSpyObj("$log", ["error"]);
|
||||
transactionService = new TransactionService(mockQ, mockLog, mockCacheService);
|
||||
});
|
||||
|
||||
it("isActive returns true if a transaction is in progress", function () {
|
||||
expect(transactionService.isActive()).toBe(false);
|
||||
transactionService.startTransaction();
|
||||
expect(transactionService.isActive()).toBe(true);
|
||||
});
|
||||
|
||||
it("addToTransaction queues onCommit and onCancel functions", function () {
|
||||
var onCommit = jasmine.createSpy('onCommit'),
|
||||
onCancel = jasmine.createSpy('onCancel');
|
||||
|
||||
transactionService.startTransaction();
|
||||
transactionService.addToTransaction(onCommit, onCancel);
|
||||
expect(transactionService.size()).toBe(1);
|
||||
});
|
||||
|
||||
it("size function returns size of commit and cancel queues", function () {
|
||||
var onCommit = jasmine.createSpy('onCommit'),
|
||||
onCancel = jasmine.createSpy('onCancel');
|
||||
|
||||
transactionService.startTransaction();
|
||||
transactionService.addToTransaction(onCommit, onCancel);
|
||||
transactionService.addToTransaction(onCommit, onCancel);
|
||||
transactionService.addToTransaction(onCommit, onCancel);
|
||||
expect(transactionService.size()).toBe(3);
|
||||
});
|
||||
|
||||
describe("commit", function () {
|
||||
var onCommits;
|
||||
|
||||
beforeEach(function () {
|
||||
onCommits = [0, 1, 2].map(function (val) {
|
||||
return jasmine.createSpy("onCommit" + val);
|
||||
});
|
||||
|
||||
transactionService.startTransaction();
|
||||
onCommits.forEach(transactionService.addToTransaction.bind(transactionService));
|
||||
});
|
||||
|
||||
it("commit calls all queued commit functions", function () {
|
||||
expect(transactionService.size()).toBe(3);
|
||||
|
||||
return transactionService.commit().then(() => {
|
||||
onCommits.forEach(function (spy) {
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("commit resets active state and clears queues", function () {
|
||||
return transactionService.commit().then(() => {
|
||||
expect(transactionService.isActive()).toBe(false);
|
||||
expect(transactionService.size()).toBe(0);
|
||||
expect(transactionService.size()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("cancel", function () {
|
||||
var onCancels;
|
||||
|
||||
beforeEach(function () {
|
||||
onCancels = [0, 1, 2].map(function (val) {
|
||||
return jasmine.createSpy("onCancel" + val);
|
||||
});
|
||||
|
||||
transactionService.startTransaction();
|
||||
onCancels.forEach(function (onCancel) {
|
||||
transactionService.addToTransaction(undefined, onCancel);
|
||||
});
|
||||
});
|
||||
|
||||
it("cancel calls all queued cancel functions", function () {
|
||||
expect(transactionService.size()).toBe(3);
|
||||
transactionService.cancel();
|
||||
onCancels.forEach(function (spy) {
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("cancel resets active state and clears queues", function () {
|
||||
transactionService.cancel();
|
||||
expect(transactionService.isActive()).toBe(false);
|
||||
expect(transactionService.size()).toBe(0);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
@ -1,109 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
["../../src/services/Transaction"],
|
||||
function (Transaction) {
|
||||
|
||||
describe("A Transaction", function () {
|
||||
var mockLog,
|
||||
transaction;
|
||||
|
||||
beforeEach(function () {
|
||||
mockLog = jasmine.createSpyObj(
|
||||
'$log',
|
||||
['warn', 'info', 'error', 'debug']
|
||||
);
|
||||
transaction = new Transaction(mockLog);
|
||||
});
|
||||
|
||||
it("initially has a size of zero", function () {
|
||||
expect(transaction.size()).toEqual(0);
|
||||
});
|
||||
|
||||
describe("when callbacks are added", function () {
|
||||
var mockCommit,
|
||||
mockCancel,
|
||||
remove;
|
||||
|
||||
beforeEach(function () {
|
||||
mockCommit = jasmine.createSpy('commit');
|
||||
mockCancel = jasmine.createSpy('cancel');
|
||||
remove = transaction.add(mockCommit, mockCancel);
|
||||
});
|
||||
|
||||
it("reports a new size", function () {
|
||||
expect(transaction.size()).toEqual(1);
|
||||
});
|
||||
|
||||
it("returns a function to remove those callbacks", function () {
|
||||
expect(remove).toEqual(jasmine.any(Function));
|
||||
remove();
|
||||
expect(transaction.size()).toEqual(0);
|
||||
});
|
||||
|
||||
describe("and the transaction is committed", function () {
|
||||
beforeEach(function () {
|
||||
transaction.commit();
|
||||
});
|
||||
|
||||
it("triggers the commit callback", function () {
|
||||
expect(mockCommit).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not trigger the cancel callback", function () {
|
||||
expect(mockCancel).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("and the transaction is cancelled", function () {
|
||||
beforeEach(function () {
|
||||
transaction.cancel();
|
||||
});
|
||||
|
||||
it("triggers the cancel callback", function () {
|
||||
expect(mockCancel).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not trigger the commit callback", function () {
|
||||
expect(mockCommit).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("and an exception is encountered during commit", function () {
|
||||
beforeEach(function () {
|
||||
mockCommit.and.callFake(function () {
|
||||
throw new Error("test error");
|
||||
});
|
||||
transaction.commit();
|
||||
});
|
||||
|
||||
it("logs an error", function () {
|
||||
expect(mockLog.error).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -25,15 +25,14 @@ define([
|
||||
], function (
|
||||
moment
|
||||
) {
|
||||
|
||||
var DATE_FORMAT = "YYYY-MM-DD HH:mm:ss.SSS",
|
||||
DATE_FORMATS = [
|
||||
DATE_FORMAT,
|
||||
DATE_FORMAT + "Z",
|
||||
"YYYY-MM-DD HH:mm:ss",
|
||||
"YYYY-MM-DD HH:mm",
|
||||
"YYYY-MM-DD"
|
||||
];
|
||||
const DATE_FORMAT = "YYYY-MM-DD HH:mm:ss.SSS";
|
||||
const DATE_FORMATS = [
|
||||
DATE_FORMAT,
|
||||
DATE_FORMAT + "Z",
|
||||
"YYYY-MM-DD HH:mm:ss",
|
||||
"YYYY-MM-DD HH:mm",
|
||||
"YYYY-MM-DD"
|
||||
];
|
||||
|
||||
/**
|
||||
* @typedef Scale
|
||||
@ -53,15 +52,27 @@ define([
|
||||
this.key = "utc";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} formatString
|
||||
* @returns the value of formatString if the value is a string type and exists in the DATE_FORMATS array; otherwise the DATE_FORMAT value.
|
||||
*/
|
||||
function validateFormatString(formatString) {
|
||||
return typeof formatString === 'string' && DATE_FORMATS.includes(formatString) ? formatString : DATE_FORMAT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} value The value to format.
|
||||
* @returns {string} the formatted date(s). If multiple values were requested, then an array of
|
||||
* @param {string} formatString The string format to format. Default "YYYY-MM-DD HH:mm:ss.SSS" + "Z"
|
||||
* @returns {string} the formatted date(s) according to the proper parameter of formatString or the default value of "YYYY-MM-DD HH:mm:ss.SSS" + "Z".
|
||||
* If multiple values were requested, then an array of
|
||||
* formatted values will be returned. Where a value could not be formatted, `undefined` will be returned at its position
|
||||
* in the array.
|
||||
*/
|
||||
UTCTimeFormat.prototype.format = function (value) {
|
||||
UTCTimeFormat.prototype.format = function (value, formatString) {
|
||||
if (value !== undefined) {
|
||||
return moment.utc(value).format(DATE_FORMAT) + "Z";
|
||||
const format = validateFormatString(formatString);
|
||||
|
||||
return moment.utc(value).format(format) + (formatString ? '' : 'Z');
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
|
@ -45,7 +45,6 @@ define([
|
||||
"./src/capabilities/MutationCapability",
|
||||
"./src/capabilities/DelegationCapability",
|
||||
"./src/capabilities/InstantiationCapability",
|
||||
"./src/runs/TransactingMutationListener",
|
||||
"./src/services/Now",
|
||||
"./src/services/Throttle",
|
||||
"./src/services/Topic",
|
||||
@ -75,7 +74,6 @@ define([
|
||||
MutationCapability,
|
||||
DelegationCapability,
|
||||
InstantiationCapability,
|
||||
TransactingMutationListener,
|
||||
Now,
|
||||
Throttle,
|
||||
Topic,
|
||||
@ -363,12 +361,6 @@ define([
|
||||
]
|
||||
}
|
||||
],
|
||||
"runs": [
|
||||
{
|
||||
"implementation": TransactingMutationListener,
|
||||
"depends": ["topic", "transactionService", "cacheService"]
|
||||
}
|
||||
],
|
||||
"constants": [
|
||||
{
|
||||
"key": "PERSISTENCE_SPACE",
|
||||
|
@ -1,55 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([], function () {
|
||||
|
||||
/**
|
||||
* Listens for mutation on domain objects and triggers persistence when
|
||||
* it occurs.
|
||||
* @param {Topic} topic the `topic` service; used to listen for mutation
|
||||
* @memberof platform/core
|
||||
*/
|
||||
function TransactingMutationListener(
|
||||
topic,
|
||||
transactionService,
|
||||
cacheService
|
||||
) {
|
||||
|
||||
function hasChanged(domainObject) {
|
||||
var model = domainObject.getModel();
|
||||
|
||||
return model.persisted === undefined || model.modified > model.persisted;
|
||||
}
|
||||
|
||||
var mutationTopic = topic('mutation');
|
||||
mutationTopic.listen(function (domainObject) {
|
||||
var persistence = domainObject.getCapability('persistence');
|
||||
cacheService.put(domainObject.getId(), domainObject.getModel());
|
||||
|
||||
if (hasChanged(domainObject)) {
|
||||
persistence.persist();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return TransactingMutationListener;
|
||||
});
|
@ -1,112 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
["../../src/runs/TransactingMutationListener"],
|
||||
function (TransactingMutationListener) {
|
||||
|
||||
describe("TransactingMutationListener", function () {
|
||||
var mockTopic,
|
||||
mockMutationTopic,
|
||||
mockCacheService,
|
||||
mockTransactionService,
|
||||
mockDomainObject,
|
||||
mockModel,
|
||||
mockPersistence;
|
||||
|
||||
beforeEach(function () {
|
||||
mockTopic = jasmine.createSpy('topic');
|
||||
mockMutationTopic =
|
||||
jasmine.createSpyObj('mutation', ['listen']);
|
||||
mockCacheService =
|
||||
jasmine.createSpyObj('cacheService', [
|
||||
'put'
|
||||
]);
|
||||
mockTransactionService =
|
||||
jasmine.createSpyObj('transactionService', [
|
||||
'isActive',
|
||||
'startTransaction',
|
||||
'commit'
|
||||
]);
|
||||
mockDomainObject = jasmine.createSpyObj(
|
||||
'domainObject',
|
||||
['getId', 'getCapability', 'getModel']
|
||||
);
|
||||
mockPersistence = jasmine.createSpyObj(
|
||||
'persistence',
|
||||
['persist', 'refresh', 'persisted']
|
||||
);
|
||||
|
||||
mockTopic.and.callFake(function (t) {
|
||||
expect(t).toBe('mutation');
|
||||
|
||||
return mockMutationTopic;
|
||||
});
|
||||
|
||||
mockDomainObject.getId.and.returnValue('mockId');
|
||||
mockDomainObject.getCapability.and.callFake(function (c) {
|
||||
expect(c).toBe('persistence');
|
||||
|
||||
return mockPersistence;
|
||||
});
|
||||
mockModel = {};
|
||||
mockDomainObject.getModel.and.returnValue(mockModel);
|
||||
|
||||
mockPersistence.persisted.and.returnValue(true);
|
||||
|
||||
return new TransactingMutationListener(
|
||||
mockTopic,
|
||||
mockTransactionService,
|
||||
mockCacheService
|
||||
);
|
||||
});
|
||||
|
||||
it("listens for mutation", function () {
|
||||
expect(mockMutationTopic.listen)
|
||||
.toHaveBeenCalledWith(jasmine.any(Function));
|
||||
});
|
||||
|
||||
it("calls persist if the model has changed", function () {
|
||||
mockModel.persisted = Date.now();
|
||||
|
||||
//Mark the model dirty by setting the mutated date later than the last persisted date.
|
||||
mockModel.modified = mockModel.persisted + 1;
|
||||
|
||||
mockMutationTopic.listen.calls.mostRecent()
|
||||
.args[0](mockDomainObject);
|
||||
|
||||
expect(mockPersistence.persist).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not call persist if the model has not changed", function () {
|
||||
mockModel.persisted = Date.now();
|
||||
|
||||
mockModel.modified = mockModel.persisted;
|
||||
|
||||
mockMutationTopic.listen.calls.mostRecent()
|
||||
.args[0](mockDomainObject);
|
||||
|
||||
expect(mockPersistence.persist).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
@ -44,9 +44,11 @@ define(
|
||||
setText(result.name);
|
||||
scope.ngModel[scope.field] = result;
|
||||
control.$setValidity("file-input", true);
|
||||
scope.$digest();
|
||||
}, function () {
|
||||
setText('Select File');
|
||||
control.$setValidity("file-input", false);
|
||||
scope.$digest();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -253,6 +253,8 @@ define([
|
||||
|
||||
this.status = new api.StatusAPI(this);
|
||||
|
||||
this.priority = api.PriorityAPI;
|
||||
|
||||
this.router = new ApplicationRouter(this);
|
||||
|
||||
this.branding = BrandingAPI.default;
|
||||
@ -263,6 +265,7 @@ define([
|
||||
// Plugins that are installed by default
|
||||
|
||||
this.install(this.plugins.Plot());
|
||||
this.install(this.plugins.Chart());
|
||||
this.install(this.plugins.TelemetryTable.default());
|
||||
this.install(PreviewPlugin.default());
|
||||
this.install(LegacyIndicatorsPlugin());
|
||||
|
@ -28,8 +28,6 @@ export default function LegacyActionAdapter(openmct, legacyActions) {
|
||||
return true;
|
||||
}
|
||||
|
||||
console.warn(`DEPRECATION WARNING: Action ${action.definition.key} in bundle ${action.bundle.path} is non-contextual and should be migrated.`);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,6 @@ define([
|
||||
'./capabilities/APICapabilityDecorator',
|
||||
'./policies/AdaptedViewPolicy',
|
||||
'./runs/AlternateCompositionInitializer',
|
||||
'./runs/TypeDeprecationChecker',
|
||||
'./runs/LegacyTelemetryProvider',
|
||||
'./runs/RegisterLegacyTypes',
|
||||
'./services/LegacyObjectAPIInterceptor',
|
||||
@ -46,7 +45,6 @@ define([
|
||||
APICapabilityDecorator,
|
||||
AdaptedViewPolicy,
|
||||
AlternateCompositionInitializer,
|
||||
TypeDeprecationChecker,
|
||||
LegacyTelemetryProvider,
|
||||
RegisterLegacyTypes,
|
||||
LegacyObjectAPIInterceptor,
|
||||
@ -135,10 +133,6 @@ define([
|
||||
}
|
||||
],
|
||||
runs: [
|
||||
{
|
||||
implementation: TypeDeprecationChecker,
|
||||
depends: ["types[]"]
|
||||
},
|
||||
{
|
||||
implementation: AlternateCompositionInitializer,
|
||||
depends: ["openmct"]
|
||||
|
@ -4,12 +4,6 @@ define([
|
||||
|
||||
) {
|
||||
function RegisterLegacyTypes(types, openmct) {
|
||||
types.forEach(function (legacyDefinition) {
|
||||
if (!openmct.types.get(legacyDefinition.key)) {
|
||||
console.warn(`DEPRECATION WARNING: Migrate type ${legacyDefinition.key} from ${legacyDefinition.bundle.path} to use the new Types API. Legacy type support will be removed soon.`);
|
||||
}
|
||||
});
|
||||
|
||||
openmct.types.importLegacyTypes(types);
|
||||
}
|
||||
|
||||
|
@ -1,46 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open openmct, Copyright (c) 2014-2021, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open openmct is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open openmct includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
|
||||
], function (
|
||||
|
||||
) {
|
||||
|
||||
function checkForDeprecatedFunctionality(typeDef) {
|
||||
if (Object.prototype.hasOwnProperty.call(typeDef, 'telemetry')) {
|
||||
console.warn(
|
||||
'DEPRECATION WARNING: Telemetry data on type '
|
||||
+ 'registrations will be deprecated in a future version, '
|
||||
+ 'please convert to a custom telemetry metadata provider '
|
||||
+ 'for type: ' + typeDef.key
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function TypeDeprecationChecker(types) {
|
||||
types.forEach(checkForDeprecatedFunctionality);
|
||||
}
|
||||
|
||||
return TypeDeprecationChecker;
|
||||
|
||||
});
|
@ -81,14 +81,8 @@ define([
|
||||
return models;
|
||||
}
|
||||
|
||||
return this.apiFetch(missingIds)
|
||||
.then(function (apiResults) {
|
||||
Object.keys(apiResults).forEach(function (k) {
|
||||
models[k] = apiResults[k];
|
||||
});
|
||||
|
||||
return models;
|
||||
});
|
||||
//Temporary fix for missing models - don't retry using this.apiFetch
|
||||
return models;
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
|
@ -15,8 +15,6 @@ define([
|
||||
};
|
||||
|
||||
function LegacyViewProvider(legacyView, openmct, convertToLegacyObject) {
|
||||
console.warn(`DEPRECATION WARNING: Migrate ${legacyView.key} from ${legacyView.bundle.path} to use the new View APIs. Legacy view support will be removed soon.`);
|
||||
|
||||
return {
|
||||
key: legacyView.key,
|
||||
name: legacyView.name,
|
||||
|
@ -4,7 +4,6 @@ define([
|
||||
|
||||
) {
|
||||
function TypeInspectorViewProvider(typeDefinition, openmct, convertToLegacyObject) {
|
||||
console.warn(`DEPRECATION WARNING: Migrate ${typeDefinition.key} from ${typeDefinition.bundle.path} to use the new Inspector View APIs. Legacy Inspector view support will be removed soon.`);
|
||||
let representation = openmct.$injector.get('representations[]')
|
||||
.filter((r) => r.key === typeDefinition.inspector)[0];
|
||||
|
||||
|
@ -34,7 +34,6 @@ export default class Editor extends EventEmitter {
|
||||
* Initiate an editing session. This will start a transaction during
|
||||
* which any persist operations will be deferred until either save()
|
||||
* or finish() are called.
|
||||
* @private
|
||||
*/
|
||||
edit() {
|
||||
if (this.editing === true) {
|
||||
@ -42,8 +41,8 @@ export default class Editor extends EventEmitter {
|
||||
}
|
||||
|
||||
this.editing = true;
|
||||
this.getTransactionService().startTransaction();
|
||||
this.emit('isEditing', true);
|
||||
this.openmct.objects.startTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -56,41 +55,36 @@ export default class Editor extends EventEmitter {
|
||||
/**
|
||||
* Save any unsaved changes from this editing session. This will
|
||||
* end the current transaction.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
save() {
|
||||
return this.getTransactionService().commit().then((result) => {
|
||||
this.editing = false;
|
||||
this.emit('isEditing', false);
|
||||
const transaction = this.openmct.objects.getActiveTransaction();
|
||||
|
||||
return result;
|
||||
}).catch((error) => {
|
||||
throw error;
|
||||
});
|
||||
return transaction.commit()
|
||||
.then(() => {
|
||||
this.editing = false;
|
||||
this.emit('isEditing', false);
|
||||
}).catch(error => {
|
||||
throw error;
|
||||
}).finally(() => {
|
||||
this.openmct.objects.endTransaction();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* End the currently active transaction and discard unsaved changes.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
cancel() {
|
||||
let cancelPromise = this.getTransactionService().cancel();
|
||||
this.editing = false;
|
||||
this.emit('isEditing', false);
|
||||
|
||||
return cancelPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
getTransactionService() {
|
||||
if (!this.transactionService) {
|
||||
this.transactionService = this.openmct.$injector.get('transactionService');
|
||||
}
|
||||
|
||||
return this.transactionService;
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.openmct.objects.getActiveTransaction();
|
||||
transaction.cancel()
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
.finally(() => {
|
||||
this.openmct.objects.endTransaction();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ class ActionsAPI extends EventEmitter {
|
||||
return actionsObject;
|
||||
}
|
||||
|
||||
_groupAndSortActions(actionsArray) {
|
||||
_groupAndSortActions(actionsArray = []) {
|
||||
if (!Array.isArray(actionsArray) && typeof actionsArray === 'object') {
|
||||
actionsArray = Object.keys(actionsArray).map(key => actionsArray[key]);
|
||||
}
|
||||
|
@ -31,7 +31,8 @@ define([
|
||||
'./Editor',
|
||||
'./menu/MenuAPI',
|
||||
'./actions/ActionsAPI',
|
||||
'./status/StatusAPI'
|
||||
'./status/StatusAPI',
|
||||
'./priority/PriorityAPI'
|
||||
], function (
|
||||
TimeAPI,
|
||||
ObjectAPI,
|
||||
@ -43,7 +44,8 @@ define([
|
||||
EditorAPI,
|
||||
MenuAPI,
|
||||
ActionsAPI,
|
||||
StatusAPI
|
||||
StatusAPI,
|
||||
PriorityAPI
|
||||
) {
|
||||
return {
|
||||
TimeAPI: TimeAPI.default,
|
||||
@ -56,6 +58,7 @@ define([
|
||||
EditorAPI: EditorAPI,
|
||||
MenuAPI: MenuAPI.default,
|
||||
ActionsAPI: ActionsAPI.default,
|
||||
StatusAPI: StatusAPI.default
|
||||
StatusAPI: StatusAPI.default,
|
||||
PriorityAPI: PriorityAPI.default
|
||||
};
|
||||
});
|
||||
|
@ -31,14 +31,22 @@ define([
|
||||
this.indicatorObjects = [];
|
||||
}
|
||||
|
||||
IndicatorAPI.prototype.getIndicatorObjectsByPriority = function () {
|
||||
const sortedIndicators = this.indicatorObjects.sort((a, b) => b.priority - a.priority);
|
||||
|
||||
return sortedIndicators;
|
||||
};
|
||||
|
||||
IndicatorAPI.prototype.simpleIndicator = function () {
|
||||
return new SimpleIndicator(this.openmct);
|
||||
};
|
||||
|
||||
/**
|
||||
* Accepts an indicator object, which is a simple object
|
||||
* with a single attribute, 'element' which has an HTMLElement
|
||||
* as its value.
|
||||
* with a two attributes: 'element' which has an HTMLElement
|
||||
* as its value, and 'priority' with an integer that specifies its order in the layout.
|
||||
* The lower the priority, the further to the right the element is placed.
|
||||
* If undefined, the priority will be assigned -1.
|
||||
*
|
||||
* We provide .simpleIndicator() as a convenience function
|
||||
* which will create a default Open MCT indicator that can
|
||||
@ -47,7 +55,7 @@ define([
|
||||
* and dynamic behavior.
|
||||
*
|
||||
* Eg.
|
||||
* var myIndicator = openmct.indicators.simpleIndicator();
|
||||
* const myIndicator = openmct.indicators.simpleIndicator();
|
||||
* openmct.indicators.add(myIndicator);
|
||||
*
|
||||
* myIndicator.text("Hello World!");
|
||||
@ -55,6 +63,10 @@ define([
|
||||
*
|
||||
*/
|
||||
IndicatorAPI.prototype.add = function (indicator) {
|
||||
if (!indicator.priority) {
|
||||
indicator.priority = this.openmct.priority.DEFAULT;
|
||||
}
|
||||
|
||||
this.indicatorObjects.push(indicator);
|
||||
};
|
||||
|
||||
|
@ -19,97 +19,64 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
import { createOpenMct, resetApplicationState } from '../../utils/testing';
|
||||
import SimpleIndicator from './SimpleIndicator';
|
||||
|
||||
define(
|
||||
[
|
||||
"../../MCT",
|
||||
"../../../platform/commonUI/general/src/directives/MCTIndicators"
|
||||
],
|
||||
function (
|
||||
MCT,
|
||||
MCTIndicators
|
||||
) {
|
||||
xdescribe("The Indicator API", function () {
|
||||
let openmct;
|
||||
let directive;
|
||||
let holderElement;
|
||||
describe("The Indicator API", () => {
|
||||
let openmct;
|
||||
|
||||
beforeEach(function () {
|
||||
openmct = new MCT();
|
||||
directive = new MCTIndicators(openmct);
|
||||
holderElement = document.createElement('div');
|
||||
});
|
||||
|
||||
describe("The simple indicator", function () {
|
||||
let simpleIndicator;
|
||||
|
||||
beforeEach(function () {
|
||||
simpleIndicator = openmct.indicators.simpleIndicator();
|
||||
openmct.indicators.add(simpleIndicator);
|
||||
renderIndicators();
|
||||
});
|
||||
|
||||
it("applies the set icon class", function () {
|
||||
simpleIndicator.iconClass('testIconClass');
|
||||
|
||||
expect(getIconElement().classList.contains('testIconClass')).toBe(true);
|
||||
|
||||
simpleIndicator.iconClass('anotherIconClass');
|
||||
expect(getIconElement().classList.contains('testIconClass')).toBe(false);
|
||||
expect(getIconElement().classList.contains('anotherIconClass')).toBe(true);
|
||||
});
|
||||
|
||||
it("applies the set status class", function () {
|
||||
simpleIndicator.statusClass('testStatusClass');
|
||||
|
||||
expect(getIconElement().classList.contains('testStatusClass')).toBe(true);
|
||||
simpleIndicator.statusClass('anotherStatusClass');
|
||||
expect(getIconElement().classList.contains('testStatusClass')).toBe(false);
|
||||
expect(getIconElement().classList.contains('anotherStatusClass')).toBe(true);
|
||||
});
|
||||
|
||||
it("displays the set text", function () {
|
||||
simpleIndicator.text('some test text');
|
||||
expect(getTextElement().textContent.trim()).toEqual('some test text');
|
||||
});
|
||||
|
||||
it("sets the indicator's title", function () {
|
||||
simpleIndicator.description('a test description');
|
||||
expect(getIndicatorElement().getAttribute('title')).toEqual('a test description');
|
||||
});
|
||||
|
||||
it("Hides indicator icon if no text is set", function () {
|
||||
simpleIndicator.text('');
|
||||
expect(getIndicatorElement().classList.contains('hidden')).toBe(true);
|
||||
});
|
||||
|
||||
function getIconElement() {
|
||||
return holderElement.querySelector('.ls-indicator');
|
||||
}
|
||||
|
||||
function getIndicatorElement() {
|
||||
return holderElement.querySelector('.ls-indicator');
|
||||
}
|
||||
|
||||
function getTextElement() {
|
||||
return holderElement.querySelector('.indicator-text');
|
||||
}
|
||||
});
|
||||
|
||||
it("Supports registration of a completely custom indicator", function () {
|
||||
const customIndicator = document.createElement('div');
|
||||
customIndicator.classList.add('customIndicator');
|
||||
customIndicator.textContent = 'A custom indicator';
|
||||
|
||||
openmct.indicators.add({element: customIndicator});
|
||||
renderIndicators();
|
||||
|
||||
expect(holderElement.querySelector('.customIndicator').textContent.trim()).toEqual('A custom indicator');
|
||||
});
|
||||
|
||||
function renderIndicators() {
|
||||
directive.link({}, holderElement);
|
||||
}
|
||||
|
||||
});
|
||||
beforeEach(() => {
|
||||
openmct = createOpenMct();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
return resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
function generateIndicator(className, label, priority) {
|
||||
const element = document.createElement('div');
|
||||
element.classList.add(className);
|
||||
const textNode = document.createTextNode(label);
|
||||
element.appendChild(textNode);
|
||||
const testIndicator = {
|
||||
element,
|
||||
priority
|
||||
};
|
||||
|
||||
return testIndicator;
|
||||
}
|
||||
|
||||
it("can register an indicator", () => {
|
||||
const testIndicator = generateIndicator('test-indicator', 'This is a test indicator', 2);
|
||||
openmct.indicators.add(testIndicator);
|
||||
expect(openmct.indicators.indicatorObjects).toBeDefined();
|
||||
// notifier indicator is installed by default
|
||||
expect(openmct.indicators.indicatorObjects.length).toBe(2);
|
||||
});
|
||||
|
||||
it("can order indicators based on priority", () => {
|
||||
const testIndicator1 = generateIndicator('test-indicator-1', 'This is a test indicator', openmct.priority.LOW);
|
||||
openmct.indicators.add(testIndicator1);
|
||||
|
||||
const testIndicator2 = generateIndicator('test-indicator-2', 'This is another test indicator', openmct.priority.DEFAULT);
|
||||
openmct.indicators.add(testIndicator2);
|
||||
|
||||
const testIndicator3 = generateIndicator('test-indicator-3', 'This is yet another test indicator', openmct.priority.LOW);
|
||||
openmct.indicators.add(testIndicator3);
|
||||
|
||||
const testIndicator4 = generateIndicator('test-indicator-4', 'This is yet another test indicator', openmct.priority.HIGH);
|
||||
openmct.indicators.add(testIndicator4);
|
||||
|
||||
expect(openmct.indicators.indicatorObjects.length).toBe(5);
|
||||
const indicatorObjectsByPriority = openmct.indicators.getIndicatorObjectsByPriority();
|
||||
expect(indicatorObjectsByPriority.length).toBe(5);
|
||||
expect(indicatorObjectsByPriority[2].priority).toBe(openmct.priority.DEFAULT);
|
||||
});
|
||||
|
||||
it("the simple indicator can be added", () => {
|
||||
const simpleIndicator = new SimpleIndicator(openmct);
|
||||
openmct.indicators.add(simpleIndicator);
|
||||
|
||||
expect(openmct.indicators.indicatorObjects.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
@ -27,6 +27,7 @@ define(['zepto', './res/indicator-template.html'],
|
||||
function SimpleIndicator(openmct) {
|
||||
this.openmct = openmct;
|
||||
this.element = $(indicatorTemplate)[0];
|
||||
this.priority = openmct.priority.DEFAULT;
|
||||
|
||||
this.textElement = this.element.querySelector('.js-indicator-text');
|
||||
|
||||
|
2
src/api/objects/ConflictError.js
Normal file
2
src/api/objects/ConflictError.js
Normal file
@ -0,0 +1,2 @@
|
||||
export default class ConflictError extends Error {
|
||||
}
|
@ -129,9 +129,7 @@ class MutableDomainObject {
|
||||
|
||||
mutable.$observe('$_synchronize_model', (updatedObject) => {
|
||||
let clone = JSON.parse(JSON.stringify(updatedObject));
|
||||
let deleted = _.difference(Object.keys(mutable), Object.keys(updatedObject));
|
||||
deleted.forEach((propertyName) => delete mutable[propertyName]);
|
||||
Object.assign(mutable, clone);
|
||||
utils.refresh(mutable, clone);
|
||||
});
|
||||
|
||||
return mutable;
|
||||
|
@ -26,6 +26,8 @@ import RootRegistry from './RootRegistry';
|
||||
import RootObjectProvider from './RootObjectProvider';
|
||||
import EventEmitter from 'EventEmitter';
|
||||
import InterceptorRegistry from './InterceptorRegistry';
|
||||
import Transaction from './Transaction';
|
||||
import ConflictError from './ConflictError';
|
||||
|
||||
/**
|
||||
* Utilities for loading, saving, and manipulating domain objects.
|
||||
@ -34,12 +36,13 @@ import InterceptorRegistry from './InterceptorRegistry';
|
||||
*/
|
||||
|
||||
function ObjectAPI(typeRegistry, openmct) {
|
||||
this.openmct = openmct;
|
||||
this.typeRegistry = typeRegistry;
|
||||
this.eventEmitter = new EventEmitter();
|
||||
this.providers = {};
|
||||
this.rootRegistry = new RootRegistry();
|
||||
this.injectIdentifierService = function () {
|
||||
this.identifierService = openmct.$injector.get("identifierService");
|
||||
this.identifierService = this.openmct.$injector.get("identifierService");
|
||||
};
|
||||
|
||||
this.rootProvider = new RootObjectProvider(this.rootRegistry);
|
||||
@ -47,6 +50,10 @@ function ObjectAPI(typeRegistry, openmct) {
|
||||
this.interceptorRegistry = new InterceptorRegistry();
|
||||
|
||||
this.SYNCHRONIZED_OBJECT_TYPES = ['notebook', 'plan'];
|
||||
|
||||
this.errors = {
|
||||
Conflict: ConflictError
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -86,6 +93,14 @@ ObjectAPI.prototype.getProvider = function (identifier) {
|
||||
return this.providers[namespace] || this.fallbackProvider;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get an active transaction instance
|
||||
* @returns {Transaction} a transaction object
|
||||
*/
|
||||
ObjectAPI.prototype.getActiveTransaction = function () {
|
||||
return this.transaction;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the root-level object.
|
||||
* @returns {Promise.<DomainObject>} a promise for the root object
|
||||
@ -181,7 +196,22 @@ ObjectAPI.prototype.get = function (identifier, abortSignal) {
|
||||
|
||||
let objectPromise = provider.get(identifier, abortSignal).then(result => {
|
||||
delete this.cache[keystring];
|
||||
|
||||
result = this.applyGetInterceptors(identifier, result);
|
||||
if (result.isMutable) {
|
||||
result.$refresh(result);
|
||||
} else {
|
||||
let mutableDomainObject = this._toMutable(result);
|
||||
mutableDomainObject.$refresh(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}).catch((result) => {
|
||||
console.warn(`Failed to retrieve ${keystring}:`, result);
|
||||
|
||||
delete this.cache[keystring];
|
||||
|
||||
result = this.applyGetInterceptors(identifier);
|
||||
|
||||
return result;
|
||||
});
|
||||
@ -285,6 +315,7 @@ ObjectAPI.prototype.isPersistable = function (idOrKeyString) {
|
||||
ObjectAPI.prototype.save = function (domainObject) {
|
||||
let provider = this.getProvider(domainObject.identifier);
|
||||
let savedResolve;
|
||||
let savedReject;
|
||||
let result;
|
||||
|
||||
if (!this.isPersistable(domainObject.identifier)) {
|
||||
@ -294,14 +325,22 @@ ObjectAPI.prototype.save = function (domainObject) {
|
||||
} else {
|
||||
const persistedTime = Date.now();
|
||||
if (domainObject.persisted === undefined) {
|
||||
result = new Promise((resolve) => {
|
||||
result = new Promise((resolve, reject) => {
|
||||
savedResolve = resolve;
|
||||
savedReject = reject;
|
||||
});
|
||||
domainObject.persisted = persistedTime;
|
||||
provider.create(domainObject).then((response) => {
|
||||
this.mutate(domainObject, 'persisted', persistedTime);
|
||||
savedResolve(response);
|
||||
});
|
||||
const newObjectPromise = provider.create(domainObject);
|
||||
if (newObjectPromise) {
|
||||
newObjectPromise.then(response => {
|
||||
this.mutate(domainObject, 'persisted', persistedTime);
|
||||
savedResolve(response);
|
||||
}).catch((error) => {
|
||||
savedReject(error);
|
||||
});
|
||||
} else {
|
||||
result = Promise.reject(`[ObjectAPI][save] Object provider returned ${newObjectPromise} when creating new object.`);
|
||||
}
|
||||
} else {
|
||||
domainObject.persisted = persistedTime;
|
||||
this.mutate(domainObject, 'persisted', persistedTime);
|
||||
@ -312,6 +351,24 @@ ObjectAPI.prototype.save = function (domainObject) {
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* After entering into edit mode, creates a new instance of Transaction to keep track of changes in Objects
|
||||
*/
|
||||
ObjectAPI.prototype.startTransaction = function () {
|
||||
if (this.isTransactionActive()) {
|
||||
throw new Error("Unable to start new Transaction: Previous Transaction is active");
|
||||
}
|
||||
|
||||
this.transaction = new Transaction(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear instance of Transaction
|
||||
*/
|
||||
ObjectAPI.prototype.endTransaction = function () {
|
||||
this.transaction = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a root-level object.
|
||||
* @param {module:openmct.ObjectAPI~Identifier|function} an array of
|
||||
@ -401,6 +458,12 @@ ObjectAPI.prototype.mutate = function (domainObject, path, value) {
|
||||
//Destroy temporary mutable object
|
||||
this.destroyMutable(mutableDomainObject);
|
||||
}
|
||||
|
||||
if (this.isTransactionActive()) {
|
||||
this.transaction.add(domainObject);
|
||||
} else {
|
||||
this.save(domainObject);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -437,6 +500,23 @@ ObjectAPI.prototype._toMutable = function (object) {
|
||||
return mutableObject;
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates a domain object based on its latest persisted state. Note that this will mutate the provided object.
|
||||
* @param {module:openmct.DomainObject} domainObject an object to refresh from its persistence store
|
||||
* @returns {Promise} the provided object, updated to reflect the latest persisted state of the object.
|
||||
*/
|
||||
ObjectAPI.prototype.refresh = async function (domainObject) {
|
||||
const refreshedObject = await this.get(domainObject.identifier);
|
||||
|
||||
if (domainObject.isMutable) {
|
||||
domainObject.$refresh(refreshedObject);
|
||||
} else {
|
||||
utils.refresh(domainObject, refreshedObject);
|
||||
}
|
||||
|
||||
return domainObject;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param module:openmct.ObjectAPI~Identifier identifier An object identifier
|
||||
* @returns {boolean} true if the object can be mutated, otherwise returns false
|
||||
@ -513,6 +593,10 @@ ObjectAPI.prototype.isObjectPathToALink = function (domainObject, objectPath) {
|
||||
&& domainObject.location !== this.makeKeyString(objectPath[1].identifier);
|
||||
};
|
||||
|
||||
ObjectAPI.prototype.isTransactionActive = function () {
|
||||
return Boolean(this.transaction && this.openmct.editor.isEditing());
|
||||
};
|
||||
|
||||
/**
|
||||
* Uniquely identifies a domain object.
|
||||
*
|
||||
|
@ -26,6 +26,10 @@ describe("The Object API", () => {
|
||||
|
||||
openmct.$injector.get.and.returnValue(mockIdentifierService);
|
||||
objectAPI = new ObjectAPI(typeRegistry, openmct);
|
||||
|
||||
openmct.editor = {};
|
||||
openmct.editor.isEditing = () => false;
|
||||
|
||||
mockDomainObject = {
|
||||
identifier: {
|
||||
namespace: TEST_NAMESPACE,
|
||||
@ -223,6 +227,28 @@ describe("The Object API", () => {
|
||||
expect(testObject.name).toBe(MUTATED_NAME);
|
||||
});
|
||||
|
||||
it('Provides a way of refreshing an object from the persistence store', () => {
|
||||
const modifiedTestObject = JSON.parse(JSON.stringify(testObject));
|
||||
const OTHER_ATTRIBUTE_VALUE = 'Modified value';
|
||||
const NEW_ATTRIBUTE_VALUE = 'A new attribute';
|
||||
modifiedTestObject.otherAttribute = OTHER_ATTRIBUTE_VALUE;
|
||||
modifiedTestObject.newAttribute = NEW_ATTRIBUTE_VALUE;
|
||||
delete modifiedTestObject.objectAttribute;
|
||||
|
||||
spyOn(objectAPI, 'get');
|
||||
objectAPI.get.and.returnValue(Promise.resolve(modifiedTestObject));
|
||||
|
||||
expect(objectAPI.get).not.toHaveBeenCalled();
|
||||
|
||||
return objectAPI.refresh(testObject).then(() => {
|
||||
expect(objectAPI.get).toHaveBeenCalledWith(testObject.identifier);
|
||||
|
||||
expect(testObject.otherAttribute).toEqual(OTHER_ATTRIBUTE_VALUE);
|
||||
expect(testObject.newAttribute).toEqual(NEW_ATTRIBUTE_VALUE);
|
||||
expect(testObject.objectAttribute).not.toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe ('uses a MutableDomainObject', () => {
|
||||
it('and retains properties of original object ', function () {
|
||||
expect(hasOwnProperty(mutable, 'identifier')).toBe(true);
|
||||
|
@ -20,35 +20,52 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
[
|
||||
"../../src/capabilities/TransactionalPersistenceCapability",
|
||||
"../../src/capabilities/TransactionCapabilityDecorator"
|
||||
],
|
||||
function (TransactionalPersistenceCapability, TransactionCapabilityDecorator) {
|
||||
export default class Transaction {
|
||||
constructor(objectAPI) {
|
||||
this.dirtyObjects = new Set();
|
||||
this.objectAPI = objectAPI;
|
||||
}
|
||||
|
||||
describe("The transaction capability decorator", function () {
|
||||
var mockQ,
|
||||
mockTransactionService,
|
||||
mockCapabilityService,
|
||||
provider;
|
||||
add(object) {
|
||||
this.dirtyObjects.add(object);
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockQ = {};
|
||||
mockTransactionService = {};
|
||||
mockCapabilityService = jasmine.createSpyObj("capabilityService", ["getCapabilities"]);
|
||||
mockCapabilityService.getCapabilities.and.returnValue({
|
||||
persistence: function () {}
|
||||
cancel() {
|
||||
return this._clear();
|
||||
}
|
||||
|
||||
commit() {
|
||||
const promiseArray = [];
|
||||
const save = this.objectAPI.save.bind(this.objectAPI);
|
||||
this.dirtyObjects.forEach(object => {
|
||||
promiseArray.push(this.createDirtyObjectPromise(object, save));
|
||||
});
|
||||
|
||||
return Promise.all(promiseArray);
|
||||
}
|
||||
|
||||
createDirtyObjectPromise(object, action) {
|
||||
return new Promise((resolve, reject) => {
|
||||
action(object)
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
.finally(() => {
|
||||
this.dirtyObjects.delete(object);
|
||||
});
|
||||
|
||||
provider = new TransactionCapabilityDecorator(mockQ, mockTransactionService, mockCapabilityService);
|
||||
|
||||
});
|
||||
it("decorates the persistence capability", function () {
|
||||
var capabilities = provider.getCapabilities();
|
||||
expect(capabilities.persistence({}) instanceof TransactionalPersistenceCapability).toBe(true);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
start() {
|
||||
this.dirtyObjects = new Set();
|
||||
}
|
||||
|
||||
_clear() {
|
||||
const promiseArray = [];
|
||||
const refresh = this.objectAPI.refresh.bind(this.objectAPI);
|
||||
this.dirtyObjects.forEach(object => {
|
||||
promiseArray.push(this.createDirtyObjectPromise(object, refresh));
|
||||
});
|
||||
|
||||
return Promise.all(promiseArray);
|
||||
}
|
||||
}
|
@ -165,12 +165,19 @@ define([
|
||||
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);
|
||||
}
|
||||
|
||||
return {
|
||||
toOldFormat: toOldFormat,
|
||||
toNewFormat: toNewFormat,
|
||||
makeKeyString: makeKeyString,
|
||||
parseKeyString: parseKeyString,
|
||||
equals: objectEquals,
|
||||
identifierEquals: identifierEquals
|
||||
identifierEquals: identifierEquals,
|
||||
refresh: refresh
|
||||
};
|
||||
});
|
||||
|
@ -19,31 +19,10 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
define(['./Transaction'], function (Transaction) {
|
||||
/**
|
||||
* A nested transaction is a transaction which takes place in the context
|
||||
* of a larger parent transaction. It becomes part of the parent
|
||||
* transaction when (and only when) committed.
|
||||
* @param parent
|
||||
* @constructor
|
||||
* @extends {platform/commonUI/edit/services.Transaction}
|
||||
* @memberof platform/commonUI/edit/services
|
||||
*/
|
||||
function NestedTransaction(parent) {
|
||||
this.parent = parent;
|
||||
Transaction.call(this, parent.$log);
|
||||
}
|
||||
|
||||
NestedTransaction.prototype = Object.create(Transaction.prototype);
|
||||
|
||||
NestedTransaction.prototype.commit = function () {
|
||||
this.parent.add(
|
||||
Transaction.prototype.commit.bind(this),
|
||||
Transaction.prototype.cancel.bind(this)
|
||||
);
|
||||
|
||||
return Promise.resolve(true);
|
||||
};
|
||||
|
||||
return NestedTransaction;
|
||||
const PRIORITIES = Object.freeze({
|
||||
HIGH: 1000,
|
||||
DEFAULT: 0,
|
||||
LOW: -1000
|
||||
});
|
||||
export default PRIORITIES;
|
@ -180,12 +180,6 @@ define([
|
||||
* @memberof module:openmct.TelemetryAPI~TelemetryProvider#
|
||||
*/
|
||||
TelemetryAPI.prototype.canProvideTelemetry = function (domainObject) {
|
||||
console.warn(
|
||||
'DEPRECATION WARNING: openmct.telemetry.canProvideTelemetry '
|
||||
+ 'will not be supported in future versions of Open MCT. Please '
|
||||
+ 'use openmct.telemetry.isTelemetryObject instead.'
|
||||
);
|
||||
|
||||
return Boolean(this.findSubscriptionProvider(domainObject))
|
||||
|| Boolean(this.findRequestProvider(domainObject));
|
||||
};
|
||||
@ -483,6 +477,10 @@ define([
|
||||
* @returns {Object<String, {TelemetryValueFormatter}>}
|
||||
*/
|
||||
TelemetryAPI.prototype.getFormatMap = function (metadata) {
|
||||
if (!metadata) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!this.formatMapCache.has(metadata)) {
|
||||
const formatMap = metadata.values().reduce(function (map, valueMetadata) {
|
||||
map[valueMetadata.key] = this.getValueFormatter(valueMetadata);
|
||||
|
@ -49,7 +49,6 @@ export class TelemetryCollection extends EventEmitter {
|
||||
this.parseTime = undefined;
|
||||
this.metadata = this.openmct.telemetry.getMetadata(domainObject);
|
||||
this.unsubscribe = undefined;
|
||||
this.historicalProvider = undefined;
|
||||
this.options = options;
|
||||
this.pageState = undefined;
|
||||
this.lastBounds = undefined;
|
||||
@ -65,13 +64,13 @@ export class TelemetryCollection extends EventEmitter {
|
||||
this._error(ERRORS.LOADED);
|
||||
}
|
||||
|
||||
this._timeSystem(this.openmct.time.timeSystem());
|
||||
this._setTimeSystem(this.openmct.time.timeSystem());
|
||||
this.lastBounds = this.openmct.time.bounds();
|
||||
|
||||
this._watchBounds();
|
||||
this._watchTimeSystem();
|
||||
|
||||
this._initiateHistoricalRequests();
|
||||
this._requestHistoricalTelemetry();
|
||||
this._initiateSubscriptionTelemetry();
|
||||
|
||||
this.loaded = true;
|
||||
@ -103,36 +102,35 @@ export class TelemetryCollection extends EventEmitter {
|
||||
return this.boundedTelemetry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the telemetry collection for historical requests,
|
||||
* this uses the "standardizeRequestOptions" from Telemetry API
|
||||
* @private
|
||||
*/
|
||||
_initiateHistoricalRequests() {
|
||||
this.openmct.telemetry.standardizeRequestOptions(this.options);
|
||||
this.historicalProvider = this.openmct.telemetry.
|
||||
findRequestProvider(this.domainObject, this.options);
|
||||
|
||||
this._requestHistoricalTelemetry();
|
||||
}
|
||||
|
||||
/**
|
||||
* If a historical provider exists, then historical requests will be made
|
||||
* @private
|
||||
*/
|
||||
async _requestHistoricalTelemetry() {
|
||||
if (!this.historicalProvider) {
|
||||
let options = { ...this.options };
|
||||
let historicalProvider;
|
||||
|
||||
this.openmct.telemetry.standardizeRequestOptions(options);
|
||||
historicalProvider = this.openmct.telemetry.
|
||||
findRequestProvider(this.domainObject, options);
|
||||
|
||||
if (!historicalProvider) {
|
||||
return;
|
||||
}
|
||||
|
||||
let historicalData;
|
||||
|
||||
this.options.onPartialResponse = this._processNewTelemetry.bind(this);
|
||||
options.onPartialResponse = this._processNewTelemetry.bind(this);
|
||||
|
||||
try {
|
||||
if (this.requestAbort) {
|
||||
this.requestAbort.abort();
|
||||
}
|
||||
|
||||
this.requestAbort = new AbortController();
|
||||
this.options.signal = this.requestAbort.signal;
|
||||
historicalData = await this.historicalProvider.request(this.domainObject, this.options);
|
||||
options.signal = this.requestAbort.signal;
|
||||
this.emit('requestStarted');
|
||||
historicalData = await historicalProvider.request(this.domainObject, options);
|
||||
} catch (error) {
|
||||
if (error.name !== 'AbortError') {
|
||||
console.error('Error requesting telemetry data...');
|
||||
@ -140,6 +138,7 @@ export class TelemetryCollection extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
this.emit('requestEnded');
|
||||
this.requestAbort = undefined;
|
||||
|
||||
this._processNewTelemetry(historicalData);
|
||||
@ -173,6 +172,10 @@ export class TelemetryCollection extends EventEmitter {
|
||||
* @private
|
||||
*/
|
||||
_processNewTelemetry(telemetryData) {
|
||||
if (telemetryData === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data = Array.isArray(telemetryData) ? telemetryData : [telemetryData];
|
||||
let parsedValue;
|
||||
let beforeStartOfBounds;
|
||||
@ -199,9 +202,10 @@ export class TelemetryCollection extends EventEmitter {
|
||||
|
||||
if (endIndex > startIndex) {
|
||||
let potentialDupes = this.boundedTelemetry.slice(startIndex, endIndex);
|
||||
|
||||
isDuplicate = potentialDupes.some(_.isEqual.bind(undefined, datum));
|
||||
}
|
||||
} else if (startIndex === this.boundedTelemetry.length) {
|
||||
isDuplicate = _.isEqual(datum, this.boundedTelemetry[this.boundedTelemetry.length - 1]);
|
||||
}
|
||||
|
||||
if (!isDuplicate) {
|
||||
@ -317,7 +321,7 @@ export class TelemetryCollection extends EventEmitter {
|
||||
* Time System
|
||||
* @private
|
||||
*/
|
||||
_timeSystem(timeSystem) {
|
||||
_setTimeSystem(timeSystem) {
|
||||
let domains = this.metadata.valuesForHints(['domain']);
|
||||
let domain = domains.find((d) => d.key === timeSystem.key);
|
||||
|
||||
@ -333,7 +337,10 @@ export class TelemetryCollection extends EventEmitter {
|
||||
this.parseTime = (datum) => {
|
||||
return valueFormatter.parse(datum);
|
||||
};
|
||||
}
|
||||
|
||||
_setTimeSystemAndFetchData(timeSystem) {
|
||||
this._setTimeSystem(timeSystem);
|
||||
this._reset();
|
||||
}
|
||||
|
||||
@ -370,19 +377,19 @@ export class TelemetryCollection extends EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* adds the _timeSystem callback to the 'timeSystem' timeAPI listener
|
||||
* adds the _setTimeSystemAndFetchData callback to the 'timeSystem' timeAPI listener
|
||||
* @private
|
||||
*/
|
||||
_watchTimeSystem() {
|
||||
this.openmct.time.on('timeSystem', this._timeSystem, this);
|
||||
this.openmct.time.on('timeSystem', this._setTimeSystemAndFetchData, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* removes the _timeSystem callback from the 'timeSystem' timeAPI listener
|
||||
* removes the _setTimeSystemAndFetchData callback from the 'timeSystem' timeAPI listener
|
||||
* @private
|
||||
*/
|
||||
_unwatchTimeSystem() {
|
||||
this.openmct.time.off('timeSystem', this._timeSystem, this);
|
||||
this.openmct.time.off('timeSystem', this._setTimeSystemAndFetchData, this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -31,11 +31,6 @@ define([
|
||||
valueMetadata.hints = valueMetadata.hints || {};
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'x')) {
|
||||
console.warn(
|
||||
'DEPRECATION WARNING: `x` hints should be replaced with '
|
||||
+ '`domain` hints moving forward. '
|
||||
+ 'https://github.com/nasa/openmct/issues/1546'
|
||||
);
|
||||
if (!Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'domain')) {
|
||||
valueMetadata.hints.domain = valueMetadata.hints.x;
|
||||
}
|
||||
@ -44,11 +39,6 @@ define([
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'y')) {
|
||||
console.warn(
|
||||
'DEPRECATION WARNING: `y` hints should be replaced with '
|
||||
+ '`range` hints moving forward. '
|
||||
+ 'https://github.com/nasa/openmct/issues/1546'
|
||||
);
|
||||
if (!Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'range')) {
|
||||
valueMetadata.hints.range = valueMetadata.hints.y;
|
||||
}
|
||||
|
@ -172,7 +172,7 @@ class TimeAPI extends GlobalTimeContext {
|
||||
* @memberof module:openmct.TimeAPI#
|
||||
* @method getContextForView
|
||||
*/
|
||||
getContextForView(objectPath) {
|
||||
getContextForView(objectPath = []) {
|
||||
let timeContext = this;
|
||||
|
||||
objectPath.forEach(item => {
|
||||
|
@ -63,12 +63,6 @@ define(['./Type'], function (Type) {
|
||||
*/
|
||||
TypeRegistry.prototype.standardizeType = function (typeDef) {
|
||||
if (Object.prototype.hasOwnProperty.call(typeDef, 'label')) {
|
||||
console.warn(
|
||||
'DEPRECATION WARNING typeDef: ' + typeDef.label + '. '
|
||||
+ '`label` is deprecated in type definitions. Please use '
|
||||
+ '`name` instead. This will cause errors in a future version '
|
||||
+ 'of Open MCT. For more information, see '
|
||||
+ 'https://github.com/nasa/openmct/issues/1568');
|
||||
if (!typeDef.name) {
|
||||
typeDef.name = typeDef.label;
|
||||
}
|
||||
|
@ -41,7 +41,6 @@ const DEFAULTS = [
|
||||
'platform/forms',
|
||||
'platform/identity',
|
||||
'platform/persistence/aggregator',
|
||||
'platform/persistence/queue',
|
||||
'platform/policy',
|
||||
'platform/entanglement',
|
||||
'platform/search',
|
||||
|
@ -32,7 +32,7 @@ describe('the plugin', function () {
|
||||
let openmct;
|
||||
let composition;
|
||||
|
||||
beforeEach((done) => {
|
||||
beforeEach(() => {
|
||||
|
||||
openmct = createOpenMct();
|
||||
|
||||
@ -47,11 +47,6 @@ describe('the plugin', function () {
|
||||
}
|
||||
}));
|
||||
|
||||
openmct.on('start', done);
|
||||
openmct.startHeadless();
|
||||
|
||||
composition = openmct.composition.get({identifier});
|
||||
|
||||
spyOn(couchPlugin.couchProvider, 'getObjectsByFilter').and.returnValue(Promise.resolve([
|
||||
{
|
||||
identifier: {
|
||||
@ -66,6 +61,19 @@ describe('the plugin', function () {
|
||||
}
|
||||
}
|
||||
]));
|
||||
|
||||
spyOn(couchPlugin.couchProvider, "get").and.callFake((id) => {
|
||||
return Promise.resolve({
|
||||
identifier: id
|
||||
});
|
||||
});
|
||||
|
||||
return new Promise((resolve) => {
|
||||
openmct.once('start', resolve);
|
||||
openmct.startHeadless();
|
||||
}).then(() => {
|
||||
composition = openmct.composition.get({identifier});
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -96,11 +96,11 @@ export default {
|
||||
|
||||
this.timestampKey = this.openmct.time.timeSystem().key;
|
||||
|
||||
this.valueMetadata = this
|
||||
this.valueMetadata = this.metadata ? this
|
||||
.metadata
|
||||
.valuesForHints(['range'])[0];
|
||||
.valuesForHints(['range'])[0] : undefined;
|
||||
|
||||
this.valueKey = this.valueMetadata.key;
|
||||
this.valueKey = this.valueMetadata ? this.valueMetadata.key : undefined;
|
||||
|
||||
this.unsubscribe = this.openmct
|
||||
.telemetry
|
||||
@ -151,7 +151,10 @@ export default {
|
||||
size: 1,
|
||||
strategy: 'latest'
|
||||
})
|
||||
.then((array) => this.updateValues(array[array.length - 1]));
|
||||
.then((array) => this.updateValues(array[array.length - 1]))
|
||||
.catch((error) => {
|
||||
console.warn('Error fetching data', error);
|
||||
});
|
||||
},
|
||||
updateBounds(bounds, isTick) {
|
||||
this.bounds = bounds;
|
||||
|
@ -73,8 +73,9 @@ export default {
|
||||
hasUnits() {
|
||||
let itemsWithUnits = this.items.filter((item) => {
|
||||
let metadata = this.openmct.telemetry.getMetadata(item.domainObject);
|
||||
const valueMetadatas = metadata ? metadata.valueMetadatas : [];
|
||||
|
||||
return this.metadataHasUnits(metadata.valueMetadatas);
|
||||
return this.metadataHasUnits(valueMetadatas);
|
||||
|
||||
});
|
||||
|
||||
|
@ -23,30 +23,26 @@
|
||||
import { BAR_GRAPH_KEY } from './BarGraphConstants';
|
||||
|
||||
export default function BarGraphCompositionPolicy(openmct) {
|
||||
function hasAggregateDomainAndRange(metadata) {
|
||||
function hasRange(metadata) {
|
||||
const rangeValues = metadata.valuesForHints(['range']);
|
||||
|
||||
return rangeValues.length > 0;
|
||||
}
|
||||
|
||||
function hasBarGraphTelemetry(domainObject) {
|
||||
if (!Object.prototype.hasOwnProperty.call(domainObject, 'telemetry')) {
|
||||
if (!openmct.telemetry.isTelemetryObject(domainObject)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let metadata = openmct.telemetry.getMetadata(domainObject);
|
||||
|
||||
return metadata.values().length > 0 && hasAggregateDomainAndRange(metadata);
|
||||
}
|
||||
|
||||
function hasNoChildren(parentObject) {
|
||||
return parentObject.composition && parentObject.composition.length < 1;
|
||||
return metadata.values().length > 0 && hasRange(metadata);
|
||||
}
|
||||
|
||||
return {
|
||||
allow: function (parent, child) {
|
||||
if ((parent.type === BAR_GRAPH_KEY)
|
||||
&& ((child.type !== 'telemetry.plot.overlay') && (hasBarGraphTelemetry(child) === false))
|
||||
&& (hasBarGraphTelemetry(child) === false)
|
||||
) {
|
||||
return false;
|
||||
}
|
@ -1,5 +1,3 @@
|
||||
export const BAR_GRAPH_VIEW = 'bar-graph.view';
|
||||
export const BAR_GRAPH_KEY = 'telemetry.plot.bar-graph';
|
||||
export const BAR_GRAPH_INSPECTOR_KEY = 'telemetry.plot.bar-graph.inspector';
|
||||
export const SUBSCRIBE = 'subscribe';
|
||||
export const UNSUBSCRIBE = 'unsubscribe';
|
@ -12,6 +12,7 @@
|
||||
</div>
|
||||
<div ref="plot"
|
||||
class="c-bar-chart"
|
||||
@plotly_relayout="zoom"
|
||||
></div>
|
||||
<div v-if="false"
|
||||
ref="localControl"
|
||||
@ -28,8 +29,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Plotly from 'plotly.js-basic-dist';
|
||||
import { SUBSCRIBE, UNSUBSCRIBE } from './BarGraphConstants';
|
||||
import Plotly from 'plotly-basic';
|
||||
|
||||
const MULTI_AXES_X_PADDING_PERCENT = {
|
||||
LEFT: 8,
|
||||
@ -79,8 +79,6 @@ export default {
|
||||
this.registerListeners();
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$refs.plot.removeAllListeners();
|
||||
|
||||
if (this.plotResizeObserver) {
|
||||
this.plotResizeObserver.unobserve(this.$refs.plotWrapper);
|
||||
clearTimeout(this.resizeTimer);
|
||||
@ -139,8 +137,8 @@ export default {
|
||||
getYAxisMeta() {
|
||||
const yAxisMeta = {};
|
||||
|
||||
this.data.forEach(d => {
|
||||
const yAxisMetadata = d.yAxisMetadata;
|
||||
this.data.forEach(datum => {
|
||||
const yAxisMetadata = datum.yAxisMetadata;
|
||||
const range = '1';
|
||||
const side = 'left';
|
||||
const name = '';
|
||||
@ -203,8 +201,6 @@ export default {
|
||||
return yaxis;
|
||||
},
|
||||
registerListeners() {
|
||||
this.$refs.plot.on('plotly_relayout', this.zoom);
|
||||
|
||||
this.removeBarColorListener = this.openmct.objects.observe(
|
||||
this.domainObject,
|
||||
'configuration.barStyles',
|
||||
@ -226,17 +222,17 @@ export default {
|
||||
this.updatePlot();
|
||||
|
||||
this.isZoomed = false;
|
||||
this.$emit(SUBSCRIBE);
|
||||
this.$emit('subscribe');
|
||||
},
|
||||
barColorChanged() {
|
||||
const colors = [];
|
||||
const indices = [];
|
||||
this.data.forEach((item, index) => {
|
||||
const key = item.key;
|
||||
const color = this.domainObject.configuration.barStyles[key] && this.domainObject.configuration.barStyles[key].color;
|
||||
const colorExists = this.domainObject.configuration.barStyles.series[key] && this.domainObject.configuration.barStyles.series[key].color;
|
||||
indices.push(index);
|
||||
if (color) {
|
||||
colors.push();
|
||||
if (colorExists) {
|
||||
colors.push(this.domainObject.configuration.barStyles.series[key].color);
|
||||
} else {
|
||||
colors.push(item.marker.color);
|
||||
}
|
||||
@ -285,7 +281,7 @@ export default {
|
||||
}
|
||||
|
||||
this.isZoomed = true;
|
||||
this.$emit(UNSUBSCRIBE);
|
||||
this.$emit('unsubscribe');
|
||||
}
|
||||
}
|
||||
};
|
@ -25,33 +25,31 @@
|
||||
class="c-plot c-bar-chart-view"
|
||||
:data="trace"
|
||||
:plot-axis-title="plotAxisTitle"
|
||||
@subscribe="subscribeToAll"
|
||||
@unsubscribe="removeAllSubscriptions"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as SPECTRAL_AGGREGATE from './BarGraphConstants';
|
||||
import ColorPalette from '../lib/ColorPalette';
|
||||
import BarGraph from './BarGraphPlot.vue';
|
||||
import Color from "@/plugins/plot/lib/Color";
|
||||
import _ from 'lodash';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BarGraph
|
||||
},
|
||||
inject: ['openmct', 'domainObject'],
|
||||
inject: ['openmct', 'domainObject', 'path'],
|
||||
data() {
|
||||
this.telemetryObjects = {};
|
||||
this.telemetryObjectFormats = {};
|
||||
this.subscriptions = [];
|
||||
this.composition = {};
|
||||
|
||||
return {
|
||||
composition: {},
|
||||
currentDomainObject: this.domainObject,
|
||||
subscriptions: [],
|
||||
telemetryObjects: {},
|
||||
trace: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
activeClock() {
|
||||
return this.openmct.time.activeClock;
|
||||
},
|
||||
plotAxisTitle() {
|
||||
const { xAxisMetadata = {}, yAxisMetadata = {} } = this.trace[0] || {};
|
||||
const xAxisUnit = xAxisMetadata.units ? `(${xAxisMetadata.units})` : '';
|
||||
@ -64,24 +62,14 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.colorPalette = new ColorPalette();
|
||||
this.loadComposition();
|
||||
|
||||
this.openmct.time.on('bounds', this.refreshData);
|
||||
this.openmct.time.on('clock', this.clockChanged);
|
||||
|
||||
this.$refs.barGraph.$on(SPECTRAL_AGGREGATE.SUBSCRIBE, this.subscribeToAll);
|
||||
this.$refs.barGraph.$on(SPECTRAL_AGGREGATE.UNSUBSCRIBE, this.removeAllSubscriptions);
|
||||
|
||||
this.unobserve = this.openmct.objects.observe(this.currentDomainObject, '*', this.updateDomainObject);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$refs.barGraph.$off();
|
||||
this.openmct.time.off('bounds', this.refreshData);
|
||||
this.openmct.time.off('clock', this.clockChanged);
|
||||
|
||||
this.removeAllSubscriptions();
|
||||
this.unobserve();
|
||||
|
||||
if (!this.composition) {
|
||||
return;
|
||||
@ -92,35 +80,34 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
addTelemetryObject(telemetryObject) {
|
||||
// grab information we need from the added telmetry object
|
||||
const key = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
||||
this.telemetryObjects[key] = telemetryObject;
|
||||
const metadata = this.openmct.telemetry.getMetadata(telemetryObject);
|
||||
this.telemetryObjectFormats[key] = this.openmct.telemetry.getFormatMap(metadata);
|
||||
const telemetryObjectPath = [telemetryObject, ...this.path];
|
||||
const telemetryIsAlias = this.openmct.objects.isObjectPathToALink(telemetryObject, telemetryObjectPath);
|
||||
|
||||
if (!this.domainObject.configuration.barStyles) {
|
||||
this.domainObject.configuration.barStyles = {};
|
||||
// make an update object that's a clone of the existing styles object so we preserve existing choices
|
||||
let stylesUpdate = {};
|
||||
if (this.domainObject.configuration.barStyles.series[key]) {
|
||||
stylesUpdate = _.clone(this.domainObject.configuration.barStyles.series[key]);
|
||||
}
|
||||
|
||||
// check to see if we've set a bar color
|
||||
if (!this.domainObject.configuration.barStyles[key] || !this.domainObject.configuration.barStyles[key].color) {
|
||||
const color = this.colorPalette.getNextColor().asHexString();
|
||||
this.domainObject.configuration.barStyles[key] = {
|
||||
name: telemetryObject.name,
|
||||
color
|
||||
};
|
||||
stylesUpdate.name = telemetryObject.name;
|
||||
stylesUpdate.type = telemetryObject.type;
|
||||
stylesUpdate.isAlias = telemetryIsAlias;
|
||||
|
||||
// if something has changed, mutate and notify listeners
|
||||
if (!_.isEqual(stylesUpdate, this.domainObject.configuration.barStyles.series[key])) {
|
||||
this.openmct.objects.mutate(
|
||||
this.domainObject,
|
||||
`configuration.barStyles[${this.key}]`,
|
||||
this.domainObject.configuration.barStyles[key]
|
||||
`configuration.barStyles.series["${key}"]`,
|
||||
stylesUpdate
|
||||
);
|
||||
} else {
|
||||
let color = this.domainObject.configuration.barStyles[key].color;
|
||||
if (!(color instanceof Color)) {
|
||||
color = Color.fromHexString(color);
|
||||
}
|
||||
|
||||
this.colorPalette.remove(color);
|
||||
}
|
||||
|
||||
this.telemetryObjects[key] = telemetryObject;
|
||||
|
||||
// ask for the current telemetry data, then subcribe for changes
|
||||
this.requestDataFor(telemetryObject);
|
||||
this.subscribeToObject(telemetryObject);
|
||||
},
|
||||
@ -144,12 +131,12 @@ export default {
|
||||
|
||||
this.trace = isInTrace ? newTrace : newTrace.concat([trace]);
|
||||
},
|
||||
clockChanged() {
|
||||
this.removeAllSubscriptions();
|
||||
this.subscribeToAll();
|
||||
},
|
||||
getAxisMetadata(telemetryObject) {
|
||||
const metadata = this.openmct.telemetry.getMetadata(telemetryObject);
|
||||
if (!metadata) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const yAxisMetadata = metadata.valuesForHints(['range'])[0];
|
||||
//Exclude 'name' and 'time' based metadata specifically, from the x-Axis values by using range hints only
|
||||
const xAxisMetadata = metadata.valuesForHints(['range']);
|
||||
@ -159,21 +146,19 @@ export default {
|
||||
yAxisMetadata
|
||||
};
|
||||
},
|
||||
getOptions(telemetryObject) {
|
||||
getOptions() {
|
||||
const { start, end } = this.openmct.time.bounds();
|
||||
|
||||
return {
|
||||
end,
|
||||
start,
|
||||
startTime: null,
|
||||
spectra: true
|
||||
start
|
||||
};
|
||||
},
|
||||
loadComposition() {
|
||||
this.composition = this.openmct.composition.get(this.currentDomainObject);
|
||||
this.composition = this.openmct.composition.get(this.domainObject);
|
||||
|
||||
if (!this.composition) {
|
||||
this.addTelemetryObject(this.currentDomainObject);
|
||||
this.addTelemetryObject(this.domainObject);
|
||||
|
||||
return;
|
||||
}
|
||||
@ -202,21 +187,34 @@ export default {
|
||||
removeTelemetryObject(identifier) {
|
||||
const key = this.openmct.objects.makeKeyString(identifier);
|
||||
delete this.telemetryObjects[key];
|
||||
if (this.domainObject.configuration.barStyles[key]) {
|
||||
delete this.domainObject.configuration.barStyles[key];
|
||||
if (this.telemetryObjectFormats && this.telemetryObjectFormats[key]) {
|
||||
delete this.telemetryObjectFormats[key];
|
||||
}
|
||||
|
||||
if (this.domainObject.configuration.barStyles.series[key]) {
|
||||
delete this.domainObject.configuration.barStyles.series[key];
|
||||
this.openmct.objects.mutate(
|
||||
this.domainObject,
|
||||
`configuration.barStyles.series["${key}"]`,
|
||||
undefined
|
||||
);
|
||||
}
|
||||
|
||||
this.removeSubscription(key);
|
||||
|
||||
this.trace = this.trace.filter(t => t.key !== key);
|
||||
},
|
||||
processData(telemetryObject, data, axisMetadata) {
|
||||
addDataToGraph(telemetryObject, data, axisMetadata) {
|
||||
const key = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
||||
|
||||
if (data.message) {
|
||||
this.openmct.notifications.alert(data.message);
|
||||
}
|
||||
|
||||
if (!this.isDataInTimeRange(data, key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let xValues = [];
|
||||
let yValues = [];
|
||||
|
||||
@ -224,10 +222,10 @@ export default {
|
||||
axisMetadata.xAxisMetadata.forEach((metadata) => {
|
||||
xValues.push(metadata.name);
|
||||
if (data[metadata.key]) {
|
||||
//TODO: Format the data?
|
||||
yValues.push(data[metadata.key]);
|
||||
const formattedValue = this.format(key, metadata.key, data);
|
||||
yValues.push(formattedValue);
|
||||
} else {
|
||||
yValues.push('');
|
||||
yValues.push(null);
|
||||
}
|
||||
});
|
||||
|
||||
@ -241,20 +239,43 @@ export default {
|
||||
yAxisMetadata: axisMetadata.yAxisMetadata,
|
||||
type: 'bar',
|
||||
marker: {
|
||||
color: this.domainObject.configuration.barStyles[key].color
|
||||
color: this.domainObject.configuration.barStyles.series[key].color
|
||||
},
|
||||
hoverinfo: 'skip'
|
||||
};
|
||||
|
||||
this.addTrace(trace, key);
|
||||
},
|
||||
isDataInTimeRange(datum, key) {
|
||||
const timeSystemKey = this.openmct.time.timeSystem().key;
|
||||
let currentTimestamp = this.parse(key, timeSystemKey, datum);
|
||||
|
||||
return currentTimestamp && this.openmct.time.bounds().end >= currentTimestamp;
|
||||
},
|
||||
format(telemetryObjectKey, metadataKey, data) {
|
||||
const formats = this.telemetryObjectFormats[telemetryObjectKey];
|
||||
|
||||
return formats[metadataKey].format(data);
|
||||
},
|
||||
parse(telemetryObjectKey, metadataKey, datum) {
|
||||
if (!datum) {
|
||||
return;
|
||||
}
|
||||
|
||||
const formats = this.telemetryObjectFormats[telemetryObjectKey];
|
||||
|
||||
return formats[metadataKey].parse(datum);
|
||||
},
|
||||
requestDataFor(telemetryObject) {
|
||||
const axisMetadata = this.getAxisMetadata(telemetryObject);
|
||||
this.openmct.telemetry.request(telemetryObject, this.getOptions(telemetryObject))
|
||||
this.openmct.telemetry.request(telemetryObject)
|
||||
.then(data => {
|
||||
data.forEach((datum) => {
|
||||
this.processData(telemetryObject, datum, axisMetadata);
|
||||
this.addDataToGraph(telemetryObject, datum, axisMetadata);
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn(`Error fetching data`, error);
|
||||
});
|
||||
},
|
||||
subscribeToObject(telemetryObject) {
|
||||
@ -262,10 +283,10 @@ export default {
|
||||
|
||||
this.removeSubscription(key);
|
||||
|
||||
const options = this.getOptions(telemetryObject);
|
||||
const options = this.getOptions();
|
||||
const axisMetadata = this.getAxisMetadata(telemetryObject);
|
||||
const unsubscribe = this.openmct.telemetry.subscribe(telemetryObject,
|
||||
data => this.processData(telemetryObject, data, axisMetadata)
|
||||
data => this.addDataToGraph(telemetryObject, data, axisMetadata)
|
||||
, options);
|
||||
|
||||
this.subscriptions.push({
|
||||
@ -276,9 +297,6 @@ export default {
|
||||
subscribeToAll() {
|
||||
const telemetryObjects = Object.values(this.telemetryObjects);
|
||||
telemetryObjects.forEach(this.subscribeToObject);
|
||||
},
|
||||
updateDomainObject(newDomainObject) {
|
||||
this.currentDomainObject = newDomainObject;
|
||||
}
|
||||
}
|
||||
};
|
@ -26,12 +26,14 @@ import Vue from 'vue';
|
||||
|
||||
export default function BarGraphViewProvider(openmct) {
|
||||
function isCompactView(objectPath) {
|
||||
return objectPath.find(object => object.type === 'time-strip');
|
||||
let isChildOfTimeStrip = objectPath.find(object => object.type === 'time-strip');
|
||||
|
||||
return isChildOfTimeStrip && !openmct.router.isNavigatedObject(objectPath);
|
||||
}
|
||||
|
||||
return {
|
||||
key: BAR_GRAPH_VIEW,
|
||||
name: 'Spectral Aggregate Plot',
|
||||
name: 'Bar Graph',
|
||||
cssClass: 'icon-telemetry',
|
||||
canView(domainObject, objectPath) {
|
||||
return domainObject && domainObject.type === BAR_GRAPH_KEY;
|
||||
@ -54,7 +56,8 @@ export default function BarGraphViewProvider(openmct) {
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject
|
||||
domainObject,
|
||||
path: objectPath
|
||||
},
|
||||
data() {
|
||||
return {
|
@ -1,6 +1,6 @@
|
||||
import { BAR_GRAPH_INSPECTOR_KEY, BAR_GRAPH_KEY } from '../BarGraphConstants';
|
||||
import Vue from 'vue';
|
||||
import Options from "./Options.vue";
|
||||
import BarGraphOptions from "./BarGraphOptions.vue";
|
||||
|
||||
export default function BarGraphInspectorViewProvider(openmct) {
|
||||
return {
|
||||
@ -24,13 +24,13 @@ export default function BarGraphInspectorViewProvider(openmct) {
|
||||
component = new Vue({
|
||||
el: element,
|
||||
components: {
|
||||
Options
|
||||
BarGraphOptions
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject: selection[0][0].context.item
|
||||
},
|
||||
template: '<options></options>'
|
||||
template: '<bar-graph-options></bar-graph-options>'
|
||||
});
|
||||
},
|
||||
destroy: function () {
|
@ -20,27 +20,31 @@
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<ul class="c-tree">
|
||||
<li v-for="series in domainObject.composition"
|
||||
:key="series.key"
|
||||
>
|
||||
<bar-graph-options :item="series" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul class="c-tree c-bar-graph-options">
|
||||
<h2 title="Display properties for this object">Bar Graph Series</h2>
|
||||
<li v-for="series in domainObject.composition"
|
||||
:key="series.key"
|
||||
>
|
||||
<series-options :item="series"
|
||||
:color-palette="colorPalette"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BarGraphOptions from "./BarGraphOptions.vue";
|
||||
import SeriesOptions from "./SeriesOptions.vue";
|
||||
import ColorPalette from '@/ui/color/ColorPalette';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BarGraphOptions
|
||||
SeriesOptions
|
||||
},
|
||||
inject: ['openmct', 'domainObject'],
|
||||
data() {
|
||||
return {
|
||||
isEditing: this.openmct.editor.isEditing()
|
||||
isEditing: this.openmct.editor.isEditing(),
|
||||
colorPalette: this.colorPalette
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -48,6 +52,9 @@ export default {
|
||||
return this.isEditing && !this.domainObject.locked;
|
||||
}
|
||||
},
|
||||
beforeMount() {
|
||||
this.colorPalette = new ColorPalette();
|
||||
},
|
||||
mounted() {
|
||||
this.openmct.editor.on('isEditing', this.setEditState);
|
||||
},
|
@ -21,21 +21,26 @@
|
||||
-->
|
||||
<template>
|
||||
<ul>
|
||||
<li class="c-tree__item menus-to-left">
|
||||
<li class="c-tree__item menus-to-left"
|
||||
:class="aliasCss"
|
||||
>
|
||||
<span class="c-disclosure-triangle is-enabled flex-elem"
|
||||
:class="expandedCssClass"
|
||||
@click="expanded = !expanded"
|
||||
>
|
||||
</span>
|
||||
<div>
|
||||
|
||||
<div class="c-object-label">
|
||||
<div :class="[seriesCss]">
|
||||
</div>
|
||||
<div class="c-object-label__name">{{ name }}</div>
|
||||
</div>
|
||||
</li>
|
||||
<ColorSwatch v-if="expanded"
|
||||
:current-color="currentColor"
|
||||
title="Manually set the color for this bar graph."
|
||||
edit-title="Manually set the color for this bar graph"
|
||||
view-title="The color for this bar graph."
|
||||
title="Manually set the color for this bar graph series."
|
||||
edit-title="Manually set the color for this bar graph series"
|
||||
view-title="The color for this bar graph series."
|
||||
short-label="Color"
|
||||
class="grid-properties"
|
||||
@colorSet="setColor"
|
||||
@ -44,7 +49,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ColorSwatch from '../../ColorSwatch.vue';
|
||||
import ColorSwatch from '@/ui/color/ColorSwatch.vue';
|
||||
import Color from "@/ui/color/Color";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -55,50 +61,90 @@ export default {
|
||||
item: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
colorPalette: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentColor: undefined,
|
||||
name: '',
|
||||
type: '',
|
||||
isAlias: false,
|
||||
expanded: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
expandedCssClass() {
|
||||
return this.expanded ? 'c-disclosure-triangle--expanded' : '';
|
||||
},
|
||||
seriesCss() {
|
||||
const type = this.openmct.types.get(this.type);
|
||||
if (type && type.definition && type.definition.cssClass) {
|
||||
return `c-object-label__type-icon ${type.definition.cssClass}`;
|
||||
}
|
||||
|
||||
return 'c-object-label__type-icon';
|
||||
},
|
||||
aliasCss() {
|
||||
let cssClass = '';
|
||||
if (this.isAlias) {
|
||||
cssClass = 'is-alias';
|
||||
}
|
||||
|
||||
return cssClass;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
item: {
|
||||
handler() {
|
||||
this.initColor();
|
||||
this.initColorAndName();
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.key = this.openmct.objects.makeKeyString(this.item);
|
||||
this.initColor();
|
||||
this.unObserve = this.openmct.objects.observe(this.domainObject, `this.domainObject.configuration.barStyles[${this.key}]`, this.initColor);
|
||||
this.initColorAndName();
|
||||
this.removeBarStylesListener = this.openmct.objects.observe(this.domainObject, `configuration.barStyles.series["${this.key}"]`, this.initColorAndName);
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.unObserve) {
|
||||
this.unObserve();
|
||||
if (this.removeBarStylesListener) {
|
||||
this.removeBarStylesListener();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initColor() {
|
||||
if (this.domainObject.configuration.barStyles && this.domainObject.configuration.barStyles[this.key]) {
|
||||
this.currentColor = this.domainObject.configuration.barStyles[this.key].color;
|
||||
this.name = this.domainObject.configuration.barStyles[this.key].name;
|
||||
initColorAndName() {
|
||||
// this is called before the plot is initialized
|
||||
if (!this.domainObject.configuration.barStyles.series[this.key]) {
|
||||
const color = this.colorPalette.getNextColor().asHexString();
|
||||
this.domainObject.configuration.barStyles.series[this.key] = {
|
||||
color,
|
||||
type: '',
|
||||
name: '',
|
||||
isAlias: false
|
||||
};
|
||||
} else if (!this.domainObject.configuration.barStyles.series[this.key].color) {
|
||||
this.domainObject.configuration.barStyles.series[this.key].color = this.colorPalette.getNextColor().asHexString();
|
||||
}
|
||||
|
||||
this.currentColor = this.domainObject.configuration.barStyles.series[this.key].color;
|
||||
this.name = this.domainObject.configuration.barStyles.series[this.key].name;
|
||||
this.type = this.domainObject.configuration.barStyles.series[this.key].type;
|
||||
this.isAlias = this.domainObject.configuration.barStyles.series[this.key].isAlias;
|
||||
|
||||
let colorHexString = this.currentColor;
|
||||
const colorObject = Color.fromHexString(colorHexString);
|
||||
|
||||
this.colorPalette.remove(colorObject);
|
||||
},
|
||||
setColor(chosenColor) {
|
||||
this.currentColor = chosenColor.asHexString();
|
||||
this.openmct.objects.mutate(
|
||||
this.domainObject,
|
||||
`configuration.barStyles[${this.key}].color`,
|
||||
`configuration.barStyles.series["${this.key}"].color`,
|
||||
this.currentColor
|
||||
);
|
||||
}
|
51
src/plugins/charts/plugin.js
Normal file
51
src/plugins/charts/plugin.js
Normal file
@ -0,0 +1,51 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
||||
*****************************************************************************/
|
||||
import { BAR_GRAPH_KEY } from './BarGraphConstants';
|
||||
import BarGraphViewProvider from './BarGraphViewProvider';
|
||||
import BarGraphInspectorViewProvider from './inspector/BarGraphInspectorViewProvider';
|
||||
import BarGraphCompositionPolicy from './BarGraphCompositionPolicy';
|
||||
|
||||
export default function () {
|
||||
return function install(openmct) {
|
||||
openmct.types.addType(BAR_GRAPH_KEY, {
|
||||
key: BAR_GRAPH_KEY,
|
||||
name: "Bar Graph",
|
||||
cssClass: "icon-bar-chart",
|
||||
description: "View data as a bar graph. Can be added to Display Layouts.",
|
||||
creatable: true,
|
||||
initialize: function (domainObject) {
|
||||
domainObject.composition = [];
|
||||
domainObject.configuration = {
|
||||
barStyles: { series: {} }
|
||||
};
|
||||
},
|
||||
priority: 891
|
||||
});
|
||||
|
||||
openmct.objectViews.addProvider(new BarGraphViewProvider(openmct));
|
||||
|
||||
openmct.inspectorViews.addProvider(new BarGraphInspectorViewProvider(openmct));
|
||||
|
||||
openmct.composition.addPolicy(new BarGraphCompositionPolicy(openmct).allow);
|
||||
};
|
||||
}
|
||||
|
485
src/plugins/charts/pluginSpec.js
Normal file
485
src/plugins/charts/pluginSpec.js
Normal file
@ -0,0 +1,485 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2021, 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.
|
||||
*****************************************************************************/
|
||||
|
||||
import {createOpenMct, resetApplicationState} from "utils/testing";
|
||||
import Vue from "vue";
|
||||
import BarGraphPlugin from "./plugin";
|
||||
import BarGraph from './BarGraphPlot.vue';
|
||||
import EventEmitter from "EventEmitter";
|
||||
import { BAR_GRAPH_VIEW, BAR_GRAPH_KEY } from './BarGraphConstants';
|
||||
|
||||
describe("the plugin", function () {
|
||||
let element;
|
||||
let child;
|
||||
let openmct;
|
||||
let telemetryPromise;
|
||||
let telemetryPromiseResolve;
|
||||
let mockObjectPath;
|
||||
|
||||
beforeEach((done) => {
|
||||
mockObjectPath = [
|
||||
{
|
||||
name: 'mock folder',
|
||||
type: 'fake-folder',
|
||||
identifier: {
|
||||
key: 'mock-folder',
|
||||
namespace: ''
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'mock parent folder',
|
||||
type: 'time-strip',
|
||||
identifier: {
|
||||
key: 'mock-parent-folder',
|
||||
namespace: ''
|
||||
}
|
||||
}
|
||||
];
|
||||
const testTelemetry = [
|
||||
{
|
||||
'utc': 1,
|
||||
'some-key': 'some-value 1',
|
||||
'some-other-key': 'some-other-value 1'
|
||||
},
|
||||
{
|
||||
'utc': 2,
|
||||
'some-key': 'some-value 2',
|
||||
'some-other-key': 'some-other-value 2'
|
||||
},
|
||||
{
|
||||
'utc': 3,
|
||||
'some-key': 'some-value 3',
|
||||
'some-other-key': 'some-other-value 3'
|
||||
}
|
||||
];
|
||||
|
||||
openmct = createOpenMct();
|
||||
|
||||
telemetryPromise = new Promise((resolve) => {
|
||||
telemetryPromiseResolve = resolve;
|
||||
});
|
||||
|
||||
spyOn(openmct.telemetry, 'request').and.callFake(() => {
|
||||
telemetryPromiseResolve(testTelemetry);
|
||||
|
||||
return telemetryPromise;
|
||||
});
|
||||
|
||||
openmct.install(new BarGraphPlugin());
|
||||
|
||||
element = document.createElement("div");
|
||||
element.style.width = "640px";
|
||||
element.style.height = "480px";
|
||||
child = document.createElement("div");
|
||||
child.style.width = "640px";
|
||||
child.style.height = "480px";
|
||||
element.appendChild(child);
|
||||
document.body.appendChild(element);
|
||||
|
||||
spyOn(window, 'ResizeObserver').and.returnValue({
|
||||
observe() {},
|
||||
unobserve() {},
|
||||
disconnect() {}
|
||||
});
|
||||
|
||||
openmct.time.timeSystem("utc", {
|
||||
start: 0,
|
||||
end: 4
|
||||
});
|
||||
|
||||
openmct.types.addType("test-object", {
|
||||
creatable: true
|
||||
});
|
||||
|
||||
openmct.on("start", done);
|
||||
openmct.startHeadless();
|
||||
});
|
||||
|
||||
afterEach((done) => {
|
||||
openmct.time.timeSystem('utc', {
|
||||
start: 0,
|
||||
end: 1
|
||||
});
|
||||
resetApplicationState(openmct).then(done).catch(done);
|
||||
});
|
||||
|
||||
describe("The bar graph view", () => {
|
||||
let testDomainObject;
|
||||
let barGraphObject;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let component;
|
||||
let mockComposition;
|
||||
|
||||
beforeEach(async () => {
|
||||
const getFunc = openmct.$injector.get;
|
||||
spyOn(openmct.$injector, "get")
|
||||
.withArgs("exportImageService").and.returnValue({
|
||||
exportPNG: () => {},
|
||||
exportJPG: () => {}
|
||||
})
|
||||
.and.callFake(getFunc);
|
||||
|
||||
barGraphObject = {
|
||||
identifier: {
|
||||
namespace: "",
|
||||
key: "test-plot"
|
||||
},
|
||||
type: "telemetry.plot.bar-graph",
|
||||
name: "Test Bar Graph"
|
||||
};
|
||||
|
||||
testDomainObject = {
|
||||
identifier: {
|
||||
namespace: "",
|
||||
key: "test-object"
|
||||
},
|
||||
configuration: {
|
||||
barStyles: {
|
||||
series: {}
|
||||
}
|
||||
},
|
||||
type: "test-object",
|
||||
name: "Test Object",
|
||||
telemetry: {
|
||||
values: [{
|
||||
key: "utc",
|
||||
format: "utc",
|
||||
name: "Time",
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
}, {
|
||||
key: "some-key",
|
||||
name: "Some attribute",
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
}, {
|
||||
key: "some-other-key",
|
||||
name: "Another attribute",
|
||||
hints: {
|
||||
range: 2
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
mockComposition = new EventEmitter();
|
||||
mockComposition.load = () => {
|
||||
mockComposition.emit('add', testDomainObject);
|
||||
|
||||
return [testDomainObject];
|
||||
};
|
||||
|
||||
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
|
||||
|
||||
let viewContainer = document.createElement("div");
|
||||
child.append(viewContainer);
|
||||
component = new Vue({
|
||||
el: viewContainer,
|
||||
components: {
|
||||
BarGraph
|
||||
},
|
||||
provide: {
|
||||
openmct: openmct,
|
||||
domainObject: barGraphObject,
|
||||
composition: openmct.composition.get(barGraphObject)
|
||||
},
|
||||
template: "<BarGraph></BarGraph>"
|
||||
});
|
||||
|
||||
await Vue.nextTick();
|
||||
});
|
||||
|
||||
it("provides a bar graph view", () => {
|
||||
const applicableViews = openmct.objectViews.get(barGraphObject, mockObjectPath);
|
||||
const plotViewProvider = applicableViews.find((viewProvider) => viewProvider.key === BAR_GRAPH_VIEW);
|
||||
expect(plotViewProvider).toBeDefined();
|
||||
});
|
||||
|
||||
it("Renders plotly bar graph", () => {
|
||||
let barChartElement = element.querySelectorAll(".plotly");
|
||||
expect(barChartElement.length).toBe(1);
|
||||
});
|
||||
|
||||
it("Handles dots in telemetry id", () => {
|
||||
const dotFullTelemetryObject = {
|
||||
identifier: {
|
||||
namespace: "someNamespace",
|
||||
key: "~OpenMCT~outer.test-object.foo.bar"
|
||||
},
|
||||
type: "test-dotful-object",
|
||||
name: "A Dotful Object",
|
||||
telemetry: {
|
||||
values: [{
|
||||
key: "utc",
|
||||
format: "utc",
|
||||
name: "Time",
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
}, {
|
||||
key: "some-key.foo.name.45",
|
||||
name: "Some dotful attribute",
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
}, {
|
||||
key: "some-other-key.bar.344.rad",
|
||||
name: "Another dotful attribute",
|
||||
hints: {
|
||||
range: 2
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
const applicableViews = openmct.objectViews.get(barGraphObject, mockObjectPath);
|
||||
const plotViewProvider = applicableViews.find((viewProvider) => viewProvider.key === BAR_GRAPH_VIEW);
|
||||
const barGraphView = plotViewProvider.view(testDomainObject, [testDomainObject]);
|
||||
barGraphView.show(child, true);
|
||||
expect(testDomainObject.configuration.barStyles.series["test-object"].name).toEqual("Test Object");
|
||||
mockComposition.emit('add', dotFullTelemetryObject);
|
||||
expect(testDomainObject.configuration.barStyles.series["someNamespace:~OpenMCT~outer.test-object.foo.bar"].name).toEqual("A Dotful Object");
|
||||
barGraphView.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("the bar graph objects", () => {
|
||||
const mockObject = {
|
||||
name: 'A very nice bar graph',
|
||||
key: BAR_GRAPH_KEY,
|
||||
creatable: true
|
||||
};
|
||||
|
||||
it('defines a bar graph object type with the correct key', () => {
|
||||
const objectDef = openmct.types.get(BAR_GRAPH_KEY).definition;
|
||||
expect(objectDef.key).toEqual(mockObject.key);
|
||||
});
|
||||
|
||||
it('is creatable', () => {
|
||||
const objectDef = openmct.types.get(BAR_GRAPH_KEY).definition;
|
||||
expect(objectDef.creatable).toEqual(mockObject.creatable);
|
||||
});
|
||||
});
|
||||
|
||||
describe("The bar graph composition policy", () => {
|
||||
|
||||
it("allows composition for telemetry that contain at least one range", () => {
|
||||
const parent = {
|
||||
"composition": [],
|
||||
"configuration": {},
|
||||
"name": "Some Bar Graph",
|
||||
"type": "telemetry.plot.bar-graph",
|
||||
"location": "mine",
|
||||
"modified": 1631005183584,
|
||||
"persisted": 1631005183502,
|
||||
"identifier": {
|
||||
"namespace": "",
|
||||
"key": "b78e7e23-f2b8-4776-b1f0-3ff778f5c8a9"
|
||||
}
|
||||
};
|
||||
const testTelemetryObject = {
|
||||
identifier: {
|
||||
namespace: "",
|
||||
key: "test-object"
|
||||
},
|
||||
type: "test-object",
|
||||
name: "Test Object",
|
||||
telemetry: {
|
||||
values: [{
|
||||
key: "some-key",
|
||||
name: "Some attribute",
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
}, {
|
||||
key: "some-other-key",
|
||||
name: "Another attribute",
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
const composition = openmct.composition.get(parent);
|
||||
expect(() => {
|
||||
composition.add(testTelemetryObject);
|
||||
}).not.toThrow();
|
||||
expect(parent.composition.length).toBe(1);
|
||||
});
|
||||
|
||||
it("disallows composition for telemetry that don't contain any range hints", () => {
|
||||
const parent = {
|
||||
"composition": [],
|
||||
"configuration": {},
|
||||
"name": "Some Bar Graph",
|
||||
"type": "telemetry.plot.bar-graph",
|
||||
"location": "mine",
|
||||
"modified": 1631005183584,
|
||||
"persisted": 1631005183502,
|
||||
"identifier": {
|
||||
"namespace": "",
|
||||
"key": "b78e7e23-f2b8-4776-b1f0-3ff778f5c8a9"
|
||||
}
|
||||
};
|
||||
const testTelemetryObject = {
|
||||
identifier: {
|
||||
namespace: "",
|
||||
key: "test-object"
|
||||
},
|
||||
type: "test-object",
|
||||
name: "Test Object",
|
||||
telemetry: {
|
||||
values: [{
|
||||
key: "some-key",
|
||||
name: "Some attribute"
|
||||
}, {
|
||||
key: "some-other-key",
|
||||
name: "Another attribute"
|
||||
}]
|
||||
}
|
||||
};
|
||||
const composition = openmct.composition.get(parent);
|
||||
expect(() => {
|
||||
composition.add(testTelemetryObject);
|
||||
}).toThrow();
|
||||
expect(parent.composition.length).toBe(0);
|
||||
});
|
||||
});
|
||||
describe('the inspector view', () => {
|
||||
let mockComposition;
|
||||
let testDomainObject;
|
||||
let selection;
|
||||
let plotInspectorView;
|
||||
let viewContainer;
|
||||
let optionsElement;
|
||||
beforeEach(async () => {
|
||||
testDomainObject = {
|
||||
identifier: {
|
||||
namespace: "",
|
||||
key: "test-object"
|
||||
},
|
||||
type: "test-object",
|
||||
name: "Test Object",
|
||||
telemetry: {
|
||||
values: [{
|
||||
key: "utc",
|
||||
format: "utc",
|
||||
name: "Time",
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
}, {
|
||||
key: "some-key",
|
||||
name: "Some attribute",
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
}, {
|
||||
key: "some-other-key",
|
||||
name: "Another attribute",
|
||||
hints: {
|
||||
range: 2
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
selection = [
|
||||
[
|
||||
{
|
||||
context: {
|
||||
item: {
|
||||
id: "test-object",
|
||||
identifier: {
|
||||
key: "test-object",
|
||||
namespace: ''
|
||||
},
|
||||
type: "telemetry.plot.bar-graph",
|
||||
configuration: {
|
||||
barStyles: {
|
||||
series: {
|
||||
'~Some~foo.bar': {
|
||||
name: 'A telemetry object',
|
||||
type: 'some-type',
|
||||
isAlias: true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
composition: [
|
||||
{
|
||||
key: '~Some~foo.bar'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
context: {
|
||||
item: {
|
||||
type: 'time-strip',
|
||||
identifier: {
|
||||
key: 'some-other-key',
|
||||
namespace: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
];
|
||||
|
||||
mockComposition = new EventEmitter();
|
||||
mockComposition.load = () => {
|
||||
mockComposition.emit('add', testDomainObject);
|
||||
|
||||
return [testDomainObject];
|
||||
};
|
||||
|
||||
spyOn(openmct.composition, 'get').and.returnValue(mockComposition);
|
||||
|
||||
viewContainer = document.createElement('div');
|
||||
child.append(viewContainer);
|
||||
|
||||
const applicableViews = openmct.inspectorViews.get(selection);
|
||||
plotInspectorView = applicableViews[0];
|
||||
plotInspectorView.show(viewContainer);
|
||||
|
||||
await Vue.nextTick();
|
||||
optionsElement = element.querySelector('.c-bar-graph-options');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
plotInspectorView.destroy();
|
||||
});
|
||||
|
||||
it('it renders the options', () => {
|
||||
expect(optionsElement).toBeDefined();
|
||||
});
|
||||
|
||||
it('shows the name', () => {
|
||||
const seriesEl = optionsElement.querySelector('.c-object-label__name');
|
||||
expect(seriesEl.innerHTML).toEqual('A telemetry object');
|
||||
});
|
||||
});
|
||||
});
|
@ -47,7 +47,9 @@ define([
|
||||
});
|
||||
|
||||
let indicator = {
|
||||
element: component.$mount().$el
|
||||
element: component.$mount().$el,
|
||||
key: 'clear-data-indicator',
|
||||
priority: openmct.priority.DEFAULT
|
||||
};
|
||||
|
||||
openmct.indicators.add(indicator);
|
||||
|
@ -41,6 +41,9 @@ describe('When the Clear Data Plugin is installed,', () => {
|
||||
const openmct = {
|
||||
objectViews: mockObjectViews,
|
||||
indicators: mockIndicatorProvider,
|
||||
priority: {
|
||||
DEFAULT: 0
|
||||
},
|
||||
actions: mockActionsProvider,
|
||||
install: function (plugin) {
|
||||
plugin(this);
|
||||
@ -69,13 +72,13 @@ describe('When the Clear Data Plugin is installed,', () => {
|
||||
];
|
||||
|
||||
it('Global Clear Indicator is installed', () => {
|
||||
openmct.install(ClearDataActionPlugin([]));
|
||||
openmct.install(ClearDataActionPlugin(openmct, {indicator: true}));
|
||||
|
||||
expect(mockIndicatorProvider.add).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Clear Data context menu action is installed', () => {
|
||||
openmct.install(ClearDataActionPlugin([]));
|
||||
openmct.install(ClearDataActionPlugin(openmct, []));
|
||||
|
||||
expect(mockActionsProvider.register).toHaveBeenCalled();
|
||||
});
|
||||
|
@ -116,7 +116,8 @@ export default function ClockPlugin(options) {
|
||||
});
|
||||
const indicator = {
|
||||
element: clockIndicator.$mount().$el,
|
||||
key: 'clock-indicator'
|
||||
key: 'clock-indicator',
|
||||
priority: openmct.priority.LOW
|
||||
};
|
||||
|
||||
openmct.indicators.add(indicator);
|
||||
|
@ -65,7 +65,7 @@ export default class Condition extends EventEmitter {
|
||||
}
|
||||
|
||||
this.trigger = conditionConfiguration.configuration.trigger;
|
||||
this.description = '';
|
||||
this.summary = '';
|
||||
}
|
||||
|
||||
updateResult(datum) {
|
||||
@ -134,7 +134,6 @@ export default class Condition extends EventEmitter {
|
||||
criterionConfigurations.forEach((criterionConfiguration) => {
|
||||
this.addCriterion(criterionConfiguration);
|
||||
});
|
||||
this.updateDescription();
|
||||
}
|
||||
|
||||
updateCriteria(criterionConfigurations) {
|
||||
@ -146,7 +145,6 @@ export default class Condition extends EventEmitter {
|
||||
this.criteria.forEach((criterion) => {
|
||||
criterion.updateTelemetryObjects(this.conditionManager.telemetryObjects);
|
||||
});
|
||||
this.updateDescription();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -200,7 +198,6 @@ export default class Condition extends EventEmitter {
|
||||
criterion.off('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
|
||||
criterion.off('telemetryIsStale', (obj) => this.handleStaleCriterion(obj));
|
||||
this.criteria.splice(found.index, 1, newCriterion);
|
||||
this.updateDescription();
|
||||
}
|
||||
}
|
||||
|
||||
@ -216,7 +213,6 @@ export default class Condition extends EventEmitter {
|
||||
});
|
||||
criterion.destroy();
|
||||
this.criteria.splice(found.index, 1);
|
||||
this.updateDescription();
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -228,7 +224,6 @@ export default class Condition extends EventEmitter {
|
||||
let found = this.findCriterion(criterion.id);
|
||||
if (found) {
|
||||
this.criteria[found.index] = criterion.data;
|
||||
this.updateDescription();
|
||||
}
|
||||
}
|
||||
|
||||
@ -254,8 +249,7 @@ export default class Condition extends EventEmitter {
|
||||
|
||||
description = `${description} ${criterion.getDescription()} ${(index < this.criteria.length - 1) ? triggerDescription.conjunction : ''}`;
|
||||
});
|
||||
this.description = description;
|
||||
this.conditionManager.updateConditionDescription(this);
|
||||
this.summary = description;
|
||||
}
|
||||
|
||||
getTriggerDescription() {
|
||||
|
@ -105,7 +105,14 @@ export default class ConditionManager extends EventEmitter {
|
||||
}
|
||||
|
||||
updateConditionTelemetryObjects() {
|
||||
this.conditions.forEach((condition) => condition.updateTelemetryObjects());
|
||||
this.conditions.forEach((condition) => {
|
||||
condition.updateTelemetryObjects();
|
||||
let index = this.conditionSetDomainObject.configuration.conditionCollection.findIndex(item => item.id === condition.id);
|
||||
if (index > -1) {
|
||||
//Only assign the summary, don't mutate the domain object
|
||||
this.conditionSetDomainObject.configuration.conditionCollection[index].summary = this.updateConditionDescription(condition);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
removeConditionTelemetryObjects() {
|
||||
@ -139,10 +146,17 @@ export default class ConditionManager extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
updateConditionDescription(condition) {
|
||||
condition.updateDescription();
|
||||
|
||||
return condition.summary;
|
||||
}
|
||||
|
||||
updateCondition(conditionConfiguration) {
|
||||
let condition = this.findConditionById(conditionConfiguration.id);
|
||||
if (condition) {
|
||||
condition.update(conditionConfiguration);
|
||||
conditionConfiguration.summary = this.updateConditionDescription(condition);
|
||||
}
|
||||
|
||||
let index = this.conditionSetDomainObject.configuration.conditionCollection.findIndex(item => item.id === conditionConfiguration.id);
|
||||
@ -152,16 +166,10 @@ export default class ConditionManager extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
updateConditionDescription(condition) {
|
||||
const found = this.conditionSetDomainObject.configuration.conditionCollection.find(conditionConfiguration => (conditionConfiguration.id === condition.id));
|
||||
if (found.summary !== condition.description) {
|
||||
found.summary = condition.description;
|
||||
this.persistConditions();
|
||||
}
|
||||
}
|
||||
|
||||
initCondition(conditionConfiguration, index) {
|
||||
let condition = new Condition(conditionConfiguration, this.openmct, this);
|
||||
conditionConfiguration.summary = this.updateConditionDescription(condition);
|
||||
|
||||
if (index !== undefined) {
|
||||
this.conditions.splice(index + 1, 0, condition);
|
||||
} else {
|
||||
|
@ -33,8 +33,10 @@ export default class ConditionSetViewProvider {
|
||||
this.cssClass = 'icon-conditional';
|
||||
}
|
||||
|
||||
canView(domainObject) {
|
||||
return domainObject.type === 'conditionSet';
|
||||
canView(domainObject, objectPath) {
|
||||
const isConditionSet = domainObject.type === 'conditionSet';
|
||||
|
||||
return isConditionSet && this.openmct.router.isNavigatedObject(objectPath);
|
||||
}
|
||||
|
||||
canEdit(domainObject) {
|
||||
|
@ -244,7 +244,7 @@ export default {
|
||||
this.telemetryMetadataOptions = [];
|
||||
telemetryObjects.forEach(telemetryObject => {
|
||||
let telemetryMetadata = this.openmct.telemetry.getMetadata(telemetryObject);
|
||||
this.addMetaDataOptions(telemetryMetadata.values());
|
||||
this.addMetaDataOptions(telemetryMetadata ? telemetryMetadata.values() : []);
|
||||
});
|
||||
this.updateOperations();
|
||||
}
|
||||
|
@ -192,7 +192,11 @@ export default {
|
||||
this.telemetry.forEach((telemetryObject) => {
|
||||
const id = this.openmct.objects.makeKeyString(telemetryObject.identifier);
|
||||
let telemetryMetadata = this.openmct.telemetry.getMetadata(telemetryObject);
|
||||
this.telemetryMetadataOptions[id] = telemetryMetadata.values().slice();
|
||||
if (telemetryMetadata) {
|
||||
this.telemetryMetadataOptions[id] = telemetryMetadata.values().slice();
|
||||
} else {
|
||||
this.telemetryMetadataOptions[id] = [];
|
||||
}
|
||||
});
|
||||
},
|
||||
addTestInput(testInput) {
|
||||
|
@ -30,10 +30,10 @@
|
||||
<div v-if="staticStyle"
|
||||
class="c-inspect-styles__style"
|
||||
>
|
||||
<style-editor class="c-inspect-styles__editor"
|
||||
:style-item="staticStyle"
|
||||
:is-editing="isEditing"
|
||||
@persist="updateStaticStyle"
|
||||
<StyleEditor class="c-inspect-styles__editor"
|
||||
:style-item="staticStyle"
|
||||
:is-editing="isEditing"
|
||||
@persist="updateStaticStyle"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
@ -87,10 +87,10 @@
|
||||
<condition-description :show-label="true"
|
||||
:condition="getCondition(conditionStyle.conditionId)"
|
||||
/>
|
||||
<style-editor class="c-inspect-styles__editor"
|
||||
:style-item="conditionStyle"
|
||||
:is-editing="isEditing"
|
||||
@persist="updateConditionalStyle"
|
||||
<StyleEditor class="c-inspect-styles__editor"
|
||||
:style-item="conditionStyle"
|
||||
:is-editing="isEditing"
|
||||
@persist="updateConditionalStyle"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -240,10 +240,10 @@ export default {
|
||||
}
|
||||
|
||||
let vm = new Vue({
|
||||
components: {ConditionSetSelectorDialog},
|
||||
provide: {
|
||||
openmct: this.openmct
|
||||
},
|
||||
components: {ConditionSetSelectorDialog},
|
||||
data() {
|
||||
return {
|
||||
handleItemSelection
|
||||
|
@ -40,13 +40,13 @@
|
||||
<div v-if="staticStyle"
|
||||
class="c-inspect-styles__style"
|
||||
>
|
||||
<style-editor class="c-inspect-styles__editor"
|
||||
:style-item="staticStyle"
|
||||
:is-editing="allowEditing"
|
||||
:mixed-styles="mixedStyles"
|
||||
:non-specific-font-properties="nonSpecificFontProperties"
|
||||
@persist="updateStaticStyle"
|
||||
@save-style="saveStyle"
|
||||
<StyleEditor class="c-inspect-styles__editor"
|
||||
:style-item="staticStyle"
|
||||
:is-editing="allowEditing"
|
||||
:mixed-styles="mixedStyles"
|
||||
:non-specific-font-properties="nonSpecificFontProperties"
|
||||
@persist="updateStaticStyle"
|
||||
@save-style="saveStyle"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
@ -108,12 +108,12 @@
|
||||
<condition-description :show-label="true"
|
||||
:condition="getCondition(conditionStyle.conditionId)"
|
||||
/>
|
||||
<style-editor class="c-inspect-styles__editor"
|
||||
:style-item="conditionStyle"
|
||||
:non-specific-font-properties="nonSpecificFontProperties"
|
||||
:is-editing="allowEditing"
|
||||
@persist="updateConditionalStyle"
|
||||
@save-style="saveStyle"
|
||||
<StyleEditor class="c-inspect-styles__editor"
|
||||
:style-item="conditionStyle"
|
||||
:non-specific-font-properties="nonSpecificFontProperties"
|
||||
:is-editing="allowEditing"
|
||||
@persist="updateConditionalStyle"
|
||||
@save-style="saveStyle"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -556,10 +556,10 @@ export default {
|
||||
}
|
||||
|
||||
let vm = new Vue({
|
||||
components: {ConditionSetSelectorDialog},
|
||||
provide: {
|
||||
openmct: this.openmct
|
||||
},
|
||||
components: {ConditionSetSelectorDialog},
|
||||
data() {
|
||||
return {
|
||||
handleItemSelection
|
||||
|
@ -177,7 +177,7 @@ export default class AllTelemetryCriterion extends TelemetryCriterion {
|
||||
const timeSystem = this.openmct.time.timeSystem();
|
||||
|
||||
telemetryRequestsResults.forEach((results, index) => {
|
||||
const latestDatum = results.length ? results[results.length - 1] : {};
|
||||
const latestDatum = (Array.isArray(results) && results.length) ? results[results.length - 1] : {};
|
||||
const datumId = keys[index];
|
||||
const normalizedDatum = this.createNormalizedDatum(latestDatum, telemetryObjects[datumId]);
|
||||
|
||||
|
@ -167,6 +167,11 @@ export default class TelemetryCriterion extends EventEmitter {
|
||||
id: this.id,
|
||||
data: this.formatData(normalizedDatum)
|
||||
};
|
||||
}).catch((error) => {
|
||||
return {
|
||||
id: this.id,
|
||||
data: this.formatData()
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@ import StylesView from "./components/inspector/StylesView.vue";
|
||||
import Vue from 'vue';
|
||||
import {getApplicableStylesForItem} from "./utils/styleUtils";
|
||||
import ConditionManager from "@/plugins/condition/ConditionManager";
|
||||
import StyleRuleManager from "./StyleRuleManager";
|
||||
|
||||
describe('the plugin', function () {
|
||||
let conditionSetDefinition;
|
||||
@ -96,8 +97,12 @@ describe('the plugin', function () {
|
||||
|
||||
mockListener = jasmine.createSpy('mockListener');
|
||||
|
||||
openmct.router.isNavigatedObject = jasmine.createSpy().and.returnValue(true);
|
||||
|
||||
conditionSetDefinition.initialize(mockConditionSetDomainObject);
|
||||
|
||||
spyOn(openmct.objects, "save").and.returnValue(Promise.resolve(true));
|
||||
|
||||
openmct.on('start', done);
|
||||
openmct.startHeadless();
|
||||
});
|
||||
@ -126,21 +131,6 @@ describe('the plugin', function () {
|
||||
expect(mockConditionSetDomainObject.composition instanceof Array).toBeTrue();
|
||||
expect(mockConditionSetDomainObject.composition.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('provides a view', () => {
|
||||
const testViewObject = {
|
||||
id: "test-object",
|
||||
type: "conditionSet",
|
||||
configuration: {
|
||||
conditionCollection: []
|
||||
}
|
||||
};
|
||||
|
||||
const applicableViews = openmct.objectViews.get(testViewObject, []);
|
||||
let conditionSetView = applicableViews.find((viewProvider) => viewProvider.key === 'conditionSet.view');
|
||||
expect(conditionSetView).toBeDefined();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('the condition set usage for multiple display layout items', () => {
|
||||
@ -722,4 +712,124 @@ describe('the plugin', function () {
|
||||
expect(result[2]).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('canView of ConditionSetViewProvider', () => {
|
||||
let conditionSetView;
|
||||
const testViewObject = {
|
||||
id: "test-object",
|
||||
type: "conditionSet",
|
||||
configuration: {
|
||||
conditionCollection: []
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
const applicableViews = openmct.objectViews.get(testViewObject, []);
|
||||
conditionSetView = applicableViews.find((viewProvider) => viewProvider.key === 'conditionSet.view');
|
||||
});
|
||||
|
||||
it('provides a view', () => {
|
||||
expect(conditionSetView).toBeDefined();
|
||||
});
|
||||
|
||||
it('returns true for type `conditionSet` and is a navigated Object', () => {
|
||||
openmct.router.isNavigatedObject = jasmine.createSpy().and.returnValue(true);
|
||||
|
||||
const isCanView = conditionSetView.canView(testViewObject, []);
|
||||
|
||||
expect(isCanView).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for type `conditionSet` and is not a navigated Object', () => {
|
||||
openmct.router.isNavigatedObject = jasmine.createSpy().and.returnValue(false);
|
||||
|
||||
const isCanView = conditionSetView.canView(testViewObject, []);
|
||||
|
||||
expect(isCanView).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false for type `notConditionSet` and is a navigated Object', () => {
|
||||
openmct.router.isNavigatedObject = jasmine.createSpy().and.returnValue(true);
|
||||
testViewObject.type = 'notConditionSet';
|
||||
const isCanView = conditionSetView.canView(testViewObject, []);
|
||||
|
||||
expect(isCanView).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('The Style Rule Manager', () => {
|
||||
it('should subscribe to the conditionSet after the editor saves', async () => {
|
||||
const stylesObject = {
|
||||
"styles": [
|
||||
{
|
||||
"conditionId": "a8bf7d1a-c1bb-4fc7-936a-62056a51b5cd",
|
||||
"style": {
|
||||
"backgroundColor": "#38761d",
|
||||
"border": "",
|
||||
"color": "#073763",
|
||||
"isStyleInvisible": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"conditionId": "0558fa77-9bdc-4142-9f9a-7a28fe95182e",
|
||||
"style": {
|
||||
"backgroundColor": "#980000",
|
||||
"border": "",
|
||||
"color": "#ff9900",
|
||||
"isStyleInvisible": ""
|
||||
}
|
||||
}
|
||||
],
|
||||
"staticStyle": {
|
||||
"style": {
|
||||
"backgroundColor": "",
|
||||
"border": "",
|
||||
"color": ""
|
||||
}
|
||||
},
|
||||
"selectedConditionId": "0558fa77-9bdc-4142-9f9a-7a28fe95182e",
|
||||
"defaultConditionId": "0558fa77-9bdc-4142-9f9a-7a28fe95182e",
|
||||
"conditionSetIdentifier": {
|
||||
"namespace": "",
|
||||
"key": "035c589c-d98f-429e-8b89-d76bd8d22b29"
|
||||
}
|
||||
};
|
||||
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
|
||||
// const mockTransactionService = jasmine.createSpyObj(
|
||||
// 'transactionService',
|
||||
// ['commit']
|
||||
// );
|
||||
openmct.telemetry = jasmine.createSpyObj('telemetry', ['isTelemetryObject', "subscribe", "getMetadata", "getValueFormatter", "request"]);
|
||||
openmct.telemetry.isTelemetryObject.and.returnValue(true);
|
||||
openmct.telemetry.subscribe.and.returnValue(function () {});
|
||||
openmct.telemetry.getValueFormatter.and.returnValue({
|
||||
parse: function (value) {
|
||||
return value;
|
||||
}
|
||||
});
|
||||
openmct.telemetry.getMetadata.and.returnValue(testTelemetryObject.telemetry);
|
||||
openmct.telemetry.request.and.returnValue(Promise.resolve([]));
|
||||
|
||||
// mockTransactionService.commit = async () => {};
|
||||
const mockIdentifierService = jasmine.createSpyObj(
|
||||
'identifierService',
|
||||
['parse']
|
||||
);
|
||||
mockIdentifierService.parse.and.returnValue({
|
||||
getSpace: () => {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
|
||||
openmct.$injector.get.withArgs('identifierService').and.returnValue(mockIdentifierService);
|
||||
// .withArgs('transactionService').and.returnValue(mockTransactionService);
|
||||
|
||||
const styleRuleManger = new StyleRuleManager(stylesObject, openmct, null, true);
|
||||
spyOn(styleRuleManger, 'subscribeToConditionSet');
|
||||
openmct.editor.edit();
|
||||
await openmct.editor.save();
|
||||
expect(styleRuleManger.subscribeToConditionSet).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -23,7 +23,7 @@
|
||||
<template>
|
||||
<component :is="urlDefined ? 'a' : 'span'"
|
||||
class="c-condition-widget u-style-receiver js-style-receiver"
|
||||
:href="urlDefined ? internalDomainObject.url : null"
|
||||
:href="url"
|
||||
>
|
||||
<div class="c-condition-widget__label">
|
||||
{{ internalDomainObject.label }}
|
||||
@ -32,6 +32,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const sanitizeUrl = require("@braintree/sanitize-url").sanitizeUrl;
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
data: function () {
|
||||
@ -42,6 +44,9 @@ export default {
|
||||
computed: {
|
||||
urlDefined() {
|
||||
return this.internalDomainObject.url && this.internalDomainObject.url.length > 0;
|
||||
},
|
||||
url() {
|
||||
return this.urlDefined ? sanitizeUrl(this.internalDomainObject.url) : null;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
@ -38,12 +38,16 @@ a.c-condition-widget {
|
||||
|
||||
// Make Condition Widget expand when in a hidden frame Layout context
|
||||
// For both static and Flexible Layouts
|
||||
.c-so-view--no-frame > .c-so-view__object-view > .c-condition-widget {
|
||||
@include abs();
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
.c-so-view--conditionWidget.c-so-view--no-frame {
|
||||
.c-condition-widget {
|
||||
@include abs();
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.c-so-view__frame-controls { display: none; }
|
||||
}
|
||||
|
||||
// Add some margin when a Condition Widget is in a Flexible Layout
|
||||
|
@ -47,8 +47,8 @@
|
||||
|
||||
.is-editing {
|
||||
.l-shell__main-container {
|
||||
&[s-selected],
|
||||
&[s-selected-parent] {
|
||||
[s-selected],
|
||||
[s-selected-parent] {
|
||||
// Display grid and allow edit marquee to display in main layout holder when editing
|
||||
> .l-layout {
|
||||
background: $editUIGridColorBg;
|
||||
|
@ -101,7 +101,7 @@ export default {
|
||||
addChildren(domainObject) {
|
||||
let keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
let metadata = this.openmct.telemetry.getMetadata(domainObject);
|
||||
let metadataWithFilters = metadata.valueMetadatas.filter(value => value.filters);
|
||||
let metadataWithFilters = metadata ? metadata.valueMetadatas.filter(value => value.filters) : [];
|
||||
let hasFiltersWithKeyString = this.persistedFilters[keyString] !== undefined;
|
||||
let mutateFilters = false;
|
||||
let childObject = {
|
||||
|
@ -3,6 +3,7 @@
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
|
||||
&__item {
|
||||
flex: 0 0 auto;
|
||||
|
@ -27,7 +27,7 @@
|
||||
'c-hyperlink--button' : isButton
|
||||
}"
|
||||
:target="domainObject.linkTarget"
|
||||
:href="domainObject.url"
|
||||
:href="url"
|
||||
>
|
||||
<span class="c-hyperlink__label">{{ domainObject.displayText }}</span>
|
||||
</a>
|
||||
@ -35,6 +35,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const sanitizeUrl = require("@braintree/sanitize-url").sanitizeUrl;
|
||||
|
||||
export default {
|
||||
inject: ['domainObject'],
|
||||
@ -45,6 +46,9 @@ export default {
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
url() {
|
||||
return sanitizeUrl(this.domainObject.url);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -66,6 +66,10 @@ export default function ImageryTimestripViewProvider(openmct) {
|
||||
destroy: function () {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
},
|
||||
|
||||
getComponent() {
|
||||
return component;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -10,7 +10,14 @@ export default class ImageryView {
|
||||
this.component = undefined;
|
||||
}
|
||||
|
||||
show(element) {
|
||||
show(element, isEditing, viewOptions) {
|
||||
let alternateObjectPath;
|
||||
let indexForFocusedImage;
|
||||
if (viewOptions) {
|
||||
indexForFocusedImage = viewOptions.indexForFocusedImage;
|
||||
alternateObjectPath = viewOptions.objectPath;
|
||||
}
|
||||
|
||||
this.component = new Vue({
|
||||
el: element,
|
||||
components: {
|
||||
@ -19,10 +26,15 @@ export default class ImageryView {
|
||||
provide: {
|
||||
openmct: this.openmct,
|
||||
domainObject: this.domainObject,
|
||||
objectPath: this.objectPath,
|
||||
objectPath: alternateObjectPath || this.objectPath,
|
||||
currentView: this
|
||||
},
|
||||
template: '<imagery-view ref="ImageryContainer"></imagery-view>'
|
||||
data() {
|
||||
return {
|
||||
indexForFocusedImage
|
||||
};
|
||||
},
|
||||
template: '<imagery-view :index-for-focused-image="indexForFocusedImage" ref="ImageryContainer"></imagery-view>'
|
||||
|
||||
});
|
||||
}
|
||||
|
@ -39,10 +39,13 @@ describe("The Compass component", () => {
|
||||
sunAngle: 30
|
||||
};
|
||||
let propsData = {
|
||||
containerWidth: 600,
|
||||
containerHeight: 600,
|
||||
naturalAspectRatio: 0.9,
|
||||
image: imageDatum
|
||||
image: imageDatum,
|
||||
sizedImageDimensions: {
|
||||
width: 100,
|
||||
height: 100
|
||||
},
|
||||
compassRoseSizingClasses: '--rose-small --rose-min'
|
||||
};
|
||||
|
||||
app = new Vue({
|
||||
@ -51,13 +54,13 @@ describe("The Compass component", () => {
|
||||
return propsData;
|
||||
},
|
||||
template: `<Compass
|
||||
:container-width="containerWidth"
|
||||
:container-height="containerHeight"
|
||||
:compass-rose-sizing-classes="compassRoseSizingClasses"
|
||||
:image="image"
|
||||
:natural-aspect-ratio="naturalAspectRatio"
|
||||
:image="image" />`
|
||||
:sized-image-dimensions="sizedImageDimensions"
|
||||
/>`
|
||||
});
|
||||
instance = app.$mount();
|
||||
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
|
@ -41,7 +41,12 @@ import _ from "lodash";
|
||||
|
||||
const PADDING = 1;
|
||||
const ROW_HEIGHT = 100;
|
||||
const IMAGE_WIDTH_THRESHOLD = 40;
|
||||
const IMAGE_SIZE = 85;
|
||||
const IMAGE_WIDTH_THRESHOLD = 25;
|
||||
const CONTAINER_CLASS = 'c-imagery-tsv-container';
|
||||
const NO_ITEMS_CLASS = 'c-imagery-tsv__no-items';
|
||||
const IMAGE_WRAPPER_CLASS = 'c-imagery-tsv__image-wrapper';
|
||||
const ID_PREFIX = 'wrapper-';
|
||||
|
||||
export default {
|
||||
mixins: [imageryData],
|
||||
@ -78,10 +83,12 @@ export default {
|
||||
this.canvasContext = this.canvas.getContext('2d');
|
||||
this.setDimensions();
|
||||
|
||||
this.updateViewBounds();
|
||||
this.setScaleAndPlotImagery = this.setScaleAndPlotImagery.bind(this);
|
||||
this.updateViewBounds = this.updateViewBounds.bind(this);
|
||||
this.setTimeContext = this.setTimeContext.bind(this);
|
||||
this.setTimeContext();
|
||||
|
||||
this.openmct.time.on("timeSystem", this.setScaleAndPlotImagery);
|
||||
this.openmct.time.on("bounds", this.updateViewBounds);
|
||||
this.updateViewBounds();
|
||||
|
||||
this.resize = _.debounce(this.resize, 400);
|
||||
this.imageryStripResizeObserver = new ResizeObserver(this.resize);
|
||||
@ -90,25 +97,36 @@ export default {
|
||||
this.unlisten = this.openmct.objects.observe(this.domainObject, '*', this.observeForChanges);
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.unsubscribe) {
|
||||
this.unsubscribe();
|
||||
delete this.unsubscribe;
|
||||
}
|
||||
|
||||
if (this.imageryStripResizeObserver) {
|
||||
this.imageryStripResizeObserver.disconnect();
|
||||
}
|
||||
|
||||
this.openmct.time.off("timeSystem", this.setScaleAndPlotImagery);
|
||||
this.openmct.time.off("bounds", this.updateViewBounds);
|
||||
this.stopFollowingTimeContext();
|
||||
if (this.unlisten) {
|
||||
this.unlisten();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setTimeContext() {
|
||||
this.stopFollowingTimeContext();
|
||||
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
||||
this.timeContext.on("timeSystem", this.setScaleAndPlotImagery);
|
||||
this.timeContext.on("bounds", this.updateViewBounds);
|
||||
this.timeContext.on("timeContext", this.setTimeContext);
|
||||
},
|
||||
stopFollowingTimeContext() {
|
||||
if (this.timeContext) {
|
||||
this.timeContext.off("timeSystem", this.setScaleAndPlotImagery);
|
||||
this.timeContext.off("bounds", this.updateViewBounds);
|
||||
this.timeContext.off("timeContext", this.setTimeContext);
|
||||
}
|
||||
},
|
||||
expand(index) {
|
||||
const path = this.objectPath[0];
|
||||
this.previewAction.invoke([path]);
|
||||
this.previewAction.invoke([path], {
|
||||
indexForFocusedImage: index,
|
||||
objectPath: this.objectPath
|
||||
});
|
||||
},
|
||||
observeForChanges(mutatedObject) {
|
||||
this.updateViewBounds();
|
||||
@ -134,14 +152,10 @@ export default {
|
||||
return clientWidth;
|
||||
},
|
||||
updateViewBounds(bounds, isTick) {
|
||||
this.viewBounds = this.openmct.time.bounds();
|
||||
//Add a 50% padding to the end bounds to look ahead
|
||||
let timespan = (this.viewBounds.end - this.viewBounds.start);
|
||||
let padding = timespan / 2;
|
||||
this.viewBounds.end = this.viewBounds.end + padding;
|
||||
this.viewBounds = this.timeContext.bounds();
|
||||
|
||||
if (this.timeSystem === undefined) {
|
||||
this.timeSystem = this.openmct.time.timeSystem();
|
||||
this.timeSystem = this.timeContext.timeSystem();
|
||||
}
|
||||
|
||||
this.setScaleAndPlotImagery(this.timeSystem, !isTick);
|
||||
@ -172,18 +186,18 @@ export default {
|
||||
},
|
||||
clearPreviousImagery(clearAllImagery) {
|
||||
//TODO: Only clear items that are out of bounds
|
||||
let noItemsEl = this.$el.querySelectorAll(".c-imagery-tsv__no-items");
|
||||
let noItemsEl = this.$el.querySelectorAll(`.${NO_ITEMS_CLASS}`);
|
||||
noItemsEl.forEach(item => {
|
||||
item.remove();
|
||||
});
|
||||
let imagery = this.$el.querySelectorAll(".c-imagery-tsv__image-wrapper");
|
||||
let imagery = this.$el.querySelectorAll(`.${IMAGE_WRAPPER_CLASS}`);
|
||||
imagery.forEach(item => {
|
||||
if (clearAllImagery) {
|
||||
item.remove();
|
||||
} else {
|
||||
const id = this.getNSAttributesForElement(item, 'id');
|
||||
const id = item.getAttributeNS(null, 'id');
|
||||
if (id) {
|
||||
const timestamp = id.replace('id-', '');
|
||||
const timestamp = id.replace(ID_PREFIX, '');
|
||||
if (!this.isImageryInBounds({
|
||||
time: timestamp
|
||||
})) {
|
||||
@ -205,7 +219,7 @@ export default {
|
||||
}
|
||||
|
||||
if (timeSystem === undefined) {
|
||||
timeSystem = this.openmct.time.timeSystem();
|
||||
timeSystem = this.timeContext.timeSystem();
|
||||
}
|
||||
|
||||
if (timeSystem.isUTCBased) {
|
||||
@ -223,19 +237,17 @@ export default {
|
||||
this.xScale.range([PADDING, this.width - PADDING * 2]);
|
||||
},
|
||||
isImageryInBounds(imageObj) {
|
||||
return (imageObj.time < this.viewBounds.end) && (imageObj.time > this.viewBounds.start);
|
||||
return (imageObj.time <= this.viewBounds.end) && (imageObj.time >= this.viewBounds.start);
|
||||
},
|
||||
getImageryContainer() {
|
||||
let svgHeight = 100;
|
||||
let svgWidth = this.imageHistory.length ? this.width : 200;
|
||||
let groupSVG;
|
||||
let containerHeight = 100;
|
||||
let containerWidth = this.imageHistory.length ? this.width : 200;
|
||||
let imageryContainer;
|
||||
|
||||
let existingSVG = this.$el.querySelector(".c-imagery-tsv__contents svg");
|
||||
if (existingSVG) {
|
||||
groupSVG = existingSVG;
|
||||
this.setNSAttributesForElement(groupSVG, {
|
||||
width: svgWidth
|
||||
});
|
||||
let existingContainer = this.$el.querySelector(`.${CONTAINER_CLASS}`);
|
||||
if (existingContainer) {
|
||||
imageryContainer = existingContainer;
|
||||
imageryContainer.style.maxWidth = `${containerWidth}px`;
|
||||
} else {
|
||||
let component = new Vue({
|
||||
components: {
|
||||
@ -246,26 +258,20 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isNested: true,
|
||||
height: svgHeight,
|
||||
width: svgWidth
|
||||
isNested: true
|
||||
};
|
||||
},
|
||||
template: `<swim-lane :is-nested="isNested" :hide-label="true"><template slot="object"><svg class="c-imagery-tsv-container" :height="height" :width="width"></svg></template></swim-lane>`
|
||||
template: `<swim-lane :is-nested="isNested" :hide-label="true"><template slot="object"><div class="c-imagery-tsv-container"></div></template></swim-lane>`
|
||||
});
|
||||
|
||||
this.$refs.imageryHolder.appendChild(component.$mount().$el);
|
||||
|
||||
groupSVG = component.$el.querySelector('svg');
|
||||
|
||||
groupSVG.addEventListener('mouseout', (event) => {
|
||||
if (event.target.nodeName === 'svg' || event.target.nodeName === 'use') {
|
||||
this.removeFromForeground();
|
||||
}
|
||||
});
|
||||
imageryContainer = component.$el.querySelector(`.${CONTAINER_CLASS}`);
|
||||
imageryContainer.style.maxWidth = `${containerWidth}px`;
|
||||
imageryContainer.style.height = `${containerHeight}px`;
|
||||
}
|
||||
|
||||
return groupSVG;
|
||||
return imageryContainer;
|
||||
},
|
||||
isImageryWidthAcceptable() {
|
||||
// We're calculating if there is enough space between images to show the thumbnails.
|
||||
@ -281,194 +287,123 @@ export default {
|
||||
return imageContainerWidth < IMAGE_WIDTH_THRESHOLD;
|
||||
},
|
||||
drawImagery() {
|
||||
let groupSVG = this.getImageryContainer();
|
||||
let imageryContainer = this.getImageryContainer();
|
||||
const showImagePlaceholders = this.isImageryWidthAcceptable();
|
||||
|
||||
let index = 0;
|
||||
if (this.imageHistory.length) {
|
||||
this.imageHistory.forEach((currentImageObject, index) => {
|
||||
this.imageHistory.forEach((currentImageObject) => {
|
||||
if (this.isImageryInBounds(currentImageObject)) {
|
||||
this.plotImagery(currentImageObject, showImagePlaceholders, groupSVG, index);
|
||||
this.plotImagery(currentImageObject, showImagePlaceholders, imageryContainer, index);
|
||||
index = index + 1;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.plotNoItems(groupSVG);
|
||||
this.plotNoItems(imageryContainer);
|
||||
}
|
||||
},
|
||||
plotNoItems(svgElement) {
|
||||
let textElement = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
||||
this.setNSAttributesForElement(textElement, {
|
||||
x: "10",
|
||||
y: "20",
|
||||
class: "c-imagery-tsv__no-items"
|
||||
});
|
||||
plotNoItems(containerElement) {
|
||||
let textElement = document.createElement('text');
|
||||
textElement.classList.add(NO_ITEMS_CLASS);
|
||||
textElement.innerHTML = 'No images within timeframe';
|
||||
|
||||
svgElement.appendChild(textElement);
|
||||
containerElement.appendChild(textElement);
|
||||
},
|
||||
setNSAttributesForElement(element, attributes) {
|
||||
Object.keys(attributes).forEach((key) => {
|
||||
if (key === 'url') {
|
||||
element.setAttributeNS('http://www.w3.org/1999/xlink', 'href', attributes[key]);
|
||||
} else {
|
||||
element.setAttributeNS(null, key, attributes[key]);
|
||||
}
|
||||
});
|
||||
},
|
||||
getNSAttributesForElement(element, attribute) {
|
||||
return element.getAttributeNS(null, attribute);
|
||||
},
|
||||
getImageWrapper(item) {
|
||||
const id = `id-${item.time}`;
|
||||
|
||||
return this.$el.querySelector(`.c-imagery-tsv__contents g[id=${id}]`);
|
||||
},
|
||||
plotImagery(item, showImagePlaceholders, svgElement, index) {
|
||||
//TODO: Placeholder image
|
||||
let existingImageWrapper = this.getImageWrapper(item);
|
||||
//imageWrapper wraps the vertical tick rect and the image
|
||||
if (existingImageWrapper) {
|
||||
this.updateExistingImageWrapper(existingImageWrapper, item, showImagePlaceholders);
|
||||
} else {
|
||||
let imageWrapper = this.createImageWrapper(index, item, showImagePlaceholders, svgElement);
|
||||
svgElement.appendChild(imageWrapper);
|
||||
}
|
||||
},
|
||||
updateExistingImageWrapper(existingImageWrapper, item, showImagePlaceholders) {
|
||||
//Update the x co-ordinates of the handle and image elements and the url of image
|
||||
//this is to avoid tearing down all elements completely and re-drawing them
|
||||
this.setNSAttributesForElement(existingImageWrapper, {
|
||||
showImagePlaceholders
|
||||
});
|
||||
let imageTickElement = existingImageWrapper.querySelector('rect.c-imagery-tsv__image-handle');
|
||||
this.setNSAttributesForElement(imageTickElement, {
|
||||
x: this.xScale(item.time)
|
||||
});
|
||||
|
||||
let imageRect = existingImageWrapper.querySelector('rect.c-imagery-tsv__image-placeholder');
|
||||
this.setNSAttributesForElement(imageRect, {
|
||||
x: this.xScale(item.time) + 2
|
||||
});
|
||||
|
||||
let imageElement = existingImageWrapper.querySelector('image');
|
||||
const selector = `href*=${existingImageWrapper.id}`;
|
||||
let hoverEl = this.$el.querySelector(`.c-imagery-tsv__contents use[${selector}]`);
|
||||
const hideImageUrl = (showImagePlaceholders && !hoverEl);
|
||||
this.setNSAttributesForElement(imageElement, {
|
||||
x: this.xScale(item.time) + 2,
|
||||
url: hideImageUrl ? '' : item.url
|
||||
});
|
||||
},
|
||||
createImageWrapper(index, item, showImagePlaceholders, svgElement) {
|
||||
const id = `id-${item.time}`;
|
||||
const imgSize = String(ROW_HEIGHT - 15);
|
||||
let imageWrapper = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
||||
this.setNSAttributesForElement(imageWrapper, {
|
||||
id,
|
||||
class: 'c-imagery-tsv__image-wrapper',
|
||||
showImagePlaceholders
|
||||
});
|
||||
//create image tick indicator
|
||||
let imageTickElement = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
||||
this.setNSAttributesForElement(imageTickElement, {
|
||||
class: 'c-imagery-tsv__image-handle',
|
||||
x: this.xScale(item.time),
|
||||
y: 5,
|
||||
rx: 0,
|
||||
width: 2,
|
||||
height: String(ROW_HEIGHT - 10)
|
||||
});
|
||||
imageWrapper.appendChild(imageTickElement);
|
||||
|
||||
let imageRect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
||||
this.setNSAttributesForElement(imageRect, {
|
||||
class: 'c-imagery-tsv__image-placeholder',
|
||||
x: this.xScale(item.time) + 2,
|
||||
y: 10,
|
||||
rx: 0,
|
||||
width: imgSize,
|
||||
height: imgSize,
|
||||
mask: `#image-${item.time}`
|
||||
});
|
||||
imageWrapper.appendChild(imageRect);
|
||||
|
||||
let imageElement = document.createElementNS('http://www.w3.org/2000/svg', 'image');
|
||||
this.setNSAttributesForElement(imageElement, {
|
||||
id: `image-${item.time}`,
|
||||
x: this.xScale(item.time) + 2,
|
||||
y: 10,
|
||||
rx: 0,
|
||||
width: imgSize,
|
||||
height: imgSize,
|
||||
url: showImagePlaceholders ? '' : item.url
|
||||
});
|
||||
imageWrapper.appendChild(imageElement);
|
||||
|
||||
//TODO: Don't add the hover listener if the width is too small
|
||||
imageWrapper.addEventListener('mouseover', this.bringToForeground.bind(this, svgElement, imageWrapper, index, item.url));
|
||||
|
||||
return imageWrapper;
|
||||
},
|
||||
bringToForeground(svgElement, imageWrapper, index, url, event) {
|
||||
const selector = `href*=${imageWrapper.id}`;
|
||||
let hoverEls = this.$el.querySelectorAll(`.c-imagery-tsv__contents use:not([${selector}])`);
|
||||
if (hoverEls.length > 0) {
|
||||
this.removeFromForeground(hoverEls);
|
||||
}
|
||||
|
||||
hoverEls = this.$el.querySelectorAll(`.c-imagery-tsv__contents use[${selector}]`);
|
||||
if (hoverEls.length) {
|
||||
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
let imageElement = imageWrapper.querySelector('image');
|
||||
Object.keys(attributes).forEach((key) => {
|
||||
element.setAttributeNS(null, key, attributes[key]);
|
||||
});
|
||||
},
|
||||
setStyles(element, styles) {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(styles).forEach((key) => {
|
||||
element.style[key] = styles[key];
|
||||
});
|
||||
},
|
||||
getImageWrapper(item) {
|
||||
const id = `${ID_PREFIX}${item.time}`;
|
||||
|
||||
return this.$el.querySelector(`.c-imagery-tsv__contents div[id=${id}]`);
|
||||
},
|
||||
plotImagery(item, showImagePlaceholders, containerElement, index) {
|
||||
let existingImageWrapper = this.getImageWrapper(item);
|
||||
//imageWrapper wraps the vertical tick and the image
|
||||
if (existingImageWrapper) {
|
||||
this.updateExistingImageWrapper(existingImageWrapper, item, showImagePlaceholders);
|
||||
} else {
|
||||
let imageWrapper = this.createImageWrapper(index, item, showImagePlaceholders);
|
||||
containerElement.appendChild(imageWrapper);
|
||||
}
|
||||
},
|
||||
setImageDisplay(imageElement, showImagePlaceholders) {
|
||||
if (showImagePlaceholders) {
|
||||
imageElement.style.display = 'none';
|
||||
} else {
|
||||
imageElement.style.display = 'block';
|
||||
}
|
||||
},
|
||||
updateExistingImageWrapper(existingImageWrapper, item, showImagePlaceholders) {
|
||||
//Update the x co-ordinates of the image wrapper and the url of image
|
||||
//this is to avoid tearing down all elements completely and re-drawing them
|
||||
this.setNSAttributesForElement(existingImageWrapper, {
|
||||
'data-show-image-placeholders': showImagePlaceholders
|
||||
});
|
||||
existingImageWrapper.style.left = `${this.xScale(item.time)}px`;
|
||||
|
||||
let imageElement = existingImageWrapper.querySelector('img');
|
||||
this.setNSAttributesForElement(imageElement, {
|
||||
url: url,
|
||||
fill: 'none'
|
||||
});
|
||||
let hoverElement = document.createElementNS('http://www.w3.org/2000/svg', 'use');
|
||||
this.setNSAttributesForElement(hoverElement, {
|
||||
class: 'image-highlight',
|
||||
x: 0,
|
||||
href: `#${imageWrapper.id}`
|
||||
src: item.url
|
||||
});
|
||||
this.setImageDisplay(imageElement, showImagePlaceholders);
|
||||
},
|
||||
createImageWrapper(index, item, showImagePlaceholders) {
|
||||
const id = `${ID_PREFIX}${item.time}`;
|
||||
let imageWrapper = document.createElement('div');
|
||||
imageWrapper.classList.add(IMAGE_WRAPPER_CLASS);
|
||||
imageWrapper.style.left = `${this.xScale(item.time)}px`;
|
||||
this.setNSAttributesForElement(imageWrapper, {
|
||||
class: 'c-imagery-tsv__image-wrapper is-hovered'
|
||||
id,
|
||||
'data-show-image-placeholders': showImagePlaceholders
|
||||
});
|
||||
// We're using mousedown here and not 'click' because 'click' doesn't seem to be triggered reliably
|
||||
hoverElement.addEventListener('mousedown', (e) => {
|
||||
//create image vertical tick indicator
|
||||
let imageTickElement = document.createElement('div');
|
||||
imageTickElement.classList.add('c-imagery-tsv__image-handle');
|
||||
imageTickElement.style.width = '2px';
|
||||
imageTickElement.style.height = `${String(ROW_HEIGHT - 10)}px`;
|
||||
imageWrapper.appendChild(imageTickElement);
|
||||
|
||||
//create placeholder - this will also hold the actual image
|
||||
let imagePlaceholder = document.createElement('div');
|
||||
imagePlaceholder.classList.add('c-imagery-tsv__image-placeholder');
|
||||
imagePlaceholder.style.width = `${IMAGE_SIZE}px`;
|
||||
imagePlaceholder.style.height = `${IMAGE_SIZE}px`;
|
||||
imageWrapper.appendChild(imagePlaceholder);
|
||||
|
||||
//create image element
|
||||
let imageElement = document.createElement('img');
|
||||
this.setNSAttributesForElement(imageElement, {
|
||||
src: item.url
|
||||
});
|
||||
imageElement.style.width = `${IMAGE_SIZE}px`;
|
||||
imageElement.style.height = `${IMAGE_SIZE}px`;
|
||||
this.setImageDisplay(imageElement, showImagePlaceholders);
|
||||
|
||||
//handle mousedown event to show the image in a large view
|
||||
imageWrapper.addEventListener('mousedown', (e) => {
|
||||
if (e.button === 0) {
|
||||
this.expand(index);
|
||||
}
|
||||
});
|
||||
|
||||
svgElement.appendChild(hoverElement);
|
||||
imagePlaceholder.appendChild(imageElement);
|
||||
|
||||
},
|
||||
removeFromForeground(items) {
|
||||
let hoverEls;
|
||||
if (items) {
|
||||
hoverEls = items;
|
||||
} else {
|
||||
hoverEls = this.$el.querySelectorAll(".c-imagery-tsv__contents use");
|
||||
}
|
||||
|
||||
hoverEls.forEach(item => {
|
||||
let selector = `id*=${this.getNSAttributesForElement(item, 'href').replace('#', '')}`;
|
||||
let imageWrapper = this.$el.querySelector(`.c-imagery-tsv__contents g[${selector}]`);
|
||||
this.setNSAttributesForElement(imageWrapper, {
|
||||
class: 'c-imagery-tsv__image-wrapper'
|
||||
});
|
||||
let showImagePlaceholders = this.getNSAttributesForElement(imageWrapper, 'showImagePlaceholders');
|
||||
if (showImagePlaceholders === 'true') {
|
||||
let imageElement = imageWrapper.querySelector('image');
|
||||
this.setNSAttributesForElement(imageElement, {
|
||||
url: ''
|
||||
});
|
||||
}
|
||||
|
||||
item.remove();
|
||||
});
|
||||
return imageWrapper;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -57,7 +57,7 @@
|
||||
</div>
|
||||
<div ref="imageBG"
|
||||
class="c-imagery__main-image__bg"
|
||||
:class="{'paused unnsynced': isPaused,'stale':false }"
|
||||
:class="{'paused unnsynced': isPaused && !isFixed,'stale':false }"
|
||||
@click="expand"
|
||||
>
|
||||
<div class="image-wrapper"
|
||||
@ -84,18 +84,18 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-buttons">
|
||||
<button class="c-nav c-nav--prev"
|
||||
title="Previous image"
|
||||
:disabled="isPrevDisabled"
|
||||
@click="prevImage()"
|
||||
></button>
|
||||
<button class="c-nav c-nav--next"
|
||||
title="Next image"
|
||||
:disabled="isNextDisabled"
|
||||
@click="nextImage()"
|
||||
></button>
|
||||
</div>
|
||||
|
||||
<button class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-button c-nav c-nav--prev"
|
||||
title="Previous image"
|
||||
:disabled="isPrevDisabled"
|
||||
@click="prevImage()"
|
||||
></button>
|
||||
|
||||
<button class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-button c-nav c-nav--next"
|
||||
title="Next image"
|
||||
:disabled="isNextDisabled"
|
||||
@click="nextImage()"
|
||||
></button>
|
||||
|
||||
<div class="c-imagery__control-bar">
|
||||
<div class="c-imagery__time">
|
||||
@ -122,6 +122,7 @@
|
||||
</div>
|
||||
<div class="h-local-controls">
|
||||
<button
|
||||
v-if="!isFixed"
|
||||
class="c-button icon-pause pause-play"
|
||||
:class="{'is-paused': isPaused}"
|
||||
@click="paused(!isPaused, 'button')"
|
||||
@ -131,7 +132,7 @@
|
||||
</div>
|
||||
<div class="c-imagery__thumbs-wrapper"
|
||||
:class="[
|
||||
{ 'is-paused': isPaused },
|
||||
{ 'is-paused': isPaused && !isFixed },
|
||||
{ 'is-autoscroll-off': !resizingWindow && !autoScroll && !isPaused }
|
||||
]"
|
||||
>
|
||||
@ -199,6 +200,14 @@ export default {
|
||||
},
|
||||
mixins: [imageryData],
|
||||
inject: ['openmct', 'domainObject', 'objectPath', 'currentView'],
|
||||
props: {
|
||||
indexForFocusedImage: {
|
||||
type: Number,
|
||||
default() {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
let timeSystem = this.openmct.time.timeSystem();
|
||||
this.metadata = {};
|
||||
@ -226,7 +235,8 @@ export default {
|
||||
imageContainerWidth: undefined,
|
||||
imageContainerHeight: undefined,
|
||||
lockCompass: true,
|
||||
resizingWindow: false
|
||||
resizingWindow: false,
|
||||
timeContext: undefined
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -258,7 +268,14 @@ export default {
|
||||
return age < cutoff && !this.refreshCSS;
|
||||
},
|
||||
canTrackDuration() {
|
||||
return this.openmct.time.clock() && this.timeSystem.isUTCBased;
|
||||
let hasClock;
|
||||
if (this.timeContext) {
|
||||
hasClock = this.timeContext.clock();
|
||||
} else {
|
||||
hasClock = this.openmct.time.clock();
|
||||
}
|
||||
|
||||
return hasClock && this.timeSystem.isUTCBased;
|
||||
},
|
||||
isNextDisabled() {
|
||||
let disabled = false;
|
||||
@ -379,11 +396,28 @@ export default {
|
||||
}
|
||||
|
||||
return sizedImageDimensions;
|
||||
},
|
||||
isFixed() {
|
||||
let clock;
|
||||
if (this.timeContext) {
|
||||
clock = this.timeContext.clock();
|
||||
} else {
|
||||
clock = this.openmct.time.clock();
|
||||
}
|
||||
|
||||
return clock === undefined;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
imageHistorySize(newSize, oldSize) {
|
||||
this.setFocusedImage(newSize - 1, false);
|
||||
let imageIndex;
|
||||
if (this.indexForFocusedImage !== undefined) {
|
||||
imageIndex = this.initFocusedImageIndex;
|
||||
} else {
|
||||
imageIndex = newSize - 1;
|
||||
}
|
||||
|
||||
this.setFocusedImage(imageIndex, false);
|
||||
this.scrollToRight();
|
||||
},
|
||||
focusedImageIndex() {
|
||||
@ -394,9 +428,14 @@ export default {
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
//listen
|
||||
this.openmct.time.on('timeSystem', this.trackDuration);
|
||||
this.openmct.time.on('clock', this.trackDuration);
|
||||
//We only need to use this till the user focuses an image manually
|
||||
if (this.indexForFocusedImage !== undefined) {
|
||||
this.initFocusedImageIndex = this.indexForFocusedImage;
|
||||
this.isPaused = true;
|
||||
}
|
||||
|
||||
this.setTimeContext = this.setTimeContext.bind(this);
|
||||
this.setTimeContext();
|
||||
|
||||
// related telemetry keys
|
||||
this.spacecraftPositionKeys = ['positionX', 'positionY', 'positionZ'];
|
||||
@ -432,8 +471,7 @@ export default {
|
||||
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.openmct.time.off('timeSystem', this.trackDuration);
|
||||
this.openmct.time.off('clock', this.trackDuration);
|
||||
this.stopFollowingTimeContext();
|
||||
|
||||
if (this.thumbWrapperResizeObserver) {
|
||||
this.thumbWrapperResizeObserver.disconnect();
|
||||
@ -457,6 +495,21 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setTimeContext() {
|
||||
this.stopFollowingTimeContext();
|
||||
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
||||
//listen
|
||||
this.timeContext.on('timeSystem', this.trackDuration);
|
||||
this.timeContext.on('clock', this.trackDuration);
|
||||
this.timeContext.on("timeContext", this.setTimeContext);
|
||||
},
|
||||
stopFollowingTimeContext() {
|
||||
if (this.timeContext) {
|
||||
this.timeContext.off("timeSystem", this.trackDuration);
|
||||
this.timeContext.off("clock", this.trackDuration);
|
||||
this.timeContext.off("timeContext", this.setTimeContext);
|
||||
}
|
||||
},
|
||||
expand() {
|
||||
const actionCollection = this.openmct.actions.getActionsCollection(this.objectPath, this.currentView);
|
||||
const visibleActions = actionCollection.getVisibleActions();
|
||||
@ -618,7 +671,12 @@ export default {
|
||||
});
|
||||
},
|
||||
setFocusedImage(index, thumbnailClick = false) {
|
||||
if (this.isPaused && !thumbnailClick) {
|
||||
if (thumbnailClick) {
|
||||
//We use the props till the user changes what they want to see
|
||||
this.initFocusedImageIndex = undefined;
|
||||
}
|
||||
|
||||
if (this.isPaused && !thumbnailClick && this.initFocusedImageIndex === undefined) {
|
||||
this.nextImageIndex = index;
|
||||
//this could happen if bounds changes
|
||||
if (this.focusedImageIndex > this.imageHistory.length - 1) {
|
||||
@ -649,8 +707,12 @@ export default {
|
||||
window.clearInterval(this.durationTracker);
|
||||
},
|
||||
updateDuration() {
|
||||
let currentTime = this.openmct.time.clock() && this.openmct.time.clock().currentValue();
|
||||
this.numericDuration = currentTime - this.parsedSelectedTime;
|
||||
let currentTime = this.timeContext.clock() && this.timeContext.clock().currentValue();
|
||||
if (currentTime === undefined) {
|
||||
this.numericDuration = currentTime;
|
||||
} else {
|
||||
this.numericDuration = currentTime - this.parsedSelectedTime;
|
||||
}
|
||||
},
|
||||
resetAgeCSS() {
|
||||
this.refreshCSS = true;
|
||||
|
@ -285,17 +285,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
.c-imagery__prev-next-buttons {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
pointer-events: none;
|
||||
.c-imagery__prev-next-button {
|
||||
pointer-events: all;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-75%);
|
||||
transform: translateY(-75%); // 75% due to transform: rotation approach to the button
|
||||
|
||||
.c-nav {
|
||||
pointer-events: all;
|
||||
&.c-nav {
|
||||
position: absolute;
|
||||
|
||||
&--prev { left: 0; }
|
||||
&--next { right: 0; }
|
||||
}
|
||||
|
||||
.s-status-taking-snapshot & {
|
||||
@ -315,13 +315,31 @@
|
||||
|
||||
/*************************************** IMAGERY IN TIMESTRIP VIEWS */
|
||||
.c-imagery-tsv {
|
||||
g.c-imagery-tsv__image-wrapper {
|
||||
div.c-imagery-tsv__image-wrapper {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
display: flex;
|
||||
z-index: 1;
|
||||
margin-top: 5px;
|
||||
|
||||
img {
|
||||
align-self: flex-end;
|
||||
}
|
||||
&:hover {
|
||||
z-index: 2;
|
||||
|
||||
&.is-hovered {
|
||||
filter: brightness(1) contrast(1) !important;
|
||||
[class*='__image-handle'] {
|
||||
fill: $colorBodyFg;
|
||||
background-color: $colorBodyFg;
|
||||
}
|
||||
|
||||
//[class*='__image-placeholder'] {
|
||||
// display: none;
|
||||
//}
|
||||
|
||||
img {
|
||||
display: block !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -331,14 +349,16 @@
|
||||
}
|
||||
|
||||
&__image-handle {
|
||||
fill: rgba($colorBodyFg, 0.5);
|
||||
background-color: rgba($colorBodyFg, 0.5);
|
||||
}
|
||||
|
||||
&__image-placeholder {
|
||||
fill: pushBack($colorBodyBg, 0.3);
|
||||
background-color: pushBack($colorBodyBg, 0.3);
|
||||
display: block;
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
&:hover g.c-imagery-tsv__image-wrapper {
|
||||
&:hover div.c-imagery-tsv__image-wrapper {
|
||||
// TODO CH: convert to theme constants
|
||||
filter: brightness(0.5) contrast(0.7);
|
||||
}
|
||||
|
@ -26,8 +26,10 @@ export default {
|
||||
inject: ['openmct', 'domainObject', 'objectPath'],
|
||||
mounted() {
|
||||
// listen
|
||||
this.openmct.time.on('bounds', this.boundsChange);
|
||||
this.openmct.time.on('timeSystem', this.timeSystemChange);
|
||||
this.boundsChange = this.boundsChange.bind(this);
|
||||
this.timeSystemChange = this.timeSystemChange.bind(this);
|
||||
this.setDataTimeContext = this.setDataTimeContext.bind(this);
|
||||
this.setDataTimeContext();
|
||||
|
||||
// set
|
||||
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
|
||||
@ -51,10 +53,24 @@ export default {
|
||||
delete this.unsubscribe;
|
||||
}
|
||||
|
||||
this.openmct.time.off('bounds', this.boundsChange);
|
||||
this.openmct.time.off('timeSystem', this.timeSystemChange);
|
||||
this.stopFollowingDataTimeContext();
|
||||
},
|
||||
methods: {
|
||||
setDataTimeContext() {
|
||||
this.stopFollowingDataTimeContext();
|
||||
this.timeContext = this.openmct.time.getContextForView(this.objectPath);
|
||||
this.timeContext.on('bounds', this.boundsChange);
|
||||
this.boundsChange(this.timeContext.bounds());
|
||||
this.timeContext.on('timeSystem', this.timeSystemChange);
|
||||
this.timeContext.on("timeContext", this.setDataTimeContext);
|
||||
},
|
||||
stopFollowingDataTimeContext() {
|
||||
if (this.timeContext) {
|
||||
this.timeContext.off('bounds', this.boundsChange);
|
||||
this.timeContext.off('timeSystem', this.timeSystemChange);
|
||||
this.timeContext.off("timeContext", this.setDataTimeContext);
|
||||
}
|
||||
},
|
||||
datumIsNotValid(datum) {
|
||||
if (this.imageHistory.length === 0) {
|
||||
return false;
|
||||
@ -111,7 +127,7 @@ export default {
|
||||
}
|
||||
},
|
||||
async requestHistory() {
|
||||
let bounds = this.openmct.time.bounds();
|
||||
let bounds = this.timeContext.bounds();
|
||||
this.requestCount++;
|
||||
const requestId = this.requestCount;
|
||||
this.imageHistory = [];
|
||||
@ -132,7 +148,7 @@ export default {
|
||||
}
|
||||
},
|
||||
timeSystemChange() {
|
||||
this.timeSystem = this.openmct.time.timeSystem();
|
||||
this.timeSystem = this.timeContext.timeSystem();
|
||||
this.timeKey = this.timeSystem.key;
|
||||
this.timeFormatter = this.getFormatter(this.timeKey);
|
||||
this.durationFormatter = this.getFormatter(this.timeSystem.durationFormat || DEFAULT_DURATION_FORMATTER);
|
||||
@ -141,7 +157,7 @@ export default {
|
||||
this.unsubscribe = this.openmct.telemetry
|
||||
.subscribe(this.domainObject, (datum) => {
|
||||
let parsedTimestamp = this.parseTime(datum);
|
||||
let bounds = this.openmct.time.bounds();
|
||||
let bounds = this.timeContext.bounds();
|
||||
|
||||
if (parsedTimestamp >= bounds.start && parsedTimestamp <= bounds.end) {
|
||||
let image = this.normalizeDatum(datum);
|
||||
@ -159,7 +175,7 @@ export default {
|
||||
let image = { ...datum };
|
||||
image.formattedTime = this.formatTime(datum);
|
||||
image.url = this.formatImageUrl(datum);
|
||||
image.time = datum[this.timeKey];
|
||||
image.time = this.parseTime(image.formattedTime);
|
||||
image.imageDownloadName = this.getImageDownloadName(datum);
|
||||
|
||||
return image;
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
import Vue from 'vue';
|
||||
import {
|
||||
createMouseEvent,
|
||||
createOpenMct,
|
||||
resetApplicationState,
|
||||
simulateKeyEvent
|
||||
@ -32,19 +33,6 @@ const TEN_MINUTES = ONE_MINUTE * 10;
|
||||
const MAIN_IMAGE_CLASS = '.js-imageryView-image';
|
||||
const NEW_IMAGE_CLASS = '.c-imagery__age.c-imagery--new';
|
||||
const REFRESH_CSS_MS = 500;
|
||||
// const TOLERANCE = 0.50;
|
||||
|
||||
// function comparisonFunction(valueOne, valueTwo) {
|
||||
// let larger = valueOne;
|
||||
// let smaller = valueTwo;
|
||||
//
|
||||
// if (larger < smaller) {
|
||||
// larger = valueTwo;
|
||||
// smaller = valueOne;
|
||||
// }
|
||||
//
|
||||
// return (larger - smaller) < TOLERANCE;
|
||||
// }
|
||||
|
||||
function getImageInfo(doc) {
|
||||
let imageElement = doc.querySelectorAll(MAIN_IMAGE_CLASS)[0];
|
||||
@ -90,11 +78,13 @@ describe("The Imagery View Layouts", () => {
|
||||
const START = Date.now();
|
||||
const COUNT = 10;
|
||||
|
||||
let resolveFunction;
|
||||
// let resolveFunction;
|
||||
let originalRouterPath;
|
||||
let telemetryPromise;
|
||||
let telemetryPromiseResolve;
|
||||
let cleanupFirst;
|
||||
|
||||
let openmct;
|
||||
let appHolder;
|
||||
let parent;
|
||||
let child;
|
||||
let imageTelemetry = generateTelemetry(START - TEN_MINUTES, COUNT);
|
||||
@ -198,44 +188,63 @@ describe("The Imagery View Layouts", () => {
|
||||
|
||||
// this setups up the app
|
||||
beforeEach((done) => {
|
||||
appHolder = document.createElement('div');
|
||||
appHolder.style.width = '640px';
|
||||
appHolder.style.height = '480px';
|
||||
cleanupFirst = [];
|
||||
|
||||
openmct = createOpenMct();
|
||||
openmct.time.timeSystem('utc', {
|
||||
start: START - (5 * ONE_MINUTE),
|
||||
end: START + (5 * ONE_MINUTE)
|
||||
});
|
||||
|
||||
openmct.install(openmct.plugins.MyItems());
|
||||
openmct.install(openmct.plugins.LocalTimeSystem());
|
||||
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||
telemetryPromise = new Promise((resolve) => {
|
||||
telemetryPromiseResolve = resolve;
|
||||
});
|
||||
|
||||
spyOn(openmct.telemetry, 'request').and.callFake(() => {
|
||||
telemetryPromiseResolve(imageTelemetry);
|
||||
|
||||
return telemetryPromise;
|
||||
});
|
||||
|
||||
parent = document.createElement('div');
|
||||
child = document.createElement('div');
|
||||
parent.appendChild(child);
|
||||
parent.style.width = '640px';
|
||||
parent.style.height = '480px';
|
||||
|
||||
// document.querySelector('body').append(parent);
|
||||
child = document.createElement('div');
|
||||
child.style.width = '640px';
|
||||
child.style.height = '480px';
|
||||
|
||||
parent.appendChild(child);
|
||||
document.body.appendChild(parent);
|
||||
|
||||
spyOn(window, 'ResizeObserver').and.returnValue({
|
||||
observe() {},
|
||||
disconnect() {}
|
||||
});
|
||||
|
||||
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
|
||||
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
|
||||
//spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
|
||||
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve(imageryObject));
|
||||
|
||||
originalRouterPath = openmct.router.path;
|
||||
|
||||
openmct.on('start', done);
|
||||
openmct.start(appHolder);
|
||||
openmct.startHeadless();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
openmct.time.timeSystem('utc', {
|
||||
start: 0,
|
||||
end: 1
|
||||
});
|
||||
afterEach((done) => {
|
||||
openmct.router.path = originalRouterPath;
|
||||
|
||||
return resetApplicationState(openmct);
|
||||
// Needs to be in a timeout because plots use a bunch of setTimeouts, some of which can resolve during or after
|
||||
// teardown, which causes problems
|
||||
// This is hacky, we should find a better approach here.
|
||||
setTimeout(() => {
|
||||
//Cleanup code that needs to happen before dom elements start being destroyed
|
||||
cleanupFirst.forEach(cleanup => cleanup());
|
||||
cleanupFirst = [];
|
||||
document.body.removeChild(parent);
|
||||
|
||||
resetApplicationState(openmct).then(done).catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it("should provide an imagery time strip view when in a time strip", () => {
|
||||
@ -262,7 +271,7 @@ describe("The Imagery View Layouts", () => {
|
||||
});
|
||||
|
||||
it("should provide an imagery view only for imagery producing objects", () => {
|
||||
let applicableViews = openmct.objectViews.get(imageryObject, []);
|
||||
let applicableViews = openmct.objectViews.get(imageryObject, [imageryObject]);
|
||||
let imageryView = applicableViews.find(
|
||||
viewProvider => viewProvider.key === imageryKey
|
||||
);
|
||||
@ -315,51 +324,53 @@ describe("The Imagery View Layouts", () => {
|
||||
let imageryViewProvider;
|
||||
let imageryView;
|
||||
|
||||
beforeEach(async () => {
|
||||
let telemetryRequestResolve;
|
||||
let telemetryRequestPromise = new Promise((resolve) => {
|
||||
telemetryRequestResolve = resolve;
|
||||
});
|
||||
beforeEach(() => {
|
||||
|
||||
openmct.telemetry.request.and.callFake(() => {
|
||||
telemetryRequestResolve(imageTelemetry);
|
||||
|
||||
return telemetryRequestPromise;
|
||||
});
|
||||
|
||||
applicableViews = openmct.objectViews.get(imageryObject, []);
|
||||
applicableViews = openmct.objectViews.get(imageryObject, [imageryObject]);
|
||||
imageryViewProvider = applicableViews.find(viewProvider => viewProvider.key === imageryKey);
|
||||
imageryView = imageryViewProvider.view(imageryObject);
|
||||
imageryView = imageryViewProvider.view(imageryObject, [imageryObject]);
|
||||
imageryView.show(child);
|
||||
|
||||
await telemetryRequestPromise;
|
||||
return Vue.nextTick();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
openmct.time.stopClock();
|
||||
openmct.router.removeListener('change:hash', resolveFunction);
|
||||
// afterEach(() => {
|
||||
// openmct.time.stopClock();
|
||||
// openmct.router.removeListener('change:hash', resolveFunction);
|
||||
//
|
||||
// imageryView.destroy();
|
||||
// });
|
||||
|
||||
imageryView.destroy();
|
||||
});
|
||||
|
||||
it("on mount should show the the most recent image", () => {
|
||||
const imageInfo = getImageInfo(parent);
|
||||
|
||||
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 1].timeId)).not.toEqual(-1);
|
||||
});
|
||||
|
||||
xit("should show the clicked thumbnail as the main image", (done) => {
|
||||
const target = imageTelemetry[5].url;
|
||||
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
||||
it("on mount should show the the most recent image", (done) => {
|
||||
//Looks like we need Vue.nextTick here so that computed properties settle down
|
||||
Vue.nextTick(() => {
|
||||
const imageInfo = getImageInfo(parent);
|
||||
|
||||
expect(imageInfo.url.indexOf(imageTelemetry[5].timeId)).not.toEqual(-1);
|
||||
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 1].timeId)).not.toEqual(-1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should show the clicked thumbnail as the main image", (done) => {
|
||||
//Looks like we need Vue.nextTick here so that computed properties settle down
|
||||
Vue.nextTick(() => {
|
||||
const target = imageTelemetry[5].url;
|
||||
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
||||
Vue.nextTick(() => {
|
||||
const imageInfo = getImageInfo(parent);
|
||||
|
||||
expect(imageInfo.url.indexOf(imageTelemetry[5].timeId)).not.toEqual(-1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
xit("should show that an image is new", (done) => {
|
||||
openmct.time.clock('local', {
|
||||
start: -1000,
|
||||
end: 1000
|
||||
});
|
||||
|
||||
Vue.nextTick(() => {
|
||||
// used in code, need to wait to the 500ms here too
|
||||
setTimeout(() => {
|
||||
@ -370,84 +381,162 @@ describe("The Imagery View Layouts", () => {
|
||||
});
|
||||
});
|
||||
|
||||
xit("should show that an image is not new", (done) => {
|
||||
const target = imageTelemetry[2].url;
|
||||
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
||||
|
||||
it("should show that an image is not new", (done) => {
|
||||
Vue.nextTick(() => {
|
||||
// used in code, need to wait to the 500ms here too
|
||||
setTimeout(() => {
|
||||
const target = imageTelemetry[2].url;
|
||||
parent.querySelectorAll(`img[src='${target}']`)[0].click();
|
||||
|
||||
Vue.nextTick(() => {
|
||||
const imageIsNew = isNew(parent);
|
||||
|
||||
expect(imageIsNew).toBeFalse();
|
||||
done();
|
||||
}, REFRESH_CSS_MS);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
xit("should navigate via arrow keys", (done) => {
|
||||
let keyOpts = {
|
||||
element: parent.querySelector('.c-imagery'),
|
||||
key: 'ArrowLeft',
|
||||
keyCode: 37,
|
||||
type: 'keyup'
|
||||
};
|
||||
|
||||
simulateKeyEvent(keyOpts);
|
||||
|
||||
it("should navigate via arrow keys", (done) => {
|
||||
Vue.nextTick(() => {
|
||||
const imageInfo = getImageInfo(parent);
|
||||
let keyOpts = {
|
||||
element: parent.querySelector('.c-imagery'),
|
||||
key: 'ArrowLeft',
|
||||
keyCode: 37,
|
||||
type: 'keyup'
|
||||
};
|
||||
|
||||
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 2].timeId)).not.toEqual(-1);
|
||||
done();
|
||||
simulateKeyEvent(keyOpts);
|
||||
|
||||
Vue.nextTick(() => {
|
||||
const imageInfo = getImageInfo(parent);
|
||||
|
||||
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 2].timeId)).not.toEqual(-1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should navigate via numerous arrow keys", (done) => {
|
||||
let element = parent.querySelector('.c-imagery');
|
||||
let type = 'keyup';
|
||||
let leftKeyOpts = {
|
||||
element,
|
||||
type,
|
||||
key: 'ArrowLeft',
|
||||
keyCode: 37
|
||||
};
|
||||
let rightKeyOpts = {
|
||||
element,
|
||||
type,
|
||||
key: 'ArrowRight',
|
||||
keyCode: 39
|
||||
};
|
||||
|
||||
// left thrice
|
||||
simulateKeyEvent(leftKeyOpts);
|
||||
simulateKeyEvent(leftKeyOpts);
|
||||
simulateKeyEvent(leftKeyOpts);
|
||||
// right once
|
||||
simulateKeyEvent(rightKeyOpts);
|
||||
|
||||
Vue.nextTick(() => {
|
||||
const imageInfo = getImageInfo(parent);
|
||||
let element = parent.querySelector('.c-imagery');
|
||||
let type = 'keyup';
|
||||
let leftKeyOpts = {
|
||||
element,
|
||||
type,
|
||||
key: 'ArrowLeft',
|
||||
keyCode: 37
|
||||
};
|
||||
let rightKeyOpts = {
|
||||
element,
|
||||
type,
|
||||
key: 'ArrowRight',
|
||||
keyCode: 39
|
||||
};
|
||||
|
||||
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 3].timeId)).not.toEqual(-1);
|
||||
// left thrice
|
||||
simulateKeyEvent(leftKeyOpts);
|
||||
simulateKeyEvent(leftKeyOpts);
|
||||
simulateKeyEvent(leftKeyOpts);
|
||||
// right once
|
||||
simulateKeyEvent(rightKeyOpts);
|
||||
|
||||
Vue.nextTick(() => {
|
||||
const imageInfo = getImageInfo(parent);
|
||||
|
||||
expect(imageInfo.url.indexOf(imageTelemetry[COUNT - 3].timeId)).not.toEqual(-1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it ('shows an auto scroll button when scroll to left', (done) => {
|
||||
Vue.nextTick(() => {
|
||||
// to mock what a scroll would do
|
||||
imageryView._getInstance().$refs.ImageryContainer.autoScroll = false;
|
||||
Vue.nextTick(() => {
|
||||
let autoScrollButton = parent.querySelector('.c-imagery__auto-scroll-resume-button');
|
||||
expect(autoScrollButton).toBeTruthy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it ('scrollToRight is called when clicking on auto scroll button', (done) => {
|
||||
Vue.nextTick(() => {
|
||||
// use spyon to spy the scroll function
|
||||
spyOn(imageryView._getInstance().$refs.ImageryContainer, 'scrollToRight');
|
||||
imageryView._getInstance().$refs.ImageryContainer.autoScroll = false;
|
||||
Vue.nextTick(() => {
|
||||
parent.querySelector('.c-imagery__auto-scroll-resume-button').click();
|
||||
expect(imageryView._getInstance().$refs.ImageryContainer.scrollToRight).toHaveBeenCalledWith('reset');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("imagery time strip view", () => {
|
||||
let applicableViews;
|
||||
let imageryViewProvider;
|
||||
let imageryView;
|
||||
let componentView;
|
||||
|
||||
beforeEach(() => {
|
||||
openmct.time.timeSystem('utc', {
|
||||
start: START - (5 * ONE_MINUTE),
|
||||
end: START + (5 * ONE_MINUTE)
|
||||
});
|
||||
|
||||
openmct.router.path = [{
|
||||
identifier: {
|
||||
key: 'test-timestrip',
|
||||
namespace: ''
|
||||
},
|
||||
type: 'time-strip'
|
||||
}];
|
||||
|
||||
applicableViews = openmct.objectViews.get(imageryObject, [imageryObject, {
|
||||
identifier: {
|
||||
key: 'test-timestrip',
|
||||
namespace: ''
|
||||
},
|
||||
type: 'time-strip'
|
||||
}]);
|
||||
imageryViewProvider = applicableViews.find(viewProvider => viewProvider.key === imageryForTimeStripKey);
|
||||
imageryView = imageryViewProvider.view(imageryObject, [imageryObject, {
|
||||
identifier: {
|
||||
key: 'test-timestrip',
|
||||
namespace: ''
|
||||
},
|
||||
type: 'time-strip'
|
||||
}]);
|
||||
imageryView.show(child);
|
||||
|
||||
componentView = imageryView.getComponent().$children[0];
|
||||
spyOn(componentView.previewAction, 'invoke').and.callThrough();
|
||||
|
||||
return Vue.nextTick();
|
||||
});
|
||||
|
||||
it("on mount should show imagery within the given bounds", (done) => {
|
||||
Vue.nextTick(() => {
|
||||
const imageElements = parent.querySelectorAll('.c-imagery-tsv__image-wrapper');
|
||||
expect(imageElements.length).toEqual(6);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it ('shows an auto scroll button when scroll to left', async () => {
|
||||
// to mock what a scroll would do
|
||||
imageryView._getInstance().$refs.ImageryContainer.autoScroll = false;
|
||||
await Vue.nextTick();
|
||||
let autoScrollButton = parent.querySelector('.c-imagery__auto-scroll-resume-button');
|
||||
expect(autoScrollButton).toBeTruthy();
|
||||
});
|
||||
it ('scrollToRight is called when clicking on auto scroll button', async () => {
|
||||
// use spyon to spy the scroll function
|
||||
spyOn(imageryView._getInstance().$refs.ImageryContainer, 'scrollToRight');
|
||||
imageryView._getInstance().$refs.ImageryContainer.autoScroll = false;
|
||||
await Vue.nextTick();
|
||||
parent.querySelector('.c-imagery__auto-scroll-resume-button').click();
|
||||
expect(imageryView._getInstance().$refs.ImageryContainer.scrollToRight).toHaveBeenCalledWith('reset');
|
||||
|
||||
it("should show the clicked thumbnail as the preview image", (done) => {
|
||||
Vue.nextTick(() => {
|
||||
const mouseDownEvent = createMouseEvent("mousedown");
|
||||
let imageWrapper = parent.querySelectorAll(`.c-imagery-tsv__image-wrapper`);
|
||||
imageWrapper[2].dispatchEvent(mouseDownEvent);
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(componentView.previewAction.invoke).toHaveBeenCalledWith([componentView.objectPath[0]], {
|
||||
indexForFocusedImage: 2,
|
||||
objectPath: componentView.objectPath
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -45,8 +45,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="sass">
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import packages from './third-party-licenses.json';
|
||||
|
||||
|
@ -180,9 +180,13 @@ export default {
|
||||
this.openmct.notifications.alert(message);
|
||||
}
|
||||
|
||||
const relativeHash = hash.slice(hash.indexOf('#'));
|
||||
const url = new URL(relativeHash, `${location.protocol}//${location.host}${location.pathname}`);
|
||||
this.openmct.router.navigate(url.hash);
|
||||
if (this.openmct.editor.isEditing()) {
|
||||
this.previewEmbed();
|
||||
} else {
|
||||
const relativeHash = hash.slice(hash.indexOf('#'));
|
||||
const url = new URL(relativeHash, `${location.protocol}//${location.host}${location.pathname}`);
|
||||
this.openmct.router.navigate(url.hash);
|
||||
}
|
||||
},
|
||||
formatTime(unixTime, timeFormat) {
|
||||
return Moment.utc(unixTime).format(timeFormat);
|
||||
|
72
src/plugins/notebook/monkeyPatchObjectAPIForNotebooks.js
Normal file
72
src/plugins/notebook/monkeyPatchObjectAPIForNotebooks.js
Normal file
@ -0,0 +1,72 @@
|
||||
import {NOTEBOOK_TYPE} from './notebook-constants';
|
||||
|
||||
export default function (openmct) {
|
||||
const apiSave = openmct.objects.save.bind(openmct.objects);
|
||||
|
||||
openmct.objects.save = async (domainObject) => {
|
||||
if (domainObject.type !== NOTEBOOK_TYPE) {
|
||||
return apiSave(domainObject);
|
||||
}
|
||||
|
||||
const localMutable = openmct.objects._toMutable(domainObject);
|
||||
let result;
|
||||
|
||||
try {
|
||||
result = await apiSave(localMutable);
|
||||
} catch (error) {
|
||||
if (error instanceof openmct.objects.errors.Conflict) {
|
||||
result = resolveConflicts(localMutable, openmct);
|
||||
} else {
|
||||
result = Promise.reject(error);
|
||||
}
|
||||
} finally {
|
||||
openmct.objects.destroyMutable(localMutable);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
function resolveConflicts(localMutable, openmct) {
|
||||
return openmct.objects.getMutable(localMutable.identifier).then((remoteMutable) => {
|
||||
const localEntries = localMutable.configuration.entries;
|
||||
remoteMutable.$refresh(remoteMutable);
|
||||
applyLocalEntries(remoteMutable, localEntries);
|
||||
|
||||
openmct.objects.destroyMutable(remoteMutable);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function applyLocalEntries(mutable, entries) {
|
||||
Object.entries(entries).forEach(([sectionKey, pagesInSection]) => {
|
||||
Object.entries(pagesInSection).forEach(([pageKey, localEntries]) => {
|
||||
const remoteEntries = mutable.configuration.entries[sectionKey][pageKey];
|
||||
const mergedEntries = [].concat(remoteEntries);
|
||||
let shouldMutate = false;
|
||||
|
||||
const locallyAddedEntries = _.differenceBy(localEntries, remoteEntries, 'id');
|
||||
const locallyModifiedEntries = _.differenceWith(localEntries, remoteEntries, (localEntry, remoteEntry) => {
|
||||
return localEntry.id === remoteEntry.id && localEntry.text === remoteEntry.text;
|
||||
});
|
||||
|
||||
locallyAddedEntries.forEach((localEntry) => {
|
||||
mergedEntries.push(localEntry);
|
||||
shouldMutate = true;
|
||||
});
|
||||
|
||||
locallyModifiedEntries.forEach((locallyModifiedEntry) => {
|
||||
let mergedEntry = mergedEntries.find(entry => entry.id === locallyModifiedEntry.id);
|
||||
if (mergedEntry !== undefined) {
|
||||
mergedEntry.text = locallyModifiedEntry.text;
|
||||
shouldMutate = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (shouldMutate) {
|
||||
mutable.$set(`configuration.entries.${sectionKey}.${pageKey}`, mergedEntries);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
@ -2,6 +2,7 @@ import CopyToNotebookAction from './actions/CopyToNotebookAction';
|
||||
import Notebook from './components/Notebook.vue';
|
||||
import NotebookSnapshotIndicator from './components/NotebookSnapshotIndicator.vue';
|
||||
import SnapshotContainer from './snapshot-container';
|
||||
import monkeyPatchObjectAPIForNotebooks from './monkeyPatchObjectAPIForNotebooks.js';
|
||||
|
||||
import { notebookImageMigration } from '../notebook/utils/notebook-migration';
|
||||
import { NOTEBOOK_TYPE } from './notebook-constants';
|
||||
@ -114,7 +115,8 @@ export default function NotebookPlugin() {
|
||||
});
|
||||
const indicator = {
|
||||
element: notebookSnapshotIndicator.$mount().$el,
|
||||
key: 'notebook-snapshot-indicator'
|
||||
key: 'notebook-snapshot-indicator',
|
||||
priority: openmct.priority.DEFAULT
|
||||
};
|
||||
|
||||
openmct.indicators.add(indicator);
|
||||
@ -165,5 +167,7 @@ export default function NotebookPlugin() {
|
||||
return domainObject;
|
||||
}
|
||||
});
|
||||
|
||||
monkeyPatchObjectAPIForNotebooks(openmct);
|
||||
};
|
||||
}
|
||||
|
@ -148,12 +148,18 @@ describe("Notebook plugin:", () => {
|
||||
'observe'
|
||||
]);
|
||||
|
||||
openmct.editor = {};
|
||||
openmct.editor.isEditing = () => false;
|
||||
|
||||
const applicableViews = openmct.objectViews.get(notebookViewObject, [notebookViewObject]);
|
||||
notebookViewProvider = applicableViews.find(viewProvider => viewProvider.key === 'notebook-vue');
|
||||
|
||||
testObjectProvider.get.and.returnValue(Promise.resolve(notebookViewObject));
|
||||
testObjectProvider.create.and.returnValue(Promise.resolve(notebookViewObject));
|
||||
openmct.objects.addProvider('test-namespace', testObjectProvider);
|
||||
testObjectProvider.observe.and.returnValue(() => {});
|
||||
testObjectProvider.create.and.returnValue(Promise.resolve(true));
|
||||
testObjectProvider.update.and.returnValue(Promise.resolve(true));
|
||||
|
||||
return openmct.objects.getMutable(notebookViewObject.identifier).then((mutableObject) => {
|
||||
mutableNotebookObject = mutableObject;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user