Compare commits

..

11 Commits

Author SHA1 Message Date
62ff404bb3 Testing with non-reactive API 2021-09-27 16:45:54 -07:00
8243cf5d7b Fix missing object handling in several vues (#4259)
* If there is a pending create request for an id, queue a duplicate request.
* Fix downsteam errors when objects are missing
* Changed error logging from console.log to console.warn
2021-09-27 14:25:33 -07:00
c4c1fea17f snapshot clicked while in edit mode should open in preview mode #4115 (#4257) 2021-09-27 10:14:03 -07:00
5e920e90ce Fix bargraph color selection (#4253)
* Fix typo for attribute key
* Adds section heading for Bar Graph Series
2021-09-24 10:07:53 -07:00
886db23eb6 Hide independent time conductor mode if only 1 mode option is available. (#4250)
* If there is a pending create request for an id, queue a duplicate request.
* Hide independent time conductor mode if there is only 1 mode available
2021-09-23 10:47:35 -07:00
0ccb546a2e starting loading as false, since that makes sense (#4247) 2021-09-23 09:42:39 -07:00
271f8ed38f Fix file selection on pressing enter key (#4246)
* Invoke angular digest cycle after file input selection returns
2021-09-22 15:14:30 -07:00
650f84e95c [Telemetry Tables] Handling Request Loading (#4245)
* added two new events for telemetry collections to denote historical requests starting and ending (can be used for loading indicators)

* updating refresh data to use correct outstanding requests variable, binding request count update methods

* removing loading spinner (replaced with progress bar)

* if making a request, cancel any existing ones

* reverting edge case code updates
2021-09-22 15:01:28 -07:00
b70af5a1bb If there is a pending create request for an id, queue a duplicate request. (#4243) 2021-09-22 09:44:22 -07:00
0af21632db Use timeFormatter.parse to get the timestamp of imagery since the source could be something other than key (#4238) 2021-09-21 11:10:18 -07:00
e2f1ff5442 Notebook conflict auto retry 1.7.7 (#4230) 2021-09-20 14:33:38 -07:00
84 changed files with 779 additions and 581 deletions

View File

@ -42,7 +42,6 @@ jobs:
- ~/.npm - ~/.npm
- ~/.cache - ~/.cache
- node_modules - node_modules
- run: npm run lint
- run: npm run test:coverage -- --browsers=<<parameters.browser>> || <<parameters.always-pass>> - run: npm run test:coverage -- --browsers=<<parameters.browser>> || <<parameters.always-pass>>
- store_test_results: - store_test_results:
path: dist/reports/tests/ path: dist/reports/tests/

View File

@ -2,7 +2,6 @@
* [ ] Have you followed the guidelines in our [Contributing document](https://github.com/nasa/openmct/blob/master/CONTRIBUTING.md)? * [ ] 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? * [ ] 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 ### Author Checklist

View File

@ -317,7 +317,6 @@ checklist).
### Reviewer Checklist ### Reviewer Checklist
* [ ] Changes appear to address issue? * [ ] Changes appear to address issue?
* [ ] Changes appear not to be breaking changes?
* [ ] Appropriate unit tests included? * [ ] Appropriate unit tests included?
* [ ] Code style and in-line documentation are appropriate? * [ ] Code style and in-line documentation are appropriate?
* [ ] Commit messages meet standards? * [ ] Commit messages meet standards?

View File

@ -25,7 +25,7 @@
const devMode = process.env.NODE_ENV !== 'production'; const devMode = process.env.NODE_ENV !== 'production';
const browsers = [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'ChromeHeadless']; const browsers = [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'ChromeHeadless'];
const coverageEnabled = process.env.COVERAGE === 'true'; const coverageEnabled = process.env.COVERAGE === 'true';
const reporters = ['spec', 'html', 'junit']; const reporters = ['progress', 'html', 'junit'];
if (coverageEnabled) { if (coverageEnabled) {
reporters.push('coverage-istanbul'); reporters.push('coverage-istanbul');
@ -60,7 +60,7 @@ module.exports = (config) => {
client: { client: {
jasmine: { jasmine: {
random: false, random: false,
timeoutInterval: 5000 timeoutInterval: 30000
} }
}, },
customLaunchers: { customLaunchers: {
@ -88,6 +88,11 @@ module.exports = (config) => {
outputFile: "test-results.xml", outputFile: "test-results.xml",
useBrowserName: false useBrowserName: false
}, },
browserConsoleLogOptions: {
level: "error",
format: "%b %T: %m",
terminal: true
},
coverageIstanbulReporter: { coverageIstanbulReporter: {
fixWebpackSourcePaths: true, fixWebpackSourcePaths: true,
dir: process.env.CIRCLE_ARTIFACTS dir: process.env.CIRCLE_ARTIFACTS
@ -100,15 +105,6 @@ module.exports = (config) => {
} }
} }
}, },
specReporter: {
maxLogLines: 5,
suppressErrorSummary: true,
suppressFailed: false,
suppressPassed: false,
suppressSkipped: true,
showSpecTiming: true,
failFast: false
},
preprocessors: { preprocessors: {
'indexTest.js': ['webpack', 'sourcemap'] 'indexTest.js': ['webpack', 'sourcemap']
}, },

View File

@ -41,7 +41,6 @@
"karma-jasmine": "4.0.1", "karma-jasmine": "4.0.1",
"karma-junit-reporter": "2.0.1", "karma-junit-reporter": "2.0.1",
"karma-sourcemap-loader": "0.3.8", "karma-sourcemap-loader": "0.3.8",
"karma-spec-reporter": "0.0.32",
"karma-webpack": "4.0.2", "karma-webpack": "4.0.2",
"location-bar": "^3.0.1", "location-bar": "^3.0.1",
"lodash": "^4.17.12", "lodash": "^4.17.12",
@ -65,7 +64,6 @@
"uuid": "^3.3.3", "uuid": "^3.3.3",
"v8-compile-cache": "^1.1.0", "v8-compile-cache": "^1.1.0",
"vue": "2.5.6", "vue": "2.5.6",
"vue-eslint-parser": "7.11.0",
"vue-loader": "^15.2.6", "vue-loader": "^15.2.6",
"vue-template-compiler": "2.5.6", "vue-template-compiler": "2.5.6",
"webpack": "^4.16.2", "webpack": "^4.16.2",

View File

@ -50,6 +50,8 @@ define(
* or finish() are called. * or finish() are called.
*/ */
EditorCapability.prototype.edit = function () { EditorCapability.prototype.edit = function () {
console.warn('DEPRECATED: cannot edit via edit capability, use openmct.editor instead.');
if (!this.openmct.editor.isEditing()) { if (!this.openmct.editor.isEditing()) {
this.openmct.editor.edit(); this.openmct.editor.edit();
this.domainObject.getCapability('status').set('editing', true); this.domainObject.getCapability('status').set('editing', true);
@ -80,6 +82,8 @@ define(
* @returns {*} * @returns {*}
*/ */
EditorCapability.prototype.save = function () { EditorCapability.prototype.save = function () {
console.warn('DEPRECATED: cannot save via edit capability, use openmct.editor instead.');
return Promise.resolve(); return Promise.resolve();
}; };
@ -91,6 +95,8 @@ define(
* @returns {*} * @returns {*}
*/ */
EditorCapability.prototype.finish = function () { EditorCapability.prototype.finish = function () {
console.warn('DEPRECATED: cannot finish via edit capability, use openmct.editor instead.');
return Promise.resolve(); return Promise.resolve();
}; };

View File

@ -25,14 +25,15 @@ define([
], function ( ], function (
moment moment
) { ) {
const DATE_FORMAT = "YYYY-MM-DD HH:mm:ss.SSS";
const DATE_FORMATS = [ var DATE_FORMAT = "YYYY-MM-DD HH:mm:ss.SSS",
DATE_FORMAT, DATE_FORMATS = [
DATE_FORMAT + "Z", DATE_FORMAT,
"YYYY-MM-DD HH:mm:ss", DATE_FORMAT + "Z",
"YYYY-MM-DD HH:mm", "YYYY-MM-DD HH:mm:ss",
"YYYY-MM-DD" "YYYY-MM-DD HH:mm",
]; "YYYY-MM-DD"
];
/** /**
* @typedef Scale * @typedef Scale
@ -52,27 +53,15 @@ define([
this.key = "utc"; 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. * @param {number} value The value to format.
* @param {string} formatString The string format to format. Default "YYYY-MM-DD HH:mm:ss.SSS" + "Z" * @returns {string} the formatted date(s). If multiple values were requested, then an array of
* @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 * formatted values will be returned. Where a value could not be formatted, `undefined` will be returned at its position
* in the array. * in the array.
*/ */
UTCTimeFormat.prototype.format = function (value, formatString) { UTCTimeFormat.prototype.format = function (value) {
if (value !== undefined) { if (value !== undefined) {
const format = validateFormatString(formatString); return moment.utc(value).format(DATE_FORMAT) + "Z";
return moment.utc(value).format(format) + (formatString ? '' : 'Z');
} else { } else {
return value; return value;
} }

View File

@ -44,9 +44,11 @@ define(
setText(result.name); setText(result.name);
scope.ngModel[scope.field] = result; scope.ngModel[scope.field] = result;
control.$setValidity("file-input", true); control.$setValidity("file-input", true);
scope.$digest();
}, function () { }, function () {
setText('Select File'); setText('Select File');
control.$setValidity("file-input", false); control.$setValidity("file-input", false);
scope.$digest();
}); });
} }

View File

@ -288,6 +288,8 @@ define([
this.install(this.plugins.ObjectInterceptors()); this.install(this.plugins.ObjectInterceptors());
this.install(this.plugins.NonEditableFolder()); this.install(this.plugins.NonEditableFolder());
this.install(this.plugins.DeviceClassifier()); this.install(this.plugins.DeviceClassifier());
this._isVue = true;
} }
MCT.prototype = Object.create(EventEmitter.prototype); MCT.prototype = Object.create(EventEmitter.prototype);

View File

@ -28,6 +28,8 @@ export default function LegacyActionAdapter(openmct, legacyActions) {
return true; return true;
} }
console.warn(`DEPRECATION WARNING: Action ${action.definition.key} in bundle ${action.bundle.path} is non-contextual and should be migrated.`);
return false; return false;
} }

View File

@ -29,6 +29,7 @@ define([
'./capabilities/APICapabilityDecorator', './capabilities/APICapabilityDecorator',
'./policies/AdaptedViewPolicy', './policies/AdaptedViewPolicy',
'./runs/AlternateCompositionInitializer', './runs/AlternateCompositionInitializer',
'./runs/TypeDeprecationChecker',
'./runs/LegacyTelemetryProvider', './runs/LegacyTelemetryProvider',
'./runs/RegisterLegacyTypes', './runs/RegisterLegacyTypes',
'./services/LegacyObjectAPIInterceptor', './services/LegacyObjectAPIInterceptor',
@ -45,6 +46,7 @@ define([
APICapabilityDecorator, APICapabilityDecorator,
AdaptedViewPolicy, AdaptedViewPolicy,
AlternateCompositionInitializer, AlternateCompositionInitializer,
TypeDeprecationChecker,
LegacyTelemetryProvider, LegacyTelemetryProvider,
RegisterLegacyTypes, RegisterLegacyTypes,
LegacyObjectAPIInterceptor, LegacyObjectAPIInterceptor,
@ -133,6 +135,10 @@ define([
} }
], ],
runs: [ runs: [
{
implementation: TypeDeprecationChecker,
depends: ["types[]"]
},
{ {
implementation: AlternateCompositionInitializer, implementation: AlternateCompositionInitializer,
depends: ["openmct"] depends: ["openmct"]

View File

@ -4,6 +4,12 @@ define([
) { ) {
function RegisterLegacyTypes(types, openmct) { 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); openmct.types.importLegacyTypes(types);
} }

View File

@ -0,0 +1,46 @@
/*****************************************************************************
* 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;
});

View File

@ -81,14 +81,8 @@ define([
return models; return models;
} }
return this.apiFetch(missingIds) //Temporary fix for missing models - don't retry using this.apiFetch
.then(function (apiResults) { return models;
Object.keys(apiResults).forEach(function (k) {
models[k] = apiResults[k];
});
return models;
});
}.bind(this)); }.bind(this));
}; };

View File

@ -15,6 +15,8 @@ define([
}; };
function LegacyViewProvider(legacyView, openmct, convertToLegacyObject) { 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 { return {
key: legacyView.key, key: legacyView.key,
name: legacyView.name, name: legacyView.name,

View File

@ -4,6 +4,7 @@ define([
) { ) {
function TypeInspectorViewProvider(typeDefinition, openmct, convertToLegacyObject) { 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[]') let representation = openmct.$injector.get('representations[]')
.filter((r) => r.key === typeDefinition.inspector)[0]; .filter((r) => r.key === typeDefinition.inspector)[0];

View File

@ -110,7 +110,7 @@ class ActionsAPI extends EventEmitter {
return actionsObject; return actionsObject;
} }
_groupAndSortActions(actionsArray) { _groupAndSortActions(actionsArray = []) {
if (!Array.isArray(actionsArray) && typeof actionsArray === 'object') { if (!Array.isArray(actionsArray) && typeof actionsArray === 'object') {
actionsArray = Object.keys(actionsArray).map(key => actionsArray[key]); actionsArray = Object.keys(actionsArray).map(key => actionsArray[key]);
} }

View File

@ -0,0 +1,2 @@
export default class ConflictError extends Error {
}

View File

@ -26,6 +26,7 @@ import RootRegistry from './RootRegistry';
import RootObjectProvider from './RootObjectProvider'; import RootObjectProvider from './RootObjectProvider';
import EventEmitter from 'EventEmitter'; import EventEmitter from 'EventEmitter';
import InterceptorRegistry from './InterceptorRegistry'; import InterceptorRegistry from './InterceptorRegistry';
import ConflictError from './ConflictError';
/** /**
* Utilities for loading, saving, and manipulating domain objects. * Utilities for loading, saving, and manipulating domain objects.
@ -34,6 +35,7 @@ import InterceptorRegistry from './InterceptorRegistry';
*/ */
function ObjectAPI(typeRegistry, openmct) { function ObjectAPI(typeRegistry, openmct) {
this.openmct = openmct;
this.typeRegistry = typeRegistry; this.typeRegistry = typeRegistry;
this.eventEmitter = new EventEmitter(); this.eventEmitter = new EventEmitter();
this.providers = {}; this.providers = {};
@ -47,6 +49,10 @@ function ObjectAPI(typeRegistry, openmct) {
this.interceptorRegistry = new InterceptorRegistry(); this.interceptorRegistry = new InterceptorRegistry();
this.SYNCHRONIZED_OBJECT_TYPES = ['notebook', 'plan']; this.SYNCHRONIZED_OBJECT_TYPES = ['notebook', 'plan'];
this.errors = {
Conflict: ConflictError
};
} }
/** /**
@ -181,13 +187,16 @@ ObjectAPI.prototype.get = function (identifier, abortSignal) {
let objectPromise = provider.get(identifier, abortSignal).then(result => { let objectPromise = provider.get(identifier, abortSignal).then(result => {
delete this.cache[keystring]; delete this.cache[keystring];
result = this.applyGetInterceptors(identifier, result); result = this.applyGetInterceptors(identifier, result);
if (result.isMutable) {
result.$refresh(result); return result;
} else { }).catch((result) => {
let mutableDomainObject = this._toMutable(result); console.warn(`Failed to retrieve ${keystring}:`, result);
mutableDomainObject.$refresh(result);
} delete this.cache[keystring];
result = this.applyGetInterceptors(identifier);
return result; return result;
}); });
@ -291,6 +300,7 @@ ObjectAPI.prototype.isPersistable = function (idOrKeyString) {
ObjectAPI.prototype.save = function (domainObject) { ObjectAPI.prototype.save = function (domainObject) {
let provider = this.getProvider(domainObject.identifier); let provider = this.getProvider(domainObject.identifier);
let savedResolve; let savedResolve;
let savedReject;
let result; let result;
if (!this.isPersistable(domainObject.identifier)) { if (!this.isPersistable(domainObject.identifier)) {
@ -300,19 +310,18 @@ ObjectAPI.prototype.save = function (domainObject) {
} else { } else {
const persistedTime = Date.now(); const persistedTime = Date.now();
if (domainObject.persisted === undefined) { if (domainObject.persisted === undefined) {
result = new Promise((resolve) => { result = new Promise((resolve, reject) => {
savedResolve = resolve; savedResolve = resolve;
savedReject = reject;
}); });
domainObject.persisted = persistedTime; domainObject.persisted = persistedTime;
const newObjectPromise = provider.create(domainObject); provider.create(domainObject)
if (newObjectPromise) { .then((response) => {
newObjectPromise.then(response => {
this.mutate(domainObject, 'persisted', persistedTime); this.mutate(domainObject, 'persisted', persistedTime);
savedResolve(response); savedResolve(response);
}).catch((error) => {
savedReject(error);
}); });
} else {
result = Promise.reject(`[ObjectAPI][save] Object provider returned ${newObjectPromise} when creating new object.`);
}
} else { } else {
domainObject.persisted = persistedTime; domainObject.persisted = persistedTime;
this.mutate(domainObject, 'persisted', persistedTime); this.mutate(domainObject, 'persisted', persistedTime);

View File

@ -180,6 +180,12 @@ define([
* @memberof module:openmct.TelemetryAPI~TelemetryProvider# * @memberof module:openmct.TelemetryAPI~TelemetryProvider#
*/ */
TelemetryAPI.prototype.canProvideTelemetry = function (domainObject) { 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)) return Boolean(this.findSubscriptionProvider(domainObject))
|| Boolean(this.findRequestProvider(domainObject)); || Boolean(this.findRequestProvider(domainObject));
}; };
@ -477,6 +483,10 @@ define([
* @returns {Object<String, {TelemetryValueFormatter}>} * @returns {Object<String, {TelemetryValueFormatter}>}
*/ */
TelemetryAPI.prototype.getFormatMap = function (metadata) { TelemetryAPI.prototype.getFormatMap = function (metadata) {
if (!metadata) {
return {};
}
if (!this.formatMapCache.has(metadata)) { if (!this.formatMapCache.has(metadata)) {
const formatMap = metadata.values().reduce(function (map, valueMetadata) { const formatMap = metadata.values().reduce(function (map, valueMetadata) {
map[valueMetadata.key] = this.getValueFormatter(valueMetadata); map[valueMetadata.key] = this.getValueFormatter(valueMetadata);

View File

@ -130,8 +130,13 @@ export class TelemetryCollection extends EventEmitter {
this.options.onPartialResponse = this._processNewTelemetry.bind(this); this.options.onPartialResponse = this._processNewTelemetry.bind(this);
try { try {
if (this.requestAbort) {
this.requestAbort.abort();
}
this.requestAbort = new AbortController(); this.requestAbort = new AbortController();
this.options.signal = this.requestAbort.signal; this.options.signal = this.requestAbort.signal;
this.emit('requestStarted');
historicalData = await this.historicalProvider.request(this.domainObject, this.options); historicalData = await this.historicalProvider.request(this.domainObject, this.options);
} catch (error) { } catch (error) {
if (error.name !== 'AbortError') { if (error.name !== 'AbortError') {
@ -140,6 +145,7 @@ export class TelemetryCollection extends EventEmitter {
} }
} }
this.emit('requestEnded');
this.requestAbort = undefined; this.requestAbort = undefined;
this._processNewTelemetry(historicalData); this._processNewTelemetry(historicalData);

View File

@ -31,6 +31,11 @@ define([
valueMetadata.hints = valueMetadata.hints || {}; valueMetadata.hints = valueMetadata.hints || {};
if (Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'x')) { 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')) { if (!Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'domain')) {
valueMetadata.hints.domain = valueMetadata.hints.x; valueMetadata.hints.domain = valueMetadata.hints.x;
} }
@ -39,6 +44,11 @@ define([
} }
if (Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'y')) { 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')) { if (!Object.prototype.hasOwnProperty.call(valueMetadata.hints, 'range')) {
valueMetadata.hints.range = valueMetadata.hints.y; valueMetadata.hints.range = valueMetadata.hints.y;
} }

View File

@ -63,6 +63,12 @@ define(['./Type'], function (Type) {
*/ */
TypeRegistry.prototype.standardizeType = function (typeDef) { TypeRegistry.prototype.standardizeType = function (typeDef) {
if (Object.prototype.hasOwnProperty.call(typeDef, 'label')) { 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) { if (!typeDef.name) {
typeDef.name = typeDef.label; typeDef.name = typeDef.label;
} }

View File

@ -41,7 +41,6 @@ const DEFAULTS = [
'platform/forms', 'platform/forms',
'platform/identity', 'platform/identity',
'platform/persistence/aggregator', 'platform/persistence/aggregator',
'platform/persistence/queue',
'platform/policy', 'platform/policy',
'platform/entanglement', 'platform/entanglement',
'platform/search', 'platform/search',

View File

@ -32,7 +32,7 @@ describe('the plugin', function () {
let openmct; let openmct;
let composition; let composition;
beforeEach((done) => { beforeEach(() => {
openmct = createOpenMct(); 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([ spyOn(couchPlugin.couchProvider, 'getObjectsByFilter').and.returnValue(Promise.resolve([
{ {
identifier: { 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(() => { afterEach(() => {

View File

@ -96,11 +96,11 @@ export default {
this.timestampKey = this.openmct.time.timeSystem().key; this.timestampKey = this.openmct.time.timeSystem().key;
this.valueMetadata = this this.valueMetadata = this.metadata ? this
.metadata .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 this.unsubscribe = this.openmct
.telemetry .telemetry
@ -151,7 +151,10 @@ export default {
size: 1, size: 1,
strategy: 'latest' 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) { updateBounds(bounds, isTick) {
this.bounds = bounds; this.bounds = bounds;

View File

@ -73,8 +73,9 @@ export default {
hasUnits() { hasUnits() {
let itemsWithUnits = this.items.filter((item) => { let itemsWithUnits = this.items.filter((item) => {
let metadata = this.openmct.telemetry.getMetadata(item.domainObject); let metadata = this.openmct.telemetry.getMetadata(item.domainObject);
const valueMetadatas = metadata ? metadata.valueMetadatas : [];
return this.metadataHasUnits(metadata.valueMetadatas); return this.metadataHasUnits(valueMetadatas);
}); });

View File

@ -30,10 +30,10 @@
<div v-if="staticStyle" <div v-if="staticStyle"
class="c-inspect-styles__style" class="c-inspect-styles__style"
> >
<StyleEditor class="c-inspect-styles__editor" <style-editor class="c-inspect-styles__editor"
:style-item="staticStyle" :style-item="staticStyle"
:is-editing="isEditing" :is-editing="isEditing"
@persist="updateStaticStyle" @persist="updateStaticStyle"
/> />
</div> </div>
<button <button
@ -87,10 +87,10 @@
<condition-description :show-label="true" <condition-description :show-label="true"
:condition="getCondition(conditionStyle.conditionId)" :condition="getCondition(conditionStyle.conditionId)"
/> />
<StyleEditor class="c-inspect-styles__editor" <style-editor class="c-inspect-styles__editor"
:style-item="conditionStyle" :style-item="conditionStyle"
:is-editing="isEditing" :is-editing="isEditing"
@persist="updateConditionalStyle" @persist="updateConditionalStyle"
/> />
</div> </div>
</div> </div>
@ -240,10 +240,10 @@ export default {
} }
let vm = new Vue({ let vm = new Vue({
components: {ConditionSetSelectorDialog},
provide: { provide: {
openmct: this.openmct openmct: this.openmct
}, },
components: {ConditionSetSelectorDialog},
data() { data() {
return { return {
handleItemSelection handleItemSelection

View File

@ -40,13 +40,13 @@
<div v-if="staticStyle" <div v-if="staticStyle"
class="c-inspect-styles__style" class="c-inspect-styles__style"
> >
<StyleEditor class="c-inspect-styles__editor" <style-editor class="c-inspect-styles__editor"
:style-item="staticStyle" :style-item="staticStyle"
:is-editing="allowEditing" :is-editing="allowEditing"
:mixed-styles="mixedStyles" :mixed-styles="mixedStyles"
:non-specific-font-properties="nonSpecificFontProperties" :non-specific-font-properties="nonSpecificFontProperties"
@persist="updateStaticStyle" @persist="updateStaticStyle"
@save-style="saveStyle" @save-style="saveStyle"
/> />
</div> </div>
<button <button
@ -108,12 +108,12 @@
<condition-description :show-label="true" <condition-description :show-label="true"
:condition="getCondition(conditionStyle.conditionId)" :condition="getCondition(conditionStyle.conditionId)"
/> />
<StyleEditor class="c-inspect-styles__editor" <style-editor class="c-inspect-styles__editor"
:style-item="conditionStyle" :style-item="conditionStyle"
:non-specific-font-properties="nonSpecificFontProperties" :non-specific-font-properties="nonSpecificFontProperties"
:is-editing="allowEditing" :is-editing="allowEditing"
@persist="updateConditionalStyle" @persist="updateConditionalStyle"
@save-style="saveStyle" @save-style="saveStyle"
/> />
</div> </div>
</div> </div>
@ -556,10 +556,10 @@ export default {
} }
let vm = new Vue({ let vm = new Vue({
components: {ConditionSetSelectorDialog},
provide: { provide: {
openmct: this.openmct openmct: this.openmct
}, },
components: {ConditionSetSelectorDialog},
data() { data() {
return { return {
handleItemSelection handleItemSelection

View File

@ -98,6 +98,8 @@ describe('the plugin', function () {
conditionSetDefinition.initialize(mockConditionSetDomainObject); conditionSetDefinition.initialize(mockConditionSetDomainObject);
spyOn(openmct.objects, "save").and.returnValue(Promise.resolve(true));
openmct.on('start', done); openmct.on('start', done);
openmct.startHeadless(); openmct.startHeadless();
}); });

View File

@ -38,16 +38,12 @@ a.c-condition-widget {
// Make Condition Widget expand when in a hidden frame Layout context // Make Condition Widget expand when in a hidden frame Layout context
// For both static and Flexible Layouts // For both static and Flexible Layouts
.c-so-view--conditionWidget.c-so-view--no-frame { .c-so-view--no-frame > .c-so-view__object-view > .c-condition-widget {
.c-condition-widget { @include abs();
@include abs(); display: flex;
display: flex; align-items: center;
align-items: center; justify-content: center;
justify-content: center; padding: 0;
padding: 0;
}
.c-so-view__frame-controls { display: none; }
} }
// Add some margin when a Condition Widget is in a Flexible Layout // Add some margin when a Condition Widget is in a Flexible Layout

View File

@ -47,8 +47,8 @@
.is-editing { .is-editing {
.l-shell__main-container { .l-shell__main-container {
[s-selected], &[s-selected],
[s-selected-parent] { &[s-selected-parent] {
// Display grid and allow edit marquee to display in main layout holder when editing // Display grid and allow edit marquee to display in main layout holder when editing
> .l-layout { > .l-layout {
background: $editUIGridColorBg; background: $editUIGridColorBg;

View File

@ -101,7 +101,7 @@ export default {
addChildren(domainObject) { addChildren(domainObject) {
let keyString = this.openmct.objects.makeKeyString(domainObject.identifier); let keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
let metadata = this.openmct.telemetry.getMetadata(domainObject); 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 hasFiltersWithKeyString = this.persistedFilters[keyString] !== undefined;
let mutateFilters = false; let mutateFilters = false;
let childObject = { let childObject = {

View File

@ -39,13 +39,10 @@ describe("The Compass component", () => {
sunAngle: 30 sunAngle: 30
}; };
let propsData = { let propsData = {
containerWidth: 600,
containerHeight: 600,
naturalAspectRatio: 0.9, naturalAspectRatio: 0.9,
image: imageDatum, image: imageDatum
sizedImageDimensions: {
width: 100,
height: 100
},
compassRoseSizingClasses: '--rose-small --rose-min'
}; };
app = new Vue({ app = new Vue({
@ -54,13 +51,13 @@ describe("The Compass component", () => {
return propsData; return propsData;
}, },
template: `<Compass template: `<Compass
:compass-rose-sizing-classes="compassRoseSizingClasses" :container-width="containerWidth"
:image="image" :container-height="containerHeight"
:natural-aspect-ratio="naturalAspectRatio" :natural-aspect-ratio="naturalAspectRatio"
:sized-image-dimensions="sizedImageDimensions" :image="image" />`
/>`
}); });
instance = app.$mount(); instance = app.$mount();
}); });
afterAll(() => { afterAll(() => {

View File

@ -84,18 +84,18 @@
/> />
</div> </div>
</div> </div>
<div class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-buttons">
<button class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-button c-nav c-nav--prev" <button class="c-nav c-nav--prev"
title="Previous image" title="Previous image"
:disabled="isPrevDisabled" :disabled="isPrevDisabled"
@click="prevImage()" @click="prevImage()"
></button> ></button>
<button class="c-nav c-nav--next"
<button class="c-local-controls c-local-controls--show-on-hover c-imagery__prev-next-button c-nav c-nav--next" title="Next image"
title="Next image" :disabled="isNextDisabled"
:disabled="isNextDisabled" @click="nextImage()"
@click="nextImage()" ></button>
></button> </div>
<div class="c-imagery__control-bar"> <div class="c-imagery__control-bar">
<div class="c-imagery__time"> <div class="c-imagery__time">

View File

@ -285,17 +285,17 @@
} }
} }
.c-imagery__prev-next-button { .c-imagery__prev-next-buttons {
pointer-events: all; display: flex;
width: 100%;
justify-content: space-between;
pointer-events: none;
position: absolute; position: absolute;
top: 50%; top: 50%;
transform: translateY(-75%); // 75% due to transform: rotation approach to the button transform: translateY(-75%);
&.c-nav { .c-nav {
position: absolute; pointer-events: all;
&--prev { left: 0; }
&--next { right: 0; }
} }
.s-status-taking-snapshot & { .s-status-taking-snapshot & {

View File

@ -159,7 +159,7 @@ export default {
let image = { ...datum }; let image = { ...datum };
image.formattedTime = this.formatTime(datum); image.formattedTime = this.formatTime(datum);
image.url = this.formatImageUrl(datum); image.url = this.formatImageUrl(datum);
image.time = datum[this.timeKey]; image.time = this.parseTime(image.formattedTime);
image.imageDownloadName = this.getImageDownloadName(datum); image.imageDownloadName = this.getImageDownloadName(datum);
return image; return image;

View File

@ -220,7 +220,7 @@ describe("The Imagery View Layouts", () => {
}); });
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([])); spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve(imageryObject)); spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
originalRouterPath = openmct.router.path; originalRouterPath = openmct.router.path;
@ -370,15 +370,18 @@ describe("The Imagery View Layouts", () => {
}); });
}); });
it("should show that an image is not new", (done) => { xit("should show that an image is not new", (done) => {
const target = imageTelemetry[2].url; const target = imageTelemetry[2].url;
parent.querySelectorAll(`img[src='${target}']`)[0].click(); parent.querySelectorAll(`img[src='${target}']`)[0].click();
Vue.nextTick(() => { Vue.nextTick(() => {
const imageIsNew = isNew(parent); // used in code, need to wait to the 500ms here too
setTimeout(() => {
const imageIsNew = isNew(parent);
expect(imageIsNew).toBeFalse(); expect(imageIsNew).toBeFalse();
done(); done();
}, REFRESH_CSS_MS);
}); });
}); });

View File

@ -45,7 +45,8 @@
</div> </div>
</div> </div>
</template> </template>
<style lang="sass">
</style>
<script> <script>
import packages from './third-party-licenses.json'; import packages from './third-party-licenses.json';

View File

@ -180,9 +180,13 @@ export default {
this.openmct.notifications.alert(message); this.openmct.notifications.alert(message);
} }
const relativeHash = hash.slice(hash.indexOf('#')); if (this.openmct.editor.isEditing()) {
const url = new URL(relativeHash, `${location.protocol}//${location.host}${location.pathname}`); this.previewEmbed();
this.openmct.router.navigate(url.hash); } 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) { formatTime(unixTime, timeFormat) {
return Moment.utc(unixTime).format(timeFormat); return Moment.utc(unixTime).format(timeFormat);

View 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);
}
});
});
}

View File

@ -2,6 +2,7 @@ import CopyToNotebookAction from './actions/CopyToNotebookAction';
import Notebook from './components/Notebook.vue'; import Notebook from './components/Notebook.vue';
import NotebookSnapshotIndicator from './components/NotebookSnapshotIndicator.vue'; import NotebookSnapshotIndicator from './components/NotebookSnapshotIndicator.vue';
import SnapshotContainer from './snapshot-container'; import SnapshotContainer from './snapshot-container';
import monkeyPatchObjectAPIForNotebooks from './monkeyPatchObjectAPIForNotebooks.js';
import { notebookImageMigration } from '../notebook/utils/notebook-migration'; import { notebookImageMigration } from '../notebook/utils/notebook-migration';
import { NOTEBOOK_TYPE } from './notebook-constants'; import { NOTEBOOK_TYPE } from './notebook-constants';
@ -165,5 +166,7 @@ export default function NotebookPlugin() {
return domainObject; return domainObject;
} }
}); });
monkeyPatchObjectAPIForNotebooks(openmct);
}; };
} }

View File

@ -154,6 +154,8 @@ describe("Notebook plugin:", () => {
testObjectProvider.get.and.returnValue(Promise.resolve(notebookViewObject)); testObjectProvider.get.and.returnValue(Promise.resolve(notebookViewObject));
openmct.objects.addProvider('test-namespace', testObjectProvider); openmct.objects.addProvider('test-namespace', testObjectProvider);
testObjectProvider.observe.and.returnValue(() => {}); 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) => { return openmct.objects.getMutable(notebookViewObject.identifier).then((mutableObject) => {
mutableNotebookObject = mutableObject; mutableNotebookObject = mutableObject;

View File

@ -125,7 +125,7 @@ export function addNotebookEntry(openmct, domainObject, notebookStorage, embed =
const newEntries = addEntryIntoPage(notebookStorage, entries, entry); const newEntries = addEntryIntoPage(notebookStorage, entries, entry);
addDefaultClass(domainObject, openmct); addDefaultClass(domainObject, openmct);
openmct.objects.mutate(domainObject, 'configuration.entries', newEntries); domainObject.configuration.entries = newEntries;
return id; return id;
} }

View File

@ -96,9 +96,6 @@ export function setDefaultNotebookPageId(pageId) {
export function validateNotebookStorageObject() { export function validateNotebookStorageObject() {
const notebookStorage = getDefaultNotebook(); const notebookStorage = getDefaultNotebook();
if (!notebookStorage) {
return true;
}
let valid = false; let valid = false;
if (notebookStorage) { if (notebookStorage) {

View File

@ -34,7 +34,7 @@ describe('the plugin', () => {
let countFramesPromise; let countFramesPromise;
beforeEach((done) => { beforeEach((done) => {
openmct = createOpenMct(); openmct = createOpenMct(false);
element = document.createElement('div'); element = document.createElement('div');
child = document.createElement('div'); child = document.createElement('div');

View File

@ -15,12 +15,16 @@
port.onmessage = async function (event) { port.onmessage = async function (event) {
if (event.data.request === 'close') { if (event.data.request === 'close') {
console.log('Closing connection');
connections.splice(event.data.connectionId - 1, 1); connections.splice(event.data.connectionId - 1, 1);
if (connections.length <= 0) { if (connections.length <= 0) {
// abort any outstanding requests if there's nobody listening to it. // abort any outstanding requests if there's nobody listening to it.
controller.abort(); controller.abort();
} }
console.log('Closed.');
connected = false;
return; return;
} }
@ -29,68 +33,9 @@
return; return;
} }
connected = true; do {
await self.listenForChanges(event.data.url, event.data.body, port);
let url = event.data.url; } while (connected);
let body = event.data.body;
let error = false;
// feed=continuous maintains an indefinitely open connection with a keep-alive of HEARTBEAT milliseconds until this client closes the connection
// style=main_only returns only the current winning revision of the document
const response = await fetch(url, {
method: 'POST',
headers: {
"Content-Type": 'application/json'
},
signal,
body
});
let reader;
if (response.body === undefined) {
error = true;
} else {
reader = response.body.getReader();
}
while (!error) {
const {done, value} = await reader.read();
//done is true when we lose connection with the provider
if (done) {
error = true;
}
if (value) {
let chunk = new Uint8Array(value.length);
chunk.set(value, 0);
const decodedChunk = new TextDecoder("utf-8").decode(chunk).split('\n');
if (decodedChunk.length && decodedChunk[decodedChunk.length - 1] === '') {
decodedChunk.forEach((doc, index) => {
try {
if (doc) {
const objectChanges = JSON.parse(doc);
connections.forEach(function (connection) {
connection.postMessage({
objectChanges
});
});
}
} catch (decodeError) {
//do nothing;
console.log(decodeError);
}
});
}
}
}
if (error) {
port.postMessage({
error
});
}
} }
}; };
@ -103,4 +48,64 @@
console.log('Error on feed'); console.log('Error on feed');
}; };
self.listenForChanges = async function (url, body, port) {
connected = true;
let error = false;
// feed=continuous maintains an indefinitely open connection with a keep-alive of HEARTBEAT milliseconds until this client closes the connection
// style=main_only returns only the current winning revision of the document
console.log('Opening changes feed connection.');
const response = await fetch(url, {
method: 'POST',
headers: {
"Content-Type": 'application/json'
},
signal,
body
});
let reader;
if (response.body === undefined) {
error = true;
} else {
reader = response.body.getReader();
}
while (!error) {
const {done, value} = await reader.read();
//done is true when we lose connection with the provider
if (done) {
error = true;
}
if (value) {
let chunk = new Uint8Array(value.length);
chunk.set(value, 0);
const decodedChunk = new TextDecoder("utf-8").decode(chunk).split('\n');
console.log('Received chunk');
if (decodedChunk.length && decodedChunk[decodedChunk.length - 1] === '') {
decodedChunk.forEach((doc, index) => {
try {
if (doc) {
const objectChanges = JSON.parse(doc);
connections.forEach(function (connection) {
connection.postMessage({
objectChanges
});
});
}
} catch (decodeError) {
//do nothing;
console.log(decodeError);
}
});
}
}
}
console.log('Done reading changes feed');
};
}()); }());

View File

@ -29,7 +29,7 @@ const ID = "_id";
const HEARTBEAT = 50000; const HEARTBEAT = 50000;
const ALL_DOCS = "_all_docs?include_docs=true"; const ALL_DOCS = "_all_docs?include_docs=true";
export default class CouchObjectProvider { class CouchObjectProvider {
constructor(openmct, options, namespace) { constructor(openmct, options, namespace) {
options = this._normalize(options); options = this._normalize(options);
this.openmct = openmct; this.openmct = openmct;
@ -74,13 +74,6 @@ export default class CouchObjectProvider {
if (event.data.type === 'connection') { if (event.data.type === 'connection') {
this.changesFeedSharedWorkerConnectionId = event.data.connectionId; this.changesFeedSharedWorkerConnectionId = event.data.connectionId;
} else { } else {
const error = event.data.error;
if (error && Object.keys(this.observers).length > 0) {
this.observeObjectChanges();
return;
}
let objectChanges = event.data.objectChanges; let objectChanges = event.data.objectChanges;
objectChanges.identifier = { objectChanges.identifier = {
namespace: this.namespace, namespace: this.namespace,
@ -126,11 +119,12 @@ export default class CouchObjectProvider {
} }
return fetch(this.url + '/' + subPath, fetchOptions) return fetch(this.url + '/' + subPath, fetchOptions)
.then(response => response.json()) .then((response) => {
.then(function (response) { if (response.status === CouchObjectProvider.HTTP_CONFLICT) {
return response; throw new this.openmct.objects.errors.Conflict(`Conflict persisting ${fetchOptions.body.name}`);
}, function () { }
return undefined;
return response.json();
}); });
} }
@ -561,12 +555,18 @@ export default class CouchObjectProvider {
let intermediateResponse = this.getIntermediateResponse(); let intermediateResponse = this.getIntermediateResponse();
const key = model.identifier.key; const key = model.identifier.key;
this.enqueueObject(key, model, intermediateResponse); this.enqueueObject(key, model, intermediateResponse);
this.objectQueue[key].pending = true; if (!this.objectQueue[key].pending) {
const queued = this.objectQueue[key].dequeue(); this.objectQueue[key].pending = true;
let document = new CouchDocument(key, queued.model); const queued = this.objectQueue[key].dequeue();
this.request(key, "PUT", document).then((response) => { let document = new CouchDocument(key, queued.model);
this.checkResponse(response, queued.intermediateResponse, key); this.request(key, "PUT", document).then((response) => {
}); console.log('create check response', key);
this.checkResponse(response, queued.intermediateResponse, key);
}).catch(error => {
queued.intermediateResponse.reject(error);
this.objectQueue[key].pending = false;
});
}
return intermediateResponse.promise; return intermediateResponse.promise;
} }
@ -581,6 +581,9 @@ export default class CouchObjectProvider {
let document = new CouchDocument(key, queued.model, this.objectQueue[key].rev); let document = new CouchDocument(key, queued.model, this.objectQueue[key].rev);
this.request(key, "PUT", document).then((response) => { this.request(key, "PUT", document).then((response) => {
this.checkResponse(response, queued.intermediateResponse, key); this.checkResponse(response, queued.intermediateResponse, key);
}).catch((error) => {
queued.intermediateResponse.reject(error);
this.objectQueue[key].pending = false;
}); });
} }
} }
@ -594,3 +597,7 @@ export default class CouchObjectProvider {
return intermediateResponse.promise; return intermediateResponse.promise;
} }
} }
CouchObjectProvider.HTTP_CONFLICT = 409;
export default CouchObjectProvider;

View File

@ -49,7 +49,7 @@ describe('the plugin', () => {
filter: {}, filter: {},
disableObserve: true disableObserve: true
}; };
openmct = createOpenMct(); openmct = createOpenMct(false);
openmct.$injector = jasmine.createSpyObj('$injector', ['get']); openmct.$injector = jasmine.createSpyObj('$injector', ['get']);
mockIdentifierService = jasmine.createSpyObj( mockIdentifierService = jasmine.createSpyObj(

View File

@ -36,15 +36,7 @@ describe('the plugin', function () {
appHolder.style.width = '640px'; appHolder.style.width = '640px';
appHolder.style.height = '480px'; appHolder.style.height = '480px';
const timeSystemOptions = { openmct = createOpenMct();
timeSystemKey: 'utc',
bounds: {
start: 1597160002854,
end: 1597181232854
}
};
openmct = createOpenMct(timeSystemOptions);
openmct.install(new PlanPlugin()); openmct.install(new PlanPlugin());
planDefinition = openmct.types.get('plan').definition; planDefinition = openmct.types.get('plan').definition;
@ -56,6 +48,7 @@ describe('the plugin', function () {
child.style.width = '640px'; child.style.width = '640px';
child.style.height = '480px'; child.style.height = '480px';
element.appendChild(child); element.appendChild(child);
openmct.on('start', done); openmct.on('start', done);
openmct.start(appHolder); openmct.start(appHolder);
}); });
@ -79,6 +72,7 @@ describe('the plugin', function () {
}); });
describe('the plan view', () => { describe('the plan view', () => {
it('provides a plan view', () => { it('provides a plan view', () => {
const testViewObject = { const testViewObject = {
id: "test-object", id: "test-object",
@ -89,6 +83,7 @@ describe('the plugin', function () {
let planView = applicableViews.find((viewProvider) => viewProvider.key === 'plan.view'); let planView = applicableViews.find((viewProvider) => viewProvider.key === 'plan.view');
expect(planView).toBeDefined(); expect(planView).toBeDefined();
}); });
}); });
describe('the plan view displays activities', () => { describe('the plan view displays activities', () => {
@ -160,22 +155,12 @@ describe('the plugin', function () {
expect(labelEl.innerHTML).toEqual('TEST-GROUP'); expect(labelEl.innerHTML).toEqual('TEST-GROUP');
}); });
it('displays the activities and their labels', (done) => { it('displays the activities and their labels', () => {
const bounds = { const rectEls = element.querySelectorAll('.c-plan__contents rect');
start: 1597160002854, expect(rectEls.length).toEqual(2);
end: 1597181232854 const textEls = element.querySelectorAll('.c-plan__contents text');
}; expect(textEls.length).toEqual(3);
openmct.time.bounds(bounds);
Vue.nextTick(() => {
const rectEls = element.querySelectorAll('.c-plan__contents rect');
expect(rectEls.length).toEqual(2);
const textEls = element.querySelectorAll('.c-plan__contents text');
expect(textEls.length).toEqual(3);
done();
});
}); });
}); });
}); });

View File

@ -21,67 +21,57 @@
--> -->
<template> <template>
<div class="u-contents"> <div class="u-contents">
<ul v-if="canEdit" <div v-if="canEdit"
class="l-inspector-part" class="grid-row"
> >
<h2 v-if="heading" <div class="grid-cell label"
:title="heading" :title="editTitle"
>{{ heading }}</h2> >{{ shortLabel }}</div>
<li class="grid-row"> <div class="grid-cell value">
<div class="grid-cell label" <div class="c-click-swatch c-click-swatch--menu"
:title="editTitle" @click="toggleSwatch()"
>{{ shortLabel }}</div> >
<div class="grid-cell value"> <span class="c-color-swatch"
<div class="c-click-swatch c-click-swatch--menu" :style="{ background: currentColor }"
@click="toggleSwatch()"
> >
<span class="c-color-swatch" </span>
:style="{ background: currentColor }" </div>
<div class="c-palette c-palette--color">
<div v-show="swatchActive"
class="c-palette__items"
>
<div v-for="group in colorPaletteGroups"
:key="group.id"
class="u-contents"
> >
</span> <div v-for="color in group"
</div> :key="color.id"
<div class="c-palette c-palette--color"> class="c-palette__item"
<div v-show="swatchActive" :class="{ 'selected': currentColor === color.hexString }"
class="c-palette__items" :style="{ background: color.hexString }"
> @click="setColor(color)"
<div v-for="group in colorPaletteGroups"
:key="group.id"
class="u-contents"
> >
<div v-for="color in group"
:key="color.id"
class="c-palette__item"
:class="{ 'selected': currentColor === color.hexString }"
:style="{ background: color.hexString }"
@click="setColor(color)"
>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</li> </div>
</ul> </div>
<ul v-else <div v-else
class="l-inspector-part" class="grid-row"
> >
<h2 v-if="heading" <div class="grid-cell label"
:title="heading" :title="viewTitle"
>{{ heading }}</h2> >{{ shortLabel }}</div>
<li class="grid-row"> <div class="grid-cell value">
<div class="grid-cell label" <span class="c-color-swatch"
:title="viewTitle" :style="{
>{{ shortLabel }}</div> 'background': currentColor
<div class="grid-cell value"> }"
<span class="c-color-swatch" >
:style="{ </span>
'background': currentColor </div>
}" </div>
>
</span>
</div>
</li>
</ul>
</div> </div>
</template> </template>
@ -114,12 +104,6 @@ export default {
default() { default() {
return 'Color'; return 'Color';
} }
},
heading: {
type: String,
default() {
return '';
}
} }
}, },
data() { data() {

View File

@ -39,6 +39,10 @@ export default function BarGraphCompositionPolicy(openmct) {
return metadata.values().length > 0 && hasAggregateDomainAndRange(metadata); return metadata.values().length > 0 && hasAggregateDomainAndRange(metadata);
} }
function hasNoChildren(parentObject) {
return parentObject.composition && parentObject.composition.length < 1;
}
return { return {
allow: function (parent, child) { allow: function (parent, child) {
if ((parent.type === BAR_GRAPH_KEY) if ((parent.type === BAR_GRAPH_KEY)

View File

@ -107,7 +107,7 @@ export default {
}; };
this.openmct.objects.mutate( this.openmct.objects.mutate(
this.domainObject, this.domainObject,
`configuration.barStyles[${this.key}]`, `configuration.barStyles[${key}]`,
this.domainObject.configuration.barStyles[key] this.domainObject.configuration.barStyles[key]
); );
} else { } else {
@ -150,6 +150,10 @@ export default {
}, },
getAxisMetadata(telemetryObject) { getAxisMetadata(telemetryObject) {
const metadata = this.openmct.telemetry.getMetadata(telemetryObject); const metadata = this.openmct.telemetry.getMetadata(telemetryObject);
if (!metadata) {
return {};
}
const yAxisMetadata = metadata.valuesForHints(['range'])[0]; const yAxisMetadata = metadata.valuesForHints(['range'])[0];
//Exclude 'name' and 'time' based metadata specifically, from the x-Axis values by using range hints only //Exclude 'name' and 'time' based metadata specifically, from the x-Axis values by using range hints only
const xAxisMetadata = metadata.valuesForHints(['range']); const xAxisMetadata = metadata.valuesForHints(['range']);
@ -255,6 +259,9 @@ export default {
data.forEach((datum) => { data.forEach((datum) => {
this.processData(telemetryObject, datum, axisMetadata); this.processData(telemetryObject, datum, axisMetadata);
}); });
})
.catch((error) => {
console.warn(`Error fetching data`, error);
}); });
}, },
subscribeToObject(telemetryObject) { subscribeToObject(telemetryObject) {

View File

@ -33,9 +33,9 @@
</li> </li>
<ColorSwatch v-if="expanded" <ColorSwatch v-if="expanded"
:current-color="currentColor" :current-color="currentColor"
title="Manually set 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" edit-title="Manually set the color for this bar graph series"
view-title="The color for this bar graph." view-title="The color for this bar graph series."
short-label="Color" short-label="Color"
class="grid-properties" class="grid-properties"
@colorSet="setColor" @colorSet="setColor"

View File

@ -20,15 +20,13 @@
at runtime from the About dialog for additional information. at runtime from the About dialog for additional information.
--> -->
<template> <template>
<div> <ul class="c-tree">
<ul class="c-tree"> <h2 title="Display properties for this object">Bar Graph Series</h2>
<li v-for="series in domainObject.composition" <bar-graph-options v-for="series in domainObject.composition"
:key="series.key" :key="series.key"
> :item="series"
<bar-graph-options :item="series" /> />
</li> </ul>
</ul>
</div>
</template> </template>
<script> <script>

View File

@ -19,9 +19,6 @@
this source code distribution or the Licensing information page available this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information. at runtime from the About dialog for additional information.
--> -->
<!-- eslint-disable vue/no-v-html -->
<template> <template>
<div class="gl-plot-chart-area"> <div class="gl-plot-chart-area">
<span v-html="canvasTemplate"></span> <span v-html="canvasTemplate"></span>

View File

@ -82,12 +82,17 @@ export default class PlotSeries extends Model {
.openmct .openmct
.telemetry .telemetry
.getMetadata(options.domainObject); .getMetadata(options.domainObject);
this.formats = options this.formats = options
.openmct .openmct
.telemetry .telemetry
.getFormatMap(this.metadata); .getFormatMap(this.metadata);
const range = this.metadata.valuesForHints(['range'])[0]; //if the object is missing or doesn't have metadata for some reason
let range = {};
if (this.metadata) {
range = this.metadata.valuesForHints(['range'])[0];
}
return { return {
name: options.domainObject.name, name: options.domainObject.name,
@ -191,7 +196,10 @@ export default class PlotSeries extends Model {
.uniq(true, point => [this.getXVal(point), this.getYVal(point)].join()) .uniq(true, point => [this.getXVal(point), this.getYVal(point)].join())
.value(); .value();
this.reset(newPoints); this.reset(newPoints);
}.bind(this)); }.bind(this))
.catch((error) => {
console.warn('Error fetching data', error);
});
/* eslint-enable you-dont-need-lodash-underscore/concat */ /* eslint-enable you-dont-need-lodash-underscore/concat */
} }
/** /**
@ -199,7 +207,9 @@ export default class PlotSeries extends Model {
*/ */
onXKeyChange(xKey) { onXKeyChange(xKey) {
const format = this.formats[xKey]; const format = this.formats[xKey];
this.getXVal = format.parse.bind(format); if (format) {
this.getXVal = format.parse.bind(format);
}
} }
/** /**
* Update y formatter on change, default to stepAfter interpolation if * Update y formatter on change, default to stepAfter interpolation if

View File

@ -184,7 +184,7 @@ export default class YAxisModel extends Model {
this.set('values', yMetadata.values); this.set('values', yMetadata.values);
if (!label) { if (!label) {
const labelName = series.map(function (s) { const labelName = series.map(function (s) {
return s.metadata.value(s.get('yKey')).name; return s.metadata ? s.metadata.value(s.get('yKey')).name : '';
}).reduce(function (a, b) { }).reduce(function (a, b) {
if (a === undefined) { if (a === undefined) {
return b; return b;
@ -204,7 +204,7 @@ export default class YAxisModel extends Model {
} }
const labelUnits = series.map(function (s) { const labelUnits = series.map(function (s) {
return s.metadata.value(s.get('yKey')).units; return s.metadata ? s.metadata.value(s.get('yKey')).units : '';
}).reduce(function (a, b) { }).reduce(function (a, b) {
if (a === undefined) { if (a === undefined) {
return b; return b;

View File

@ -37,6 +37,7 @@ describe("the plugin", function () {
let openmct; let openmct;
let telemetryPromise; let telemetryPromise;
let telemetryPromiseResolve; let telemetryPromiseResolve;
let cleanupFirst;
let mockObjectPath; let mockObjectPath;
let telemetrylimitProvider; let telemetrylimitProvider;
@ -76,16 +77,9 @@ describe("the plugin", function () {
'some-other-key': 'some-other-value 3' 'some-other-key': 'some-other-value 3'
} }
]; ];
cleanupFirst = [];
const timeSystem = { openmct = createOpenMct();
timeSystemKey: 'utc',
bounds: {
start: 0,
end: 4
}
};
openmct = createOpenMct(timeSystem);
telemetryPromise = new Promise((resolve) => { telemetryPromise = new Promise((resolve) => {
telemetryPromiseResolve = resolve; telemetryPromiseResolve = resolve;
@ -152,6 +146,11 @@ describe("the plugin", function () {
disconnect() {} disconnect() {}
}); });
openmct.time.timeSystem("utc", {
start: 0,
end: 4
});
openmct.types.addType("test-object", { openmct.types.addType("test-object", {
creatable: true creatable: true
}); });
@ -171,8 +170,19 @@ describe("the plugin", function () {
end: 1 end: 1
}); });
configStore.deleteAll(); // Needs to be in a timeout because plots use a bunch of setTimeouts, some of which can resolve during or after
resetApplicationState(openmct).then(done).catch(done); // 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(element);
configStore.deleteAll();
resetApplicationState(openmct).then(done).catch(done);
});
}); });
describe("the plot views", () => { describe("the plot views", () => {
@ -385,6 +395,10 @@ describe("the plugin", function () {
plotView = plotViewProvider.view(testTelemetryObject, [testTelemetryObject]); plotView = plotViewProvider.view(testTelemetryObject, [testTelemetryObject]);
plotView.show(child, true); plotView.show(child, true);
cleanupFirst.push(() => {
plotView.destroy();
});
return Vue.nextTick(); return Vue.nextTick();
}); });
@ -404,23 +418,12 @@ describe("the plugin", function () {
expect(legend.length).toBe(6); expect(legend.length).toBe(6);
}); });
it("Renders X-axis ticks for the telemetry object", (done) => { it("Renders X-axis ticks for the telemetry object", () => {
const configId = openmct.objects.makeKeyString(testTelemetryObject.identifier); let xAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-x .gl-plot-tick-wrapper");
const config = configStore.get(configId); expect(xAxisElement.length).toBe(1);
config.xAxis.set('displayRange', {
min: 0,
max: 4
});
Vue.nextTick(() => { let ticks = xAxisElement[0].querySelectorAll(".gl-plot-tick");
let xAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-x .gl-plot-tick-wrapper"); expect(ticks.length).toBe(5);
expect(xAxisElement.length).toBe(1);
let ticks = xAxisElement[0].querySelectorAll(".gl-plot-tick");
expect(ticks.length).toBe(5);
done();
});
}); });
it("Renders Y-axis options for the telemetry object", () => { it("Renders Y-axis options for the telemetry object", () => {
@ -747,6 +750,11 @@ describe("the plugin", function () {
template: "<stacked-plot></stacked-plot>" template: "<stacked-plot></stacked-plot>"
}); });
cleanupFirst.push(() => {
component.$destroy();
component = undefined;
});
return telemetryPromise return telemetryPromise
.then(Vue.nextTick()) .then(Vue.nextTick())
.then(() => { .then(() => {
@ -772,21 +780,12 @@ describe("the plugin", function () {
expect(legend.length).toBe(6); expect(legend.length).toBe(6);
}); });
it("Renders X-axis ticks for the telemetry object", (done) => { xit("Renders X-axis ticks for the telemetry object", () => {
let xAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-x .gl-plot-tick-wrapper"); let xAxisElement = element.querySelectorAll(".gl-plot-axis-area.gl-plot-x .gl-plot-tick-wrapper");
expect(xAxisElement.length).toBe(1); expect(xAxisElement.length).toBe(1);
config.xAxis.set('displayRange', { let ticks = xAxisElement[0].querySelectorAll(".gl-plot-tick");
min: 0, expect(ticks.length).toBe(5);
max: 4
});
Vue.nextTick(() => {
let ticks = xAxisElement[0].querySelectorAll(".gl-plot-tick");
expect(ticks.length).toBe(5);
done();
});
}); });
it("Renders Y-axis ticks for the telemetry object", (done) => { it("Renders Y-axis ticks for the telemetry object", (done) => {

View File

@ -52,24 +52,22 @@
> >
</button> </button>
</div> </div>
<div class="l-view-section"> <stacked-plot-item v-for="object in compositionObjects"
<stacked-plot-item v-for="object in compositionObjects" :key="object.id"
:key="object.id" class="c-plot--stacked-container"
class="c-plot--stacked-container" :object="object"
:object="object" :options="options"
:options="options" :grid-lines="gridLines"
:grid-lines="gridLines" :cursor-guide="cursorGuide"
:cursor-guide="cursorGuide" :plot-tick-width="maxTickWidth"
:plot-tick-width="maxTickWidth" @plotTickWidth="onTickWidthChange"
@plotTickWidth="onTickWidthChange" @loadingUpdated="loadingUpdated"
@loadingUpdated="loadingUpdated" />
/>
</div>
</div> </div>
</template> </template>
<script> <script>
import eventHelpers from '../lib/eventHelpers';
import StackedPlotItem from './StackedPlotItem.vue'; import StackedPlotItem from './StackedPlotItem.vue';
import ImageExporter from '../../../exporters/ImageExporter'; import ImageExporter from '../../../exporters/ImageExporter';
@ -104,6 +102,8 @@ export default {
this.destroy(); this.destroy();
}, },
mounted() { mounted() {
eventHelpers.extend(this);
this.imageExporter = new ImageExporter(this.openmct); this.imageExporter = new ImageExporter(this.openmct);
this.tickWidthMap = {}; this.tickWidthMap = {};
@ -118,6 +118,7 @@ export default {
this.loading = loaded; this.loading = loaded;
}, },
destroy() { destroy() {
this.stopListening();
this.composition.off('add', this.addChild); this.composition.off('add', this.addChild);
this.composition.off('remove', this.removeChild); this.composition.off('remove', this.removeChild);
this.composition.off('reorder', this.compositionReorder); this.composition.off('reorder', this.compositionReorder);

View File

@ -75,11 +75,6 @@ export default {
mounted() { mounted() {
this.updateView(); this.updateView();
}, },
beforeDestroy() {
if (this.component) {
this.component.$destroy();
}
},
methods: { methods: {
updateComponentProp(prop, value) { updateComponentProp(prop, value) {
if (this.component) { if (this.component) {

View File

@ -48,17 +48,17 @@ define([
components: { components: {
TabsComponent: TabsComponent.default TabsComponent: TabsComponent.default
}, },
data() {
return {
isEditing: editMode
};
},
provide: { provide: {
openmct, openmct,
domainObject, domainObject,
objectPath, objectPath,
composition: openmct.composition.get(domainObject) composition: openmct.composition.get(domainObject)
}, },
data() {
return {
isEditing: editMode
};
},
template: '<tabs-component :isEditing="isEditing"></tabs-component>' template: '<tabs-component :isEditing="isEditing"></tabs-component>'
}); });
}, },

View File

@ -60,18 +60,17 @@ define([
this.addTelemetryObject = this.addTelemetryObject.bind(this); this.addTelemetryObject = this.addTelemetryObject.bind(this);
this.removeTelemetryObject = this.removeTelemetryObject.bind(this); this.removeTelemetryObject = this.removeTelemetryObject.bind(this);
this.removeTelemetryCollection = this.removeTelemetryCollection.bind(this); this.removeTelemetryCollection = this.removeTelemetryCollection.bind(this);
this.incrementOutstandingRequests = this.incrementOutstandingRequests.bind(this);
this.decrementOutstandingRequests = this.decrementOutstandingRequests.bind(this);
this.resetRowsFromAllData = this.resetRowsFromAllData.bind(this); this.resetRowsFromAllData = this.resetRowsFromAllData.bind(this);
this.isTelemetryObject = this.isTelemetryObject.bind(this); this.isTelemetryObject = this.isTelemetryObject.bind(this);
this.refreshData = this.refreshData.bind(this);
this.updateFilters = this.updateFilters.bind(this); this.updateFilters = this.updateFilters.bind(this);
this.clearData = this.clearData.bind(this);
this.buildOptionsFromConfiguration = this.buildOptionsFromConfiguration.bind(this); this.buildOptionsFromConfiguration = this.buildOptionsFromConfiguration.bind(this);
this.filterObserver = undefined; this.filterObserver = undefined;
this.createTableRowCollections(); this.createTableRowCollections();
openmct.time.on('bounds', this.refreshData);
openmct.time.on('timeSystem', this.refreshData);
} }
/** /**
@ -141,8 +140,6 @@ define([
let columnMap = this.getColumnMapForObject(keyString); let columnMap = this.getColumnMapForObject(keyString);
let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject); let limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject);
this.incrementOutstandingRequests();
const telemetryProcessor = this.getTelemetryProcessor(keyString, columnMap, limitEvaluator); const telemetryProcessor = this.getTelemetryProcessor(keyString, columnMap, limitEvaluator);
const telemetryRemover = this.getTelemetryRemover(); const telemetryRemover = this.getTelemetryRemover();
@ -151,13 +148,13 @@ define([
this.telemetryCollections[keyString] = this.openmct.telemetry this.telemetryCollections[keyString] = this.openmct.telemetry
.requestCollection(telemetryObject, requestOptions); .requestCollection(telemetryObject, requestOptions);
this.telemetryCollections[keyString].on('requestStarted', this.incrementOutstandingRequests);
this.telemetryCollections[keyString].on('requestEnded', this.decrementOutstandingRequests);
this.telemetryCollections[keyString].on('remove', telemetryRemover); this.telemetryCollections[keyString].on('remove', telemetryRemover);
this.telemetryCollections[keyString].on('add', telemetryProcessor); this.telemetryCollections[keyString].on('add', telemetryProcessor);
this.telemetryCollections[keyString].on('clear', this.tableRows.clear); this.telemetryCollections[keyString].on('clear', this.clearData);
this.telemetryCollections[keyString].load(); this.telemetryCollections[keyString].load();
this.decrementOutstandingRequests();
this.telemetryObjects[keyString] = { this.telemetryObjects[keyString] = {
telemetryObject, telemetryObject,
keyString, keyString,
@ -268,17 +265,6 @@ define([
this.emit('object-removed', objectIdentifier); this.emit('object-removed', objectIdentifier);
} }
refreshData(bounds, isTick) {
if (!isTick && this.tableRows.outstandingRequests === 0) {
this.tableRows.clear();
this.tableRows.sortBy({
key: this.openmct.time.timeSystem().key,
direction: 'asc'
});
this.tableRows.resubscribe();
}
}
clearData() { clearData() {
this.tableRows.clear(); this.tableRows.clear();
this.emit('refresh'); this.emit('refresh');
@ -378,9 +364,6 @@ define([
let keystrings = Object.keys(this.telemetryCollections); let keystrings = Object.keys(this.telemetryCollections);
keystrings.forEach(this.removeTelemetryCollection); keystrings.forEach(this.removeTelemetryCollection);
this.openmct.time.off('bounds', this.refreshData);
this.openmct.time.off('timeSystem', this.refreshData);
if (this.filterObserver) { if (this.filterObserver) {
this.filterObserver(); this.filterObserver();
} }

View File

@ -131,7 +131,8 @@ export default {
objects.forEach(object => this.addColumnsForObject(object, false)); objects.forEach(object => this.addColumnsForObject(object, false));
}, },
addColumnsForObject(telemetryObject) { addColumnsForObject(telemetryObject) {
let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values(); const metadata = this.openmct.telemetry.getMetadata(telemetryObject);
let metadataValues = metadata ? metadata.values() : [];
metadataValues.forEach(metadatum => { metadataValues.forEach(metadatum => {
let column = new TelemetryTableColumn(this.openmct, metadatum); let column = new TelemetryTableColumn(this.openmct, metadatum);
this.tableConfiguration.addSingleColumnForObject(telemetryObject, column); this.tableConfiguration.addSingleColumnForObject(telemetryObject, column);

View File

@ -105,7 +105,8 @@ export default {
composition.load().then((domainObjects) => { composition.load().then((domainObjects) => {
domainObjects.forEach(telemetryObject => { domainObjects.forEach(telemetryObject => {
let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier); let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values(); const metadata = this.openmct.telemetry.getMetadata(telemetryObject);
let metadataValues = metadata ? metadata.values() : [];
let filters = this.filteredTelemetry[keyString]; let filters = this.filteredTelemetry[keyString];
if (filters !== undefined) { if (filters !== undefined) {

View File

@ -125,7 +125,6 @@
<div <div
class="c-table c-telemetry-table c-table--filterable c-table--sortable has-control-bar u-style-receiver js-style-receiver" class="c-table c-telemetry-table c-table--filterable c-table--sortable has-control-bar u-style-receiver js-style-receiver"
:class="{ :class="{
'loading': loading,
'is-paused' : paused 'is-paused' : paused
}" }"
> >
@ -362,7 +361,7 @@ export default {
autoScroll: true, autoScroll: true,
sortOptions: {}, sortOptions: {},
filters: {}, filters: {},
loading: true, loading: false,
scrollable: undefined, scrollable: undefined,
tableEl: undefined, tableEl: undefined,
headersHolderEl: undefined, headersHolderEl: undefined,
@ -422,6 +421,14 @@ export default {
} }
}, },
watch: { watch: {
loading: {
handler(isLoading) {
if (this.viewActionsCollection) {
let action = isLoading ? 'disable' : 'enable';
this.viewActionsCollection[action](['export-csv-all']);
}
}
},
markedRows: { markedRows: {
handler(newVal, oldVal) { handler(newVal, oldVal) {
this.$emit('marked-rows-updated', newVal, oldVal); this.$emit('marked-rows-updated', newVal, oldVal);
@ -528,6 +535,7 @@ export default {
if (!this.updatingView) { if (!this.updatingView) {
this.updatingView = true; this.updatingView = true;
requestAnimationFrame(() => { requestAnimationFrame(() => {
let start = 0; let start = 0;
let end = VISIBLE_ROW_COUNT; let end = VISIBLE_ROW_COUNT;
let tableRows = this.table.tableRows.getRows(); let tableRows = this.table.tableRows.getRows();
@ -1019,6 +1027,12 @@ export default {
this.viewActionsCollection.disable(['export-csv-marked', 'unmark-all-rows']); this.viewActionsCollection.disable(['export-csv-marked', 'unmark-all-rows']);
} }
if (this.loading) {
this.viewActionsCollection.disable(['export-csv-all']);
} else {
this.viewActionsCollection.enable(['export-csv-all']);
}
if (this.paused) { if (this.paused) {
this.viewActionsCollection.hide(['pause-data']); this.viewActionsCollection.hide(['pause-data']);
this.viewActionsCollection.show(['play-data']); this.viewActionsCollection.show(['play-data']);

View File

@ -222,13 +222,9 @@ describe("the plugin", () => {
openmct.router.path = originalRouterPath; openmct.router.path = originalRouterPath;
}); });
it("Renders a row for every telemetry datum returned", (done) => { it("Renders a row for every telemetry datum returned", () => {
let rows = element.querySelectorAll('table.c-telemetry-table__body tr'); let rows = element.querySelectorAll('table.c-telemetry-table__body tr');
Vue.nextTick(() => { expect(rows.length).toBe(3);
expect(rows.length).toBe(3);
done();
});
}); });
it("Renders a column for every item in telemetry metadata", () => { it("Renders a column for every item in telemetry metadata", () => {

View File

@ -168,16 +168,13 @@ export default {
} }
}, },
zoom(bounds) { zoom(bounds) {
if (isNaN(bounds.start) || isNaN(bounds.end)) { this.isZooming = true;
this.isZooming = false; this.formattedBounds.start = this.timeFormatter.format(bounds.start);
} else { this.formattedBounds.end = this.timeFormatter.format(bounds.end);
this.isZooming = true;
this.formattedBounds.start = this.timeFormatter.format(bounds.start);
this.formattedBounds.end = this.timeFormatter.format(bounds.end);
}
}, },
endZoom(bounds) { endZoom(bounds) {
this.isZooming = false; this.isZooming = false;
if (bounds) { if (bounds) {
this.openmct.time.bounds(bounds); this.openmct.time.bounds(bounds);
} else { } else {

View File

@ -40,7 +40,7 @@ const LOCAL_STORAGE_HISTORY_KEY_FIXED = 'tcHistory';
const LOCAL_STORAGE_HISTORY_KEY_REALTIME = 'tcHistoryRealtime'; const LOCAL_STORAGE_HISTORY_KEY_REALTIME = 'tcHistoryRealtime';
const DEFAULT_RECORDS = 10; const DEFAULT_RECORDS = 10;
import { millisecondsToDHMS } from "utils/duration"; import { getDuration } from "utils/duration";
export default { export default {
inject: ['openmct', 'configuration'], inject: ['openmct', 'configuration'],
@ -142,7 +142,7 @@ export default {
let description = `${startTime} - ${this.formatTime(timespan.end)}`; let description = `${startTime} - ${this.formatTime(timespan.end)}`;
if (this.timeSystem.isUTCBased && !this.openmct.time.clock()) { if (this.timeSystem.isUTCBased && !this.openmct.time.clock()) {
name = `${startTime} ${millisecondsToDHMS(timespan.end - timespan.start)}`; name = `${startTime} ${getDuration(timespan.end - timespan.start)}`;
} else { } else {
name = description; name = description;
} }
@ -263,7 +263,7 @@ export default {
format: format format: format
}).formatter; }).formatter;
return (isNegativeOffset ? '-' : '') + formatter.format(time, 'YYYY-MM-DD HH:mm:ss'); return (isNegativeOffset ? '-' : '') + formatter.format(time);
}, },
showHistoryMenu() { showHistoryMenu() {
const elementBoundingClientRect = this.$refs.historyButton.getBoundingClientRect(); const elementBoundingClientRect = this.$refs.historyButton.getBoundingClientRect();

View File

@ -151,29 +151,22 @@ export default {
this.stopFollowingTimeContext(); this.stopFollowingTimeContext();
this.timeContext = this.openmct.time.getContextForView([this.domainObject]); this.timeContext = this.openmct.time.getContextForView([this.domainObject]);
this.timeContext.on('timeContext', this.setTimeContext); this.timeContext.on('timeContext', this.setTimeContext);
this.timeContext.on('clock', this.setViewFromClock); this.timeContext.on('clock', this.setTimeOptions);
}, },
stopFollowingTimeContext() { stopFollowingTimeContext() {
if (this.timeContext) { if (this.timeContext) {
this.timeContext.off('timeContext', this.setTimeContext); this.timeContext.off('timeContext', this.setTimeContext);
this.timeContext.off('clock', this.setViewFromClock); this.timeContext.off('clock', this.setTimeOptions);
} }
}, },
setViewFromClock(clock) { setTimeOptions(clock) {
if (!this.timeOptions.mode) { this.timeOptions.clockOffsets = this.timeOptions.clockOffsets || this.timeContext.clockOffsets();
this.setTimeOptions(clock); this.timeOptions.fixedOffsets = this.timeOptions.fixedOffsets || this.timeContext.bounds();
}
},
setTimeOptions() {
if (!this.timeOptions || !this.timeOptions.mode) {
this.mode = this.timeContext.clock() === undefined ? { key: 'fixed' } : { key: Object.create(this.timeContext.clock()).key};
this.timeOptions = {
clockOffsets: this.timeContext.clockOffsets(),
fixedOffsets: this.timeContext.bounds()
};
}
this.registerIndependentTimeOffsets(); if (!this.timeOptions.mode) {
this.mode = this.timeContext.clock() === undefined ? {key: 'fixed'} : {key: Object.create(this.timeContext.clock()).key};
this.registerIndependentTimeOffsets();
}
}, },
saveFixedOffsets(offsets) { saveFixedOffsets(offsets) {
const newOptions = Object.assign({}, this.timeOptions, { const newOptions = Object.assign({}, this.timeOptions, {

View File

@ -20,7 +20,8 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
<template> <template>
<div ref="modeMenuButton" <div v-if="modes.length > 1"
ref="modeMenuButton"
class="c-ctrl-wrapper c-ctrl-wrapper--menus-up" class="c-ctrl-wrapper c-ctrl-wrapper--menus-up"
> >
<div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left"> <div class="c-menu-button c-ctrl-wrapper c-ctrl-wrapper--menus-left">

View File

@ -88,7 +88,7 @@ export default {
this.mutablePromise.then(() => { this.mutablePromise.then(() => {
this.openmct.objects.destroyMutable(this.domainObject); this.openmct.objects.destroyMutable(this.domainObject);
}); });
} else { } else if (this.domainObject.isMutable) {
this.openmct.objects.destroyMutable(this.domainObject); this.openmct.objects.destroyMutable(this.domainObject);
} }
}, },

View File

@ -51,15 +51,7 @@ describe('the plugin', function () {
} }
]; ];
const timeSystem = { openmct = createOpenMct();
timeSystemKey: 'utc',
bounds: {
start: 1597160002854,
end: 1597181232854
}
};
openmct = createOpenMct(timeSystem);
openmct.install(new TimelinePlugin()); openmct.install(new TimelinePlugin());
objectDef = openmct.types.get('time-strip').definition; objectDef = openmct.types.get('time-strip').definition;
@ -72,6 +64,11 @@ describe('the plugin', function () {
child.style.height = '480px'; child.style.height = '480px';
element.appendChild(child); element.appendChild(child);
openmct.time.timeSystem('utc', {
start: 1597160002854,
end: 1597181232854
});
openmct.on('start', done); openmct.on('start', done);
openmct.startHeadless(); openmct.startHeadless();
}); });
@ -91,6 +88,7 @@ describe('the plugin', function () {
}); });
describe('the time-strip object', () => { describe('the time-strip object', () => {
it('is creatable', () => { it('is creatable', () => {
expect(objectDef.creatable).toEqual(mockObject.creatable); expect(objectDef.creatable).toEqual(mockObject.creatable);
}); });

View File

@ -453,16 +453,12 @@ select {
} }
} }
.c-so-view--hyperlink.c-so-view--no-frame { .c-so-view--no-frame > .c-so-view__object-view > .c-hyperlink--button {
.c-hyperlink--button {
@include abs(); @include abs();
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 0; padding: 0;
}
.c-so-view__frame-controls { display: none; }
} }
/******************************************************** MENUS */ /******************************************************** MENUS */
@ -1010,9 +1006,6 @@ input[type="range"] {
transition: $transIn; transition: $transIn;
opacity: 1; opacity: 1;
pointer-events: inherit; pointer-events: inherit;
&[disabled] { opacity: $controlDisabledOpacity; }
} }
} }

View File

@ -46,6 +46,9 @@ mct-plot {
.c-plot, .c-plot,
.gl-plot { .gl-plot {
overflow: hidden;
min-height: 100px;
.s-status-taking-snapshot & { .s-status-taking-snapshot & {
.c-control-bar { .c-control-bar {
display: none; display: none;
@ -64,17 +67,16 @@ mct-plot {
.c-plot { .c-plot {
@include abs($mainViewPad); @include abs($mainViewPad);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden;
min-height: $plotMinH;
.c-control-bar { .c-control-bar {
flex: 0 0 auto; flex: 0 0 auto;
margin-bottom: $interiorMargin; margin-bottom: $interiorMargin;
} }
.l-view-section { .l-view-section, .c-plot--stacked-container {
display: flex; display: flex;
flex: 1 1 auto; flex: 1 1 auto;
flex-direction: column; flex-direction: column;
@ -82,18 +84,7 @@ mct-plot {
overflow-x: hidden; overflow-x: hidden;
} }
.c-plot--stacked-container {
display: flex;
flex: 1 1 auto;
flex-direction: column;
min-height: $plotMinH;
overflow: hidden;
}
;
&--stacked { &--stacked {
min-height: auto !important;
.child-frame { .child-frame {
.has-control-bar { .has-control-bar {
.c-control-bar { .c-control-bar {
@ -133,7 +124,7 @@ mct-plot {
.plot-wrapper-axis-and-display-area { .plot-wrapper-axis-and-display-area {
position: relative; position: relative;
flex: 1 1 auto; flex: 1 1 auto;
//min-height: $plotMinH; min-height: $plotMinH;
} }
.gl-plot-wrapper-display-area-and-x-axis { .gl-plot-wrapper-display-area-and-x-axis {
@ -474,7 +465,6 @@ mct-plot {
.gl-plot-legend, .gl-plot-legend,
.c-plot-legend { .c-plot-legend {
overflow: hidden; overflow: hidden;
flex: 0 0 auto; // Prevents clipping for all legend placements (top, bottom, etc.)
&__wrapper { &__wrapper {
// Holds view-control and both collapsed and expanded legends // Holds view-control and both collapsed and expanded legends

View File

@ -599,6 +599,8 @@
@mixin cArrowButtonBase($colorBg: transparent, $colorFg: $colorBtnFg, $filterHov: $filterHov) { @mixin cArrowButtonBase($colorBg: transparent, $colorFg: $colorBtnFg, $filterHov: $filterHov) {
// Copied from branch new-tree-refactor // Copied from branch new-tree-refactor
flex: 0 0 auto;
position: relative;
background: $colorBg; background: $colorBg;
&:before { &:before {

View File

@ -160,7 +160,9 @@ export default {
this.status = this.openmct.status.get(this.domainObject.identifier); this.status = this.openmct.status.get(this.domainObject.identifier);
this.removeStatusListener = this.openmct.status.observe(this.domainObject.identifier, this.setStatus); this.removeStatusListener = this.openmct.status.observe(this.domainObject.identifier, this.setStatus);
const provider = this.openmct.objectViews.get(this.domainObject, this.objectPath)[0]; const provider = this.openmct.objectViews.get(this.domainObject, this.objectPath)[0];
this.$refs.objectView.show(this.domainObject, provider.key, false, this.objectPath); if (provider) {
this.$refs.objectView.show(this.domainObject, provider.key, false, this.objectPath);
}
}, },
beforeDestroy() { beforeDestroy() {
this.removeStatusListener(); this.removeStatusListener();
@ -193,8 +195,10 @@ export default {
}, },
showMenuItems(event) { showMenuItems(event) {
const sortedActions = this.openmct.actions._groupAndSortActions(this.menuActionItems); const sortedActions = this.openmct.actions._groupAndSortActions(this.menuActionItems);
const menuItems = this.openmct.menus.actionsToMenuItems(sortedActions, this.actionCollection.objectPath, this.actionCollection.view); if (sortedActions.length) {
this.openmct.menus.showMenu(event.x, event.y, menuItems); const menuItems = this.openmct.menus.actionsToMenuItems(sortedActions, this.actionCollection.objectPath, this.actionCollection.view);
this.openmct.menus.showMenu(event.x, event.y, menuItems);
}
}, },
setStatus(status) { setStatus(status) {
this.status = status; this.status = status;

View File

@ -49,6 +49,7 @@ describe("the inspector", () => {
beforeEach((done) => { beforeEach((done) => {
openmct = createOpenMct(); openmct = createOpenMct();
spyOn(openmct.objects, 'save').and.returnValue(Promise.resolve(true));
openmct.on('start', done); openmct.on('start', done);
openmct.startHeadless(); openmct.startHeadless();
}); });
@ -77,12 +78,12 @@ describe("the inspector", () => {
expect(savedStylesViewComponent.$children[0].$children.length).toBe(0); expect(savedStylesViewComponent.$children[0].$children.length).toBe(0);
stylesViewComponent.$children[0].saveStyle(mockStyle); stylesViewComponent.$children[0].saveStyle(mockStyle);
stylesViewComponent.$nextTick().then(() => { return stylesViewComponent.$nextTick().then(() => {
expect(savedStylesViewComponent.$children[0].$children.length).toBe(1); expect(savedStylesViewComponent.$children[0].$children.length).toBe(1);
}); });
}); });
it("should allow a saved style to be applied", () => { xit("should allow a saved style to be applied", () => {
spyOn(openmct.editor, 'isEditing').and.returnValue(true); spyOn(openmct.editor, 'isEditing').and.returnValue(true);
selection = mockTelemetryTableSelection; selection = mockTelemetryTableSelection;
@ -91,12 +92,12 @@ describe("the inspector", () => {
stylesViewComponent.$children[0].saveStyle(mockStyle); stylesViewComponent.$children[0].saveStyle(mockStyle);
stylesViewComponent.$nextTick().then(() => { return stylesViewComponent.$nextTick().then(() => {
const styleSelectorComponent = savedStylesViewComponent.$children[0].$children[0]; const styleSelectorComponent = savedStylesViewComponent.$children[0].$children[0];
styleSelectorComponent.selectStyle(); styleSelectorComponent.selectStyle();
savedStylesViewComponent.$nextTick().then(() => { return savedStylesViewComponent.$nextTick().then(() => {
const styleEditorComponentIndex = stylesViewComponent.$children[0].$children.length - 1; const styleEditorComponentIndex = stylesViewComponent.$children[0].$children.length - 1;
const styleEditorComponent = stylesViewComponent.$children[0].$children[styleEditorComponentIndex]; const styleEditorComponent = stylesViewComponent.$children[0].$children[styleEditorComponentIndex];
const styles = styleEditorComponent.$children.filter(component => component.options.value === mockStyle.color); const styles = styleEditorComponent.$children.filter(component => component.options.value === mockStyle.color);
@ -147,7 +148,7 @@ describe("the inspector", () => {
stylesViewComponent = createViewComponent(StylesView, selection, openmct); stylesViewComponent = createViewComponent(StylesView, selection, openmct);
savedStylesViewComponent = createViewComponent(SavedStylesView, selection, openmct); savedStylesViewComponent = createViewComponent(SavedStylesView, selection, openmct);
stylesViewComponent.$nextTick().then(() => { return stylesViewComponent.$nextTick().then(() => {
const styleEditorComponentIndex = stylesViewComponent.$children[0].$children.length - 1; const styleEditorComponentIndex = stylesViewComponent.$children[0].$children.length - 1;
const styleEditorComponent = stylesViewComponent.$children[0].$children[styleEditorComponentIndex]; const styleEditorComponent = stylesViewComponent.$children[0].$children[styleEditorComponentIndex];
const saveStyleButtonIndex = styleEditorComponent.$children.length - 1; const saveStyleButtonIndex = styleEditorComponent.$children.length - 1;
@ -168,7 +169,7 @@ describe("the inspector", () => {
stylesViewComponent = createViewComponent(StylesView, selection, openmct); stylesViewComponent = createViewComponent(StylesView, selection, openmct);
savedStylesViewComponent = createViewComponent(SavedStylesView, selection, openmct); savedStylesViewComponent = createViewComponent(SavedStylesView, selection, openmct);
stylesViewComponent.$nextTick().then(() => { return stylesViewComponent.$nextTick().then(() => {
const styleEditorComponentIndex = stylesViewComponent.$children[0].$children.length - 1; const styleEditorComponentIndex = stylesViewComponent.$children[0].$children.length - 1;
const styleEditorComponent = stylesViewComponent.$children[0].$children[styleEditorComponentIndex]; const styleEditorComponent = stylesViewComponent.$children[0].$children[styleEditorComponentIndex];
const saveStyleButtonIndex = styleEditorComponent.$children.length - 1; const saveStyleButtonIndex = styleEditorComponent.$children.length - 1;
@ -185,7 +186,7 @@ describe("the inspector", () => {
stylesViewComponent = createViewComponent(StylesView, selection, openmct); stylesViewComponent = createViewComponent(StylesView, selection, openmct);
savedStylesViewComponent = createViewComponent(SavedStylesView, selection, openmct); savedStylesViewComponent = createViewComponent(SavedStylesView, selection, openmct);
stylesViewComponent.$nextTick().then(() => { return stylesViewComponent.$nextTick().then(() => {
const styleEditorComponentIndex = stylesViewComponent.$children[0].$children.length - 1; const styleEditorComponentIndex = stylesViewComponent.$children[0].$children.length - 1;
const styleEditorComponent = stylesViewComponent.$children[0].$children[styleEditorComponentIndex]; const styleEditorComponent = stylesViewComponent.$children[0].$children[styleEditorComponentIndex];
const saveStyleButtonIndex = styleEditorComponent.$children.length - 1; const saveStyleButtonIndex = styleEditorComponent.$children.length - 1;

View File

@ -54,15 +54,15 @@ export default {
let viewContainer = document.createElement('div'); let viewContainer = document.createElement('div');
this.$el.append(viewContainer); this.$el.append(viewContainer);
this.component = new Vue({ this.component = new Vue({
el: viewContainer,
components: {
StylesView
},
provide: { provide: {
openmct: this.openmct, openmct: this.openmct,
selection: selection, selection: selection,
stylesManager: this.stylesManager stylesManager: this.stylesManager
}, },
el: viewContainer,
components: {
StylesView
},
template: '<styles-view/>' template: '<styles-view/>'
}); });
} }

View File

@ -42,10 +42,10 @@ export default {
methods: { methods: {
launchAbout() { launchAbout() {
let vm = new Vue({ let vm = new Vue({
components: {AboutDialog},
provide: { provide: {
openmct: this.openmct openmct: this.openmct
}, },
components: {AboutDialog},
template: '<about-dialog></about-dialog>' template: '<about-dialog></about-dialog>'
}).$mount(); }).$mount();

View File

@ -6,74 +6,133 @@ let child;
let appHolder; let appHolder;
let resolveFunction; let resolveFunction;
describe('Application router utility functions', () => { let initialHash = '';
beforeEach(done => {
xdescribe('Application router utility functions', () => {
beforeAll(done => {
appHolder = document.createElement('div'); appHolder = document.createElement('div');
appHolder.style.width = '640px'; appHolder.style.width = '640px';
appHolder.style.height = '480px'; appHolder.style.height = '480px';
openmct = createOpenMct(); openmct = createOpenMct();
openmct.install(openmct.plugins.MyItems()); openmct.install(openmct.plugins.MyItems());
openmct.install(openmct.plugins.LocalTimeSystem());
openmct.install(openmct.plugins.UTCTimeSystem());
element = document.createElement('div'); element = document.createElement('div');
child = document.createElement('div'); child = document.createElement('div');
element.appendChild(child); element.appendChild(child);
openmct.on('start', () => { openmct.on('start', done);
resolveFunction = () => {
const success = window.location.hash !== null && window.location.hash !== '';
if (success) {
done();
}
};
openmct.router.on('change:hash', resolveFunction);
openmct.router.setLocationFromUrl();
});
openmct.start(appHolder); openmct.start(appHolder);
document.body.append(appHolder); document.body.append(appHolder);
}); });
afterEach(() => { afterAll(() => {
openmct.router.removeListener('change:hash', resolveFunction); openmct.router.setHash(initialHash);
appHolder.remove(); appHolder.remove();
return resetApplicationState(openmct); return resetApplicationState(openmct);
}); });
it('has initial hash when loaded', () => { it('has initial hash when loaded', (done) => {
const success = window.location.hash !== null; let success;
expect(success).toBe(true); resolveFunction = () => {
openmct.router.setLocationFromUrl();
success = window.location.hash !== null;
if (success) {
initialHash = window.location.hash;
expect(success).toBe(true);
openmct.router.removeListener('change:hash', resolveFunction);
done();
}
};
openmct.router.on('change:hash', resolveFunction);
}); });
it('The setSearchParam function sets an individual search parameter in the window location hash', () => { it('The setSearchParam function sets an individual search parameter in the window location hash', (done) => {
let success;
openmct.router.setSearchParam('testParam', 'testValue'); openmct.router.setSearchParam('testParam', 'testValue');
const searchParams = openmct.router.getAllSearchParams(); resolveFunction = () => {
expect(searchParams.get('testParam')).toBe('testValue'); success = window.location.hash.includes('testParam=testValue');
if (success) {
expect(success).toBe(true);
openmct.router.removeListener('change:hash', resolveFunction);
done();
}
};
openmct.router.on('change:hash', resolveFunction);
}); });
it('The deleteSearchParam function deletes an individual search paramater in the window location hash', () => { it('The getSearchParam function returns the value of an individual search parameter in the window location hash', () => {
expect(openmct.router.getSearchParam('testParam')).toBe('testValue');
});
it('The deleteSearchParam function deletes an individual search parameter in the window location hash', (done) => {
let success;
openmct.router.deleteSearchParam('testParam'); openmct.router.deleteSearchParam('testParam');
const searchParams = openmct.router.getAllSearchParams(); resolveFunction = () => {
expect(searchParams.get('testParam')).toBe(null); success = window.location.hash.includes('testParam=testValue') === false;
if (success) {
expect(success).toBe(true);
openmct.router.removeListener('change:hash', resolveFunction);
done();
}
};
openmct.router.on('change:hash', resolveFunction);
}); });
it('The setSearchParam function sets a multiple individual search parameters in the window location hash', () => { it('The setSearchParam function sets an individual search parameters in the window location hash', (done) => {
let success;
openmct.router.setSearchParam('testParam1', 'testValue1'); openmct.router.setSearchParam('testParam1', 'testValue1');
openmct.router.setSearchParam('testParam2', 'testValue2'); openmct.router.setSearchParam('testParam2', 'testValue2');
const searchParams = openmct.router.getAllSearchParams(); resolveFunction = () => {
expect(searchParams.get('testParam1')).toBe('testValue1'); const hasTestParam1 = window.location.hash.includes('testParam1=testValue1');
expect(searchParams.get('testParam2')).toBe('testValue2'); const hasTestParam2 = window.location.hash.includes('testParam2=testValue2');
success = hasTestParam1 && hasTestParam2;
if (success) {
expect(success).toBe(true);
openmct.router.removeListener('change:hash', resolveFunction);
done();
}
};
openmct.router.on('change:hash', resolveFunction);
}); });
it('The setAllSearchParams function replaces all search paramaters in the window location hash', () => { it('The setAllSearchParams function replaces all search parameters in the window location hash', (done) => {
let success;
openmct.router.setSearchParam('testParam2', 'updatedtestValue2'); openmct.router.setSearchParam('testParam2', 'updatedtestValue2');
openmct.router.setSearchParam('newTestParam3', 'newTestValue3'); openmct.router.setSearchParam('newTestParam3', 'newTestValue3');
const searchParams = openmct.router.getAllSearchParams(); resolveFunction = () => {
const hasupdatedValueForTestParam2 = window.location.hash.includes('testParam2=updatedtestValue2');
const hasNewTestParam3 = window.location.hash.includes('newTestParam3=newTestValue3');
success = hasupdatedValueForTestParam2 && hasNewTestParam3;
if (success) {
expect(success).toBe(true);
openmct.router.removeListener('change:hash', resolveFunction);
done();
}
};
openmct.router.on('change:hash', resolveFunction);
});
it('The getAllSearchParams function returns the values of all search parameters in the window location hash', () => {
let searchParams = openmct.router.getAllSearchParams();
expect(searchParams.get('testParam1')).toBe('testValue1');
expect(searchParams.get('testParam2')).toBe('updatedtestValue2'); expect(searchParams.get('testParam2')).toBe('updatedtestValue2');
expect(searchParams.get('newTestParam3')).toBe('newTestValue3'); expect(searchParams.get('newTestParam3')).toBe('newTestValue3');
}); });

View File

@ -119,16 +119,11 @@ define([
function navigateToFirstChildOfRoot() { function navigateToFirstChildOfRoot() {
openmct.objects.get('ROOT') openmct.objects.get('ROOT')
.then(rootObject => { .then(rootObject => {
const composition = openmct.composition.get(rootObject); openmct.composition.get(rootObject).load()
if (!composition) {
return;
}
composition.load()
.then(children => { .then(children => {
let lastChild = children[children.length - 1]; let lastChild = children[children.length - 1];
if (!lastChild) { if (!lastChild) {
console.debug('Unable to navigate to anything. No root objects found.'); console.error('Unable to navigate to anything. No root objects found.');
} else { } else {
let lastChildId = openmct.objects.makeKeyString(lastChild.identifier); let lastChildId = openmct.objects.makeKeyString(lastChild.identifier);
openmct.router.setPath(`#/browse/${lastChildId}`); openmct.router.setPath(`#/browse/${lastChildId}`);

View File

@ -20,8 +20,7 @@
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
const ONE_SECOND = 1000; const ONE_MINUTE = 60 * 1000;
const ONE_MINUTE = 60 * ONE_SECOND;
const ONE_HOUR = ONE_MINUTE * 60; const ONE_HOUR = ONE_MINUTE * 60;
const ONE_DAY = ONE_HOUR * 24; const ONE_DAY = ONE_HOUR * 24;
@ -40,20 +39,34 @@ function toDoubleDigits(num) {
} }
} }
function addTimeSuffix(value, suffix) { export function getDuration(numericDuration) {
return typeof value === 'number' && value > 0 ? `${value + suffix}` : ''; let result;
} let age;
export function millisecondsToDHMS(numericDuration) { if (numericDuration > ONE_DAY - 1) {
const ms = numericDuration || 0; age = normalizeAge((numericDuration / ONE_DAY)).toFixed(2);
const dhms = [ result = `+ ${age} day`;
addTimeSuffix(Math.floor(normalizeAge(ms / ONE_DAY)), 'd'),
addTimeSuffix(Math.floor(normalizeAge((ms % ONE_DAY) / ONE_HOUR)), 'h'),
addTimeSuffix(Math.floor(normalizeAge((ms % ONE_HOUR) / ONE_MINUTE)), 'm'),
addTimeSuffix(Math.floor(normalizeAge((ms % ONE_MINUTE) / ONE_SECOND)), 's')
].filter(Boolean).join(' ');
return `${ dhms ? '+' : ''} ${dhms}`; if (age !== 1) {
result += 's';
}
} else if (numericDuration > ONE_HOUR - 1) {
age = normalizeAge((numericDuration / ONE_HOUR).toFixed(2));
result = `+ ${age} hour`;
if (age !== 1) {
result += 's';
}
} else {
age = normalizeAge((numericDuration / ONE_MINUTE).toFixed(2));
result = `+ ${age} min`;
if (age !== 1) {
result += 's';
}
}
return result;
} }
export function getPreciseDuration(numericDuration) { export function getPreciseDuration(numericDuration) {

View File

@ -25,26 +25,13 @@ import MCT from 'MCT';
let nativeFunctions = []; let nativeFunctions = [];
let mockObjects = setMockObjects(); let mockObjects = setMockObjects();
const DEFAULT_TIME_OPTIONS = { export function createOpenMct() {
timeSystemKey: 'utc',
bounds: {
start: 0,
end: 1
}
};
export function createOpenMct(timeSystemOptions = DEFAULT_TIME_OPTIONS) {
const openmct = new MCT(); const openmct = new MCT();
openmct.install(openmct.plugins.LocalStorage()); openmct.install(openmct.plugins.LocalStorage());
openmct.install(openmct.plugins.UTCTimeSystem()); openmct.install(openmct.plugins.UTCTimeSystem());
openmct.time.timeSystem('utc', {
const timeSystemKey = timeSystemOptions.timeSystemKey; start: 0,
const start = timeSystemOptions.bounds.start; end: 1
const end = timeSystemOptions.bounds.end;
openmct.time.timeSystem(timeSystemKey, {
start,
end
}); });
return openmct; return openmct;