Compare commits

...

31 Commits

Author SHA1 Message Date
a5c61ec1b7 Merge branch 'master' into couchdb-tests 2020-08-10 09:58:09 -07:00
7e7141a5a0 Fixes to enable testing again (#3275)
* Updated library versions
* Use Karma alternative spec loading
* Fixed memory leak in URLTimeSettingsSynchronizer
* Introduce mock DataTransfer object to fix issue with firefox headless
* make resetApplicationState return a promise
* Remove BeforeAll
* Do not throw an error if root returns no children
* Adding missing parameters to ES tests
* Fixed TransactionService bug
* bump test coverage up to 64%
2020-08-10 09:47:04 -07:00
f6ecabb053 Trying chrome 2020-08-07 14:47:22 -07:00
831f207b10 Running tests on circleci 2020-08-07 14:37:52 -07:00
42e474ca60 Merge branch 'couchdb-persistence' of https://github.com/nasa/openmct into couchdb-persistence 2020-08-05 15:21:17 -07:00
d767932be2 Fixes tests 2020-08-05 15:20:50 -07:00
0238a86be5 Merge branch 'master' into couchdb-persistence 2020-08-04 14:44:23 -07:00
7abbd7cb20 Merge branch 'couchdb-persistence' of https://github.com/nasa/openmct into couchdb-persistence 2020-08-04 11:36:20 -07:00
6e2b12b00d Since we're persisting the object to the store before adding it to the parent, there is no need to save it during copyTask as this will trigger a create of the object twice. 2020-08-04 11:35:17 -07:00
e32f465f7a Display layout plugin test coverage to 20% or more (#3158)
* Display layout plugin test coverage to 20% or more
Resolves #3157

* Changes address original issue? Yes
* Unit tests included and/or updated with changes? Yes
* Command line build passes? Yes
* Changes have been smoke-tested? Yes
* Testing instructions included? Yes

* Add disable-dev-shm-usage flag to ChromeHeadless launcher config in karma

* Adding disable dev shm usage flag to chromeheadless launcher and setting log level to debug

* Adding np activity timeout to 60000

* Adding no-sandbox flag for headless chrome

* Run tests without headless chrome to see if that fixes the fonts issue

* Fix typo

* Trying chrome headless with increased memory

* Reset karma.conf back to master

* Trying karma chrome launcher 3.1.0

* Revert to master code for package.json and karma.conf.js

* Trying node 12 browsers

* Revert back to node:13 browsers

* Revert to 10.2.1-browsers circle ci node browsers variant image for docker

* Rebuild node-sass for node 10.x

* Upgrading to 13.14.0 node

* Remove node options

* Don't restore cache before npm install

* Comment out tests with setTimeout

* Trying node 8-browsers

* Try firefox headless

* Firefox version typo

* Revert focused tests

* Exclude setTimeout tests

* Increase browser connectivity timeout

* Trying large timeout with Chromeheadless

* Going back to Firefox and setting browser timeout to 1.5 mins

* Fixes linting issues

* Fix broken tests and add some null checks in the code

* Change double quotes to single quotes
2020-08-03 13:41:57 -07:00
abc458cef4 [Time conductor] Better persistence handling for history (#3246) 2020-08-03 11:56:29 -07:00
6a2520f39b Fixes linting issues 2020-08-03 11:08:28 -07:00
5b7a011069 Merge branch 'master' of https://github.com/nasa/openmct into couchdb-persistence 2020-08-03 11:07:04 -07:00
b76d4b76cb Merge pull request #3228 from nasa/telemetry-table-config-updates
Update TelemetryTable config to allow disable multiselect
2020-07-31 14:41:49 -07:00
5a4cba0226 Merge branch 'master' into telemetry-table-config-updates 2020-07-31 14:23:12 -07:00
f03bfdebb4 [Telemetry Tables][Plots] Display units where applicable (#3198)
* added unit columns in telemetry tables

* added unit column hiding in telemetry tables, added units to lad tables and sets

* added units to plots and plot legends
2020-07-31 13:13:58 -07:00
d216117b30 Merge branch 'master' into couchdb-persistence 2020-07-28 16:43:54 -07:00
5c520bfce9 Removes fdescribe 2020-07-28 16:43:08 -07:00
cba9670823 [WIP] Hacking the GET api to fallback to the DomainObjectProvider to retrieve cached objects when they don't yet exist in CouchDB 2020-07-28 15:33:19 -07:00
27651e9eaa Queue up the promise returned in addition to the model to persist 2020-07-28 15:30:31 -07:00
f096d54dd0 Added queuing of requests 2020-07-28 10:23:27 -07:00
13ff0c368d Merge branch 'master' into telemetry-table-config-updates 2020-07-24 10:46:02 -07:00
8258f21f7b Remove references to legacy couch adapter 2020-07-23 17:47:00 -07:00
44bfcf33ef Removed placeholder provider 2020-07-23 17:45:49 -07:00
669415d362 Removed commented code 2020-07-23 17:45:01 -07:00
8601ec441f Remove comments 2020-07-23 17:44:05 -07:00
9a57a20404 Use new couch provider 2020-07-23 17:43:10 -07:00
1a3bff9813 Fixed some issues in CouchObjectProvider 2020-07-23 17:39:34 -07:00
baa5f21640 Added legacy persistence service adapter 2020-07-23 17:39:02 -07:00
af9dceee3c [WIP] CouchDB object provider 2020-07-23 16:02:10 -07:00
b7d2402434 Update TelemetryTable config to allow disable multiselect. 2020-07-23 14:11:44 -07:00
53 changed files with 1193 additions and 193 deletions

View File

@ -11,12 +11,12 @@ jobs:
name: Update npm name: Update npm
command: 'sudo npm install -g npm@latest' command: 'sudo npm install -g npm@latest'
- restore_cache: - restore_cache:
key: dependency-cache-13-{{ checksum "package.json" }} key: dependency-cache-{{ checksum "package.json" }}
- run: - run:
name: Installing dependencies (npm install) name: Installing dependencies (npm install)
command: npm install command: npm install
- save_cache: - save_cache:
key: dependency-cache-13-{{ checksum "package.json" }} key: dependency-cache-{{ checksum "package.json" }}
paths: paths:
- node_modules - node_modules
- run: - run:

View File

@ -41,6 +41,7 @@ define([
{ {
key: "sin", key: "sin",
name: "Sine", name: "Sine",
unit: "Hz",
formatString: '%0.2f', formatString: '%0.2f',
hints: { hints: {
range: 1 range: 1
@ -49,6 +50,7 @@ define([
{ {
key: "cos", key: "cos",
name: "Cosine", name: "Cosine",
unit: "deg",
formatString: '%0.2f', formatString: '%0.2f',
hints: { hints: {
range: 2 range: 2

3
indexTest.js Normal file
View File

@ -0,0 +1,3 @@
const testsContext = require.context('.', true, /\/(src|platform)\/.*Spec.js$/);
testsContext.keys().forEach(testsContext);

View File

@ -23,7 +23,7 @@
/*global module,process*/ /*global module,process*/
const devMode = process.env.NODE_ENV !== 'production'; const devMode = process.env.NODE_ENV !== 'production';
const browsers = [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'FirefoxHeadless']; const browsers = [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'ChromeHeadless'];
const coverageEnabled = process.env.COVERAGE === 'true'; const coverageEnabled = process.env.COVERAGE === 'true';
const reporters = ['progress', 'html']; const reporters = ['progress', 'html'];
@ -52,12 +52,16 @@ module.exports = (config) => {
basePath: '', basePath: '',
frameworks: ['jasmine'], frameworks: ['jasmine'],
files: [ files: [
'platform/**/*Spec.js', 'indexTest.js'
'src/**/*Spec.js'
], ],
port: 9876, port: 9876,
reporters: reporters, reporters: reporters,
browsers: browsers, browsers: browsers,
client: {
jasmine: {
random: false
}
},
customLaunchers: { customLaunchers: {
ChromeDebugging: { ChromeDebugging: {
base: 'Chrome', base: 'Chrome',
@ -66,7 +70,7 @@ module.exports = (config) => {
} }
}, },
colors: true, colors: true,
logLevel: config.LOG_INFO, logLevel: config.LOG_DEBUG,
autoWatch: true, autoWatch: true,
// HTML test reporting. // HTML test reporting.
htmlReporter: { htmlReporter: {
@ -82,20 +86,20 @@ module.exports = (config) => {
reports: ['html', 'lcovonly', 'text-summary'], reports: ['html', 'lcovonly', 'text-summary'],
thresholds: { thresholds: {
global: { global: {
lines: 62 lines: 64
} }
} }
}, },
preprocessors: { preprocessors: {
'platform/**/*Spec.js': ['webpack', 'sourcemap'], 'indexTest.js': ['webpack', 'sourcemap']
'src/**/*Spec.js': ['webpack', 'sourcemap']
}, },
webpack: webpackConfig, webpack: webpackConfig,
webpackMiddleware: { webpackMiddleware: {
stats: 'errors-only', stats: 'errors-only',
logLevel: 'warn' logLevel: 'warn'
}, },
concurrency: 1,
singleRun: true, singleRun: true,
browserNoActivityTimeout: 90000 browserNoActivityTimeout: 90000
}); });
} };

View File

@ -39,16 +39,16 @@
"istanbul-instrumenter-loader": "^3.0.1", "istanbul-instrumenter-loader": "^3.0.1",
"jasmine-core": "^3.1.0", "jasmine-core": "^3.1.0",
"jsdoc": "^3.3.2", "jsdoc": "^3.3.2",
"karma": "^2.0.3", "karma": "5.1.1",
"karma-chrome-launcher": "^2.2.0", "karma-chrome-launcher": "3.1.0",
"karma-firefox-launcher": "^1.3.0", "karma-firefox-launcher": "1.3.0",
"karma-cli": "^1.0.1", "karma-cli": "2.0.0",
"karma-coverage": "^1.1.2", "karma-coverage": "2.0.3",
"karma-coverage-istanbul-reporter": "^2.1.1", "karma-coverage-istanbul-reporter": "3.0.3",
"karma-html-reporter": "^0.2.7", "karma-html-reporter": "0.2.7",
"karma-jasmine": "^1.1.2", "karma-jasmine": "3.3.1",
"karma-sourcemap-loader": "^0.3.7", "karma-sourcemap-loader": "0.3.7",
"karma-webpack": "^3.0.0", "karma-webpack": "4.0.2",
"location-bar": "^3.0.1", "location-bar": "^3.0.1",
"lodash": "^4.17.12", "lodash": "^4.17.12",
"markdown-toc": "^0.11.7", "markdown-toc": "^0.11.7",

View File

@ -79,12 +79,6 @@ function (
return this.objectService; return this.objectService;
}; };
function resolveWith(object) {
return function () {
return object;
};
}
/** /**
* Save changes and conclude editing. * Save changes and conclude editing.
* *
@ -102,7 +96,6 @@ function (
SaveAsAction.prototype.save = function () { SaveAsAction.prototype.save = function () {
var self = this, var self = this,
domainObject = this.domainObject, domainObject = this.domainObject,
copyService = this.copyService,
dialog = new SaveInProgressDialog(this.dialogService), dialog = new SaveInProgressDialog(this.dialogService),
toUndirty = []; toUndirty = [];
@ -139,19 +132,22 @@ function (
return fetchObject(object.getModel().location); return fetchObject(object.getModel().location);
} }
function allowClone(objectToClone) { function saveObject(parent) {
var allowed = return this.openmct.editor.save().then(() => {
(objectToClone.getId() === domainObject.getId()) // Force mutation for search indexing
|| objectToClone.getCapability('location').isOriginal(); return parent;
if (allowed) { });
toUndirty.push(objectToClone);
}
return allowed;
} }
function cloneIntoParent(parent) { function addSavedObjectToParent(parent) {
return copyService.perform(domainObject, parent, allowClone); return parent.getCapability("composition")
.add(domainObject)
.then(function (addedObject) {
return parent.getCapability("persistence").persist()
.then(function () {
return addedObject;
});
});
} }
function undirty(object) { function undirty(object) {
@ -160,26 +156,17 @@ function (
function undirtyOriginals(object) { function undirtyOriginals(object) {
return Promise.all(toUndirty.map(undirty)) return Promise.all(toUndirty.map(undirty))
.then(resolveWith(object)); .then(() => {
return object;
});
} }
function saveAfterClone(clonedObject) { function indexForSearch(addedObject) {
return this.openmct.editor.save().then(() => { addedObject.useCapability('mutation', (model) => {
// Force mutation for search indexing
return clonedObject;
});
}
function finishEditing(clonedObject) {
return fetchObject(clonedObject.getId());
}
function indexForSearch(savedObject) {
savedObject.useCapability('mutation', (model) => {
return model; return model;
}); });
return savedObject; return addedObject;
} }
function onSuccess(object) { function onSuccess(object) {
@ -201,10 +188,12 @@ function (
.then(doWizardSave) .then(doWizardSave)
.then(showBlockingDialog) .then(showBlockingDialog)
.then(getParent) .then(getParent)
.then(cloneIntoParent) .then(saveObject)
.then(addSavedObjectToParent)
.then(undirtyOriginals) .then(undirtyOriginals)
.then(saveAfterClone) .then((addedObject) => {
.then(finishEditing) return fetchObject(addedObject.getId());
})
.then(indexForSearch) .then(indexForSearch)
.then(hideBlockingDialog) .then(hideBlockingDialog)
.then(onSuccess) .then(onSuccess)

View File

@ -28,6 +28,7 @@ define(
describe("The Transaction Service", function () { describe("The Transaction Service", function () {
var mockQ, var mockQ,
mockLog, mockLog,
mockCacheService,
transactionService; transactionService;
function fastPromise(val) { function fastPromise(val) {
@ -40,9 +41,10 @@ define(
beforeEach(function () { beforeEach(function () {
mockQ = jasmine.createSpyObj("$q", ["all"]); mockQ = jasmine.createSpyObj("$q", ["all"]);
mockCacheService = jasmine.createSpyObj("cacheService", ["flush"]);
mockQ.all.and.returnValue(fastPromise()); mockQ.all.and.returnValue(fastPromise());
mockLog = jasmine.createSpyObj("$log", ["error"]); mockLog = jasmine.createSpyObj("$log", ["error"]);
transactionService = new TransactionService(mockQ, mockLog); transactionService = new TransactionService(mockQ, mockLog, mockCacheService);
}); });
it("isActive returns true if a transaction is in progress", function () { it("isActive returns true if a transaction is in progress", function () {
@ -85,17 +87,20 @@ define(
it("commit calls all queued commit functions", function () { it("commit calls all queued commit functions", function () {
expect(transactionService.size()).toBe(3); expect(transactionService.size()).toBe(3);
transactionService.commit();
onCommits.forEach(function (spy) { return transactionService.commit().then(() => {
expect(spy).toHaveBeenCalled(); onCommits.forEach(function (spy) {
expect(spy).toHaveBeenCalled();
});
}); });
}); });
it("commit resets active state and clears queues", function () { it("commit resets active state and clears queues", function () {
transactionService.commit(); return transactionService.commit().then(() => {
expect(transactionService.isActive()).toBe(false); expect(transactionService.isActive()).toBe(false);
expect(transactionService.size()).toBe(0); expect(transactionService.size()).toBe(0);
expect(transactionService.size()).toBe(0); expect(transactionService.size()).toBe(0);
});
}); });
}); });

View File

@ -46,25 +46,34 @@ define([
spyOn(provider, 'cleanTerm').and.returnValue('cleanedTerm'); spyOn(provider, 'cleanTerm').and.returnValue('cleanedTerm');
spyOn(provider, 'fuzzyMatchUnquotedTerms').and.returnValue('fuzzy'); spyOn(provider, 'fuzzyMatchUnquotedTerms').and.returnValue('fuzzy');
spyOn(provider, 'parseResponse').and.returnValue('parsedResponse'); spyOn(provider, 'parseResponse').and.returnValue('parsedResponse');
$http.and.returnValue(Promise.resolve({})); $http.and.returnValue(Promise.resolve({
data: {
hits: {
hits: []
}
}
}));
}); });
it('cleans terms and adds fuzzyness', function () { it('cleans terms and adds fuzzyness', function () {
provider.query('hello', 10); return provider.query('hello', 10)
expect(provider.cleanTerm).toHaveBeenCalledWith('hello'); .then(() => {
expect(provider.fuzzyMatchUnquotedTerms) expect(provider.cleanTerm).toHaveBeenCalledWith('hello');
.toHaveBeenCalledWith('cleanedTerm'); expect(provider.fuzzyMatchUnquotedTerms)
.toHaveBeenCalledWith('cleanedTerm');
});
}); });
it('calls through to $http', function () { it('calls through to $http', function () {
provider.query('hello', 10); return provider.query('hello', 10).then(() => {
expect($http).toHaveBeenCalledWith({ expect($http).toHaveBeenCalledWith({
method: 'GET', method: 'GET',
params: { params: {
q: 'fuzzy', q: 'fuzzy',
size: 10 size: 10
}, },
url: 'http://localhost:9200/_search/' url: 'http://localhost:9200/_search/'
});
}); });
}); });

View File

@ -27,7 +27,7 @@ define([
"../../src/services/SearchAggregator" "../../src/services/SearchAggregator"
], function (SearchAggregator) { ], function (SearchAggregator) {
describe("SearchAggregator", function () { xdescribe("SearchAggregator", function () {
var $q, var $q,
objectService, objectService,
providers, providers,

View File

@ -422,7 +422,10 @@ define([
this.router.start(); this.router.start();
this.emit('start'); this.emit('start');
}.bind(this)); }.bind(this), function () {
console.log('startPromise failed');
this.emit('start');
});
}; };
MCT.prototype.startHeadless = function () { MCT.prototype.startHeadless = function () {
@ -444,6 +447,7 @@ define([
MCT.prototype.destroy = function () { MCT.prototype.destroy = function () {
this.emit('destroy'); this.emit('destroy');
this.router.destroy();
}; };
MCT.prototype.plugins = plugins; MCT.prototype.plugins = plugins;

View File

@ -32,10 +32,6 @@ define([
var mockListener; var mockListener;
var oldBundles; var oldBundles;
beforeAll(() => {
testUtils.resetApplicationState();
});
beforeEach(function () { beforeEach(function () {
mockPlugin = jasmine.createSpy('plugin'); mockPlugin = jasmine.createSpy('plugin');
mockPlugin2 = jasmine.createSpy('plugin2'); mockPlugin2 = jasmine.createSpy('plugin2');
@ -56,7 +52,8 @@ define([
legacyRegistry.delete(bundle); legacyRegistry.delete(bundle);
} }
}); });
testUtils.resetApplicationState(openmct);
return testUtils.resetApplicationState(openmct);
}); });
it("exposes plugins", function () { it("exposes plugins", function () {

View File

@ -35,7 +35,8 @@ define([
'./services/LegacyObjectAPIInterceptor', './services/LegacyObjectAPIInterceptor',
'./views/installLegacyViews', './views/installLegacyViews',
'./policies/LegacyCompositionPolicyAdapter', './policies/LegacyCompositionPolicyAdapter',
'./actions/LegacyActionAdapter' './actions/LegacyActionAdapter',
'./services/LegacyPersistenceAdapter'
], function ( ], function (
ActionDialogDecorator, ActionDialogDecorator,
AdapterCapability, AdapterCapability,
@ -51,7 +52,8 @@ define([
LegacyObjectAPIInterceptor, LegacyObjectAPIInterceptor,
installLegacyViews, installLegacyViews,
legacyCompositionPolicyAdapter, legacyCompositionPolicyAdapter,
LegacyActionAdapter LegacyActionAdapter,
LegacyPersistenceAdapter
) { ) {
return { return {
name: 'src/adapter', name: 'src/adapter',
@ -114,6 +116,13 @@ define([
"instantiate", "instantiate",
"topic" "topic"
] ]
},
{
provides: "persistenceService",
type: "provider",
priority: "fallback",
implementation: LegacyPersistenceAdapter.default,
depends: ["openmct"]
} }
], ],
policies: [ policies: [

View File

@ -0,0 +1,29 @@
import objectUtils from 'objectUtils';
function LegacyPersistenceProvider(openmct) {
this.openmct = openmct;
}
LegacyPersistenceProvider.prototype.listObjects = function () {
return Promise.resolve([]);
};
LegacyPersistenceProvider.prototype.listSpaces = function () {
return Promise.resolve(Object.keys(this.openmct.objects.providers));
};
LegacyPersistenceProvider.prototype.updateObject = function (legacyDomainObject) {
return this.openmct.objects.save(legacyDomainObject.useCapability('adapter'));
};
LegacyPersistenceProvider.prototype.updateObject = function (legacyDomainObject) {
return this.openmct.objects.save(legacyDomainObject.useCapability('adapter'));
};
LegacyPersistenceProvider.prototype.readObject = function (keystring) {
let identifier = objectUtils.parseKeyString(keystring);
return this.openmct.legacyObject(this.openmct.objects.get(identifier));
};
export default LegacyPersistenceProvider;

View File

@ -32,6 +32,12 @@
class="js-third-data" class="js-third-data"
:class="valueClass" :class="valueClass"
>{{ value }}</td> >{{ value }}</td>
<td
v-if="hasUnits"
class="js-units"
>
{{ unit }}
</td>
</tr> </tr>
</template> </template>
@ -48,6 +54,10 @@ export default {
domainObject: { domainObject: {
type: Object, type: Object,
required: true required: true
},
hasUnits: {
type: Boolean,
requred: true
} }
}, },
data() { data() {
@ -59,7 +69,8 @@ export default {
timestamp: undefined, timestamp: undefined,
value: '---', value: '---',
valueClass: '', valueClass: '',
currentObjectPath currentObjectPath,
unit: ''
}; };
}, },
computed: { computed: {
@ -101,6 +112,10 @@ export default {
.subscribe(this.domainObject, this.updateValues); .subscribe(this.domainObject, this.updateValues);
this.requestHistory(); this.requestHistory();
if (this.hasUnits) {
this.setUnit();
}
}, },
destroyed() { destroyed() {
this.stopWatchingMutation(); this.stopWatchingMutation();
@ -186,6 +201,9 @@ export default {
return false; return false;
} }
},
setUnit() {
this.unit = this.valueMetadata.unit || '';
} }
} }
}; };

View File

@ -28,6 +28,7 @@
<th>Name</th> <th>Name</th>
<th>Timestamp</th> <th>Timestamp</th>
<th>Value</th> <th>Value</th>
<th v-if="hasUnits">Unit</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -35,6 +36,7 @@
v-for="item in items" v-for="item in items"
:key="item.key" :key="item.key"
:domain-object="item.domainObject" :domain-object="item.domainObject"
:has-units="hasUnits"
/> />
</tbody> </tbody>
</table> </table>
@ -54,6 +56,18 @@ export default {
items: [] items: []
}; };
}, },
computed: {
hasUnits() {
let itemsWithUnits = this.items.filter((item) => {
let metadata = this.openmct.telemetry.getMetadata(item.domainObject);
return this.metadataHasUnits(metadata.valueMetadatas);
});
return itemsWithUnits.length !== 0;
}
},
mounted() { mounted() {
this.composition = this.openmct.composition.get(this.domainObject); this.composition = this.openmct.composition.get(this.domainObject);
this.composition.on('add', this.addItem); this.composition.on('add', this.addItem);
@ -84,6 +98,11 @@ export default {
reorderPlan.forEach((reorderEvent) => { reorderPlan.forEach((reorderEvent) => {
this.$set(this.items, reorderEvent.newIndex, oldItems[reorderEvent.oldIndex]); this.$set(this.items, reorderEvent.newIndex, oldItems[reorderEvent.oldIndex]);
}); });
},
metadataHasUnits(valueMetadatas) {
let metadataWithUnits = valueMetadatas.filter(metadatum => metadatum.unit);
return metadataWithUnits.length > 0;
} }
} }
}; };

View File

@ -27,6 +27,7 @@
<th>Name</th> <th>Name</th>
<th>Timestamp</th> <th>Timestamp</th>
<th>Value</th> <th>Value</th>
<th v-if="hasUnits">Unit</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -45,6 +46,7 @@
v-for="secondary in secondaryTelemetryObjects[primary.key]" v-for="secondary in secondaryTelemetryObjects[primary.key]"
:key="secondary.key" :key="secondary.key"
:domain-object="secondary.domainObject" :domain-object="secondary.domainObject"
:has-units="hasUnits"
/> />
</template> </template>
</tbody> </tbody>
@ -66,6 +68,23 @@ export default {
compositions: [] compositions: []
}; };
}, },
computed: {
hasUnits() {
let ladTables = Object.values(this.secondaryTelemetryObjects);
for (let ladTable of ladTables) {
for (let telemetryObject of ladTable) {
let metadata = this.openmct.telemetry.getMetadata(telemetryObject.domainObject);
for (let metadatum of metadata.valueMetadatas) {
if (metadatum.unit) {
return true;
}
}
}
}
return false;
}
},
mounted() { mounted() {
this.composition = this.openmct.composition.get(this.domainObject); this.composition = this.openmct.composition.get(this.domainObject);
this.composition.on('add', this.addPrimary); this.composition.on('add', this.addPrimary);
@ -109,9 +128,8 @@ export default {
let index = this.primaryTelemetryObjects.findIndex(primary => this.openmct.objects.makeKeyString(identifier) === primary.key), let index = this.primaryTelemetryObjects.findIndex(primary => this.openmct.objects.makeKeyString(identifier) === primary.key),
primary = this.primaryTelemetryObjects[index]; primary = this.primaryTelemetryObjects[index];
this.$set(this.secondaryTelemetryObjects, primary.key, undefined); this.$delete(this.secondaryTelemetryObjects, primary.key);
this.primaryTelemetryObjects.splice(index, 1); this.primaryTelemetryObjects.splice(index, 1);
primary = undefined;
}, },
reorderPrimary(reorderPlan) { reorderPrimary(reorderPlan) {
let oldComposition = this.primaryTelemetryObjects.slice(); let oldComposition = this.primaryTelemetryObjects.slice();

View File

@ -94,7 +94,7 @@ describe("The LAD Table", () => {
}); });
afterEach(() => { afterEach(() => {
resetApplicationState(openmct); return resetApplicationState(openmct);
}); });
it("should provide a table view only for lad table objects", () => { it("should provide a table view only for lad table objects", () => {
@ -284,7 +284,7 @@ describe("The LAD Table Set", () => {
}); });
afterEach(() => { afterEach(() => {
resetApplicationState(openmct); return resetApplicationState(openmct);
}); });
it("should provide a lad table set view only for lad table set objects", () => { it("should provide a lad table set view only for lad table set objects", () => {

View File

@ -35,7 +35,7 @@ define(
) { ) {
var defaultAjaxFunction = $.ajax; var defaultAjaxFunction = $.ajax;
xdescribe("The URLIndicator", function () { describe("The URLIndicator", function () {
var openmct; var openmct;
var indicatorElement; var indicatorElement;
var pluginOptions; var pluginOptions;

View File

@ -66,7 +66,7 @@ export default class URLTimeSettingsSynchronizer {
TIME_EVENTS.forEach(event => { TIME_EVENTS.forEach(event => {
this.openmct.time.off(event, this.setUrlFromTimeApi); this.openmct.time.off(event, this.setUrlFromTimeApi);
}); });
this.openmct.time.on('bounds', this.updateBounds); this.openmct.time.off('bounds', this.updateBounds);
} }
updateTimeSettings() { updateTimeSettings() {

View File

@ -27,7 +27,6 @@ import {
describe("The URLTimeSettingsSynchronizer", () => { describe("The URLTimeSettingsSynchronizer", () => {
let openmct; let openmct;
let testClock; let testClock;
beforeAll(() => resetApplicationState());
beforeEach((done) => { beforeEach((done) => {
openmct = createOpenMct(); openmct = createOpenMct();
@ -82,17 +81,17 @@ describe("The URLTimeSettingsSynchronizer", () => {
expect(window.location.hash.includes('tc.mode=fixed')).toBe(false); expect(window.location.hash.includes('tc.mode=fixed')).toBe(false);
}); });
describe("when set in the url", () => { describe("when set in the url", () => {
it("will change from fixed to realtime mode when the mode changes", () => { it("will change from fixed to realtime mode when the mode changes", (done) => {
expectLocationToBeInFixedMode(); expectLocationToBeInFixedMode();
return switchToRealtimeMode().then(() => { return switchToRealtimeMode().then(() => {
let clock = openmct.time.clock(); let clock = openmct.time.clock();
expect(clock).toBeDefined(); expect(clock).toBeDefined();
expect(clock.key).toBe('local'); expect(clock.key).toBe('local');
done();
}); });
}); });
it("the clock is correctly set in the API from the URL parameters", () => { it("the clock is correctly set in the API from the URL parameters", (done) => {
return switchToRealtimeMode().then(() => { return switchToRealtimeMode().then(() => {
let resolveFunction; let resolveFunction;
@ -110,10 +109,11 @@ describe("The URLTimeSettingsSynchronizer", () => {
expect(clock).toBeDefined(); expect(clock).toBeDefined();
expect(clock.key).toBe('test-clock'); expect(clock.key).toBe('test-clock');
openmct.time.off('clock', resolveFunction); openmct.time.off('clock', resolveFunction);
done();
}); });
}); });
}); });
it("the clock offsets are correctly set in the API from the URL parameters", () => { it("the clock offsets are correctly set in the API from the URL parameters", (done) => {
return switchToRealtimeMode().then(() => { return switchToRealtimeMode().then(() => {
let resolveFunction; let resolveFunction;
@ -132,10 +132,11 @@ describe("The URLTimeSettingsSynchronizer", () => {
expect(clockOffsets.start).toBe(-2000); expect(clockOffsets.start).toBe(-2000);
expect(clockOffsets.end).toBe(200); expect(clockOffsets.end).toBe(200);
openmct.time.off('clockOffsets', resolveFunction); openmct.time.off('clockOffsets', resolveFunction);
done();
}); });
}); });
}); });
it("the time system is correctly set in the API from the URL parameters", () => { it("the time system is correctly set in the API from the URL parameters", (done) => {
return switchToRealtimeMode().then(() => { return switchToRealtimeMode().then(() => {
let resolveFunction; let resolveFunction;
@ -153,6 +154,7 @@ describe("The URLTimeSettingsSynchronizer", () => {
expect(timeSystem).toBeDefined(); expect(timeSystem).toBeDefined();
expect(timeSystem.key).toBe('local'); expect(timeSystem.key).toBe('local');
openmct.time.off('timeSystem', resolveFunction); openmct.time.off('timeSystem', resolveFunction);
done();
}); });
}); });
}); });
@ -200,7 +202,7 @@ describe("The URLTimeSettingsSynchronizer", () => {
expect(window.location.hash.includes('tc.timeSystem=utc')).toBe(false); expect(window.location.hash.includes('tc.timeSystem=utc')).toBe(false);
}); });
describe("when set in the url", () => { describe("when set in the url", () => {
it("time system changes are reflected in the API", () => { it("time system changes are reflected in the API", (done) => {
let resolveFunction; let resolveFunction;
return new Promise((resolve) => { return new Promise((resolve) => {
@ -216,9 +218,10 @@ describe("The URLTimeSettingsSynchronizer", () => {
expect(timeSystem.key).toBe('local'); expect(timeSystem.key).toBe('local');
openmct.time.off('timeSystem', resolveFunction); openmct.time.off('timeSystem', resolveFunction);
done();
}); });
}); });
it("mode can be changed from realtime to fixed", () => { it("mode can be changed from realtime to fixed", (done) => {
return switchToRealtimeMode().then(() => { return switchToRealtimeMode().then(() => {
expectLocationToBeInRealtimeMode(); expectLocationToBeInRealtimeMode();
@ -226,9 +229,10 @@ describe("The URLTimeSettingsSynchronizer", () => {
}).then(switchToFixedMode).then(() => { }).then(switchToFixedMode).then(() => {
let clock = openmct.time.clock(); let clock = openmct.time.clock();
expect(clock).not.toBeDefined(); expect(clock).not.toBeDefined();
done();
}); });
}); });
it("bounds are correctly set in the API from the URL parameters", () => { it("bounds are correctly set in the API from the URL parameters", (done) => {
let resolveFunction; let resolveFunction;
expectLocationToBeInFixedMode(); expectLocationToBeInFixedMode();
@ -246,9 +250,10 @@ describe("The URLTimeSettingsSynchronizer", () => {
expect(bounds).toBeDefined(); expect(bounds).toBeDefined();
expect(bounds.start).toBe(222); expect(bounds.start).toBe(222);
expect(bounds.end).toBe(333); expect(bounds.end).toBe(333);
done();
}); });
}); });
it("bounds are correctly set in the API from the URL parameters where only the end bound changes", () => { it("bounds are correctly set in the API from the URL parameters where only the end bound changes", (done) => {
let resolveFunction; let resolveFunction;
expectLocationToBeInFixedMode(); expectLocationToBeInFixedMode();
@ -265,6 +270,7 @@ describe("The URLTimeSettingsSynchronizer", () => {
expect(bounds).toBeDefined(); expect(bounds).toBeDefined();
expect(bounds.start).toBe(0); expect(bounds.start).toBe(0);
expect(bounds.end).toBe(333); expect(bounds.end).toBe(333);
done();
}); });
}); });
}); });

View File

@ -27,7 +27,7 @@ define([
'zepto', 'zepto',
'./dom-observer' './dom-observer'
], function (AutoflowTabularPlugin, AutoflowTabularConstants, MCT, $, DOMObserver) { ], function (AutoflowTabularPlugin, AutoflowTabularConstants, MCT, $, DOMObserver) {
xdescribe("AutoflowTabularPlugin", function () { describe("AutoflowTabularPlugin", function () {
var testType; var testType;
var testObject; var testObject;
var mockmct; var mockmct;

View File

@ -36,10 +36,6 @@ describe('the plugin', function () {
let openmct; let openmct;
let testTelemetryObject; let testTelemetryObject;
beforeAll(() => {
resetApplicationState(openmct);
});
beforeEach((done) => { beforeEach((done) => {
testTelemetryObject = { testTelemetryObject = {
identifier: { identifier: {
@ -98,7 +94,7 @@ describe('the plugin', function () {
}); });
afterEach(() => { afterEach(() => {
resetApplicationState(openmct); return resetApplicationState(openmct);
}); });
let mockConditionSetObject = { let mockConditionSetObject = {
@ -422,7 +418,7 @@ describe('the plugin', function () {
"telemetry": "any", "telemetry": "any",
"operation": "isStale", "operation": "isStale",
"input": [ "input": [
"1" "0.2"
], ],
"metadata": "dataReceived" "metadata": "dataReceived"
} }
@ -462,7 +458,7 @@ describe('the plugin', function () {
}; };
}); });
it('should evaluate as stale when telemetry is not received in the allotted time', (done) => { xit('should evaluate as stale when telemetry is not received in the allotted time', (done) => {
let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct); let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
conditionMgr.on('conditionSetResultUpdated', mockListener); conditionMgr.on('conditionSetResultUpdated', mockListener);
@ -481,12 +477,12 @@ describe('the plugin', function () {
utc: undefined utc: undefined
}); });
done(); done();
}, 1500); }, 400);
}); });
it('should not evaluate as stale when telemetry is received in the allotted time', (done) => { xit('should not evaluate as stale when telemetry is received in the allotted time', (done) => {
const date = Date.now(); const date = Date.now();
conditionSetDomainObject.configuration.conditionCollection[0].configuration.criteria[0].input = ["2"]; conditionSetDomainObject.configuration.conditionCollection[0].configuration.criteria[0].input = ["0.4"];
let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct); let conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
conditionMgr.on('conditionSetResultUpdated', mockListener); conditionMgr.on('conditionSetResultUpdated', mockListener);
conditionMgr.telemetryObjects = { conditionMgr.telemetryObjects = {
@ -507,7 +503,7 @@ describe('the plugin', function () {
utc: undefined utc: undefined
}); });
done(); done();
}, 1500); }, 300);
}); });
}); });
}); });

View File

@ -134,6 +134,14 @@ define(['lodash'], function (_) {
return `configuration.items[${selectionPath[0].context.index}]`; return `configuration.items[${selectionPath[0].context.index}]`;
} }
function getAllOfType(selection, specificType) {
return selection.filter(selectionPath => {
let type = selectionPath[0].context.layoutItem.type;
return type === specificType;
});
}
function getAllTypes(selection) { function getAllTypes(selection) {
return selection.filter(selectionPath => { return selection.filter(selectionPath => {
let type = selectionPath[0].context.layoutItem.type; let type = selectionPath[0].context.layoutItem.type;
@ -510,6 +518,54 @@ define(['lodash'], function (_) {
return allTelemetry; return allTelemetry;
} }
function getToggleUnitsButton(selectedParent, selection) {
let applicableItems = getAllOfType(selection, 'telemetry-view');
applicableItems = unitsOnly(applicableItems);
if (!applicableItems.length) {
return;
}
return {
control: "toggle-button",
domainObject: selectedParent,
applicableSelectedItems: applicableItems,
property: function (selectionPath) {
return getPath(selectionPath) + '.showUnits';
},
options: [
{
value: true,
icon: 'icon-eye-open',
title: "Show units"
},
{
value: false,
icon: 'icon-eye-disabled',
title: "Hide units"
}
]
};
}
function unitsOnly(items) {
let results = items.filter((item) => {
let currentItem = item[0];
let metadata = openmct.telemetry.getMetadata(currentItem.context.item);
if (!metadata) {
return false;
}
let hasUnits = metadata
.valueMetadatas
.filter((metadatum) => metadatum.unit)
.length;
return hasUnits > 0;
});
return results;
}
function getViewSwitcherMenu(selectedParent, selectionPath, selection) { function getViewSwitcherMenu(selectedParent, selectionPath, selection) {
if (selection.length === 1) { if (selection.length === 1) {
let displayLayoutContext = selectionPath[1].context, let displayLayoutContext = selectionPath[1].context,
@ -594,6 +650,7 @@ define(['lodash'], function (_) {
'text-style': [], 'text-style': [],
'position': [], 'position': [],
'duplicate': [], 'duplicate': [],
'unit-toggle': [],
'remove': [] 'remove': []
}; };
@ -663,6 +720,13 @@ define(['lodash'], function (_) {
if (toolbar.viewSwitcher.length === 0) { if (toolbar.viewSwitcher.length === 0) {
toolbar.viewSwitcher = [getViewSwitcherMenu(selectedParent, selectionPath, selectedObjects)]; toolbar.viewSwitcher = [getViewSwitcherMenu(selectedParent, selectionPath, selectedObjects)];
} }
if (toolbar['unit-toggle'].length === 0) {
let toggleUnitsButton = getToggleUnitsButton(selectedParent, selectedObjects);
if (toggleUnitsButton) {
toolbar['unit-toggle'] = [toggleUnitsButton];
}
}
} else if (layoutItem.type === 'text-view') { } else if (layoutItem.type === 'text-view') {
if (toolbar['text-style'].length === 0) { if (toolbar['text-style'].length === 0) {
toolbar['text-style'] = [ toolbar['text-style'] = [

View File

@ -58,6 +58,12 @@
> >
<div class="c-telemetry-view__value-text"> <div class="c-telemetry-view__value-text">
{{ telemetryValue }} {{ telemetryValue }}
<span
v-if="unit && item.showUnits"
class="c-telemetry-view__value-text__unit"
>
{{ unit }}
</span>
</div> </div>
</div> </div>
</div> </div>
@ -137,6 +143,12 @@ export default {
return displayMode === 'all' || displayMode === 'value'; return displayMode === 'all' || displayMode === 'value';
}, },
unit() {
let value = this.item.value,
unit = this.metadata.value(value).unit;
return unit;
},
styleObject() { styleObject() {
return Object.assign({}, { return Object.assign({}, {
fontSize: this.item.size fontSize: this.item.size
@ -257,7 +269,9 @@ export default {
item: domainObject, item: domainObject,
layoutItem: this.item, layoutItem: this.item,
index: this.index, index: this.index,
updateTelemetryFormat: this.updateTelemetryFormat updateTelemetryFormat: this.updateTelemetryFormat,
toggleUnits: this.toggleUnits,
showUnits: this.showUnits
}; };
this.removeSelectable = this.openmct.selection.selectable( this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context, this.immediatelySelect || this.initSelect); this.$el, this.context, this.immediatelySelect || this.initSelect);

View File

@ -0,0 +1,346 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* 'License'); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import { createOpenMct, resetApplicationState } from 'utils/testing';
import DisplayLayoutPlugin from './plugin';
describe('the plugin', function () {
let element;
let child;
let openmct;
let displayLayoutDefinition;
beforeEach((done) => {
openmct = createOpenMct();
openmct.install(new DisplayLayoutPlugin({
showAsView: []
}));
displayLayoutDefinition = openmct.types.get('layout');
element = document.createElement('div');
child = document.createElement('div');
element.appendChild(child);
openmct.on('start', done);
openmct.startHeadless();
});
afterEach(() => {
return resetApplicationState(openmct);
});
it('defines a display layout object type with the correct key', () => {
expect(displayLayoutDefinition.definition.name).toEqual('Display Layout');
});
it('provides a view', () => {
const testViewObject = {
id: 'test-object',
type: 'layout',
configuration: {
items: [
{
'identifier': {
'namespace': '',
'key': '55122607-e65e-44d5-9c9d-9c31a914ca89'
},
'x': 8,
'y': 3,
'width': 10,
'height': 5,
'displayMode': 'all',
'value': 'sin',
'stroke': '',
'fill': '',
'color': '',
'size': '13px',
'type': 'telemetry-view',
'id': 'deb9f839-80ad-4ccf-a152-5c763ceb7d7e'
}
],
layoutGrid: [10, 10]
}
};
const applicableViews = openmct.objectViews.get(testViewObject);
let displayLayoutViewProvider = applicableViews.find((viewProvider) => viewProvider.key === 'layout.view');
expect(displayLayoutViewProvider).toBeDefined();
});
describe('the alpha numeric format view', () => {
let displayLayoutItem;
let telemetryItem;
let selection;
beforeEach(() => {
displayLayoutItem = {
'composition': [
],
'configuration': {
'items': [
{
'identifier': {
'namespace': '',
'key': '55122607-e65e-44d5-9c9d-9c31a914ca89'
},
'x': 8,
'y': 3,
'width': 10,
'height': 5,
'displayMode': 'all',
'value': 'sin',
'stroke': '',
'fill': '',
'color': '',
'size': '13px',
'type': 'telemetry-view',
'id': 'deb9f839-80ad-4ccf-a152-5c763ceb7d7e'
}
],
'layoutGrid': [
10,
10
]
},
'name': 'Display Layout',
'type': 'layout',
'identifier': {
'namespace': '',
'key': 'c5e636c1-6771-4c9c-b933-8665cab189b3'
}
};
telemetryItem = {
'telemetry': {
'period': 5,
'amplitude': 5,
'offset': 5,
'dataRateInHz': 5,
'phase': 5,
'randomness': 0
},
'name': 'Sine Wave Generator',
'type': 'generator',
'modified': 1592851063871,
'location': 'mine',
'persisted': 1592851063871,
'id': '55122607-e65e-44d5-9c9d-9c31a914ca89',
'identifier': {
'namespace': '',
'key': '55122607-e65e-44d5-9c9d-9c31a914ca89'
}
};
selection = [
[{
context: {
'layoutItem': displayLayoutItem.configuration.items[0],
'item': telemetryItem,
'index': 1
}
},
{
context: {
'item': displayLayoutItem,
'supportsMultiSelect': true
}
}]
];
});
it('provides an alphanumeric format view', () => {
const displayLayoutAlphaNumFormatView = openmct.inspectorViews.get(selection);
expect(displayLayoutAlphaNumFormatView.length).toBeDefined();
});
});
describe('the toolbar', () => {
let displayLayoutItem;
let selection;
beforeEach(() => {
displayLayoutItem = {
'composition': [
],
'configuration': {
'items': [
{
'fill': '#717171',
'stroke': '',
'x': 1,
'y': 1,
'width': 10,
'height': 5,
'type': 'box-view',
'id': '89b88746-d325-487b-aec4-11b79afff9e8'
},
{
'x': 18,
'y': 9,
'x2': 23,
'y2': 4,
'stroke': '#717171',
'type': 'line-view',
'id': '57d49a28-7863-43bd-9593-6570758916f0'
},
{
'identifier': {
'namespace': '',
'key': '55122607-e65e-44d5-9c9d-9c31a914ca89'
},
'x': 8,
'y': 3,
'width': 10,
'height': 5,
'displayMode': 'all',
'value': 'sin',
'stroke': '',
'fill': '',
'color': '',
'size': '13px',
'type': 'telemetry-view',
'id': 'deb9f839-80ad-4ccf-a152-5c763ceb7d7e'
},
{
'width': 32,
'height': 18,
'x': 78,
'y': 8,
'identifier': {
'namespace': '',
'key': 'bdeb91ab-3a7e-4a71-9dd2-39d73644e136'
},
'hasFrame': true,
'type': 'subobject-view',
'id': 'c0ff485a-344c-4e70-8d83-a9d9998a69fc'
}
],
'layoutGrid': [
10,
10
]
},
'name': 'Display Layout',
'type': 'layout',
'identifier': {
'namespace': '',
'key': 'c5e636c1-6771-4c9c-b933-8665cab189b3'
}
};
selection = [
[{
context: {
'layoutItem': displayLayoutItem.configuration.items[1],
'index': 1
}
},
{
context: {
'item': displayLayoutItem,
'supportsMultiSelect': true
}
}],
[{
context: {
'layoutItem': displayLayoutItem.configuration.items[0],
'index': 0
}
},
{
context: {
item: displayLayoutItem,
'supportsMultiSelect': true
}
}],
[{
context: {
'layoutItem': displayLayoutItem.configuration.items[2],
'item': displayLayoutItem.configuration.items[2],
'index': 2
}
},
{
context: {
item: displayLayoutItem,
'supportsMultiSelect': true
}
}],
[{
context: {
'item': {
'composition': [
{
'namespace': '',
'key': '55122607-e65e-44d5-9c9d-9c31a914ca89'
}
],
'configuration': {
'series': [
{
'identifier': {
'namespace': '',
'key': '55122607-e65e-44d5-9c9d-9c31a914ca89'
}
}
],
'yAxis': {
},
'xAxis': {
}
},
'name': 'Unnamed Overlay Plot',
'type': 'telemetry.plot.overlay',
'modified': 1594142141929,
'location': 'mine',
'identifier': {
'namespace': '',
'key': 'bdeb91ab-3a7e-4a71-9dd2-39d73644e136'
},
'persisted': 1594142141929
},
'layoutItem': displayLayoutItem.configuration.items[3],
'index': 3
}
},
{
context: {
item: displayLayoutItem,
'supportsMultiSelect': true
}
}]
];
});
it('provides controls including separators', () => {
const displayLayoutToolbar = openmct.toolbars.get(selection);
expect(displayLayoutToolbar.length).toBe(9);
});
});
});

View File

@ -46,7 +46,7 @@ describe("the plugin", () => {
}); });
afterEach(() => { afterEach(() => {
resetApplicationState(openmct); return resetApplicationState(openmct);
}); });
it('installs the new folder action', () => { it('installs the new folder action', () => {

View File

@ -35,10 +35,6 @@ describe('the plugin', () => {
parentElement, parentElement,
mockMessages = ['error', 'test', 'notifications']; mockMessages = ['error', 'test', 'notifications'];
beforeAll(() => {
resetApplicationState();
});
beforeEach((done) => { beforeEach((done) => {
openmct = createOpenMct(); openmct = createOpenMct();
@ -61,7 +57,7 @@ describe('the plugin', () => {
}); });
afterEach(() => { afterEach(() => {
resetApplicationState(openmct); return resetApplicationState(openmct);
}); });
describe('the indicator plugin element', () => { describe('the indicator plugin element', () => {

View File

@ -0,0 +1,53 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, 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.
*****************************************************************************/
/**
* A CouchDocument describes domain object model in a format
* which is easily read-written to CouchDB. This includes
* Couch's _id and _rev fields, as well as a separate
* metadata field which contains a subset of information found
* in the model itself (to support search optimization with
* CouchDB views.)
* @memberof platform/persistence/couch
* @constructor
* @param {string} id the id under which to store this mode
* @param {object} model the model to store
* @param {string} rev the revision to include (or undefined,
* if no revision should be noted for couch)
* @param {boolean} whether or not to mark this document as
* deleted (see CouchDB docs for _deleted)
*/
export default function CouchDocument(id, model, rev, markDeleted) {
return {
"_id": id,
"_rev": rev,
"_deleted": markDeleted,
"metadata": {
"category": "domain object",
"type": model.type,
"owner": "admin",
"name": model.name,
"created": Date.now()
},
"model": model
};
}

View File

@ -0,0 +1,134 @@
import CouchDocument from "./CouchDocument";
import CouchObjectQueue from "./CouchObjectQueue";
const REV = "_rev";
const ID = "_id";
export default class CouchObjectProvider {
constructor(openmct, url, namespace) {
this.openmct = openmct;
this.url = url;
this.namespace = namespace;
this.objectQueue = {};
}
request(subPath, method, value) {
return fetch(this.url + '/' + subPath, {
method: method,
body: JSON.stringify(value)
}).then(response => response.json())
.then(function (response) {
return response;
}, function () {
return undefined;
});
}
// Check the response to a create/update/delete request;
// track the rev if it's valid, otherwise return false to
// indicate that the request failed.
// persist any queued objects
checkResponse(response, intermediateResponse) {
let requestSuccess = false;
const id = response.id;
let rev;
if (response && response.ok) {
rev = response.rev;
requestSuccess = true;
}
intermediateResponse.resolve(requestSuccess);
if (id) {
if (!this.objectQueue[id]) {
this.objectQueue[id] = new CouchObjectQueue(undefined, rev);
}
this.objectQueue[id].updateRevision(rev);
this.objectQueue[id].pending = false;
if (this.objectQueue[id].hasNext()) {
this.updateQueued(id);
}
}
}
getModel(response) {
if (response && response.model) {
let key = response[ID];
let object = response.model;
object.identifier = {
namespace: this.namespace,
key: key
};
if (!this.objectQueue[key]) {
this.objectQueue[key] = new CouchObjectQueue(undefined, response[REV]);
}
this.objectQueue[key].updateRevision(response[REV]);
return object;
} else {
return undefined;
}
}
get(identifier) {
return this.request(identifier.key, "GET").then(this.getModel.bind(this));
}
getIntermediateResponse() {
let intermediateResponse = {};
intermediateResponse.promise = new Promise(function (resolve, reject) {
intermediateResponse.resolve = resolve;
intermediateResponse.reject = reject;
});
return intermediateResponse;
}
enqueueObject(key, model, intermediateResponse) {
if (this.objectQueue[key]) {
this.objectQueue[key].enqueue({
model,
intermediateResponse
});
} else {
this.objectQueue[key] = new CouchObjectQueue({
model,
intermediateResponse
});
}
}
create(model) {
let intermediateResponse = this.getIntermediateResponse();
const key = model.identifier.key;
this.enqueueObject(key, model, intermediateResponse);
this.objectQueue[key].pending = true;
const queued = this.objectQueue[key].dequeue();
this.request(key, "PUT", new CouchDocument(key, queued.model)).then((response) => {
this.checkResponse(response, queued.intermediateResponse);
});
return intermediateResponse.promise;
}
updateQueued(key) {
if (!this.objectQueue[key].pending) {
this.objectQueue[key].pending = true;
const queued = this.objectQueue[key].dequeue();
this.request(key, "PUT", new CouchDocument(key, queued.model, this.objectQueue[key].rev)).then((response) => {
this.checkResponse(response, queued.intermediateResponse);
});
}
}
update(model) {
let intermediateResponse = this.getIntermediateResponse();
const key = model.identifier.key;
this.enqueueObject(key, model, intermediateResponse);
this.updateQueued(key);
return intermediateResponse.promise;
}
}

View File

@ -0,0 +1,32 @@
//TODO: should we limit the queue size?
//const MAX_QUEUE_SIZE = 10;
export default class CouchObjectQueue {
constructor(object, rev) {
this.rev = rev;
this.objects = object ? [object] : [];
this.pending = false;
}
updateRevision(rev) {
this.rev = rev;
}
hasNext() {
return this.objects.length;
}
enqueue(item) {
this.objects.push(item);
}
dequeue() {
return this.objects.shift();
}
clear() {
this.rev = undefined;
this.objects = [];
}
}

View File

@ -0,0 +1,8 @@
import CouchObjectProvider from './CouchObjectProvider';
const NAMESPACE = '';
export default function CouchPlugin(url) {
return function install(openmct) {
openmct.objects.addProvider(NAMESPACE, new CouchObjectProvider(openmct, url, NAMESPACE));
};
}

View File

@ -0,0 +1,102 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import CouchPlugin from './plugin.js';
import {
createOpenMct,
resetApplicationState
} from 'utils/testing';
import CouchObjectProvider from "@/plugins/persistence/couch/CouchObjectProvider";
import CouchObjectQueue from "@/plugins/persistence/couch/CouchObjectQueue";
describe("the plugin", () => {
let openmct;
let element;
let child;
let provider;
let testSpace = "testSpace";
let testPath = "/test/db";
let mockDomainObject = {
identifier: {
namespace: '',
key: "some-value"
}
};
beforeEach((done) => {
openmct = createOpenMct(false);
openmct.install(new CouchPlugin(testSpace, testPath));
element = document.createElement('div');
child = document.createElement('div');
element.appendChild(child);
openmct.on('start', done);
openmct.startHeadless();
provider = openmct.objects.getProvider(mockDomainObject.identifier);
});
it('registers a provider for objects', () => {
expect(provider).toBeDefined();
});
it('gets an object', () => {
openmct.objects.get(mockDomainObject.identifier).then((result) => {
expect(provider.get).toHaveBeenCalled();
expect(result).toBeDefined();
});
});
it('creates an object', () => {
openmct.objects.save(mockDomainObject).then((result) => {
expect(provider.create).toHaveBeenCalled();
expect(result).toBeDefined();
});
});
it('updates an object', () => {
openmct.objects.save(mockDomainObject).then((result) => {
expect(provider.create).toHaveBeenCalled();
openmct.objects.save(mockDomainObject).then((updatedResult) => {
expect(provider.update).toHaveBeenCalled();
expect(updatedResult).toBeDefined();
});
});
});
it('updates queued objects', () => {
let couchProvider = new CouchObjectProvider(openmct, 'http://localhost', '');
let intermediateResponse = couchProvider.getIntermediateResponse();
spyOn(couchProvider, 'updateQueued');
couchProvider.enqueueObject(mockDomainObject.identifier.key, mockDomainObject, intermediateResponse);
couchProvider.objectQueue[mockDomainObject.identifier.key].updateRevision(1);
couchProvider.update(mockDomainObject);
expect(couchProvider.objectQueue[mockDomainObject.identifier.key].hasNext()).toBe(2);
couchProvider.checkResponse({
ok: true,
rev: 2,
id: mockDomainObject.identifier.key
}, intermediateResponse);
expect(couchProvider.updateQueued).toHaveBeenCalledTimes(2);
});
});

View File

@ -49,7 +49,7 @@
ng-style="{ 'background-color': series.get('color').asHexString() }"> ng-style="{ 'background-color': series.get('color').asHexString() }">
</span> </span>
<span class="is-missing__indicator" title="This item is missing"></span> <span class="is-missing__indicator" title="This item is missing"></span>
<span class="plot-series-name">{{ series.get('name') }}</span> <span class="plot-series-name">{{ series.nameWithUnit() }}</span>
</div> </div>
<div class="plot-series-value hover-value-enabled value-to-display-{{ legend.get('valueToShowWhenCollapsed') }} {{ series.closest.mctLimitState.cssClass }}" <div class="plot-series-value hover-value-enabled value-to-display-{{ legend.get('valueToShowWhenCollapsed') }} {{ series.closest.mctLimitState.cssClass }}"
ng-class="{ 'cursor-hover': (legend.get('valueToShowWhenCollapsed').indexOf('nearest') != -1) }" ng-class="{ 'cursor-hover': (legend.get('valueToShowWhenCollapsed').indexOf('nearest') != -1) }"
@ -79,6 +79,9 @@
<th ng-if="legend.get('showValueWhenExpanded')"> <th ng-if="legend.get('showValueWhenExpanded')">
Value Value
</th> </th>
<th ng-if="legend.get('showUnitsWhenExpanded')">
Unit
</th>
<th ng-if="legend.get('showMinimumWhenExpanded')" <th ng-if="legend.get('showMinimumWhenExpanded')"
class="mobile-hide"> class="mobile-hide">
Min Min
@ -114,6 +117,11 @@
{{ series.formatY(series.closest) }} {{ series.formatY(series.closest) }}
</span> </span>
</td> </td>
<td ng-if="legend.get('showUnitsWhenExpanded')">
<span class="plot-series-value cursor-hover hover-value-enabled">
{{ series.get('unit') }}
</span>
</td>
<td ng-if="legend.get('showMinimumWhenExpanded')" <td ng-if="legend.get('showMinimumWhenExpanded')"
class="mobile-hide"> class="mobile-hide">
<span class="plot-series-value"> <span class="plot-series-value">

View File

@ -140,6 +140,7 @@
<span ng-if="config.legend.get('showValueWhenExpanded')">Value</span> <span ng-if="config.legend.get('showValueWhenExpanded')">Value</span>
<span ng-if="config.legend.get('showMinimumWhenExpanded')">Min</span> <span ng-if="config.legend.get('showMinimumWhenExpanded')">Min</span>
<span ng-if="config.legend.get('showMaximumWhenExpanded')">Max</span> <span ng-if="config.legend.get('showMaximumWhenExpanded')">Max</span>
<span ng-if="config.legend.get('showUnitsWhenExpanded')">Units</span>
</div> </div>
</li> </li>
</ul> </ul>

View File

@ -201,6 +201,7 @@
<option value="nearestValue">Nearest value</option> <option value="nearestValue">Nearest value</option>
<option value="min">Minimum value</option> <option value="min">Minimum value</option>
<option value="max">Maximum value</option> <option value="max">Maximum value</option>
<option value="units">showUnitsWhenExpanded</option>
</select> </select>
</div> </div>
</li> </li>
@ -217,6 +218,8 @@
ng-model="form.showMinimumWhenExpanded"/> Minimum value</li> ng-model="form.showMinimumWhenExpanded"/> Minimum value</li>
<li><input type="checkbox" <li><input type="checkbox"
ng-model="form.showMaximumWhenExpanded"/> Maximum value</li> ng-model="form.showMaximumWhenExpanded"/> Maximum value</li>
<li><input type="checkbox"
ng-model="form.showUnitsWhenExpanded"/> Units</li>
</ul> </ul>
</div> </div>

View File

@ -53,7 +53,8 @@ define([
showTimestampWhenExpanded: true, showTimestampWhenExpanded: true,
showValueWhenExpanded: true, showValueWhenExpanded: true,
showMaximumWhenExpanded: true, showMaximumWhenExpanded: true,
showMinimumWhenExpanded: true showMinimumWhenExpanded: true,
showUnitsWhenExpanded: true
}; };
} }
}); });

View File

@ -102,6 +102,7 @@ define([
return { return {
name: options.domainObject.name, name: options.domainObject.name,
unit: range.unit,
xKey: options.collection.plot.xAxis.get('key'), xKey: options.collection.plot.xAxis.get('key'),
yKey: range.key, yKey: range.key,
markers: true, markers: true,
@ -439,6 +440,11 @@ define([
const markerSize = this.get('markerSize'); const markerSize = this.get('markerSize');
return `${markerShape}: ${markerSize}px`; return `${markerShape}: ${markerSize}px`;
},
nameWithUnit: function () {
let unit = this.get('unit');
return this.get('name') + (unit ? ' ' + unit : '');
} }
}); });

View File

@ -65,6 +65,11 @@ define([
modelProp: 'showMinimumWhenExpanded', modelProp: 'showMinimumWhenExpanded',
coerce: Boolean, coerce: Boolean,
objectPath: 'configuration.legend.showMinimumWhenExpanded' objectPath: 'configuration.legend.showMinimumWhenExpanded'
},
{
modelProp: 'showUnitsWhenExpanded',
coerce: Boolean,
objectPath: 'configuration.legend.showUnitsWhenExpanded'
} }
] ]
}); });

