diff --git a/example/generator/GeneratorMetadataProvider.js b/example/generator/GeneratorMetadataProvider.js index 112166117b..7a8cd9832a 100644 --- a/example/generator/GeneratorMetadataProvider.js +++ b/example/generator/GeneratorMetadataProvider.js @@ -118,100 +118,6 @@ define([ } } ] - }, - 'example.spectral-generator': { - values: [ - { - key: "name", - name: "Name", - format: "string" - }, - { - key: "utc", - name: "Time", - format: "utc", - hints: { - domain: 1 - } - }, - { - key: "wavelength", - name: "Wavelength", - unit: "Hz", - formatString: '%0.2f', - hints: { - domain: 2, - spectralAttribute: true - } - }, - { - key: "cos", - name: "Cosine", - unit: "deg", - formatString: '%0.2f', - hints: { - range: 2, - spectralAttribute: true - } - } - ] - }, - 'example.spectral-aggregate-generator': { - values: [ - { - key: "name", - name: "Name", - format: "string" - }, - { - key: "utc", - name: "Time", - format: "utc", - hints: { - domain: 1 - } - }, - { - key: "ch1", - name: "Channel 1", - format: "string", - hints: { - range: 1 - } - }, - { - key: "ch2", - name: "Channel 2", - format: "string", - hints: { - range: 2 - } - }, - { - key: "ch3", - name: "Channel 3", - format: "string", - hints: { - range: 3 - } - }, - { - key: "ch4", - name: "Channel 4", - format: "string", - hints: { - range: 4 - } - }, - { - key: "ch5", - name: "Channel 5", - format: "string", - hints: { - range: 5 - } - } - ] } }; diff --git a/example/generator/SpectralAggregateGeneratorProvider.js b/example/generator/SpectralAggregateGeneratorProvider.js deleted file mode 100644 index dcd75c89e0..0000000000 --- a/example/generator/SpectralAggregateGeneratorProvider.js +++ /dev/null @@ -1,86 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - -], function ( - -) { - - function SpectralAggregateGeneratorProvider() { - - } - - function pointForTimestamp(timestamp, count, name) { - return { - name: name, - utc: String(Math.floor(timestamp / count) * count), - ch1: String(Math.floor(timestamp / count) % 1), - ch2: String(Math.floor(timestamp / count) % 2), - ch3: String(Math.floor(timestamp / count) % 3), - ch4: String(Math.floor(timestamp / count) % 4), - ch5: String(Math.floor(timestamp / count) % 5) - }; - } - - SpectralAggregateGeneratorProvider.prototype.supportsSubscribe = function (domainObject) { - return domainObject.type === 'example.spectral-aggregate-generator'; - }; - - SpectralAggregateGeneratorProvider.prototype.subscribe = function (domainObject, callback) { - var count = 5000; - - var interval = setInterval(function () { - var now = Date.now(); - var datum = pointForTimestamp(now, count, domainObject.name); - callback(datum); - }, count); - - return function () { - clearInterval(interval); - }; - }; - - SpectralAggregateGeneratorProvider.prototype.supportsRequest = function (domainObject, options) { - return domainObject.type === 'example.spectral-aggregate-generator'; - }; - - SpectralAggregateGeneratorProvider.prototype.request = function (domainObject, options) { - var start = options.start; - var end = Math.min(Date.now(), options.end); // no future values - var count = 5000; - if (options.strategy === 'latest' || options.size === 1) { - start = end; - } - - var data = []; - while (start <= end && data.length < 5000) { - data.push(pointForTimestamp(start, count, domainObject.name)); - start += count; - } - - return Promise.resolve(data); - }; - - return SpectralAggregateGeneratorProvider; - -}); diff --git a/example/generator/SpectralGeneratorProvider.js b/example/generator/SpectralGeneratorProvider.js deleted file mode 100644 index 4011bf90d8..0000000000 --- a/example/generator/SpectralGeneratorProvider.js +++ /dev/null @@ -1,102 +0,0 @@ -/***************************************************************************** - * Open MCT, Copyright (c) 2014-2021, United States Government - * as represented by the Administrator of the National Aeronautics and Space - * Administration. All rights reserved. - * - * Open MCT is licensed under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Open MCT includes source code licensed under additional open source - * licenses. See the Open Source Licenses file (LICENSES.md) included with - * this source code distribution or the Licensing information page available - * at runtime from the About dialog for additional information. - *****************************************************************************/ - -define([ - './WorkerInterface' -], function ( - WorkerInterface -) { - - var REQUEST_DEFAULTS = { - amplitude: 1, - wavelength: 1, - period: 10, - offset: 0, - dataRateInHz: 1, - randomness: 0, - phase: 0 - }; - - function SpectralGeneratorProvider() { - this.workerInterface = new WorkerInterface(); - } - - SpectralGeneratorProvider.prototype.canProvideTelemetry = function (domainObject) { - return domainObject.type === 'example.spectral-generator'; - }; - - SpectralGeneratorProvider.prototype.supportsRequest = - SpectralGeneratorProvider.prototype.supportsSubscribe = - SpectralGeneratorProvider.prototype.canProvideTelemetry; - - SpectralGeneratorProvider.prototype.makeWorkerRequest = function (domainObject, request = {}) { - var props = [ - 'amplitude', - 'wavelength', - 'period', - 'offset', - 'dataRateInHz', - 'phase', - 'randomness' - ]; - - var workerRequest = {}; - - props.forEach(function (prop) { - if (domainObject.telemetry && Object.prototype.hasOwnProperty.call(domainObject.telemetry, prop)) { - workerRequest[prop] = domainObject.telemetry[prop]; - } - - if (request && Object.prototype.hasOwnProperty.call(request, prop)) { - workerRequest[prop] = request[prop]; - } - - if (!Object.prototype.hasOwnProperty.call(workerRequest, prop)) { - workerRequest[prop] = REQUEST_DEFAULTS[prop]; - } - - workerRequest[prop] = Number(workerRequest[prop]); - }); - - workerRequest.name = domainObject.name; - - return workerRequest; - }; - - SpectralGeneratorProvider.prototype.request = function (domainObject, request) { - var workerRequest = this.makeWorkerRequest(domainObject, request); - workerRequest.start = request.start; - workerRequest.end = request.end; - workerRequest.spectra = true; - - return this.workerInterface.request(workerRequest); - }; - - SpectralGeneratorProvider.prototype.subscribe = function (domainObject, callback) { - var workerRequest = this.makeWorkerRequest(domainObject, {}); - workerRequest.spectra = true; - - return this.workerInterface.subscribe(workerRequest, callback); - }; - - return SpectralGeneratorProvider; -}); diff --git a/example/generator/plugin.js b/example/generator/plugin.js index e985722006..a7a027e32e 100644 --- a/example/generator/plugin.js +++ b/example/generator/plugin.js @@ -24,15 +24,11 @@ define([ "./GeneratorProvider", "./SinewaveLimitProvider", "./StateGeneratorProvider", - "./SpectralGeneratorProvider", - "./SpectralAggregateGeneratorProvider", "./GeneratorMetadataProvider" ], function ( GeneratorProvider, SinewaveLimitProvider, StateGeneratorProvider, - SpectralGeneratorProvider, - SpectralAggregateGeneratorProvider, GeneratorMetadataProvider ) { @@ -65,37 +61,6 @@ define([ openmct.telemetry.addProvider(new StateGeneratorProvider()); - openmct.types.addType("example.spectral-generator", { - name: "Spectral Generator", - description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.", - cssClass: "icon-generator-telemetry", - creatable: true, - initialize: function (object) { - object.telemetry = { - period: 10, - amplitude: 1, - wavelength: 1, - frequency: 1, - offset: 0, - dataRateInHz: 1, - phase: 0, - randomness: 0 - }; - } - }); - openmct.telemetry.addProvider(new SpectralGeneratorProvider()); - - openmct.types.addType("example.spectral-aggregate-generator", { - name: "Spectral Aggregate Generator", - description: "For development use. Generates example streaming telemetry data using a simple state algorithm.", - cssClass: "icon-generator-telemetry", - creatable: true, - initialize: function (object) { - object.telemetry = {}; - } - }); - openmct.telemetry.addProvider(new SpectralAggregateGeneratorProvider()); - openmct.types.addType("generator", { name: "Sine Wave Generator", description: "For development use. Generates example streaming telemetry data using a simple sine wave algorithm.", diff --git a/package.json b/package.json index 3c0d3d8530..f889f007ef 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,9 @@ { "name": "openmct", - "version": "1.7.8-SNAPSHOT", + "version": "1.7.8", "description": "The Open MCT core platform", "devDependencies": { + "@braintree/sanitize-url": "^5.0.2", "angular": ">=1.8.0", "angular-route": "1.4.14", "babel-eslint": "10.0.3", diff --git a/platform/forms/src/MCTFileInput.js b/platform/forms/src/MCTFileInput.js index 074e279f00..935557f7f3 100644 --- a/platform/forms/src/MCTFileInput.js +++ b/platform/forms/src/MCTFileInput.js @@ -44,9 +44,11 @@ define( setText(result.name); scope.ngModel[scope.field] = result; control.$setValidity("file-input", true); + scope.$digest(); }, function () { setText('Select File'); control.$setValidity("file-input", false); + scope.$digest(); }); } diff --git a/src/MCT.js b/src/MCT.js index 6d341ada6f..1f100ce550 100644 --- a/src/MCT.js +++ b/src/MCT.js @@ -265,6 +265,7 @@ define([ // Plugins that are installed by default this.install(this.plugins.Plot()); + this.install(this.plugins.Chart()); this.install(this.plugins.TelemetryTable.default()); this.install(PreviewPlugin.default()); this.install(LegacyIndicatorsPlugin()); diff --git a/src/adapter/services/MissingModelCompatibilityDecorator.js b/src/adapter/services/MissingModelCompatibilityDecorator.js index 57568cfa13..76cc300a87 100644 --- a/src/adapter/services/MissingModelCompatibilityDecorator.js +++ b/src/adapter/services/MissingModelCompatibilityDecorator.js @@ -81,14 +81,8 @@ define([ return models; } - return this.apiFetch(missingIds) - .then(function (apiResults) { - Object.keys(apiResults).forEach(function (k) { - models[k] = apiResults[k]; - }); - - return models; - }); + //Temporary fix for missing models - don't retry using this.apiFetch + return models; }.bind(this)); }; diff --git a/src/api/actions/ActionsAPI.js b/src/api/actions/ActionsAPI.js index a2be065dce..8af7b3cbb1 100644 --- a/src/api/actions/ActionsAPI.js +++ b/src/api/actions/ActionsAPI.js @@ -110,7 +110,7 @@ class ActionsAPI extends EventEmitter { return actionsObject; } - _groupAndSortActions(actionsArray) { + _groupAndSortActions(actionsArray = []) { if (!Array.isArray(actionsArray) && typeof actionsArray === 'object') { actionsArray = Object.keys(actionsArray).map(key => actionsArray[key]); } diff --git a/src/api/objects/ConflictError.js b/src/api/objects/ConflictError.js new file mode 100644 index 0000000000..a75f0e169b --- /dev/null +++ b/src/api/objects/ConflictError.js @@ -0,0 +1,2 @@ +export default class ConflictError extends Error { +} diff --git a/src/api/objects/ObjectAPI.js b/src/api/objects/ObjectAPI.js index 4dbe2da7e1..6aad11760f 100644 --- a/src/api/objects/ObjectAPI.js +++ b/src/api/objects/ObjectAPI.js @@ -27,6 +27,7 @@ import RootObjectProvider from './RootObjectProvider'; import EventEmitter from 'EventEmitter'; import InterceptorRegistry from './InterceptorRegistry'; import Transaction from './Transaction'; +import ConflictError from './ConflictError'; /** * Utilities for loading, saving, and manipulating domain objects. @@ -36,7 +37,6 @@ import Transaction from './Transaction'; function ObjectAPI(typeRegistry, openmct) { this.openmct = openmct; - this.typeRegistry = typeRegistry; this.eventEmitter = new EventEmitter(); this.providers = {}; @@ -50,6 +50,10 @@ function ObjectAPI(typeRegistry, openmct) { this.interceptorRegistry = new InterceptorRegistry(); this.SYNCHRONIZED_OBJECT_TYPES = ['notebook', 'plan']; + + this.errors = { + Conflict: ConflictError + }; } /** @@ -192,6 +196,7 @@ ObjectAPI.prototype.get = function (identifier, abortSignal) { let objectPromise = provider.get(identifier, abortSignal).then(result => { delete this.cache[keystring]; + result = this.applyGetInterceptors(identifier, result); if (result.isMutable) { result.$refresh(result); @@ -200,6 +205,14 @@ ObjectAPI.prototype.get = function (identifier, abortSignal) { mutableDomainObject.$refresh(result); } + return result; + }).catch((result) => { + console.warn(`Failed to retrieve ${keystring}:`, result); + + delete this.cache[keystring]; + + result = this.applyGetInterceptors(identifier); + return result; }); @@ -302,6 +315,7 @@ ObjectAPI.prototype.isPersistable = function (idOrKeyString) { ObjectAPI.prototype.save = function (domainObject) { let provider = this.getProvider(domainObject.identifier); let savedResolve; + let savedReject; let result; if (!this.isPersistable(domainObject.identifier)) { @@ -311,8 +325,9 @@ ObjectAPI.prototype.save = function (domainObject) { } else { const persistedTime = Date.now(); if (domainObject.persisted === undefined) { - result = new Promise((resolve) => { + result = new Promise((resolve, reject) => { savedResolve = resolve; + savedReject = reject; }); domainObject.persisted = persistedTime; const newObjectPromise = provider.create(domainObject); @@ -320,6 +335,8 @@ ObjectAPI.prototype.save = function (domainObject) { newObjectPromise.then(response => { this.mutate(domainObject, 'persisted', persistedTime); savedResolve(response); + }).catch((error) => { + savedReject(error); }); } else { result = Promise.reject(`[ObjectAPI][save] Object provider returned ${newObjectPromise} when creating new object.`); diff --git a/src/api/telemetry/TelemetryAPI.js b/src/api/telemetry/TelemetryAPI.js index 6b4150958b..b65a80b96d 100644 --- a/src/api/telemetry/TelemetryAPI.js +++ b/src/api/telemetry/TelemetryAPI.js @@ -477,6 +477,10 @@ define([ * @returns {Object} */ TelemetryAPI.prototype.getFormatMap = function (metadata) { + if (!metadata) { + return {}; + } + if (!this.formatMapCache.has(metadata)) { const formatMap = metadata.values().reduce(function (map, valueMetadata) { map[valueMetadata.key] = this.getValueFormatter(valueMetadata); diff --git a/src/api/telemetry/TelemetryCollection.js b/src/api/telemetry/TelemetryCollection.js index 66c5959632..0eae37a549 100644 --- a/src/api/telemetry/TelemetryCollection.js +++ b/src/api/telemetry/TelemetryCollection.js @@ -49,7 +49,6 @@ export class TelemetryCollection extends EventEmitter { this.parseTime = undefined; this.metadata = this.openmct.telemetry.getMetadata(domainObject); this.unsubscribe = undefined; - this.historicalProvider = undefined; this.options = options; this.pageState = undefined; this.lastBounds = undefined; @@ -65,13 +64,13 @@ export class TelemetryCollection extends EventEmitter { this._error(ERRORS.LOADED); } - this._timeSystem(this.openmct.time.timeSystem()); + this._setTimeSystem(this.openmct.time.timeSystem()); this.lastBounds = this.openmct.time.bounds(); this._watchBounds(); this._watchTimeSystem(); - this._initiateHistoricalRequests(); + this._requestHistoricalTelemetry(); this._initiateSubscriptionTelemetry(); this.loaded = true; @@ -103,36 +102,35 @@ export class TelemetryCollection extends EventEmitter { return this.boundedTelemetry; } - /** - * Sets up the telemetry collection for historical requests, - * this uses the "standardizeRequestOptions" from Telemetry API - * @private - */ - _initiateHistoricalRequests() { - this.openmct.telemetry.standardizeRequestOptions(this.options); - this.historicalProvider = this.openmct.telemetry. - findRequestProvider(this.domainObject, this.options); - - this._requestHistoricalTelemetry(); - } - /** * If a historical provider exists, then historical requests will be made * @private */ async _requestHistoricalTelemetry() { - if (!this.historicalProvider) { + let options = { ...this.options }; + let historicalProvider; + + this.openmct.telemetry.standardizeRequestOptions(options); + historicalProvider = this.openmct.telemetry. + findRequestProvider(this.domainObject, options); + + if (!historicalProvider) { return; } let historicalData; - this.options.onPartialResponse = this._processNewTelemetry.bind(this); + options.onPartialResponse = this._processNewTelemetry.bind(this); try { + if (this.requestAbort) { + this.requestAbort.abort(); + } + this.requestAbort = new AbortController(); - this.options.signal = this.requestAbort.signal; - historicalData = await this.historicalProvider.request(this.domainObject, this.options); + options.signal = this.requestAbort.signal; + this.emit('requestStarted'); + historicalData = await historicalProvider.request(this.domainObject, options); } catch (error) { if (error.name !== 'AbortError') { console.error('Error requesting telemetry data...'); @@ -140,6 +138,7 @@ export class TelemetryCollection extends EventEmitter { } } + this.emit('requestEnded'); this.requestAbort = undefined; this._processNewTelemetry(historicalData); @@ -173,6 +172,10 @@ export class TelemetryCollection extends EventEmitter { * @private */ _processNewTelemetry(telemetryData) { + if (telemetryData === undefined) { + return; + } + let data = Array.isArray(telemetryData) ? telemetryData : [telemetryData]; let parsedValue; let beforeStartOfBounds; @@ -199,9 +202,10 @@ export class TelemetryCollection extends EventEmitter { if (endIndex > startIndex) { let potentialDupes = this.boundedTelemetry.slice(startIndex, endIndex); - isDuplicate = potentialDupes.some(_.isEqual.bind(undefined, datum)); } + } else if (startIndex === this.boundedTelemetry.length) { + isDuplicate = _.isEqual(datum, this.boundedTelemetry[this.boundedTelemetry.length - 1]); } if (!isDuplicate) { @@ -317,7 +321,7 @@ export class TelemetryCollection extends EventEmitter { * Time System * @private */ - _timeSystem(timeSystem) { + _setTimeSystem(timeSystem) { let domains = this.metadata.valuesForHints(['domain']); let domain = domains.find((d) => d.key === timeSystem.key); @@ -333,7 +337,10 @@ export class TelemetryCollection extends EventEmitter { this.parseTime = (datum) => { return valueFormatter.parse(datum); }; + } + _setTimeSystemAndFetchData(timeSystem) { + this._setTimeSystem(timeSystem); this._reset(); } @@ -370,19 +377,19 @@ export class TelemetryCollection extends EventEmitter { } /** - * adds the _timeSystem callback to the 'timeSystem' timeAPI listener + * adds the _setTimeSystemAndFetchData callback to the 'timeSystem' timeAPI listener * @private */ _watchTimeSystem() { - this.openmct.time.on('timeSystem', this._timeSystem, this); + this.openmct.time.on('timeSystem', this._setTimeSystemAndFetchData, this); } /** - * removes the _timeSystem callback from the 'timeSystem' timeAPI listener + * removes the _setTimeSystemAndFetchData callback from the 'timeSystem' timeAPI listener * @private */ _unwatchTimeSystem() { - this.openmct.time.off('timeSystem', this._timeSystem, this); + this.openmct.time.off('timeSystem', this._setTimeSystemAndFetchData, this); } /** diff --git a/src/api/time/TimeAPI.js b/src/api/time/TimeAPI.js index 34cf6483e0..348046de52 100644 --- a/src/api/time/TimeAPI.js +++ b/src/api/time/TimeAPI.js @@ -172,7 +172,7 @@ class TimeAPI extends GlobalTimeContext { * @memberof module:openmct.TimeAPI# * @method getContextForView */ - getContextForView(objectPath) { + getContextForView(objectPath = []) { let timeContext = this; objectPath.forEach(item => { diff --git a/src/installDefaultBundles.js b/src/installDefaultBundles.js index 7803f7fa70..4236cc47ee 100644 --- a/src/installDefaultBundles.js +++ b/src/installDefaultBundles.js @@ -41,7 +41,6 @@ const DEFAULTS = [ 'platform/forms', 'platform/identity', 'platform/persistence/aggregator', - 'platform/persistence/queue', 'platform/policy', 'platform/entanglement', 'platform/search', diff --git a/src/plugins/CouchDBSearchFolder/pluginSpec.js b/src/plugins/CouchDBSearchFolder/pluginSpec.js index 15eff8801f..34ca265879 100644 --- a/src/plugins/CouchDBSearchFolder/pluginSpec.js +++ b/src/plugins/CouchDBSearchFolder/pluginSpec.js @@ -32,7 +32,7 @@ describe('the plugin', function () { let openmct; let composition; - beforeEach((done) => { + beforeEach(() => { openmct = createOpenMct(); @@ -47,11 +47,6 @@ describe('the plugin', function () { } })); - openmct.on('start', done); - openmct.startHeadless(); - - composition = openmct.composition.get({identifier}); - spyOn(couchPlugin.couchProvider, 'getObjectsByFilter').and.returnValue(Promise.resolve([ { identifier: { @@ -66,6 +61,19 @@ describe('the plugin', function () { } } ])); + + spyOn(couchPlugin.couchProvider, "get").and.callFake((id) => { + return Promise.resolve({ + identifier: id + }); + }); + + return new Promise((resolve) => { + openmct.once('start', resolve); + openmct.startHeadless(); + }).then(() => { + composition = openmct.composition.get({identifier}); + }); }); afterEach(() => { diff --git a/src/plugins/LADTable/components/LADRow.vue b/src/plugins/LADTable/components/LADRow.vue index 6778a3c6da..c26a87e68c 100644 --- a/src/plugins/LADTable/components/LADRow.vue +++ b/src/plugins/LADTable/components/LADRow.vue @@ -96,11 +96,11 @@ export default { this.timestampKey = this.openmct.time.timeSystem().key; - this.valueMetadata = this + this.valueMetadata = this.metadata ? this .metadata - .valuesForHints(['range'])[0]; + .valuesForHints(['range'])[0] : undefined; - this.valueKey = this.valueMetadata.key; + this.valueKey = this.valueMetadata ? this.valueMetadata.key : undefined; this.unsubscribe = this.openmct .telemetry @@ -151,7 +151,10 @@ export default { size: 1, strategy: 'latest' }) - .then((array) => this.updateValues(array[array.length - 1])); + .then((array) => this.updateValues(array[array.length - 1])) + .catch((error) => { + console.warn('Error fetching data', error); + }); }, updateBounds(bounds, isTick) { this.bounds = bounds; diff --git a/src/plugins/LADTable/components/LADTable.vue b/src/plugins/LADTable/components/LADTable.vue index 764d2b50a2..26d305c307 100644 --- a/src/plugins/LADTable/components/LADTable.vue +++ b/src/plugins/LADTable/components/LADTable.vue @@ -73,8 +73,9 @@ export default { hasUnits() { let itemsWithUnits = this.items.filter((item) => { let metadata = this.openmct.telemetry.getMetadata(item.domainObject); + const valueMetadatas = metadata ? metadata.valueMetadatas : []; - return this.metadataHasUnits(metadata.valueMetadatas); + return this.metadataHasUnits(valueMetadatas); }); diff --git a/src/plugins/plot/barGraph/BarGraphCompositionPolicy.js b/src/plugins/charts/BarGraphCompositionPolicy.js similarity index 84% rename from src/plugins/plot/barGraph/BarGraphCompositionPolicy.js rename to src/plugins/charts/BarGraphCompositionPolicy.js index 3cb6306bf2..9d429c30d9 100644 --- a/src/plugins/plot/barGraph/BarGraphCompositionPolicy.js +++ b/src/plugins/charts/BarGraphCompositionPolicy.js @@ -23,26 +23,26 @@ import { BAR_GRAPH_KEY } from './BarGraphConstants'; export default function BarGraphCompositionPolicy(openmct) { - function hasAggregateDomainAndRange(metadata) { + function hasRange(metadata) { const rangeValues = metadata.valuesForHints(['range']); return rangeValues.length > 0; } function hasBarGraphTelemetry(domainObject) { - if (!Object.prototype.hasOwnProperty.call(domainObject, 'telemetry')) { + if (!openmct.telemetry.isTelemetryObject(domainObject)) { return false; } let metadata = openmct.telemetry.getMetadata(domainObject); - return metadata.values().length > 0 && hasAggregateDomainAndRange(metadata); + return metadata.values().length > 0 && hasRange(metadata); } return { allow: function (parent, child) { if ((parent.type === BAR_GRAPH_KEY) - && ((child.type !== 'telemetry.plot.overlay') && (hasBarGraphTelemetry(child) === false)) + && (hasBarGraphTelemetry(child) === false) ) { return false; } diff --git a/src/plugins/plot/barGraph/BarGraphConstants.js b/src/plugins/charts/BarGraphConstants.js similarity index 69% rename from src/plugins/plot/barGraph/BarGraphConstants.js rename to src/plugins/charts/BarGraphConstants.js index ba3f78e73f..bdd88a7ce3 100644 --- a/src/plugins/plot/barGraph/BarGraphConstants.js +++ b/src/plugins/charts/BarGraphConstants.js @@ -1,5 +1,3 @@ export const BAR_GRAPH_VIEW = 'bar-graph.view'; export const BAR_GRAPH_KEY = 'telemetry.plot.bar-graph'; export const BAR_GRAPH_INSPECTOR_KEY = 'telemetry.plot.bar-graph.inspector'; -export const SUBSCRIBE = 'subscribe'; -export const UNSUBSCRIBE = 'unsubscribe'; diff --git a/src/plugins/plot/barGraph/BarGraphPlot.vue b/src/plugins/charts/BarGraphPlot.vue similarity index 93% rename from src/plugins/plot/barGraph/BarGraphPlot.vue rename to src/plugins/charts/BarGraphPlot.vue index 97cfdf9ee7..b27dcf1226 100644 --- a/src/plugins/plot/barGraph/BarGraphPlot.vue +++ b/src/plugins/charts/BarGraphPlot.vue @@ -12,6 +12,7 @@
diff --git a/src/plugins/plot/stackedPlot/StackedPlot.vue b/src/plugins/plot/stackedPlot/StackedPlot.vue index cfa262cab1..3ff08582d7 100644 --- a/src/plugins/plot/stackedPlot/StackedPlot.vue +++ b/src/plugins/plot/stackedPlot/StackedPlot.vue @@ -92,7 +92,8 @@ export default { cursorGuide: false, gridLines: true, loading: false, - compositionObjects: [] + compositionObjects: [], + tickWidthMap: {} }; }, computed: { @@ -106,8 +107,6 @@ export default { mounted() { this.imageExporter = new ImageExporter(this.openmct); - this.tickWidthMap = {}; - this.composition.on('add', this.addChild); this.composition.on('remove', this.removeChild); this.composition.on('reorder', this.compositionReorder); @@ -126,13 +125,15 @@ export default { addChild(child) { const id = this.openmct.objects.makeKeyString(child.identifier); - this.tickWidthMap[id] = 0; + this.$set(this.tickWidthMap, id, 0); this.compositionObjects.push(child); }, removeChild(childIdentifier) { const id = this.openmct.objects.makeKeyString(childIdentifier); - delete this.tickWidthMap[id]; + + this.$delete(this.tickWidthMap, id); + const childObj = this.compositionObjects.filter((c) => { const identifier = this.openmct.objects.makeKeyString(c.identifier); @@ -190,14 +191,7 @@ export default { return; } - //update the tickWidth for this plotId, the computed max tick width of the stacked plot will be cascaded down - //TODO: Might need to do this using $set - this.tickWidthMap[plotId] = Math.max(width, this.tickWidthMap[plotId]); - // const newTickWidth = Math.max(...Object.values(this.tickWidthMap)); - // if (newTickWidth !== tickWidth || width !== tickWidth) { - // tickWidth = newTickWidth; - // $scope.$broadcast('plot:tickWidth', tickWidth); - // } + this.$set(this.tickWidthMap, plotId, width); } } }; diff --git a/src/plugins/plot/stackedPlot/StackedPlotViewProvider.js b/src/plugins/plot/stackedPlot/StackedPlotViewProvider.js index c69ec7e684..2ff60d9e6c 100644 --- a/src/plugins/plot/stackedPlot/StackedPlotViewProvider.js +++ b/src/plugins/plot/stackedPlot/StackedPlotViewProvider.js @@ -25,7 +25,9 @@ import Vue from 'vue'; export default function StackedPlotViewProvider(openmct) { function isCompactView(objectPath) { - return objectPath.find(object => object.type === 'time-strip'); + let isChildOfTimeStrip = objectPath.find(object => object.type === 'time-strip'); + + return isChildOfTimeStrip && !openmct.router.isNavigatedObject(objectPath); } return { diff --git a/src/plugins/plugins.js b/src/plugins/plugins.js index dd3f85b3f9..3132d233c9 100644 --- a/src/plugins/plugins.js +++ b/src/plugins/plugins.js @@ -36,6 +36,7 @@ define([ './URLIndicatorPlugin/URLIndicatorPlugin', './telemetryMean/plugin', './plot/plugin', + './charts/plugin', './telemetryTable/plugin', './staticRootPlugin/plugin', './notebook/plugin', @@ -87,6 +88,7 @@ define([ URLIndicatorPlugin, TelemetryMean, PlotPlugin, + ChartPlugin, TelemetryTablePlugin, StaticRootPlugin, Notebook, @@ -189,6 +191,7 @@ define([ plugins.ExampleImagery = ExampleImagery; plugins.ImageryPlugin = ImageryPlugin; plugins.Plot = PlotPlugin.default; + plugins.Chart = ChartPlugin.default; plugins.TelemetryTable = TelemetryTablePlugin; plugins.SummaryWidget = SummaryWidget; diff --git a/src/plugins/summaryWidget/src/SummaryWidget.js b/src/plugins/summaryWidget/src/SummaryWidget.js index e3561d7b9b..2d4d1bea9b 100644 --- a/src/plugins/summaryWidget/src/SummaryWidget.js +++ b/src/plugins/summaryWidget/src/SummaryWidget.js @@ -7,7 +7,8 @@ define([ './eventHelpers', 'objectUtils', 'lodash', - 'zepto' + 'zepto', + '@braintree/sanitize-url' ], function ( widgetTemplate, Rule, @@ -17,7 +18,8 @@ define([ eventHelpers, objectUtils, _, - $ + $, + urlSanitizeLib ) { //default css configuration for new rules @@ -88,7 +90,7 @@ define([ function toggleTestData() { self.outerWrapper.toggleClass('expanded-widget-test-data'); self.toggleTestDataControl.toggleClass('c-disclosure-triangle--expanded'); - } + } this.listenTo(this.toggleTestDataControl, 'click', toggleTestData); @@ -99,7 +101,7 @@ define([ function toggleRules() { self.outerWrapper.toggleClass('expanded-widget-rules'); self.toggleRulesControl.toggleClass('c-disclosure-triangle--expanded'); - } + } this.listenTo(this.toggleRulesControl, 'click', toggleRules); @@ -114,7 +116,7 @@ define([ */ SummaryWidget.prototype.addHyperlink = function (url, openNewTab) { if (url) { - this.widgetButton.attr('href', url); + this.widgetButton.attr('href', urlSanitizeLib.sanitizeUrl(url)); } else { this.widgetButton.removeAttr('href'); } @@ -317,7 +319,7 @@ define([ expanded: 'true' }; - } + } ruleConfig = this.domainObject.configuration.ruleConfigById[ruleId]; this.rulesById[ruleId] = new Rule(ruleConfig, this.domainObject, this.openmct, @@ -345,7 +347,7 @@ define([ ruleOrder.splice(targetIndex + 1, 0, event.draggingId); this.domainObject.configuration.ruleOrder = ruleOrder; this.updateDomainObject(); - } + } this.refreshRules(); }; diff --git a/src/plugins/summaryWidget/src/views/SummaryWidgetView.js b/src/plugins/summaryWidget/src/views/SummaryWidgetView.js index bf01ece913..b59e521fd3 100644 --- a/src/plugins/summaryWidget/src/views/SummaryWidgetView.js +++ b/src/plugins/summaryWidget/src/views/SummaryWidgetView.js @@ -1,7 +1,9 @@ define([ - './summary-widget.html' + './summary-widget.html', + '@braintree/sanitize-url' ], function ( - summaryWidgetTemplate + summaryWidgetTemplate, + urlSanitizeLib ) { const WIDGET_ICON_CLASS = 'c-sw__icon js-sw__icon'; @@ -35,8 +37,9 @@ define([ this.icon = this.container.querySelector('#widgetIcon'); this.label = this.container.querySelector('.js-sw__label'); - if (this.domainObject.url) { - this.widget.setAttribute('href', this.domainObject.url); + let url = this.domainObject.url; + if (url) { + this.widget.setAttribute('href', urlSanitizeLib.sanitizeUrl(url)); } else { this.widget.removeAttribute('href'); } diff --git a/src/plugins/telemetryTable/TelemetryTable.js b/src/plugins/telemetryTable/TelemetryTable.js index e7e8ef5173..99824b4634 100644 --- a/src/plugins/telemetryTable/TelemetryTable.js +++ b/src/plugins/telemetryTable/TelemetryTable.js @@ -60,18 +60,17 @@ define([ this.addTelemetryObject = this.addTelemetryObject.bind(this); this.removeTelemetryObject = this.removeTelemetryObject.bind(this); this.removeTelemetryCollection = this.removeTelemetryCollection.bind(this); + this.incrementOutstandingRequests = this.incrementOutstandingRequests.bind(this); + this.decrementOutstandingRequests = this.decrementOutstandingRequests.bind(this); this.resetRowsFromAllData = this.resetRowsFromAllData.bind(this); this.isTelemetryObject = this.isTelemetryObject.bind(this); - this.refreshData = this.refreshData.bind(this); this.updateFilters = this.updateFilters.bind(this); + this.clearData = this.clearData.bind(this); this.buildOptionsFromConfiguration = this.buildOptionsFromConfiguration.bind(this); this.filterObserver = undefined; 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 limitEvaluator = this.openmct.telemetry.limitEvaluator(telemetryObject); - this.incrementOutstandingRequests(); - const telemetryProcessor = this.getTelemetryProcessor(keyString, columnMap, limitEvaluator); const telemetryRemover = this.getTelemetryRemover(); @@ -151,13 +148,13 @@ define([ this.telemetryCollections[keyString] = this.openmct.telemetry .requestCollection(telemetryObject, requestOptions); + this.telemetryCollections[keyString].on('requestStarted', this.incrementOutstandingRequests); + this.telemetryCollections[keyString].on('requestEnded', this.decrementOutstandingRequests); this.telemetryCollections[keyString].on('remove', telemetryRemover); this.telemetryCollections[keyString].on('add', telemetryProcessor); - this.telemetryCollections[keyString].on('clear', this.tableRows.clear); + this.telemetryCollections[keyString].on('clear', this.clearData); this.telemetryCollections[keyString].load(); - this.decrementOutstandingRequests(); - this.telemetryObjects[keyString] = { telemetryObject, keyString, @@ -268,17 +265,6 @@ define([ 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() { this.tableRows.clear(); this.emit('refresh'); @@ -378,9 +364,6 @@ define([ let keystrings = Object.keys(this.telemetryCollections); keystrings.forEach(this.removeTelemetryCollection); - this.openmct.time.off('bounds', this.refreshData); - this.openmct.time.off('timeSystem', this.refreshData); - if (this.filterObserver) { this.filterObserver(); } diff --git a/src/plugins/telemetryTable/components/table-configuration.vue b/src/plugins/telemetryTable/components/table-configuration.vue index 2d43573969..8d7ed3eca0 100644 --- a/src/plugins/telemetryTable/components/table-configuration.vue +++ b/src/plugins/telemetryTable/components/table-configuration.vue @@ -131,7 +131,8 @@ export default { objects.forEach(object => this.addColumnsForObject(object, false)); }, 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 => { let column = new TelemetryTableColumn(this.openmct, metadatum); this.tableConfiguration.addSingleColumnForObject(telemetryObject, column); diff --git a/src/plugins/telemetryTable/components/table-footer-indicator.vue b/src/plugins/telemetryTable/components/table-footer-indicator.vue index a6bea0d465..4fe43005c2 100644 --- a/src/plugins/telemetryTable/components/table-footer-indicator.vue +++ b/src/plugins/telemetryTable/components/table-footer-indicator.vue @@ -105,7 +105,8 @@ export default { composition.load().then((domainObjects) => { domainObjects.forEach(telemetryObject => { 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]; if (filters !== undefined) { diff --git a/src/plugins/telemetryTable/components/table.vue b/src/plugins/telemetryTable/components/table.vue index b1266b96fe..27893f3adc 100644 --- a/src/plugins/telemetryTable/components/table.vue +++ b/src/plugins/telemetryTable/components/table.vue @@ -125,7 +125,6 @@
@@ -362,7 +361,7 @@ export default { autoScroll: true, sortOptions: {}, filters: {}, - loading: true, + loading: false, scrollable: undefined, tableEl: undefined, headersHolderEl: undefined, @@ -422,6 +421,14 @@ export default { } }, watch: { + loading: { + handler(isLoading) { + if (this.viewActionsCollection) { + let action = isLoading ? 'disable' : 'enable'; + this.viewActionsCollection[action](['export-csv-all']); + } + } + }, markedRows: { handler(newVal, oldVal) { this.$emit('marked-rows-updated', newVal, oldVal); @@ -1019,6 +1026,12 @@ export default { 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) { this.viewActionsCollection.hide(['pause-data']); this.viewActionsCollection.show(['play-data']); diff --git a/src/plugins/telemetryTable/pluginSpec.js b/src/plugins/telemetryTable/pluginSpec.js index b461661101..6eef2d5417 100644 --- a/src/plugins/telemetryTable/pluginSpec.js +++ b/src/plugins/telemetryTable/pluginSpec.js @@ -222,6 +222,24 @@ describe("the plugin", () => { openmct.router.path = originalRouterPath; }); + it("Shows no progress bar initially", () => { + let progressBar = element.querySelector('.c-progress-bar'); + + expect(tableInstance.outstandingRequests).toBe(0); + expect(progressBar).toBeNull(); + }); + + it("Shows a progress bar while making requests", async () => { + tableInstance.incrementOutstandingRequests(); + await Vue.nextTick(); + + let progressBar = element.querySelector('.c-progress-bar'); + + expect(tableInstance.outstandingRequests).toBe(1); + expect(progressBar).not.toBeNull(); + + }); + it("Renders a row for every telemetry datum returned", (done) => { let rows = element.querySelectorAll('table.c-telemetry-table__body tr'); Vue.nextTick(() => { diff --git a/src/plugins/timeConductor/ConductorAxis.vue b/src/plugins/timeConductor/ConductorAxis.vue index 0445302380..5c84020ce8 100644 --- a/src/plugins/timeConductor/ConductorAxis.vue +++ b/src/plugins/timeConductor/ConductorAxis.vue @@ -231,8 +231,8 @@ export default { const panStart = bounds.start - percX * deltaTime; return { - start: panStart, - end: panStart + deltaTime + start: parseInt(panStart, 10), + end: parseInt(panStart + deltaTime, 10) }; }, startZoom() { @@ -296,7 +296,7 @@ export default { const valueDelta = value - this.left; const offset = valueDelta / this.width * timeDelta; - return bounds.start + offset; + return parseInt(bounds.start + offset, 10); }, isChangingViewBounds() { return this.dragStartX && this.dragX && this.dragStartX !== this.dragX; diff --git a/src/plugins/timeConductor/ConductorInputsFixed.vue b/src/plugins/timeConductor/ConductorInputsFixed.vue index 2675fa6af9..d1e3056fc3 100644 --- a/src/plugins/timeConductor/ConductorInputsFixed.vue +++ b/src/plugins/timeConductor/ConductorInputsFixed.vue @@ -1,13 +1,7 @@ @@ -183,10 +173,7 @@ export default { submitForm() { // Allow Vue model to catch up to user input. // Submitting form will cause validation messages to display (but only if triggered by button click) - this.$nextTick(() => this.$refs.submitButton.click()); - }, - updateTimeFromConductor() { - this.setBoundsFromView(); + this.$nextTick(() => this.setBoundsFromView()); }, validateAllBounds(ref) { if (!this.areBoundsFormatsValid()) { diff --git a/src/plugins/timeConductor/independent/IndependentTimeConductor.vue b/src/plugins/timeConductor/independent/IndependentTimeConductor.vue index 1abe6a8c40..66dd657641 100644 --- a/src/plugins/timeConductor/independent/IndependentTimeConductor.vue +++ b/src/plugins/timeConductor/independent/IndependentTimeConductor.vue @@ -151,29 +151,22 @@ export default { this.stopFollowingTimeContext(); this.timeContext = this.openmct.time.getContextForView([this.domainObject]); this.timeContext.on('timeContext', this.setTimeContext); - this.timeContext.on('clock', this.setViewFromClock); + this.timeContext.on('clock', this.setTimeOptions); }, stopFollowingTimeContext() { if (this.timeContext) { this.timeContext.off('timeContext', this.setTimeContext); - this.timeContext.off('clock', this.setViewFromClock); + this.timeContext.off('clock', this.setTimeOptions); } }, - setViewFromClock(clock) { - if (!this.timeOptions.mode) { - this.setTimeOptions(clock); - } - }, - 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() - }; - } + setTimeOptions(clock) { + this.timeOptions.clockOffsets = this.timeOptions.clockOffsets || this.timeContext.clockOffsets(); + this.timeOptions.fixedOffsets = this.timeOptions.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) { const newOptions = Object.assign({}, this.timeOptions, { diff --git a/src/plugins/timeConductor/independent/Mode.vue b/src/plugins/timeConductor/independent/Mode.vue index 565564f337..0607eb2e29 100644 --- a/src/plugins/timeConductor/independent/Mode.vue +++ b/src/plugins/timeConductor/independent/Mode.vue @@ -20,7 +20,8 @@ * at runtime from the About dialog for additional information. *****************************************************************************/