Compare commits

..

95 Commits

Author SHA1 Message Date
a1d1301542 added unsubscribe, subscribeTo methods 2020-05-26 10:35:41 -07:00
aef8589872 Merge branch 'master' of https://github.com/nasa/openmct into joel/plot-test 2020-05-22 13:35:58 -07:00
38f3e60884 added dynamic layout for fixed, local clock 2020-05-22 13:35:47 -07:00
331c086a60 implemented Plotly.relayout to set x-axis correctly for local clock 2020-05-21 16:45:21 -07:00
0890ebfd0f added back if in updateData 2020-05-21 12:57:14 -07:00
1547ea5cf7 small changes 2020-05-21 12:29:04 -07:00
b1551478e4 refactor of refreshData 2020-05-21 12:03:53 -07:00
213c469758 debugging local clock issues 2020-05-21 11:44:32 -07:00
03ac43d306 added some comments 2020-05-20 18:17:29 -07:00
f3de6f2548 Merge branch 'joel/plot-test' of https://github.com/nasa/openmct into joel/plot-test 2020-05-20 15:48:44 -07:00
62dfb7d8f9 added requestHistory 2020-05-20 15:48:01 -07:00
0a33cbeea2 resolve merge conflict 2020-05-20 09:32:59 -07:00
4908d5afb0 Merge branch 'lad-testing' of https://github.com/nasa/openmct into lad-testing
merging in master I believe.
2020-05-19 12:42:55 -07:00
aa4bf5eaf6 updates from PR Review, making new lad set tests pending for the time being #3044 2020-05-19 12:39:31 -07:00
4eb4cbfffc Merge pull request #3020 from nasa/updated-checklists
Add details about pull requests to contributing guide
2020-05-19 09:30:05 -07:00
eda01abcbc Merge branch 'master' into updated-checklists 2020-05-18 12:12:26 -07:00
694b8f4666 provide format for name metadata (#3054) 2020-05-18 11:40:05 -07:00
4e4031e700 added listeners and bounds methods 2020-05-18 11:37:24 -07:00
46a20bb76d Merge branch 'master' into lad-testing 2020-05-18 11:03:49 -07:00
1d482f318e working on adding render tests for lad table sets 2020-05-15 15:34:03 -07:00
6826b579a6 WIP... 2020-05-15 15:32:53 -07:00
bbb271a678 clarify value hints (#2673)
remove confusing comments regarding domain - input and range - output
2020-05-15 14:48:25 -07:00
fec1438806 dont emit a viewport change event when marquee doesnt happen (#3036)
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2020-05-15 14:35:13 -07:00
28f19ec310 [Notebook] Clicking on an embed in a notebook entry does not result in bounds change #3034 (#3042) 2020-05-14 14:56:41 -07:00
3d4c721232 removing fdescribe 2020-05-14 13:30:13 -07:00
190d34c939 added composition tests for lad table set as well 2020-05-14 13:22:12 -07:00
47bc0e9793 pushing final version of tests for PR 2020-05-14 10:38:38 -07:00
35115aaa50 WIP: adding bounds functions 2020-05-13 16:32:03 -07:00
f934454c25 Fixes computation of result when telemetry is not used by an object (#3040)
* Resolves issue #30307
Don't compute the result of a condition if telemetry datum is not being used by that condition

* Adds tests for condition results
2020-05-13 14:30:45 -07:00
b252051c83 add, remove multiple traces, remove hover effects 2020-05-13 11:20:35 -07:00
4ca1181eb6 Merge branch 'lad-bounds-listener' into lad-testing
mergin in other lad changes.
2020-05-13 09:32:31 -07:00
eb49ffae02 Update CONTRIBUTING.md 2020-05-13 09:21:00 -07:00
5751012872 Update CONTRIBUTING.md 2020-05-13 09:20:03 -07:00
f973f42729 random error fixed for testing, was a system setup issue 2020-05-12 15:05:29 -07:00
4788561631 WIP 2020-05-12 15:04:05 -07:00
e8699d54d5 issue with javascript heap error for tests 2020-05-12 14:19:06 -07:00
14bc49451b removed "existingTimestamp" variable, it is now stored in instance "timestamp" variable 2020-05-12 13:12:53 -07:00
dd2e8c0460 removing console logs for the moment 2020-05-12 11:58:01 -07:00
031753a9a8 still some pending, but all these are successful 2020-05-12 10:23:41 -07:00
8edae5f02c Merge branch 'master' into lad-testing
Merging master
.
2020-05-12 10:09:15 -07:00
7ed3de01b9 commit before merge master 2020-05-12 10:08:01 -07:00
aa041e04cf Merge remote-tracking branch 'origin/update-contributing-guidelines' into updated-checklists 2020-05-11 17:13:53 -07:00
24e7ea143a Added more details on the process around pull requests 2020-05-11 17:12:25 -07:00
79d5d9c4d0 Update CONTRIBUTING.md 2020-05-11 16:15:17 -07:00
b5bfdc4418 Update CONTRIBUTING.md 2020-05-11 15:56:49 -07:00
59730c60ec Update CONTRIBUTING.md 2020-05-11 15:54:25 -07:00
4a87a5d847 Show object styles in preview modal (#3018)
* Adds conditional styles to Preview window
2020-05-11 14:25:39 -07:00
4a1931f594 Merge branch 'lad-bounds-listener' into lad-testing
Merging lad bounds listener updates in for lad testing..
2020-05-11 11:20:31 -07:00
f174dcc2f6 Merge branch 'ladtable-composition-policy' into lad-testing
Merging in new ES6 module style for lad plugin..
2020-05-11 11:18:37 -07:00
0c741c67f8 import RemoveAction 2020-05-11 09:59:41 -07:00
85da5e43b3 moved formatedTimestamp to a computed value to utilize caching 2020-05-08 16:24:58 -07:00
d26e45f636 add code to set Y-axis and plot multiple telemetry types 2020-05-08 13:50:56 -07:00
0bd6814097 fixed case sensitive file name error 2020-05-08 12:13:28 -07:00
4e7410b4bf trying to fix circle ci missing LADTable.vue error 2020-05-08 12:09:04 -07:00
436550adba converted LADTable to es6 module 2020-05-08 11:52:37 -07:00
421c09ec2c Allow users to lazy load Tabs (#2958)
* lazy load tabs

* remove listener on destroy

* fix lint error

* Store current tab position on domainObject

* remove lodash dependency and use keystring
2020-05-08 10:36:13 -07:00
f6b1be0486 Merge branch 'ladtable-composition-policy' of https://github.com/nasa/openmct into ladtable-composition-policy
forgot to pull first
.
2020-05-07 18:35:37 -07:00
eca12201c7 switching over toe es6 module 2020-05-07 18:35:25 -07:00
68ff69664b added a phew more tests 2020-05-07 16:23:37 -07:00
0679b246b8 [Notebook]: Remove default section/page from localstorage on notebook delete (#2900)
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2020-05-07 16:13:32 -07:00
51fbb9cee6 Merge branch 'master' into joel/plot-test 2020-05-07 14:55:07 -07:00
437156e04c changed mode and type, dropped old data as data extends 2020-05-07 14:45:34 -07:00
d244ee622a rendering a plotly plot from SWG 2020-05-07 13:00:54 -07:00
b30018c315 added mock objects and mock telemetry to testTools 2020-05-06 13:48:17 -07:00
eb5eb7d540 Un-focused test 2020-05-06 10:55:06 -07:00
609a47c62c Fixed failing test 2020-05-06 10:36:15 -07:00
83f9c6c528 improve plot gestures - and clean up (#3013) 2020-05-06 10:33:59 -07:00
a5f3ba6259 Use evalAsync instead of digest() (#3001)
Co-authored-by: Deep Tailor <deep.j.tailor@nasa.gov>
2020-05-05 12:23:41 -07:00
a70facf0c8 Merge pull request #3000 from nasa/upgrade-moment
Upgrade moment to 2.25.3
2020-05-05 11:01:39 -07:00
447fe94325 Merge branch 'upgrade-moment' of https://github.com/nasa/openmct into upgrade-moment 2020-05-05 10:36:43 -07:00
8e2b666766 Upgraded moment version to 2.25.3 2020-05-05 10:36:33 -07:00
dcbfbdbb89 Merge branch 'master' into upgrade-moment 2020-05-05 10:12:50 -07:00
4c76bf34ab Highlight currently winning Condition in Condition Set Edit and Read-only views (#2936)
* Preview condition styles on selecting that condition or one of it's styles

Co-authored-by: charlesh88 <charlesh88@gmail.com>
2020-05-05 09:55:42 -07:00
688a03c8ac added composition, working on table views 2020-05-04 11:09:19 -07:00
81b7a9d3e0 Merge branch 'master' into upgrade-moment 2020-05-01 16:47:26 -07:00
dc573c479c Upgrade moment to 2.24.0 2020-05-01 16:25:49 -07:00
4f12f41685 added composition policy testing 2020-05-01 15:20:08 -07:00
189882afc8 first "it" in, working on the next 2020-04-30 10:36:44 -07:00
2b3541a323 Merge branch 'master' into lad-bounds-listener 2020-04-29 12:43:20 -07:00
7c9a140481 plugin spec for lad tables 2020-04-29 11:27:56 -07:00
5e9b313ee9 Merge branch 'ladtable-composition-policy' into lad-testing
merging lad branches for unit testing
.
2020-04-29 10:46:06 -07:00
c64db6c07d Revert "Merge branch 'lad-conductor-response' into lad-testing"
This reverts commit 9e2751acf7, reversing
changes made to 51fb72dc04.

not what I wanted apparently#
2020-04-29 10:45:21 -07:00
9e2751acf7 Merge branch 'lad-conductor-response' into lad-testing
Merging all lad branches for lad unit testing
.
2020-04-29 10:20:34 -07:00
51fb72dc04 updates from PR review 2020-04-28 17:02:04 -07:00
d51052ab46 Merge branch 'master' into ladtable-composition-policy 2020-04-28 16:44:21 -07:00
f62010fb99 added parsing for timestamps, removed currentData variable as it was not needed, moved tick check to bounds listener 2020-04-24 12:48:34 -07:00
ba4ef43673 linting and removing console logs 2020-04-23 15:50:49 -07:00
f6c16b7483 added a check for lad table sets, child can only be lad table 2020-04-23 15:17:07 -07:00
58e63e649f added LAD Table composition policy 2020-04-23 13:33:32 -07:00
7a04aea90e removing console log 2020-04-17 16:45:24 -07:00
0bb475327c added matadatum with key === name to be ignored as well when selecting value to show 2020-04-17 16:44:18 -07:00
c474b998f0 added a backup if the object does not have a range hint (ex. images) 2020-04-17 16:22:03 -07:00
a02d421093 added bounds listener, moved history request to function, checking for race conditions 2020-04-16 12:19:20 -07:00
9026099fd2 added bounds listener, moved history request to function, checking for race conditions 2020-04-16 11:51:48 -07:00
295ccea195 cleaned up subscription and bounds listener, added timesystem listener 2020-04-14 12:33:34 -07:00
33 changed files with 1214 additions and 344 deletions

4
API.md
View File

@ -427,8 +427,8 @@ Each telemetry value description has an object defining hints. Keys in this thi
Known hints: Known hints:
* `domain`: Indicates that the value represents the "input" of a datum. Values with a `domain` hint will be used for the x-axis of a plot, and tables will render columns for these values first. * `domain`: Values with a `domain` hint will be used for the x-axis of a plot, and tables will render columns for these values first.
* `range`: Indicates that the value is the "output" of a datum. Values with a `range` hint will be used as the y-axis on a plot, and tables will render columns for these values after the `domain` values. * `range`: Values with a `range` hint will be used as the y-axis on a plot, and tables will render columns for these values after the `domain` values.
* `image`: Indicates that the value may be interpreted as the URL to an image file, in which case appropriate views will be made available. * `image`: Indicates that the value may be interpreted as the URL to an image file, in which case appropriate views will be made available.
##### The Time Conductor and Telemetry ##### The Time Conductor and Telemetry

View File

@ -103,7 +103,7 @@ the name chosen could not be mistaken for a topic or master branch.
### Merging ### Merging
When development is complete on an issue, the first step toward merging it When development is complete on an issue, the first step toward merging it
back into the master branch is to file a Pull Request. The contributions back into the master branch is to file a Pull Request (PR). The contributions
should meet code, test, and commit message standards as described below, should meet code, test, and commit message standards as described below,
and the pull request should include a completed author checklist, also and the pull request should include a completed author checklist, also
as described below. Pull requests may be assigned to specific team as described below. Pull requests may be assigned to specific team
@ -114,6 +114,15 @@ request. When the reviewer is satisfied, they should add a comment to
the pull request containing the reviewer checklist (from below) and complete the pull request containing the reviewer checklist (from below) and complete
the merge back to the master branch. the merge back to the master branch.
Additionally:
* Every pull request must link to the issue that it addresses. Eg. “Addresses #1234” or “Closes #1234”. This is the responsibility of the pull requests __author__. If no issue exists, create one.
* Every __author__ must include testing instructions. These instructions should identify the areas of code affected, and some minimal test steps. If addressing a bug, reproduction steps should be included, if they were not included in the original issue. If reproduction steps were included on the original issue, and are sufficient, refer to them.
* A pull request that closes an issue should say so in the description. Including the text “Closes #1234” will cause the linked issue to be automatically closed when the pull request is merged. This is the responsibility of the pull requests __author__.
* When a pull request is merged, and the corresponding issue closed, the __reviewer__ must add the tag “unverified” to the original issue. This will indicate that although the issue is closed, it has not been tested yet.
* Every PR must have two reviewers assigned, though only one approval is necessary for merge.
* Changes to API require approval by a senior developer.
* When creating a PR, it is the author's responsibility to apply any priority label from the issue to the PR as well. This helps with prioritization.
## Standards ## Standards
Contributions to Open MCT are expected to meet the following standards. Contributions to Open MCT are expected to meet the following standards.
@ -292,6 +301,7 @@ checklist).
2. Unit tests included and/or updated with changes? 2. Unit tests included and/or updated with changes?
3. Command line build passes? 3. Command line build passes?
4. Changes have been smoke-tested? 4. Changes have been smoke-tested?
5. Testing instructions included?
### Reviewer Checklist ### Reviewer Checklist
@ -299,3 +309,4 @@ checklist).
2. Appropriate unit tests included? 2. Appropriate unit tests included?
3. Code style and in-line documentation are appropriate? 3. Code style and in-line documentation are appropriate?
4. Commit messages meet standards? 4. Commit messages meet standards?
5. Has associated issue been labelled `unverified`? (only applicable if this PR closes the issue)

View File

@ -50,7 +50,8 @@ define([
values: [ values: [
{ {
key: "name", key: "name",
name: "Name" name: "Name",
format: "string"
}, },
{ {
key: "utc", key: "utc",

View File

@ -55,9 +55,9 @@
"marked": "^0.3.5", "marked": "^0.3.5",
"mini-css-extract-plugin": "^0.4.1", "mini-css-extract-plugin": "^0.4.1",
"minimist": "^1.1.1", "minimist": "^1.1.1",
"moment": "^2.25.3", "moment": "2.25.3",
"moment-duration-format": "^2.2.2", "moment-duration-format": "^2.2.2",
"moment-timezone": "^0.5.21", "moment-timezone": "0.5.28",
"node-bourbon": "^4.2.3", "node-bourbon": "^4.2.3",
"node-sass": "^4.9.2", "node-sass": "^4.9.2",
"painterro": "^0.2.65", "painterro": "^0.2.65",

View File

@ -0,0 +1,39 @@
/*****************************************************************************
* 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.
*****************************************************************************/
export default class LADTableCompositionPolicy {
constructor(openmct) {
this.openmct = openmct;
return this.allow.bind(this);
}
allow(parent, child) {
if(parent.type === 'LadTable') {
return this.openmct.telemetry.isTelemetryObject(child);
} else if(parent.type === 'LadTableSet') {
return child.type === 'LadTable';
}
return true;
}
}

View File

@ -19,53 +19,46 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
import LadTableSet from './components/LadTableSet.vue';
import Vue from 'vue';
define([ export default function LADTableSetViewProvider(openmct) {
'./components/LadTableSet.vue', return {
'vue' key: 'LadTableSet',
], function ( name: 'LAD Table Set',
LadTableSet, cssClass: 'icon-tabular-lad-set',
Vue canView: function (domainObject) {
) { return domainObject.type === 'LadTableSet';
function LADTableSetViewProvider(openmct) { },
return { canEdit: function (domainObject) {
key: 'LadTableSet', return domainObject.type === 'LadTableSet';
name: 'LAD Table Set', },
cssClass: 'icon-tabular-lad-set', view: function (domainObject, objectPath) {
canView: function (domainObject) { let component;
return domainObject.type === 'LadTableSet';
},
canEdit: function (domainObject) {
return domainObject.type === 'LadTableSet';
},
view: function (domainObject, objectPath) {
let component;
return { return {
show: function (element) { show: function (element) {
component = new Vue({ component = new Vue({
el: element, el: element,
components: { components: {
LadTableSet: LadTableSet.default LadTableSet: LadTableSet
}, },
provide: { provide: {
openmct, openmct,
domainObject, domainObject,
objectPath objectPath
}, },
template: '<lad-table-set></lad-table-set>' template: '<lad-table-set></lad-table-set>'
}); });
}, },
destroy: function (element) { destroy: function (element) {
component.$destroy(); component.$destroy();
component = undefined; component = undefined;
} }
}; };
}, },
priority: function () { priority: function () {
return 1; return 1;
} }
}; };
} }
return LADTableSetViewProvider;
});

View File

@ -19,53 +19,46 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
import LadTable from './components/LADTable.vue';
import Vue from 'vue';
define([ export default function LADTableViewProvider(openmct) {
'./components/LADTable.vue', return {
'vue' key: 'LadTable',
], function ( name: 'LAD Table',
LadTableComponent, cssClass: 'icon-tabular-lad',
Vue canView: function (domainObject) {
) { return domainObject.type === 'LadTable';
function LADTableViewProvider(openmct) { },
return { canEdit: function (domainObject) {
key: 'LadTable', return domainObject.type === 'LadTable';
name: 'LAD Table', },
cssClass: 'icon-tabular-lad', view: function (domainObject, objectPath) {
canView: function (domainObject) { let component;
return domainObject.type === 'LadTable';
},
canEdit: function (domainObject) {
return domainObject.type === 'LadTable';
},
view: function (domainObject, objectPath) {
let component;
return { return {
show: function (element) { show: function (element) {
component = new Vue({ component = new Vue({
el: element, el: element,
components: { components: {
LadTableComponent: LadTableComponent.default LadTableComponent: LadTable
}, },
provide: { provide: {
openmct, openmct,
domainObject, domainObject,
objectPath objectPath
}, },
template: '<lad-table-component></lad-table-component>' template: '<lad-table-component></lad-table-component>'
}); });
}, },
destroy: function (element) { destroy: function (element) {
component.$destroy(); component.$destroy();
component = undefined; component = undefined;
} }
}; };
}, },
priority: function () { priority: function () {
return 1; return 1;
} }
}; };
} }
return LADTableViewProvider;
});

View File

@ -1,6 +1,6 @@
/***************************************************************************** /*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government * Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space * as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved. * Administration. All rights reserved.
* *
@ -24,10 +24,8 @@
<template> <template>
<tr @contextmenu.prevent="showContextMenu"> <tr @contextmenu.prevent="showContextMenu">
<td>{{ name }}</td> <td>{{ name }}</td>
<td>{{ timestamp }}</td> <td>{{ formattedTimestamp }}</td>
<td :class="valueClass"> <td :class="valueClass">{{ value }}</td>
{{ value }}
</td>
</tr> </tr>
</template> </template>
@ -58,10 +56,16 @@ export default {
currentObjectPath currentObjectPath
} }
}, },
computed: {
formattedTimestamp() {
return this.timestamp !== '---' ? this.formats[this.timestampKey].format(this.timestamp) : this.timestamp;
}
},
mounted() { mounted() {
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject); this.metadata = this.openmct.telemetry.getMetadata(this.domainObject);
this.formats = this.openmct.telemetry.getFormatMap(this.metadata); this.formats = this.openmct.telemetry.getFormatMap(this.metadata);
this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier); this.keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);
this.bounds = this.openmct.time.bounds();
this.limitEvaluator = this.openmct this.limitEvaluator = this.openmct
.telemetry .telemetry
@ -76,6 +80,7 @@ export default {
); );
this.openmct.time.on('timeSystem', this.updateTimeSystem); this.openmct.time.on('timeSystem', this.updateTimeSystem);
this.openmct.time.on('bounds', this.updateBounds);
this.timestampKey = this.openmct.time.timeSystem().key; this.timestampKey = this.openmct.time.timeSystem().key;
@ -89,43 +94,64 @@ export default {
.telemetry .telemetry
.subscribe(this.domainObject, this.updateValues); .subscribe(this.domainObject, this.updateValues);
this.openmct this.requestHistory();
.telemetry
.request(this.domainObject, {strategy: 'latest'})
.then((array) => this.updateValues(array[array.length - 1]));
}, },
destroyed() { destroyed() {
this.stopWatchingMutation(); this.stopWatchingMutation();
this.unsubscribe(); this.unsubscribe();
this.openmct.off('timeSystem', this.updateTimeSystem); this.openmct.time.off('timeSystem', this.updateTimeSystem);
this.openmct.time.off('bounds', this.updateBounds);
}, },
methods: { methods: {
updateValues(datum) { updateValues(datum) {
this.timestamp = this.formats[this.timestampKey].format(datum); let newTimestamp = this.formats[this.timestampKey].parse(datum),
this.value = this.formats[this.valueKey].format(datum); shouldUpdate = this.timestamp === '---' || newTimestamp >= this.timestamp,
limit;
var limit = this.limitEvaluator.evaluate(datum, this.valueMetadata); if(!this.inBounds(newTimestamp)) {
return;
if (limit) {
this.valueClass = limit.cssClass;
} else {
this.valueClass = '';
} }
if(shouldUpdate) {
this.timestamp = this.formats[this.timestampKey].parse(datum);
this.value = this.formats[this.valueKey].format(datum);
limit = this.limitEvaluator.evaluate(datum, this.valueMetadata);
if (limit) {
this.valueClass = limit.cssClass;
} else {
this.valueClass = '';
}
}
},
requestHistory() {
this.timestamp = '---';
this.openmct
.telemetry
.request(this.domainObject, {
start: this.bounds.start,
end: this.bounds.end,
strategy: 'latest'
})
.then((data) => this.updateValues(data[data.length - 1]));
}, },
updateName(name) { updateName(name) {
this.name = name; this.name = name;
}, },
updateBounds(bounds, isTick) {
this.bounds = bounds;
if(!isTick) {
this.requestHistory();
}
},
inBounds(timestamp) {
return timestamp >= this.bounds.start && timestamp <= this.bounds.end;
},
updateTimeSystem(timeSystem) { updateTimeSystem(timeSystem) {
this.value = '---'; this.value = '---';
this.timestamp = '---'; this.timestamp = '---';
this.valueClass = ''; this.valueClass = '';
this.timestampKey = timeSystem.key; this.timestampKey = timeSystem.key;
this.openmct
.telemetry
.request(this.domainObject, {strategy: 'latest'})
.then((array) => this.updateValues(array[array.length - 1]));
}, },
showContextMenu(event) { showContextMenu(event) {
this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS); this.openmct.contextMenu._showContextMenuForObjectPath(this.currentObjectPath, event.x, event.y, CONTEXT_MENU_ACTIONS);

View File

@ -88,4 +88,3 @@ export default {
} }
} }
</script> </script>

View File

@ -19,38 +19,36 @@
* this source code distribution or the Licensing information page available * this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information. * at runtime from the About dialog for additional information.
*****************************************************************************/ *****************************************************************************/
import LADTableViewProvider from './LADTableViewProvider';
import LADTableSetViewProvider from './LADTableSetViewProvider';
import LADTableCompositionPolicy from './LADTableCompositionPolicy';
define([ export default function plugin() {
'./LADTableViewProvider', return function install(openmct) {
'./LADTableSetViewProvider'
], function (
LADTableViewProvider,
LADTableSetViewProvider
) {
return function plugin() {
return function install(openmct) {
openmct.objectViews.addProvider(new LADTableViewProvider(openmct));
openmct.objectViews.addProvider(new LADTableSetViewProvider(openmct));
openmct.types.addType('LadTable', { openmct.objectViews.addProvider(new LADTableViewProvider(openmct));
name: "LAD Table", openmct.objectViews.addProvider(new LADTableSetViewProvider(openmct));
creatable: true,
description: "A Latest Available Data tabular view in which each row displays the values for one or more contained telemetry objects.",
cssClass: 'icon-tabular-lad',
initialize(domainObject) {
domainObject.composition = [];
}
});
openmct.types.addType('LadTableSet', { openmct.types.addType('LadTable', {
name: "LAD Table Set", name: "LAD Table",
creatable: true, creatable: true,
description: "A Latest Available Data tabular view in which each row displays the values for one or more contained telemetry objects.", description: "A Latest Available Data tabular view in which each row displays the values for one or more contained telemetry objects.",
cssClass: 'icon-tabular-lad-set', cssClass: 'icon-tabular-lad',
initialize(domainObject) { initialize(domainObject) {
domainObject.composition = []; domainObject.composition = [];
} }
}); });
};
openmct.types.addType('LadTableSet', {
name: "LAD Table Set",
creatable: true,
description: "A Latest Available Data tabular view in which each row displays the values for one or more contained telemetry objects.",
cssClass: 'icon-tabular-lad-set',
initialize(domainObject) {
domainObject.composition = [];
}
});
openmct.composition.addPolicy(new LADTableCompositionPolicy(openmct));
}; };
}); }

View File

@ -0,0 +1,356 @@
/*****************************************************************************
* 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.
*****************************************************************************/
import LadPlugin from './plugin.js';
import Vue from 'vue';
import {
createOpenMct,
getMockObjects,
getMockTelemetry,
getLatestTelemetry
} from 'testTools';
let openmct,
ladPlugin,
parent,
child;
let selectors = {};
selectors.ladTableClass = '.c-table.c-lad-table';
selectors.ladTableBodyRows = selectors.ladTableClass + ' tbody tr';
selectors.ladTableBodyRowsFirstData = selectors.ladTableBodyRows + ' td:first-child';
selectors.ladTableBodyRowsSecondtData = selectors.ladTableBodyRows + ' td:nth-child(2)';
selectors.ladTableBodyRowsThirdData = selectors.ladTableBodyRows + ' td:nth-child(3)';
selectors.ladTableFirstBodyRow = selectors.ladTableClass + ' tbody tr:first-child';
selectors.ladTableFirstRowFirstData = selectors.ladTableBodyRows + ' td:first-child';
selectors.ladTableFirstRowSecondData = selectors.ladTableBodyRows + ' td:nth-child(2)';
selectors.ladTableFirstRowThirdData = selectors.ladTableBodyRows + ' td:nth-child(3)';
selectors.ladTableSetTableHeaders = selectors.ladTableClass + ' .c-table__group-header';
function utcTimeFormat(value) {
return new Date(value).toISOString().replace('T', ' ')
}
describe("The LAD Table", () => {
const ladTableKey = 'LadTable';
let telemetryCount = 3,
timeFormat = 'utc',
mockTelemetry = getMockTelemetry({ count: telemetryCount, format: timeFormat }),
mockObj = getMockObjects({
objectKeyStrings: ['ladTable', 'telemetry'],
format: timeFormat
}),
bounds = {
start: 0,
end: 4
};
// add telemetry object as composition in lad table
mockObj.ladTable.composition.push(mockObj.telemetry.identifier);
// this setups up the app
beforeEach((done) => {
const appHolder = document.createElement('div');
appHolder.style.width = '640px';
appHolder.style.height = '480px';
openmct = createOpenMct();
parent = document.createElement('div');
child = document.createElement('div');
parent.appendChild(child);
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
ladPlugin = new LadPlugin();
openmct.install(ladPlugin);
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
openmct.time.bounds({ start: bounds.start, end: bounds.end });
openmct.on('start', done);
openmct.start(appHolder);
});
it("should provide a table view only for lad table objects", () => {
let applicableViews = openmct.objectViews.get(mockObj.ladTable),
ladTableView = applicableViews.find(
(viewProvider) => viewProvider.key === ladTableKey
);
expect(applicableViews.length).toEqual(1);
expect(ladTableView).toBeDefined();
});
describe('composition', () => {
let ladTableCompositionCollection;
beforeEach(() => {
ladTableCompositionCollection = openmct.composition.get(mockObj.ladTable);
ladTableCompositionCollection.load();
});
it("should accept telemetry producing objects", () => {
expect(() => {
ladTableCompositionCollection.add(mockObj.telemetry);
}).not.toThrow();
});
it("should reject non-telemtry producing objects", () => {
expect(()=> {
ladTableCompositionCollection.add(mockObj.ladTable);
}).toThrow();
});
});
describe("table view", () => {
let applicableViews,
ladTableViewProvider,
ladTableView,
anotherTelemetryObj = getMockObjects({
objectKeyStrings: ['telemetry'],
overwrite: {
telemetry: {
name: "New Telemetry Object",
identifier: { namespace: "", key: "another-telemetry-object" }
}
}
}).telemetry;
// add another telemetry object as composition in lad table to test multi rows
mockObj.ladTable.composition.push(anotherTelemetryObj.identifier);
beforeEach(async () => {
let telemetryRequestResolve,
telemetryObjectResolve,
anotherTelemetryObjectResolve;
let telemetryRequestPromise = new Promise((resolve) => {
telemetryRequestResolve = resolve;
}),
telemetryObjectPromise = new Promise((resolve) => {
telemetryObjectResolve = resolve;
}),
anotherTelemetryObjectPromise = new Promise((resolve) => {
anotherTelemetryObjectResolve = resolve;
})
openmct.telemetry.request.and.callFake(() => {
telemetryRequestResolve(mockTelemetry);
return telemetryRequestPromise;
});
openmct.objects.get.and.callFake((obj) => {
if(obj.key === 'telemetry-object') {
telemetryObjectResolve(mockObj.telemetry);
return telemetryObjectPromise;
} else {
anotherTelemetryObjectResolve(anotherTelemetryObj);
return anotherTelemetryObjectPromise;
}
});
openmct.time.bounds({ start: bounds.start, end: bounds.end });
applicableViews = openmct.objectViews.get(mockObj.ladTable);
ladTableViewProvider = applicableViews.find((viewProvider) => viewProvider.key === ladTableKey);
ladTableView = ladTableViewProvider.view(mockObj.ladTable, [mockObj.ladTable]);
ladTableView.show(child, true);
await Promise.all([telemetryRequestPromise, telemetryObjectPromise, anotherTelemetryObjectPromise]);
return await Vue.nextTick();
});
it("should show one row per object in the composition", () => {
const rowCount = parent.querySelectorAll(selectors.ladTableBodyRows).length;
expect(rowCount).toBe(mockObj.ladTable.composition.length);
});
it("should show the most recent datum from the telemetry producing object", async () => {
const latestDatum = getLatestTelemetry(mockTelemetry, { timeFormat });
const expectedDate = utcTimeFormat(latestDatum[timeFormat]);
await Vue.nextTick();
const latestDate = parent.querySelector(selectors.ladTableFirstRowSecondData).innerText;
expect(latestDate).toBe(expectedDate);
});
it("should show the name provided for the the telemetry producing object", () => {
const rowName = parent.querySelector(selectors.ladTableFirstRowFirstData).innerText,
expectedName = mockObj.telemetry.name;
expect(rowName).toBe(expectedName);
});
it("should show the correct values for the datum based on domain and range hints", async () => {
const range = mockObj.telemetry.telemetry.values.find((val) => {
return val.hints && val.hints.range !== undefined;
}).key;
const domain = mockObj.telemetry.telemetry.values.find((val) => {
return val.hints && val.hints.domain !== undefined;
}).key;
const mostRecentTelemetry = getLatestTelemetry(mockTelemetry, { timeFormat });
const rangeValue = mostRecentTelemetry[range];
const domainValue = utcTimeFormat(mostRecentTelemetry[domain]);
await Vue.nextTick();
const actualDomainValue = parent.querySelector(selectors.ladTableFirstRowSecondData).innerText;
const actualRangeValue = parent.querySelector(selectors.ladTableFirstRowThirdData).innerText;
expect(actualRangeValue).toBe(rangeValue);
expect(actualDomainValue).toBe(domainValue);
});
});
});
describe("The LAD Table Set", () => {
const ladTableSetKey = 'LadTableSet';
let telemetryCount = 3,
timeFormat = 'utc',
mockTelemetry = getMockTelemetry({ count: telemetryCount, format: timeFormat }),
mockObj = getMockObjects({
objectKeyStrings: ['ladTable', 'ladTableSet', 'telemetry']
}),
bounds = {
start: 0,
end: 4
};
// add mock telemetry to lad table and lad table to lad table set (composition)
mockObj.ladTable.composition.push(mockObj.telemetry.identifier);
mockObj.ladTableSet.composition.push(mockObj.ladTable.identifier);
beforeEach((done) => {
const appHolder = document.createElement('div');
appHolder.style.width = '640px';
appHolder.style.height = '480px';
openmct = createOpenMct();
parent = document.createElement('div');
child = document.createElement('div');
parent.appendChild(child);
spyOn(openmct.telemetry, 'request').and.returnValue(Promise.resolve([]));
ladPlugin = new LadPlugin();
openmct.install(ladPlugin);
spyOn(openmct.objects, 'get').and.returnValue(Promise.resolve({}));
openmct.time.bounds({ start: bounds.start, end: bounds.end });
openmct.on('start', done);
openmct.start(appHolder);
});
it("should provide a lad table set view only for lad table set objects", () => {
let applicableViews = openmct.objectViews.get(mockObj.ladTableSet),
ladTableSetView = applicableViews.find(
(viewProvider) => viewProvider.key === ladTableSetKey
);
expect(applicableViews.length).toEqual(1);
expect(ladTableSetView).toBeDefined();
});
describe('composition', () => {
let ladTableSetCompositionCollection;
beforeEach(() => {
ladTableSetCompositionCollection = openmct.composition.get(mockObj.ladTableSet);
ladTableSetCompositionCollection.load();
});
it("should accept lad table objects", () => {
expect(() => {
ladTableSetCompositionCollection.add(mockObj.ladTable);
}).not.toThrow();
});
it("should reject non lad table objects", () => {
expect(()=> {
ladTableSetCompositionCollection.add(mockObj.telemetry);
}).toThrow();
});
});
describe("table view", () => {
let applicableViews,
ladTableSetViewProvider,
ladTableSetView,
otherObj = getMockObjects({
objectKeyStrings: ['ladTable'],
overwrite: {
ladTable: {
name: "New LAD Table Object",
identifier: { namespace: "", key: "another-lad-object" }
}
}
});
// add another lad table (with telemetry object) object to the lad table set for multi row test
otherObj.ladTable.composition.push(mockObj.telemetry.identifier);
mockObj.ladTableSet.composition.push(otherObj.ladTable.identifier);
beforeEach(async () => {
let telemetryRequestResolve,
ladObjectResolve,
anotherLadObjectResolve;
let telemetryRequestPromise = new Promise((resolve) => {
telemetryRequestResolve = resolve;
}),
ladObjectPromise = new Promise((resolve) => {
ladObjectResolve = resolve;
}),
anotherLadObjectPromise = new Promise((resolve) => {
anotherLadObjectResolve = resolve;
})
openmct.telemetry.request.and.callFake(() => {
telemetryRequestResolve(mockTelemetry);
return telemetryRequestPromise;
});
openmct.objects.get.and.callFake((obj) => {
if(obj.key === 'lad-object') {
ladObjectResolve(mockObj.ladObject);
return ladObjectPromise;
} else if(obj.key === 'another-lad-object') {
anotherLadObjectResolve(otherObj.ladObject);
return anotherLadObjectPromise;
}
return Promise.resolve({});
});
openmct.time.bounds({ start: bounds.start, end: bounds.end });
applicableViews = openmct.objectViews.get(mockObj.ladTableSet);
ladTableSetViewProvider = applicableViews.find((viewProvider) => viewProvider.key === ladTableSetKey);
ladTableSetView = ladTableSetViewProvider.view(mockObj.ladTableSet, [mockObj.ladTableSet]);
ladTableSetView.show(child, true);
await Promise.all([telemetryRequestPromise, ladObjectPromise, anotherLadObjectPromise]);
return await Vue.nextTick();
});
it("should show one row per lad table object in the composition", () => {
const rowCount = parent.querySelectorAll(selectors.ladTableSetTableHeaders).length;
expect(rowCount).toBe(mockObj.ladTableSet.composition.length);
pending();
});
});
});

View File

@ -69,15 +69,19 @@ export default class ConditionClass extends EventEmitter {
console.log('no data received'); console.log('no data received');
return; return;
} }
this.criteria.forEach(criterion => {
if (this.isAnyOrAllTelemetry(criterion)) {
criterion.getResult(datum, this.conditionManager.telemetryObjects);
} else {
criterion.getResult(datum);
}
});
this.result = evaluateResults(this.criteria.map(criterion => criterion.result), this.trigger); if (this.isTelemetryUsed(datum.id)) {
this.criteria.forEach(criterion => {
if (this.isAnyOrAllTelemetry(criterion)) {
criterion.getResult(datum, this.conditionManager.telemetryObjects);
} else {
criterion.getResult(datum);
}
});
this.result = evaluateResults(this.criteria.map(criterion => criterion.result), this.trigger);
}
} }
isAnyOrAllTelemetry(criterion) { isAnyOrAllTelemetry(criterion) {

View File

@ -47,17 +47,24 @@ describe("The condition", function () {
name: "Test Object", name: "Test Object",
telemetry: { telemetry: {
values: [{ values: [{
key: "some-key", key: "value",
name: "Some attribute", name: "Value",
hints: {
range: 2
}
},
{
key: "utc",
name: "Time",
format: "utc",
hints: { hints: {
domain: 1 domain: 1
} }
}, { }, {
key: "some-other-key", key: "testSource",
name: "Another attribute", source: "value",
hints: { name: "Test",
range: 1 format: "string"
}
}] }]
} }
}; };
@ -136,4 +143,38 @@ describe("The condition", function () {
expect(result).toBeTrue(); expect(result).toBeTrue();
expect(conditionObj.criteria.length).toEqual(0); expect(conditionObj.criteria.length).toEqual(0);
}); });
it("gets the result of a condition when new telemetry data is received", function () {
conditionObj.getResult({
value: '0',
utc: 'Hi',
id: testTelemetryObject.identifier.key
});
expect(conditionObj.result).toBeTrue();
});
it("gets the result of a condition when new telemetry data is received", function () {
conditionObj.getResult({
value: '1',
utc: 'Hi',
id: testTelemetryObject.identifier.key
});
expect(conditionObj.result).toBeFalse();
});
it("keeps the old result new telemetry data is not used by it", function () {
conditionObj.getResult({
value: '0',
utc: 'Hi',
id: testTelemetryObject.identifier.key
});
expect(conditionObj.result).toBeTrue();
conditionObj.getResult({
value: '1',
utc: 'Hi',
id: '1234'
});
expect(conditionObj.result).toBeTrue();
});
}); });

View File

@ -157,7 +157,6 @@ export default class StyleRuleManager extends EventEmitter {
delete this.stopProvidingTelemetry; delete this.stopProvidingTelemetry;
this.conditionSetIdentifier = undefined; this.conditionSetIdentifier = undefined;
this.isEditing = undefined; this.isEditing = undefined;
this.callback = undefined;
} }
} }

View File

@ -30,6 +30,7 @@
> >
<div class="c-condition-h__drop-target"></div> <div class="c-condition-h__drop-target"></div>
<div v-if="isEditing" <div v-if="isEditing"
:class="{'is-current': condition.id === currentConditionId}"
class="c-condition c-condition--edit" class="c-condition c-condition--edit"
> >
<!-- Edit view --> <!-- Edit view -->
@ -167,6 +168,7 @@
</div> </div>
<div v-else <div v-else
class="c-condition c-condition--browse" class="c-condition c-condition--browse"
:class="{'is-current': condition.id === currentConditionId}"
> >
<!-- Browse view --> <!-- Browse view -->
<div class="c-condition__header"> <div class="c-condition__header">
@ -199,6 +201,10 @@ export default {
ConditionDescription ConditionDescription
}, },
props: { props: {
currentConditionId: {
type: String,
default: ''
},
condition: { condition: {
type: Object, type: Object,
required: true required: true

View File

@ -58,6 +58,7 @@
<Condition v-for="(condition, index) in conditionCollection" <Condition v-for="(condition, index) in conditionCollection"
:key="condition.id" :key="condition.id"
:condition="condition" :condition="condition"
:current-condition-id="currentConditionId"
:condition-index="index" :condition-index="index"
:telemetry="telemetryObjs" :telemetry="telemetryObjs"
:is-editing="isEditing" :is-editing="isEditing"
@ -107,7 +108,8 @@ export default {
moveIndex: undefined, moveIndex: undefined,
isDragging: false, isDragging: false,
defaultOutput: undefined, defaultOutput: undefined,
dragCounter: 0 dragCounter: 0,
currentConditionId: ''
}; };
}, },
watch: { watch: {
@ -145,6 +147,7 @@ export default {
}, },
methods: { methods: {
handleConditionSetResultUpdated(data) { handleConditionSetResultUpdated(data) {
this.currentConditionId = data.conditionId;
this.$emit('conditionSetResultUpdated', data) this.$emit('conditionSetResultUpdated', data)
}, },
observeForChanges() { observeForChanges() {

View File

@ -190,6 +190,7 @@
} }
.c-condition { .c-condition {
border: 1px solid transparent;
flex-direction: column; flex-direction: column;
min-width: 400px; min-width: 400px;
@ -234,6 +235,12 @@
&__summary { &__summary {
flex: 1 1 auto; flex: 1 1 auto;
} }
&.is-current {
$c: $colorBodyFg;
border-color: rgba($c, 0.2);
background: rgba($c, 0.2);
}
} }
/***************************** CONDITION DEFINITION, EDITING */ /***************************** CONDITION DEFINITION, EDITING */

View File

@ -65,7 +65,7 @@
&.is-current { &.is-current {
$c: $colorBodyFg; $c: $colorBodyFg;
border-color: rgba($c, 0.5); border-color: rgba($c, 0.2);
background: rgba($c, 0.2); background: rgba($c, 0.2);
} }

View File

@ -172,6 +172,7 @@ export default {
&& this.embed.bounds.end !== bounds.end; && this.embed.bounds.end !== bounds.end;
const isFixedTimespanMode = !this.openmct.time.clock(); const isFixedTimespanMode = !this.openmct.time.clock();
this.openmct.time.stopClock();
window.location.href = link; window.location.href = link;
let message = ''; let message = '';

View File

@ -29,7 +29,7 @@
<script> <script>
import Snapshot from '../snapshot'; import Snapshot from '../snapshot';
import { clearDefaultNotebook, getDefaultNotebook } from '../utils/notebook-storage'; import { getDefaultNotebook } from '../utils/notebook-storage';
import { NOTEBOOK_DEFAULT, NOTEBOOK_SNAPSHOT } from '../notebook-constants'; import { NOTEBOOK_DEFAULT, NOTEBOOK_SNAPSHOT } from '../notebook-constants';
export default { export default {
@ -72,28 +72,22 @@ export default {
methods: { methods: {
async setNotebookTypes() { async setNotebookTypes() {
const notebookTypes = []; const notebookTypes = [];
let defaultPath = '';
const defaultNotebook = getDefaultNotebook(); const defaultNotebook = getDefaultNotebook();
if (defaultNotebook) { if (defaultNotebook) {
const domainObject = await this.openmct.objects.get(defaultNotebook.notebookMeta.identifier) const domainObject = defaultNotebook.domainObject;
.then(d => d);
if (!domainObject.location) { if (domainObject.location) {
clearDefaultNotebook(); const defaultPath = `${domainObject.name} - ${defaultNotebook.section.name} - ${defaultNotebook.page.name}`;
} else {
defaultPath = `${domainObject.name} - ${defaultNotebook.section.name} - ${defaultNotebook.page.name}`; notebookTypes.push({
cssClass: 'icon-notebook',
name: `Save to Notebook ${defaultPath}`,
type: NOTEBOOK_DEFAULT
});
} }
} }
if (defaultPath.length !== 0) {
notebookTypes.push({
cssClass: 'icon-notebook',
name: `Save to Notebook ${defaultPath}`,
type: NOTEBOOK_DEFAULT
});
}
notebookTypes.push({ notebookTypes.push({
cssClass: 'icon-notebook', cssClass: 'icon-notebook',
name: 'Save to Notebook Snapshots', name: 'Save to Notebook Snapshots',

View File

@ -239,6 +239,7 @@ export default {
const section = this.getSelectedSection(); const section = this.getSelectedSection();
return { return {
domainObject: this.internalDomainObject,
notebookMeta, notebookMeta,
section, section,
page page
@ -440,7 +441,7 @@ export default {
async updateDefaultNotebook(notebookStorage) { async updateDefaultNotebook(notebookStorage) {
const defaultNotebookObject = await this.getDefaultNotebookObject(); const defaultNotebookObject = await this.getDefaultNotebookObject();
this.removeDefaultClass(defaultNotebookObject); this.removeDefaultClass(defaultNotebookObject);
setDefaultNotebook(notebookStorage); setDefaultNotebook(this.openmct, notebookStorage);
this.addDefaultClass(); this.addDefaultClass();
this.defaultSectionId = notebookStorage.section.id; this.defaultSectionId = notebookStorage.section.id;
this.defaultPageId = notebookStorage.page.id; this.defaultPageId = notebookStorage.page.id;

View File

@ -1,6 +1,46 @@
const NOTEBOOK_LOCAL_STORAGE = 'notebook-storage'; const NOTEBOOK_LOCAL_STORAGE = 'notebook-storage';
let currentNotebookObject = null;
let unlisten = null;
function defaultNotebookObjectChanged(newDomainObject) {
if (newDomainObject.location !== null) {
currentNotebookObject = newDomainObject;
const notebookStorage = getDefaultNotebook();
notebookStorage.domainObject = newDomainObject;
saveDefaultNotebook(notebookStorage);
return;
}
if (unlisten) {
unlisten();
unlisten = null;
}
clearDefaultNotebook();
}
function observeDefaultNotebookObject(openmct, notebookStorage) {
const domainObject = notebookStorage.domainObject;
if (currentNotebookObject
&& currentNotebookObject.identifier.key === domainObject.identifier.key) {
return;
}
if (unlisten) {
unlisten();
unlisten = null;
}
unlisten = openmct.objects.observe(notebookStorage.domainObject, '*', defaultNotebookObjectChanged);
}
function saveDefaultNotebook(notebookStorage) {
window.localStorage.setItem(NOTEBOOK_LOCAL_STORAGE, JSON.stringify(notebookStorage));
}
export function clearDefaultNotebook() { export function clearDefaultNotebook() {
currentNotebookObject = null;
window.localStorage.setItem(NOTEBOOK_LOCAL_STORAGE, null); window.localStorage.setItem(NOTEBOOK_LOCAL_STORAGE, null);
} }
@ -10,20 +50,21 @@ export function getDefaultNotebook() {
return JSON.parse(notebookStorage); return JSON.parse(notebookStorage);
} }
export function setDefaultNotebook(notebookStorage) { export function setDefaultNotebook(openmct, notebookStorage) {
window.localStorage.setItem(NOTEBOOK_LOCAL_STORAGE, JSON.stringify(notebookStorage)); observeDefaultNotebookObject(openmct, notebookStorage);
saveDefaultNotebook(notebookStorage);
} }
export function setDefaultNotebookSection(section) { export function setDefaultNotebookSection(section) {
const notebookStorage = getDefaultNotebook(); const notebookStorage = getDefaultNotebook();
notebookStorage.section = section; notebookStorage.section = section;
window.localStorage.setItem(NOTEBOOK_LOCAL_STORAGE, JSON.stringify(notebookStorage)); saveDefaultNotebook(notebookStorage);
} }
export function setDefaultNotebookPage(page) { export function setDefaultNotebookPage(page) {
const notebookStorage = getDefaultNotebook(); const notebookStorage = getDefaultNotebook();
notebookStorage.page = page; notebookStorage.page = page;
window.localStorage.setItem(NOTEBOOK_LOCAL_STORAGE, JSON.stringify(notebookStorage)); saveDefaultNotebook(notebookStorage);
} }

View File

@ -67,10 +67,10 @@ define([
} }
this.$canvas = this.$element.find('canvas'); this.$canvas = this.$element.find('canvas');
this.listenTo(this.$canvas, 'click', this.onMouseClick, this);
this.listenTo(this.$canvas, 'mousemove', this.trackMousePosition, this); this.listenTo(this.$canvas, 'mousemove', this.trackMousePosition, this);
this.listenTo(this.$canvas, 'mouseleave', this.untrackMousePosition, this); this.listenTo(this.$canvas, 'mouseleave', this.untrackMousePosition, this);
this.listenTo(this.$canvas, 'mousedown', this.onMouseDown, this); this.listenTo(this.$canvas, 'mousedown', this.onMouseDown, this);
this.listenTo(this.$canvas, 'wheel', this.wheelZoom, this);
this.watchForMarquee(); this.watchForMarquee();
}; };
@ -78,7 +78,6 @@ define([
MCTPlotController.prototype.initialize = function () { MCTPlotController.prototype.initialize = function () {
this.$canvas = this.$element.find('canvas'); this.$canvas = this.$element.find('canvas');
this.listenTo(this.$canvas, 'click', this.onMouseClick, this);
this.listenTo(this.$canvas, 'mousemove', this.trackMousePosition, this); this.listenTo(this.$canvas, 'mousemove', this.trackMousePosition, this);
this.listenTo(this.$canvas, 'mouseleave', this.untrackMousePosition, this); this.listenTo(this.$canvas, 'mouseleave', this.untrackMousePosition, this);
this.listenTo(this.$canvas, 'mousedown', this.onMouseDown, this); this.listenTo(this.$canvas, 'mousedown', this.onMouseDown, this);
@ -210,23 +209,6 @@ define([
this.highlightValues(point); this.highlightValues(point);
}; };
MCTPlotController.prototype.onMouseClick = function ($event) {
const isClick = this.isMouseClick();
if (this.pan) {
this.endPan($event);
}
if (this.marquee) {
this.endMarquee($event);
}
this.$scope.$apply();
if (!this.$scope.highlights.length || !isClick) {
return;
}
this.$scope.lockHighlightPoint = !this.$scope.lockHighlightPoint;
};
MCTPlotController.prototype.highlightValues = function (point) { MCTPlotController.prototype.highlightValues = function (point) {
this.highlightPoint = point; this.highlightPoint = point;
this.$scope.$emit('plot:highlight:update', point); this.$scope.$emit('plot:highlight:update', point);
@ -274,11 +256,23 @@ define([
MCTPlotController.prototype.onMouseUp = function ($event) { MCTPlotController.prototype.onMouseUp = function ($event) {
this.stopListening(this.$window, 'mouseup', this.onMouseUp, this); this.stopListening(this.$window, 'mouseup', this.onMouseUp, this);
this.stopListening(this.$window, 'mousemove', this.trackMousePosition, this); this.stopListening(this.$window, 'mousemove', this.trackMousePosition, this);
if (this.isMouseClick()) {
this.$scope.lockHighlightPoint = !this.$scope.lockHighlightPoint;
}
if (this.allowPan) {
return this.endPan($event);
}
if (this.allowMarquee) {
return this.endMarquee($event);
}
}; };
MCTPlotController.prototype.isMouseClick = function () { MCTPlotController.prototype.isMouseClick = function () {
if (!this.marquee) { if (!this.marquee) {
return; return false;
} }
const { start, end } = this.marquee; const { start, end } = this.marquee;
@ -331,7 +325,7 @@ define([
} else { } else {
// A history entry is created by startMarquee, need to remove // A history entry is created by startMarquee, need to remove
// if marquee zoom doesn't occur. // if marquee zoom doesn't occur.
this.back(); this.plotHistory.pop();
} }
this.$scope.rectangles = []; this.$scope.rectangles = [];
this.marquee = undefined; this.marquee = undefined;

View File

@ -227,8 +227,9 @@ define([
}; };
PlotController.prototype.stopLoading = function () { PlotController.prototype.stopLoading = function () {
this.$scope.pending -= 1; this.$scope.$evalAsync(() => {
this.$scope.$digest(); this.$scope.pending -= 1;
});
}; };
/** /**

View File

@ -1,35 +0,0 @@
export default class PlotlyTelemetryProvider {
constructor(openmct) {
this.openmct = openmct;
}
isTelemetryObject(domainObject) {
return domainObject.type === 'plotlyPlot';
}
supportsRequest(domainObject) {
return domainObject.type === 'plotlyPlot';
}
supportsSubscribe(domainObject) {
return domainObject.type === 'plotlyPlot';
}
request(domainObject) {
// let conditionManager = this.getConditionManager(domainObject);
// return conditionManager.requestLADConditionSetOutput()
// .then(latestOutput => {
// return latestOutput;
// });
}
subscribe(domainObject, callback) {
// let conditionManager = this.getConditionManager(domainObject);
// conditionManager.on('conditionSetResultUpdated', callback);
// return this.destroyConditionManager.bind(this, this.openmct.objects.makeKeyString(domainObject.identifier));
}
}

View File

@ -34,7 +34,7 @@ export default function PlotlyViewProvider(openmct) {
canEdit: function (domainObject) { canEdit: function (domainObject) {
return domainObject.type === 'plotlyPlot'; return domainObject.type === 'plotlyPlot';
}, },
view: function (domainObject, objectPath) { view: function (domainObject) {
let component; let component;
return { return {
@ -42,8 +42,7 @@ export default function PlotlyViewProvider(openmct) {
component = new Vue({ component = new Vue({
provide: { provide: {
openmct, openmct,
domainObject, domainObject
objectPath
}, },
el: element, el: element,
components: { components: {

View File

@ -7,34 +7,89 @@ import Plotly from 'plotly.js-dist';
import moment from 'moment' import moment from 'moment'
export default { export default {
inject: ['openmct', 'domainObject', 'objectPath'], inject: ['openmct', 'domainObject'],
data: function () { data: function () {
return { return {
telemetryObjects: [] telemetryObjects: [],
// currentDomainObject: this.domainObject bounds: this.openmct.time.bounds(),
timeRange: 0,
plotData: {},
subscriptions: {}
} }
}, },
mounted() { mounted() {
this.plotElement = document.querySelector('.l-view-section');
this.composition = this.openmct.composition.get(this.domainObject); this.composition = this.openmct.composition.get(this.domainObject);
this.composition.on('add', this.addTelemetry); this.composition.on('add', this.addTelemetry);
this.composition.on('remove', this.removeTelemetry);
this.composition.load(); this.composition.load();
this.metadata = this.openmct.telemetry.getMetadata(this.domainObject); this.openmct.time.on('bounds', this.refreshData);
this.openmct.time.on('clock', this.changeClock);
console.log('this.metadata', this.metadata); },
destroyed() {
// this.keystring = this.openmct.objects.makeKeyString(this.domainObject.identifier); this.unsubscribe();
// this.subscribe(this.domainObject);
this.plotElement = document.querySelector('.l-view-section');
// Plotly.newPlot(this.plotElement, [{
// x: [1, 2, 3, 4, 5],
// y: [1, 2, 4, 8, 16]
// }], this.getLayout(), {displayModeBar: false});
}, },
methods: { methods: {
getLayout() { changeClock() {
if (this.openmct.time.clock()) {
Plotly.purge(this.plotElement);
this.telemetryObjects.forEach((telemetryObject, index) => {
this.subscribeTo(telemetryObject, index);
});
}
},
addTelemetry(telemetryObject) {
this.telemetryObjects.push(telemetryObject);
const index = this.telemetryObjects.findIndex(obj => obj === telemetryObject);
this.requestHistory(telemetryObject, index, true);
this.subscribeTo(telemetryObject, index);
},
subscribeTo(telemetryObject, index) {
let keyString = this.openmct.objects.makeKeyString(telemetryObject.identifier);
this.subscriptions[keyString] = this.openmct.telemetry.subscribe(telemetryObject, (datum) => {
//Check that telemetry object has not been removed since telemetry was requested.
if (!this.telemetryObjects.includes(telemetryObject)) {
return;
}
const length = this.plotData[telemetryObject.identifier.key].x.length;
this.updateData(datum, index, length);
});
},
unsubscribe(keyString) {
this.subscriptions[keyString]();
delete this.subscriptions[keyString];
},
refreshData(bounds, isTick) {
this.bounds = bounds;
this.telemetryObjects.forEach((telemetryObject, index) => {
if(!isTick) {
this.requestHistory(telemetryObject, index, false);
} else {
if (this.timeRange === 0 || this.timeRange !== this.bounds.end - this.bounds.start) {
this.timeRange = this.bounds.end - this.bounds.start;
this.requestHistory(telemetryObject, index, false);
}
}
});
},
requestHistory(telemetryObject, index, isAdd) {
this.openmct
.telemetry
.request(telemetryObject, {
start: this.bounds.start,
end: this.bounds.end
})
.then((telemetryData) => {
this.addTrace(telemetryData, telemetryObject, index, isAdd);
});
},
getLayout(telemetryObject, isFixed) {
return { return {
hovermode: 'compare', hovermode: 'compare',
hoverdistance: -1, hoverdistance: -1,
@ -45,93 +100,121 @@ export default {
size: "12px", size: "12px",
color: "#666" color: "#666"
}, },
xaxis: { xaxis: { // hardcoded as UTC for now
// title: this.plotAxisTitle.xAxisTitle, title: 'UTC',
zeroline: false zeroline: false,
range: isFixed ? 'undefined' : [
this.formatDatumX({utc: this.bounds.start}),
this.formatDatumX({utc: this.bounds.start})
]
}, },
yaxis: { yaxis: {
// title: this.plotAxisTitle.yAxisTitle, title: this.getYAxisLabel(telemetryObject),
zeroline: false zeroline: false
}, },
margin: { margin: {
l: 20, l: 40,
r: 10, r: 10,
b: 20, b: 40,
t: 10 t: 10
}, },
paper_bgcolor: 'transparent', paper_bgcolor: 'transparent',
plot_bgcolor: 'transparent' plot_bgcolor: 'transparent'
} }
}, },
addTelemetry(telemetryObject) { removeTelemetry(identifier) {
return this.openmct.telemetry.request(telemetryObject) let keyString = this.openmct.objects.makeKeyString(identifier);
.then(telemetryData => { this.unsubscribe(keyString);
this.createPlot(telemetryData, telemetryObject); this.telemetryObjects = this.telemetryObjects.filter((object) => !_.eq(identifier, object.identifier));
}, () => {console.log(error)}); if (!this.domainObject.composition.length) {
Plotly.purge(this.plotElement);
} else {
Plotly.deleteTraces(this.plotElement, this.domainObject.composition.length);
}
},
getYAxisLabel(telemetryObject) {
this.setYAxisProp(telemetryObject);
const valueMetadatas = this.openmct.telemetry.getMetadata(telemetryObject).values();
const index = valueMetadatas.findIndex(value => value.key === this.yAxisProp);
const yLabel = valueMetadatas[index].name;
return yLabel;
},
setYAxisProp(telemetryObject) {
if (telemetryObject.type === 'generator') {
this.yAxisProp = 'sin';
} else if (telemetryObject.type === 'example.state-generator') {
this.yAxisProp = 'state';
} else if (telemetryObject.type === 'conditionSet') {
this.yAxisProp = 'output';
}
}, },
formatDatumX(datum) { formatDatumX(datum) {
let timestamp = moment.utc(datum.utc).format('YYYY-MM-DD hh:mm:ss.ms'); let timestamp = moment.utc(datum.utc).format('YYYY-MM-DDTHH:mm:ss[Z]');
return timestamp; return timestamp;
}, },
formatDatumY(datum) { formatDatumY(datum) {
return datum.sin; return datum.sin;
}, },
createPlot(telemetryData, telemetryObject) { addTrace(telemetryData, telemetryObject, index, isAdd) {
let x = [], let x = [];
y = []; let y = [];
telemetryData.forEach((datum, index) => { const colors = ['red', 'green', 'blue'];
telemetryData.forEach((datum) => {
x.push(this.formatDatumX(datum)); x.push(this.formatDatumX(datum));
y.push(this.formatDatumY(datum)); y.push(this.formatDatumY(datum));
}) })
let data = [{ let traceData = [{ // trace configuration
x, x,
y, y,
mode: 'line' type: 'scattergl',
mode: 'lines+markers',
line: {
color: colors[index], // to set new color for each trace
shape: 'linear'
}
}]; }];
var layout = {
title:'Line and Scatter Plot'
};
Plotly.newPlot(
this.plotElement,
data,
this.getLayout()
)
this.subscribe(telemetryObject); this.plotData[telemetryObject.identifier.key] = traceData[0];
if (!this.plotElement.childNodes.length) { // not traces yet, so create new plot
Plotly.newPlot(
this.plotElement,
traceData,
this.getLayout(telemetryObject, true),
{
displayModeBar: false, // turns off hover-activated toolbar
staticPlot: true // turns off hover effects on datapoints
}
);
} else {
if (isAdd) { // add a new trace to existing plot
Plotly.addTraces(this.plotElement, traceData);
} else { // update existing trace with new data (bounds change)
Plotly.react(this.plotElement, Object.values(this.plotData), this.getLayout(telemetryObject, false));
}
}
}, },
subscribe(domainObject) { updateData(datum, index, length) {
// this.date = '' // plot all datapoints within bounds
// this.openmct.objects.get(this.keystring) if (datum.utc <= this.bounds.end && this.openmct.time.clock()) {
// .then((object) => { Plotly.extendTraces(
// const metadata = this.openmct.telemetry.getMetadata(this.domainObject); this.plotElement,
// console.log('metadata', metadata); {
// // this.timeKey = this.openmct.time.timeSystem().key; x: [[this.formatDatumX(datum)]],
// // this.timeFormat = this.openmct.telemetry.getValueFormatter(metadata.value(this.timeKey)); y: [[this.formatDatumY(datum)]]
// // // this.imageFormat = this.openmct.telemetry.getValueFormatter(metadata.valuesForHints(['image'])[0]); },
// // this.unsubscribe = this.openmct.telemetry [index], // apply changes to particular trace
// // .subscribe(this.domainObject, (datum) => { length // set the fixed number of points (will drop points from beginning as new points are added)
// // this.updateHistory(datum); );
// // this.updateValues(datum); let newRange = {
// // }); 'xaxis.range': [this.formatDatumX({utc: this.bounds.start}),this.formatDatumX({utc: this.bounds.end})]
};
// // this.requestHistory(this.openmct.time.bounds()); Plotly.relayout(this.plotElement, newRange);
// }); }
this.openmct.telemetry.subscribe(domainObject, (datum) => {
this.updateData(datum)
})
},
updateData(datum) {
Plotly.extendTraces(
this.plotElement,
{
x: [[this.formatDatumX(datum)]],
y: [[this.formatDatumY(datum)]]
},
[0]
);
} }
} }
} }

View File

@ -1,10 +1,8 @@
import PlotlyViewProvider from './PlotlyViewProvider.js'; import PlotlyViewProvider from './PlotlyViewProvider.js';
import PlotlyTelemetryProvider from './PlotlyTelemetryProvider.js';
export default function () { export default function () {
return function install(openmct) { return function install(openmct) {
openmct.objectViews.addProvider(new PlotlyViewProvider(openmct)); openmct.objectViews.addProvider(new PlotlyViewProvider(openmct));
openmct.telemetry.addProvider(new PlotlyTelemetryProvider(openmct));
openmct.types.addType('plotlyPlot', { openmct.types.addType('plotlyPlot', {
name: "Plotly Plot", name: "Plotly Plot",

View File

@ -183,7 +183,7 @@ define([
plugins.FolderView = FolderView; plugins.FolderView = FolderView;
plugins.Tabs = Tabs; plugins.Tabs = Tabs;
plugins.FlexibleLayout = FlexibleLayout; plugins.FlexibleLayout = FlexibleLayout;
plugins.LADTable = LADTable; plugins.LADTable = LADTable.default;
plugins.Filters = Filters; plugins.Filters = Filters;
plugins.ObjectMigration = ObjectMigration.default; plugins.ObjectMigration = ObjectMigration.default;
plugins.GoToOriginalAction = GoToOriginalAction.default; plugins.GoToOriginalAction = GoToOriginalAction.default;

View File

@ -27,7 +27,7 @@
{'is-current': isCurrent(tab)}, {'is-current': isCurrent(tab)},
tab.type.definition.cssClass tab.type.definition.cssClass
]" ]"
@click="showTab(tab)" @click="showTab(tab, index)"
> >
<span class="c-button__label">{{ tab.domainObject.name }}</span> <span class="c-button__label">{{ tab.domainObject.name }}</span>
</button> </button>
@ -48,6 +48,7 @@
</div> </div>
</div> </div>
<object-view <object-view
v-if="internalDomainObject.keep_alive ? currentTab : isCurrent(tab)"
class="c-tabs-view__object" class="c-tabs-view__object"
:object="tab.domainObject" :object="tab.domainObject"
/> />
@ -57,7 +58,6 @@
<script> <script>
import ObjectView from '../../../ui/components/ObjectView.vue'; import ObjectView from '../../../ui/components/ObjectView.vue';
import _ from 'lodash';
var unknownObjectType = { var unknownObjectType = {
definition: { definition: {
@ -73,6 +73,7 @@ export default {
}, },
data: function () { data: function () {
return { return {
internalDomainObject: this.domainObject,
currentTab: {}, currentTab: {},
tabsList: [], tabsList: [],
setCurrentTab: true, setCurrentTab: true,
@ -85,9 +86,17 @@ export default {
this.composition.on('add', this.addItem); this.composition.on('add', this.addItem);
this.composition.on('remove', this.removeItem); this.composition.on('remove', this.removeItem);
this.composition.on('reorder', this.onReorder); this.composition.on('reorder', this.onReorder);
this.composition.load(); this.composition.load().then(() => {
let currentTabIndex = this.domainObject.currentTabIndex;
if (currentTabIndex !== undefined && this.tabsList.length > currentTabIndex) {
this.currentTab = this.tabsList[currentTabIndex];
}
});
} }
this.unsubscribe = this.openmct.objects.observe(this.internalDomainObject, '*', this.updateInternalDomainObject);
document.addEventListener('dragstart', this.dragstart); document.addEventListener('dragstart', this.dragstart);
document.addEventListener('dragend', this.dragend); document.addEventListener('dragend', this.dragend);
}, },
@ -96,18 +105,25 @@ export default {
this.composition.off('remove', this.removeItem); this.composition.off('remove', this.removeItem);
this.composition.off('reorder', this.onReorder); this.composition.off('reorder', this.onReorder);
this.unsubscribe();
document.removeEventListener('dragstart', this.dragstart); document.removeEventListener('dragstart', this.dragstart);
document.removeEventListener('dragend', this.dragend); document.removeEventListener('dragend', this.dragend);
}, },
methods:{ methods:{
showTab(tab) { showTab(tab, index) {
if (index !== undefined) {
this.storeCurrentTabIndex(index);
}
this.currentTab = tab; this.currentTab = tab;
}, },
addItem(domainObject) { addItem(domainObject) {
let type = this.openmct.types.get(domainObject.type) || unknownObjectType, let type = this.openmct.types.get(domainObject.type) || unknownObjectType,
tabItem = { tabItem = {
domainObject, domainObject,
type: type type: type,
key: this.openmct.objects.makeKeyString(domainObject.identifier)
}; };
this.tabsList.push(tabItem); this.tabsList.push(tabItem);
@ -126,7 +142,7 @@ export default {
this.tabsList.splice(pos, 1); this.tabsList.splice(pos, 1);
if (this.isCurrent(tabToBeRemoved)) { if (this.isCurrent(tabToBeRemoved)) {
this.showTab(this.tabsList[this.tabsList.length - 1]); this.showTab(this.tabsList[this.tabsList.length - 1], this.tabsList.length - 1);
} }
}, },
onReorder(reorderPlan) { onReorder(reorderPlan) {
@ -138,6 +154,7 @@ export default {
}, },
onDrop(e) { onDrop(e) {
this.setCurrentTab = true; this.setCurrentTab = true;
this.storeCurrentTabIndex(this.tabsList.length);
}, },
dragstart(e) { dragstart(e) {
if (e.dataTransfer.types.includes('openmct/domain-object-path')) { if (e.dataTransfer.types.includes('openmct/domain-object-path')) {
@ -155,7 +172,13 @@ export default {
this.allowDrop = false; this.allowDrop = false;
}, },
isCurrent(tab) { isCurrent(tab) {
return _.isEqual(this.currentTab, tab) return this.currentTab.key === tab.key;
},
updateInternalDomainObject(domainObject) {
this.internalDomainObject = domainObject;
},
storeCurrentTabIndex(index) {
this.openmct.objects.mutate(this.internalDomainObject, 'currentTabIndex', index);
} }
} }
} }

View File

@ -36,7 +36,27 @@ define([
cssClass: 'icon-tabs-view', cssClass: 'icon-tabs-view',
initialize(domainObject) { initialize(domainObject) {
domainObject.composition = []; domainObject.composition = [];
} domainObject.keep_alive = true;
},
form: [
{
"key": "keep_alive",
"name": "Keep Tabs Alive",
"control": "select",
"options": [
{
'name': 'True',
'value': true
},
{
'name': 'False',
'value': false
}
],
"required": true,
"cssClass": "l-input"
}
]
}); });
}; };
}; };

View File

@ -1,4 +1,6 @@
import MCT from 'MCT'; import MCT from 'MCT';
let nativeFunctions = [],
mockObjects = setMockObjects();
export function createOpenMct() { export function createOpenMct() {
const openmct = new MCT(); const openmct = new MCT();
@ -16,3 +18,225 @@ export function createMouseEvent(eventName) {
view: window view: window
}); });
} }
export const spyOnBuiltins = (functionNames, object = window) => {
functionNames.forEach(functionName => {
if (nativeFunctions[functionName]) {
throw `Builtin spy function already defined for ${functionName}`;
}
nativeFunctions.push({functionName, object, nativeFunction: object[functionName]});
spyOn(object, functionName);
});
};
export const clearBuiltinSpies = () => {
nativeFunctions.forEach(clearBuiltinSpy);
nativeFunctions = [];
};
function clearBuiltinSpy(funcDefinition) {
funcDefinition.object[funcDefinition.functionName] = funcDefinition.nativeFunction;
}
export const getLatestTelemetry = (telemetry = [], opts = {}) => {
let latest = [],
timeFormat = opts.timeFormat || 'utc';
if(telemetry.length) {
latest = telemetry.reduce((prev, cur) => {
return prev[timeFormat] > cur[timeFormat] ? prev : cur;
});
}
return latest;
};
// EXAMPLE:
// getMockObjects({
// name: 'Jamie Telemetry',
// keys: ['test','other','yeah','sup'],
// format: 'local',
// telemetryConfig: {
// hints: {
// test: {
// domain: 1
// },
// other: {
// range: 2
// }
// }
// }
// })
export const getMockObjects = (opts = {}) => {
opts.type = opts.type || 'default';
if(opts.objectKeyStrings && !Array.isArray(opts.objectKeyStrings)) {
throw `"getMockObjects" optional parameter "objectKeyStrings" must be an array of string object keys`;
}
let requestedMocks = {};
if (!opts.objectKeyStrings) {
requestedMocks = copyObj(mockObjects[opts.type]);
} else {
opts.objectKeyStrings.forEach(objKey => {
if(mockObjects[opts.type] && mockObjects[opts.type][objKey]) {
requestedMocks[objKey] = copyObj(mockObjects[opts.type][objKey]);
} else {
throw `No mock object for object key "${objKey}" of type "${opts.type}"`;
}
});
}
// build out custom telemetry mappings if necessary
if(requestedMocks.telemetry && opts.telemetryConfig) {
let keys = opts.telemetryConfig.keys,
format = opts.telemetryConfig.format || 'utc',
hints = opts.telemetryConfig.hints,
values;
// if utc, keep default
if(format === 'utc') {
// save for later if new keys
if(keys) {
format = requestedMocks.telemetry
.telemetry.values.find((vals) => vals.key === 'utc');
}
} else {
format = {
key: format,
name: "Time",
format: format === 'local' ? 'local-format' : format,
hints: {
domain: 1
}
}
}
if(keys) {
values = keys.map((key) => ({ key, name: key + ' attribute' }));
values.push(format); // add time format back in
} else {
values = requestedMocks.telemetry.telemetry.values;
}
if(hints) {
for(let val of values) {
if(hints[val.key]) {
val.hints = hints[val.key];
}
}
}
requestedMocks.telemetry.telemetry.values = values;
}
// overwrite any field keys
if(opts.overwrite) {
for(let mock in requestedMocks) {
if(opts.overwrite[mock]) {
for(let key in opts.overwrite[mock]) {
if (Object.prototype.hasOwnProperty.call(opts.overwrite[mock], key)) {
requestedMocks[mock][key] = opts.overwrite[mock][key];
}
}
}
}
}
return requestedMocks;
}
// EXAMPLE:
// getMockTelemetry({
// name: 'My Telemetry',
// keys: ['test','other','yeah','sup'],
// count: 8,
// format: 'local'
// })
export const getMockTelemetry = (opts = {}) => {
let count = opts.count || 2,
format = opts.format || 'utc',
name = opts.name || 'Mock Telemetry Datum',
keyCount = 2,
keys = false,
telemetry = [];
if(opts.keys && Array.isArray(opts.keys)) {
keyCount = opts.keys.length;
keys = opts.keys;
} else if(opts.keyCount) {
keyCount = opts.keyCount;
}
for(let i = 1; i < count + 1; i++) {
let datum = {
[format]: i,
name
}
for(let k = 1; k < keyCount + 1; k++) {
let key = keys ? keys[k - 1] : 'some-key-' + k,
value = keys ? keys[k - 1] + ' value ' + i : 'some value ' + i + '-' + k;
datum[key] = value;
}
telemetry.push(datum);
}
return telemetry;
}
// copy objects a bit more easily
function copyObj(obj) {
return JSON.parse(JSON.stringify(obj));
}
// add any other necessary types to this mockObjects object
function setMockObjects() {
return {
default: {
ladTable: {
identifier: { namespace: "", key: "lad-object"},
type: 'LadTable',
composition: []
},
ladTableSet: {
identifier: { namespace: "", key: "lad-set-object"},
type: 'LadTableSet',
composition: []
},
telemetry: {
identifier: { namespace: "", key: "telemetry-object"},
type: "test-telemetry-object",
name: "Test Telemetry Object",
telemetry: {
values: [{
key: "name",
name: "Name",
format: "string"
},{
key: "utc",
name: "Time",
format: "utc",
hints: {
domain: 1
}
},{
name: "Some attribute 1",
key: "some-key-1",
hints: {
range: 1
}
}, {
name: "Some attribute 2",
key: "some-key-2"
}]
}
}
},
otherType: {
example: {}
}
}
}

View File

@ -35,6 +35,8 @@
<script> <script>
import PreviewHeader from './preview-header.vue'; import PreviewHeader from './preview-header.vue';
import {STYLE_CONSTANTS} from "@/plugins/condition/utils/constants";
import StyleRuleManager from "@/plugins/condition/StyleRuleManager";
export default { export default {
components: { components: {
@ -69,6 +71,14 @@ export default {
}, },
destroyed() { destroyed() {
this.view.destroy(); this.view.destroy();
if (this.stopListeningStyles) {
this.stopListeningStyles();
}
if (this.styleRuleManager) {
this.styleRuleManager.destroy();
delete this.styleRuleManager;
}
}, },
methods: { methods: {
clear() { clear() {
@ -90,6 +100,46 @@ export default {
this.view = this.currentView.view(this.domainObject, this.objectPath); this.view = this.currentView.view(this.domainObject, this.objectPath);
this.view.show(this.viewContainer, false); this.view.show(this.viewContainer, false);
this.initObjectStyles();
},
initObjectStyles() {
if (!this.styleRuleManager) {
this.styleRuleManager = new StyleRuleManager((this.domainObject.configuration && this.domainObject.configuration.objectStyles), this.openmct, this.updateStyle.bind(this));
} else {
this.styleRuleManager.updateObjectStyleConfig(this.domainObject.configuration && this.domainObject.configuration.objectStyles);
}
if (this.stopListeningStyles) {
this.stopListeningStyles();
}
this.stopListeningStyles = this.openmct.objects.observe(this.domainObject, 'configuration.objectStyles', (newObjectStyle) => {
//Updating styles in the inspector view will trigger this so that the changes are reflected immediately
this.styleRuleManager.updateObjectStyleConfig(newObjectStyle);
});
},
updateStyle(styleObj) {
if (!styleObj) {
return;
}
let keys = Object.keys(styleObj);
keys.forEach(key => {
let firstChild = this.$refs.objectView.querySelector(':first-child');
if (firstChild) {
if ((typeof styleObj[key] === 'string') && (styleObj[key].indexOf('__no_value') > -1)) {
if (firstChild.style[key]) {
firstChild.style[key] = '';
}
} else {
if (!styleObj.isStyleInvisible && firstChild.classList.contains(STYLE_CONSTANTS.isStyleInvisible)) {
firstChild.classList.remove(STYLE_CONSTANTS.isStyleInvisible);
} else if (styleObj.isStyleInvisible && !firstChild.classList.contains(styleObj.isStyleInvisible)) {
firstChild.classList.add(styleObj.isStyleInvisible);
}
firstChild.style[key] = styleObj[key];
}
}
});
} }
} }
} }