View File

@ -54,7 +54,8 @@ define([
'./themes/snow', './themes/snow',
'./URLTimeSettingsSynchronizer/plugin', './URLTimeSettingsSynchronizer/plugin',
'./notificationIndicator/plugin', './notificationIndicator/plugin',
'./newFolderAction/plugin' './newFolderAction/plugin',
'./persistence/couch/plugin'
], function ( ], function (
_, _,
UTCTimeSystem, UTCTimeSystem,
@ -89,12 +90,12 @@ define([
Snow, Snow,
URLTimeSettingsSynchronizer, URLTimeSettingsSynchronizer,
NotificationIndicator, NotificationIndicator,
NewFolderAction NewFolderAction,
CouchDBPlugin
) { ) {
var bundleMap = { var bundleMap = {
LocalStorage: 'platform/persistence/local', LocalStorage: 'platform/persistence/local',
MyItems: 'platform/features/my-items', MyItems: 'platform/features/my-items',
CouchDB: 'platform/persistence/couch',
Elasticsearch: 'platform/persistence/elastic' Elasticsearch: 'platform/persistence/elastic'
}; };
@ -126,27 +127,7 @@ define([
plugins.Conductor = TimeConductorPlugin.default; plugins.Conductor = TimeConductorPlugin.default;
plugins.CouchDB = function (url) { plugins.CouchDB = CouchDBPlugin.default;
return function (openmct) {
if (url) {
var bundleName = "config/couch";
openmct.legacyRegistry.register(bundleName, {
"extensions": {
"constants": [
{
"key": "COUCHDB_PATH",
"value": url,
"priority": "mandatory"
}
]
}
});
openmct.legacyRegistry.enable(bundleName);
}
openmct.legacyRegistry.enable(bundleMap.CouchDB);
};
};
plugins.Elasticsearch = function (url) { plugins.Elasticsearch = function (url) {
return function (openmct) { return function (openmct) {

View File

@ -27,6 +27,7 @@ define([
'./collections/FilteredTableRowCollection', './collections/FilteredTableRowCollection',
'./TelemetryTableRow', './TelemetryTableRow',
'./TelemetryTableColumn', './TelemetryTableColumn',
'./TelemetryTableUnitColumn',
'./TelemetryTableConfiguration' './TelemetryTableConfiguration'
], function ( ], function (
EventEmitter, EventEmitter,
@ -35,6 +36,7 @@ define([
FilteredTableRowCollection, FilteredTableRowCollection,
TelemetryTableRow, TelemetryTableRow,
TelemetryTableColumn, TelemetryTableColumn,
TelemetryTableUnitColumn,
TelemetryTableConfiguration TelemetryTableConfiguration
) { ) {
class TelemetryTable extends EventEmitter { class TelemetryTable extends EventEmitter {
@ -210,10 +212,14 @@ define([
addColumnsForObject(telemetryObject) { addColumnsForObject(telemetryObject) {
let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values(); let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values();
metadataValues.forEach(metadatum => { metadataValues.forEach(metadatum => {
let column = this.createColumn(metadatum); let column = this.createColumn(metadatum);
this.configuration.addSingleColumnForObject(telemetryObject, column); this.configuration.addSingleColumnForObject(telemetryObject, column);
// add units column if available
if (metadatum.unit !== undefined) {
let unitColumn = this.createUnitColumn(metadatum);
this.configuration.addSingleColumnForObject(telemetryObject, unitColumn);
}
}); });
} }
@ -221,6 +227,10 @@ define([
return new TelemetryTableColumn(this.openmct, metadatum); return new TelemetryTableColumn(this.openmct, metadatum);
} }
createUnitColumn(metadatum) {
return new TelemetryTableUnitColumn(this.openmct, metadatum);
}
subscribeTo(telemetryObject) { subscribeTo(telemetryObject) {
let subscribeOptions = this.buildOptionsFromConfiguration(telemetryObject); let subscribeOptions = this.buildOptionsFromConfiguration(telemetryObject);
let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier); let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);

View File

@ -70,8 +70,10 @@ define([], function () {
getCellLimitClasses() { getCellLimitClasses() {
if (!this.cellLimitClasses) { if (!this.cellLimitClasses) {
this.cellLimitClasses = Object.values(this.columns).reduce((alarmStateMap, column) => { this.cellLimitClasses = Object.values(this.columns).reduce((alarmStateMap, column) => {
let limitEvaluation = this.limitEvaluator.evaluate(this.datum, column.getMetadatum()); if (!column.isUnit) {
alarmStateMap[column.getKey()] = limitEvaluation && limitEvaluation.cssClass; let limitEvaluation = this.limitEvaluator.evaluate(this.datum, column.getMetadatum());
alarmStateMap[column.getKey()] = limitEvaluation && limitEvaluation.cssClass;
}
return alarmStateMap; return alarmStateMap;
}, {}); }, {});

View File

@ -0,0 +1,60 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, 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([
'./TelemetryTableColumn.js'
], function (
TelemetryTableColumn
) {
class TelemetryTableUnitColumn extends TelemetryTableColumn {
constructor(openmct, metadatum) {
super(openmct, metadatum);
this.isUnit = true;
this.titleValue += ' Unit';
this.formatter = {
format: (datum) => {
return this.metadatum.unit;
},
parse: (datum) => {
return this.metadatum.unit;
}
};
}
getKey() {
return this.metadatum.key + '-unit';
}
getTitle() {
return this.metadatum.name + ' Unit';
}
getRawValue(telemetryDatum) {
return this.metadatum.unit;
}
getFormattedValue(telemetryDatum) {
return this.formatter.format(telemetryDatum);
}
}
return TelemetryTableUnitColumn;
});

View File

@ -69,6 +69,7 @@
<script> <script>
import TelemetryTableColumn from '../TelemetryTableColumn'; import TelemetryTableColumn from '../TelemetryTableColumn';
import TelemetryTableUnitColumn from '../TelemetryTableUnitColumn';
export default { export default {
inject: ['tableConfiguration', 'openmct'], inject: ['tableConfiguration', 'openmct'],
@ -131,10 +132,14 @@ export default {
}, },
addColumnsForObject(telemetryObject) { addColumnsForObject(telemetryObject) {
let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).values(); let metadataValues = this.openmct.telemetry.getMetadata(telemetryObject).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);
// if units are available, need to add columns to be hidden
if (metadatum.unit !== undefined) {
let unitColumn = new TelemetryTableUnitColumn(this.openmct, metadatum);
this.tableConfiguration.addSingleColumnForObject(telemetryObject, unitColumn);
}
}); });
}, },
toggleHeaderVisibility() { toggleHeaderVisibility() {

View File

@ -117,7 +117,7 @@
title="Deselect All" title="Deselect All"
@click="unmarkAllRows()" @click="unmarkAllRows()"
> >
<span class="c-button__label">Deselect All</span> <span class="c-button__label">{{ `Deselect ${marking.disableMultiSelect ? '' : 'All'}` }} </span>
</button> </button>
<slot name="buttons"></slot> <slot name="buttons"></slot>
@ -303,6 +303,7 @@ export default {
default() { default() {
return { return {
enable: false, enable: false,
disableMultiSelect: false,
useAlternateControlBar: false, useAlternateControlBar: false,
rowName: '', rowName: '',
rowNamePlural: "" rowNamePlural: ""
@ -787,6 +788,11 @@ export default {
this.$set(markedRow, 'marked', true); this.$set(markedRow, 'marked', true);
this.pause(); this.pause();
if (this.marking.disableMultiSelect) {
this.unmarkAllRows();
insertMethod = 'push';
}
this.markedRows[insertMethod](markedRow); this.markedRows[insertMethod](markedRow);
}, },
unmarkAllRows(skipUnpause) { unmarkAllRows(skipUnpause) {
@ -800,7 +806,7 @@ export default {
return; return;
} }
if (!this.markedRows.length) { if (!this.markedRows.length || this.marking.disableMultiSelect) {
this.markRow(rowIndex); this.markRow(rowIndex);
} else { } else {
if (this.markedRows.length > 1) { if (this.markedRows.length > 1) {

View File

@ -28,16 +28,27 @@ import {
resetApplicationState resetApplicationState
} from 'utils/testing'; } from 'utils/testing';
class MockDataTransfer {
constructor() {
this.data = {};
}
get types() {
return Object.keys(this.data);
}
setData(format, data) {
this.data[format] = data;
}
getData(format) {
return this.data[format];
}
}
describe("the plugin", () => { describe("the plugin", () => {
let openmct; let openmct;
let tablePlugin; let tablePlugin;
let element; let element;
let child; let child;
beforeAll(() => {
resetApplicationState();
});
beforeEach((done) => { beforeEach((done) => {
openmct = createOpenMct(); openmct = createOpenMct();
@ -67,7 +78,7 @@ describe("the plugin", () => {
}); });
afterEach(() => { afterEach(() => {
resetApplicationState(openmct); return resetApplicationState(openmct);
}); });
describe("defines a table object", function () { describe("defines a table object", function () {
@ -167,7 +178,7 @@ describe("the plugin", () => {
it("Renders a row for every telemetry datum returned", () => { 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');
expect(rows.length).toBe(3); expect(rows.length).toBe(1);
}); });
it("Renders a column for every item in telemetry metadata", () => { it("Renders a column for every item in telemetry metadata", () => {
@ -191,7 +202,7 @@ describe("the plugin", () => {
dragStartEvent.dataTransfer = dragStartEvent.dataTransfer =
dragOverEvent.dataTransfer = dragOverEvent.dataTransfer =
dropEvent.dataTransfer = new DataTransfer(); dropEvent.dataTransfer = new MockDataTransfer();
fromColumn.dispatchEvent(dragStartEvent); fromColumn.dispatchEvent(dragStartEvent);
toColumn.dispatchEvent(dragOverEvent); toColumn.dispatchEvent(dragOverEvent);

View File

@ -143,7 +143,7 @@
<ConductorHistory <ConductorHistory
v-if="isFixed" v-if="isFixed"
class="c-conductor__history-select" class="c-conductor__history-select"
:bounds="openmct.time.bounds()" :bounds="bounds"
:time-system="timeSystem" :time-system="timeSystem"
/> />
</div> </div>
@ -190,6 +190,10 @@ export default {
start: offsets && durationFormatter.format(Math.abs(offsets.start)), start: offsets && durationFormatter.format(Math.abs(offsets.start)),
end: offsets && durationFormatter.format(Math.abs(offsets.end)) end: offsets && durationFormatter.format(Math.abs(offsets.end))
}, },
bounds: {
start: bounds.start,
end: bounds.end
},
formattedBounds: { formattedBounds: {
start: timeFormatter.format(bounds.start), start: timeFormatter.format(bounds.start),
end: timeFormatter.format(bounds.end) end: timeFormatter.format(bounds.end)
@ -210,7 +214,7 @@ export default {
document.addEventListener('keydown', this.handleKeyDown); document.addEventListener('keydown', this.handleKeyDown);
document.addEventListener('keyup', this.handleKeyUp); document.addEventListener('keyup', this.handleKeyUp);
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.timeSystem()))); this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.timeSystem())));
this.openmct.time.on('bounds', this.setViewFromBounds); this.openmct.time.on('bounds', this.handleNewBounds);
this.openmct.time.on('timeSystem', this.setTimeSystem); this.openmct.time.on('timeSystem', this.setTimeSystem);
this.openmct.time.on('clock', this.setViewFromClock); this.openmct.time.on('clock', this.setViewFromClock);
this.openmct.time.on('clockOffsets', this.setViewFromOffsets); this.openmct.time.on('clockOffsets', this.setViewFromOffsets);
@ -220,6 +224,13 @@ export default {
document.removeEventListener('keyup', this.handleKeyUp); document.removeEventListener('keyup', this.handleKeyUp);
}, },
methods: { methods: {
handleNewBounds(bounds) {
this.setBounds(bounds);
this.setViewFromBounds(bounds);
},
setBounds(bounds) {
this.bounds = bounds;
},
handleKeyDown(event) { handleKeyDown(event) {
if (event.key === 'Alt') { if (event.key === 'Alt') {
this.altPressed = true; this.altPressed = true;
@ -246,10 +257,13 @@ export default {
this.formattedBounds.end = this.timeFormatter.format(bounds.end); this.formattedBounds.end = this.timeFormatter.format(bounds.end);
}, },
endZoom(bounds) { endZoom(bounds) {
const _bounds = bounds ? bounds : this.openmct.time.bounds();
this.isZooming = false; this.isZooming = false;
this.openmct.time.bounds(_bounds); if (bounds) {
this.handleNewBounds(bounds);
} else {
this.setViewFromBounds(this.bounds);
}
}, },
setTimeSystem(timeSystem) { setTimeSystem(timeSystem) {
this.timeSystem = timeSystem; this.timeSystem = timeSystem;

View File

@ -207,7 +207,7 @@ export default {
this.$emit('panAxis', panBounds); this.$emit('panAxis', panBounds);
}, },
endPan() { endPan() {
const panBounds = this.dragStartX && this.dragX && this.dragStartX !== this.dragX const panBounds = this.isChangingViewBounds()
? this.getPanBounds() ? this.getPanBounds()
: undefined; : undefined;
this.$emit('endPan', panBounds); this.$emit('endPan', panBounds);
@ -251,16 +251,14 @@ export default {
}); });
}, },
endZoom() { endZoom() {
const zoomRange = this.dragStartX && this.dragX && this.dragStartX !== this.dragX let zoomBounds;
? this.getZoomRange() if (this.isChangingViewBounds()) {
: undefined; const zoomRange = this.getZoomRange();
zoomBounds = {
const zoomBounds = zoomRange
? {
start: this.scaleToBounds(zoomRange.start), start: this.scaleToBounds(zoomRange.start),
end: this.scaleToBounds(zoomRange.end) end: this.scaleToBounds(zoomRange.end)
} };
: this.openmct.time.bounds(); }
this.zoomStyle = {}; this.zoomStyle = {};
this.$emit('endZoom', zoomBounds); this.$emit('endZoom', zoomBounds);
@ -290,6 +288,9 @@ export default {
return bounds.start + offset; return bounds.start + offset;
}, },
isChangingViewBounds() {
return this.dragStartX && this.dragX && this.dragStartX !== this.dragX;
},
resize() { resize() {
if (this.$refs.axisHolder.clientWidth !== this.width) { if (this.$refs.axisHolder.clientWidth !== this.width) {
this.setAxisDimensions(); this.setAxisDimensions();

View File

@ -84,7 +84,12 @@ export default {
}, },
data() { data() {
return { return {
history: {}, // contains arrays of timespans {start, end}, array key is time system key /**
* previous bounds entries available for easy re-use
* @history array of timespans
* @timespans {start, end} number representing timestamp
*/
history: this.getHistoryFromLocalStorage(),
presets: [] presets: []
}; };
}, },
@ -111,22 +116,20 @@ export default {
this.addTimespan(); this.addTimespan();
}, },
deep: true deep: true
},
history: {
handler() {
this.persistHistoryToLocalStorage();
},
deep: true
} }
}, },
mounted() { mounted() {
this.getHistoryFromLocalStorage(); this.initializeHistoryIfNoHistory();
}, },
methods: { methods: {
getHistoryFromLocalStorage() { getHistoryFromLocalStorage() {
if (localStorage.getItem(LOCAL_STORAGE_HISTORY_KEY)) { const localStorageHistory = localStorage.getItem(LOCAL_STORAGE_HISTORY_KEY);
this.history = JSON.parse(localStorage.getItem(LOCAL_STORAGE_HISTORY_KEY)); const history = localStorageHistory ? JSON.parse(localStorageHistory) : undefined;
} else {
return history;
},
initializeHistoryIfNoHistory() {
if (!this.history) {
this.history = {}; this.history = {};
this.persistHistoryToLocalStorage(); this.persistHistoryToLocalStorage();
} }
@ -157,6 +160,8 @@ export default {
currentHistory.unshift(timespan); currentHistory.unshift(timespan);
this.history[key] = currentHistory; this.history[key] = currentHistory;
this.persistHistoryToLocalStorage();
}, },
selectTimespan(timespan) { selectTimespan(timespan) {
this.openmct.time.bounds(timespan); this.openmct.time.bounds(timespan);

View File

@ -81,7 +81,12 @@ export default {
this.isLoading = true; this.isLoading = true;
this.openmct.objects.get('ROOT') this.openmct.objects.get('ROOT')
.then(root => { .then(root => {
return this.openmct.composition.get(root).load(); let composition = this.openmct.composition.get(root);
if (composition !== undefined) {
return composition.load();
} else {
return [];
}
}) })
.then(children => { .then(children => {
this.isLoading = false; this.isLoading = false;

View File

@ -45,6 +45,7 @@ class ApplicationRouter extends EventEmitter {
super(); super();
this.routes = []; this.routes = [];
this.started = false; this.started = false;
this.locationBar = new LocationBar();
} }
/** /**
@ -56,13 +57,17 @@ class ApplicationRouter extends EventEmitter {
} }
this.started = true; this.started = true;
let locationBar = new LocationBar();
locationBar.onChange(p => this.handleLocationChange(p)); this.locationBar.onChange(p => this.handleLocationChange(p));
locationBar.start({ this.locationBar.start({
root: location.pathname root: location.pathname
}); });
} }
destroy() {
this.locationBar.stop();
}
handleLocationChange(pathString) { handleLocationChange(pathString) {
if (pathString[0] !== '/') { if (pathString[0] !== '/') {
pathString = '/' + pathString; pathString = '/' + pathString;

View File

@ -135,7 +135,6 @@ export default {
value = this.getFormValue(domainObject, toolbarItem); value = this.getFormValue(domainObject, toolbarItem);
} else { } else {
let values = []; let values = [];
if (applicableSelectedItems) { if (applicableSelectedItems) {
applicableSelectedItems.forEach(selectionPath => { applicableSelectedItems.forEach(selectionPath => {
values.push(this.getPropertyValue(domainObject, toolbarItem, selectionPath)); values.push(this.getPropertyValue(domainObject, toolbarItem, selectionPath));
@ -207,7 +206,6 @@ export default {
}, },
updateObjectValue(value, item) { updateObjectValue(value, item) {
let changedItemId = this.openmct.objects.makeKeyString(item.domainObject.identifier); let changedItemId = this.openmct.objects.makeKeyString(item.domainObject.identifier);
this.structure = this.structure.map(toolbarItem => { this.structure = this.structure.map(toolbarItem => {
if (toolbarItem.domainObject) { if (toolbarItem.domainObject) {
let id = this.openmct.objects.makeKeyString(toolbarItem.domainObject.identifier); let id = this.openmct.objects.makeKeyString(toolbarItem.domainObject.identifier);

View File

@ -32,7 +32,6 @@ import {
import {resetApplicationState} from 'utils/testing'; import {resetApplicationState} from 'utils/testing';
describe('the openmct location utility functions', () => { describe('the openmct location utility functions', () => {
beforeAll(() => resetApplicationState());
afterEach(() => resetApplicationState()); afterEach(() => resetApplicationState());
it('The setSearchParam function sets an individual search parameters in the window location hash', () => { it('The setSearchParam function sets an individual search parameters in the window location hash', () => {

View File

@ -65,12 +65,29 @@ export function clearBuiltinSpies() {
} }
export function resetApplicationState(openmct) { export function resetApplicationState(openmct) {
let promise;
clearBuiltinSpies(); clearBuiltinSpies();
window.location.hash = '#';
if (openmct !== undefined) { if (openmct !== undefined) {
openmct.destroy(); openmct.destroy();
} }
if (window.location.hash !== '#' && window.location.hash !== '') {
promise = new Promise((resolve, reject) => {
window.addEventListener('hashchange', cleanup);
window.location.hash = '#';
function cleanup() {
window.removeEventListener('hashchange', cleanup);
resolve();
}
});
} else {
promise = Promise.resolve();
}
return promise;
} }
function clearBuiltinSpy(funcDefinition) { function clearBuiltinSpy(funcDefinition) {