mirror of
https://github.com/nasa/openmct.git
synced 2025-06-25 10:44:21 +00:00
Compare commits
871 Commits
forms-retu
...
plotly-imp
Author | SHA1 | Date | |
---|---|---|---|
75d8f64b70 | |||
b9919c19ee | |||
076588441e | |||
afe8382f8d | |||
be18a764c0 | |||
6f4208c3fd | |||
41138a1731 | |||
a54a2f8f84 | |||
15ad5f5719 | |||
5bbe710552 | |||
76df729f2f | |||
ee0b1f04bd | |||
f2d34d7c33 | |||
8fa1770885 | |||
7221dc1ac6 | |||
25bb9939d6 | |||
e7e12504f2 | |||
63bf856d89 | |||
e3dcd51f8d | |||
cb63f4eca1 | |||
3f60c3c0f1 | |||
16bb22e834 | |||
b1467548da | |||
baa7c0bc58 | |||
73b81e38e7 | |||
8b088b7a2c | |||
894da25461 | |||
87d63806b9 | |||
f0e7f8cfc0 | |||
db597e1e93 | |||
352fe8ea7c | |||
29650f54de | |||
98db273f5d | |||
8a6f944655 | |||
bacad24811 | |||
8cc58946cf | |||
3338bc1000 | |||
80c20b3d05 | |||
02d00aeb07 | |||
fd21594e4a | |||
5d0beb4351 | |||
0d9558b891 | |||
c29c3c386f | |||
9ceb3c5b1e | |||
bee3a9eedf | |||
e515d19acd | |||
dd13efe065 | |||
b5dfbe268c | |||
a9b9107cc3 | |||
cfda4e4214 | |||
0a657de4b2 | |||
8153edb9cb | |||
f88d3bcaf3 | |||
22ca339fb9 | |||
c1102ed4b1 | |||
fcd8a9a9c9 | |||
7f8764560b | |||
4411bb0a2d | |||
5f729640b2 | |||
4ecd264d93 | |||
5fc12c771a | |||
7931177497 | |||
195aa0a95b | |||
c252d435bf | |||
87aa5a4342 | |||
bd98b81339 | |||
16677c99c9 | |||
6ab468086a | |||
56794b0ed5 | |||
0398679abc | |||
9d2991ee10 | |||
dadb6120c2 | |||
d9a94db59d | |||
6dd8d448df | |||
1bc60f8108 | |||
ef2db1edaf | |||
3748927e87 | |||
122f3efa1f | |||
7e4aac028b | |||
32791b442d | |||
8e54b8a819 | |||
9e5eddec9b | |||
7aaccdb286 | |||
c46e4c5dad | |||
f0dc928230 | |||
6f674930d9 | |||
8675fc3fa6 | |||
25434342f3 | |||
8044dfe726 | |||
cd6c7fdc5e | |||
7ff85dc396 | |||
fb4877924a | |||
4b13cbdb33 | |||
51c9328dfd | |||
31ac67b393 | |||
f49556adad | |||
0399766ccd | |||
18ab034147 | |||
f726bfa31a | |||
8a4bc2a463 | |||
e9c6c5760e | |||
07c3dd6cfa | |||
771fb9c044 | |||
651a369391 | |||
ae67a2f438 | |||
055cf2b118 | |||
e9cf337aac | |||
04a18248c7 | |||
d462db60de | |||
67ebcf4749 | |||
38dbf2ccab | |||
e9968e3649 | |||
d9fafd2956 | |||
1acda469a9 | |||
d5b0ef735c | |||
a6cac13dfc | |||
3d058151f2 | |||
76817193eb | |||
35ef4407be | |||
f3526f9185 | |||
b5aba7ce8f | |||
0db5648e10 | |||
83325da738 | |||
4d1b2f3456 | |||
6137700c82 | |||
91a1b3f31d | |||
70649b0657 | |||
357b25a76b | |||
3b89bf0b8c | |||
a314aa3c95 | |||
dc5723c227 | |||
61fd7d4e4e | |||
bab53ad9bd | |||
d0d4579f13 | |||
32fc871e67 | |||
6b74a133d8 | |||
ceaf4c2ef0 | |||
982b69e7e1 | |||
8e5182732d | |||
2d0b92cc38 | |||
75e846ea3f | |||
1b5dee981d | |||
4fee7f73e7 | |||
4308a4c9cf | |||
abbffcfbf1 | |||
01710876fb | |||
9e3d97c85d | |||
519075001e | |||
96a48dd9ee | |||
550daae76f | |||
b8fcb8ff14 | |||
373ddd0bf5 | |||
d62cc6b3ee | |||
5116d38437 | |||
29771f2722 | |||
7bfe4bb25c | |||
510c637081 | |||
02b537580c | |||
d7c65fec4c | |||
7073b0717f | |||
d6bb1b2a12 | |||
626e2d8e80 | |||
1fe673c1f5 | |||
d9b00574e7 | |||
7c48b3ba9a | |||
c256696790 | |||
b6e589eed4 | |||
d5480e7524 | |||
ab463e93fe | |||
fb1813c14b | |||
cce834f873 | |||
ca60d02614 | |||
9520c09b49 | |||
0d70717b35 | |||
f18da542d6 | |||
8962b0c88b | |||
e7c38d473b | |||
2f8db31a33 | |||
1c58d8c85f | |||
baf426055c | |||
aa3af520ea | |||
4a43893ccc | |||
77f50a41c9 | |||
e4cd5f441f | |||
a9cfb002fb | |||
b603a5b722 | |||
be165761c7 | |||
da88cf58cc | |||
bc2bd53f9b | |||
6b7fd5f22a | |||
4ee214a142 | |||
4af24db38a | |||
8db27809ef | |||
3116b1addd | |||
5f67c45b50 | |||
8158afc29a | |||
13b3acb7e0 | |||
8363c65312 | |||
04598b6cf1 | |||
3876151a4b | |||
43628ad9d6 | |||
67bea86bc8 | |||
4eb4cbfffc | |||
eda01abcbc | |||
2fa29124bf | |||
694b8f4666 | |||
33faeafa98 | |||
a40ff07353 | |||
41dc9c794d | |||
f1faf3965d | |||
da2ecbbcad | |||
32c892fe98 | |||
bbb271a678 | |||
fec1438806 | |||
28f19ec310 | |||
f934454c25 | |||
eb49ffae02 | |||
5751012872 | |||
aa041e04cf | |||
24e7ea143a | |||
79d5d9c4d0 | |||
b5bfdc4418 | |||
59730c60ec | |||
4a87a5d847 | |||
421c09ec2c | |||
0679b246b8 | |||
83f9c6c528 | |||
a5f3ba6259 | |||
a70facf0c8 | |||
447fe94325 | |||
8e2b666766 | |||
dcbfbdbb89 | |||
4c76bf34ab | |||
81b7a9d3e0 | |||
dc573c479c | |||
23303c910e | |||
3282934cf6 | |||
c157fab081 | |||
7c07b66cc9 | |||
7a906ccf5c | |||
ff7debfb81 | |||
92ba103f45 | |||
2c2d8d6b56 | |||
cfadb9f4fd | |||
396817b2d1 | |||
96eb6d6b74 | |||
cb5d47f66f | |||
ea90d02d66 | |||
95f73d8eb8 | |||
a37c686993 | |||
f12166097c | |||
d103a22fa0 | |||
04a60cfcbb | |||
8d723960f4 | |||
6d3cd2c699 | |||
87bf94fe0a | |||
af93823b6f | |||
4a39ddf425 | |||
83c273b976 | |||
7dd81beb03 | |||
1842d3923c | |||
26838635b6 | |||
11f2c35bb2 | |||
766f48c1ba | |||
da7b93f9b3 | |||
99c095a69f | |||
f885e83505 | |||
928bc4c68a | |||
d5539c7ae4 | |||
c86a104fb6 | |||
46fedc1a30 | |||
3b6ef9b44b | |||
c68edd9b7d | |||
11574b7c40 | |||
abc2cd2413 | |||
5d74882646 | |||
9fe7f230e6 | |||
de4c5b3729 | |||
2a7901914a | |||
73b0fc6f79 | |||
ddef16795c | |||
d188b9a056 | |||
f510f3edd0 | |||
e05b0bb562 | |||
713c5e9fb7 | |||
17bca04560 | |||
61bdadc33c | |||
e0c5bca47d | |||
cdc7c1af64 | |||
3158baa998 | |||
698508fde4 | |||
68a96989e1 | |||
46a6a43234 | |||
d41fc27b55 | |||
24bb96cc90 | |||
483ee173d6 | |||
469e93d916 | |||
f96dfcc942 | |||
063a6c0e51 | |||
7c289d76b6 | |||
c617a440eb | |||
8f81a45b9b | |||
666459be87 | |||
53df89aa5d | |||
8f553f6327 | |||
f91a64483b | |||
de8d63c09d | |||
58b4a6ebf5 | |||
9d45080526 | |||
d3fe2a6811 | |||
97b37edce4 | |||
d3443518d6 | |||
c33314a4bf | |||
59eb034ab4 | |||
67d53fb62b | |||
0fd637c0e9 | |||
169cc6617a | |||
a946325e95 | |||
1beba78111 | |||
2edfeaa606 | |||
2a1f9fd063 | |||
63fe92f8ea | |||
35f303ffa4 | |||
dd70bb470f | |||
250fee125a | |||
956029c786 | |||
b9ab599c35 | |||
5a690932e9 | |||
b00757150e | |||
8515a411bd | |||
3034ec016a | |||
a81af1ce34 | |||
64c5725687 | |||
72a3248123 | |||
ee4a81bdfd | |||
b90eb80584 | |||
ebaf702c59 | |||
3956cd1c06 | |||
064cf6747e | |||
8512b634c7 | |||
4220a8a68a | |||
e7e5116773 | |||
079201273e | |||
e4c9f156a7 | |||
42eeeea374 | |||
7b060509f5 | |||
3ca9e9ae56 | |||
984bede43b | |||
87d838c690 | |||
072bf361de | |||
4e39d9fb84 | |||
14eaf4e899 | |||
a31d10e708 | |||
f9e88321a3 | |||
9e12203b86 | |||
a5326a7b95 | |||
84874f22e6 | |||
d00e8b68a5 | |||
2907d6d79c | |||
a5a4bb87c5 | |||
389589d7f7 | |||
c02cbd1ba7 | |||
90b475e17b | |||
89a298f5b3 | |||
f990a14a3c | |||
6b00af6ece | |||
5ec8ac95c1 | |||
5f061001d6 | |||
ef3c4ccf47 | |||
1a86204637 | |||
0a1959df38 | |||
03a690a158 | |||
459a055455 | |||
502d29dd25 | |||
bf947a8835 | |||
f0fd0a9cc7 | |||
7282792da1 | |||
ec0291c54d | |||
4413c29abb | |||
91e1a144ed | |||
53440c31d5 | |||
5128af2531 | |||
eedc0f13bc | |||
4172fdf1d5 | |||
7c200df4c4 | |||
d1b28e079a | |||
23ec838643 | |||
271c619c63 | |||
0552769670 | |||
99a9f5b3ba | |||
4b535ade31 | |||
de8f8088e2 | |||
32c16416d3 | |||
0cf27c349b | |||
015aa8c637 | |||
4a07ddbefc | |||
a81009541c | |||
1b680cfaca | |||
311ff003c0 | |||
0cae61444d | |||
28a603def8 | |||
d2b7407674 | |||
fff89a6384 | |||
953b95f79c | |||
6ff5ce78e1 | |||
0857fd95a7 | |||
a1f2608e7c | |||
b7cea7b955 | |||
32da19a486 | |||
39d7dc8372 | |||
f76f537be7 | |||
a681d67e05 | |||
0a634eb490 | |||
fcca8fa8d9 | |||
829eecf1ae | |||
dc7f83754a | |||
51498c0e75 | |||
6dbdebbe05 | |||
c994227d5d | |||
761ca7ad56 | |||
7f49a7bc99 | |||
ca022b8a28 | |||
6f500d0d0b | |||
07d101ac1c | |||
cdf0dd0c10 | |||
c27c347d29 | |||
dc54eef2c9 | |||
57a68a24de | |||
2c1b4b4cfc | |||
437e8a0263 | |||
0a2e912091 | |||
87513a14b7 | |||
168c040f3c | |||
78487a48f6 | |||
a5a197680d | |||
d42bd44485 | |||
77b705ecc8 | |||
48af39a584 | |||
ec978f3a35 | |||
f13714e0c4 | |||
42ac3ef9af | |||
26ffe8efde | |||
87b4000b12 | |||
f790c9bd39 | |||
096c9688d5 | |||
1f19f480ce | |||
44f48a3e2a | |||
60fce4a003 | |||
f04b5b689e | |||
05d981768e | |||
e4a6c21101 | |||
d51dd8b7d0 | |||
aed5377ad2 | |||
459b2060a5 | |||
cbeb25c583 | |||
b38a9ad4ce | |||
ecf3e19f16 | |||
3b82fd5d8b | |||
e08b4ff0ab | |||
1f3ec77bf1 | |||
3f61db2067 | |||
ce1fdbddda | |||
5332d136b7 | |||
983ed7f0e7 | |||
11978cd869 | |||
00d1b5e69f | |||
6731283cf8 | |||
6b4cd25417 | |||
0cd2799d00 | |||
243b9cac24 | |||
316e0f24cf | |||
05f94edb49 | |||
e22458f09e | |||
cc2df8401b | |||
43a82ec05f | |||
fe2e29d69b | |||
60aecfe27e | |||
5d21a8b6fe | |||
500ab52476 | |||
b0bb723357 | |||
b7fffeab1c | |||
e339d743ed | |||
84f0d49d6f | |||
090e89d524 | |||
90dd53e954 | |||
6ab60ab52e | |||
8975bc8c55 | |||
55e5c49f6e | |||
f090f7ffe7 | |||
94b5617e63 | |||
41c79c6032 | |||
83c648cc26 | |||
76e7fec1a0 | |||
09bfd80f69 | |||
15a7d03e74 | |||
1dc9743484 | |||
8f05c57d1a | |||
81caa27cba | |||
74a7ef2565 | |||
649575fd2d | |||
b75b7a958a | |||
625b39d722 | |||
65f80f4c45 | |||
02cd3048c8 | |||
63feaef988 | |||
6095872682 | |||
dba55867f4 | |||
0da80c2a67 | |||
084df5329a | |||
49ff0c79db | |||
7a4b967a01 | |||
ddfa611c44 | |||
efca7c8e58 | |||
8900072239 | |||
a7e57c62f4 | |||
24bade2284 | |||
5fcc4eebe1 | |||
27a09239e3 | |||
8d86c914a1 | |||
fab04519c6 | |||
4a5e106709 | |||
4675fc8ae6 | |||
cf9336dae9 | |||
7f32d196e4 | |||
897d05276a | |||
3e6509ce6f | |||
576b843bd5 | |||
9b8b63a0d8 | |||
a1b1fa464e | |||
95f855f905 | |||
5b00246cc0 | |||
47c388450f | |||
34a149661c | |||
4c4b587d9c | |||
b8b838f490 | |||
8cb29ba4a9 | |||
ece6223b23 | |||
ecabd00b0c | |||
768df84f10 | |||
f8b3899bb9 | |||
3b046db4f8 | |||
97f829da9f | |||
fb1eed1982 | |||
dd9b567025 | |||
fa83b4867c | |||
47d4fc9103 | |||
dabd0bff29 | |||
51c70d02d7 | |||
b74733bf3f | |||
84ae65536b | |||
71424dcf8d | |||
2c40396139 | |||
16a0bf9d6c | |||
5498ba8e1e | |||
0f9d7d2832 | |||
9bd1c51a6e | |||
fd74fb0ec4 | |||
3626ff9947 | |||
fd568409d3 | |||
14e3500a88 | |||
83d08ae369 | |||
39bf601ee1 | |||
cfafecdd64 | |||
629ca089cf | |||
89ae6ef8c7 | |||
300acd6ec8 | |||
ba780981a5 | |||
62774678a7 | |||
ac13bc5850 | |||
e526626e09 | |||
564be6f8ba | |||
371a7d3a3e | |||
8539d60562 | |||
d333fd5822 | |||
364191eddc | |||
583f4dac85 | |||
28255dce01 | |||
c9419d3e2d | |||
b386275330 | |||
d2a45e46f1 | |||
35d1727dbf | |||
8125a4f321 | |||
1a409afb03 | |||
e57c18fd69 | |||
3aec9ec6ff | |||
0e9bf74332 | |||
2609a41ee8 | |||
b8dc5acf00 | |||
fbbdf8cff7 | |||
b0edb19239 | |||
85902b878e | |||
9d5c7a4015 | |||
fc53e855c4 | |||
467c57b7c6 | |||
a51c0d5139 | |||
d46310ca7d | |||
8f87cc78e8 | |||
ee6e0f310e | |||
f328a1078e | |||
b4cf81a0ef | |||
1b9b7e2345 | |||
4456633010 | |||
cda97d142a | |||
858199e396 | |||
f504a335af | |||
463ec47af6 | |||
ec4d121a98 | |||
fea6d2df96 | |||
598d2b31e9 | |||
25e28ab97c | |||
43056c4068 | |||
614206b10c | |||
30a493d038 | |||
96e433beaa | |||
0915aaea3b | |||
80656c1be0 | |||
acd75f86f4 | |||
486dae54bd | |||
92ecf3af1d | |||
fd0c19026d | |||
3109c8d825 | |||
78cf75323f | |||
b744467f21 | |||
a0b7999ea2 | |||
2bb2bb6a1b | |||
11ed7027e7 | |||
36bcfd5a41 | |||
70b5c627ca | |||
f4f1d0387b | |||
a1bf4a92e5 | |||
7d2256d70f | |||
5814d2a35e | |||
6ab84c0bc3 | |||
a7fc9b3caa | |||
67f493f012 | |||
0686e6d38f | |||
7fc825949c | |||
2a9ccdcffd | |||
6db78af69f | |||
038489256c | |||
53b785269b | |||
007b14b5c9 | |||
50b331c451 | |||
44fc62e0ba | |||
2635f085f0 | |||
22161fce7f | |||
386fc75047 | |||
fa6dd84945 | |||
d425bd564c | |||
93e3065b3e | |||
0ad2d59924 | |||
f4468a8233 | |||
dc08877bbb | |||
f08caa6135 | |||
ad7d029ce8 | |||
387912b4d3 | |||
53e0ed4d4a | |||
11c205b5c4 | |||
4ede6351ec | |||
24bbcb466f | |||
682601477c | |||
b6b5cfe403 | |||
b6ce9c6ed7 | |||
6e5e8f0ce8 | |||
2415d785cc | |||
2b5d6beb84 | |||
86316d8940 | |||
1f2b5ec5c8 | |||
0a9d23d86f | |||
379e37c881 | |||
37ef269dce | |||
8db6f8f633 | |||
79557165a3 | |||
ec1d4abde9 | |||
07c5e2800a | |||
79811d6662 | |||
67919ece16 | |||
7029dcf09e | |||
38deef6e72 | |||
b6220288ac | |||
fc03b3a79d | |||
096d6371f1 | |||
e580734c95 | |||
2690156a9d | |||
7ac7a40b1b | |||
dc9e572052 | |||
b15ebfd492 | |||
8baee7a0c9 | |||
dc85063467 | |||
be428b326e | |||
dd0e360709 | |||
04da88e3b4 | |||
9bcab02e35 | |||
1ff4d41b7c | |||
04a5c8f69f | |||
8886a94a01 | |||
f25eebdf3f | |||
f9ba46fe85 | |||
6f6fb859d6 | |||
1e3389b427 | |||
c977c64139 | |||
e419149378 | |||
a5a3e41d21 | |||
ecef8eaf86 | |||
de03cfbe64 | |||
03a6de55d6 | |||
3c5047df5e | |||
3cc630d4c2 | |||
b3488c54cd | |||
01b1d66bea | |||
2e82edb306 | |||
8f0e773ac1 | |||
223a0feada | |||
bc9cadaa77 | |||
0fd0da8331 | |||
f42ec7e2c5 | |||
d6a422fbdb | |||
d98b54bea7 | |||
0beda1d053 | |||
e912ab8f4e | |||
5055a18ca1 | |||
fa21911287 | |||
96746f4042 | |||
b22ad3ded9 | |||
7e0f475c63 | |||
efb3c2b71e | |||
862ea6986f | |||
cfa5dcb02e | |||
23aaada79d | |||
9e4458db10 | |||
a8da06033c | |||
0bf3597147 | |||
cfd9730055 | |||
e88ead30dc | |||
67b24ce846 | |||
709c3fff65 | |||
ab6e87ae6b | |||
cdb7066bed | |||
73d0507f1f | |||
4d263bcf32 | |||
2d8f61172d | |||
621c1dc11e | |||
dd136a5ff4 | |||
8fc785bbd6 | |||
82be503f4f | |||
7feb933519 | |||
e806e5a293 | |||
770951c6da | |||
7b7c7b528a | |||
a554aa20f8 | |||
ff1ef1f184 | |||
bf1efaf912 | |||
ff2bc41317 | |||
bdaf8aff15 | |||
1dc4f9f6bb | |||
d6320f5da1 | |||
276be5e857 | |||
3101e77ecc | |||
ab6dae16f1 | |||
36222d79c6 | |||
08656a6674 | |||
8034317796 | |||
a59f3a550e | |||
415b967c0b | |||
642499d519 | |||
fa0a54eee7 | |||
82f175f6c7 | |||
8df549e8d9 | |||
9fd720777b | |||
4c68c725b1 | |||
06a5207c6d | |||
78b885c508 | |||
a18a3b6099 | |||
68949e070c | |||
654333dabe | |||
81b8a76f1b | |||
31736fa194 | |||
33632ef1dc | |||
94305ed82c | |||
c8abc45e25 | |||
cd25459ac9 | |||
aaf1eb8059 | |||
1589e4236a | |||
8ca202d0a9 | |||
d2f7904118 | |||
2d059fb856 | |||
8bbd7898bb | |||
ea6f8c9a50 | |||
d152440436 | |||
1ffe76a525 | |||
a6825f530c | |||
7cf6dc386f | |||
5d8252bb07 | |||
e1e1e0fb2f | |||
4f7345563f | |||
68a2b9f3a8 | |||
d79402c568 | |||
d0e8f650be | |||
d819c6efe2 | |||
91c877f234 | |||
55a674ba7b | |||
36055b7c04 | |||
fe3cc661d3 | |||
eb7efae1cc | |||
63f8fb54d4 | |||
097fa2e655 | |||
3d0b4d51c2 | |||
37650487f7 | |||
6ccc0b4fbf | |||
79fe95372d | |||
6adb190d0e | |||
c094e6c6f4 | |||
8c796b4e57 | |||
c08e9a89ff | |||
03829af2ad | |||
cc8ba18ccc | |||
57c671a42e | |||
1ee6ecf3ae | |||
5f80b3773b | |||
8452455050 | |||
e5d8f60cdb | |||
de466000a0 | |||
49664c011c | |||
cf34d6b127 | |||
e52f6ce099 | |||
1ecdc4c487 | |||
d38e2c49cb | |||
f8464fa76f | |||
308ae2cb2e | |||
88219659fb | |||
c34c2df061 | |||
99c7bd4c10 | |||
f93d5a6fbf | |||
cd116667be | |||
2f2de3952d | |||
45e56798c5 | |||
0664d480e6 | |||
5d31806fb7 | |||
283599ddf5 | |||
09e3ceefa0 | |||
87f76ebfe4 | |||
55a195b841 | |||
c7946fd7b3 | |||
5d3ba3199c | |||
f0d10306fc | |||
161943b5b8 | |||
e545043a26 | |||
40fb58b5b7 | |||
1f9d4708b3 | |||
162809e081 | |||
482c871ac2 | |||
f0b3311630 | |||
656d6d6c3f | |||
ea45f0f4aa | |||
6a25cb0a58 | |||
4a1901420d | |||
ad64f00608 | |||
65aea29cb9 | |||
7981424e9a | |||
10c4340475 | |||
0a95db1a51 | |||
ace77dce65 | |||
c1d58bb25f | |||
fbcafe0f62 | |||
9a9d9222a9 | |||
221e5b4f6c | |||
5df74aee68 | |||
17838d8040 | |||
f82ca91a61 | |||
b06c234b59 | |||
31a7ebd4f1 |
@ -2,7 +2,7 @@ version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/node:8-browsers
|
||||
- image: circleci/node:13-browsers
|
||||
environment:
|
||||
CHROME_BIN: "/usr/bin/google-chrome"
|
||||
steps:
|
||||
@ -11,17 +11,17 @@ jobs:
|
||||
name: Update npm
|
||||
command: 'sudo npm install -g npm@latest'
|
||||
- restore_cache:
|
||||
key: dependency-cache-{{ checksum "package.json" }}
|
||||
key: dependency-cache-13-{{ checksum "package.json" }}
|
||||
- run:
|
||||
name: Installing dependencies (npm install)
|
||||
command: npm install
|
||||
- save_cache:
|
||||
key: dependency-cache-{{ checksum "package.json" }}
|
||||
key: dependency-cache-13-{{ checksum "package.json" }}
|
||||
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
|
||||
|
64
.eslintrc.js
64
.eslintrc.js
@ -1,3 +1,4 @@
|
||||
const LEGACY_FILES = ["platform/**", "example/**"];
|
||||
module.exports = {
|
||||
"env": {
|
||||
"browser": true,
|
||||
@ -10,7 +11,8 @@ module.exports = {
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:vue/recommended"
|
||||
"plugin:vue/recommended",
|
||||
"plugin:you-dont-need-lodash-underscore/compatible"
|
||||
],
|
||||
"parser": "vue-eslint-parser",
|
||||
"parserOptions": {
|
||||
@ -22,6 +24,9 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"you-dont-need-lodash-underscore/omit": "off",
|
||||
"you-dont-need-lodash-underscore/throttle": "off",
|
||||
"you-dont-need-lodash-underscore/flatten": "off",
|
||||
"no-bitwise": "error",
|
||||
"curly": "error",
|
||||
"eqeqeq": "error",
|
||||
@ -66,6 +71,56 @@ module.exports = {
|
||||
],
|
||||
"dot-notation": "error",
|
||||
"indent": ["error", 4],
|
||||
|
||||
// https://eslint.org/docs/rules/no-case-declarations
|
||||
"no-case-declarations": "error",
|
||||
// https://eslint.org/docs/rules/max-classes-per-file
|
||||
"max-classes-per-file": ["error", 1],
|
||||
// https://eslint.org/docs/rules/no-eq-null
|
||||
"no-eq-null": "error",
|
||||
// https://eslint.org/docs/rules/no-eval
|
||||
"no-eval": "error",
|
||||
// https://eslint.org/docs/rules/no-floating-decimal
|
||||
"no-floating-decimal": "error",
|
||||
// https://eslint.org/docs/rules/no-implicit-globals
|
||||
"no-implicit-globals": "error",
|
||||
// https://eslint.org/docs/rules/no-implied-eval
|
||||
"no-implied-eval": "error",
|
||||
// https://eslint.org/docs/rules/no-lone-blocks
|
||||
"no-lone-blocks": "error",
|
||||
// https://eslint.org/docs/rules/no-loop-func
|
||||
"no-loop-func": "error",
|
||||
// https://eslint.org/docs/rules/no-new-func
|
||||
"no-new-func": "error",
|
||||
// https://eslint.org/docs/rules/no-new-wrappers
|
||||
"no-new-wrappers": "error",
|
||||
// https://eslint.org/docs/rules/no-octal-escape
|
||||
"no-octal-escape": "error",
|
||||
// https://eslint.org/docs/rules/no-proto
|
||||
"no-proto": "error",
|
||||
// https://eslint.org/docs/rules/no-return-await
|
||||
"no-return-await": "error",
|
||||
// https://eslint.org/docs/rules/no-script-url
|
||||
"no-script-url": "error",
|
||||
// https://eslint.org/docs/rules/no-self-compare
|
||||
"no-self-compare": "error",
|
||||
// https://eslint.org/docs/rules/no-sequences
|
||||
"no-sequences": "error",
|
||||
// https://eslint.org/docs/rules/no-unmodified-loop-condition
|
||||
"no-unmodified-loop-condition": "error",
|
||||
// https://eslint.org/docs/rules/no-useless-call
|
||||
"no-useless-call": "error",
|
||||
// https://eslint.org/docs/rules/wrap-iife
|
||||
"wrap-iife": "error",
|
||||
// https://eslint.org/docs/rules/no-nested-ternary
|
||||
"no-nested-ternary": "error",
|
||||
// https://eslint.org/docs/rules/switch-colon-spacing
|
||||
"switch-colon-spacing": "error",
|
||||
// https://eslint.org/docs/rules/no-useless-computed-key
|
||||
"no-useless-computed-key": "error",
|
||||
// https://eslint.org/docs/rules/rest-spread-spacing
|
||||
"rest-spread-spacing": ["error"],
|
||||
|
||||
"vue/html-indent": [
|
||||
"error",
|
||||
4,
|
||||
@ -112,6 +167,13 @@ module.exports = {
|
||||
}
|
||||
]
|
||||
}
|
||||
}, {
|
||||
"files": LEGACY_FILES,
|
||||
"rules": {
|
||||
// https://eslint.org/docs/rules/no-nested-ternary
|
||||
"no-nested-ternary": "off",
|
||||
"no-var": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -37,4 +37,8 @@ protractor/logs
|
||||
# npm-debug log
|
||||
npm-debug.log
|
||||
|
||||
# karma reports
|
||||
report.*.json
|
||||
|
||||
package-lock.json
|
||||
report.*.json
|
||||
|
4
API.md
4
API.md
@ -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
|
||||
|
200
CONTRIBUTING.md
200
CONTRIBUTING.md
@ -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 request’s __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 request’s __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.
|
||||
@ -122,97 +131,104 @@ changes.
|
||||
|
||||
### Code Standards
|
||||
|
||||
JavaScript sources in Open MCT must satisfy JSLint under its default
|
||||
settings. This is verified by the command line build.
|
||||
JavaScript sources in Open MCT must satisfy the ESLint rules defined in
|
||||
this repository. This is verified by the command line build.
|
||||
|
||||
#### Code Guidelines
|
||||
|
||||
JavaScript sources in Open MCT should:
|
||||
|
||||
* Use four spaces for indentation. Tabs should not be used.
|
||||
* Include JSDoc for any exposed API (e.g. public methods, constructors).
|
||||
* Include non-JSDoc comments as-needed for explaining private variables,
|
||||
methods, or algorithms when they are non-obvious.
|
||||
* Define one public class per script, expressed as a constructor function
|
||||
returned from an AMD-style module.
|
||||
* Follow “Java-like” naming conventions. These includes:
|
||||
* Classes should use camel case, first letter capitalized
|
||||
(e.g. SomeClassName).
|
||||
* Methods, variables, fields, and function names should use camel case,
|
||||
first letter lower-case (e.g. someVariableName).
|
||||
* Constants (variables or fields which are meant to be declared and
|
||||
initialized statically, and never changed) should use only capital
|
||||
letters, with underscores between words (e.g. SOME_CONSTANT).
|
||||
* File names should be the name of the exported class, plus a .js extension
|
||||
(e.g. SomeClassName.js).
|
||||
* Avoid anonymous functions, except when functions are short (a few lines)
|
||||
and/or their inclusion makes sense within the flow of the code
|
||||
(e.g. as arguments to a forEach call).
|
||||
* Avoid deep nesting (especially of functions), except where necessary
|
||||
(e.g. due to closure scope).
|
||||
* End with a single new-line character.
|
||||
* Expose public methods by declaring them on the class's prototype.
|
||||
* Within a given function's scope, do not mix declarations and imperative
|
||||
code, and present these in the following order:
|
||||
* First, variable declarations and initialization.
|
||||
* Second, function declarations.
|
||||
* Third, imperative statements.
|
||||
* Finally, the returned value.
|
||||
The following guidelines are provided for anyone contributing source code to the Open MCT project:
|
||||
|
||||
1. Write clean code. Here’s a good summary - https://github.com/ryanmcdermott/clean-code-javascript.
|
||||
1. Include JSDoc for any exposed API (e.g. public methods, classes).
|
||||
1. Include non-JSDoc comments as-needed for explaining private variables,
|
||||
methods, or algorithms when they are non-obvious. Otherwise code
|
||||
should be self-documenting.
|
||||
1. Classes and Vue components should use camel case, first letter capitalized
|
||||
(e.g. SomeClassName).
|
||||
1. Methods, variables, fields, events, and function names should use camelCase,
|
||||
first letter lower-case (e.g. someVariableName).
|
||||
1. Source files that export functions should use camelCase, first letter lower-case (eg. testTools.js)
|
||||
1. Constants (variables or fields which are meant to be declared and
|
||||
initialized statically, and never changed) should use only capital
|
||||
letters, with underscores between words (e.g. SOME_CONSTANT). They should always be declared as `const`s
|
||||
1. File names should be the name of the exported class, plus a .js extension
|
||||
(e.g. SomeClassName.js).
|
||||
1. Avoid anonymous functions, except when functions are short (one or two lines)
|
||||
and their inclusion makes sense within the flow of the code
|
||||
(e.g. as arguments to a forEach call). Anonymous functions should always be arrow functions.
|
||||
1. Named functions are preferred over functions assigned to variables.
|
||||
eg.
|
||||
```JavaScript
|
||||
function renameObject(object, newName) {
|
||||
Object.name = newName;
|
||||
}
|
||||
```
|
||||
is preferable to
|
||||
```JavaScript
|
||||
const rename = (object, newName) => {
|
||||
Object.name = newName;
|
||||
}
|
||||
```
|
||||
1. Avoid deep nesting (especially of functions), except where necessary
|
||||
(e.g. due to closure scope).
|
||||
1. End with a single new-line character.
|
||||
1. Always use ES6 `Class`es and inheritence rather than the pre-ES6 prototypal
|
||||
pattern.
|
||||
1. Within a given function's scope, do not mix declarations and imperative
|
||||
code, and present these in the following order:
|
||||
* First, variable declarations and initialization.
|
||||
* Secondly, imperative statements.
|
||||
* Finally, the returned value. A single return statement at the end of the function should be used, except where an early return would improve code clarity.
|
||||
1. Avoid the use of "magic" values.
|
||||
eg.
|
||||
```JavaScript
|
||||
Const UNAUTHORIZED = 401
|
||||
if (responseCode === UNAUTHORIZED)
|
||||
```
|
||||
is preferable to
|
||||
```JavaScript
|
||||
if (responseCode === 401)
|
||||
```
|
||||
1. Use the ternary operator only for simple cases such as variable assignment. Nested ternaries should be avoided in all cases.
|
||||
1. Test specs should reside alongside the source code they test, not in a separate directory.
|
||||
1. Organize code by feature, not by type.
|
||||
eg.
|
||||
```
|
||||
- telemetryTable
|
||||
- row
|
||||
TableRow.js
|
||||
TableRowCollection.js
|
||||
TableRow.vue
|
||||
- column
|
||||
TableColumn.js
|
||||
TableColumn.vue
|
||||
plugin.js
|
||||
pluginSpec.js
|
||||
```
|
||||
is preferable to
|
||||
```
|
||||
- telemetryTable
|
||||
- components
|
||||
TableRow.vue
|
||||
TableColumn.vue
|
||||
- collections
|
||||
TableRowCollection.js
|
||||
TableColumn.js
|
||||
TableRow.js
|
||||
plugin.js
|
||||
pluginSpec.js
|
||||
```
|
||||
Deviations from Open MCT code style guidelines require two-party agreement,
|
||||
typically from the author of the change and its reviewer.
|
||||
|
||||
#### Code Example
|
||||
|
||||
```js
|
||||
/*global define*/
|
||||
|
||||
/**
|
||||
* Bundles should declare themselves as namespaces in whichever source
|
||||
* file is most like the "main point of entry" to the bundle.
|
||||
* @namespace some/bundle
|
||||
*/
|
||||
define(
|
||||
['./OtherClass'],
|
||||
function (OtherClass) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* A summary of how to use this class goes here.
|
||||
*
|
||||
* @constructor
|
||||
* @memberof some/bundle
|
||||
*/
|
||||
function ExampleClass() {
|
||||
}
|
||||
|
||||
// Methods which are not intended for external use should
|
||||
// not have JSDoc (or should be marked @private)
|
||||
ExampleClass.prototype.privateMethod = function () {
|
||||
};
|
||||
|
||||
/**
|
||||
* A summary of this method goes here.
|
||||
* @param {number} n a parameter
|
||||
* @returns {number} a return value
|
||||
*/
|
||||
ExampleClass.prototype.publicMethod = function (n) {
|
||||
return n * 2;
|
||||
}
|
||||
|
||||
return ExampleClass;
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
### Test Standards
|
||||
|
||||
Automated testing shall occur whenever changes are merged into the main
|
||||
development branch and must be confirmed alongside any pull request.
|
||||
|
||||
Automated tests are typically unit tests which exercise individual software
|
||||
components. Tests are subject to code review along with the actual
|
||||
implementation, to ensure that tests are applicable and useful.
|
||||
Automated tests are tests which exercise plugins, API, and utility classes.
|
||||
Tests are subject to code review along with the actual implementation, to
|
||||
ensure that tests are applicable and useful.
|
||||
|
||||
Examples of useful tests:
|
||||
* Tests which replicate bugs (or their root causes) to verify their
|
||||
@ -222,8 +238,26 @@ Examples of useful tests:
|
||||
* Tests which verify expected interactions with other components in the
|
||||
system.
|
||||
|
||||
During automated testing, code coverage metrics will be reported. Line
|
||||
coverage must remain at or above 80%.
|
||||
#### Guidelines
|
||||
* 100% statement coverage is achievable and desirable.
|
||||
* Do blackbox testing. Test external behaviors, not internal details. Write tests that describe what your plugin is supposed to do. How it does this doesn't matter, so don't test it.
|
||||
* Unit test specs for plugins should be defined at the plugin level. Start with one test spec per plugin named pluginSpec.js, and as this test spec grows too big, break it up into multiple test specs that logically group related tests.
|
||||
* Unit tests for API or for utility functions and classes may be defined at a per-source file level.
|
||||
* Wherever possible only use and mock public API, builtin functions, and UI in your test specs. Do not directly invoke any private functions. ie. only call or mock functions and objects exposed by openmct.* (eg. openmct.telemetry, openmct.objectView, etc.), and builtin browser functions (fetch, requestAnimationFrame, setTimeout, etc.).
|
||||
* Where builtin functions have been mocked, be sure to clear them between tests.
|
||||
* Test at an appropriate level of isolation. Eg.
|
||||
* If you’re testing a view, you do not need to test the whole application UI, you can just fetch the view provider using the public API and render the view into an element that you have created.
|
||||
* You do not need to test that the view switcher works, there should be separate tests for that.
|
||||
* You do not need to test that telemetry providers work, you can mock openmct.telemetry.request() to feed test data to the view.
|
||||
* Use your best judgement when deciding on appropriate scope.
|
||||
* Automated tests for plugins should start by actually installing the plugin being tested, and then test that installing the plugin adds the desired features and behavior to Open MCT, observing the above rules.
|
||||
* All variables used in a test spec, including any instances of the Open MCT API should be declared inside of an appropriate block scope (not at the root level of the source file), and should be initialized in the relevant beforeEach block. `beforeEach` is preferable to `beforeAll` to avoid leaking of state between tests.
|
||||
* A `afterEach` or `afterAll` should be used to do any clean up necessary to prevent leakage of state between test specs. This can happen when functions on `window` are wrapped, or when the URL is changed. [A convenience function](https://github.com/nasa/openmct/blob/master/src/utils/testing.js#L59) is provided for resetting the URL and clearing builtin spies between tests.
|
||||
* If writing unit tests for legacy Angular code be sure to follow [best practices in order to avoid memory leaks](https://www.thecodecampus.de/blog/avoid-memory-leaks-angularjs-unit-tests/).
|
||||
|
||||
#### Examples
|
||||
* [Example of an automated test spec for an object view plugin](https://github.com/nasa/openmct/blob/master/src/plugins/telemetryTable/pluginSpec.js)
|
||||
* [Example of an automated test spec for API](https://github.com/nasa/openmct/blob/master/src/api/time/TimeAPISpec.js)
|
||||
|
||||
### Commit Message Standards
|
||||
|
||||
@ -292,6 +326,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 +334,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)
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Open MCT License
|
||||
|
||||
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, 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.
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
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.
|
||||
|
||||
@ -18,4 +18,4 @@
|
||||
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.
|
||||
-->
|
||||
-->
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*
|
||||
@ -18,4 +18,4 @@
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
*****************************************************************************/
|
||||
|
@ -125,3 +125,22 @@ A release is not closed until both categories have been performed on
|
||||
the latest snapshot of the software, _and_ no issues labelled as
|
||||
["blocker" or "critical"](https://github.com/nasa/openmctweb/blob/master/CONTRIBUTING.md#issue-reporting)
|
||||
remain open.
|
||||
|
||||
### Testathons
|
||||
Testathons can be used as a means of performing per-sprint and per-release testing.
|
||||
|
||||
#### Timing
|
||||
For per-sprint testing, a testathon is typically performed at the beginning of the third week of a sprint, and again later that week to verify any fixes. For per-release testing, a testathon is typically performed prior to any formal testing processes that are applicable to that release.
|
||||
|
||||
#### Process
|
||||
|
||||
1. Prior to the scheduled testathon, a list will be compiled of all issues that are closed and unverified.
|
||||
2. For each issue, testers should review the associated PR for testing instructions. See the contributing guide for instructions on [pull requests](https://github.com/nasa/openmct/blob/master/CONTRIBUTING.md#merging).
|
||||
3. As each issue is verified via testing, any team members testing it should leave a comment on that issue indicating that it has been verified fixed.
|
||||
4. If a bug is found that relates to an issue being tested, notes should be included on the associated issue, and the issue should be reopened. Bug notes should include reproduction steps.
|
||||
5. For any bugs that are not obviously related to any of the issues under test, a new issue should be created with details about the bug, including reproduction steps. If unsure about whether a bug relates to an issue being tested, just create a new issue.
|
||||
6. At the end of the testathon, triage will take place, where all tested issues will be reviewed.
|
||||
7. If verified fixed, an issue will remain closed, and will have the “unverified” label removed.
|
||||
8. For any bugs found, a severity will be assigned.
|
||||
9. A second testathon will be scheduled for later in the week that will aim to address all issues identified as blockers, as well as any other issues scoped by the team during triage.
|
||||
10. Any issues that were not tested will remain "unverified" and will be picked up in the next testathon.
|
||||
|
@ -9,7 +9,8 @@ define([
|
||||
values: [
|
||||
{
|
||||
key: "name",
|
||||
name: "Name"
|
||||
name: "Name",
|
||||
format: "string"
|
||||
},
|
||||
{
|
||||
key: "utc",
|
||||
@ -27,6 +28,16 @@ define([
|
||||
domain: 2
|
||||
}
|
||||
},
|
||||
// Need to enable "LocalTimeSystem" plugin to make use of this
|
||||
// {
|
||||
// key: "local",
|
||||
// name: "Time",
|
||||
// format: "local-format",
|
||||
// source: "utc",
|
||||
// hints: {
|
||||
// domain: 3
|
||||
// }
|
||||
// },
|
||||
{
|
||||
key: "sin",
|
||||
name: "Sine",
|
||||
@ -49,7 +60,8 @@ define([
|
||||
values: [
|
||||
{
|
||||
key: "name",
|
||||
name: "Name"
|
||||
name: "Name",
|
||||
format: "string"
|
||||
},
|
||||
{
|
||||
key: "utc",
|
||||
@ -59,6 +71,15 @@ define([
|
||||
domain: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "local",
|
||||
name: "Time",
|
||||
format: "utc",
|
||||
source: "utc",
|
||||
hints: {
|
||||
domain: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "state",
|
||||
source: "value",
|
||||
@ -98,7 +119,7 @@ define([
|
||||
};
|
||||
|
||||
GeneratorMetadataProvider.prototype.getMetadata = function (domainObject) {
|
||||
return _.extend(
|
||||
return Object.assign(
|
||||
{},
|
||||
domainObject.telemetry,
|
||||
METADATA_BY_TYPE[domainObject.type]
|
||||
|
@ -31,6 +31,7 @@ define([
|
||||
period: 10,
|
||||
offset: 0,
|
||||
dataRateInHz: 1,
|
||||
randomness: 0,
|
||||
phase: 0
|
||||
};
|
||||
|
||||
@ -52,7 +53,8 @@ define([
|
||||
'period',
|
||||
'offset',
|
||||
'dataRateInHz',
|
||||
'phase'
|
||||
'phase',
|
||||
'randomness'
|
||||
];
|
||||
|
||||
request = request || {};
|
||||
|
@ -65,8 +65,8 @@
|
||||
name: data.name,
|
||||
utc: nextStep,
|
||||
yesterday: nextStep - 60*60*24*1000,
|
||||
sin: sin(nextStep, data.period, data.amplitude, data.offset, data.phase),
|
||||
cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase)
|
||||
sin: sin(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness),
|
||||
cos: cos(nextStep, data.period, data.amplitude, data.offset, data.phase, data.randomness)
|
||||
}
|
||||
});
|
||||
nextStep += step;
|
||||
@ -99,6 +99,7 @@
|
||||
var offset = request.offset;
|
||||
var dataRateInHz = request.dataRateInHz;
|
||||
var phase = request.phase;
|
||||
var randomness = request.randomness;
|
||||
|
||||
var step = 1000 / dataRateInHz;
|
||||
var nextStep = start - (start % step) + step;
|
||||
@ -110,8 +111,8 @@
|
||||
name: request.name,
|
||||
utc: nextStep,
|
||||
yesterday: nextStep - 60*60*24*1000,
|
||||
sin: sin(nextStep, period, amplitude, offset, phase),
|
||||
cos: cos(nextStep, period, amplitude, offset, phase)
|
||||
sin: sin(nextStep, period, amplitude, offset, phase, randomness),
|
||||
cos: cos(nextStep, period, amplitude, offset, phase, randomness)
|
||||
});
|
||||
}
|
||||
self.postMessage({
|
||||
@ -120,14 +121,14 @@
|
||||
});
|
||||
}
|
||||
|
||||
function cos(timestamp, period, amplitude, offset, phase) {
|
||||
function cos(timestamp, period, amplitude, offset, phase, randomness) {
|
||||
return amplitude *
|
||||
Math.cos(phase + (timestamp / period / 1000 * Math.PI * 2)) + offset;
|
||||
Math.cos(phase + (timestamp / period / 1000 * Math.PI * 2)) + (amplitude * Math.random() * randomness) + offset;
|
||||
}
|
||||
|
||||
function sin(timestamp, period, amplitude, offset, phase) {
|
||||
function sin(timestamp, period, amplitude, offset, phase, randomness) {
|
||||
return amplitude *
|
||||
Math.sin(phase + (timestamp / period / 1000 * Math.PI * 2)) + offset;
|
||||
Math.sin(phase + (timestamp / period / 1000 * Math.PI * 2)) + (amplitude * Math.random() * randomness) + offset;
|
||||
}
|
||||
|
||||
function sendError(error, message) {
|
||||
|
@ -122,6 +122,17 @@ define([
|
||||
"telemetry",
|
||||
"phase"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Randomness",
|
||||
control: "numberfield",
|
||||
cssClass: "l-input-sm l-numeric",
|
||||
key: "randomness",
|
||||
required: true,
|
||||
property: [
|
||||
"telemetry",
|
||||
"randomness"
|
||||
]
|
||||
}
|
||||
],
|
||||
initialize: function (object) {
|
||||
@ -130,7 +141,8 @@ define([
|
||||
amplitude: 1,
|
||||
offset: 0,
|
||||
dataRateInHz: 1,
|
||||
phase: 0
|
||||
phase: 0,
|
||||
randomness: 0
|
||||
};
|
||||
}
|
||||
});
|
||||
|
@ -52,6 +52,7 @@ define([
|
||||
return {
|
||||
name: name,
|
||||
utc: Math.floor(timestamp / 5000) * 5000,
|
||||
local: Math.floor(timestamp / 5000) * 5000,
|
||||
url: IMAGE_SAMPLES[Math.floor(timestamp / 5000) % IMAGE_SAMPLES.length]
|
||||
};
|
||||
}
|
||||
@ -78,7 +79,7 @@ define([
|
||||
},
|
||||
request: function (domainObject, options) {
|
||||
var start = options.start;
|
||||
var end = options.end;
|
||||
var end = Math.min(options.end, Date.now());
|
||||
var data = [];
|
||||
while (start <= end && data.length < 5000) {
|
||||
data.push(pointForTimestamp(start, domainObject.name));
|
||||
@ -118,6 +119,14 @@ define([
|
||||
name: 'Time',
|
||||
key: 'utc',
|
||||
format: 'utc',
|
||||
hints: {
|
||||
domain: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Local Time',
|
||||
key: 'local',
|
||||
format: 'local-format',
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
|
@ -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>
|
||||
|
||||
|
49
index.html
49
index.html
@ -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.
|
||||
|
||||
@ -34,8 +34,8 @@
|
||||
<body>
|
||||
</body>
|
||||
<script>
|
||||
const FIVE_MINUTES = 5 * 60 * 1000;
|
||||
const THIRTY_MINUTES = 30 * 60 * 1000;
|
||||
const THIRTY_SECONDS = 30 * 1000;
|
||||
const THIRTY_MINUTES = THIRTY_SECONDS * 60;
|
||||
|
||||
[
|
||||
'example/eventGenerator'
|
||||
@ -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());
|
||||
@ -63,7 +63,39 @@
|
||||
bounds: {
|
||||
start: Date.now() - THIRTY_MINUTES,
|
||||
end: Date.now()
|
||||
}
|
||||
},
|
||||
// commonly used bounds can be stored in history
|
||||
// bounds (start and end) can accept either a milliseconds number
|
||||
// or a callback function returning a milliseconds number
|
||||
// a function is useful for invoking Date.now() at exact moment of preset selection
|
||||
presets: [
|
||||
{
|
||||
label: 'Last Day',
|
||||
bounds: {
|
||||
start: () => Date.now() - 1000 * 60 * 60 * 24,
|
||||
end: () => Date.now()
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Last 2 hours',
|
||||
bounds: {
|
||||
start: () => Date.now() - 1000 * 60 * 60 * 2,
|
||||
end: () => Date.now()
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Last hour',
|
||||
bounds: {
|
||||
start: () => Date.now() - 1000 * 60 * 60,
|
||||
end: () => Date.now()
|
||||
}
|
||||
}
|
||||
],
|
||||
// maximum recent bounds to retain in conductor history
|
||||
records: 10,
|
||||
// maximum duration between start and end bounds
|
||||
// for utc-based time systems this is in milliseconds
|
||||
limit: 1000 * 60 * 60 * 24
|
||||
},
|
||||
{
|
||||
name: "Realtime",
|
||||
@ -71,7 +103,7 @@
|
||||
clock: 'local',
|
||||
clockOffsets: {
|
||||
start: - THIRTY_MINUTES,
|
||||
end: FIVE_MINUTES
|
||||
end: THIRTY_SECONDS
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -81,7 +113,10 @@
|
||||
openmct.install(openmct.plugins.LADTable());
|
||||
openmct.install(openmct.plugins.Filters(['table', 'telemetry.plot.overlay']));
|
||||
openmct.install(openmct.plugins.ObjectMigration());
|
||||
openmct.install(openmct.plugins.ClearData(['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked']));
|
||||
openmct.install(openmct.plugins.ClearData(
|
||||
['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked'],
|
||||
{indicator: true}
|
||||
));
|
||||
openmct.start();
|
||||
</script>
|
||||
</html>
|
||||
|
@ -23,17 +23,28 @@
|
||||
/*global module,process*/
|
||||
|
||||
const devMode = process.env.NODE_ENV !== 'production';
|
||||
const browsers = [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'ChromeHeadless'];
|
||||
const browsers = [process.env.NODE_ENV === 'debug' ? 'ChromeDebugging' : 'FirefoxHeadless'];
|
||||
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,33 +68,34 @@ 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: {
|
||||
stats: 'errors-only',
|
||||
logLevel: 'warn'
|
||||
},
|
||||
singleRun: true
|
||||
singleRun: true,
|
||||
browserNoActivityTimeout: 90000
|
||||
});
|
||||
}
|
||||
|
29
package.json
29
package.json
@ -2,10 +2,8 @@
|
||||
"name": "openmct",
|
||||
"version": "1.0.0-snapshot",
|
||||
"description": "The Open MCT core platform",
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"acorn": "6.2.0",
|
||||
"angular": "1.4.14",
|
||||
"angular": ">=1.8.0",
|
||||
"angular-route": "1.4.14",
|
||||
"babel-eslint": "8.2.6",
|
||||
"comma-separated-values": "^3.6.4",
|
||||
@ -25,6 +23,7 @@
|
||||
"d3-time-format": "2.1.x",
|
||||
"eslint": "5.2.0",
|
||||
"eslint-plugin-vue": "^6.0.0",
|
||||
"eslint-plugin-you-dont-need-lodash-underscore": "^6.10.0",
|
||||
"eventemitter3": "^1.2.0",
|
||||
"exports-loader": "^0.7.0",
|
||||
"express": "^4.13.1",
|
||||
@ -41,29 +40,33 @@
|
||||
"jsdoc": "^3.3.2",
|
||||
"karma": "^2.0.3",
|
||||
"karma-chrome-launcher": "^2.2.0",
|
||||
"karma-firefox-launcher": "^1.3.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",
|
||||
"karma-webpack": "^3.0.0",
|
||||
"location-bar": "^3.0.1",
|
||||
"lodash": "^3.10.1",
|
||||
"lodash": "^4.17.12",
|
||||
"markdown-toc": "^0.11.7",
|
||||
"marked": "^0.3.5",
|
||||
"mini-css-extract-plugin": "^0.4.1",
|
||||
"minimist": "^1.1.1",
|
||||
"moment": "^2.11.1",
|
||||
"moment": "2.25.3",
|
||||
"moment-duration-format": "^2.2.2",
|
||||
"moment-timezone": "^0.5.21",
|
||||
"moment-timezone": "0.5.28",
|
||||
"node-bourbon": "^4.2.3",
|
||||
"node-sass": "^4.9.2",
|
||||
"painterro": "^0.2.65",
|
||||
"painterro": "^1.0.35",
|
||||
"plotly.js-dist": "^1.54.5",
|
||||
"printj": "^1.2.1",
|
||||
"raw-loader": "^0.5.1",
|
||||
"request": "^2.69.0",
|
||||
"split": "^1.0.0",
|
||||
"style-loader": "^1.0.1",
|
||||
"uuid": "^3.3.3",
|
||||
"v8-compile-cache": "^1.1.0",
|
||||
"vue": "2.5.6",
|
||||
"vue-loader": "^15.2.6",
|
||||
@ -75,15 +78,17 @@
|
||||
"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:watch": "karma start --no-single-run",
|
||||
"test": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --single-run",
|
||||
"test:debug": "cross-env NODE_ENV=debug karma start --no-single-run",
|
||||
"test:coverage": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" COVERAGE=true karma start --single-run",
|
||||
"test:watch": "cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" karma start --no-single-run",
|
||||
"verify": "concurrently 'npm:test' 'npm:lint'",
|
||||
"jsdoc": "jsdoc -c jsdoc.json -R API.md -r -d dist/docs/api",
|
||||
"otherdoc": "node docs/gendocs.js --in docs/src --out dist/docs --suppress-toc 'docs/src/index.md|docs/src/process/index.md'",
|
||||
|
@ -6,6 +6,12 @@
|
||||
ng-show="ngModel.dialog.messages.length > 1 ||
|
||||
ngModel.dialog.messages.length == 0">s</span>
|
||||
</div>
|
||||
<button
|
||||
ng-if="ngModel.dialog.topBarButton"
|
||||
class="c-button c-button--major"
|
||||
ng-click="ngModel.topBarButton.onClick">
|
||||
{{ ngModel.topBarButton.label }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="w-messages c-overlay__messages">
|
||||
<mct-include
|
||||
@ -16,7 +22,7 @@
|
||||
<button ng-repeat="dialogAction in ngModel.dialog.actions"
|
||||
class="c-button c-button--major"
|
||||
ng-click="dialogAction.action()">
|
||||
{{dialogAction.label}}
|
||||
{{ dialogAction.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -81,10 +81,15 @@ define(
|
||||
* context.
|
||||
*/
|
||||
PropertiesAction.appliesTo = function (context) {
|
||||
|
||||
var domainObject = (context || {}).domainObject,
|
||||
type = domainObject && domainObject.getCapability('type'),
|
||||
creatable = type && type.hasFeature('creation');
|
||||
|
||||
if (domainObject && domainObject.model && domainObject.model.locked) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only allow creatable types to be edited
|
||||
return domainObject && creatable;
|
||||
};
|
||||
|
@ -21,7 +21,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
['../../../../../src/api/objects/object-utils'],
|
||||
['objectUtils'],
|
||||
function (objectUtils) {
|
||||
|
||||
/**
|
||||
@ -36,8 +36,6 @@ define(
|
||||
}
|
||||
|
||||
EditPersistableObjectsPolicy.prototype.allow = function (action, context) {
|
||||
var identifier;
|
||||
var provider;
|
||||
var domainObject = context.domainObject;
|
||||
var key = action.getMetadata().key;
|
||||
var category = (context || {}).category;
|
||||
@ -46,9 +44,8 @@ define(
|
||||
// is also invoked during the create process which should be allowed,
|
||||
// because it may be saved elsewhere
|
||||
if ((key === 'edit' && category === 'view-control') || key === 'properties') {
|
||||
identifier = objectUtils.parseKeyString(domainObject.getId());
|
||||
provider = this.openmct.objects.getProvider(identifier);
|
||||
return provider.save !== undefined;
|
||||
let newStyleObject = objectUtils.toNewFormat(domainObject, domainObject.getId());
|
||||
return this.openmct.objects.isPersistable(newStyleObject);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -43,7 +43,7 @@ define(
|
||||
);
|
||||
|
||||
mockObjectAPI = jasmine.createSpyObj('objectAPI', [
|
||||
'getProvider'
|
||||
'isPersistable'
|
||||
]);
|
||||
|
||||
mockAPI = {
|
||||
@ -69,34 +69,31 @@ define(
|
||||
});
|
||||
|
||||
it("Applies to edit action", function () {
|
||||
mockObjectAPI.getProvider.and.returnValue({});
|
||||
expect(mockObjectAPI.getProvider).not.toHaveBeenCalled();
|
||||
expect(mockObjectAPI.isPersistable).not.toHaveBeenCalled();
|
||||
|
||||
policy.allow(mockEditAction, testContext);
|
||||
expect(mockObjectAPI.getProvider).toHaveBeenCalled();
|
||||
expect(mockObjectAPI.isPersistable).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("Applies to properties action", function () {
|
||||
mockObjectAPI.getProvider.and.returnValue({});
|
||||
expect(mockObjectAPI.getProvider).not.toHaveBeenCalled();
|
||||
expect(mockObjectAPI.isPersistable).not.toHaveBeenCalled();
|
||||
|
||||
policy.allow(mockPropertiesAction, testContext);
|
||||
expect(mockObjectAPI.getProvider).toHaveBeenCalled();
|
||||
expect(mockObjectAPI.isPersistable).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not apply to other actions", function () {
|
||||
mockObjectAPI.getProvider.and.returnValue({});
|
||||
expect(mockObjectAPI.getProvider).not.toHaveBeenCalled();
|
||||
expect(mockObjectAPI.isPersistable).not.toHaveBeenCalled();
|
||||
|
||||
policy.allow(mockOtherAction, testContext);
|
||||
expect(mockObjectAPI.getProvider).not.toHaveBeenCalled();
|
||||
expect(mockObjectAPI.isPersistable).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("Tests object provider for editability", function () {
|
||||
mockObjectAPI.getProvider.and.returnValue({});
|
||||
mockObjectAPI.isPersistable.and.returnValue(false);
|
||||
expect(policy.allow(mockEditAction, testContext)).toBe(false);
|
||||
expect(mockObjectAPI.getProvider).toHaveBeenCalled();
|
||||
mockObjectAPI.getProvider.and.returnValue({save: function () {}});
|
||||
expect(mockObjectAPI.isPersistable).toHaveBeenCalled();
|
||||
mockObjectAPI.isPersistable.and.returnValue(true);
|
||||
expect(policy.allow(mockEditAction, testContext)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
@ -19,7 +19,13 @@
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<div class="c-object-label">
|
||||
<div class="c-object-label__type-icon {{type.getCssClass()}}" ng-class="{ 'l-icon-link':location.isLink() }"></div>
|
||||
<div class="c-object-label"
|
||||
ng-class="{ 'is-missing': model.status === 'missing' }"
|
||||
>
|
||||
<div class="c-object-label__type-icon {{type.getCssClass()}}"
|
||||
ng-class="{ 'l-icon-link':location.isLink() }"
|
||||
>
|
||||
<span class="is-missing__indicator" title="This item is missing"></span>
|
||||
</div>
|
||||
<div class='c-object-label__name'>{{model.name}}</div>
|
||||
</div>
|
||||
|
@ -21,44 +21,15 @@
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
"./src/NotificationIndicatorController",
|
||||
"./src/NotificationIndicator",
|
||||
"./src/NotificationService",
|
||||
"./res/notification-indicator.html"
|
||||
"./src/NotificationService"
|
||||
], function (
|
||||
NotificationIndicatorController,
|
||||
NotificationIndicator,
|
||||
NotificationService,
|
||||
notificationIndicatorTemplate
|
||||
NotificationService
|
||||
) {
|
||||
|
||||
return {
|
||||
name:"platform/commonUI/notification",
|
||||
definition: {
|
||||
"extensions": {
|
||||
"templates": [
|
||||
{
|
||||
"key": "notificationIndicatorTemplate",
|
||||
"template": notificationIndicatorTemplate
|
||||
}
|
||||
],
|
||||
"controllers": [
|
||||
{
|
||||
"key": "NotificationIndicatorController",
|
||||
"implementation": NotificationIndicatorController,
|
||||
"depends": [
|
||||
"$scope",
|
||||
"openmct",
|
||||
"dialogService"
|
||||
]
|
||||
}
|
||||
],
|
||||
"indicators": [
|
||||
{
|
||||
"implementation": NotificationIndicator,
|
||||
"priority": "fallback"
|
||||
}
|
||||
],
|
||||
"services": [
|
||||
{
|
||||
"key": "notificationService",
|
||||
|
@ -1,8 +0,0 @@
|
||||
<!-- DO NOT ADD SPACES BETWEEN THE SPANS - IT ADDS WHITE SPACE!! -->
|
||||
<div ng-show="notifications.length > 0" class="c-indicator c-indicator--clickable s-status-{{highest.severity}} icon-bell"
|
||||
ng-controller="NotificationIndicatorController">
|
||||
<span class="label c-indicator__label">
|
||||
<button ng-click="showNotificationsList()">
|
||||
{{notifications.length}} Notification<span ng-show="notifications.length > 1">s</span></button>
|
||||
</span><span class="c-indicator__count">{{notifications.length}}</span>
|
||||
</div>
|
@ -1,73 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
|
||||
/**
|
||||
* Provides an indicator that is visible when there are
|
||||
* banner notifications that have been minimized. Will also indicate
|
||||
* the number of notifications. Notifications can be viewed by
|
||||
* clicking on the indicator to launch a dialog showing a list of
|
||||
* notifications.
|
||||
* @param $scope
|
||||
* @param notificationService
|
||||
* @param dialogService
|
||||
* @constructor
|
||||
*/
|
||||
function NotificationIndicatorController($scope, openmct, dialogService) {
|
||||
$scope.notifications = openmct.notifications.notifications;
|
||||
$scope.highest = openmct.notifications.highest;
|
||||
|
||||
/**
|
||||
* Launch a dialog showing a list of current notifications.
|
||||
*/
|
||||
$scope.showNotificationsList = function () {
|
||||
let notificationsList = openmct.notifications.notifications.map(notification => {
|
||||
if (notification.model.severity === 'alert' || notification.model.severity === 'info') {
|
||||
notification.model.primaryOption = {
|
||||
label: 'Dismiss',
|
||||
callback: () => {
|
||||
let currentIndex = notificationsList.indexOf(notification);
|
||||
notification.dismiss();
|
||||
notificationsList.splice(currentIndex, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return notification;
|
||||
})
|
||||
dialogService.getDialogResponse('overlay-message-list', {
|
||||
dialog: {
|
||||
title: "Messages",
|
||||
//Launch the message list dialog with the models
|
||||
// from the notifications
|
||||
messages: notificationsList
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
}
|
||||
return NotificationIndicatorController;
|
||||
}
|
||||
);
|
||||
|
@ -1,60 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
['../src/NotificationIndicatorController'],
|
||||
function (NotificationIndicatorController) {
|
||||
|
||||
xdescribe("The notification indicator controller ", function () {
|
||||
var mockNotificationService,
|
||||
mockScope,
|
||||
mockDialogService,
|
||||
controller;
|
||||
|
||||
beforeEach(function () {
|
||||
mockNotificationService = jasmine.createSpy("notificationService");
|
||||
mockScope = jasmine.createSpy("$scope");
|
||||
mockDialogService = jasmine.createSpyObj(
|
||||
"dialogService",
|
||||
["getDialogResponse","dismiss"]
|
||||
);
|
||||
mockNotificationService.highest = {
|
||||
severity: "error"
|
||||
};
|
||||
controller = new NotificationIndicatorController(mockScope, mockNotificationService, mockDialogService);
|
||||
});
|
||||
|
||||
it("exposes the highest notification severity to the template", function () {
|
||||
expect(mockScope.highest).toBeTruthy();
|
||||
expect(mockScope.highest.severity).toBe("error");
|
||||
});
|
||||
|
||||
it("invokes the dialog service to show list of messages", function () {
|
||||
expect(mockScope.showNotificationsList).toBeDefined();
|
||||
mockScope.showNotificationsList();
|
||||
expect(mockDialogService.getDialogResponse).toHaveBeenCalled();
|
||||
expect(mockDialogService.getDialogResponse.calls.mostRecent().args[0]).toBe('overlay-message-list');
|
||||
expect(mockDialogService.getDialogResponse.calls.mostRecent().args[1].dialog).toBeDefined();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
@ -26,7 +26,7 @@
|
||||
* @namespace platform/containment
|
||||
*/
|
||||
define(
|
||||
['../../../src/api/objects/object-utils'],
|
||||
['objectUtils'],
|
||||
function (objectUtils) {
|
||||
|
||||
function PersistableCompositionPolicy(openmct) {
|
||||
@ -48,9 +48,8 @@ define(
|
||||
// prevents editing of objects that cannot be persisted, so we can assume that this
|
||||
// is a new object.
|
||||
if (!(parent.hasCapability('editor') && parent.getCapability('editor').isEditContextRoot())) {
|
||||
var identifier = objectUtils.parseKeyString(parent.getId());
|
||||
var provider = this.openmct.objects.getProvider(identifier);
|
||||
return provider.save !== undefined;
|
||||
let newStyleObject = objectUtils.toNewFormat(parent, parent.getId());
|
||||
return this.openmct.objects.isPersistable(newStyleObject);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
@ -33,7 +33,7 @@ define(
|
||||
|
||||
beforeEach(function () {
|
||||
objectAPI = jasmine.createSpyObj('objectsAPI', [
|
||||
'getProvider'
|
||||
'isPersistable'
|
||||
]);
|
||||
|
||||
mockOpenMCT = {
|
||||
@ -51,10 +51,6 @@ define(
|
||||
'isEditContextRoot'
|
||||
]);
|
||||
mockParent.getCapability.and.returnValue(mockEditorCapability);
|
||||
|
||||
objectAPI.getProvider.and.returnValue({
|
||||
save: function () {}
|
||||
});
|
||||
persistableCompositionPolicy = new PersistableCompositionPolicy(mockOpenMCT);
|
||||
});
|
||||
|
||||
@ -65,19 +61,22 @@ define(
|
||||
|
||||
it("Does not allow composition for objects that are not persistable", function () {
|
||||
mockEditorCapability.isEditContextRoot.and.returnValue(false);
|
||||
objectAPI.isPersistable.and.returnValue(true);
|
||||
expect(persistableCompositionPolicy.allow(mockParent, mockChild)).toBe(true);
|
||||
objectAPI.getProvider.and.returnValue({});
|
||||
objectAPI.isPersistable.and.returnValue(false);
|
||||
expect(persistableCompositionPolicy.allow(mockParent, mockChild)).toBe(false);
|
||||
});
|
||||
|
||||
it("Always allows composition of objects in edit mode to support object creation", function () {
|
||||
mockEditorCapability.isEditContextRoot.and.returnValue(true);
|
||||
objectAPI.isPersistable.and.returnValue(true);
|
||||
expect(persistableCompositionPolicy.allow(mockParent, mockChild)).toBe(true);
|
||||
expect(objectAPI.getProvider).not.toHaveBeenCalled();
|
||||
expect(objectAPI.isPersistable).not.toHaveBeenCalled();
|
||||
|
||||
mockEditorCapability.isEditContextRoot.and.returnValue(false);
|
||||
objectAPI.isPersistable.and.returnValue(true);
|
||||
expect(persistableCompositionPolicy.allow(mockParent, mockChild)).toBe(true);
|
||||
expect(objectAPI.getProvider).toHaveBeenCalled();
|
||||
expect(objectAPI.isPersistable).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -297,7 +297,8 @@ define([
|
||||
"persistenceService",
|
||||
"identifierService",
|
||||
"notificationService",
|
||||
"$q"
|
||||
"$q",
|
||||
"openmct"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -81,7 +81,7 @@ define(
|
||||
baseContext = context || {};
|
||||
}
|
||||
|
||||
var actionContext = _.extend({}, baseContext);
|
||||
var actionContext = Object.assign({}, baseContext);
|
||||
actionContext.domainObject = this.domainObject;
|
||||
|
||||
return this.actionService.getActions(actionContext);
|
||||
|
@ -20,8 +20,8 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(
|
||||
function () {
|
||||
define(["objectUtils"],
|
||||
function (objectUtils) {
|
||||
|
||||
/**
|
||||
* Defines the `persistence` capability, used to trigger the
|
||||
@ -47,6 +47,7 @@ define(
|
||||
identifierService,
|
||||
notificationService,
|
||||
$q,
|
||||
openmct,
|
||||
domainObject
|
||||
) {
|
||||
// Cache modified timestamp
|
||||
@ -58,6 +59,7 @@ define(
|
||||
this.persistenceService = persistenceService;
|
||||
this.notificationService = notificationService;
|
||||
this.$q = $q;
|
||||
this.openmct = openmct;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -66,7 +68,7 @@ define(
|
||||
*/
|
||||
function rejectIfFalsey(value, $q) {
|
||||
if (!value) {
|
||||
return $q.reject("Error persisting object");
|
||||
return Promise.reject("Error persisting object");
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
@ -98,7 +100,7 @@ define(
|
||||
dismissable: true
|
||||
});
|
||||
|
||||
return $q.reject(error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -110,34 +112,16 @@ define(
|
||||
*/
|
||||
PersistenceCapability.prototype.persist = function () {
|
||||
var self = this,
|
||||
domainObject = this.domainObject,
|
||||
model = domainObject.getModel(),
|
||||
modified = model.modified,
|
||||
persisted = model.persisted,
|
||||
persistenceService = this.persistenceService,
|
||||
persistenceFn = persisted !== undefined ?
|
||||
this.persistenceService.updateObject :
|
||||
this.persistenceService.createObject;
|
||||
domainObject = this.domainObject;
|
||||
|
||||
if (persisted !== undefined && persisted === modified) {
|
||||
return this.$q.when(true);
|
||||
}
|
||||
|
||||
// Update persistence timestamp...
|
||||
domainObject.useCapability("mutation", function (m) {
|
||||
m.persisted = modified;
|
||||
}, modified);
|
||||
|
||||
// ...and persist
|
||||
return persistenceFn.apply(persistenceService, [
|
||||
this.getSpace(),
|
||||
this.getKey(),
|
||||
domainObject.getModel()
|
||||
]).then(function (result) {
|
||||
return rejectIfFalsey(result, self.$q);
|
||||
}).catch(function (error) {
|
||||
return notifyOnError(error, domainObject, self.notificationService, self.$q);
|
||||
});
|
||||
let newStyleObject = objectUtils.toNewFormat(domainObject.getModel(), domainObject.getId());
|
||||
return this.openmct.objects
|
||||
.save(newStyleObject)
|
||||
.then(function (result) {
|
||||
return rejectIfFalsey(result, self.$q);
|
||||
}).catch(function (error) {
|
||||
return notifyOnError(error, domainObject, self.notificationService, self.$q);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -19,7 +19,6 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* PersistenceCapabilitySpec. Created by vwoeltje on 11/6/14.
|
||||
*/
|
||||
@ -40,7 +39,8 @@ define(
|
||||
model,
|
||||
SPACE = "some space",
|
||||
persistence,
|
||||
happyPromise;
|
||||
mockOpenMCT,
|
||||
mockNewStyleDomainObject;
|
||||
|
||||
function asPromise(value, doCatch) {
|
||||
return (value || {}).then ? value : {
|
||||
@ -56,7 +56,6 @@ define(
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
happyPromise = asPromise(true);
|
||||
model = { someKey: "some value", name: "domain object"};
|
||||
|
||||
mockPersistenceService = jasmine.createSpyObj(
|
||||
@ -94,12 +93,23 @@ define(
|
||||
},
|
||||
useCapability: jasmine.createSpy()
|
||||
};
|
||||
|
||||
mockNewStyleDomainObject = Object.assign({}, model);
|
||||
mockNewStyleDomainObject.identifier = {
|
||||
namespace: "",
|
||||
key: id
|
||||
}
|
||||
|
||||
// Simulate mutation capability
|
||||
mockDomainObject.useCapability.and.callFake(function (capability, mutator) {
|
||||
if (capability === 'mutation') {
|
||||
model = mutator(model) || model;
|
||||
}
|
||||
});
|
||||
|
||||
mockOpenMCT = {};
|
||||
mockOpenMCT.objects = jasmine.createSpyObj('Object API', ['save']);
|
||||
|
||||
mockIdentifierService.parse.and.returnValue(mockIdentifier);
|
||||
mockIdentifier.getSpace.and.returnValue(SPACE);
|
||||
mockIdentifier.getKey.and.returnValue(key);
|
||||
@ -110,51 +120,28 @@ define(
|
||||
mockIdentifierService,
|
||||
mockNofificationService,
|
||||
mockQ,
|
||||
mockOpenMCT,
|
||||
mockDomainObject
|
||||
);
|
||||
});
|
||||
|
||||
describe("successful persistence", function () {
|
||||
beforeEach(function () {
|
||||
mockPersistenceService.updateObject.and.returnValue(happyPromise);
|
||||
mockPersistenceService.createObject.and.returnValue(happyPromise);
|
||||
mockOpenMCT.objects.save.and.returnValue(Promise.resolve(true));
|
||||
});
|
||||
it("creates unpersisted objects with the persistence service", function () {
|
||||
// Verify precondition; no call made during constructor
|
||||
expect(mockPersistenceService.createObject).not.toHaveBeenCalled();
|
||||
expect(mockOpenMCT.objects.save).not.toHaveBeenCalled();
|
||||
|
||||
persistence.persist();
|
||||
|
||||
expect(mockPersistenceService.createObject).toHaveBeenCalledWith(
|
||||
SPACE,
|
||||
key,
|
||||
model
|
||||
);
|
||||
});
|
||||
|
||||
it("updates previously persisted objects with the persistence service", function () {
|
||||
// Verify precondition; no call made during constructor
|
||||
expect(mockPersistenceService.updateObject).not.toHaveBeenCalled();
|
||||
|
||||
model.persisted = 12321;
|
||||
persistence.persist();
|
||||
|
||||
expect(mockPersistenceService.updateObject).toHaveBeenCalledWith(
|
||||
SPACE,
|
||||
key,
|
||||
model
|
||||
);
|
||||
expect(mockOpenMCT.objects.save).toHaveBeenCalledWith(mockNewStyleDomainObject);
|
||||
});
|
||||
|
||||
it("reports which persistence space an object belongs to", function () {
|
||||
expect(persistence.getSpace()).toEqual(SPACE);
|
||||
});
|
||||
|
||||
it("updates persisted timestamp on persistence", function () {
|
||||
model.modified = 12321;
|
||||
persistence.persist();
|
||||
expect(model.persisted).toEqual(12321);
|
||||
});
|
||||
it("refreshes the domain object model from persistence", function () {
|
||||
var refreshModel = {someOtherKey: "some other value"};
|
||||
model.persisted = 1;
|
||||
@ -165,30 +152,37 @@ define(
|
||||
|
||||
it("does not trigger error notification on successful" +
|
||||
" persistence", function () {
|
||||
persistence.persist();
|
||||
expect(mockQ.reject).not.toHaveBeenCalled();
|
||||
expect(mockNofificationService.error).not.toHaveBeenCalled();
|
||||
let rejected = false;
|
||||
return persistence.persist()
|
||||
.catch(() => rejected = true)
|
||||
.then(() => {
|
||||
expect(rejected).toBe(false);
|
||||
expect(mockNofificationService.error).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("unsuccessful persistence", function () {
|
||||
var sadPromise = {
|
||||
then: function (callback) {
|
||||
return asPromise(callback(0), true);
|
||||
}
|
||||
};
|
||||
beforeEach(function () {
|
||||
mockPersistenceService.createObject.and.returnValue(sadPromise);
|
||||
mockOpenMCT.objects.save.and.returnValue(Promise.resolve(false));
|
||||
});
|
||||
it("rejects on falsey persistence result", function () {
|
||||
persistence.persist();
|
||||
expect(mockQ.reject).toHaveBeenCalled();
|
||||
let rejected = false;
|
||||
return persistence.persist()
|
||||
.catch(() => rejected = true)
|
||||
.then(() => {
|
||||
expect(rejected).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("notifies user on persistence failure", function () {
|
||||
persistence.persist();
|
||||
expect(mockQ.reject).toHaveBeenCalled();
|
||||
expect(mockNofificationService.error).toHaveBeenCalled();
|
||||
let rejected = false;
|
||||
return persistence.persist()
|
||||
.catch(() => rejected = true)
|
||||
.then(() => {
|
||||
expect(rejected).toBe(true);
|
||||
expect(mockNofificationService.error).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -40,7 +40,18 @@ define(
|
||||
}
|
||||
|
||||
MoveAction.prototype = Object.create(AbstractComposeAction.prototype);
|
||||
MoveAction.appliesTo = AbstractComposeAction.appliesTo;
|
||||
|
||||
MoveAction.appliesTo = function (context) {
|
||||
var applicableObject =
|
||||
context.selectedObject || context.domainObject;
|
||||
|
||||
if (applicableObject && applicableObject.model.locked) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Boolean(applicableObject &&
|
||||
applicableObject.hasCapability('context'));
|
||||
};
|
||||
|
||||
return MoveAction;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -121,7 +121,7 @@ define(['lodash'], function (_) {
|
||||
*/
|
||||
ExportAsJSONAction.prototype.rewriteLink = function (child, parent) {
|
||||
this.externalIdentifiers.push(this.getId(child));
|
||||
var index = _.findIndex(parent.composition, function (id) {
|
||||
var index = parent.composition.findIndex(id => {
|
||||
return _.isEqual(child.identifier, id);
|
||||
});
|
||||
var copyOfChild = this.copyObject(child);
|
||||
|
@ -19,7 +19,7 @@
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
define(['zepto', '../../../../src/api/objects/object-utils.js'], function ($, objectUtils) {
|
||||
define(['zepto', 'objectUtils'], function ($, objectUtils) {
|
||||
|
||||
/**
|
||||
* The ImportAsJSONAction is available from context menus and allows a user
|
||||
@ -216,8 +216,14 @@ define(['zepto', '../../../../src/api/objects/object-utils.js'], function ($, ob
|
||||
};
|
||||
|
||||
ImportAsJSONAction.appliesTo = function (context) {
|
||||
return context.domainObject !== undefined &&
|
||||
context.domainObject.hasCapability("composition");
|
||||
let domainObject = context.domainObject;
|
||||
|
||||
if (domainObject && domainObject.model.locked) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return domainObject !== undefined &&
|
||||
domainObject.hasCapability("composition");
|
||||
};
|
||||
|
||||
return ImportAsJSONAction;
|
||||
|
@ -33,7 +33,7 @@ define(
|
||||
var CONNECTED = {
|
||||
text: "Connected",
|
||||
glyphClass: "ok",
|
||||
statusClass: "s-status-ok",
|
||||
statusClass: "s-status-on",
|
||||
description: "Connected to the domain object database."
|
||||
},
|
||||
DISCONNECTED = {
|
||||
|
@ -71,7 +71,7 @@ define([
|
||||
},
|
||||
{
|
||||
"key": "ELASTIC_PATH",
|
||||
"value": "mct/domain_object",
|
||||
"value": "mct/_doc",
|
||||
"priority": "fallback"
|
||||
},
|
||||
{
|
||||
|
@ -32,7 +32,7 @@ define(
|
||||
var CONNECTED = {
|
||||
text: "Connected",
|
||||
glyphClass: "ok",
|
||||
statusClass: "s-status-ok",
|
||||
statusClass: "s-status-on",
|
||||
description: "Connected to the domain object database."
|
||||
},
|
||||
DISCONNECTED = {
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
|
@ -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");
|
||||
|
||||
|
@ -25,7 +25,7 @@
|
||||
* Module defining GenericSearchProvider. Created by shale on 07/16/2015.
|
||||
*/
|
||||
define([
|
||||
'../../../../src/api/objects/object-utils',
|
||||
'objectUtils',
|
||||
'lodash'
|
||||
], function (
|
||||
objectUtils,
|
||||
@ -191,7 +191,7 @@ define([
|
||||
}
|
||||
|
||||
var domainObject = objectUtils.toNewFormat(model, id);
|
||||
var composition = _.find(this.openmct.composition.registry, function (p) {
|
||||
var composition = this.openmct.composition.registry.find(p => {
|
||||
return p.appliesTo(domainObject);
|
||||
});
|
||||
|
||||
|
@ -25,7 +25,7 @@
|
||||
*/
|
||||
define(
|
||||
[
|
||||
'../../../src/api/objects/object-utils',
|
||||
'objectUtils',
|
||||
'lodash'
|
||||
],
|
||||
function (
|
||||
@ -235,7 +235,7 @@ define(
|
||||
var defaultRange = metadata.valuesForHints(['range'])[0];
|
||||
defaultRange = defaultRange ? defaultRange.key : undefined;
|
||||
|
||||
var sourceMap = _.indexBy(metadata.values(), 'key');
|
||||
var sourceMap = _.keyBy(metadata.values(), 'key');
|
||||
|
||||
var isLegacyProvider = telemetryAPI.findRequestProvider(domainObject) ===
|
||||
telemetryAPI.legacyProvider;
|
||||
@ -300,7 +300,7 @@ define(
|
||||
var defaultRange = metadata.valuesForHints(['range'])[0];
|
||||
defaultRange = defaultRange ? defaultRange.key : undefined;
|
||||
|
||||
var sourceMap = _.indexBy(metadata.values(), 'key');
|
||||
var sourceMap = _.keyBy(metadata.values(), 'key');
|
||||
|
||||
var isLegacyProvider = telemetryAPI.findSubscriptionProvider(domainObject) ===
|
||||
telemetryAPI.legacyProvider;
|
||||
|
53
src/MCT.js
53
src/MCT.js
@ -1,5 +1,5 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*
|
||||
@ -28,7 +28,7 @@ define([
|
||||
'./api/api',
|
||||
'./api/overlays/OverlayAPI',
|
||||
'./selection/Selection',
|
||||
'./api/objects/object-utils',
|
||||
'objectUtils',
|
||||
'./plugins/plugins',
|
||||
'./adapter/indicators/legacy-indicators-plugin',
|
||||
'./plugins/buildInfo/plugin',
|
||||
@ -249,9 +249,10 @@ define([
|
||||
this.legacyRegistry = new BundleRegistry();
|
||||
installDefaultBundles(this.legacyRegistry);
|
||||
|
||||
// Plugin's that are installed by default
|
||||
// Plugins 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());
|
||||
@ -264,6 +265,11 @@ define([
|
||||
this.install(this.plugins.GoToOriginalAction());
|
||||
this.install(this.plugins.ImportExport());
|
||||
this.install(this.plugins.WebPage());
|
||||
this.install(this.plugins.Condition());
|
||||
this.install(this.plugins.ConditionWidget());
|
||||
this.install(this.plugins.URLTimeSettingsSynchronizer());
|
||||
this.install(this.plugins.NotificationIndicator());
|
||||
this.install(this.plugins.NewFolderAction());
|
||||
}
|
||||
|
||||
MCT.prototype = Object.create(EventEmitter.prototype);
|
||||
@ -348,17 +354,13 @@ define([
|
||||
* @param {HTMLElement} [domElement] the DOM element in which to run
|
||||
* MCT; if undefined, MCT will be run in the body of the document
|
||||
*/
|
||||
MCT.prototype.start = function (domElement) {
|
||||
MCT.prototype.start = function (domElement = document.body, isHeadlessMode = false) {
|
||||
if (!this.plugins.DisplayLayout._installed) {
|
||||
this.install(this.plugins.DisplayLayout({
|
||||
showAsView: ['summary-widget']
|
||||
}));
|
||||
}
|
||||
|
||||
if (!domElement) {
|
||||
domElement = document.body;
|
||||
}
|
||||
|
||||
this.element = domElement;
|
||||
|
||||
this.legacyExtension('runs', {
|
||||
@ -398,24 +400,31 @@ define([
|
||||
// something has depended upon objectService. Cool, right?
|
||||
this.$injector.get('objectService');
|
||||
|
||||
var appLayout = new Vue({
|
||||
components: {
|
||||
'Layout': Layout.default
|
||||
},
|
||||
provide: {
|
||||
openmct: this
|
||||
},
|
||||
template: '<Layout ref="layout"></Layout>'
|
||||
});
|
||||
domElement.appendChild(appLayout.$mount().$el);
|
||||
if (!isHeadlessMode) {
|
||||
var appLayout = new Vue({
|
||||
components: {
|
||||
'Layout': Layout.default
|
||||
},
|
||||
provide: {
|
||||
openmct: this
|
||||
},
|
||||
template: '<Layout ref="layout"></Layout>'
|
||||
});
|
||||
domElement.appendChild(appLayout.$mount().$el);
|
||||
|
||||
this.layout = appLayout.$refs.layout;
|
||||
Browse(this);
|
||||
this.layout = appLayout.$refs.layout;
|
||||
Browse(this);
|
||||
}
|
||||
this.router.start();
|
||||
this.emit('start');
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
MCT.prototype.startHeadless = function () {
|
||||
let unreachableNode = document.createElement('div');
|
||||
return this.start(unreachableNode, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Install a plugin in MCT.
|
||||
*
|
||||
@ -427,6 +436,10 @@ define([
|
||||
plugin(this);
|
||||
};
|
||||
|
||||
MCT.prototype.destroy = function () {
|
||||
this.emit('destroy');
|
||||
};
|
||||
|
||||
MCT.prototype.plugins = plugins;
|
||||
|
||||
return MCT;
|
||||
|
@ -21,24 +21,28 @@
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./MCT',
|
||||
'./plugins/plugins',
|
||||
'legacyRegistry'
|
||||
], function (MCT, plugins, legacyRegistry) {
|
||||
xdescribe("MCT", function () {
|
||||
'legacyRegistry',
|
||||
'utils/testing'
|
||||
], function (plugins, legacyRegistry, testUtils) {
|
||||
describe("MCT", function () {
|
||||
var openmct;
|
||||
var mockPlugin;
|
||||
var mockPlugin2;
|
||||
var mockListener;
|
||||
var oldBundles;
|
||||
|
||||
beforeAll(() => {
|
||||
testUtils.resetApplicationState();
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
mockPlugin = jasmine.createSpy('plugin');
|
||||
mockPlugin2 = jasmine.createSpy('plugin2');
|
||||
mockListener = jasmine.createSpy('listener');
|
||||
oldBundles = legacyRegistry.list();
|
||||
|
||||
openmct = new MCT();
|
||||
openmct = testUtils.createOpenMct();
|
||||
|
||||
openmct.install(mockPlugin);
|
||||
openmct.install(mockPlugin2);
|
||||
@ -52,6 +56,7 @@ define([
|
||||
legacyRegistry.delete(bundle);
|
||||
}
|
||||
});
|
||||
testUtils.resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
it("exposes plugins", function () {
|
||||
@ -63,8 +68,11 @@ define([
|
||||
});
|
||||
|
||||
describe("start", function () {
|
||||
beforeEach(function () {
|
||||
openmct.start();
|
||||
let appHolder;
|
||||
beforeEach(function (done) {
|
||||
appHolder = document.createElement("div");
|
||||
openmct.on('start', done);
|
||||
openmct.start(appHolder);
|
||||
});
|
||||
|
||||
it("calls plugins for configuration", function () {
|
||||
@ -75,25 +83,51 @@ define([
|
||||
it("emits a start event", function () {
|
||||
expect(mockListener).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("Renders the application into the provided container element", function () {
|
||||
let openMctShellElements = appHolder.querySelectorAll('div.l-shell');
|
||||
expect(openMctShellElements.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("startHeadless", function () {
|
||||
beforeEach(function (done) {
|
||||
openmct.on('start', done);
|
||||
openmct.startHeadless();
|
||||
});
|
||||
|
||||
it("calls plugins for configuration", function () {
|
||||
expect(mockPlugin).toHaveBeenCalledWith(openmct);
|
||||
expect(mockPlugin2).toHaveBeenCalledWith(openmct);
|
||||
});
|
||||
|
||||
it("emits a start event", function () {
|
||||
expect(mockListener).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("Does not render Open MCT", function () {
|
||||
let openMctShellElements = document.body.querySelectorAll('div.l-shell');
|
||||
expect(openMctShellElements.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("setAssetPath", function () {
|
||||
var testAssetPath;
|
||||
|
||||
beforeEach(function () {
|
||||
testAssetPath = "some/path";
|
||||
openmct.legacyExtension = jasmine.createSpy('legacyExtension');
|
||||
openmct.setAssetPath(testAssetPath);
|
||||
});
|
||||
|
||||
it("internally configures the path for assets", function () {
|
||||
expect(openmct.legacyExtension).toHaveBeenCalledWith(
|
||||
'constants',
|
||||
{
|
||||
key: "ASSETS_PATH",
|
||||
value: testAssetPath
|
||||
}
|
||||
);
|
||||
it("configures the path for assets", function () {
|
||||
testAssetPath = "some/path/";
|
||||
openmct.setAssetPath(testAssetPath);
|
||||
expect(openmct.getAssetPath()).toBe(testAssetPath);
|
||||
});
|
||||
|
||||
it("adds a trailing /", function () {
|
||||
testAssetPath = "some/path";
|
||||
openmct.setAssetPath(testAssetPath);
|
||||
expect(openmct.getAssetPath()).toBe(testAssetPath + "/");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -21,7 +21,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'../../api/objects/object-utils'
|
||||
'objectUtils'
|
||||
], function (objectUtils) {
|
||||
function ActionDialogDecorator(mct, actionService) {
|
||||
this.mct = mct;
|
||||
|
@ -29,7 +29,6 @@ define([
|
||||
'./capabilities/APICapabilityDecorator',
|
||||
'./policies/AdaptedViewPolicy',
|
||||
'./runs/AlternateCompositionInitializer',
|
||||
'./runs/TimeSettingsURLHandler',
|
||||
'./runs/TypeDeprecationChecker',
|
||||
'./runs/LegacyTelemetryProvider',
|
||||
'./runs/RegisterLegacyTypes',
|
||||
@ -46,7 +45,6 @@ define([
|
||||
APICapabilityDecorator,
|
||||
AdaptedViewPolicy,
|
||||
AlternateCompositionInitializer,
|
||||
TimeSettingsURLHandler,
|
||||
TypeDeprecationChecker,
|
||||
LegacyTelemetryProvider,
|
||||
RegisterLegacyTypes,
|
||||
@ -134,16 +132,6 @@ define([
|
||||
implementation: AlternateCompositionInitializer,
|
||||
depends: ["openmct"]
|
||||
},
|
||||
{
|
||||
implementation: function (openmct, $location, $rootScope) {
|
||||
return new TimeSettingsURLHandler(
|
||||
openmct.time,
|
||||
$location,
|
||||
$rootScope
|
||||
);
|
||||
},
|
||||
depends: ["openmct", "$location", "$rootScope"]
|
||||
},
|
||||
{
|
||||
implementation: LegacyTelemetryProvider,
|
||||
depends: [
|
||||
|
@ -20,7 +20,7 @@
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define(['../../api/objects/object-utils'], function (objectUtils) {
|
||||
define(['objectUtils'], function (objectUtils) {
|
||||
function AdapterCapability(domainObject) {
|
||||
this.domainObject = domainObject;
|
||||
}
|
||||
|
@ -24,7 +24,7 @@
|
||||
* Module defining AlternateCompositionCapability. Created by vwoeltje on 11/7/14.
|
||||
*/
|
||||
define([
|
||||
'../../api/objects/object-utils',
|
||||
'objectUtils',
|
||||
'../../../platform/core/src/capabilities/ContextualDomainObject'
|
||||
], function (objectUtils, ContextualDomainObject) {
|
||||
function AlternateCompositionCapability($injector, domainObject) {
|
||||
|
@ -31,6 +31,7 @@ define([
|
||||
var capability = viewConstructor(domainObject);
|
||||
var oldInvoke = capability.invoke.bind(capability);
|
||||
|
||||
/* eslint-disable you-dont-need-lodash-underscore/map */
|
||||
capability.invoke = function () {
|
||||
var availableViews = oldInvoke();
|
||||
var newDomainObject = capability
|
||||
@ -52,6 +53,8 @@ define([
|
||||
.map('view')
|
||||
.value();
|
||||
};
|
||||
/* eslint-enable you-dont-need-lodash-underscore/map */
|
||||
|
||||
return capability;
|
||||
};
|
||||
}
|
||||
|
@ -22,7 +22,7 @@
|
||||
|
||||
define([
|
||||
'../capabilities/AlternateCompositionCapability',
|
||||
'../../api/objects/object-utils'
|
||||
'objectUtils'
|
||||
], function (
|
||||
AlternateCompositionCapability,
|
||||
objectUtils
|
||||
|
@ -21,7 +21,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'../../api/objects/object-utils'
|
||||
'objectUtils'
|
||||
], function (
|
||||
utils
|
||||
) {
|
||||
|
@ -1,150 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'lodash'
|
||||
], function (
|
||||
_
|
||||
) {
|
||||
// Parameter names in query string
|
||||
var SEARCH = {
|
||||
MODE: 'tc.mode',
|
||||
TIME_SYSTEM: 'tc.timeSystem',
|
||||
START_BOUND: 'tc.startBound',
|
||||
END_BOUND: 'tc.endBound',
|
||||
START_DELTA: 'tc.startDelta',
|
||||
END_DELTA: 'tc.endDelta'
|
||||
};
|
||||
var TIME_EVENTS = ['bounds', 'timeSystem', 'clock', 'clockOffsets'];
|
||||
// Used to shorthand calls to $location, which clears null parameters
|
||||
var NULL_PARAMETERS = { key: null, start: null, end: null };
|
||||
|
||||
/**
|
||||
* Communicates settings from the URL to the time API,
|
||||
* and vice versa.
|
||||
*/
|
||||
function TimeSettingsURLHandler(time, $location, $rootScope) {
|
||||
this.time = time;
|
||||
this.$location = $location;
|
||||
|
||||
$rootScope.$on('$locationChangeSuccess', this.updateTime.bind(this));
|
||||
|
||||
TIME_EVENTS.forEach(function (event) {
|
||||
this.time.on(event, this.updateQueryParams.bind(this));
|
||||
}, this);
|
||||
|
||||
this.updateTime(); // Initialize
|
||||
}
|
||||
|
||||
TimeSettingsURLHandler.prototype.updateQueryParams = function () {
|
||||
var clock = this.time.clock();
|
||||
var fixed = !clock;
|
||||
var mode = fixed ? 'fixed' : clock.key;
|
||||
var timeSystem = this.time.timeSystem() || NULL_PARAMETERS;
|
||||
var bounds = fixed ? this.time.bounds() : NULL_PARAMETERS;
|
||||
var deltas = fixed ? NULL_PARAMETERS : this.time.clockOffsets();
|
||||
|
||||
bounds = bounds || NULL_PARAMETERS;
|
||||
deltas = deltas || NULL_PARAMETERS;
|
||||
if (deltas.start) {
|
||||
deltas = { start: -deltas.start, end: deltas.end };
|
||||
}
|
||||
|
||||
this.$location.search(SEARCH.MODE, mode);
|
||||
this.$location.search(SEARCH.TIME_SYSTEM, timeSystem.key);
|
||||
this.$location.search(SEARCH.START_BOUND, bounds.start);
|
||||
this.$location.search(SEARCH.END_BOUND, bounds.end);
|
||||
this.$location.search(SEARCH.START_DELTA, deltas.start);
|
||||
this.$location.search(SEARCH.END_DELTA, deltas.end);
|
||||
};
|
||||
|
||||
TimeSettingsURLHandler.prototype.parseQueryParams = function () {
|
||||
var searchParams = _.pick(this.$location.search(), _.values(SEARCH));
|
||||
var parsedParams = {
|
||||
clock: searchParams[SEARCH.MODE],
|
||||
timeSystem: searchParams[SEARCH.TIME_SYSTEM]
|
||||
};
|
||||
if (!isNaN(parseInt(searchParams[SEARCH.START_DELTA], 0xA)) &&
|
||||
!isNaN(parseInt(searchParams[SEARCH.END_DELTA], 0xA))) {
|
||||
parsedParams.clockOffsets = {
|
||||
start: -searchParams[SEARCH.START_DELTA],
|
||||
end: +searchParams[SEARCH.END_DELTA]
|
||||
};
|
||||
}
|
||||
if (!isNaN(parseInt(searchParams[SEARCH.START_BOUND], 0xA)) &&
|
||||
!isNaN(parseInt(searchParams[SEARCH.END_BOUND], 0xA))) {
|
||||
parsedParams.bounds = {
|
||||
start: +searchParams[SEARCH.START_BOUND],
|
||||
end: +searchParams[SEARCH.END_BOUND]
|
||||
};
|
||||
}
|
||||
return parsedParams;
|
||||
};
|
||||
|
||||
TimeSettingsURLHandler.prototype.updateTime = function () {
|
||||
var params = this.parseQueryParams();
|
||||
if (_.isEqual(params, this.last)) {
|
||||
return; // Do nothing;
|
||||
}
|
||||
this.last = params;
|
||||
|
||||
if (!params.timeSystem) {
|
||||
this.updateQueryParams();
|
||||
} else if (params.clock === 'fixed' && params.bounds) {
|
||||
if (!this.time.timeSystem() ||
|
||||
this.time.timeSystem().key !== params.timeSystem) {
|
||||
|
||||
this.time.timeSystem(
|
||||
params.timeSystem,
|
||||
params.bounds
|
||||
);
|
||||
} else if (!_.isEqual(this.time.bounds(), params.bounds)) {
|
||||
this.time.bounds(params.bounds);
|
||||
}
|
||||
if (this.time.clock()) {
|
||||
this.time.stopClock();
|
||||
}
|
||||
} else if (params.clockOffsets) {
|
||||
if (params.clock === 'fixed') {
|
||||
this.time.stopClock();
|
||||
return;
|
||||
}
|
||||
if (!this.time.clock() ||
|
||||
this.time.clock().key !== params.clock) {
|
||||
|
||||
this.time.clock(params.clock, params.clockOffsets);
|
||||
} else if (!_.isEqual(this.time.clockOffsets(), params.clockOffsets)) {
|
||||
this.time.clockOffsets(params.clockOffsets);
|
||||
}
|
||||
if (!this.time.timeSystem() ||
|
||||
this.time.timeSystem().key !== params.timeSystem) {
|
||||
|
||||
this.time.timeSystem(params.timeSystem);
|
||||
}
|
||||
} else {
|
||||
// Neither found, update from timeSystem.
|
||||
this.updateQueryParams();
|
||||
}
|
||||
};
|
||||
|
||||
return TimeSettingsURLHandler;
|
||||
});
|
@ -1,576 +0,0 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2018, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./TimeSettingsURLHandler',
|
||||
'../../api/time/TimeAPI'
|
||||
], function (
|
||||
TimeSettingsURLHandler,
|
||||
TimeAPI
|
||||
) {
|
||||
describe("TimeSettingsURLHandler", function () {
|
||||
var time;
|
||||
var $location;
|
||||
var $rootScope;
|
||||
var search;
|
||||
var handler; // eslint-disable-line
|
||||
var clockA;
|
||||
var clockB;
|
||||
var timeSystemA;
|
||||
var timeSystemB;
|
||||
var boundsA;
|
||||
var boundsB;
|
||||
var offsetsA;
|
||||
var offsetsB;
|
||||
var initialize;
|
||||
var triggerLocationChange;
|
||||
|
||||
|
||||
beforeEach(function () {
|
||||
clockA = jasmine.createSpyObj('clockA', ['on', 'off']);
|
||||
clockA.key = 'clockA';
|
||||
clockA.currentValue = function () {
|
||||
return 1000;
|
||||
};
|
||||
clockB = jasmine.createSpyObj('clockB', ['on', 'off']);
|
||||
clockB.key = 'clockB';
|
||||
clockB.currentValue = function () {
|
||||
return 2000;
|
||||
};
|
||||
timeSystemA = {key: 'timeSystemA'};
|
||||
timeSystemB = {key: 'timeSystemB'};
|
||||
boundsA = {
|
||||
start: 10,
|
||||
end: 20
|
||||
};
|
||||
boundsB = {
|
||||
start: 120,
|
||||
end: 360
|
||||
};
|
||||
offsetsA = {
|
||||
start: -100,
|
||||
end: 0
|
||||
};
|
||||
offsetsB = {
|
||||
start: -50,
|
||||
end: 50
|
||||
};
|
||||
|
||||
time = new TimeAPI();
|
||||
|
||||
[
|
||||
'on',
|
||||
'bounds',
|
||||
'clockOffsets',
|
||||
'timeSystem',
|
||||
'clock',
|
||||
'stopClock'
|
||||
].forEach(function (method) {
|
||||
spyOn(time, method).and.callThrough();
|
||||
});
|
||||
time.addTimeSystem(timeSystemA);
|
||||
time.addTimeSystem(timeSystemB);
|
||||
time.addClock(clockA);
|
||||
time.addClock(clockB);
|
||||
|
||||
$location = jasmine.createSpyObj('$location', [
|
||||
'search'
|
||||
]);
|
||||
$rootScope = jasmine.createSpyObj('$rootScope', [
|
||||
'$on'
|
||||
]);
|
||||
|
||||
search = {};
|
||||
$location.search.and.callFake(function (key, value) {
|
||||
if (arguments.length === 0) {
|
||||
return search;
|
||||
}
|
||||
if (value === null) {
|
||||
delete search[key];
|
||||
} else {
|
||||
search[key] = String(value);
|
||||
}
|
||||
return this;
|
||||
});
|
||||
|
||||
expect(time.timeSystem()).toBeUndefined();
|
||||
expect(time.bounds()).toEqual({});
|
||||
expect(time.clockOffsets()).toBeUndefined();
|
||||
expect(time.clock()).toBeUndefined();
|
||||
|
||||
initialize = function () {
|
||||
handler = new TimeSettingsURLHandler(
|
||||
time,
|
||||
$location,
|
||||
$rootScope
|
||||
);
|
||||
expect($rootScope.$on).toHaveBeenCalledWith(
|
||||
'$locationChangeSuccess',
|
||||
jasmine.any(Function)
|
||||
);
|
||||
triggerLocationChange = $rootScope.$on.calls.mostRecent().args[1];
|
||||
|
||||
};
|
||||
});
|
||||
|
||||
it("initializes with missing time system", function () {
|
||||
// This handles an odd transitory case where a url does not include
|
||||
// a timeSystem. It's generally only experienced by those who
|
||||
// based their code on the tutorial before it specified a time
|
||||
// system.
|
||||
search['tc.mode'] = 'clockA';
|
||||
search['tc.timeSystem'] = undefined;
|
||||
search['tc.startDelta'] = '123';
|
||||
search['tc.endDelta'] = '456';
|
||||
|
||||
// We don't specify behavior right now other than "don't break."
|
||||
expect(initialize).not.toThrow();
|
||||
});
|
||||
|
||||
it("can initalize fixed mode from location", function () {
|
||||
search['tc.mode'] = 'fixed';
|
||||
search['tc.timeSystem'] = 'timeSystemA';
|
||||
search['tc.startBound'] = '123';
|
||||
search['tc.endBound'] = '456';
|
||||
|
||||
initialize();
|
||||
|
||||
expect(time.timeSystem).toHaveBeenCalledWith(
|
||||
'timeSystemA',
|
||||
{
|
||||
start: 123,
|
||||
end: 456
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("can initialize clock mode from location", function () {
|
||||
search['tc.mode'] = 'clockA';
|
||||
search['tc.timeSystem'] = 'timeSystemA';
|
||||
search['tc.startDelta'] = '123';
|
||||
search['tc.endDelta'] = '456';
|
||||
|
||||
initialize();
|
||||
|
||||
expect(time.clock).toHaveBeenCalledWith(
|
||||
'clockA',
|
||||
{
|
||||
start: -123,
|
||||
end: 456
|
||||
}
|
||||
);
|
||||
expect(time.timeSystem).toHaveBeenCalledWith(
|
||||
'timeSystemA'
|
||||
);
|
||||
});
|
||||
|
||||
it("can initialize fixed mode from time API", function () {
|
||||
time.timeSystem(timeSystemA.key, boundsA);
|
||||
initialize();
|
||||
expect($location.search)
|
||||
.toHaveBeenCalledWith('tc.mode', 'fixed');
|
||||
expect($location.search)
|
||||
.toHaveBeenCalledWith('tc.timeSystem', 'timeSystemA');
|
||||
expect($location.search)
|
||||
.toHaveBeenCalledWith('tc.startBound', 10);
|
||||
expect($location.search)
|
||||
.toHaveBeenCalledWith('tc.endBound', 20);
|
||||
expect($location.search)
|
||||
.toHaveBeenCalledWith('tc.startDelta', null);
|
||||
expect($location.search)
|
||||
.toHaveBeenCalledWith('tc.endDelta', null);
|
||||
});
|
||||
|
||||
it("can initialize clock mode from time API", function () {
|
||||
time.clock(clockA.key, offsetsA);
|
||||
time.timeSystem(timeSystemA.key);
|
||||
initialize();
|
||||
expect($location.search)
|
||||
.toHaveBeenCalledWith('tc.mode', 'clockA');
|
||||
expect($location.search)
|
||||
.toHaveBeenCalledWith('tc.timeSystem', 'timeSystemA');
|
||||
expect($location.search)
|
||||
.toHaveBeenCalledWith('tc.startBound', null);
|
||||
expect($location.search)
|
||||
.toHaveBeenCalledWith('tc.endBound', null);
|
||||
expect($location.search)
|
||||
.toHaveBeenCalledWith('tc.startDelta', 100);
|
||||
expect($location.search)
|
||||
.toHaveBeenCalledWith('tc.endDelta', 0);
|
||||
});
|
||||
|
||||
describe('location changes in fixed mode', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
time.timeSystem(timeSystemA.key, boundsA);
|
||||
initialize();
|
||||
time.timeSystem.calls.reset();
|
||||
time.bounds.calls.reset();
|
||||
time.clock.calls.reset();
|
||||
time.stopClock.calls.reset();
|
||||
});
|
||||
|
||||
it("does not change on spurious location change", function () {
|
||||
triggerLocationChange();
|
||||
expect(time.timeSystem).not.toHaveBeenCalledWith(
|
||||
'timeSystemA',
|
||||
jasmine.any(Object)
|
||||
);
|
||||
expect(time.bounds).not.toHaveBeenCalledWith(
|
||||
jasmine.any(Object)
|
||||
);
|
||||
expect(time.stopClock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("updates timeSystem changes", function () {
|
||||
search['tc.timeSystem'] = 'timeSystemB';
|
||||
triggerLocationChange();
|
||||
expect(time.timeSystem).toHaveBeenCalledWith(
|
||||
'timeSystemB',
|
||||
{
|
||||
start: 10,
|
||||
end: 20
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("updates bounds changes", function () {
|
||||
search['tc.startBound'] = '100';
|
||||
search['tc.endBound'] = '200';
|
||||
triggerLocationChange();
|
||||
expect(time.timeSystem).not.toHaveBeenCalledWith(
|
||||
jasmine.anything(), jasmine.anything()
|
||||
);
|
||||
expect(time.bounds).toHaveBeenCalledWith({
|
||||
start: 100,
|
||||
end: 200
|
||||
});
|
||||
search['tc.endBound'] = '300';
|
||||
triggerLocationChange();
|
||||
expect(time.timeSystem).not.toHaveBeenCalledWith(
|
||||
jasmine.anything(), jasmine.anything()
|
||||
);
|
||||
expect(time.bounds).toHaveBeenCalledWith({
|
||||
start: 100,
|
||||
end: 300
|
||||
});
|
||||
});
|
||||
|
||||
it("updates clock mode w/o timeSystem change", function () {
|
||||
search['tc.mode'] = 'clockA';
|
||||
search['tc.startDelta'] = '50';
|
||||
search['tc.endDelta'] = '50';
|
||||
delete search['tc.endBound'];
|
||||
delete search['tc.startBound'];
|
||||
triggerLocationChange();
|
||||
expect(time.clock).toHaveBeenCalledWith(
|
||||
'clockA',
|
||||
{
|
||||
start: -50,
|
||||
end: 50
|
||||
}
|
||||
);
|
||||
expect(time.timeSystem).not.toHaveBeenCalledWith(
|
||||
jasmine.anything(), jasmine.anything()
|
||||
);
|
||||
});
|
||||
|
||||
it("updates clock mode and timeSystem", function () {
|
||||
search['tc.mode'] = 'clockA';
|
||||
search['tc.startDelta'] = '50';
|
||||
search['tc.endDelta'] = '50';
|
||||
search['tc.timeSystem'] = 'timeSystemB';
|
||||
delete search['tc.endBound'];
|
||||
delete search['tc.startBound'];
|
||||
triggerLocationChange();
|
||||
expect(time.clock).toHaveBeenCalledWith(
|
||||
'clockA',
|
||||
{
|
||||
start: -50,
|
||||
end: 50
|
||||
}
|
||||
);
|
||||
expect(time.timeSystem).toHaveBeenCalledWith('timeSystemB');
|
||||
});
|
||||
});
|
||||
|
||||
describe('location changes in clock mode', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
time.clock(clockA.key, offsetsA);
|
||||
time.timeSystem(timeSystemA.key);
|
||||
initialize();
|
||||
time.timeSystem.calls.reset();
|
||||
time.bounds.calls.reset();
|
||||
time.clock.calls.reset();
|
||||
time.clockOffsets.calls.reset();
|
||||
time.stopClock.calls.reset();
|
||||
});
|
||||
|
||||
it("does not change on spurious location change", function () {
|
||||
triggerLocationChange();
|
||||
expect(time.timeSystem).not.toHaveBeenCalledWith(
|
||||
'timeSystemA',
|
||||
jasmine.any(Object)
|
||||
);
|
||||
expect(time.clockOffsets).not.toHaveBeenCalledWith(
|
||||
jasmine.any(Object)
|
||||
);
|
||||
expect(time.clock).not.toHaveBeenCalledWith(
|
||||
jasmine.any(Object)
|
||||
);
|
||||
expect(time.bounds).not.toHaveBeenCalledWith(
|
||||
jasmine.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
it("changes time system", function () {
|
||||
search['tc.timeSystem'] = 'timeSystemB';
|
||||
triggerLocationChange();
|
||||
expect(time.timeSystem).toHaveBeenCalledWith(
|
||||
'timeSystemB'
|
||||
);
|
||||
expect(time.clockOffsets).not.toHaveBeenCalledWith(
|
||||
jasmine.any(Object)
|
||||
);
|
||||
expect(time.clock).not.toHaveBeenCalledWith(
|
||||
jasmine.any(Object)
|
||||
);
|
||||
expect(time.stopClock).not.toHaveBeenCalled();
|
||||
expect(time.bounds).not.toHaveBeenCalledWith(
|
||||
jasmine.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
it("changes offsets", function () {
|
||||
search['tc.startDelta'] = '50';
|
||||
search['tc.endDelta'] = '50';
|
||||
triggerLocationChange();
|
||||
expect(time.timeSystem).not.toHaveBeenCalledWith(
|
||||
'timeSystemA',
|
||||
jasmine.any(Object)
|
||||
);
|
||||
expect(time.clockOffsets).toHaveBeenCalledWith(
|
||||
{
|
||||
start: -50,
|
||||
end: 50
|
||||
}
|
||||
);
|
||||
expect(time.clock).not.toHaveBeenCalledWith(
|
||||
jasmine.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
it("updates to fixed w/o timeSystem change", function () {
|
||||
search['tc.mode'] = 'fixed';
|
||||
search['tc.startBound'] = '234';
|
||||
search['tc.endBound'] = '567';
|
||||
delete search['tc.endDelta'];
|
||||
delete search['tc.startDelta'];
|
||||
|
||||
triggerLocationChange();
|
||||
expect(time.stopClock).toHaveBeenCalled();
|
||||
expect(time.bounds).toHaveBeenCalledWith({
|
||||
start: 234,
|
||||
end: 567
|
||||
});
|
||||
expect(time.timeSystem).not.toHaveBeenCalledWith(
|
||||
jasmine.anything(), jasmine.anything()
|
||||
);
|
||||
});
|
||||
|
||||
it("updates fixed and timeSystem", function () {
|
||||
search['tc.mode'] = 'fixed';
|
||||
search['tc.startBound'] = '234';
|
||||
search['tc.endBound'] = '567';
|
||||
search['tc.timeSystem'] = 'timeSystemB';
|
||||
delete search['tc.endDelta'];
|
||||
delete search['tc.startDelta'];
|
||||
|
||||
triggerLocationChange();
|
||||
expect(time.stopClock).toHaveBeenCalled();
|
||||
expect(time.timeSystem).toHaveBeenCalledWith(
|
||||
'timeSystemB',
|
||||
{
|
||||
start: 234,
|
||||
end: 567
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("updates clock", function () {
|
||||
search['tc.mode'] = 'clockB';
|
||||
triggerLocationChange();
|
||||
expect(time.clock).toHaveBeenCalledWith(
|
||||
'clockB',
|
||||
{
|
||||
start: -100,
|
||||
end: 0
|
||||
}
|
||||
);
|
||||
expect(time.timeSystem).not.toHaveBeenCalledWith(jasmine.anything());
|
||||
});
|
||||
|
||||
it("updates clock and timeSystem", function () {
|
||||
search['tc.mode'] = 'clockB';
|
||||
search['tc.timeSystem'] = 'timeSystemB';
|
||||
triggerLocationChange();
|
||||
expect(time.clock).toHaveBeenCalledWith(
|
||||
'clockB',
|
||||
{
|
||||
start: -100,
|
||||
end: 0
|
||||
}
|
||||
);
|
||||
expect(time.timeSystem).toHaveBeenCalledWith(
|
||||
'timeSystemB'
|
||||
);
|
||||
});
|
||||
|
||||
it("updates clock and timeSystem and offsets", function () {
|
||||
search['tc.mode'] = 'clockB';
|
||||
search['tc.timeSystem'] = 'timeSystemB';
|
||||
search['tc.startDelta'] = '50';
|
||||
search['tc.endDelta'] = '50';
|
||||
triggerLocationChange();
|
||||
expect(time.clock).toHaveBeenCalledWith(
|
||||
'clockB',
|
||||
{
|
||||
start: -50,
|
||||
end: 50
|
||||
}
|
||||
);
|
||||
expect(time.timeSystem).toHaveBeenCalledWith(
|
||||
'timeSystemB'
|
||||
);
|
||||
});
|
||||
|
||||
it("stops the clock", function () {
|
||||
// this is a robustness test, unsure if desired, requires
|
||||
// user to be manually editing location strings.
|
||||
search['tc.mode'] = 'fixed';
|
||||
triggerLocationChange();
|
||||
expect(time.stopClock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe("location updates from time API in fixed", function () {
|
||||
beforeEach(function () {
|
||||
time.timeSystem(timeSystemA.key, boundsA);
|
||||
initialize();
|
||||
});
|
||||
|
||||
it("updates on bounds change", function () {
|
||||
time.bounds(boundsB);
|
||||
expect(search).toEqual({
|
||||
'tc.mode': 'fixed',
|
||||
'tc.startBound': '120',
|
||||
'tc.endBound': '360',
|
||||
'tc.timeSystem': 'timeSystemA'
|
||||
});
|
||||
});
|
||||
|
||||
it("updates on timeSystem change", function () {
|
||||
time.timeSystem(timeSystemB, boundsA);
|
||||
expect(search).toEqual({
|
||||
'tc.mode': 'fixed',
|
||||
'tc.startBound': '10',
|
||||
'tc.endBound': '20',
|
||||
'tc.timeSystem': 'timeSystemB'
|
||||
});
|
||||
time.timeSystem(timeSystemA, boundsB);
|
||||
expect(search).toEqual({
|
||||
'tc.mode': 'fixed',
|
||||
'tc.startBound': '120',
|
||||
'tc.endBound': '360',
|
||||
'tc.timeSystem': 'timeSystemA'
|
||||
});
|
||||
});
|
||||
|
||||
it("Updates to clock", function () {
|
||||
time.clock(clockA, offsetsA);
|
||||
expect(search).toEqual({
|
||||
'tc.mode': 'clockA',
|
||||
'tc.startDelta': '100',
|
||||
'tc.endDelta': '0',
|
||||
'tc.timeSystem': 'timeSystemA'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("location updates from time API in fixed", function () {
|
||||
beforeEach(function () {
|
||||
time.clock(clockA.key, offsetsA);
|
||||
time.timeSystem(timeSystemA.key);
|
||||
initialize();
|
||||
});
|
||||
|
||||
it("updates offsets", function () {
|
||||
time.clockOffsets(offsetsB);
|
||||
expect(search).toEqual({
|
||||
'tc.mode': 'clockA',
|
||||
'tc.startDelta': '50',
|
||||
'tc.endDelta': '50',
|
||||
'tc.timeSystem': 'timeSystemA'
|
||||
});
|
||||
});
|
||||
|
||||
it("updates clocks", function () {
|
||||
time.clock(clockB, offsetsA);
|
||||
expect(search).toEqual({
|
||||
'tc.mode': 'clockB',
|
||||
'tc.startDelta': '100',
|
||||
'tc.endDelta': '0',
|
||||
'tc.timeSystem': 'timeSystemA'
|
||||
});
|
||||
time.clock(clockA, offsetsB);
|
||||
expect(search).toEqual({
|
||||
'tc.mode': 'clockA',
|
||||
'tc.startDelta': '50',
|
||||
'tc.endDelta': '50',
|
||||
'tc.timeSystem': 'timeSystemA'
|
||||
});
|
||||
});
|
||||
|
||||
it("updates timesystems", function () {
|
||||
time.timeSystem(timeSystemB);
|
||||
expect(search).toEqual({
|
||||
'tc.mode': 'clockA',
|
||||
'tc.startDelta': '100',
|
||||
'tc.endDelta': '0',
|
||||
'tc.timeSystem': 'timeSystemB'
|
||||
});
|
||||
});
|
||||
|
||||
it("stops the clock", function () {
|
||||
time.stopClock();
|
||||
expect(search).toEqual({
|
||||
'tc.mode': 'fixed',
|
||||
'tc.startBound': '900',
|
||||
'tc.endBound': '1000',
|
||||
'tc.timeSystem': 'timeSystemA'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -21,14 +21,15 @@
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'../../api/objects/object-utils'
|
||||
'objectUtils'
|
||||
], function (
|
||||
utils
|
||||
) {
|
||||
function ObjectServiceProvider(eventEmitter, objectService, instantiate, topic) {
|
||||
function ObjectServiceProvider(eventEmitter, objectService, instantiate, topic, $injector) {
|
||||
this.eventEmitter = eventEmitter;
|
||||
this.objectService = objectService;
|
||||
this.instantiate = instantiate;
|
||||
this.$injector = $injector;
|
||||
|
||||
this.generalTopic = topic('mutation');
|
||||
this.bridgeEventBuses();
|
||||
@ -68,16 +69,53 @@ define([
|
||||
removeGeneralTopicListener = this.generalTopic.listen(handleLegacyMutation);
|
||||
};
|
||||
|
||||
ObjectServiceProvider.prototype.save = function (object) {
|
||||
var key = object.key;
|
||||
ObjectServiceProvider.prototype.create = async function (object) {
|
||||
let model = utils.toOldFormat(object);
|
||||
|
||||
return object.getCapability('persistence')
|
||||
.persist()
|
||||
.then(function () {
|
||||
return utils.toNewFormat(object, key);
|
||||
});
|
||||
return this.getPersistenceService().createObject(
|
||||
this.getSpace(utils.makeKeyString(object.identifier)),
|
||||
object.identifier.key,
|
||||
model
|
||||
);
|
||||
}
|
||||
ObjectServiceProvider.prototype.update = async function (object) {
|
||||
let model = utils.toOldFormat(object);
|
||||
|
||||
return this.getPersistenceService().updateObject(
|
||||
this.getSpace(utils.makeKeyString(object.identifier)),
|
||||
object.identifier.key,
|
||||
model
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the space in which this domain object is persisted;
|
||||
* this is useful when, for example, decided which space a
|
||||
* newly-created domain object should be persisted to (by
|
||||
* default, this should be the space of its containing
|
||||
* object.)
|
||||
*
|
||||
* @returns {string} the name of the space which should
|
||||
* be used to persist this object
|
||||
*/
|
||||
ObjectServiceProvider.prototype.getSpace = function (keystring) {
|
||||
return this.getIdentifierService().parse(keystring).getSpace();
|
||||
};
|
||||
|
||||
ObjectServiceProvider.prototype.getIdentifierService = function () {
|
||||
if (this.identifierService === undefined) {
|
||||
this.identifierService = this.$injector.get('identifierService');
|
||||
}
|
||||
return this.identifierService;
|
||||
};
|
||||
|
||||
ObjectServiceProvider.prototype.getPersistenceService = function () {
|
||||
if (this.persistenceService === undefined) {
|
||||
this.persistenceService = this.$injector.get('persistenceService');
|
||||
}
|
||||
return this.persistenceService;
|
||||
}
|
||||
|
||||
ObjectServiceProvider.prototype.delete = function (object) {
|
||||
// TODO!
|
||||
};
|
||||
@ -118,7 +156,8 @@ define([
|
||||
eventEmitter,
|
||||
objectService,
|
||||
instantiate,
|
||||
topic
|
||||
topic,
|
||||
openmct.$injector
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -21,7 +21,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'../../api/objects/object-utils'
|
||||
'objectUtils'
|
||||
], function (
|
||||
objectUtils
|
||||
) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
define([
|
||||
'./LegacyViewProvider',
|
||||
'./TypeInspectorViewProvider',
|
||||
'../../api/objects/object-utils'
|
||||
'objectUtils'
|
||||
], function (
|
||||
LegacyViewProvider,
|
||||
TypeInspectorViewProvider,
|
||||
|
@ -70,7 +70,7 @@ define([
|
||||
* @memberof module:openmct.CompositionAPI#
|
||||
*/
|
||||
CompositionAPI.prototype.get = function (domainObject) {
|
||||
var provider = _.find(this.registry, function (p) {
|
||||
var provider = this.registry.find(p => {
|
||||
return p.appliesTo(domainObject);
|
||||
});
|
||||
|
||||
|
@ -122,7 +122,7 @@ define([
|
||||
throw new Error('Event not supported by composition: ' + event);
|
||||
}
|
||||
|
||||
var index = _.findIndex(this.listeners[event], function (l) {
|
||||
var index = this.listeners[event].findIndex(l => {
|
||||
return l.callback === callback && l.context === context;
|
||||
});
|
||||
|
||||
|
@ -22,7 +22,7 @@
|
||||
|
||||
define([
|
||||
'lodash',
|
||||
'../objects/object-utils'
|
||||
'objectUtils'
|
||||
], function (
|
||||
_,
|
||||
objectUtils
|
||||
@ -143,7 +143,7 @@ define([
|
||||
var keyString = objectUtils.makeKeyString(domainObject.identifier);
|
||||
var objectListeners = this.listeningTo[keyString];
|
||||
|
||||
var index = _.findIndex(objectListeners[event], function (l) {
|
||||
var index = objectListeners[event].findIndex(l => {
|
||||
return l.callback === callback && l.context === context;
|
||||
});
|
||||
|
||||
@ -196,8 +196,8 @@ define([
|
||||
* @private
|
||||
*/
|
||||
DefaultCompositionProvider.prototype.includes = function (parent, childId) {
|
||||
return parent.composition.findIndex(composee =>
|
||||
this.publicAPI.objects.areIdsEqual(composee, childId)) !== -1;
|
||||
return parent.composition.some(composee =>
|
||||
this.publicAPI.objects.areIdsEqual(composee, childId));
|
||||
};
|
||||
|
||||
DefaultCompositionProvider.prototype.reorder = function (domainObject, oldIndex, newIndex) {
|
||||
|
@ -128,6 +128,11 @@ export default class NotificationAPI extends EventEmitter {
|
||||
return this._notify(notificationModel);
|
||||
}
|
||||
|
||||
dismissAllNotifications() {
|
||||
this.notifications = [];
|
||||
this.emit('dismiss-all');
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimize a notification. The notification will still be available
|
||||
* from the notification list. Typically notifications with a
|
||||
|
@ -21,7 +21,7 @@
|
||||
*****************************************************************************/
|
||||
|
||||
define([
|
||||
'./object-utils.js',
|
||||
'objectUtils',
|
||||
'lodash'
|
||||
], function (
|
||||
utils,
|
||||
|
@ -22,7 +22,7 @@
|
||||
|
||||
define([
|
||||
'lodash',
|
||||
'./object-utils',
|
||||
'objectUtils',
|
||||
'./MutableObject',
|
||||
'./RootRegistry',
|
||||
'./RootObjectProvider',
|
||||
@ -101,14 +101,25 @@ define([
|
||||
*/
|
||||
|
||||
/**
|
||||
* Save this domain object in its current state.
|
||||
* Create the given domain object in the corresponding persistence store
|
||||
*
|
||||
* @method save
|
||||
* @method create
|
||||
* @memberof module:openmct.ObjectProvider#
|
||||
* @param {module:openmct.DomainObject} domainObject the domain object to
|
||||
* save
|
||||
* create
|
||||
* @returns {Promise} a promise which will resolve when the domain object
|
||||
* has been saved, or be rejected if it cannot be saved
|
||||
* has been created, or be rejected if it cannot be saved
|
||||
*/
|
||||
|
||||
/**
|
||||
* Update this domain object in its persistence store
|
||||
*
|
||||
* @method update
|
||||
* @memberof module:openmct.ObjectProvider#
|
||||
* @param {module:openmct.DomainObject} domainObject the domain object to
|
||||
* update
|
||||
* @returns {Promise} a promise which will resolve when the domain object
|
||||
* has been updated, or be rejected if it cannot be saved
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -161,8 +172,41 @@ define([
|
||||
throw new Error('Delete not implemented');
|
||||
};
|
||||
|
||||
ObjectAPI.prototype.save = function () {
|
||||
throw new Error('Save not implemented');
|
||||
ObjectAPI.prototype.isPersistable = function (domainObject) {
|
||||
let provider = this.getProvider(domainObject.identifier);
|
||||
return provider !== undefined &&
|
||||
provider.create !== undefined &&
|
||||
provider.update !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save this domain object in its current state. EXPERIMENTAL
|
||||
*
|
||||
* @private
|
||||
* @memberof module:openmct.ObjectAPI#
|
||||
* @param {module:openmct.DomainObject} domainObject the domain object to
|
||||
* save
|
||||
* @returns {Promise} a promise which will resolve when the domain object
|
||||
* has been saved, or be rejected if it cannot be saved
|
||||
*/
|
||||
ObjectAPI.prototype.save = function (domainObject) {
|
||||
let provider = this.getProvider(domainObject.identifier);
|
||||
let result;
|
||||
|
||||
if (!this.isPersistable(domainObject)) {
|
||||
result = Promise.reject('Object provider does not support saving');
|
||||
} else if (hasAlreadyBeenPersisted(domainObject)) {
|
||||
result = Promise.resolve(true);
|
||||
} else {
|
||||
if (domainObject.persisted === undefined) {
|
||||
this.mutate(domainObject, 'persisted', domainObject.modified);
|
||||
result = provider.create(domainObject);
|
||||
} else {
|
||||
this.mutate(domainObject, 'persisted', domainObject.modified);
|
||||
result = provider.update(domainObject);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -276,5 +320,9 @@ define([
|
||||
* @memberof module:openmct
|
||||
*/
|
||||
|
||||
function hasAlreadyBeenPersisted(domainObject) {
|
||||
return domainObject.persisted !== undefined &&
|
||||
domainObject.persisted === domainObject.modified;
|
||||
}
|
||||
return ObjectAPI;
|
||||
});
|
||||
|
60
src/api/objects/ObjectAPISpec.js
Normal file
60
src/api/objects/ObjectAPISpec.js
Normal file
@ -0,0 +1,60 @@
|
||||
import ObjectAPI from './ObjectAPI.js';
|
||||
|
||||
describe("The Object API", () => {
|
||||
let objectAPI;
|
||||
let mockDomainObject;
|
||||
const TEST_NAMESPACE = "test-namespace";
|
||||
const FIFTEEN_MINUTES = 15 * 60 * 1000;
|
||||
|
||||
beforeEach(() => {
|
||||
objectAPI = new ObjectAPI();
|
||||
mockDomainObject = {
|
||||
identifier: {
|
||||
namespace: TEST_NAMESPACE,
|
||||
key: "test-key"
|
||||
},
|
||||
name: "test object",
|
||||
type: "test-type"
|
||||
};
|
||||
})
|
||||
describe("The save function", () => {
|
||||
it("Rejects if no provider available", () => {
|
||||
let rejected = false;
|
||||
return objectAPI.save(mockDomainObject)
|
||||
.catch(() => rejected = true)
|
||||
.then(() => expect(rejected).toBe(true));
|
||||
});
|
||||
describe("when a provider is available", () => {
|
||||
let mockProvider;
|
||||
beforeEach(() => {
|
||||
mockProvider = jasmine.createSpyObj("mock provider", [
|
||||
"create",
|
||||
"update"
|
||||
]);
|
||||
objectAPI.addProvider(TEST_NAMESPACE, mockProvider);
|
||||
})
|
||||
it("Calls 'create' on provider if object is new", () => {
|
||||
objectAPI.save(mockDomainObject);
|
||||
expect(mockProvider.create).toHaveBeenCalled();
|
||||
expect(mockProvider.update).not.toHaveBeenCalled();
|
||||
});
|
||||
it("Calls 'update' on provider if object is not new", () => {
|
||||
mockDomainObject.persisted = Date.now() - FIFTEEN_MINUTES;
|
||||
mockDomainObject.modified = Date.now();
|
||||
|
||||
objectAPI.save(mockDomainObject);
|
||||
expect(mockProvider.create).not.toHaveBeenCalled();
|
||||
expect(mockProvider.update).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("Does not persist if the object is unchanged", () => {
|
||||
mockDomainObject.persisted =
|
||||
mockDomainObject.modified = Date.now();
|
||||
|
||||
objectAPI.save(mockDomainObject);
|
||||
expect(mockProvider.create).not.toHaveBeenCalled();
|
||||
expect(mockProvider.update).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
})
|
||||
});
|
@ -43,7 +43,7 @@ define([
|
||||
}
|
||||
|
||||
RootRegistry.prototype.addRoot = function (key) {
|
||||
if (isKey(key) || (_.isArray(key) && _.every(key, isKey))) {
|
||||
if (isKey(key) || (Array.isArray(key) && key.every(isKey))) {
|
||||
this.providers.push(function () {
|
||||
return key;
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
define([
|
||||
'../object-utils'
|
||||
'objectUtils'
|
||||
], function (
|
||||
objectUtils
|
||||
) {
|
||||
|
@ -85,9 +85,9 @@ define([
|
||||
value: +e.value
|
||||
};
|
||||
}), 'e.value');
|
||||
valueMetadata.values = _.pluck(valueMetadata.enumerations, 'value');
|
||||
valueMetadata.max = _.max(valueMetadata.values);
|
||||
valueMetadata.min = _.min(valueMetadata.values);
|
||||
valueMetadata.values = valueMetadata.enumerations.map(e => e.value);
|
||||
valueMetadata.max = Math.max(valueMetadata.values);
|
||||
valueMetadata.min = Math.min(valueMetadata.values);
|
||||
}
|
||||
|
||||
valueMetadatas.push(valueMetadata);
|
||||
@ -103,7 +103,7 @@ define([
|
||||
var metadata = domainObject.telemetry || {};
|
||||
if (this.typeHasTelemetry(domainObject)) {
|
||||
var typeMetadata = this.typeService.getType(domainObject.type).typeDef.telemetry;
|
||||
_.extend(metadata, typeMetadata);
|
||||
Object.assign(metadata, typeMetadata);
|
||||
if (!metadata.values) {
|
||||
metadata.values = valueMetadatasFromOldFormat(metadata);
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ define([
|
||||
'./TelemetryMetadataManager',
|
||||
'./TelemetryValueFormatter',
|
||||
'./DefaultMetadataProvider',
|
||||
'../objects/object-utils',
|
||||
'objectUtils',
|
||||
'lodash'
|
||||
], function (
|
||||
TelemetryMetadataManager,
|
||||
@ -334,8 +334,8 @@ define([
|
||||
});
|
||||
if (subscriber.callbacks.length === 0) {
|
||||
subscriber.unsubscribe();
|
||||
delete this.subscribeCache[keyString];
|
||||
}
|
||||
delete this.subscribeCache[keyString];
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
@ -370,7 +370,7 @@ define([
|
||||
TelemetryAPI.prototype.commonValuesForHints = function (metadatas, hints) {
|
||||
var options = metadatas.map(function (metadata) {
|
||||
var values = metadata.valuesForHints(hints);
|
||||
return _.indexBy(values, 'key');
|
||||
return _.keyBy(values, 'key');
|
||||
}).reduce(function (a, b) {
|
||||
var results = {};
|
||||
Object.keys(a).forEach(function (key) {
|
||||
@ -383,7 +383,7 @@ define([
|
||||
var sortKeys = hints.map(function (h) {
|
||||
return 'hints.' + h;
|
||||
});
|
||||
return _.sortByAll(options, sortKeys);
|
||||
return _.sortBy(options, sortKeys);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -156,6 +156,29 @@ define([
|
||||
expect(callbacktwo).not.toHaveBeenCalledWith('anotherValue');
|
||||
});
|
||||
|
||||
it('only deletes subscription cache when there are no more subscribers', function () {
|
||||
var unsubFunc = jasmine.createSpy('unsubscribe');
|
||||
telemetryProvider.subscribe.and.returnValue(unsubFunc);
|
||||
telemetryProvider.supportsSubscribe.and.returnValue(true);
|
||||
telemetryAPI.addProvider(telemetryProvider);
|
||||
|
||||
var callback = jasmine.createSpy('callback');
|
||||
var callbacktwo = jasmine.createSpy('callback two');
|
||||
var callbackThree = jasmine.createSpy('callback three');
|
||||
var unsubscribe = telemetryAPI.subscribe(domainObject, callback);
|
||||
var unsubscribeTwo = telemetryAPI.subscribe(domainObject, callbacktwo);
|
||||
|
||||
expect(telemetryProvider.subscribe.calls.count()).toBe(1);
|
||||
unsubscribe();
|
||||
var unsubscribeThree = telemetryAPI.subscribe(domainObject, callbackThree);
|
||||
// Regression test for where subscription cache was deleted on each unsubscribe, resulting in
|
||||
// superfluous additional subscriptions. If the subscription cache is being deleted on each unsubscribe,
|
||||
// then a subsequent subscribe will result in a new subscription at the provider.
|
||||
expect(telemetryProvider.subscribe.calls.count()).toBe(1);
|
||||
unsubscribeTwo();
|
||||
unsubscribeThree();
|
||||
});
|
||||
|
||||
it('does subscribe/unsubscribe', function () {
|
||||
var unsubFunc = jasmine.createSpy('unsubscribe');
|
||||
telemetryProvider.subscribe.and.returnValue(unsubFunc);
|
||||
|
@ -57,13 +57,13 @@ define([
|
||||
|
||||
if (valueMetadata.format === 'enum') {
|
||||
if (!valueMetadata.values) {
|
||||
valueMetadata.values = _.pluck(valueMetadata.enumerations, 'value');
|
||||
valueMetadata.values = valueMetadata.enumerations.map(e => e.value);
|
||||
}
|
||||
if (!valueMetadata.hasOwnProperty('max')) {
|
||||
valueMetadata.max = _.max(valueMetadata.values) + 1;
|
||||
valueMetadata.max = Math.max(valueMetadata.values) + 1;
|
||||
}
|
||||
if (!valueMetadata.hasOwnProperty('min')) {
|
||||
valueMetadata.min = _.min(valueMetadata.values) - 1;
|
||||
valueMetadata.min = Math.min(valueMetadata.values) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,7 +81,7 @@ define([
|
||||
function TelemetryMetadataManager(metadata) {
|
||||
this.metadata = metadata;
|
||||
|
||||
this.valueMetadatas = this.metadata.values.map(applyReasonableDefaults);
|
||||
this.valueMetadatas = this.metadata.values ? this.metadata.values.map(applyReasonableDefaults) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -121,7 +121,7 @@ define([
|
||||
return metadata.hints[hint];
|
||||
}
|
||||
});
|
||||
return _.sortByAll(matchingMetadata, ...iteratees);
|
||||
return _.sortBy(matchingMetadata, ...iteratees);
|
||||
};
|
||||
|
||||
TelemetryMetadataManager.prototype.getFilterableValues = function () {
|
||||
|
@ -81,6 +81,24 @@ define([
|
||||
return printj.sprintf(formatString, baseFormat.call(this, value));
|
||||
};
|
||||
}
|
||||
if (valueMetadata.format === 'string') {
|
||||
this.formatter.parse = function (value) {
|
||||
if (value === undefined) {
|
||||
return '';
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
return value;
|
||||
} else {
|
||||
return value.toString();
|
||||
}
|
||||
};
|
||||
this.formatter.format = function (value) {
|
||||
return value;
|
||||
};
|
||||
this.formatter.validate = function (value) {
|
||||
return typeof value === 'string';
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
TelemetryValueFormatter.prototype.parse = function (datum) {
|
||||
|
32
src/plugins/LADTable/LADTableCompositionPolicy.js
Normal file
32
src/plugins/LADTable/LADTableCompositionPolicy.js
Normal file
@ -0,0 +1,32 @@
|
||||
/*****************************************************************************
|
||||
* 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 function ladTableCompositionPolicy(openmct) {
|
||||
return function (parent, child) {
|
||||
if(parent.type === 'LadTable') {
|
||||
return openmct.telemetry.isTelemetryObject(child);
|
||||
} else if(parent.type === 'LadTableSet') {
|
||||
return child.type === 'LadTable';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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.
|
||||
*
|
||||
@ -22,12 +22,16 @@
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<tr @contextmenu.prevent="showContextMenu">
|
||||
<td>{{ name }}</td>
|
||||
<td>{{ timestamp }}</td>
|
||||
<td :class="valueClass">
|
||||
{{ value }}
|
||||
</td>
|
||||
<tr
|
||||
class="js-lad-table__body__row"
|
||||
@contextmenu.prevent="showContextMenu"
|
||||
>
|
||||
<td class="js-first-data">{{ name }}</td>
|
||||
<td class="js-second-data">{{ formattedTimestamp }}</td>
|
||||
<td
|
||||
class="js-third-data"
|
||||
:class="valueClass"
|
||||
>{{ value }}</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
@ -52,16 +56,22 @@ export default {
|
||||
|
||||
return {
|
||||
name: this.domainObject.name,
|
||||
timestamp: '---',
|
||||
timestamp: undefined,
|
||||
value: '---',
|
||||
valueClass: '',
|
||||
currentObjectPath
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
formattedTimestamp() {
|
||||
return this.timestamp !== undefined ? this.getFormattedTimestamp(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 +86,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,46 +100,91 @@ 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.getParsedTimestamp(datum),
|
||||
limit;
|
||||
|
||||
var limit = this.limitEvaluator.evaluate(datum, this.valueMetadata);
|
||||
|
||||
if (limit) {
|
||||
this.valueClass = limit.cssClass;
|
||||
} else {
|
||||
this.valueClass = '';
|
||||
if(this.shouldUpdate(newTimestamp)) {
|
||||
this.timestamp = newTimestamp;
|
||||
this.value = this.formats[this.valueKey].format(datum);
|
||||
limit = this.limitEvaluator.evaluate(datum, this.valueMetadata);
|
||||
if (limit) {
|
||||
this.valueClass = limit.cssClass;
|
||||
} else {
|
||||
this.valueClass = '';
|
||||
}
|
||||
}
|
||||
},
|
||||
shouldUpdate(newTimestamp) {
|
||||
let newTimestampInBounds = this.inBounds(newTimestamp),
|
||||
noExistingTimestamp = this.timestamp === undefined,
|
||||
newTimestampIsLatest = newTimestamp > this.timestamp;
|
||||
|
||||
return newTimestampInBounds &&
|
||||
(noExistingTimestamp || newTimestampIsLatest);
|
||||
},
|
||||
requestHistory() {
|
||||
this.openmct
|
||||
.telemetry
|
||||
.request(this.domainObject, {
|
||||
start: this.bounds.start,
|
||||
end: this.bounds.end,
|
||||
size: 1,
|
||||
strategy: 'latest'
|
||||
})
|
||||
.then((array) => this.updateValues(array[array.length - 1]));
|
||||
},
|
||||
updateName(name) {
|
||||
this.name = name;
|
||||
},
|
||||
updateBounds(bounds, isTick) {
|
||||
this.bounds = bounds;
|
||||
if(!isTick) {
|
||||
this.resetValues();
|
||||
this.requestHistory();
|
||||
}
|
||||
},
|
||||
inBounds(timestamp) {
|
||||
return timestamp >= this.bounds.start && timestamp <= this.bounds.end;
|
||||
},
|
||||
updateTimeSystem(timeSystem) {
|
||||
this.value = '---';
|
||||
this.timestamp = '---';
|
||||
this.valueClass = '';
|
||||
this.resetValues();
|
||||
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);
|
||||
},
|
||||
resetValues() {
|
||||
this.value = '---';
|
||||
this.timestamp = undefined;
|
||||
this.valueClass = '';
|
||||
},
|
||||
getParsedTimestamp(timestamp) {
|
||||
if(this.timeSystemFormat()) {
|
||||
return this.formats[this.timestampKey].parse(timestamp);
|
||||
}
|
||||
},
|
||||
getFormattedTimestamp(timestamp) {
|
||||
if(this.timeSystemFormat()) {
|
||||
return this.formats[this.timestampKey].format(timestamp);
|
||||
}
|
||||
},
|
||||
timeSystemFormat() {
|
||||
if(this.formats[this.timestampKey]) {
|
||||
return true;
|
||||
} else {
|
||||
console.warn(`No formatter for ${this.timestampKey} time system for ${this.domainObject.name}.`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,22 +21,24 @@
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<table class="c-table c-lad-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Timestamp</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<lad-row
|
||||
v-for="item in items"
|
||||
:key="item.key"
|
||||
:domain-object="item.domainObject"
|
||||
/>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="c-lad-table-wrapper">
|
||||
<table class="c-table c-lad-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Timestamp</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<lad-row
|
||||
v-for="item in items"
|
||||
:key="item.key"
|
||||
:domain-object="item.domainObject"
|
||||
/>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -73,7 +75,7 @@ export default {
|
||||
this.items.push(item);
|
||||
},
|
||||
removeItem(identifier) {
|
||||
let index = _.findIndex(this.items, (item) => this.openmct.objects.makeKeyString(identifier) === item.key);
|
||||
let index = this.items.findIndex(item => this.openmct.objects.makeKeyString(identifier) === item.key);
|
||||
|
||||
this.items.splice(index, 1);
|
||||
},
|
||||
@ -86,4 +88,3 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -35,7 +35,7 @@
|
||||
>
|
||||
<tr
|
||||
:key="primary.key"
|
||||
class="c-table__group-header"
|
||||
class="c-table__group-header js-lad-table-set__table-headers"
|
||||
>
|
||||
<td colspan="10">
|
||||
{{ primary.domainObject.name }}
|
||||
@ -102,7 +102,7 @@ export default {
|
||||
this.compositions.push({composition, addCallback, removeCallback});
|
||||
},
|
||||
removePrimary(identifier) {
|
||||
let index = _.findIndex(this.primaryTelemetryObjects, (primary) => this.openmct.objects.makeKeyString(identifier) === primary.key),
|
||||
let index = this.primaryTelemetryObjects.findIndex(primary => this.openmct.objects.makeKeyString(identifier) === primary.key),
|
||||
primary = this.primaryTelemetryObjects[index];
|
||||
|
||||
this.$set(this.secondaryTelemetryObjects, primary.key, undefined);
|
||||
@ -130,7 +130,7 @@ export default {
|
||||
removeSecondary(primary) {
|
||||
return (identifier) => {
|
||||
let array = this.secondaryTelemetryObjects[primary.key],
|
||||
index = _.findIndex(array, (secondary) => this.openmct.objects.makeKeyString(identifier) === secondary.key);
|
||||
index = array.findIndex(secondary => this.openmct.objects.makeKeyString(identifier) === secondary.key);
|
||||
|
||||
array.splice(index, 1);
|
||||
|
||||
|
@ -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(ladTableCompositionPolicy(openmct));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
365
src/plugins/LADTable/pluginSpec.js
Normal file
365
src/plugins/LADTable/pluginSpec.js
Normal file
@ -0,0 +1,365 @@
|
||||
/*****************************************************************************
|
||||
* 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,
|
||||
resetApplicationState
|
||||
} from 'utils/testing';
|
||||
|
||||
const TABLE_BODY_ROWS = '.js-lad-table__body__row';
|
||||
const TABLE_BODY_FIRST_ROW = TABLE_BODY_ROWS + ':first-child';
|
||||
const TABLE_BODY_FIRST_ROW_FIRST_DATA = TABLE_BODY_FIRST_ROW + ' .js-first-data';
|
||||
const TABLE_BODY_FIRST_ROW_SECOND_DATA = TABLE_BODY_FIRST_ROW + ' .js-second-data';
|
||||
const TABLE_BODY_FIRST_ROW_THIRD_DATA = TABLE_BODY_FIRST_ROW + ' .js-third-data';
|
||||
const LAD_SET_TABLE_HEADERS = '.js-lad-table-set__table-headers';
|
||||
|
||||
function utcTimeFormat(value) {
|
||||
return new Date(value).toISOString().replace('T', ' ')
|
||||
}
|
||||
|
||||
describe("The LAD Table", () => {
|
||||
const ladTableKey = 'LadTable';
|
||||
|
||||
let openmct,
|
||||
ladPlugin,
|
||||
parent,
|
||||
child,
|
||||
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.startHeadless(appHolder);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
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]);
|
||||
await Vue.nextTick();
|
||||
return;
|
||||
});
|
||||
|
||||
it("should show one row per object in the composition", () => {
|
||||
const rowCount = parent.querySelectorAll(TABLE_BODY_ROWS).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(TABLE_BODY_FIRST_ROW_SECOND_DATA).innerText;
|
||||
expect(latestDate).toBe(expectedDate);
|
||||
});
|
||||
|
||||
it("should show the name provided for the the telemetry producing object", () => {
|
||||
const rowName = parent.querySelector(TABLE_BODY_FIRST_ROW_FIRST_DATA).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(TABLE_BODY_FIRST_ROW_SECOND_DATA).innerText;
|
||||
const actualRangeValue = parent.querySelector(TABLE_BODY_FIRST_ROW_THIRD_DATA).innerText;
|
||||
expect(actualRangeValue).toBe(rangeValue);
|
||||
expect(actualDomainValue).toBe(domainValue);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("The LAD Table Set", () => {
|
||||
const ladTableSetKey = 'LadTableSet';
|
||||
|
||||
let openmct,
|
||||
ladPlugin,
|
||||
parent,
|
||||
child,
|
||||
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);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetApplicationState(openmct);
|
||||
});
|
||||
|
||||
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]);
|
||||
await Vue.nextTick();
|
||||
return;
|
||||
});
|
||||
|
||||
it("should show one row per lad table object in the composition", () => {
|
||||
const rowCount = parent.querySelectorAll(LAD_SET_TABLE_HEADERS).length;
|
||||
expect(rowCount).toBe(mockObj.ladTableSet.composition.length);
|
||||
pending();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -30,7 +30,7 @@ define(
|
||||
// DISCONNECTED: HTTP failed; maybe misconfigured, disconnected.
|
||||
// PENDING: Still trying to connect, and haven't failed yet.
|
||||
var CONNECTED = {
|
||||
statusClass: "s-status-ok"
|
||||
statusClass: "s-status-on"
|
||||
},
|
||||
PENDING = {
|
||||
statusClass: "s-status-warning-lo"
|
||||
|
@ -122,7 +122,7 @@ define(
|
||||
it("indicates success if connection is nominal", function () {
|
||||
jasmine.clock().tick(pluginOptions.interval + 1);
|
||||
ajaxOptions.success();
|
||||
expect(indicatorElement.classList.contains('s-status-ok')).toBe(true);
|
||||
expect(indicatorElement.classList.contains('s-status-on')).toBe(true);
|
||||
});
|
||||
|
||||
it("indicates an error when the server cannot be reached", function () {
|
||||
|
@ -0,0 +1,237 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
import {
|
||||
getAllSearchParams,
|
||||
setAllSearchParams
|
||||
} from 'utils/openmctLocation';
|
||||
|
||||
const TIME_EVENTS = ['timeSystem', 'clock', 'clockOffsets'];
|
||||
const SEARCH_MODE = 'tc.mode';
|
||||
const SEARCH_TIME_SYSTEM = 'tc.timeSystem';
|
||||
const SEARCH_START_BOUND = 'tc.startBound';
|
||||
const SEARCH_END_BOUND = 'tc.endBound';
|
||||
const SEARCH_START_DELTA = 'tc.startDelta';
|
||||
const SEARCH_END_DELTA = 'tc.endDelta';
|
||||
const MODE_FIXED = 'fixed';
|
||||
|
||||
export default class URLTimeSettingsSynchronizer {
|
||||
constructor(openmct) {
|
||||
this.openmct = openmct;
|
||||
this.isUrlUpdateInProgress = false;
|
||||
|
||||
this.initialize = this.initialize.bind(this);
|
||||
this.destroy = this.destroy.bind(this);
|
||||
this.updateTimeSettings = this.updateTimeSettings.bind(this);
|
||||
this.setUrlFromTimeApi = this.setUrlFromTimeApi.bind(this);
|
||||
this.updateBounds = this.updateBounds.bind(this);
|
||||
|
||||
openmct.on('start', this.initialize);
|
||||
openmct.on('destroy', this.destroy);
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.updateTimeSettings();
|
||||
|
||||
window.addEventListener('hashchange', this.updateTimeSettings);
|
||||
TIME_EVENTS.forEach(event => {
|
||||
this.openmct.time.on(event, this.setUrlFromTimeApi);
|
||||
});
|
||||
this.openmct.time.on('bounds', this.updateBounds);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
window.removeEventListener('hashchange', this.updateTimeSettings);
|
||||
this.openmct.off('start', this.initialize);
|
||||
this.openmct.off('destroy', this.destroy);
|
||||
|
||||
TIME_EVENTS.forEach(event => {
|
||||
this.openmct.time.off(event, this.setUrlFromTimeApi);
|
||||
});
|
||||
this.openmct.time.on('bounds', this.updateBounds);
|
||||
}
|
||||
|
||||
updateTimeSettings() {
|
||||
// Prevent from triggering self
|
||||
if (!this.isUrlUpdateInProgress) {
|
||||
let timeParameters = this.parseParametersFromUrl();
|
||||
|
||||
if (this.areTimeParametersValid(timeParameters)) {
|
||||
this.setTimeApiFromUrl(timeParameters);
|
||||
} else {
|
||||
this.setUrlFromTimeApi();
|
||||
}
|
||||
} else {
|
||||
this.isUrlUpdateInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
parseParametersFromUrl() {
|
||||
let searchParams = getAllSearchParams();
|
||||
|
||||
let mode = searchParams.get(SEARCH_MODE);
|
||||
let timeSystem = searchParams.get(SEARCH_TIME_SYSTEM);
|
||||
|
||||
let startBound = parseInt(searchParams.get(SEARCH_START_BOUND), 10);
|
||||
let endBound = parseInt(searchParams.get(SEARCH_END_BOUND), 10);
|
||||
let bounds = {
|
||||
start: startBound,
|
||||
end: endBound
|
||||
};
|
||||
|
||||
let startOffset = parseInt(searchParams.get(SEARCH_START_DELTA), 10);
|
||||
let endOffset = parseInt(searchParams.get(SEARCH_END_DELTA), 10);
|
||||
let clockOffsets = {
|
||||
start: 0 - startOffset,
|
||||
end: endOffset
|
||||
};
|
||||
|
||||
return {
|
||||
mode,
|
||||
timeSystem,
|
||||
bounds,
|
||||
clockOffsets
|
||||
};
|
||||
}
|
||||
|
||||
setTimeApiFromUrl(timeParameters) {
|
||||
if (timeParameters.mode === 'fixed') {
|
||||
if (this.openmct.time.timeSystem().key !== timeParameters.timeSystem) {
|
||||
this.openmct.time.timeSystem(
|
||||
timeParameters.timeSystem,
|
||||
timeParameters.bounds
|
||||
);
|
||||
} else if (!this.areStartAndEndEqual(this.openmct.time.bounds(), timeParameters.bounds)) {
|
||||
this.openmct.time.bounds(timeParameters.bounds);
|
||||
}
|
||||
if (this.openmct.time.clock()) {
|
||||
this.openmct.time.stopClock();
|
||||
}
|
||||
} else {
|
||||
if (!this.openmct.time.clock() ||
|
||||
this.openmct.time.clock().key !== timeParameters.mode) {
|
||||
this.openmct.time.clock(timeParameters.mode, timeParameters.clockOffsets);
|
||||
} else if (!this.areStartAndEndEqual(this.openmct.time.clockOffsets(), timeParameters.clockOffsets)) {
|
||||
this.openmct.time.clockOffsets(timeParameters.clockOffsets);
|
||||
}
|
||||
if (!this.openmct.time.timeSystem() ||
|
||||
this.openmct.time.timeSystem().key !== timeParameters.timeSystem) {
|
||||
this.openmct.time.timeSystem(timeParameters.timeSystem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateBounds(bounds, isTick) {
|
||||
if (!isTick) {
|
||||
this.setUrlFromTimeApi();
|
||||
}
|
||||
}
|
||||
|
||||
setUrlFromTimeApi() {
|
||||
let searchParams = getAllSearchParams();
|
||||
let clock = this.openmct.time.clock();
|
||||
let bounds = this.openmct.time.bounds();
|
||||
let clockOffsets = this.openmct.time.clockOffsets();
|
||||
|
||||
if (clock === undefined) {
|
||||
searchParams.set(SEARCH_MODE, MODE_FIXED);
|
||||
searchParams.set(SEARCH_START_BOUND, bounds.start);
|
||||
searchParams.set(SEARCH_END_BOUND, bounds.end);
|
||||
|
||||
searchParams.delete(SEARCH_START_DELTA);
|
||||
searchParams.delete(SEARCH_END_DELTA);
|
||||
} else {
|
||||
searchParams.set(SEARCH_MODE, clock.key);
|
||||
|
||||
if (clockOffsets !== undefined) {
|
||||
searchParams.set(SEARCH_START_DELTA, 0 - clockOffsets.start);
|
||||
searchParams.set(SEARCH_END_DELTA, clockOffsets.end);
|
||||
} else {
|
||||
searchParams.delete(SEARCH_START_DELTA);
|
||||
searchParams.delete(SEARCH_END_DELTA);
|
||||
}
|
||||
searchParams.delete(SEARCH_START_BOUND);
|
||||
searchParams.delete(SEARCH_END_BOUND);
|
||||
}
|
||||
|
||||
searchParams.set(SEARCH_TIME_SYSTEM, this.openmct.time.timeSystem().key);
|
||||
this.isUrlUpdateInProgress = true;
|
||||
setAllSearchParams(searchParams);
|
||||
}
|
||||
|
||||
areTimeParametersValid(timeParameters) {
|
||||
let isValid = false;
|
||||
|
||||
if (this.isModeValid(timeParameters.mode) &&
|
||||
this.isTimeSystemValid(timeParameters.timeSystem)) {
|
||||
|
||||
if (timeParameters.mode === 'fixed') {
|
||||
isValid = this.areStartAndEndValid(timeParameters.bounds);
|
||||
} else {
|
||||
isValid = this.areStartAndEndValid(timeParameters.clockOffsets);
|
||||
}
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
areStartAndEndValid(bounds) {
|
||||
return bounds !== undefined &&
|
||||
bounds.start !== undefined &&
|
||||
bounds.start !== null &&
|
||||
bounds.end !== undefined &&
|
||||
bounds.start !== null &&
|
||||
!isNaN(bounds.start) &&
|
||||
!isNaN(bounds.end);
|
||||
}
|
||||
|
||||
isTimeSystemValid(timeSystem) {
|
||||
let isValid = timeSystem !== undefined;
|
||||
if (isValid) {
|
||||
let timeSystemObject = this.openmct.time.timeSystems.get(timeSystem);
|
||||
isValid = timeSystemObject !== undefined;
|
||||
}
|
||||
return isValid;
|
||||
}
|
||||
|
||||
isModeValid(mode) {
|
||||
let isValid = false;
|
||||
|
||||
if (mode !== undefined &&
|
||||
mode !== null) {
|
||||
isValid = true;
|
||||
}
|
||||
|
||||
if (isValid) {
|
||||
if (mode.toLowerCase() === MODE_FIXED) {
|
||||
isValid = true;
|
||||
} else {
|
||||
isValid = this.openmct.time.clocks.get(mode) !== undefined;
|
||||
}
|
||||
}
|
||||
return isValid;
|
||||
}
|
||||
|
||||
areStartAndEndEqual(firstBounds, secondBounds) {
|
||||
return firstBounds.start === secondBounds.start &&
|
||||
firstBounds.end === secondBounds.end;
|
||||
}
|
||||
}
|
28
src/plugins/URLTimeSettingsSynchronizer/plugin.js
Normal file
28
src/plugins/URLTimeSettingsSynchronizer/plugin.js
Normal file
@ -0,0 +1,28 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
import URLTimeSettingsSynchronizer from "./URLTimeSettingsSynchronizer.js";
|
||||
|
||||
export default function () {
|
||||
return function install(openmct) {
|
||||
return new URLTimeSettingsSynchronizer(openmct);
|
||||
}
|
||||
}
|
307
src/plugins/URLTimeSettingsSynchronizer/pluginSpec.js
Normal file
307
src/plugins/URLTimeSettingsSynchronizer/pluginSpec.js
Normal file
@ -0,0 +1,307 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
import {
|
||||
createOpenMct,
|
||||
resetApplicationState
|
||||
} from 'utils/testing';
|
||||
|
||||
describe("The URLTimeSettingsSynchronizer", () => {
|
||||
let openmct;
|
||||
let testClock;
|
||||
beforeAll(() => resetApplicationState());
|
||||
|
||||
beforeEach((done) => {
|
||||
openmct = createOpenMct();
|
||||
openmct.install(openmct.plugins.LocalTimeSystem());
|
||||
testClock = jasmine.createSpyObj("testClock", ["start", "stop", "tick", "currentValue", "on", "off"]);
|
||||
testClock.key = "test-clock";
|
||||
testClock.currentValue.and.returnValue(0);
|
||||
|
||||
openmct.time.addClock(testClock);
|
||||
|
||||
openmct.on('start', done);
|
||||
openmct.startHeadless();
|
||||
});
|
||||
|
||||
afterEach(() => resetApplicationState(openmct));
|
||||
|
||||
describe("realtime mode", () => {
|
||||
it("when the clock is set via the time API, it is immediately reflected in the URL", () => {
|
||||
//Test expected initial conditions
|
||||
expect(window.location.hash.includes('tc.mode=fixed')).toBe(true);
|
||||
|
||||
openmct.time.clock('local', {start: -1000, end: 100});
|
||||
|
||||
expect(window.location.hash.includes('tc.mode=local')).toBe(true);
|
||||
|
||||
//Test that expected initial conditions are no longer true
|
||||
expect(window.location.hash.includes('tc.mode=fixed')).toBe(false);
|
||||
});
|
||||
it("when offsets are set via the time API, they are immediately reflected in the URL", () => {
|
||||
//Test expected initial conditions
|
||||
expect(window.location.hash.includes('tc.startDelta')).toBe(false);
|
||||
expect(window.location.hash.includes('tc.endDelta')).toBe(false);
|
||||
|
||||
openmct.time.clock('local', {start: -1000, end: 100});
|
||||
expect(window.location.hash.includes('tc.startDelta=1000')).toBe(true);
|
||||
expect(window.location.hash.includes('tc.endDelta=100')).toBe(true);
|
||||
|
||||
openmct.time.clockOffsets({start: -2000, end: 200});
|
||||
expect(window.location.hash.includes('tc.startDelta=2000')).toBe(true);
|
||||
expect(window.location.hash.includes('tc.endDelta=200')).toBe(true);
|
||||
|
||||
//Test that expected initial conditions are no longer true
|
||||
expect(window.location.hash.includes('tc.mode=fixed')).toBe(false);
|
||||
});
|
||||
describe("when set in the url", () => {
|
||||
it("will change from fixed to realtime mode when the mode changes", () => {
|
||||
expectLocationToBeInFixedMode();
|
||||
return switchToRealtimeMode().then(() => {
|
||||
let clock = openmct.time.clock();
|
||||
|
||||
expect(clock).toBeDefined();
|
||||
expect(clock.key).toBe('local');
|
||||
});
|
||||
});
|
||||
it("the clock is correctly set in the API from the URL parameters", () => {
|
||||
return switchToRealtimeMode().then(() => {
|
||||
let resolveFunction;
|
||||
return new Promise((resolve) => {
|
||||
resolveFunction = resolve;
|
||||
|
||||
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
|
||||
//detected in the API.
|
||||
openmct.time.on('clock', resolveFunction);
|
||||
let hash = window.location.hash;
|
||||
hash = hash.replace('tc.mode=local', 'tc.mode=test-clock');
|
||||
window.location.hash = hash;
|
||||
}).then(() => {
|
||||
let clock = openmct.time.clock();
|
||||
expect(clock).toBeDefined();
|
||||
expect(clock.key).toBe('test-clock');
|
||||
openmct.time.off('clock', resolveFunction);
|
||||
});
|
||||
});
|
||||
});
|
||||
it("the clock offsets are correctly set in the API from the URL parameters", () => {
|
||||
return switchToRealtimeMode().then(() => {
|
||||
let resolveFunction;
|
||||
return new Promise((resolve) => {
|
||||
resolveFunction = resolve;
|
||||
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
|
||||
//detected in the API.
|
||||
openmct.time.on('clockOffsets', resolveFunction);
|
||||
let hash = window.location.hash;
|
||||
hash = hash.replace('tc.startDelta=1000', 'tc.startDelta=2000');
|
||||
hash = hash.replace('tc.endDelta=100', 'tc.endDelta=200');
|
||||
window.location.hash = hash;
|
||||
}).then(() => {
|
||||
let clockOffsets = openmct.time.clockOffsets();
|
||||
expect(clockOffsets).toBeDefined();
|
||||
expect(clockOffsets.start).toBe(-2000);
|
||||
expect(clockOffsets.end).toBe(200);
|
||||
openmct.time.off('clockOffsets', resolveFunction);
|
||||
});
|
||||
});
|
||||
});
|
||||
it("the time system is correctly set in the API from the URL parameters", () => {
|
||||
return switchToRealtimeMode().then(() => {
|
||||
let resolveFunction;
|
||||
return new Promise((resolve) => {
|
||||
resolveFunction = resolve;
|
||||
|
||||
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
|
||||
//detected in the API.
|
||||
openmct.time.on('timeSystem', resolveFunction);
|
||||
let hash = window.location.hash;
|
||||
hash = hash.replace('tc.timeSystem=utc', 'tc.timeSystem=local');
|
||||
window.location.hash = hash;
|
||||
}).then(() => {
|
||||
let timeSystem = openmct.time.timeSystem();
|
||||
expect(timeSystem).toBeDefined();
|
||||
expect(timeSystem.key).toBe('local');
|
||||
openmct.time.off('timeSystem', resolveFunction);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("fixed timespan mode", () => {
|
||||
beforeEach(() => {
|
||||
openmct.time.stopClock();
|
||||
openmct.time.timeSystem('utc', {start: 0, end: 1});
|
||||
});
|
||||
|
||||
it("when bounds are set via the time API, they are immediately reflected in the URL", ()=>{
|
||||
//Test expected initial conditions
|
||||
expect(window.location.hash.includes('tc.startBound=0')).toBe(true);
|
||||
expect(window.location.hash.includes('tc.endBound=1')).toBe(true);
|
||||
|
||||
openmct.time.bounds({start: 10, end: 20});
|
||||
|
||||
expect(window.location.hash.includes('tc.startBound=10')).toBe(true);
|
||||
expect(window.location.hash.includes('tc.endBound=20')).toBe(true);
|
||||
|
||||
//Test that expected initial conditions are no longer true
|
||||
expect(window.location.hash.includes('tc.startBound=0')).toBe(false);
|
||||
expect(window.location.hash.includes('tc.endBound=1')).toBe(false);
|
||||
});
|
||||
|
||||
it("when time system is set via the time API, it is immediately reflected in the URL", ()=>{
|
||||
//Test expected initial conditions
|
||||
expect(window.location.hash.includes('tc.timeSystem=utc')).toBe(true);
|
||||
|
||||
openmct.time.timeSystem('local', {start: 20, end: 30});
|
||||
|
||||
expect(window.location.hash.includes('tc.timeSystem=local')).toBe(true);
|
||||
|
||||
//Test that expected initial conditions are no longer true
|
||||
expect(window.location.hash.includes('tc.timeSystem=utc')).toBe(false);
|
||||
});
|
||||
describe("when set in the url", () => {
|
||||
it("time system changes are reflected in the API", () => {
|
||||
let resolveFunction;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
let timeSystem = openmct.time.timeSystem();
|
||||
resolveFunction = resolve;
|
||||
|
||||
expect(timeSystem.key).toBe('utc');
|
||||
window.location.hash = window.location.hash.replace('tc.timeSystem=utc', 'tc.timeSystem=local');
|
||||
|
||||
openmct.time.on('timeSystem', resolveFunction);
|
||||
}).then(() => {
|
||||
let timeSystem = openmct.time.timeSystem();
|
||||
expect(timeSystem.key).toBe('local');
|
||||
|
||||
openmct.time.off('timeSystem', resolveFunction);
|
||||
});
|
||||
});
|
||||
it("mode can be changed from realtime to fixed", () => {
|
||||
return switchToRealtimeMode().then(() => {
|
||||
expectLocationToBeInRealtimeMode();
|
||||
|
||||
expect(openmct.time.clock()).toBeDefined();
|
||||
}).then(switchToFixedMode).then(() => {
|
||||
let clock = openmct.time.clock();
|
||||
expect(clock).not.toBeDefined();
|
||||
});
|
||||
});
|
||||
it("bounds are correctly set in the API from the URL parameters", () => {
|
||||
let resolveFunction;
|
||||
|
||||
expectLocationToBeInFixedMode();
|
||||
|
||||
return new Promise((resolve) => {
|
||||
resolveFunction = resolve;
|
||||
openmct.time.on('bounds', resolveFunction);
|
||||
let hash = window.location.hash;
|
||||
hash = hash.replace('tc.startBound=0', 'tc.startBound=222')
|
||||
.replace('tc.endBound=1', 'tc.endBound=333');
|
||||
window.location.hash = hash;
|
||||
}).then(() => {
|
||||
let bounds = openmct.time.bounds();
|
||||
|
||||
expect(bounds).toBeDefined();
|
||||
expect(bounds.start).toBe(222);
|
||||
expect(bounds.end).toBe(333);
|
||||
});
|
||||
});
|
||||
it("bounds are correctly set in the API from the URL parameters where only the end bound changes", () => {
|
||||
let resolveFunction;
|
||||
|
||||
expectLocationToBeInFixedMode();
|
||||
|
||||
return new Promise((resolve) => {
|
||||
resolveFunction = resolve;
|
||||
openmct.time.on('bounds', resolveFunction);
|
||||
let hash = window.location.hash;
|
||||
hash = hash.replace('tc.endBound=1', 'tc.endBound=333');
|
||||
window.location.hash = hash;
|
||||
}).then(() => {
|
||||
let bounds = openmct.time.bounds();
|
||||
|
||||
expect(bounds).toBeDefined();
|
||||
expect(bounds.start).toBe(0);
|
||||
expect(bounds.end).toBe(333);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function setRealtimeLocationParameters() {
|
||||
let hash = window.location.hash.toString()
|
||||
.replace('tc.mode=fixed', 'tc.mode=local')
|
||||
.replace('tc.startBound=0', 'tc.startDelta=1000')
|
||||
.replace('tc.endBound=1', 'tc.endDelta=100');
|
||||
|
||||
window.location.hash = hash;
|
||||
}
|
||||
|
||||
function setFixedLocationParameters() {
|
||||
let hash = window.location.hash.toString()
|
||||
.replace('tc.mode=local', 'tc.mode=fixed')
|
||||
.replace('tc.timeSystem=utc', 'tc.timeSystem=local')
|
||||
.replace('tc.startDelta=1000', 'tc.startBound=50')
|
||||
.replace('tc.endDelta=100', 'tc.endBound=60');
|
||||
|
||||
window.location.hash = hash;
|
||||
}
|
||||
|
||||
function switchToRealtimeMode() {
|
||||
let resolveFunction;
|
||||
return new Promise((resolve) => {
|
||||
resolveFunction = resolve;
|
||||
openmct.time.on('clock', resolveFunction);
|
||||
setRealtimeLocationParameters();
|
||||
}).then(() => {
|
||||
openmct.time.off('clock', resolveFunction);
|
||||
});
|
||||
}
|
||||
|
||||
function switchToFixedMode() {
|
||||
let resolveFunction;
|
||||
return new Promise((resolve) => {
|
||||
resolveFunction = resolve;
|
||||
//The 'hashchange' event appears to be asynchronous, so we need to wait until a clock change has been
|
||||
//detected in the API.
|
||||
openmct.time.on('clock', resolveFunction);
|
||||
setFixedLocationParameters();
|
||||
}).then(() => {
|
||||
openmct.time.off('clock', resolveFunction);
|
||||
});
|
||||
}
|
||||
|
||||
function expectLocationToBeInRealtimeMode() {
|
||||
expect(window.location.hash.includes('tc.mode=local')).toBe(true);
|
||||
expect(window.location.hash.includes('tc.startDelta=1000')).toBe(true);
|
||||
expect(window.location.hash.includes('tc.endDelta=100')).toBe(true);
|
||||
expect(window.location.hash.includes('tc.mode=fixed')).toBe(false);
|
||||
}
|
||||
|
||||
function expectLocationToBeInFixedMode() {
|
||||
expect(window.location.hash.includes('tc.mode=fixed')).toBe(true);
|
||||
expect(window.location.hash.includes('tc.startBound=0')).toBe(true);
|
||||
expect(window.location.hash.includes('tc.endBound=1')).toBe(true);
|
||||
expect(window.location.hash.includes('tc.mode=local')).toBe(false);
|
||||
}
|
||||
});
|
@ -29,24 +29,28 @@ define([
|
||||
ClearDataAction,
|
||||
Vue
|
||||
) {
|
||||
return function plugin(appliesToObjects) {
|
||||
return function plugin(appliesToObjects, options = {indicator: true}) {
|
||||
let installIndicator = options.indicator;
|
||||
|
||||
appliesToObjects = appliesToObjects || [];
|
||||
|
||||
return function install(openmct) {
|
||||
let component = new Vue ({
|
||||
provide: {
|
||||
openmct
|
||||
},
|
||||
components: {
|
||||
GlobalClearIndicator: GlobaClearIndicator.default
|
||||
},
|
||||
template: '<GlobalClearIndicator></GlobalClearIndicator>'
|
||||
}),
|
||||
indicator = {
|
||||
element: component.$mount().$el
|
||||
};
|
||||
if (installIndicator) {
|
||||
let component = new Vue ({
|
||||
provide: {
|
||||
openmct
|
||||
},
|
||||
components: {
|
||||
GlobalClearIndicator: GlobaClearIndicator.default
|
||||
},
|
||||
template: '<GlobalClearIndicator></GlobalClearIndicator>'
|
||||
}),
|
||||
indicator = {
|
||||
element: component.$mount().$el
|
||||
};
|
||||
|
||||
openmct.indicators.add(indicator);
|
||||
openmct.indicators.add(indicator);
|
||||
}
|
||||
|
||||
openmct.contextMenu.registerAction(new ClearDataAction.default(openmct, appliesToObjects));
|
||||
};
|
||||
|
299
src/plugins/condition/Condition.js
Normal file
299
src/plugins/condition/Condition.js
Normal file
@ -0,0 +1,299 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import EventEmitter from 'EventEmitter';
|
||||
import uuid from 'uuid';
|
||||
import TelemetryCriterion from "./criterion/TelemetryCriterion";
|
||||
import { evaluateResults } from './utils/evaluator';
|
||||
import { getLatestTimestamp } from './utils/time';
|
||||
import AllTelemetryCriterion from "./criterion/AllTelemetryCriterion";
|
||||
import {TRIGGER_CONJUNCTION, TRIGGER_LABEL} from "./utils/constants";
|
||||
|
||||
/*
|
||||
* conditionConfiguration = {
|
||||
* id: uuid,
|
||||
* trigger: 'any'/'all'/'not','xor',
|
||||
* criteria: [
|
||||
* {
|
||||
* telemetry: '',
|
||||
* operation: '',
|
||||
* input: [],
|
||||
* metadata: ''
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
export default class Condition extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Manages criteria and emits the result of - true or false - based on criteria evaluated.
|
||||
* @constructor
|
||||
* @param conditionConfiguration: {id: uuid,trigger: enum, criteria: Array of {id: uuid, operation: enum, input: Array, metaDataKey: string, key: {domainObject.identifier} }
|
||||
* @param openmct
|
||||
* @param conditionManager
|
||||
*/
|
||||
constructor(conditionConfiguration, openmct, conditionManager) {
|
||||
super();
|
||||
|
||||
this.openmct = openmct;
|
||||
this.conditionManager = conditionManager;
|
||||
this.id = conditionConfiguration.id;
|
||||
this.criteria = [];
|
||||
this.result = undefined;
|
||||
this.timeSystems = this.openmct.time.getAllTimeSystems();
|
||||
if (conditionConfiguration.configuration.criteria) {
|
||||
this.createCriteria(conditionConfiguration.configuration.criteria);
|
||||
}
|
||||
this.trigger = conditionConfiguration.configuration.trigger;
|
||||
this.description = '';
|
||||
}
|
||||
|
||||
getResult(datum) {
|
||||
if (!datum || !datum.id) {
|
||||
console.log('no data received');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isTelemetryUsed(datum.id)) {
|
||||
|
||||
this.criteria.forEach(criterion => {
|
||||
if (this.isAnyOrAllTelemetry(criterion)) {
|
||||
criterion.getResult(datum, this.conditionManager.telemetryObjects);
|
||||
} else {
|
||||
criterion.getResult(datum);
|
||||
}
|
||||
});
|
||||
|
||||
this.result = evaluateResults(this.criteria.map(criterion => criterion.result), this.trigger);
|
||||
}
|
||||
}
|
||||
|
||||
isAnyOrAllTelemetry(criterion) {
|
||||
return (criterion.telemetry && (criterion.telemetry === 'all' || criterion.telemetry === 'any'));
|
||||
}
|
||||
|
||||
isTelemetryUsed(id) {
|
||||
return this.criteria.some(criterion => {
|
||||
return this.isAnyOrAllTelemetry(criterion) || criterion.telemetryObjectIdAsString === id;
|
||||
});
|
||||
}
|
||||
|
||||
update(conditionConfiguration) {
|
||||
this.updateTrigger(conditionConfiguration.configuration.trigger);
|
||||
this.updateCriteria(conditionConfiguration.configuration.criteria);
|
||||
}
|
||||
|
||||
updateTrigger(trigger) {
|
||||
if (this.trigger !== trigger) {
|
||||
this.trigger = trigger;
|
||||
}
|
||||
}
|
||||
|
||||
generateCriterion(criterionConfiguration) {
|
||||
return {
|
||||
id: criterionConfiguration.id || uuid(),
|
||||
telemetry: criterionConfiguration.telemetry || '',
|
||||
telemetryObjects: this.conditionManager.telemetryObjects,
|
||||
operation: criterionConfiguration.operation || '',
|
||||
input: criterionConfiguration.input === undefined ? [] : criterionConfiguration.input,
|
||||
metadata: criterionConfiguration.metadata || ''
|
||||
};
|
||||
}
|
||||
|
||||
createCriteria(criterionConfigurations) {
|
||||
criterionConfigurations.forEach((criterionConfiguration) => {
|
||||
this.addCriterion(criterionConfiguration);
|
||||
});
|
||||
this.updateDescription();
|
||||
}
|
||||
|
||||
updateCriteria(criterionConfigurations) {
|
||||
this.destroyCriteria();
|
||||
this.createCriteria(criterionConfigurations);
|
||||
}
|
||||
|
||||
updateTelemetryObjects() {
|
||||
this.criteria.forEach((criterion) => {
|
||||
criterion.updateTelemetryObjects(this.conditionManager.telemetryObjects);
|
||||
});
|
||||
this.updateDescription();
|
||||
}
|
||||
|
||||
/**
|
||||
* adds criterion to the condition.
|
||||
*/
|
||||
addCriterion(criterionConfiguration) {
|
||||
let criterion;
|
||||
let criterionConfigurationWithId = this.generateCriterion(criterionConfiguration || null);
|
||||
if (criterionConfiguration.telemetry && (criterionConfiguration.telemetry === 'any' || criterionConfiguration.telemetry === 'all')) {
|
||||
criterion = new AllTelemetryCriterion(criterionConfigurationWithId, this.openmct);
|
||||
} else {
|
||||
criterion = new TelemetryCriterion(criterionConfigurationWithId, this.openmct);
|
||||
}
|
||||
criterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
|
||||
criterion.on('telemetryIsStale', (obj) => this.handleStaleCriterion(obj));
|
||||
if (!this.criteria) {
|
||||
this.criteria = [];
|
||||
}
|
||||
this.criteria.push(criterion);
|
||||
return criterionConfigurationWithId.id;
|
||||
}
|
||||
|
||||
findCriterion(id) {
|
||||
let criterion;
|
||||
|
||||
for (let i=0, ii=this.criteria.length; i < ii; i ++) {
|
||||
if (this.criteria[i].id === id) {
|
||||
criterion = {
|
||||
item: this.criteria[i],
|
||||
index: i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return criterion;
|
||||
}
|
||||
|
||||
updateCriterion(id, criterionConfiguration) {
|
||||
let found = this.findCriterion(id);
|
||||
if (found) {
|
||||
const newCriterionConfiguration = this.generateCriterion(criterionConfiguration);
|
||||
let newCriterion = new TelemetryCriterion(newCriterionConfiguration, this.openmct);
|
||||
newCriterion.on('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
|
||||
newCriterion.on('telemetryIsStale', (obj) => this.handleStaleCriterion(obj));
|
||||
|
||||
let criterion = found.item;
|
||||
criterion.unsubscribe();
|
||||
criterion.off('criterionUpdated', (obj) => this.handleCriterionUpdated(obj));
|
||||
criterion.off('telemetryIsStale', (obj) => this.handleStaleCriterion(obj));
|
||||
this.criteria.splice(found.index, 1, newCriterion);
|
||||
this.updateDescription();
|
||||
}
|
||||
}
|
||||
|
||||
destroyCriterion(id) {
|
||||
let found = this.findCriterion(id);
|
||||
if (found) {
|
||||
let criterion = found.item;
|
||||
criterion.off('criterionUpdated', (obj) => {
|
||||
this.handleCriterionUpdated(obj);
|
||||
});
|
||||
criterion.off('telemetryIsStale', (obj) => {
|
||||
this.handleStaleCriterion(obj);
|
||||
});
|
||||
criterion.destroy();
|
||||
this.criteria.splice(found.index, 1);
|
||||
this.updateDescription();
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
handleCriterionUpdated(criterion) {
|
||||
let found = this.findCriterion(criterion.id);
|
||||
if (found) {
|
||||
this.criteria[found.index] = criterion.data;
|
||||
this.updateDescription();
|
||||
}
|
||||
}
|
||||
|
||||
handleStaleCriterion(updatedCriterion) {
|
||||
this.result = evaluateResults(this.criteria.map(criterion => criterion.result), this.trigger);
|
||||
let latestTimestamp = {};
|
||||
latestTimestamp = getLatestTimestamp(
|
||||
latestTimestamp,
|
||||
updatedCriterion.data,
|
||||
this.timeSystems,
|
||||
this.openmct.time.timeSystem()
|
||||
);
|
||||
this.conditionManager.updateCurrentCondition(latestTimestamp);
|
||||
}
|
||||
|
||||
updateDescription() {
|
||||
const triggerDescription = this.getTriggerDescription();
|
||||
let description = '';
|
||||
this.criteria.forEach((criterion, index) => {
|
||||
if (!index) {
|
||||
description = `Match if ${triggerDescription.prefix}`;
|
||||
}
|
||||
description = `${description} ${criterion.getDescription()} ${(index < this.criteria.length - 1) ? triggerDescription.conjunction : ''}`;
|
||||
});
|
||||
this.description = description;
|
||||
this.conditionManager.updateConditionDescription(this);
|
||||
}
|
||||
|
||||
getTriggerDescription() {
|
||||
return {
|
||||
conjunction: TRIGGER_CONJUNCTION[this.trigger],
|
||||
prefix: `${TRIGGER_LABEL[this.trigger]}: `
|
||||
};
|
||||
}
|
||||
|
||||
requestLADConditionResult() {
|
||||
let latestTimestamp;
|
||||
let criteriaResults = {};
|
||||
const criteriaRequests = this.criteria
|
||||
.map(criterion => criterion.requestLAD(this.conditionManager.telemetryObjects));
|
||||
|
||||
return Promise.all(criteriaRequests)
|
||||
.then(results => {
|
||||
results.forEach(resultObj => {
|
||||
const { id, data, data: { result } } = resultObj;
|
||||
if (this.findCriterion(id)) {
|
||||
criteriaResults[id] = !!result;
|
||||
}
|
||||
latestTimestamp = getLatestTimestamp(
|
||||
latestTimestamp,
|
||||
data,
|
||||
this.timeSystems,
|
||||
this.openmct.time.timeSystem()
|
||||
);
|
||||
});
|
||||
return {
|
||||
id: this.id,
|
||||
data: Object.assign(
|
||||
{},
|
||||
latestTimestamp,
|
||||
{ result: evaluateResults(Object.values(criteriaResults), this.trigger) }
|
||||
)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
getCriteria() {
|
||||
return this.criteria;
|
||||
}
|
||||
|
||||
destroyCriteria() {
|
||||
let success = true;
|
||||
//looping through the array backwards since destroyCriterion modifies the criteria array
|
||||
for (let i=this.criteria.length-1; i >= 0; i--) {
|
||||
success = success && this.destroyCriterion(this.criteria[i].id);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.destroyCriteria();
|
||||
}
|
||||
}
|
388
src/plugins/condition/ConditionManager.js
Normal file
388
src/plugins/condition/ConditionManager.js
Normal file
@ -0,0 +1,388 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import Condition from "./Condition";
|
||||
import { getLatestTimestamp } from './utils/time';
|
||||
import uuid from "uuid";
|
||||
import EventEmitter from 'EventEmitter';
|
||||
|
||||
export default class ConditionManager extends EventEmitter {
|
||||
constructor(conditionSetDomainObject, openmct) {
|
||||
super();
|
||||
this.openmct = openmct;
|
||||
this.conditionSetDomainObject = conditionSetDomainObject;
|
||||
this.timeSystems = this.openmct.time.getAllTimeSystems();
|
||||
this.composition = this.openmct.composition.get(conditionSetDomainObject);
|
||||
this.composition.on('add', this.subscribeToTelemetry, this);
|
||||
this.composition.on('remove', this.unsubscribeFromTelemetry, this);
|
||||
this.compositionLoad = this.composition.load();
|
||||
this.subscriptions = {};
|
||||
this.telemetryObjects = {};
|
||||
this.testData = {conditionTestData: [], applied: false};
|
||||
this.initialize();
|
||||
|
||||
this.stopObservingForChanges = this.openmct.objects.observe(this.conditionSetDomainObject, '*', (newDomainObject) => {
|
||||
this.conditionSetDomainObject = newDomainObject;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
subscribeToTelemetry(endpoint) {
|
||||
const id = this.openmct.objects.makeKeyString(endpoint.identifier);
|
||||
if (this.subscriptions[id]) {
|
||||
console.log('subscription already exists');
|
||||
return;
|
||||
}
|
||||
this.telemetryObjects[id] = Object.assign({}, endpoint, {telemetryMetaData: this.openmct.telemetry.getMetadata(endpoint).valueMetadatas});
|
||||
this.subscriptions[id] = this.openmct.telemetry.subscribe(
|
||||
endpoint,
|
||||
this.telemetryReceived.bind(this, endpoint)
|
||||
);
|
||||
this.updateConditionTelemetryObjects();
|
||||
}
|
||||
|
||||
unsubscribeFromTelemetry(endpointIdentifier) {
|
||||
const id = this.openmct.objects.makeKeyString(endpointIdentifier);
|
||||
if (!this.subscriptions[id]) {
|
||||
console.log('no subscription to remove');
|
||||
return;
|
||||
}
|
||||
|
||||
this.subscriptions[id]();
|
||||
delete this.subscriptions[id];
|
||||
delete this.telemetryObjects[id];
|
||||
this.removeConditionTelemetryObjects();
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.conditions = [];
|
||||
if (this.conditionSetDomainObject.configuration.conditionCollection.length) {
|
||||
this.conditionSetDomainObject.configuration.conditionCollection.forEach((conditionConfiguration, index) => {
|
||||
this.initCondition(conditionConfiguration, index);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updateConditionTelemetryObjects() {
|
||||
this.conditions.forEach((condition) => condition.updateTelemetryObjects());
|
||||
}
|
||||
|
||||
removeConditionTelemetryObjects() {
|
||||
let conditionsChanged = false;
|
||||
this.conditionSetDomainObject.configuration.conditionCollection.forEach((conditionConfiguration, conditionIndex) => {
|
||||
let conditionChanged = false;
|
||||
conditionConfiguration.configuration.criteria.forEach((criterion, index) => {
|
||||
const isAnyAllTelemetry = criterion.telemetry && (criterion.telemetry === 'any' || criterion.telemetry === 'all');
|
||||
if (!isAnyAllTelemetry) {
|
||||
const found = Object.values(this.telemetryObjects).find((telemetryObject) => {
|
||||
return this.openmct.objects.areIdsEqual(telemetryObject.identifier, criterion.telemetry);
|
||||
});
|
||||
if (!found) {
|
||||
criterion.telemetry = '';
|
||||
criterion.metadata = '';
|
||||
criterion.input = [];
|
||||
criterion.operation = '';
|
||||
conditionChanged = true;
|
||||
}
|
||||
} else {
|
||||
conditionChanged = true;
|
||||
}
|
||||
});
|
||||
if (conditionChanged) {
|
||||
this.updateCondition(conditionConfiguration, conditionIndex);
|
||||
conditionsChanged = true;
|
||||
}
|
||||
});
|
||||
if (conditionsChanged) {
|
||||
this.persistConditions();
|
||||
}
|
||||
}
|
||||
|
||||
updateCondition(conditionConfiguration, index) {
|
||||
let condition = this.conditions[index];
|
||||
this.conditionSetDomainObject.configuration.conditionCollection[index] = conditionConfiguration;
|
||||
condition.update(conditionConfiguration);
|
||||
this.persistConditions();
|
||||
}
|
||||
|
||||
updateConditionDescription(condition) {
|
||||
const found = this.conditionSetDomainObject.configuration.conditionCollection.find(conditionConfiguration => (conditionConfiguration.id === condition.id));
|
||||
found.summary = condition.description;
|
||||
this.persistConditions();
|
||||
}
|
||||
|
||||
initCondition(conditionConfiguration, index) {
|
||||
let condition = new Condition(conditionConfiguration, this.openmct, this);
|
||||
if (index !== undefined) {
|
||||
this.conditions.splice(index + 1, 0, condition);
|
||||
} else {
|
||||
this.conditions.unshift(condition);
|
||||
}
|
||||
}
|
||||
|
||||
createCondition(conditionConfiguration) {
|
||||
let conditionObj;
|
||||
if (conditionConfiguration) {
|
||||
conditionObj = {
|
||||
...conditionConfiguration,
|
||||
id: uuid(),
|
||||
configuration: {
|
||||
...conditionConfiguration.configuration,
|
||||
name: `Copy of ${conditionConfiguration.configuration.name}`
|
||||
}
|
||||
};
|
||||
} else {
|
||||
conditionObj = {
|
||||
id: uuid(),
|
||||
configuration: {
|
||||
name: 'Unnamed Condition',
|
||||
output: 'false',
|
||||
trigger: 'all',
|
||||
criteria: [{
|
||||
id: uuid(),
|
||||
telemetry: '',
|
||||
operation: '',
|
||||
input: [],
|
||||
metadata: ''
|
||||
}]
|
||||
},
|
||||
summary: ''
|
||||
};
|
||||
}
|
||||
|
||||
return conditionObj;
|
||||
}
|
||||
|
||||
addCondition() {
|
||||
this.createAndSaveCondition();
|
||||
}
|
||||
|
||||
cloneCondition(conditionConfiguration, index) {
|
||||
let clonedConfig = JSON.parse(JSON.stringify(conditionConfiguration));
|
||||
clonedConfig.configuration.criteria.forEach((criterion) => criterion.id = uuid());
|
||||
this.createAndSaveCondition(index, clonedConfig);
|
||||
}
|
||||
|
||||
createAndSaveCondition(index, conditionConfiguration) {
|
||||
const newCondition = this.createCondition(conditionConfiguration);
|
||||
if (index !== undefined) {
|
||||
this.conditionSetDomainObject.configuration.conditionCollection.splice(index + 1, 0, newCondition);
|
||||
} else {
|
||||
this.conditionSetDomainObject.configuration.conditionCollection.unshift(newCondition);
|
||||
}
|
||||
this.initCondition(newCondition, index);
|
||||
this.persistConditions();
|
||||
}
|
||||
|
||||
removeCondition(index) {
|
||||
let condition = this.conditions[index];
|
||||
condition.destroy();
|
||||
this.conditions.splice(index, 1);
|
||||
this.conditionSetDomainObject.configuration.conditionCollection.splice(index, 1);
|
||||
this.persistConditions();
|
||||
}
|
||||
|
||||
findConditionById(id) {
|
||||
return this.conditions.find(condition => condition.id === id);
|
||||
}
|
||||
|
||||
reorderConditions(reorderPlan) {
|
||||
let oldConditions = Array.from(this.conditionSetDomainObject.configuration.conditionCollection);
|
||||
let newCollection = [];
|
||||
reorderPlan.forEach((reorderEvent) => {
|
||||
let item = oldConditions[reorderEvent.oldIndex];
|
||||
newCollection.push(item);
|
||||
this.conditionSetDomainObject.configuration.conditionCollection = newCollection;
|
||||
});
|
||||
this.persistConditions();
|
||||
}
|
||||
|
||||
getCurrentCondition() {
|
||||
const conditionCollection = this.conditionSetDomainObject.configuration.conditionCollection;
|
||||
let currentCondition = conditionCollection[conditionCollection.length-1];
|
||||
|
||||
for (let i = 0; i < conditionCollection.length - 1; i++) {
|
||||
const condition = this.findConditionById(conditionCollection[i].id)
|
||||
if (condition.result) {
|
||||
//first condition to be true wins
|
||||
currentCondition = conditionCollection[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return currentCondition;
|
||||
}
|
||||
|
||||
getCurrentConditionLAD(conditionResults) {
|
||||
const conditionCollection = this.conditionSetDomainObject.configuration.conditionCollection;
|
||||
let currentCondition = conditionCollection[conditionCollection.length-1];
|
||||
|
||||
for (let i = 0; i < conditionCollection.length - 1; i++) {
|
||||
if (conditionResults[conditionCollection[i].id]) {
|
||||
//first condition to be true wins
|
||||
currentCondition = conditionCollection[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return currentCondition;
|
||||
}
|
||||
|
||||
requestLADConditionSetOutput() {
|
||||
if (!this.conditions.length) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
return this.compositionLoad.then(() => {
|
||||
let latestTimestamp;
|
||||
let conditionResults = {};
|
||||
const conditionRequests = this.conditions
|
||||
.map(condition => condition.requestLADConditionResult());
|
||||
|
||||
return Promise.all(conditionRequests)
|
||||
.then((results) => {
|
||||
results.forEach(resultObj => {
|
||||
const { id, data, data: { result } } = resultObj;
|
||||
if (this.findConditionById(id)) {
|
||||
conditionResults[id] = !!result;
|
||||
}
|
||||
latestTimestamp = getLatestTimestamp(
|
||||
latestTimestamp,
|
||||
data,
|
||||
this.timeSystems,
|
||||
this.openmct.time.timeSystem()
|
||||
);
|
||||
});
|
||||
|
||||
if (!Object.values(latestTimestamp).some(timeSystem => timeSystem)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const currentCondition = this.getCurrentConditionLAD(conditionResults);
|
||||
const currentOutput = Object.assign(
|
||||
{
|
||||
output: currentCondition.configuration.output,
|
||||
id: this.conditionSetDomainObject.identifier,
|
||||
conditionId: currentCondition.id
|
||||
},
|
||||
latestTimestamp
|
||||
);
|
||||
|
||||
return [currentOutput];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
isTelemetryUsed(endpoint) {
|
||||
const id = this.openmct.objects.makeKeyString(endpoint.identifier);
|
||||
|
||||
for(const condition of this.conditions) {
|
||||
if (condition.isTelemetryUsed(id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
telemetryReceived(endpoint, datum) {
|
||||
if (!this.isTelemetryUsed(endpoint)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const normalizedDatum = this.createNormalizedDatum(datum, endpoint);
|
||||
const timeSystemKey = this.openmct.time.timeSystem().key;
|
||||
let timestamp = {};
|
||||
timestamp[timeSystemKey] = normalizedDatum[timeSystemKey];
|
||||
|
||||
this.conditions.forEach(condition => {
|
||||
condition.getResult(normalizedDatum);
|
||||
});
|
||||
|
||||
this.updateCurrentCondition(timestamp);
|
||||
}
|
||||
|
||||
updateCurrentCondition(timestamp) {
|
||||
const currentCondition = this.getCurrentCondition();
|
||||
|
||||
this.emit('conditionSetResultUpdated',
|
||||
Object.assign(
|
||||
{
|
||||
output: currentCondition.configuration.output,
|
||||
id: this.conditionSetDomainObject.identifier,
|
||||
conditionId: currentCondition.id
|
||||
},
|
||||
timestamp
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
getTestData(metadatum) {
|
||||
let data = undefined;
|
||||
if (this.testData.applied) {
|
||||
const found = this.testData.conditionTestInputs.find((testInput) => (testInput.metadata === metadatum.source));
|
||||
if (found) {
|
||||
data = found.value;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
createNormalizedDatum(telemetryDatum, endpoint) {
|
||||
const id = this.openmct.objects.makeKeyString(endpoint.identifier);
|
||||
const metadata = this.openmct.telemetry.getMetadata(endpoint).valueMetadatas;
|
||||
|
||||
const normalizedDatum = Object.values(metadata).reduce((datum, metadatum) => {
|
||||
const testValue = this.getTestData(metadatum);
|
||||
const formatter = this.openmct.telemetry.getValueFormatter(metadatum);
|
||||
datum[metadatum.key] = testValue !== undefined ? formatter.parse(testValue) : formatter.parse(telemetryDatum[metadatum.source]);
|
||||
return datum;
|
||||
}, {});
|
||||
|
||||
normalizedDatum.id = id;
|
||||
|
||||
return normalizedDatum;
|
||||
}
|
||||
|
||||
updateTestData(testData) {
|
||||
this.testData = testData;
|
||||
this.openmct.objects.mutate(this.conditionSetDomainObject, 'configuration.conditionTestData', this.testData.conditionTestInputs);
|
||||
}
|
||||
|
||||
persistConditions() {
|
||||
this.openmct.objects.mutate(this.conditionSetDomainObject, 'configuration.conditionCollection', this.conditionSetDomainObject.configuration.conditionCollection);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.composition.off('add', this.subscribeToTelemetry, this);
|
||||
this.composition.off('remove', this.unsubscribeFromTelemetry, this);
|
||||
Object.values(this.subscriptions).forEach(unsubscribe => unsubscribe());
|
||||
delete this.subscriptions;
|
||||
|
||||
if(this.stopObservingForChanges) {
|
||||
this.stopObservingForChanges();
|
||||
}
|
||||
|
||||
this.conditions.forEach((condition) => {
|
||||
condition.destroy();
|
||||
})
|
||||
}
|
||||
}
|
133
src/plugins/condition/ConditionManagerSpec.js
Normal file
133
src/plugins/condition/ConditionManagerSpec.js
Normal file
@ -0,0 +1,133 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import ConditionManager from './ConditionManager';
|
||||
|
||||
describe('ConditionManager', () => {
|
||||
|
||||
let conditionMgr;
|
||||
let mockListener;
|
||||
let openmct = {};
|
||||
let mockCondition = {
|
||||
isDefault: true,
|
||||
id: '1234-5678',
|
||||
configuration: {
|
||||
criteria: []
|
||||
}
|
||||
};
|
||||
let conditionSetDomainObject = {
|
||||
identifier: {
|
||||
namespace: "",
|
||||
key: "600a7372-8d48-4dc4-98b6-548611b1ff7e"
|
||||
},
|
||||
type: "conditionSet",
|
||||
location: "mine",
|
||||
configuration: {
|
||||
conditionCollection: [
|
||||
mockCondition
|
||||
]
|
||||
}
|
||||
};
|
||||
let mockComposition;
|
||||
let loader;
|
||||
let mockTimeSystems;
|
||||
|
||||
function mockAngularComponents() {
|
||||
let mockInjector = jasmine.createSpyObj('$injector', ['get']);
|
||||
|
||||
let mockInstantiate = jasmine.createSpy('mockInstantiate');
|
||||
mockInstantiate.and.returnValue(mockInstantiate);
|
||||
|
||||
let mockDomainObject = {
|
||||
useCapability: function () {
|
||||
return mockCondition;
|
||||
}
|
||||
};
|
||||
mockInstantiate.and.callFake(function () {
|
||||
return mockDomainObject;
|
||||
});
|
||||
mockInjector.get.and.callFake(function (service) {
|
||||
return {
|
||||
'instantiate': mockInstantiate
|
||||
}[service];
|
||||
});
|
||||
|
||||
openmct.$injector = mockInjector;
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
|
||||
mockAngularComponents();
|
||||
mockListener = jasmine.createSpy('mockListener');
|
||||
loader = {};
|
||||
loader.promise = new Promise(function (resolve, reject) {
|
||||
loader.resolve = resolve;
|
||||
loader.reject = reject;
|
||||
});
|
||||
|
||||
mockComposition = jasmine.createSpyObj('compositionCollection', [
|
||||
'load',
|
||||
'on',
|
||||
'off'
|
||||
]);
|
||||
mockComposition.load.and.callFake(() => {
|
||||
setTimeout(() => {
|
||||
loader.resolve();
|
||||
});
|
||||
return loader.promise;
|
||||
});
|
||||
mockComposition.on('add', mockListener);
|
||||
mockComposition.on('remove', mockListener);
|
||||
openmct.composition = jasmine.createSpyObj('compositionAPI', [
|
||||
'get'
|
||||
]);
|
||||
openmct.composition.get.and.returnValue(mockComposition);
|
||||
|
||||
openmct.objects = jasmine.createSpyObj('objects', ['get', 'makeKeyString', 'observe', 'mutate']);
|
||||
openmct.objects.get.and.returnValues(new Promise(function (resolve, reject) {
|
||||
resolve(conditionSetDomainObject);
|
||||
}), new Promise(function (resolve, reject) {
|
||||
resolve(mockCondition);
|
||||
}));
|
||||
openmct.objects.makeKeyString.and.returnValue(conditionSetDomainObject.identifier.key);
|
||||
openmct.objects.observe.and.returnValue(function () {});
|
||||
openmct.objects.mutate.and.returnValue(function () {});
|
||||
|
||||
mockTimeSystems = {
|
||||
key: 'utc'
|
||||
};
|
||||
openmct.time = jasmine.createSpyObj('time', ['getAllTimeSystems']);
|
||||
openmct.time.getAllTimeSystems.and.returnValue([mockTimeSystems]);
|
||||
|
||||
conditionMgr = new ConditionManager(conditionSetDomainObject, openmct);
|
||||
|
||||
conditionMgr.on('conditionSetResultUpdated', mockListener);
|
||||
conditionMgr.on('telemetryReceived', mockListener);
|
||||
});
|
||||
|
||||
it('creates a conditionCollection with a default condition', function () {
|
||||
expect(conditionMgr.conditionSetDomainObject.configuration.conditionCollection.length).toEqual(1);
|
||||
let defaultConditionId = conditionMgr.conditions[0].id;
|
||||
expect(defaultConditionId).toEqual(mockCondition.id);
|
||||
});
|
||||
|
||||
});
|
33
src/plugins/condition/ConditionSetCompositionPolicy.js
Normal file
33
src/plugins/condition/ConditionSetCompositionPolicy.js
Normal file
@ -0,0 +1,33 @@
|
||||
/*****************************************************************************
|
||||
* 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 function ConditionSetCompositionPolicy(openmct) {
|
||||
return {
|
||||
allow: function (parent, child) {
|
||||
if (parent.type === 'conditionSet' && !openmct.telemetry.isTelemetryObject(child)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
85
src/plugins/condition/ConditionSetCompositionPolicySpec.js
Normal file
85
src/plugins/condition/ConditionSetCompositionPolicySpec.js
Normal file
@ -0,0 +1,85 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import ConditionSetCompositionPolicy from './ConditionSetCompositionPolicy';
|
||||
|
||||
describe('ConditionSetCompositionPolicy', () => {
|
||||
|
||||
let policy;
|
||||
let testTelemetryObject;
|
||||
let openmct = {};
|
||||
let parentDomainObject;
|
||||
|
||||
beforeAll(function () {
|
||||
testTelemetryObject = {
|
||||
identifier:{ namespace: "", key: "test-object"},
|
||||
type: "test-object",
|
||||
name: "Test Object",
|
||||
telemetry: {
|
||||
values: [{
|
||||
key: "some-key",
|
||||
name: "Some attribute",
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
}, {
|
||||
key: "some-other-key",
|
||||
name: "Another attribute",
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
openmct.objects = jasmine.createSpyObj('objects', ['get', 'makeKeyString']);
|
||||
openmct.objects.get.and.returnValue(testTelemetryObject);
|
||||
openmct.objects.makeKeyString.and.returnValue(testTelemetryObject.identifier.key);
|
||||
openmct.telemetry = jasmine.createSpyObj('telemetry', ['isTelemetryObject']);
|
||||
policy = new ConditionSetCompositionPolicy(openmct);
|
||||
parentDomainObject = {};
|
||||
});
|
||||
|
||||
it('returns true for object types that are not conditionSets', function () {
|
||||
parentDomainObject.type = 'random';
|
||||
openmct.telemetry.isTelemetryObject.and.returnValue(false);
|
||||
expect(policy.allow(parentDomainObject, {})).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for object types that are not telemetry objects when parent is a conditionSet', function () {
|
||||
parentDomainObject.type = 'conditionSet';
|
||||
openmct.telemetry.isTelemetryObject.and.returnValue(false);
|
||||
expect(policy.allow(parentDomainObject, {})).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true for object types that are telemetry objects when parent is a conditionSet', function () {
|
||||
parentDomainObject.type = 'conditionSet';
|
||||
openmct.telemetry.isTelemetryObject.and.returnValue(true);
|
||||
expect(policy.allow(parentDomainObject, testTelemetryObject)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true for object types that are telemetry objects when parent is not a conditionSet', function () {
|
||||
parentDomainObject.type = 'random';
|
||||
openmct.telemetry.isTelemetryObject.and.returnValue(true);
|
||||
expect(policy.allow(parentDomainObject, testTelemetryObject)).toBe(true);
|
||||
});
|
||||
|
||||
});
|
77
src/plugins/condition/ConditionSetMetadataProvider.js
Normal file
77
src/plugins/condition/ConditionSetMetadataProvider.js
Normal file
@ -0,0 +1,77 @@
|
||||
/*****************************************************************************
|
||||
* 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 ConditionSetMetadataProvider {
|
||||
constructor(openmct) {
|
||||
this.openmct = openmct;
|
||||
}
|
||||
|
||||
supportsMetadata(domainObject) {
|
||||
return domainObject.type === 'conditionSet';
|
||||
}
|
||||
|
||||
getDomains(domainObject) {
|
||||
return this.openmct.time.getAllTimeSystems().map(function (ts, i) {
|
||||
return {
|
||||
key: ts.key,
|
||||
name: ts.name,
|
||||
format: ts.timeFormat,
|
||||
hints: {
|
||||
domain: i
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
getMetadata(domainObject) {
|
||||
const enumerations = domainObject.configuration.conditionCollection
|
||||
.map((condition, index) => {
|
||||
return {
|
||||
string: condition.configuration.output,
|
||||
value: index
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
values: this.getDomains().concat([
|
||||
{
|
||||
key: "state",
|
||||
source: "output",
|
||||
name: "State",
|
||||
format: "enum",
|
||||
enumerations: enumerations,
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "output",
|
||||
name: "Value",
|
||||
format: "string",
|
||||
hints: {
|
||||
range: 2
|
||||
}
|
||||
}
|
||||
])
|
||||
};
|
||||
}
|
||||
}
|
86
src/plugins/condition/ConditionSetTelemetryProvider.js
Normal file
86
src/plugins/condition/ConditionSetTelemetryProvider.js
Normal file
@ -0,0 +1,86 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import ConditionManager from './ConditionManager'
|
||||
|
||||
export default class ConditionSetTelemetryProvider {
|
||||
constructor(openmct) {
|
||||
this.openmct = openmct;
|
||||
this.conditionManagerPool = {};
|
||||
}
|
||||
|
||||
isTelemetryObject(domainObject) {
|
||||
return domainObject.type === 'conditionSet';
|
||||
}
|
||||
|
||||
supportsRequest(domainObject) {
|
||||
return domainObject.type === 'conditionSet';
|
||||
}
|
||||
|
||||
supportsSubscribe(domainObject) {
|
||||
return domainObject.type === 'conditionSet';
|
||||
}
|
||||
|
||||
request(domainObject) {
|
||||
let conditionManager = this.getConditionManager(domainObject);
|
||||
|
||||
return conditionManager.requestLADConditionSetOutput()
|
||||
.then(latestOutput => {
|
||||
return latestOutput;
|
||||
});
|
||||
}
|
||||
|
||||
subscribe(domainObject, callback) {
|
||||
let conditionManager = this.getConditionManager(domainObject);
|
||||
|
||||
conditionManager.on('conditionSetResultUpdated', callback);
|
||||
|
||||
return this.destroyConditionManager.bind(this, this.openmct.objects.makeKeyString(domainObject.identifier));
|
||||
}
|
||||
|
||||
/**
|
||||
* returns conditionManager instance for corresponding domain object
|
||||
* creates the instance if it is not yet created
|
||||
* @private
|
||||
*/
|
||||
getConditionManager(domainObject) {
|
||||
const id = this.openmct.objects.makeKeyString(domainObject.identifier);
|
||||
|
||||
if (!this.conditionManagerPool[id]) {
|
||||
this.conditionManagerPool[id] = new ConditionManager(domainObject, this.openmct);
|
||||
}
|
||||
|
||||
return this.conditionManagerPool[id];
|
||||
}
|
||||
|
||||
/**
|
||||
* cleans up and destroys conditionManager instance for corresponding domain object id
|
||||
* can be called manually for views that only request but do not subscribe to data
|
||||
*/
|
||||
destroyConditionManager(id) {
|
||||
if (this.conditionManagerPool[id]) {
|
||||
this.conditionManagerPool[id].off('conditionSetResultUpdated');
|
||||
this.conditionManagerPool[id].destroy();
|
||||
delete this.conditionManagerPool[id];
|
||||
}
|
||||
}
|
||||
}
|
33
src/plugins/condition/ConditionSetViewPolicy.js
Normal file
33
src/plugins/condition/ConditionSetViewPolicy.js
Normal file
@ -0,0 +1,33 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
function ConditionSetViewPolicy() {
|
||||
}
|
||||
|
||||
ConditionSetViewPolicy.prototype.allow = function (view, domainObject) {
|
||||
if (domainObject.getModel().type === 'conditionSet') {
|
||||
return view.key === 'conditionSet.view';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export default ConditionSetViewPolicy;
|
84
src/plugins/condition/ConditionSetViewProvider.js
Normal file
84
src/plugins/condition/ConditionSetViewProvider.js
Normal file
@ -0,0 +1,84 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import ConditionSet from './components/ConditionSet.vue';
|
||||
import Vue from 'vue';
|
||||
|
||||
const DEFAULT_VIEW_PRIORITY = 100;
|
||||
|
||||
export default class ConditionSetViewProvider {
|
||||
constructor(openmct) {
|
||||
this.openmct = openmct;
|
||||
this.name = 'Conditions View';
|
||||
this.key = 'conditionSet.view';
|
||||
this.cssClass = 'icon-conditional';
|
||||
}
|
||||
|
||||
canView(domainObject) {
|
||||
return domainObject.type === 'conditionSet';
|
||||
}
|
||||
|
||||
canEdit(domainObject) {
|
||||
return domainObject.type === 'conditionSet';
|
||||
}
|
||||
|
||||
view(domainObject, objectPath) {
|
||||
let component;
|
||||
const openmct = this.openmct;
|
||||
return {
|
||||
show: (container, isEditing) => {
|
||||
component = new Vue({
|
||||
el: container,
|
||||
components: {
|
||||
ConditionSet
|
||||
},
|
||||
provide: {
|
||||
openmct,
|
||||
domainObject,
|
||||
objectPath
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isEditing
|
||||
}
|
||||
},
|
||||
template: '<condition-set :isEditing="isEditing"></condition-set>'
|
||||
});
|
||||
},
|
||||
onEditModeChange: (isEditing) => {
|
||||
component.isEditing = isEditing;
|
||||
},
|
||||
destroy: () => {
|
||||
component.$destroy();
|
||||
component = undefined;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
priority(domainObject) {
|
||||
if (domainObject.type === 'conditionSet') {
|
||||
return Number.MAX_VALUE;
|
||||
} else {
|
||||
return DEFAULT_VIEW_PRIORITY;
|
||||
}
|
||||
}
|
||||
}
|
181
src/plugins/condition/ConditionSpec.js
Normal file
181
src/plugins/condition/ConditionSpec.js
Normal file
@ -0,0 +1,181 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import Condition from "./Condition";
|
||||
import {TRIGGER} from "./utils/constants";
|
||||
import TelemetryCriterion from "./criterion/TelemetryCriterion";
|
||||
|
||||
let openmct = {},
|
||||
testConditionDefinition,
|
||||
testTelemetryObject,
|
||||
conditionObj,
|
||||
conditionManager,
|
||||
mockTelemetryReceived,
|
||||
mockTimeSystems;
|
||||
|
||||
describe("The condition", function () {
|
||||
|
||||
beforeEach (() => {
|
||||
conditionManager = jasmine.createSpyObj('conditionManager',
|
||||
['on', 'updateConditionDescription']
|
||||
);
|
||||
mockTelemetryReceived = jasmine.createSpy('listener');
|
||||
conditionManager.on('telemetryReceived', mockTelemetryReceived);
|
||||
conditionManager.updateConditionDescription.and.returnValue(function () {});
|
||||
|
||||
testTelemetryObject = {
|
||||
identifier:{ namespace: "", key: "test-object"},
|
||||
type: "test-object",
|
||||
name: "Test Object",
|
||||
telemetry: {
|
||||
valueMetadatas: [{
|
||||
key: "some-key",
|
||||
name: "Some attribute",
|
||||
hints: {
|
||||
range: 2
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "utc",
|
||||
name: "Time",
|
||||
format: "utc",
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
}, {
|
||||
key: "testSource",
|
||||
source: "value",
|
||||
name: "Test",
|
||||
format: "string"
|
||||
}]
|
||||
}
|
||||
};
|
||||
conditionManager.telemetryObjects = {
|
||||
"test-object": testTelemetryObject
|
||||
};
|
||||
openmct.objects = jasmine.createSpyObj('objects', ['get', 'makeKeyString']);
|
||||
openmct.objects.get.and.returnValue(new Promise(function (resolve, reject) {
|
||||
resolve(testTelemetryObject);
|
||||
})); openmct.objects.makeKeyString.and.returnValue(testTelemetryObject.identifier.key);
|
||||
openmct.telemetry = jasmine.createSpyObj('telemetry', ['isTelemetryObject', 'subscribe', 'getMetadata']);
|
||||
openmct.telemetry.isTelemetryObject.and.returnValue(true);
|
||||
openmct.telemetry.subscribe.and.returnValue(function () {});
|
||||
openmct.telemetry.getMetadata.and.returnValue(testTelemetryObject.telemetry);
|
||||
|
||||
mockTimeSystems = {
|
||||
key: 'utc'
|
||||
};
|
||||
openmct.time = jasmine.createSpyObj('time', ['getAllTimeSystems']);
|
||||
openmct.time.getAllTimeSystems.and.returnValue([mockTimeSystems]);
|
||||
|
||||
testConditionDefinition = {
|
||||
id: '123-456',
|
||||
configuration: {
|
||||
name: 'mock condition',
|
||||
output: 'mock output',
|
||||
trigger: TRIGGER.ANY,
|
||||
criteria: [
|
||||
{
|
||||
id: '1234-5678-9999-0000',
|
||||
operation: 'equalTo',
|
||||
input: ['0'],
|
||||
metadata: 'value',
|
||||
telemetry: testTelemetryObject.identifier
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
conditionObj = new Condition(
|
||||
testConditionDefinition,
|
||||
openmct,
|
||||
conditionManager
|
||||
);
|
||||
});
|
||||
|
||||
it("generates criteria with the correct properties", function () {
|
||||
const testCriterion = testConditionDefinition.configuration.criteria[0];
|
||||
let criterion = conditionObj.generateCriterion(testCriterion);
|
||||
expect(criterion.id).toBeDefined();
|
||||
expect(criterion.operation).toEqual(testCriterion.operation);
|
||||
expect(criterion.input).toEqual(testCriterion.input);
|
||||
expect(criterion.metadata).toEqual(testCriterion.metadata);
|
||||
expect(criterion.telemetry).toEqual(testCriterion.telemetry);
|
||||
});
|
||||
|
||||
it("initializes with an id", function () {
|
||||
expect(conditionObj.id).toBeDefined();
|
||||
});
|
||||
|
||||
it("initializes with criteria from the condition definition", function () {
|
||||
expect(conditionObj.criteria.length).toEqual(1);
|
||||
let criterion = conditionObj.criteria[0];
|
||||
expect(criterion instanceof TelemetryCriterion).toBeTrue();
|
||||
expect(criterion.operator).toEqual(testConditionDefinition.configuration.criteria[0].operator);
|
||||
expect(criterion.input).toEqual(testConditionDefinition.configuration.criteria[0].input);
|
||||
expect(criterion.metadata).toEqual(testConditionDefinition.configuration.criteria[0].metadata);
|
||||
});
|
||||
|
||||
it("initializes with the trigger from the condition definition", function () {
|
||||
expect(conditionObj.trigger).toEqual(testConditionDefinition.configuration.trigger);
|
||||
});
|
||||
|
||||
it("destroys all criteria for a condition", function () {
|
||||
const result = conditionObj.destroyCriteria();
|
||||
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();
|
||||
});
|
||||
});
|
164
src/plugins/condition/StyleRuleManager.js
Normal file
164
src/plugins/condition/StyleRuleManager.js
Normal file
@ -0,0 +1,164 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT, Copyright (c) 2014-2020, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT is licensed under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*
|
||||
* Open MCT includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
|
||||
import EventEmitter from 'EventEmitter';
|
||||
|
||||
export default class StyleRuleManager extends EventEmitter {
|
||||
constructor(styleConfiguration, openmct, callback, suppressSubscriptionOnEdit) {
|
||||
super();
|
||||
this.openmct = openmct;
|
||||
this.callback = callback;
|
||||
if (suppressSubscriptionOnEdit) {
|
||||
this.openmct.editor.on('isEditing', this.toggleSubscription.bind(this));
|
||||
this.isEditing = this.openmct.editor.editing;
|
||||
}
|
||||
if (styleConfiguration) {
|
||||
this.initialize(styleConfiguration);
|
||||
if (styleConfiguration.conditionSetIdentifier) {
|
||||
this.subscribeToConditionSet();
|
||||
} else {
|
||||
this.applyStaticStyle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toggleSubscription(isEditing) {
|
||||
this.isEditing = isEditing;
|
||||
if (this.isEditing) {
|
||||
if (this.stopProvidingTelemetry) {
|
||||
this.stopProvidingTelemetry();
|
||||
delete 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 || []);
|
||||
}
|
||||
|
||||
subscribeToConditionSet() {
|
||||
if (this.stopProvidingTelemetry) {
|
||||
this.stopProvidingTelemetry();
|
||||
delete this.stopProvidingTelemetry;
|
||||
}
|
||||
this.openmct.objects.get(this.conditionSetIdentifier).then((conditionSetDomainObject) => {
|
||||
this.openmct.telemetry.request(conditionSetDomainObject)
|
||||
.then(output => {
|
||||
if (output && output.length) {
|
||||
this.handleConditionSetResultUpdated(output[0]);
|
||||
}
|
||||
});
|
||||
this.stopProvidingTelemetry = this.openmct.telemetry.subscribe(conditionSetDomainObject, this.handleConditionSetResultUpdated.bind(this));
|
||||
});
|
||||
}
|
||||
|
||||
updateObjectStyleConfig(styleConfiguration) {
|
||||
if (!styleConfiguration || !styleConfiguration.conditionSetIdentifier) {
|
||||
this.initialize(styleConfiguration || {});
|
||||
this.destroy();
|
||||
} else {
|
||||
let isNewConditionSet = !this.conditionSetIdentifier ||
|
||||
!this.openmct.objects.areIdsEqual(this.conditionSetIdentifier, styleConfiguration.conditionSetIdentifier);
|
||||
this.initialize(styleConfiguration);
|
||||
if (this.isEditing) {
|
||||
this.applySelectedConditionStyle();
|
||||
} else {
|
||||
//Only resubscribe if the conditionSet has changed.
|
||||
if (isNewConditionSet) {
|
||||
this.subscribeToConditionSet();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateConditionStylesMap(conditionStyles) {
|
||||
let conditionStyleMap = {};
|
||||
conditionStyles.forEach((conditionStyle) => {
|
||||
if (conditionStyle.conditionId) {
|
||||
conditionStyleMap[conditionStyle.conditionId] = conditionStyle.style;
|
||||
} else {
|
||||
conditionStyleMap.static = conditionStyle.style;
|
||||
}
|
||||
});
|
||||
this.conditionalStyleMap = conditionStyleMap;
|
||||
}
|
||||
|
||||
handleConditionSetResultUpdated(resultData) {
|
||||
let foundStyle = this.conditionalStyleMap[resultData.conditionId];
|
||||
if (foundStyle) {
|
||||
if (foundStyle !== this.currentStyle) {
|
||||
this.currentStyle = foundStyle;
|
||||
}
|
||||
this.updateDomainObjectStyle();
|
||||
} else {
|
||||
this.applyStaticStyle();
|
||||
}
|
||||
}
|
||||
|
||||
updateDomainObjectStyle() {
|
||||
if (this.callback) {
|
||||
this.callback(Object.assign({}, this.currentStyle));
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
} else {
|
||||
if (this.currentStyle) {
|
||||
Object.keys(this.currentStyle).forEach(key => {
|
||||
this.currentStyle[key] = '__no_value';
|
||||
});
|
||||
}
|
||||
}
|
||||
this.updateDomainObjectStyle();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.applyStaticStyle();
|
||||
if (this.stopProvidingTelemetry) {
|
||||
this.stopProvidingTelemetry();
|
||||
delete this.stopProvidingTelemetry;
|
||||
}
|
||||
this.conditionSetIdentifier = undefined;
|
||||
}
|
||||
|
||||
}
|
377
src/plugins/condition/components/Condition.vue
Normal file
377
src/plugins/condition/components/Condition.vue
Normal file
@ -0,0 +1,377 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<div class="c-condition-h"
|
||||
:class="{ 'is-drag-target': draggingOver }"
|
||||
@dragover.prevent
|
||||
@drop.prevent="dropCondition($event, conditionIndex)"
|
||||
@dragenter="dragEnter($event, conditionIndex)"
|
||||
@dragleave="dragLeave($event, conditionIndex)"
|
||||
>
|
||||
<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 -->
|
||||
<div class="c-condition__header">
|
||||
<span class="c-condition__drag-grippy c-grippy c-grippy--vertical-drag"
|
||||
title="Drag to reorder conditions"
|
||||
:class="[{ 'is-enabled': !condition.isDefault }, { 'hide-nice': condition.isDefault }]"
|
||||
:draggable="!condition.isDefault"
|
||||
@dragstart="dragStart"
|
||||
@dragend="dragEnd"
|
||||
></span>
|
||||
|
||||
<span class="c-condition__disclosure c-disclosure-triangle c-tree__item__view-control is-enabled"
|
||||
:class="{ 'c-disclosure-triangle--expanded': expanded }"
|
||||
@click="expanded = !expanded"
|
||||
></span>
|
||||
|
||||
<span class="c-condition__name">{{ condition.configuration.name }}</span>
|
||||
<span class="c-condition__summary">
|
||||
<template v-if="!condition.isDefault && !canEvaluateCriteria">
|
||||
Define criteria
|
||||
</template>
|
||||
<span v-else>
|
||||
<condition-description :show-label="false"
|
||||
:condition="condition"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<div class="c-condition__buttons">
|
||||
<button v-if="!condition.isDefault"
|
||||
class="c-click-icon c-condition__duplicate-button icon-duplicate"
|
||||
title="Duplicate this condition"
|
||||
@click="cloneCondition"
|
||||
></button>
|
||||
|
||||
<button v-if="!condition.isDefault"
|
||||
class="c-click-icon c-condition__delete-button icon-trash"
|
||||
title="Delete this condition"
|
||||
@click="removeCondition"
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="expanded"
|
||||
class="c-condition__definition c-cdef"
|
||||
>
|
||||
<span class="c-cdef__separator c-row-separator"></span>
|
||||
<span class="c-cdef__label">Condition Name</span>
|
||||
<span class="c-cdef__controls">
|
||||
<input v-model="condition.configuration.name"
|
||||
class="t-condition-input__name"
|
||||
type="text"
|
||||
@change="persist"
|
||||
>
|
||||
</span>
|
||||
|
||||
<span class="c-cdef__label">Output</span>
|
||||
<span class="c-cdef__controls">
|
||||
<span class="c-cdef__control">
|
||||
<select v-model="selectedOutputSelection"
|
||||
@change="setOutputValue"
|
||||
>
|
||||
<option v-for="option in outputOptions"
|
||||
:key="option"
|
||||
:value="option"
|
||||
>
|
||||
{{ initCap(option) }}
|
||||
</option>
|
||||
</select>
|
||||
</span>
|
||||
<span class="c-cdef__control">
|
||||
<input v-if="selectedOutputSelection === outputOptions[2]"
|
||||
v-model="condition.configuration.output"
|
||||
class="t-condition-name-input"
|
||||
type="text"
|
||||
@change="persist"
|
||||
>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<div v-if="!condition.isDefault"
|
||||
class="c-cdef__match-and-criteria"
|
||||
>
|
||||
<span class="c-cdef__separator c-row-separator"></span>
|
||||
<span class="c-cdef__label">Match</span>
|
||||
<span class="c-cdef__controls">
|
||||
<select v-model="condition.configuration.trigger"
|
||||
@change="persist"
|
||||
>
|
||||
<option v-for="option in triggers"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
> {{ option.label }}</option>
|
||||
</select>
|
||||
</span>
|
||||
|
||||
<template v-if="telemetry.length || condition.configuration.criteria.length">
|
||||
<div v-for="(criterion, index) in condition.configuration.criteria"
|
||||
:key="criterion.id"
|
||||
class="c-cdef__criteria"
|
||||
>
|
||||
<Criterion :telemetry="telemetry"
|
||||
:criterion="criterion"
|
||||
:index="index"
|
||||
:trigger="condition.configuration.trigger"
|
||||
:is-default="condition.configuration.criteria.length === 1"
|
||||
@persist="persist"
|
||||
/>
|
||||
<div class="c-cdef__criteria__buttons">
|
||||
<button class="c-click-icon c-cdef__criteria-duplicate-button icon-duplicate"
|
||||
title="Duplicate this criteria"
|
||||
@click="cloneCriterion(index)"
|
||||
></button>
|
||||
<button v-if="!(condition.configuration.criteria.length === 1)"
|
||||
class="c-click-icon c-cdef__criteria-duplicate-button icon-trash"
|
||||
title="Delete this criteria"
|
||||
@click="removeCriterion(index)"
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="c-cdef__separator c-row-separator"></div>
|
||||
<div class="c-cdef__controls"
|
||||
:disabled="!telemetry.length"
|
||||
>
|
||||
<button
|
||||
class="c-cdef__add-criteria-button c-button c-button--labeled icon-plus"
|
||||
@click="addCriteria"
|
||||
>
|
||||
<span class="c-button__label">Add Criteria</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else
|
||||
class="c-condition c-condition--browse"
|
||||
:class="{'is-current': condition.id === currentConditionId}"
|
||||
>
|
||||
<!-- Browse view -->
|
||||
<div class="c-condition__header">
|
||||
<span class="c-condition__name">
|
||||
{{ condition.configuration.name }}
|
||||
</span>
|
||||
<span class="c-condition__output">
|
||||
Output: {{ condition.configuration.output }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="c-condition__summary">
|
||||
<condition-description :show-label="false"
|
||||
:condition="condition"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Criterion from './Criterion.vue';
|
||||
import ConditionDescription from "./ConditionDescription.vue";
|
||||
import { TRIGGER, TRIGGER_LABEL } from "@/plugins/condition/utils/constants";
|
||||
import uuid from 'uuid';
|
||||
|
||||
export default {
|
||||
inject: ['openmct'],
|
||||
components: {
|
||||
Criterion,
|
||||
ConditionDescription
|
||||
},
|
||||
props: {
|
||||
currentConditionId: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
condition: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
conditionIndex: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
isEditing: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
telemetry: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => []
|
||||
},
|
||||
isDragging: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
moveIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentCriteria: this.currentCriteria,
|
||||
expanded: true,
|
||||
trigger: 'all',
|
||||
selectedOutputSelection: '',
|
||||
outputOptions: ['false', 'true', 'string'],
|
||||
criterionIndex: 0,
|
||||
draggingOver: false,
|
||||
isDefault: this.condition.isDefault
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
triggers() {
|
||||
const keys = Object.keys(TRIGGER);
|
||||
const triggerOptions = [];
|
||||
keys.forEach((trigger) => {
|
||||
triggerOptions.push({
|
||||
value: TRIGGER[trigger],
|
||||
label: `when ${TRIGGER_LABEL[TRIGGER[trigger]]}`
|
||||
});
|
||||
});
|
||||
return triggerOptions;
|
||||
},
|
||||
canEvaluateCriteria: function () {
|
||||
let criteria = this.condition.configuration.criteria;
|
||||
if (criteria.length) {
|
||||
let lastCriterion = criteria[criteria.length - 1];
|
||||
if (lastCriterion.telemetry &&
|
||||
lastCriterion.operation &&
|
||||
(lastCriterion.input.length ||
|
||||
lastCriterion.operation === 'isDefined' ||
|
||||
lastCriterion.operation === 'isUndefined')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
this.destroy();
|
||||
},
|
||||
mounted() {
|
||||
this.setOutputSelection();
|
||||
},
|
||||
methods: {
|
||||
setOutputSelection() {
|
||||
let conditionOutput = this.condition.configuration.output;
|
||||
if (conditionOutput) {
|
||||
if (conditionOutput !== 'false' && conditionOutput !== 'true') {
|
||||
this.selectedOutputSelection = 'string';
|
||||
} else {
|
||||
this.selectedOutputSelection = conditionOutput;
|
||||
}
|
||||
}
|
||||
},
|
||||
setOutputValue() {
|
||||
if (this.selectedOutputSelection === 'string') {
|
||||
this.condition.configuration.output = '';
|
||||
} else {
|
||||
this.condition.configuration.output = this.selectedOutputSelection;
|
||||
}
|
||||
this.persist();
|
||||
},
|
||||
addCriteria() {
|
||||
const criteriaObject = {
|
||||
id: uuid(),
|
||||
telemetry: '',
|
||||
operation: '',
|
||||
input: '',
|
||||
metadata: ''
|
||||
};
|
||||
this.condition.configuration.criteria.push(criteriaObject);
|
||||
},
|
||||
dragStart(e) {
|
||||
e.dataTransfer.setData('dragging', e.target); // required for FF to initiate drag
|
||||
e.dataTransfer.effectAllowed = "copyMove";
|
||||
e.dataTransfer.setDragImage(e.target.closest('.c-condition-h'), 0, 0);
|
||||
this.$emit('setMoveIndex', this.conditionIndex);
|
||||
},
|
||||
dragEnd(event) {
|
||||
this.dragStarted = false;
|
||||
event.dataTransfer.clearData();
|
||||
this.$emit('dragComplete');
|
||||
},
|
||||
dropCondition(event, targetIndex) {
|
||||
if (!this.isDragging) { return }
|
||||
if (targetIndex > this.moveIndex) { targetIndex-- } // for 'downward' move
|
||||
if (this.isValidTarget(targetIndex)) {
|
||||
this.dragElement = undefined;
|
||||
this.draggingOver = false;
|
||||
this.$emit('dropCondition', targetIndex);
|
||||
}
|
||||
},
|
||||
dragEnter(event, targetIndex) {
|
||||
if (!this.isDragging) { return }
|
||||
if (targetIndex > this.moveIndex) { targetIndex-- } // for 'downward' move
|
||||
if (this.isValidTarget(targetIndex)) {
|
||||
this.dragElement = event.target.parentElement;
|
||||
this.draggingOver = true;
|
||||
}
|
||||
},
|
||||
dragLeave(event) {
|
||||
if (event.target.parentElement === this.dragElement) {
|
||||
this.draggingOver = false;
|
||||
this.dragElement = undefined;
|
||||
}
|
||||
},
|
||||
isValidTarget(targetIndex) {
|
||||
return this.moveIndex !== targetIndex;
|
||||
},
|
||||
destroy() {
|
||||
},
|
||||
removeCondition(ev) {
|
||||
this.$emit('removeCondition', this.conditionIndex);
|
||||
},
|
||||
cloneCondition(ev) {
|
||||
this.$emit('cloneCondition', {
|
||||
condition: this.condition,
|
||||
index: this.conditionIndex
|
||||
});
|
||||
},
|
||||
removeCriterion(index) {
|
||||
this.condition.configuration.criteria.splice(index, 1);
|
||||
this.persist();
|
||||
},
|
||||
cloneCriterion(index) {
|
||||
const clonedCriterion = JSON.parse(JSON.stringify(this.condition.configuration.criteria[index]));
|
||||
clonedCriterion.id = uuid();
|
||||
this.condition.configuration.criteria.splice(index + 1, 0, clonedCriterion);
|
||||
this.persist();
|
||||
},
|
||||
persist() {
|
||||
this.$emit('updateCondition', {
|
||||
condition: this.condition,
|
||||
index: this.conditionIndex
|
||||
});
|
||||
},
|
||||
initCap(str) {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
233
src/plugins/condition/components/ConditionCollection.vue
Normal file
233
src/plugins/condition/components/ConditionCollection.vue
Normal file
@ -0,0 +1,233 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<section id="conditionCollection"
|
||||
:class="{ 'is-expanded': expanded }"
|
||||
>
|
||||
<div class="c-cs__header c-section__header">
|
||||
<span
|
||||
class="c-disclosure-triangle c-tree__item__view-control is-enabled"
|
||||
:class="{ 'c-disclosure-triangle--expanded': expanded }"
|
||||
@click="expanded = !expanded"
|
||||
></span>
|
||||
<div class="c-cs__header-label c-section__label">Conditions</div>
|
||||
</div>
|
||||
<div v-if="expanded"
|
||||
class="c-cs__content"
|
||||
>
|
||||
<div v-show="isEditing"
|
||||
class="hint"
|
||||
:class="{ 's-status-icon-warning-lo': !telemetryObjs.length }"
|
||||
>
|
||||
<template v-if="!telemetryObjs.length">Drag telemetry into this Condition Set to configure Conditions and add criteria.</template>
|
||||
<template v-else>The first condition to match is the one that is applied. Drag conditions to reorder.</template>
|
||||
</div>
|
||||
|
||||
<button
|
||||
v-show="isEditing"
|
||||
id="addCondition"
|
||||
class="c-button c-button--major icon-plus labeled"
|
||||
@click="addCondition"
|
||||
>
|
||||
<span class="c-cs-button__label">Add Condition</span>
|
||||
</button>
|
||||
|
||||
<div class="c-cs__conditions-h"
|
||||
:class="{ 'is-active-dragging': isDragging }"
|
||||
>
|
||||
<Condition v-for="(condition, index) in conditionCollection"
|
||||
:key="condition.id"
|
||||
:condition="condition"
|
||||
:current-condition-id="currentConditionId"
|
||||
:condition-index="index"
|
||||
:telemetry="telemetryObjs"
|
||||
:is-editing="isEditing"
|
||||
:move-index="moveIndex"
|
||||
:is-dragging="isDragging"
|
||||
@updateCondition="updateCondition"
|
||||
@removeCondition="removeCondition"
|
||||
@cloneCondition="cloneCondition"
|
||||
@setMoveIndex="setMoveIndex"
|
||||
@dragComplete="dragComplete"
|
||||
@dropCondition="dropCondition"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Condition from './Condition.vue';
|
||||
import ConditionManager from '../ConditionManager';
|
||||
|
||||
export default {
|
||||
inject: ['openmct', 'domainObject'],
|
||||
components: {
|
||||
Condition
|
||||
},
|
||||
props: {
|
||||
isEditing: Boolean,
|
||||
testData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => {
|
||||
return {
|
||||
applied: false,
|
||||
conditionTestInputs: []
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
expanded: true,
|
||||
conditionCollection: [],
|
||||
conditionResults: {},
|
||||
conditions: [],
|
||||
telemetryObjs: [],
|
||||
moveIndex: undefined,
|
||||
isDragging: false,
|
||||
defaultOutput: undefined,
|
||||
dragCounter: 0,
|
||||
currentConditionId: ''
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
defaultOutput(newOutput, oldOutput) {
|
||||
this.$emit('updateDefaultOutput', newOutput);
|
||||
},
|
||||
testData: {
|
||||
handler() {
|
||||
this.updateTestData();
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
this.composition.off('add', this.addTelemetryObject);
|
||||
this.composition.off('remove', this.removeTelemetryObject);
|
||||
if(this.conditionManager) {
|
||||
this.conditionManager.off('conditionSetResultUpdated', this.handleConditionSetResultUpdated);
|
||||
this.conditionManager.destroy();
|
||||
}
|
||||
if (this.stopObservingForChanges) {
|
||||
this.stopObservingForChanges();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.composition = this.openmct.composition.get(this.domainObject);
|
||||
this.composition.on('add', this.addTelemetryObject);
|
||||
this.composition.on('remove', this.removeTelemetryObject);
|
||||
this.composition.load();
|
||||
this.conditionCollection = this.domainObject.configuration.conditionCollection;
|
||||
this.observeForChanges();
|
||||
this.conditionManager = new ConditionManager(this.domainObject, this.openmct);
|
||||
this.conditionManager.on('conditionSetResultUpdated', this.handleConditionSetResultUpdated);
|
||||
this.updateDefaultCondition();
|
||||
},
|
||||
methods: {
|
||||
handleConditionSetResultUpdated(data) {
|
||||
this.currentConditionId = data.conditionId;
|
||||
this.$emit('conditionSetResultUpdated', data)
|
||||
},
|
||||
observeForChanges() {
|
||||
this.stopObservingForChanges = this.openmct.objects.observe(this.domainObject, 'configuration.conditionCollection', (newConditionCollection) => {
|
||||
//this forces children to re-render
|
||||
this.conditionCollection = newConditionCollection.map(condition => condition);
|
||||
this.updateDefaultCondition();
|
||||
});
|
||||
},
|
||||
updateDefaultCondition() {
|
||||
const defaultCondition = this.domainObject.configuration.conditionCollection
|
||||
.find(conditionConfiguration => conditionConfiguration.isDefault);
|
||||
this.defaultOutput = defaultCondition.configuration.output;
|
||||
},
|
||||
setMoveIndex(index) {
|
||||
this.moveIndex = index;
|
||||
this.isDragging = true;
|
||||
},
|
||||
dropCondition(targetIndex) {
|
||||
const oldIndexArr = Object.keys(this.conditionCollection);
|
||||
const move = function (arr, old_index, new_index) {
|
||||
while (old_index < 0) {
|
||||
old_index += arr.length;
|
||||
}
|
||||
while (new_index < 0) {
|
||||
new_index += arr.length;
|
||||
}
|
||||
if (new_index >= arr.length) {
|
||||
var k = new_index - arr.length;
|
||||
while ((k--) + 1) {
|
||||
arr.push(undefined);
|
||||
}
|
||||
}
|
||||
arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
|
||||
return arr;
|
||||
}
|
||||
const newIndexArr = move(oldIndexArr, this.moveIndex, targetIndex);
|
||||
const reorderPlan = [];
|
||||
|
||||
for (let i = 0; i < oldIndexArr.length; i++) {
|
||||
reorderPlan.push({oldIndex: Number(newIndexArr[i]), newIndex: i});
|
||||
}
|
||||
|
||||
this.reorder(reorderPlan);
|
||||
},
|
||||
dragComplete() {
|
||||
this.isDragging = false;
|
||||
},
|
||||
addTelemetryObject(domainObject) {
|
||||
this.telemetryObjs.push(domainObject);
|
||||
this.$emit('telemetryUpdated', this.telemetryObjs);
|
||||
},
|
||||
removeTelemetryObject(identifier) {
|
||||
let index = this.telemetryObjs.findIndex(obj => {
|
||||
let objId = this.openmct.objects.makeKeyString(obj.identifier);
|
||||
let id = this.openmct.objects.makeKeyString(identifier);
|
||||
return objId === id;
|
||||
});
|
||||
if (index > -1) {
|
||||
this.telemetryObjs.splice(index, 1);
|
||||
}
|
||||
},
|
||||
addCondition() {
|
||||
this.conditionManager.addCondition();
|
||||
},
|
||||
updateCondition(data) {
|
||||
this.conditionManager.updateCondition(data.condition, data.index);
|
||||
},
|
||||
removeCondition(index) {
|
||||
this.conditionManager.removeCondition(index);
|
||||
},
|
||||
reorder(reorderPlan) {
|
||||
this.conditionManager.reorderConditions(reorderPlan);
|
||||
},
|
||||
cloneCondition(data) {
|
||||
this.conditionManager.cloneCondition(data.condition, data.index);
|
||||
},
|
||||
updateTestData() {
|
||||
this.conditionManager.updateTestData(this.testData);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
68
src/plugins/condition/components/ConditionDescription.vue
Normal file
68
src/plugins/condition/components/ConditionDescription.vue
Normal file
@ -0,0 +1,68 @@
|
||||
/*****************************************************************************
|
||||
* 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.
|
||||
*****************************************************************************/
|
||||
|
||||
<template>
|
||||
<div class="c-style__condition-desc">
|
||||
<span v-if="showLabel && condition"
|
||||
class="c-style__condition-desc__name c-condition__name"
|
||||
>
|
||||
{{ condition.configuration.name }}
|
||||
</span>
|
||||
<span v-if="!condition.isDefault"
|
||||
class="c-style__condition-desc__text"
|
||||
>
|
||||
{{ description }}
|
||||
</span>
|
||||
<span v-else
|
||||
class="c-style__condition-desc__text"
|
||||
>
|
||||
Match if no other condition is matched
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'ConditionDescription',
|
||||
inject: [
|
||||
'openmct'
|
||||
],
|
||||
props: {
|
||||
showLabel: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
condition: {
|
||||
type: Object,
|
||||
default() {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
description() {
|
||||
return this.condition ? this.condition.summary : '';
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user