Compare commits

..

152 Commits

Author SHA1 Message Date
Joel McKinnon
a1d1301542 added unsubscribe, subscribeTo methods 2020-05-26 10:35:41 -07:00
Joel McKinnon
aef8589872 Merge branch 'master' of https://github.com/nasa/openmct into joel/plot-test 2020-05-22 13:35:58 -07:00
Joel McKinnon
38f3e60884 added dynamic layout for fixed, local clock 2020-05-22 13:35:47 -07:00
Joel McKinnon
331c086a60 implemented Plotly.relayout to set x-axis correctly for local clock 2020-05-21 16:45:21 -07:00
Joel McKinnon
0890ebfd0f added back if in updateData 2020-05-21 12:57:14 -07:00
Nikhil Mandlik
1547ea5cf7 small changes 2020-05-21 12:29:04 -07:00
Joel McKinnon
b1551478e4 refactor of refreshData 2020-05-21 12:03:53 -07:00
Joel McKinnon
213c469758 debugging local clock issues 2020-05-21 11:44:32 -07:00
Joel McKinnon
03ac43d306 added some comments 2020-05-20 18:17:29 -07:00
Joel McKinnon
f3de6f2548 Merge branch 'joel/plot-test' of https://github.com/nasa/openmct into joel/plot-test 2020-05-20 15:48:44 -07:00
Joel McKinnon
62dfb7d8f9 added requestHistory 2020-05-20 15:48:01 -07:00
Joel McKinnon
0a33cbeea2 resolve merge conflict 2020-05-20 09:32:59 -07:00
Jamie Vigliotta
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
Jamie Vigliotta
aa4bf5eaf6 updates from PR Review, making new lad set tests pending for the time being #3044 2020-05-19 12:39:31 -07:00
Shefali Joshi
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
Shefali Joshi
eda01abcbc Merge branch 'master' into updated-checklists 2020-05-18 12:12:26 -07:00
David Tsay
694b8f4666 provide format for name metadata (#3054) 2020-05-18 11:40:05 -07:00
Joel McKinnon
4e4031e700 added listeners and bounds methods 2020-05-18 11:37:24 -07:00
Shefali Joshi
46a20bb76d Merge branch 'master' into lad-testing 2020-05-18 11:03:49 -07:00
Jamie Vigliotta
1d482f318e working on adding render tests for lad table sets 2020-05-15 15:34:03 -07:00
Joel McKinnon
6826b579a6 WIP... 2020-05-15 15:32:53 -07:00
David Tsay
bbb271a678 clarify value hints (#2673)
remove confusing comments regarding domain - input and range - output
2020-05-15 14:48:25 -07:00
Deep Tailor
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
Nikhil
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
Jamie Vigliotta
3d4c721232 removing fdescribe 2020-05-14 13:30:13 -07:00
Jamie Vigliotta
190d34c939 added composition tests for lad table set as well 2020-05-14 13:22:12 -07:00
Jamie Vigliotta
47bc0e9793 pushing final version of tests for PR 2020-05-14 10:38:38 -07:00
Joel McKinnon
35115aaa50 WIP: adding bounds functions 2020-05-13 16:32:03 -07:00
Shefali Joshi
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
Joel McKinnon
b252051c83 add, remove multiple traces, remove hover effects 2020-05-13 11:20:35 -07:00
Jamie Vigliotta
4ca1181eb6 Merge branch 'lad-bounds-listener' into lad-testing
mergin in other lad changes.
2020-05-13 09:32:31 -07:00
Andrew Henry
eb49ffae02 Update CONTRIBUTING.md 2020-05-13 09:21:00 -07:00
Andrew Henry
5751012872 Update CONTRIBUTING.md 2020-05-13 09:20:03 -07:00
Jamie Vigliotta
f973f42729 random error fixed for testing, was a system setup issue 2020-05-12 15:05:29 -07:00
Joel McKinnon
4788561631 WIP 2020-05-12 15:04:05 -07:00
Jamie Vigliotta
e8699d54d5 issue with javascript heap error for tests 2020-05-12 14:19:06 -07:00
Jamie Vigliotta
14bc49451b removed "existingTimestamp" variable, it is now stored in instance "timestamp" variable 2020-05-12 13:12:53 -07:00
Jamie Vigliotta
dd2e8c0460 removing console logs for the moment 2020-05-12 11:58:01 -07:00
Jamie Vigliotta
031753a9a8 still some pending, but all these are successful 2020-05-12 10:23:41 -07:00
Jamie Vigliotta
8edae5f02c Merge branch 'master' into lad-testing
Merging master
.
2020-05-12 10:09:15 -07:00
Jamie Vigliotta
7ed3de01b9 commit before merge master 2020-05-12 10:08:01 -07:00
Andrew Henry
aa041e04cf Merge remote-tracking branch 'origin/update-contributing-guidelines' into updated-checklists 2020-05-11 17:13:53 -07:00
Andrew Henry
24e7ea143a Added more details on the process around pull requests 2020-05-11 17:12:25 -07:00
Andrew Henry
79d5d9c4d0 Update CONTRIBUTING.md 2020-05-11 16:15:17 -07:00
Andrew Henry
b5bfdc4418 Update CONTRIBUTING.md 2020-05-11 15:56:49 -07:00
Andrew Henry
59730c60ec Update CONTRIBUTING.md 2020-05-11 15:54:25 -07:00
Shefali Joshi
4a87a5d847 Show object styles in preview modal (#3018)
* Adds conditional styles to Preview window
2020-05-11 14:25:39 -07:00
Jamie Vigliotta
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
Jamie Vigliotta
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
Joel McKinnon
0c741c67f8 import RemoveAction 2020-05-11 09:59:41 -07:00
Jamie Vigliotta
85da5e43b3 moved formatedTimestamp to a computed value to utilize caching 2020-05-08 16:24:58 -07:00
Joel McKinnon
d26e45f636 add code to set Y-axis and plot multiple telemetry types 2020-05-08 13:50:56 -07:00
Jamie Vigliotta
0bd6814097 fixed case sensitive file name error 2020-05-08 12:13:28 -07:00
Jamie Vigliotta
4e7410b4bf trying to fix circle ci missing LADTable.vue error 2020-05-08 12:09:04 -07:00
Jamie Vigliotta
436550adba converted LADTable to es6 module 2020-05-08 11:52:37 -07:00
Deep Tailor
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
Jamie Vigliotta
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
Jamie Vigliotta
eca12201c7 switching over toe es6 module 2020-05-07 18:35:25 -07:00
Jamie Vigliotta
68ff69664b added a phew more tests 2020-05-07 16:23:37 -07:00
Nikhil
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
Joel McKinnon
51fbb9cee6 Merge branch 'master' into joel/plot-test 2020-05-07 14:55:07 -07:00
Joel McKinnon
437156e04c changed mode and type, dropped old data as data extends 2020-05-07 14:45:34 -07:00
Joel McKinnon
d244ee622a rendering a plotly plot from SWG 2020-05-07 13:00:54 -07:00
Deep Tailor
043d6aa9c3 extend traces on subscribe 2020-05-07 12:23:32 -07:00
Deep Tailor
ecfab8f7f3 add subscribe 2020-05-07 12:00:39 -07:00
Deep Tailor
05c38c37aa working historical 2020-05-07 11:37:01 -07:00
Joel McKinnon
ce78925119 wip: added telemetry provider 2020-05-07 10:45:15 -07:00
Jamie Vigliotta
b30018c315 added mock objects and mock telemetry to testTools 2020-05-06 13:48:17 -07:00
Joel McKinnon
26aca0f433 working on viewProvider 2020-05-06 11:31:17 -07:00
Andrew Henry
eb5eb7d540 Un-focused test 2020-05-06 10:55:06 -07:00
Andrew Henry
609a47c62c Fixed failing test 2020-05-06 10:36:15 -07:00
Deep Tailor
83f9c6c528 improve plot gestures - and clean up (#3013) 2020-05-06 10:33:59 -07:00
Joel McKinnon
41259bbd40 added hardcoded test plot 2020-05-05 16:00:48 -07:00
Joel McKinnon
580640ff47 basic plotly plugin structure 2020-05-05 12:50:55 -07:00
Andrew Henry
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
Shefali Joshi
a70facf0c8 Merge pull request #3000 from nasa/upgrade-moment
Upgrade moment to 2.25.3
2020-05-05 11:01:39 -07:00
Andrew Henry
447fe94325 Merge branch 'upgrade-moment' of https://github.com/nasa/openmct into upgrade-moment 2020-05-05 10:36:43 -07:00
Andrew Henry
8e2b666766 Upgraded moment version to 2.25.3 2020-05-05 10:36:33 -07:00
Shefali Joshi
dcbfbdbb89 Merge branch 'master' into upgrade-moment 2020-05-05 10:12:50 -07:00
Shefali Joshi
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
Jamie Vigliotta
688a03c8ac added composition, working on table views 2020-05-04 11:09:19 -07:00
Andrew Henry
81b7a9d3e0 Merge branch 'master' into upgrade-moment 2020-05-01 16:47:26 -07:00
Andrew Henry
dc573c479c Upgrade moment to 2.24.0 2020-05-01 16:25:49 -07:00
Jamie Vigliotta
4f12f41685 added composition policy testing 2020-05-01 15:20:08 -07:00
Joel McKinnon
a4aec5d492 Merge branch 'master' of https://github.com/nasa/openmct 2020-05-01 10:46:57 -07:00
Nikhil
23303c910e Don't allow recursive Preview actions #2775 (#2869)
* Don't allow recursive Preview actions #2775

* actionsToBeIncluded and actionsToBeSkipped passed in as options object.

* Revert "actionsToBeIncluded and actionsToBeSkipped passed in as options object."

This reverts commit f501d0b4ba.

* Revert "Don't allow recursive Preview actions #2775"

This reverts commit 5563cbea3a.

* Don't allow recursive Preview actions

Co-authored-by: Andrew Henry <akhenry@gmail.com>
2020-05-01 09:33:10 -07:00
Joel McKinnon
00b3f3ac0b Merge branch 'master' of https://github.com/nasa/openmct 2020-05-01 09:25:44 -07:00
Nikhil
3282934cf6 fix test coverage. (#2951)
* fix test coverage.

* changes per comments + added test-coverage script to increase max-old-space-size of V8
ref: https://nodejs.org/api/cli.html#cli_max_old_space_size_size_in_mbytes

* renamed test:coverage and test:debug.

* circle-ci to use test:coverage.

* reduced test coverage thresholds.

Co-authored-by: Andrew Henry <akhenry@gmail.com>
2020-04-30 16:57:53 -07:00
Nikhil
c157fab081 [Notebook] Save snapshot dropdown should be available from "view large" overlay #2922 (#2926)
* [Notebook] Save snapshot dropdown should be available from "view large" overlay #2922\
* Significant improvements to Snapshot styling
* [Notebook] Embed links aren't navigating #2979

Co-authored-by: charlesh88 <charlesh88@gmail.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2020-04-30 16:39:20 -07:00
Nikhil
7c07b66cc9 [Notebook] : Error in event handler for "updateSection": "TypeError: Cannot read property 'id' of undefined" #2921 (#2924)
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2020-04-30 16:30:09 -07:00
Shefali Joshi
7a906ccf5c Preview condition styles on selecting that condition or one of it's styles (#2925)
* Preview condition styles on selecting that condition or one of it's styles
* Do not evaluate conditional styles in edit mode

Co-authored-by: charlesh88 <charlesh88@gmail.com>
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2020-04-30 13:00:43 -07:00
Nikhil
ff7debfb81 [Notebook] Entries and Embeds need to use the same timesystem #2920 (#2923)
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2020-04-30 11:56:08 -07:00
Joel McKinnon
92ba103f45 modified eslint script and fixed errors found (#2905)
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2020-04-30 11:53:07 -07:00
Nikhil
2c2d8d6b56 [Notebook]: Unnecessary notification "Time bounds changed to fixed timespan mode" #2843 (#2866)
* [Notebook]: Unnecessary notification "Time bounds changed to fixed timespan mode" #2843

* removed stopclock call.

Co-authored-by: Andrew Henry <akhenry@gmail.com>
Co-authored-by: Deep Tailor <deep.j.tailor@nasa.gov>
2020-04-30 10:47:49 -07:00
Jamie Vigliotta
189882afc8 first "it" in, working on the next 2020-04-30 10:36:44 -07:00
Charles Hacskaylo
cfadb9f4fd Fixes #2901 (#2902)
- Mod PreviewAction to prevent folders from being previewed;

Co-authored-by: Andrew Henry <akhenry@gmail.com>
2020-04-30 09:48:16 -07:00
Joel McKinnon
c185f77a15 Merge branch 'master' of https://github.com/nasa/openmct 2020-04-29 16:52:05 -07:00
David Tsay
396817b2d1 handle non-valid requests (#2984)
Co-authored-by: Deep Tailor <deep.j.tailor@nasa.gov>
2020-04-29 15:24:12 -07:00
David Tsay
96eb6d6b74 [Conditionals] evaluation fixes (#2981)
* change single output to state and value

* do not send telemetryObjects to telemetry api request cal

* normalize data on requests

Co-authored-by: Deep Tailor <deep.j.tailor@nasa.gov>
2020-04-29 14:56:07 -07:00
Andrew Henry
2b3541a323 Merge branch 'master' into lad-bounds-listener 2020-04-29 12:43:20 -07:00
Jamie Vigliotta
7c9a140481 plugin spec for lad tables 2020-04-29 11:27:56 -07:00
Jamie Vigliotta
5e9b313ee9 Merge branch 'ladtable-composition-policy' into lad-testing
merging lad branches for unit testing
.
2020-04-29 10:46:06 -07:00
Jamie Vigliotta
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
Jamie Vigliotta
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
Jamie Vigliotta
51fb72dc04 updates from PR review 2020-04-28 17:02:04 -07:00
Shefali Joshi
cb5d47f66f Use only the values required for description (#2919)
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2020-04-28 16:58:58 -07:00
Andrew Henry
d51052ab46 Merge branch 'master' into ladtable-composition-policy 2020-04-28 16:44:21 -07:00
Shefali Joshi
ea90d02d66 Show the Styles tab for non creatable layout objects including condition sets (#2975) 2020-04-28 13:10:29 -07:00
David Tsay
95f73d8eb8 [Conditionals] fix #2961 in master (#2969)
* use correct id for telemetry requests

* request and subscription data cache should be mutually exclusive

use latest timestamp for any/all requests

* do not add prop to datum

remove unnecessary if check

Co-authored-by: Deep Tailor <deep.j.tailor@nasa.gov>
2020-04-28 13:02:23 -07:00
Joel McKinnon
0dff431f4a Merge branch 'master' of https://github.com/nasa/openmct 2020-04-27 12:02:12 -07:00
Jamie Vigliotta
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
Joel McKinnon
a37c686993 Merge pull request #2968 from nasa/revert-2885-lodash-upgrade-test
Revert "Lodash upgrade"
2020-04-24 11:55:44 -07:00
Deep Tailor
f12166097c Revert "Lodash upgrade (#2885)"
This reverts commit d103a22fa0.
2020-04-24 11:53:31 -07:00
Joel McKinnon
61d238a097 Merge branch 'master' of https://github.com/nasa/openmct 2020-04-23 16:30:01 -07:00
Jamie Vigliotta
ba4ef43673 linting and removing console logs 2020-04-23 15:50:49 -07:00
Jamie Vigliotta
f6c16b7483 added a check for lad table sets, child can only be lad table 2020-04-23 15:17:07 -07:00
Jamie Vigliotta
58e63e649f added LAD Table composition policy 2020-04-23 13:33:32 -07:00
Joel McKinnon
d103a22fa0 Lodash upgrade (#2885)
* upgraded lodash, changed method names
* native implementations as requested
2020-04-23 10:38:44 -07:00
Deep Tailor
04a60cfcbb fixes #2713 (#2928)
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2020-04-22 15:22:11 -07:00
Joel McKinnon
8d723960f4 removed acorn from package.json (#2906)
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2020-04-21 15:50:45 -07:00
Joel McKinnon
6d3cd2c699 Upgrade angular from 1.4.14 to 1.7.9 (#2955)
* successfully upgraded to v1.6 with $compileProvider.preAssignBindingsEnabled(true)
* removed $compileProvider.preAssignBindingsEnabled(true), wrapped constructors for plot and chart inside onInit function
Co-authored-by: Andrew Henry <akhenry@gmail.com>
2020-04-21 15:34:12 -07:00
Andrew Henry
87bf94fe0a Updated year in index.html (#2930) 2020-04-21 15:28:10 -07:00
Joel McKinnon
af93823b6f Elasticsearch support for change to typeless API (#2941)
* added elasticsearch to bundlemap
2020-04-21 15:23:43 -07:00
Jamie Vigliotta
7a04aea90e removing console log 2020-04-17 16:45:24 -07:00
Jamie Vigliotta
0bb475327c added matadatum with key === name to be ignored as well when selecting value to show 2020-04-17 16:44:18 -07:00
Jamie Vigliotta
c474b998f0 added a backup if the object does not have a range hint (ex. images) 2020-04-17 16:22:03 -07:00
Joel McKinnon
f9deb80350 Merge branch 'master' of https://github.com/nasa/openmct 2020-04-16 15:01:42 -07:00
Jamie Vigliotta
a02d421093 added bounds listener, moved history request to function, checking for race conditions 2020-04-16 12:19:20 -07:00
Jamie Vigliotta
9026099fd2 added bounds listener, moved history request to function, checking for race conditions 2020-04-16 11:51:48 -07:00
Jamie Vigliotta
295ccea195 cleaned up subscription and bounds listener, added timesystem listener 2020-04-14 12:33:34 -07:00
Joel McKinnon
021d730814 resolve merge conflicts 2020-04-13 09:05:43 -07:00
David Tsay
ae62b15abf Merge branch 'fix-default-output' of github.com:nasa/openmct into fix-default-output 2020-04-10 15:44:42 -07:00
David Tsay
ba41c1a30e fix unit tests 2020-04-10 15:43:48 -07:00
Andrew Henry
b9a85d9c4d Merge branch 'master' into fix-default-output 2020-04-10 15:34:10 -07:00
David Tsay
80eab8bad1 fix telemetrycriterion and unit testing 2020-04-10 15:26:18 -07:00
Andrew Henry
b2d8d640ae Merge branch 'master' into fix-default-output 2020-04-10 15:23:10 -07:00
Andrew Henry
56e6fa66c2 Merge branch 'master' into fix-default-output 2020-04-10 15:13:36 -07:00
David Tsay
9fa4707c82 Merge branch 'master' into fix-default-output 2020-04-10 13:04:14 -07:00
David Tsay
7e2cfa36de AllTelemetryCriterion extends TelemetryCriterion 2020-04-10 12:17:55 -07:00
David Tsay
aaa60a1545 scope function names 2020-04-10 11:28:13 -07:00
David Tsay
717231fed2 use current timesystem to compare latest 2020-04-10 11:20:51 -07:00
David Tsay
7fb2bc9729 tie in requests and eliminate unused code 2020-04-10 10:42:31 -07:00
David Tsay
addeb635e9 refactor all/any telemetry criterion to use new evaluator 2020-04-10 09:48:31 -07:00
David Tsay
608d63a7b0 telemetry criterion stores its own result 2020-04-10 09:00:39 -07:00
David Tsay
10679e5f4f remove commented code 2020-04-10 00:12:55 -07:00
David Tsay
38b8f03b1a linting 2020-04-09 21:03:56 -07:00
David Tsay
779a42c28c remove unused listeners, events, and helpers 2020-04-09 18:14:19 -07:00
David Tsay
80c2504768 get results directly instead of using events 2020-04-09 17:54:47 -07:00
David Tsay
80359e3f16 remove generating timestamp for telemetry data 2020-04-09 16:20:16 -07:00
Joshi
66aa4f099f Remove unused code 2020-04-09 15:22:35 -07:00
Joshi
aa6c6cb88b Removes timestamp based evalutation from conditionManager 2020-04-09 15:21:25 -07:00
Joshi
4e5cc840d7 Ensures that results for a specific datapoint are evaluated atomically. 2020-04-09 12:10:29 -07:00
64 changed files with 1942 additions and 479 deletions

View File

@@ -20,8 +20,8 @@ jobs:
paths:
- node_modules
- run:
name: npm run test
command: npm run test
name: npm run test:coverage
command: npm run test:coverage
- run:
name: npm run lint
command: npm run lint

4
API.md
View File

@@ -427,8 +427,8 @@ Each telemetry value description has an object defining hints. Keys in this thi
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.
* `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.
* `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`: 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.
##### 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
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,
and the pull request should include a completed author checklist, also
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 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
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?
3. Command line build passes?
4. Changes have been smoke-tested?
5. Testing instructions included?
### Reviewer Checklist
@@ -299,3 +309,4 @@ checklist).
2. Appropriate unit tests included?
3. Code style and in-line documentation are appropriate?
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: [
{
key: "name",
name: "Name"
name: "Name",
format: "string"
},
{
key: "utc",

View File

@@ -1,14 +1,14 @@
<template>
<div class="example">{{ msg }}</div>
<div class="example">{{ msg }}</div>
</template>
<script>
export default {
data () {
return {
msg: 'Hello world!'
data() {
return {
msg: 'Hello world!'
}
}
}
}
</script>

View File

@@ -1,5 +1,5 @@
<!--
Open MCT, Copyright (c) 2014-2017, United States Government
Open MCT, Copyright (c) 2014-2020, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
@@ -43,9 +43,9 @@
openmct.legacyRegistry.enable.bind(openmct.legacyRegistry)
);
openmct.install(openmct.plugins.LocalStorage());
openmct.install(openmct.plugins.Espresso());
openmct.install(openmct.plugins.MyItems());
openmct.install(openmct.plugins.LocalStorage());
openmct.install(openmct.plugins.Generator());
openmct.install(openmct.plugins.ExampleImagery());
openmct.install(openmct.plugins.UTCTimeSystem());

View File

@@ -24,16 +24,27 @@
const devMode = process.env.NODE_ENV !== 'production';
const browsers = [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'ChromeHeadless'];
const coverageEnabled = process.env.COVERAGE === 'true';
const reporters = ['progress', 'html'];
if (coverageEnabled) {
reporters.push('coverage-istanbul');
}
module.exports = (config) => {
const webpackConfig = require('./webpack.config.js');
delete webpackConfig.output;
if (!devMode) {
if (!devMode || coverageEnabled) {
webpackConfig.module.rules.push({
test: /\.js$/,
exclude: /node_modules|example/,
use: 'istanbul-instrumenter-loader'
exclude: /node_modules|example|lib|dist/,
use: {
loader: 'istanbul-instrumenter-loader',
options: {
esModules: true
}
}
});
}
@@ -45,11 +56,7 @@ module.exports = (config) => {
'src/**/*Spec.js'
],
port: 9876,
reporters: [
'progress',
'coverage',
'html'
],
reporters: reporters,
browsers: browsers,
customLaunchers: {
ChromeDebugging: {
@@ -61,27 +68,27 @@ module.exports = (config) => {
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
coverageReporter: {
dir: process.env.CIRCLE_ARTIFACTS ?
process.env.CIRCLE_ARTIFACTS + '/coverage' :
"dist/reports/coverage",
check: {
global: {
lines: 80,
excludes: ['src/plugins/plot/**/*.js']
}
}
},
// HTML test reporting.
htmlReporter: {
outputDir: "dist/reports/tests",
preserveDescribeNesting: true,
foldAll: false
},
coverageIstanbulReporter: {
fixWebpackSourcePaths: true,
dir: process.env.CIRCLE_ARTIFACTS ?
process.env.CIRCLE_ARTIFACTS + '/coverage' :
"dist/reports/coverage",
reports: ['html', 'lcovonly', 'text-summary'],
thresholds: {
global: {
lines: 62
}
}
},
preprocessors: {
// add webpack as preprocessor
'platform/**/*Spec.js': [ 'webpack', 'sourcemap' ],
'src/**/*Spec.js': [ 'webpack', 'sourcemap' ]
'platform/**/*Spec.js': ['webpack', 'sourcemap'],
'src/**/*Spec.js': ['webpack', 'sourcemap']
},
webpack: webpackConfig,
webpackMiddleware: {

View File

@@ -2,10 +2,11 @@
"name": "openmct",
"version": "1.0.0-snapshot",
"description": "The Open MCT core platform",
"dependencies": {},
"dependencies": {
"plotly.js-dist": "^1.54.1"
},
"devDependencies": {
"acorn": "6.2.0",
"angular": "1.4.14",
"angular": "1.7.9",
"angular-route": "1.4.14",
"babel-eslint": "8.2.6",
"comma-separated-values": "^3.6.4",
@@ -43,6 +44,7 @@
"karma-chrome-launcher": "^2.2.0",
"karma-cli": "^1.0.1",
"karma-coverage": "^1.1.2",
"karma-coverage-istanbul-reporter": "^2.1.1",
"karma-html-reporter": "^0.2.7",
"karma-jasmine": "^1.1.2",
"karma-sourcemap-loader": "^0.3.7",
@@ -76,14 +78,16 @@
"zepto": "^1.2.0"
},
"scripts": {
"clean": "rm -rf ./dist",
"start": "node app.js",
"lint": "eslint platform example src/**/*.{js,vue} openmct.js",
"lint:fix": "eslint platform example src/**/*.{js,vue} openmct.js --fix",
"lint": "eslint platform example src --ext .js,.vue openmct.js",
"lint:fix": "eslint platform example src --ext .js,.vue openmct.js --fix",
"build:prod": "cross-env NODE_ENV=production webpack",
"build:dev": "webpack",
"build:watch": "webpack --watch",
"test": "karma start --single-run",
"test-debug": "cross-env NODE_ENV=debug karma start --no-single-run",
"test:debug": "cross-env NODE_ENV=debug karma start --no-single-run",
"test:coverage": "./scripts/test-coverage.sh",
"test:watch": "karma start --no-single-run",
"verify": "concurrently 'npm:test' 'npm:lint'",
"jsdoc": "jsdoc -c jsdoc.json -R API.md -r -d dist/docs/api",

View File

@@ -87,6 +87,11 @@ define([
bootstrapper
);
// Override of angular1.6 ! hashPrefix
app.config(['$locationProvider', function ($locationProvider) {
$locationProvider.hashPrefix('');
}]);
// Apply logging levels; this must be done now, before the
// first log statement.
new LogLevel(logLevel).configure(app, $log);

View File

@@ -71,7 +71,7 @@ define([
},
{
"key": "ELASTIC_PATH",
"value": "mct/domain_object",
"value": "mct/_doc",
"priority": "fallback"
},
{

View File

@@ -32,9 +32,9 @@ define(
// JSLint doesn't like underscore-prefixed properties,
// so hide them here.
var SRC = "_source",
REV = "_version",
ID = "_id",
CONFLICT = 409;
CONFLICT = 409,
SEQ_NO = "_seq_no",
PRIMARY_TERM = "_primary_term";
/**
* The ElasticPersistenceProvider reads and writes JSON documents
@@ -104,7 +104,8 @@ define(
// Get a domain object model out of ElasticSearch's response
ElasticPersistenceProvider.prototype.getModel = function (response) {
if (response && response[SRC]) {
this.revs[response[ID]] = response[REV];
this.revs[response[SEQ_NO]] = response[SEQ_NO];
this.revs[response[PRIMARY_TERM]] = response[PRIMARY_TERM];
return response[SRC];
} else {
return undefined;
@@ -116,7 +117,8 @@ define(
// indicate that the request failed.
ElasticPersistenceProvider.prototype.checkResponse = function (response, key) {
if (response && !response.error) {
this.revs[key] = response[REV];
this.revs[SEQ_NO] = response[SEQ_NO];
this.revs[PRIMARY_TERM] = response[PRIMARY_TERM];
return response;
} else {
return this.handleError(response, key);
@@ -147,7 +149,7 @@ define(
function checkUpdate(response) {
return self.checkResponse(response, key);
}
return this.put(key, value, { version: this.revs[key] })
return this.put(key, value)
.then(checkUpdate);
};

View File

@@ -85,7 +85,7 @@ define(
it("allows object creation", function () {
var model = { someKey: "some value" };
mockHttp.and.returnValue(mockPromise({
data: { "_id": "abc", "_version": 1 }
data: { "_id": "abc", "_seq_no": 1, "_primary_term": 1 }
}));
provider.createObject("testSpace", "abc", model).then(capture);
expect(mockHttp).toHaveBeenCalledWith({
@@ -100,7 +100,7 @@ define(
it("allows object models to be read back", function () {
var model = { someKey: "some value" };
mockHttp.and.returnValue(mockPromise({
data: { "_id": "abc", "_version": 1, "_source": model }
data: { "_id": "abc", "_seq_no": 1, "_primary_term": 1, "_source": model }
}));
provider.readObject("testSpace", "abc").then(capture);
expect(mockHttp).toHaveBeenCalledWith({
@@ -117,19 +117,19 @@ define(
// First do a read to populate rev tags...
mockHttp.and.returnValue(mockPromise({
data: { "_id": "abc", "_version": 42, "_source": {} }
data: { "_id": "abc", "_source": {} }
}));
provider.readObject("testSpace", "abc");
// Now perform an update
mockHttp.and.returnValue(mockPromise({
data: { "_id": "abc", "_version": 43, "_source": {} }
data: { "_id": "abc", "_seq_no": 1, "_source": {} }
}));
provider.updateObject("testSpace", "abc", model).then(capture);
expect(mockHttp).toHaveBeenCalledWith({
url: "/test/db/abc",
method: "PUT",
params: { version: 42 },
params: undefined,
data: model
});
expect(capture.calls.mostRecent().args[0]).toBeTruthy();
@@ -138,13 +138,13 @@ define(
it("allows object deletion", function () {
// First do a read to populate rev tags...
mockHttp.and.returnValue(mockPromise({
data: { "_id": "abc", "_version": 42, "_source": {} }
data: { "_id": "abc", "_source": {} }
}));
provider.readObject("testSpace", "abc");
// Now perform an update
mockHttp.and.returnValue(mockPromise({
data: { "_id": "abc", "_version": 42, "_source": {} }
data: { "_id": "abc", "_source": {} }
}));
provider.deleteObject("testSpace", "abc", {}).then(capture);
expect(mockHttp).toHaveBeenCalledWith({
@@ -167,13 +167,13 @@ define(
expect(capture).toHaveBeenCalledWith(undefined);
});
it("handles rejection due to version", function () {
it("handles rejection due to _seq_no", function () {
var model = { someKey: "some value" },
mockErrorCallback = jasmine.createSpy('error');
// First do a read to populate rev tags...
mockHttp.and.returnValue(mockPromise({
data: { "_id": "abc", "_version": 42, "_source": {} }
data: { "_id": "abc", "_seq_no": 1, "_source": {} }
}));
provider.readObject("testSpace", "abc");
@@ -196,7 +196,7 @@ define(
// First do a read to populate rev tags...
mockHttp.and.returnValue(mockPromise({
data: { "_id": "abc", "_version": 42, "_source": {} }
data: { "_id": "abc", "_seq_no": 1, "_source": {} }
}));
provider.readObject("testSpace", "abc");

2
scripts/test-coverage.sh Executable file
View File

@@ -0,0 +1,2 @@
export NODE_OPTIONS=--max_old_space_size=4096
cross-env COVERAGE=true karma start --single-run

View File

@@ -252,6 +252,7 @@ define([
// Plugin's that are installed by default
this.install(this.plugins.Plot());
this.install(this.plugins.PlotlyPlot());
this.install(this.plugins.TelemetryTable());
this.install(PreviewPlugin.default());
this.install(LegacyIndicatorsPlugin());

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
* at runtime from the About dialog for additional information.
*****************************************************************************/
import LadTableSet from './components/LadTableSet.vue';
import Vue from 'vue';
define([
'./components/LadTableSet.vue',
'vue'
], function (
LadTableSet,
Vue
) {
function LADTableSetViewProvider(openmct) {
return {
key: 'LadTableSet',
name: 'LAD Table Set',
cssClass: 'icon-tabular-lad-set',
canView: function (domainObject) {
return domainObject.type === 'LadTableSet';
},
canEdit: function (domainObject) {
return domainObject.type === 'LadTableSet';
},
view: function (domainObject, objectPath) {
let component;
export default function LADTableSetViewProvider(openmct) {
return {
key: 'LadTableSet',
name: 'LAD Table Set',
cssClass: 'icon-tabular-lad-set',
canView: function (domainObject) {
return domainObject.type === 'LadTableSet';
},
canEdit: function (domainObject) {
return domainObject.type === 'LadTableSet';
},
view: function (domainObject, objectPath) {
let component;
return {
show: function (element) {
component = new Vue({
el: element,
components: {
LadTableSet: LadTableSet.default
},
provide: {
openmct,
domainObject,
objectPath
},
template: '<lad-table-set></lad-table-set>'
});
},
destroy: function (element) {
component.$destroy();
component = undefined;
}
};
},
priority: function () {
return 1;
}
};
}
return LADTableSetViewProvider;
});
return {
show: function (element) {
component = new Vue({
el: element,
components: {
LadTableSet: LadTableSet
},
provide: {
openmct,
domainObject,
objectPath
},
template: '<lad-table-set></lad-table-set>'
});
},
destroy: function (element) {
component.$destroy();
component = undefined;
}
};
},
priority: function () {
return 1;
}
};
}

View File

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

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

View File

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

View File

@@ -19,38 +19,36 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
import LADTableViewProvider from './LADTableViewProvider';
import LADTableSetViewProvider from './LADTableSetViewProvider';
import LADTableCompositionPolicy from './LADTableCompositionPolicy';
define([
'./LADTableViewProvider',
'./LADTableSetViewProvider'
], function (
LADTableViewProvider,
LADTableSetViewProvider
) {
return function plugin() {
return function install(openmct) {
openmct.objectViews.addProvider(new LADTableViewProvider(openmct));
openmct.objectViews.addProvider(new LADTableSetViewProvider(openmct));
export default function plugin() {
return function install(openmct) {
openmct.types.addType('LadTable', {
name: "LAD Table",
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.objectViews.addProvider(new LADTableViewProvider(openmct));
openmct.objectViews.addProvider(new LADTableSetViewProvider(openmct));
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.types.addType('LadTable', {
name: "LAD Table",
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', {
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

@@ -70,15 +70,18 @@ export default class ConditionClass extends EventEmitter {
return;
}
this.criteria.forEach(criterion => {
if (this.isAnyOrAllTelemetry(criterion)) {
criterion.getResult(datum, this.conditionManager.telemetryObjects);
} else {
criterion.getResult(datum);
}
});
if (this.isTelemetryUsed(datum.id)) {
this.result = evaluateResults(this.criteria.map(criterion => criterion.result), this.trigger);
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) {

View File

@@ -57,6 +57,7 @@ export default class ConditionManager extends EventEmitter {
endpoint,
this.telemetryReceived.bind(this, endpoint)
);
// TODO check if this is needed
this.updateConditionTelemetry();
}

View File

@@ -47,17 +47,24 @@ describe("The condition", function () {
name: "Test Object",
telemetry: {
values: [{
key: "some-key",
name: "Some attribute",
key: "value",
name: "Value",
hints: {
range: 2
}
},
{
key: "utc",
name: "Time",
format: "utc",
hints: {
domain: 1
}
}, {
key: "some-other-key",
name: "Another attribute",
hints: {
range: 1
}
key: "testSource",
source: "value",
name: "Test",
format: "string"
}]
}
};
@@ -136,4 +143,38 @@ describe("The condition", function () {
expect(result).toBeTrue();
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

@@ -23,10 +23,13 @@
import EventEmitter from 'EventEmitter';
export default class StyleRuleManager extends EventEmitter {
constructor(styleConfiguration, openmct, callback) {
constructor(styleConfiguration, openmct, callback, suppressSubscriptionOnEdit) {
super();
this.openmct = openmct;
this.callback = callback;
if (suppressSubscriptionOnEdit) {
this.openmct.editor.on('isEditing', this.toggleSubscription.bind(this));
}
if (styleConfiguration) {
this.initialize(styleConfiguration);
if (styleConfiguration.conditionSetIdentifier) {
@@ -37,9 +40,25 @@ export default class StyleRuleManager extends EventEmitter {
}
}
toggleSubscription(isEditing) {
this.isEditing = isEditing;
if (this.isEditing) {
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
}
if (this.conditionSetIdentifier) {
this.applySelectedConditionStyle();
}
} else if (this.conditionSetIdentifier) {
this.subscribeToConditionSet();
}
}
initialize(styleConfiguration) {
this.conditionSetIdentifier = styleConfiguration.conditionSetIdentifier;
this.staticStyle = styleConfiguration.staticStyle;
this.selectedConditionId = styleConfiguration.selectedConditionId;
this.defaultConditionId = styleConfiguration.defaultConditionId;
this.updateConditionStylesMap(styleConfiguration.styles || []);
}
@@ -54,7 +73,7 @@ export default class StyleRuleManager extends EventEmitter {
this.handleConditionSetResultUpdated(output[0]);
}
});
this.stopProvidingTelemetry = this.openmct.telemetry.subscribe(conditionSetDomainObject, output => this.handleConditionSetResultUpdated(output));
this.stopProvidingTelemetry = this.openmct.telemetry.subscribe(conditionSetDomainObject, this.handleConditionSetResultUpdated.bind(this));
});
}
@@ -66,9 +85,13 @@ export default class StyleRuleManager extends EventEmitter {
let isNewConditionSet = !this.conditionSetIdentifier ||
!this.openmct.objects.areIdsEqual(this.conditionSetIdentifier, styleConfiguration.conditionSetIdentifier);
this.initialize(styleConfiguration);
//Only resubscribe if the conditionSet has changed.
if (isNewConditionSet) {
this.subscribeToConditionSet();
if (this.isEditing) {
this.applySelectedConditionStyle();
} else {
//Only resubscribe if the conditionSet has changed.
if (isNewConditionSet) {
this.subscribeToConditionSet();
}
}
}
}
@@ -103,6 +126,16 @@ export default class StyleRuleManager extends EventEmitter {
}
}
applySelectedConditionStyle() {
const conditionId = this.selectedConditionId || this.defaultConditionId;
if (!conditionId) {
this.applyStaticStyle();
} else if (this.conditionalStyleMap[conditionId]) {
this.currentStyle = this.conditionalStyleMap[conditionId];
this.updateDomainObjectStyle();
}
}
applyStaticStyle() {
if (this.staticStyle) {
this.currentStyle = this.staticStyle.style;
@@ -123,6 +156,7 @@ export default class StyleRuleManager extends EventEmitter {
}
delete this.stopProvidingTelemetry;
this.conditionSetIdentifier = undefined;
this.isEditing = undefined;
}
}

View File

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

View File

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

View File

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

View File

@@ -79,6 +79,8 @@
<div v-for="(conditionStyle, index) in conditionalStyles"
:key="index"
class="c-inspect-styles__condition"
:class="{'is-current': conditionStyle.conditionId === selectedConditionId}"
@click="applySelectedConditionStyle(conditionStyle.conditionId)"
>
<condition-error :show-label="true"
:condition="getCondition(conditionStyle.conditionId)"
@@ -126,7 +128,8 @@ export default {
isEditing: this.openmct.editor.isEditing(),
conditions: undefined,
conditionsLoaded: false,
navigateToPath: ''
navigateToPath: '',
selectedConditionId: ''
}
},
destroyed() {
@@ -191,6 +194,9 @@ export default {
if (this.stopObservingItems) {
this.stopObservingItems();
}
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
}
},
initialize(conditionSetDomainObject) {
//If there are new conditions in the conditionSet we need to set those styles to default
@@ -200,6 +206,13 @@ export default {
},
setEditState(isEditing) {
this.isEditing = isEditing;
if (this.isEditing) {
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
}
} else {
this.subscribeToConditionSet();
}
},
addConditionSet() {
let conditionSetDomainObject;
@@ -270,6 +283,8 @@ export default {
let domainObjectStyles = (this.domainObject.configuration && this.domainObject.configuration.objectStyles) || {};
if (this.itemId) {
domainObjectStyles[this.itemId].conditionSetIdentifier = undefined;
domainObjectStyles[this.itemId].selectedConditionId = undefined;
domainObjectStyles[this.itemId].defaultConditionId = undefined;
delete domainObjectStyles[this.itemId].conditionSetIdentifier;
domainObjectStyles[this.itemId].styles = undefined;
delete domainObjectStyles[this.itemId].styles;
@@ -278,6 +293,8 @@ export default {
}
} else {
domainObjectStyles.conditionSetIdentifier = undefined;
domainObjectStyles.selectedConditionId = undefined;
domainObjectStyles.defaultConditionId = undefined;
delete domainObjectStyles.conditionSetIdentifier;
domainObjectStyles.styles = undefined;
delete domainObjectStyles.styles;
@@ -287,16 +304,23 @@ export default {
}
this.persist(domainObjectStyles);
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
}
},
updateDomainObjectItemStyles(newItems) {
//check that all items that have been styles still exist. Otherwise delete those styles
let domainObjectStyles = (this.domainObject.configuration && this.domainObject.configuration.objectStyles) || {};
let itemsToRemove = [];
let keys = Object.keys(domainObjectStyles);
//TODO: Need an easier way to find which properties are itemIds
keys.forEach((key) => {
if ((key !== 'styles') &&
const keyIsItemId = (key !== 'styles') &&
(key !== 'staticStyle') &&
(key !== 'conditionSetIdentifier')) {
(key !== 'defaultConditionId') &&
(key !== 'selectedConditionId') &&
(key !== 'conditionSetIdentifier');
if (keyIsItemId) {
if (!(newItems.find(item => item.id === key))) {
itemsToRemove.push(key);
}
@@ -324,6 +348,9 @@ export default {
}
let conditionalStyles = [];
this.conditionSetDomainObject.configuration.conditionCollection.forEach((conditionConfiguration, index) => {
if (conditionConfiguration.isDefault) {
this.selectedConditionId = conditionConfiguration.id;
}
this.conditions[conditionConfiguration.id] = conditionConfiguration;
let foundStyle = this.findStyleByConditionId(conditionConfiguration.id);
if (foundStyle) {
@@ -339,7 +366,27 @@ export default {
//we're doing this so that we remove styles for any conditions that have been removed from the condition set
this.conditionalStyles = conditionalStyles;
this.conditionsLoaded = true;
this.persist(this.getDomainObjectConditionalStyle());
this.persist(this.getDomainObjectConditionalStyle(this.selectedConditionId));
if (!this.isEditing) {
this.subscribeToConditionSet();
}
},
subscribeToConditionSet() {
if (this.stopProvidingTelemetry) {
this.stopProvidingTelemetry();
}
if (this.conditionSetDomainObject) {
this.openmct.telemetry.request(this.conditionSetDomainObject)
.then(output => {
if (output && output.length) {
this.handleConditionSetResultUpdated(output[0]);
}
});
this.stopProvidingTelemetry = this.openmct.telemetry.subscribe(this.conditionSetDomainObject, this.handleConditionSetResultUpdated.bind(this));
}
},
handleConditionSetResultUpdated(resultData) {
this.selectedConditionId = resultData ? resultData.conditionId : '';
},
initializeStaticStyle(objectStyles) {
let staticStyle = objectStyles && objectStyles.staticStyle;
@@ -364,14 +411,19 @@ export default {
let found = this.findStyleByConditionId(conditionStyle.conditionId);
if (found) {
found.style = conditionStyle.style;
this.selectedConditionId = found.conditionId;
this.persist(this.getDomainObjectConditionalStyle());
}
},
getDomainObjectConditionalStyle() {
getDomainObjectConditionalStyle(defaultConditionId) {
let objectStyle = {
styles: this.conditionalStyles,
staticStyle: this.staticStyle
staticStyle: this.staticStyle,
selectedConditionId: this.selectedConditionId
};
if (defaultConditionId) {
objectStyle.defaultConditionId = defaultConditionId;
}
if (this.conditionSetDomainObject) {
objectStyle.conditionSetIdentifier = this.conditionSetDomainObject.identifier;
}
@@ -393,6 +445,10 @@ export default {
getCondition(id) {
return this.conditions ? this.conditions[id] : {};
},
applySelectedConditionStyle(conditionId) {
this.selectedConditionId = conditionId;
this.persist(this.getDomainObjectConditionalStyle());
},
persist(style) {
this.openmct.objects.mutate(this.domainObject, 'configuration.objectStyles', style);
}

View File

@@ -60,6 +60,31 @@
&__condition {
@include discreteItem();
border: 1px solid transparent;
pointer-events: none; // Prevent selecting when the object isn't being edited
&.is-current {
$c: $colorBodyFg;
border-color: rgba($c, 0.2);
background: rgba($c, 0.2);
}
.is-editing & {
cursor: pointer;
pointer-events: initial;
transition: $transOut;
&:hover {
background: rgba($colorBodyFg, 0.1);
transition: $transIn;
}
&.is-current {
$c: $editUIColorBg;
border-color: $c;
background: rgba($c, 0.1);
}
}
}
.c-style {

View File

@@ -34,6 +34,10 @@ const convertToStrings = (input) => {
return stringInputs;
};
const joinValues = (values, length) => {
return values.slice(0, length).join(', ');
};
export const OPERATIONS = [
{
name: 'equalTo',
@@ -44,7 +48,7 @@ export const OPERATIONS = [
appliesTo: ['number'],
inputCount: 1,
getDescription: function (values) {
return ' is ' + values.join(', ');
return ' is ' + joinValues(values, 1);
}
},
{
@@ -56,7 +60,7 @@ export const OPERATIONS = [
appliesTo: ['number'],
inputCount: 1,
getDescription: function (values) {
return ' is not ' + values.join(', ');
return ' is not ' + joinValues(values, 1);
}
},
{
@@ -68,7 +72,7 @@ export const OPERATIONS = [
appliesTo: ['number'],
inputCount: 1,
getDescription: function (values) {
return ' > ' + values.join(', ');
return ' > ' + joinValues(values, 1);
}
},
{
@@ -80,7 +84,7 @@ export const OPERATIONS = [
appliesTo: ['number'],
inputCount: 1,
getDescription: function (values) {
return ' < ' + values.join(', ');
return ' < ' + joinValues(values, 1);
}
},
{
@@ -92,7 +96,7 @@ export const OPERATIONS = [
appliesTo: ['number'],
inputCount: 1,
getDescription: function (values) {
return ' >= ' + values.join(', ');
return ' >= ' + joinValues(values, 1);
}
},
{
@@ -104,7 +108,7 @@ export const OPERATIONS = [
appliesTo: ['number'],
inputCount: 1,
getDescription: function (values) {
return ' <= ' + values.join(', ');
return ' <= ' + joinValues(values, 1);
}
},
{
@@ -146,7 +150,7 @@ export const OPERATIONS = [
appliesTo: ['string'],
inputCount: 1,
getDescription: function (values) {
return ' contains ' + values.join(', ');
return ' contains ' + joinValues(values, 1);
}
},
{
@@ -158,7 +162,7 @@ export const OPERATIONS = [
appliesTo: ['string'],
inputCount: 1,
getDescription: function (values) {
return ' does not contain ' + values.join(', ');
return ' does not contain ' + joinValues(values, 1);
}
},
{
@@ -170,7 +174,7 @@ export const OPERATIONS = [
appliesTo: ['string'],
inputCount: 1,
getDescription: function (values) {
return ' starts with ' + values.join(', ');
return ' starts with ' + joinValues(values, 1);
}
},
{
@@ -182,7 +186,7 @@ export const OPERATIONS = [
appliesTo: ['string'],
inputCount: 1,
getDescription: function (values) {
return ' ends with ' + values.join(', ');
return ' ends with ' + joinValues(values, 1);
}
},
{
@@ -194,7 +198,7 @@ export const OPERATIONS = [
appliesTo: ['string'],
inputCount: 1,
getDescription: function (values) {
return ' is exactly ' + values.join(', ');
return ' is exactly ' + joinValues(values, 1);
}
},
{
@@ -231,7 +235,7 @@ export const OPERATIONS = [
appliesTo: ['enum'],
inputCount: 1,
getDescription: function (values) {
return ' is ' + values.join(', ');
return ' is ' + joinValues(values, 1);
}
},
{
@@ -244,7 +248,7 @@ export const OPERATIONS = [
appliesTo: ['enum'],
inputCount: 1,
getDescription: function (values) {
return ' is not ' + values.join(', ');
return ' is not ' + joinValues(values, 1);
}
},
{

View File

@@ -52,7 +52,7 @@ export default {
},
initObjectStyles() {
if (!this.styleRuleManager) {
this.styleRuleManager = new StyleRuleManager(this.objectStyle, this.openmct, this.updateStyle.bind(this));
this.styleRuleManager = new StyleRuleManager(this.objectStyle, this.openmct, this.updateStyle.bind(this), true);
} else {
this.styleRuleManager.updateObjectStyleConfig(this.objectStyle);
}

View File

@@ -162,19 +162,32 @@ export default {
}).show(this.embed.snapshot.src);
},
changeLocation() {
this.openmct.time.stopClock();
this.openmct.time.bounds({
start: this.embed.bounds.start,
end: this.embed.bounds.end
});
const link = this.embed.historicLink;
if (!link) {
return;
}
const bounds = this.openmct.time.bounds();
const isTimeBoundChanged = this.embed.bounds.start !== bounds.start
&& this.embed.bounds.end !== bounds.end;
const isFixedTimespanMode = !this.openmct.time.clock();
this.openmct.time.stopClock();
window.location.href = link;
const message = 'Time bounds changed to fixed timespan mode';
let message = '';
if (isTimeBoundChanged) {
this.openmct.time.bounds({
start: this.embed.bounds.start,
end: this.embed.bounds.end
});
message = 'Time bound values changed';
}
if (!isFixedTimespanMode) {
message = 'Time bound values changed to fixed timespan mode';
}
this.openmct.notifications.alert(message);
},
formatTime(unixTime, timeFormat) {

View File

@@ -221,7 +221,7 @@ export default {
return position;
},
formatTime(unixTime, timeFormat) {
return Moment(unixTime).format(timeFormat);
return Moment.utc(unixTime).format(timeFormat);
},
moveSnapshot(snapshotId) {
const snapshot = this.snapshotContainer.getSnapshot(snapshotId);

View File

@@ -29,7 +29,7 @@
<script>
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';
export default {
@@ -40,6 +40,18 @@ export default {
default() {
return {};
}
},
ignoreLink: {
type: Boolean,
default() {
return false;
}
},
objectPath: {
type: Array,
default() {
return null;
}
}
},
data() {
@@ -60,28 +72,22 @@ export default {
methods: {
async setNotebookTypes() {
const notebookTypes = [];
let defaultPath = '';
const defaultNotebook = getDefaultNotebook();
if (defaultNotebook) {
const domainObject = await this.openmct.objects.get(defaultNotebook.notebookMeta.identifier)
.then(d => d);
const domainObject = defaultNotebook.domainObject;
if (!domainObject.location) {
clearDefaultNotebook();
} else {
defaultPath = `${domainObject.name} - ${defaultNotebook.section.name} - ${defaultNotebook.page.name}`;
if (domainObject.location) {
const 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({
cssClass: 'icon-notebook',
name: 'Save to Notebook Snapshots',
@@ -97,17 +103,27 @@ export default {
this.showMenu = false;
},
snapshot(notebook) {
let element = document.getElementsByClassName("l-shell__main-container")[0];
const bounds = this.openmct.time.bounds();
const objectPath = this.openmct.router.path;
const snapshotMeta = {
bounds,
link: window.location.href,
objectPath,
openmct: this.openmct
};
this.hideMenu();
this.notebookSnapshot.capture(snapshotMeta, notebook.type, element);
this.$nextTick(() => {
const element = document.querySelector('.c-overlay__contents')
|| document.getElementsByClassName('l-shell__main-container')[0];
const bounds = this.openmct.time.bounds();
const link = !this.ignoreLink
? window.location.href
: null;
const objectPath = this.objectPath || this.openmct.router.path;
const snapshotMeta = {
bounds,
link,
objectPath,
openmct: this.openmct
};
this.notebookSnapshot.capture(snapshotMeta, notebook.type, element);
});
}
}
}

View File

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

View File

@@ -1,6 +1,46 @@
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() {
currentNotebookObject = null;
window.localStorage.setItem(NOTEBOOK_LOCAL_STORAGE, null);
}
@@ -10,20 +50,21 @@ export function getDefaultNotebook() {
return JSON.parse(notebookStorage);
}
export function setDefaultNotebook(notebookStorage) {
window.localStorage.setItem(NOTEBOOK_LOCAL_STORAGE, JSON.stringify(notebookStorage));
export function setDefaultNotebook(openmct, notebookStorage) {
observeDefaultNotebookObject(openmct, notebookStorage);
saveDefaultNotebook(notebookStorage);
}
export function setDefaultNotebookSection(section) {
const notebookStorage = getDefaultNotebook();
notebookStorage.section = section;
window.localStorage.setItem(NOTEBOOK_LOCAL_STORAGE, JSON.stringify(notebookStorage));
saveDefaultNotebook(notebookStorage);
}
export function setDefaultNotebookPage(page) {
const notebookStorage = getDefaultNotebook();
notebookStorage.page = page;
window.localStorage.setItem(NOTEBOOK_LOCAL_STORAGE, JSON.stringify(notebookStorage));
saveDefaultNotebook(notebookStorage);
}

View File

@@ -21,7 +21,7 @@
-->
<div ng-controller="StackedPlotController as stackedPlot"
class="c-plot c-plot--stacked holder holder-plot has-control-bar">
<div class="l-control-bar" ng-show="!stackedPlot.hideExportButtons">
<div class="c-control-bar" ng-show="!stackedPlot.hideExportButtons">
<span class="c-button-set c-button-set--strip-h">
<button class="c-button icon-download"
ng-click="stackedPlot.exportPNG()"

View File

@@ -54,27 +54,29 @@ function (
* @constructor
*/
function MCTChartController($scope) {
this.$scope = $scope;
this.isDestroyed = false;
this.lines = [];
this.pointSets = [];
this.alarmSets = [];
this.offset = {};
this.config = $scope.config;
this.listenTo(this.$scope, '$destroy', this.destroy, this);
this.draw = this.draw.bind(this);
this.scheduleDraw = this.scheduleDraw.bind(this);
this.seriesElements = new WeakMap();
this.$onInit = () => {
this.$scope = $scope;
this.isDestroyed = false;
this.lines = [];
this.pointSets = [];
this.alarmSets = [];
this.offset = {};
this.config = $scope.config;
this.listenTo(this.$scope, '$destroy', this.destroy, this);
this.draw = this.draw.bind(this);
this.scheduleDraw = this.scheduleDraw.bind(this);
this.seriesElements = new WeakMap();
this.listenTo(this.config.series, 'add', this.onSeriesAdd, this);
this.listenTo(this.config.series, 'remove', this.onSeriesRemove, this);
this.listenTo(this.config.yAxis, 'change:key', this.clearOffset, this);
this.listenTo(this.config.xAxis, 'change:key', this.clearOffset, this);
this.listenTo(this.config.yAxis, 'change', this.scheduleDraw);
this.listenTo(this.config.xAxis, 'change', this.scheduleDraw);
this.$scope.$watch('highlights', this.scheduleDraw);
this.$scope.$watch('rectangles', this.scheduleDraw);
this.config.series.forEach(this.onSeriesAdd, this);
this.listenTo(this.config.series, 'add', this.onSeriesAdd, this);
this.listenTo(this.config.series, 'remove', this.onSeriesRemove, this);
this.listenTo(this.config.yAxis, 'change:key', this.clearOffset, this);
this.listenTo(this.config.xAxis, 'change:key', this.clearOffset, this);
this.listenTo(this.config.yAxis, 'change', this.scheduleDraw);
this.listenTo(this.config.xAxis, 'change', this.scheduleDraw);
this.$scope.$watch('highlights', this.scheduleDraw);
this.$scope.$watch('rectangles', this.scheduleDraw);
this.config.series.forEach(this.onSeriesAdd, this);
}
}
eventHelpers.extend(MCTChartController.prototype);

View File

@@ -34,25 +34,27 @@ define([
* values near the cursor.
*/
function MCTPlotController($scope, $element, $window) {
this.$scope = $scope;
this.$scope.config = this.config;
this.$scope.plot = this;
this.$element = $element;
this.$window = $window;
this.$onInit = () => {
this.$scope = $scope;
this.$scope.config = this.config;
this.$scope.plot = this;
this.$element = $element;
this.$window = $window;
this.xScale = new LinearScale(this.config.xAxis.get('displayRange'));
this.yScale = new LinearScale(this.config.yAxis.get('displayRange'));
this.xScale = new LinearScale(this.config.xAxis.get('displayRange'));
this.yScale = new LinearScale(this.config.yAxis.get('displayRange'));
this.pan = undefined;
this.marquee = undefined;
this.pan = undefined;
this.marquee = undefined;
this.chartElementBounds = undefined;
this.tickUpdate = false;
this.chartElementBounds = undefined;
this.tickUpdate = false;
this.$scope.plotHistory = this.plotHistory = [];
this.listenTo(this.$scope, 'plot:clearHistory', this.clear, this);
this.$scope.plotHistory = this.plotHistory = [];
this.listenTo(this.$scope, 'plot:clearHistory', this.clear, this);
this.initialize();
this.initialize();
}
}
MCTPlotController.$inject = ['$scope', '$element', '$window'];
@@ -65,10 +67,10 @@ define([
}
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, 'mouseleave', this.untrackMousePosition, this);
this.listenTo(this.$canvas, 'mousedown', this.onMouseDown, this);
this.listenTo(this.$canvas, 'wheel', this.wheelZoom, this);
this.watchForMarquee();
};
@@ -76,7 +78,6 @@ define([
MCTPlotController.prototype.initialize = function () {
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, 'mouseleave', this.untrackMousePosition, this);
this.listenTo(this.$canvas, 'mousedown', this.onMouseDown, this);
@@ -208,23 +209,6 @@ define([
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) {
this.highlightPoint = point;
this.$scope.$emit('plot:highlight:update', point);
@@ -272,11 +256,23 @@ define([
MCTPlotController.prototype.onMouseUp = function ($event) {
this.stopListening(this.$window, 'mouseup', this.onMouseUp, 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 () {
if (!this.marquee) {
return;
return false;
}
const { start, end } = this.marquee;
@@ -329,7 +325,7 @@ define([
} else {
// A history entry is created by startMarquee, need to remove
// if marquee zoom doesn't occur.
this.back();
this.plotHistory.pop();
}
this.$scope.rectangles = [];
this.marquee = undefined;

View File

@@ -114,15 +114,17 @@ define([
}
function MCTTicksController($scope, $element) {
this.$scope = $scope;
this.$element = $element;
this.$onInit = () => {
this.$scope = $scope;
this.$element = $element;
this.tickCount = 4;
this.tickUpdate = false;
this.listenTo(this.axis, 'change:displayRange', this.updateTicks, this);
this.listenTo(this.axis, 'change:format', this.updateTicks, this);
this.listenTo(this.$scope, '$destroy', this.stopListening, this);
this.updateTicks();
this.tickCount = 4;
this.tickUpdate = false;
this.listenTo(this.axis, 'change:displayRange', this.updateTicks, this);
this.listenTo(this.axis, 'change:format', this.updateTicks, this);
this.listenTo(this.$scope, '$destroy', this.stopListening, this);
this.updateTicks();
}
}
MCTTicksController.$inject = ['$scope', '$element'];

View File

@@ -81,7 +81,8 @@ define(
clonedElement.classList.add(className);
}
element.id = oldId;
}
},
removeContainer: true // Set to false to debug what html2canvas renders
}).then(function (canvas) {
dialog.dismiss();
return new Promise(function (resolve, reject) {

View File

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

View File

@@ -0,0 +1,72 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2019, 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 PlotlyViewLayout from './components/PlotlyViewLayout.vue';
import Vue from 'vue';
export default function PlotlyViewProvider(openmct) {
return {
key: 'plotlyPlot',
name: 'Plotly Plot',
cssClass: 'icon-plot-overlay',
canView: function (domainObject) {
return domainObject.type === 'plotlyPlot';
},
canEdit: function (domainObject) {
return domainObject.type === 'plotlyPlot';
},
view: function (domainObject) {
let component;
return {
show: function (element, isEditing) {
component = new Vue({
provide: {
openmct,
domainObject
},
el: element,
components: {
PlotlyViewLayout
},
data() {
return {
isEditing
}
},
template: '<plotly-view-layout :isEditing="isEditing"></plotly-view-layout>'
});
},
onEditModeChange: function (isEditing) {
component.isEditing = isEditing;
},
destroy: function (element) {
component.$destroy();
component = undefined;
}
};
},
priority: function () {
return 1;
}
};
}

View File

@@ -0,0 +1,221 @@
<template>
<div class="l-view-section"></div>
</template>
<script>
import Plotly from 'plotly.js-dist';
import moment from 'moment'
export default {
inject: ['openmct', 'domainObject'],
data: function () {
return {
telemetryObjects: [],
bounds: this.openmct.time.bounds(),
timeRange: 0,
plotData: {},
subscriptions: {}
}
},
mounted() {
this.plotElement = document.querySelector('.l-view-section');
this.composition = this.openmct.composition.get(this.domainObject);
this.composition.on('add', this.addTelemetry);
this.composition.on('remove', this.removeTelemetry);
this.composition.load();
this.openmct.time.on('bounds', this.refreshData);
this.openmct.time.on('clock', this.changeClock);
},
destroyed() {
this.unsubscribe();
},
methods: {
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 {
hovermode: 'compare',
hoverdistance: -1,
autosize: "true",
showlegend: false,
font: {
family: "'Helvetica Neue', Helvetica, Arial, sans-serif",
size: "12px",
color: "#666"
},
xaxis: { // hardcoded as UTC for now
title: 'UTC',
zeroline: false,
range: isFixed ? 'undefined' : [
this.formatDatumX({utc: this.bounds.start}),
this.formatDatumX({utc: this.bounds.start})
]
},
yaxis: {
title: this.getYAxisLabel(telemetryObject),
zeroline: false
},
margin: {
l: 40,
r: 10,
b: 40,
t: 10
},
paper_bgcolor: 'transparent',
plot_bgcolor: 'transparent'
}
},
removeTelemetry(identifier) {
let keyString = this.openmct.objects.makeKeyString(identifier);
this.unsubscribe(keyString);
this.telemetryObjects = this.telemetryObjects.filter((object) => !_.eq(identifier, object.identifier));
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) {
let timestamp = moment.utc(datum.utc).format('YYYY-MM-DDTHH:mm:ss[Z]');
return timestamp;
},
formatDatumY(datum) {
return datum.sin;
},
addTrace(telemetryData, telemetryObject, index, isAdd) {
let x = [];
let y = [];
const colors = ['red', 'green', 'blue'];
telemetryData.forEach((datum) => {
x.push(this.formatDatumX(datum));
y.push(this.formatDatumY(datum));
})
let traceData = [{ // trace configuration
x,
y,
type: 'scattergl',
mode: 'lines+markers',
line: {
color: colors[index], // to set new color for each trace
shape: 'linear'
}
}];
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));
}
}
},
updateData(datum, index, length) {
// plot all datapoints within bounds
if (datum.utc <= this.bounds.end && this.openmct.time.clock()) {
Plotly.extendTraces(
this.plotElement,
{
x: [[this.formatDatumX(datum)]],
y: [[this.formatDatumY(datum)]]
},
[index], // apply changes to particular trace
length // set the fixed number of points (will drop points from beginning as new points are added)
);
let newRange = {
'xaxis.range': [this.formatDatumX({utc: this.bounds.start}),this.formatDatumX({utc: this.bounds.end})]
};
Plotly.relayout(this.plotElement, newRange);
}
}
}
}
</script>

View File

@@ -0,0 +1,2 @@
.plot svg {
}

View File

@@ -0,0 +1,18 @@
import PlotlyViewProvider from './PlotlyViewProvider.js';
export default function () {
return function install(openmct) {
openmct.objectViews.addProvider(new PlotlyViewProvider(openmct));
openmct.types.addType('plotlyPlot', {
name: "Plotly Plot",
description: "Simple plot rendered by plotly.js",
creatable: true,
cssClass: 'icon-plot-overlay',
initialize: function (domainObject) {
domainObject.composition = [];
domainObject.telemetry = {};
}
});
};
}

View File

@@ -34,6 +34,7 @@ define([
'./URLIndicatorPlugin/URLIndicatorPlugin',
'./telemetryMean/plugin',
'./plot/plugin',
'./plotlyPlot/plugin',
'./telemetryTable/plugin',
'./staticRootPlugin/plugin',
'./notebook/plugin',
@@ -66,6 +67,7 @@ define([
URLIndicatorPlugin,
TelemetryMean,
PlotPlugin,
PlotlyPlotPlugin,
TelemetryTablePlugin,
StaticRootPlugin,
Notebook,
@@ -88,7 +90,8 @@ define([
var bundleMap = {
LocalStorage: 'platform/persistence/local',
MyItems: 'platform/features/my-items',
CouchDB: 'platform/persistence/couch'
CouchDB: 'platform/persistence/couch',
Elasticsearch: 'platform/persistence/elastic'
};
var plugins = _.mapValues(bundleMap, function (bundleName, pluginName) {
@@ -170,8 +173,8 @@ define([
plugins.ExampleImagery = ExampleImagery;
plugins.ImageryPlugin = ImageryPlugin;
plugins.Plot = PlotPlugin;
plugins.PlotlyPlot = PlotlyPlotPlugin.default;
plugins.TelemetryTable = TelemetryTablePlugin;
plugins.SummaryWidget = SummaryWidget;
plugins.TelemetryMean = TelemetryMean;
plugins.URLIndicator = URLIndicatorPlugin;
@@ -180,7 +183,7 @@ define([
plugins.FolderView = FolderView;
plugins.Tabs = Tabs;
plugins.FlexibleLayout = FlexibleLayout;
plugins.LADTable = LADTable;
plugins.LADTable = LADTable.default;
plugins.Filters = Filters;
plugins.ObjectMigration = ObjectMigration.default;
plugins.GoToOriginalAction = GoToOriginalAction.default;

View File

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

@@ -165,7 +165,16 @@
/******************************* LEGACY */
.s-status-taking-snapshot,
.overlay.snapshot {
// Handle overflow-y issues with tables and html2canvas
// Replaces .l-sticky-headers .l-tabular-body { overflow: auto; }
.c-table__body-w { overflow: auto; }
.c-table {
&__body-w {
overflow: auto; // Handle overflow-y issues with tables and html2canvas
}
&-control-bar {
display: none;
+ * {
margin-top: 0 !important;
}
}
}
}

View File

@@ -565,6 +565,20 @@ select {
}
}
}
/******************************************************** CONTROL BARS */
.c-control-bar {
display: flex;
align-items: center;
> * + * {
margin-left: $interiorMarginSm;
}
&__label {
display: inline-block;
white-space: nowrap;
}
}
/******************************************************** PALETTES */
.c-palette {

View File

@@ -40,28 +40,58 @@ mct-plot {
}
}
}
.c-plot,
.gl-plot {
.s-status-taking-snapshot & {
.c-control-bar {
display: none;
}
.gl-plot-y-label__select {
display: none;
}
}
}
.c-plot {
$p: $mainViewPad;
position: absolute;
top: $p; right: $p; bottom: $p; left: $p;
//$p: $mainViewPad;
@include abs($mainViewPad);
//position: absolute;
//top: $p; right: $p; bottom: $p; left: $p;
display: flex;
flex-direction: column;
> * + * {
margin-top: $interiorMargin;
}
.l-control-bar {
flex: 0 0 auto;
}
.l-view-section {
display: flex;
flex: 1 1 auto;
flex-direction: column;
}
&--stacked {
.l-view-section {
// Make this a flex container
display: flex;
flex-flow: column nowrap;
.gl-plot.child-frame {
mct-plot {
display: flex;
flex: 1 1 auto;
height: 100%;
position: relative;
}
flex: 1 1 auto;
&:not(:first-child) {
margin-top: $interiorMargin;
.child-frame {
.has-control-bar {
.c-control-bar {
// Hides buttons per plot element in a stacked plot
display: none;
}
}
mct-plot {
display: flex;
flex: 1 1 auto;
height: 100%;
position: relative;
}
flex: 1 1 auto;
}
.s-status-timeconductor-unsynced .holder-plot {
@@ -70,7 +100,6 @@ mct-plot {
}
}
}
}
@@ -186,7 +215,7 @@ mct-plot {
left: 0; top: 0; right: auto; bottom: 0;
padding-left: 5px;
text-orientation: mixed;
overflow: hidden;
//overflow: hidden;
writing-mode: vertical-lr;
&:before {
// Icon denoting configurability
@@ -339,11 +368,11 @@ mct-plot {
z-index: -10;
.l-view-section {
$m: $interiorMargin;
top: $m !important;
right: $m;
bottom: $m;
left: $m;
//$m: $interiorMargin;
//top: $m !important;
//right: $m;
//bottom: $m;
//left: $m;
.s-status-timeconductor-unsynced .holder-plot {
.t-object-alert.t-alert-unsynced {

View File

@@ -19,53 +19,6 @@
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/******************************************************************* VIEWS */
// From _views.scss
// Legacy overlay and stacked plots depend on this for now
// Styles for sub-dividing views generically
.l-control-bar {
// Element that can be placed above l-view-section, holds controls, buttons, etc.
height: $controlBarH;
}
.c-control-bar {
display: flex;
align-items: center;
> * + * {
margin-left: $interiorMarginSm;
}
&__label {
display: inline-block;
white-space: nowrap;
}
}
.l-view-section {
@include abs();
overflow: auto;
}
.has-control-bar {
.l-view-section {
top: $controlBarH + $interiorMargin;
}
}
.child-frame {
.has-control-bar {
.l-control-bar,
.c-control-bar {
// Hides buttons per plot element in a stacked plot
display: none;
}
.l-view-section {
top: 0;
}
}
}
/*********************************************************************** CLOCKS AND TIMERS */
.c-clock,
.c-timer {

View File

@@ -388,7 +388,21 @@
.s-status-taking-snapshot,
.overlay.snapshot {
// Handle overflow-y issues with tables and html2canvas
background: $colorBodyBg; // Prevent html2canvas from using white background
color: $colorBodyFg;
padding: $interiorMarginSm !important; // Prevents items from going right to the edge of the image
.l-sticky-headers .l-tabular-body { overflow: auto; }
.l-browse-bar {
display: none; // Suppress browse-bar when snapshotting from view-large overlay
+ * {
margin-top: 0 !important; // Remove margin from any following elements
}
}
* {
box-shadow: none !important; // Prevent html2canvas problems with box-shadow
}
}
.c-notebook-snapshot {

View File

@@ -18,6 +18,7 @@
@import "../plugins/folderView/components/list-item.scss";
@import "../plugins/folderView/components/list-view.scss";
@import "../plugins/imagery/components/imagery-view-layout.scss";
@import "../plugins/plotlyPlot/components/plotly.scss";
@import "../plugins/telemetryTable/components/table-row.scss";
@import "../plugins/telemetryTable/components/telemetry-filter-indicator.scss";
@import "../plugins/tabs/components/tabs.scss";

View File

@@ -1,4 +1,6 @@
import MCT from 'MCT';
let nativeFunctions = [],
mockObjects = setMockObjects();
export function createOpenMct() {
const openmct = new MCT();
@@ -16,3 +18,225 @@ export function createMouseEvent(eventName) {
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

@@ -59,6 +59,8 @@
<script>
import ObjectView from './ObjectView.vue'
import ContextMenuDropDown from './contextMenuDropDown.vue';
import PreviewHeader from '@/ui/preview/preview-header.vue';
import Vue from 'vue';
const SIMPLE_CONTENT_TYPES = [
'clock',
@@ -116,13 +118,41 @@ export default {
childElement = parentElement.children[0];
this.openmct.overlays.overlay({
element: childElement,
element: this.getOverlayElement(childElement),
size: 'large',
onDestroy() {
parentElement.append(childElement);
}
});
},
getOverlayElement(childElement) {
const fragment = new DocumentFragment();
const header = this.getPreviewHeader();
fragment.append(header);
fragment.append(childElement);
return fragment;
},
getPreviewHeader() {
const domainObject = this.objectPath[0];
const preview = new Vue({
components: {
PreviewHeader
},
provide: {
openmct: this.openmct,
objectPath: this.objectPath
},
data() {
return {
domainObject
}
},
template: '<PreviewHeader :domainObject="domainObject" :hideViewSwitcher="true" :showNotebookMenuSwitcher="true"></PreviewHeader>'
});
return preview.$mount().$el;
},
getSelectionContext() {
return this.$refs.objectView.getSelectionContext();
}

View File

@@ -201,7 +201,7 @@ export default {
},
initObjectStyles() {
if (!this.styleRuleManager) {
this.styleRuleManager = new StyleRuleManager((this.currentObject.configuration && this.currentObject.configuration.objectStyles), this.openmct, this.updateStyle.bind(this));
this.styleRuleManager = new StyleRuleManager((this.currentObject.configuration && this.currentObject.configuration.objectStyles), this.openmct, this.updateStyle.bind(this), true);
} else {
this.styleRuleManager.updateObjectStyleConfig(this.currentObject.configuration && this.currentObject.configuration.objectStyles);
}

View File

@@ -23,9 +23,12 @@
}
&:not(.c-so-view--no-frame) {
background: $colorBodyBg;
border: $browseFrameBorder;
padding: $interiorMargin;
.is-editing & {
background: rgba($colorBodyBg, 0.8);
}
}
&--no-frame {

View File

@@ -36,6 +36,7 @@
<!-- Action buttons -->
<NotebookMenuSwitcher v-if="notebookEnabled"
:domain-object="domainObject"
:object-path="openmct.router.path"
class="c-notebook-snapshot-menubutton"
/>
<div class="l-browse-bar__actions">
@@ -198,8 +199,6 @@ export default {
updateName(event) {
if (event.target.innerText !== this.domainObject.name && event.target.innerText.match(/\S/)) {
this.openmct.objects.mutate(this.domainObject, 'name', event.target.innerText);
} else {
event.target.innerText = this.domainObject.name;
}
},
updateNameOnEnterKeyPress(event) {

View File

@@ -21,28 +21,12 @@
*****************************************************************************/
<template>
<div class="l-preview-window">
<div class="l-browse-bar">
<div class="l-browse-bar__start">
<div
class="l-browse-bar__object-name--w"
:class="type.cssClass"
>
<span class="l-browse-bar__object-name">
{{ domainObject.name }}
</span>
<context-menu-drop-down :object-path="objectPath" />
</div>
</div>
<div class="l-browse-bar__end">
<div class="l-browse-bar__actions">
<view-switcher
:views="views"
:current-view="currentView"
@setView="setView"
/>
</div>
</div>
</div>
<PreviewHeader
:current-view="currentView"
:domain-object="domainObject"
:views="views"
@setView="setView"
/>
<div class="l-preview-window__object-view">
<div ref="objectView"></div>
</div>
@@ -50,13 +34,13 @@
</template>
<script>
import ContextMenuDropDown from '../../ui/components/contextMenuDropDown.vue';
import ViewSwitcher from '../../ui/layout/ViewSwitcher.vue';
import PreviewHeader from './preview-header.vue';
import {STYLE_CONSTANTS} from "@/plugins/condition/utils/constants";
import StyleRuleManager from "@/plugins/condition/StyleRuleManager";
export default {
components: {
ContextMenuDropDown,
ViewSwitcher
PreviewHeader
},
inject: [
'openmct',
@@ -64,12 +48,9 @@ export default {
],
data() {
let domainObject = this.objectPath[0];
let type = this.openmct.types.get(domainObject.type);
return {
domainObject: domainObject,
type: type,
notebookEnabled: false,
viewKey: undefined
};
},
@@ -90,6 +71,14 @@ export default {
},
destroyed() {
this.view.destroy();
if (this.stopListeningStyles) {
this.stopListeningStyles();
}
if (this.styleRuleManager) {
this.styleRuleManager.destroy();
delete this.styleRuleManager;
}
},
methods: {
clear() {
@@ -97,6 +86,7 @@ export default {
this.view.destroy();
this.$refs.objectView.innerHTML = '';
}
delete this.view;
delete this.viewContainer;
},
@@ -110,6 +100,46 @@ export default {
this.view = this.currentView.view(this.domainObject, this.objectPath);
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];
}
}
});
}
}
}

View File

@@ -36,7 +36,12 @@ export default class PreviewAction {
* Dependencies
*/
this._openmct = openmct;
if (PreviewAction.isVisible === undefined) {
PreviewAction.isVisible = false;
}
}
invoke(objectPath) {
let preview = new Vue({
components: {
@@ -59,16 +64,27 @@ export default class PreviewAction {
callback: () => overlay.dismiss()
}
],
onDestroy: () => preview.$destroy()
onDestroy: () => {
PreviewAction.isVisible = false;
preview.$destroy()
}
});
PreviewAction.isVisible = true;
}
appliesTo(objectPath) {
return !this._isNavigatedObject(objectPath)
return !PreviewAction.isVisible && !this._isNavigatedObject(objectPath);
}
_isNavigatedObject(objectPath) {
let targetObject = objectPath[0];
let navigatedObject = this._openmct.router.path[0];
return targetObject.identifier.namespace === navigatedObject.identifier.namespace &&
targetObject.identifier.key === navigatedObject.identifier.key;
}
_preventPreview(objectPath) {
const noPreviewTypes = ['folder'];
return noPreviewTypes.includes(objectPath[0].type);
}
}

View File

@@ -0,0 +1,92 @@
<template>
<div class="l-browse-bar">
<div class="l-browse-bar__start">
<div
class="l-browse-bar__object-name--w"
:class="type.cssClass"
>
<span class="l-browse-bar__object-name">
{{ domainObject.name }}
</span>
<context-menu-drop-down :object-path="objectPath" />
</div>
</div>
<div class="l-browse-bar__end">
<div class="l-browse-bar__actions">
<view-switcher
:v-if="!hideViewSwitcher"
:views="views"
:current-view="currentView"
@setView="setView"
/>
<NotebookMenuSwitcher v-if="showNotebookMenuSwitcher"
:domain-object="domainObject"
:ignore-link="true"
:object-path="objectPath"
class="c-notebook-snapshot-menubutton"
/>
</div>
</div>
</div>
</template>
<script>
import ContextMenuDropDown from '../../ui/components/contextMenuDropDown.vue';
import NotebookMenuSwitcher from '@/plugins/notebook/components/notebook-menu-switcher.vue';
import ViewSwitcher from '../../ui/layout/ViewSwitcher.vue';
export default {
inject: [
'openmct',
'objectPath'
],
components: {
ContextMenuDropDown,
NotebookMenuSwitcher,
ViewSwitcher
},
props: {
currentView: {
type: Object,
default: () => {
return {};
}
},
domainObject: {
type: Object,
default: () => {
return {};
}
},
hideViewSwitcher: {
type: Boolean,
default: () => {
return false;
}
},
showNotebookMenuSwitcher: {
type: Boolean,
default: () => {
return false;
}
},
views: {
type: Array,
default: () => {
return [];
}
}
},
data() {
return {
type: this.openmct.types.get(this.domainObject.type)
};
},
methods: {
setView(view) {
this.$emit('setView', view);
}
}
}
</script>